rspace-online/modules/rschedule/landing.ts

262 lines
13 KiB
TypeScript

/**
* rSchedule landing page — persistent job scheduling for rSpace.
*/
export function renderLanding(): string {
return `
<!-- Hero -->
<div class="rl-hero">
<span class="rl-tagline" style="color:#f59e0b;background:rgba(245,158,11,0.1);border-color:rgba(245,158,11,0.2)">
Persistent Scheduling
</span>
<h1 class="rl-heading" style="background:linear-gradient(to right,#f59e0b,#f97316,#ef4444);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
Automate (you)rSpace,<br>on (you)rSchedule.
</h1>
<p class="rl-subtitle">
Cron-powered job scheduling with email, webhooks, calendar events, and backlog briefings &mdash; all managed from within rSpace.
</p>
<p class="rl-subtext">
rSchedule replaces system-level crontabs with an <span style="color:#f59e0b;font-weight:600">in-process, persistent scheduler</span>.
Jobs survive restarts, fire on a 60-second tick loop, and are fully configurable through the UI.
</p>
<div class="rl-cta-row">
<a href="#" class="rl-cta-primary" id="ml-primary"
style="background:linear-gradient(to right,#f59e0b,#f97316);color:#0b1120"
onclick="document.querySelector('.rl-hero').closest('[data-space]')?.getAttribute('data-space') ? window.location.href='/' + document.querySelector('.rl-hero').closest('[data-space]').getAttribute('data-space') + '/rschedule' : void 0; return false;">
Open Scheduler
</a>
<a href="#" class="rl-cta-primary" id="ml-automations"
style="background:linear-gradient(to right,#8b5cf6,#6366f1);color:#fff"
onclick="document.querySelector('.rl-hero').closest('[data-space]')?.getAttribute('data-space') ? window.location.href='/' + document.querySelector('.rl-hero').closest('[data-space]').getAttribute('data-space') + '/rschedule/reminders' : window.location.href='/demo/rschedule/reminders'; return false;">
Automation Canvas
</a>
<a href="#features" class="rl-cta-secondary">Learn More</a>
</div>
</div>
<!-- Features (4-card grid) -->
<section class="rl-section" id="features" style="border-top:none">
<div class="rl-container">
<div class="rl-grid-4">
<div class="rl-card rl-card--center" style="padding:2rem">
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);font-size:1.5rem">
<span style="font-size:1.5rem">&#9200;</span>
</div>
<h3>Cron Expressions</h3>
<p>Standard cron syntax with timezone support. Schedule anything from every minute to once a year.</p>
</div>
<div class="rl-card rl-card--center" style="padding:2rem">
<div class="rl-icon-box" style="background:rgba(249,115,22,0.12);font-size:1.5rem">
<span style="font-size:1.5rem">&#128231;</span>
</div>
<h3>Email Actions</h3>
<p>Send scheduled emails via SMTP &mdash; morning briefings, weekly digests, monthly audits.</p>
</div>
<div class="rl-card rl-card--center" style="padding:2rem">
<div class="rl-icon-box" style="background:rgba(239,68,68,0.12);font-size:1.5rem">
<span style="font-size:1.5rem">&#128279;</span>
</div>
<h3>Webhook Actions</h3>
<p>Fire HTTP requests on schedule &mdash; trigger builds, sync data, or ping external services.</p>
</div>
<div class="rl-card rl-card--center" style="padding:2rem">
<div class="rl-icon-box" style="background:rgba(52,211,153,0.12);font-size:1.5rem">
<span style="font-size:1.5rem">&#128203;</span>
</div>
<h3>Backlog Briefings</h3>
<p>Automated task digests from your Backlog &mdash; morning, weekly, and monthly summaries delivered by email.</p>
</div>
</div>
</div>
</section>
<!-- Your Automations -->
<section class="rl-section" id="automations">
<div class="rl-container">
<h2 style="text-align:center;font-size:1.5rem;margin-bottom:0.5rem;color:#e2e8f0">Your Automations</h2>
<p style="text-align:center;color:rgba(148,163,184,0.7);margin-bottom:2rem;font-size:0.9rem">
Visual workflows built on the automation canvas
</p>
<div id="rs-automations-list" style="min-height:120px">
<p style="text-align:center;color:rgba(148,163,184,0.5);padding:2rem 0">Loading automations&hellip;</p>
</div>
</div>
</section>
<script>
(function() {
var space = 'demo';
var el = document.querySelector('.rl-hero');
if (el) {
var ds = el.closest('[data-space]');
if (ds) space = ds.getAttribute('data-space') || 'demo';
}
var basePath = '/' + space + '/rschedule/';
var container = document.getElementById('rs-automations-list');
fetch(basePath + 'api/workflows')
.then(function(r) { return r.ok ? r.json() : { results: [] }; })
.then(function(data) {
var wfs = data.results || [];
if (wfs.length === 0) {
container.innerHTML =
'<div style="text-align:center;padding:2.5rem 1rem">' +
'<p style="color:rgba(148,163,184,0.6);margin-bottom:1.5rem">No automations yet.</p>' +
'<a href="' + basePath + 'reminders" ' +
'style="display:inline-block;padding:0.6rem 1.5rem;border-radius:8px;' +
'background:linear-gradient(to right,#8b5cf6,#6366f1);color:#fff;text-decoration:none;font-weight:600;font-size:0.9rem">' +
'+ Create your first automation</a>' +
'</div>';
return;
}
var viewMode = wfs.length > 6 ? 'list' : 'grid';
var html = '';
if (viewMode === 'grid') {
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem">';
for (var i = 0; i < wfs.length; i++) {
var w = wfs[i];
var nodeCount = (w.nodes || []).length;
var edgeCount = (w.edges || []).length;
var statusColor = w.enabled ? '#34d399' : '#64748b';
var statusLabel = w.enabled ? 'Active' : 'Disabled';
var lastRun = w.lastRunAt ? new Date(w.lastRunAt).toLocaleDateString() : 'Never';
var runStatusIcon = w.lastRunStatus === 'success' ? '&#10003;' : w.lastRunStatus === 'error' ? '&#10007;' : '&mdash;';
var runStatusColor = w.lastRunStatus === 'success' ? '#34d399' : w.lastRunStatus === 'error' ? '#ef4444' : '#64748b';
// Build a mini node-count summary
var triggers = 0, conditions = 0, actions = 0;
for (var n = 0; n < (w.nodes || []).length; n++) {
var t = (w.nodes[n].type || '');
if (t.indexOf('trigger') === 0) triggers++;
else if (t.indexOf('condition') === 0) conditions++;
else if (t.indexOf('action') === 0) actions++;
}
html += '<a href="' + basePath + 'reminders?wf=' + w.id + '" ' +
'style="display:block;text-decoration:none;border-radius:12px;' +
'background:rgba(30,41,59,0.6);border:1px solid rgba(148,163,184,0.12);' +
'padding:1.25rem;transition:border-color 0.2s,transform 0.15s;cursor:pointer" ' +
'onmouseover="this.style.borderColor=\'rgba(139,92,246,0.4)\';this.style.transform=\'translateY(-2px)\'" ' +
'onmouseout="this.style.borderColor=\'rgba(148,163,184,0.12)\';this.style.transform=\'none\'">' +
// Header row
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem">' +
'<span style="font-weight:600;color:#e2e8f0;font-size:0.95rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + (w.name || 'Untitled') + '</span>' +
'<span style="font-size:0.7rem;padding:2px 8px;border-radius:9999px;background:' + statusColor + '20;color:' + statusColor + ';font-weight:500;white-space:nowrap">' + statusLabel + '</span>' +
'</div>' +
// Node summary
'<div style="display:flex;gap:0.75rem;margin-bottom:0.6rem;font-size:0.8rem;color:rgba(148,163,184,0.7)">' +
(triggers ? '<span style="color:#60a5fa">' + triggers + ' trigger' + (triggers > 1 ? 's' : '') + '</span>' : '') +
(conditions ? '<span style="color:#fbbf24">' + conditions + ' condition' + (conditions > 1 ? 's' : '') + '</span>' : '') +
(actions ? '<span style="color:#34d399">' + actions + ' action' + (actions > 1 ? 's' : '') + '</span>' : '') +
(!nodeCount ? '<span>Empty workflow</span>' : '') +
'</div>' +
// Footer
'<div style="display:flex;align-items:center;justify-content:space-between;font-size:0.75rem;color:rgba(148,163,184,0.5)">' +
'<span>Runs: ' + (w.runCount || 0) + '</span>' +
'<span>Last: <span style="color:' + runStatusColor + '">' + runStatusIcon + '</span> ' + lastRun + '</span>' +
'</div>' +
'</a>';
}
html += '</div>';
} else {
// List view for many workflows
html += '<div style="display:flex;flex-direction:column;gap:0.5rem">';
for (var i = 0; i < wfs.length; i++) {
var w = wfs[i];
var nodeCount = (w.nodes || []).length;
var statusColor = w.enabled ? '#34d399' : '#64748b';
var statusLabel = w.enabled ? 'Active' : 'Disabled';
var lastRun = w.lastRunAt ? new Date(w.lastRunAt).toLocaleDateString() : 'Never';
var runStatusIcon = w.lastRunStatus === 'success' ? '&#10003;' : w.lastRunStatus === 'error' ? '&#10007;' : '&mdash;';
var runStatusColor = w.lastRunStatus === 'success' ? '#34d399' : w.lastRunStatus === 'error' ? '#ef4444' : '#64748b';
html += '<a href="' + basePath + 'reminders?wf=' + w.id + '" ' +
'style="display:flex;align-items:center;gap:1rem;text-decoration:none;border-radius:8px;' +
'background:rgba(30,41,59,0.4);border:1px solid rgba(148,163,184,0.08);' +
'padding:0.75rem 1rem;transition:border-color 0.2s" ' +
'onmouseover="this.style.borderColor=\'rgba(139,92,246,0.4)\'" ' +
'onmouseout="this.style.borderColor=\'rgba(148,163,184,0.08)\'">' +
'<span style="width:8px;height:8px;border-radius:50%;background:' + statusColor + ';flex-shrink:0"></span>' +
'<span style="flex:1;font-weight:500;color:#e2e8f0;font-size:0.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + (w.name || 'Untitled') + '</span>' +
'<span style="font-size:0.75rem;color:rgba(148,163,184,0.5)">' + nodeCount + ' nodes</span>' +
'<span style="font-size:0.75rem;color:rgba(148,163,184,0.5)">' + (w.runCount || 0) + ' runs</span>' +
'<span style="font-size:0.75rem;color:' + runStatusColor + '">' + runStatusIcon + ' ' + lastRun + '</span>' +
'</a>';
}
html += '</div>';
}
// Add "Open Canvas" link at the bottom
html += '<div style="text-align:center;margin-top:1.5rem">' +
'<a href="' + basePath + 'reminders" ' +
'style="color:#8b5cf6;text-decoration:none;font-size:0.9rem;font-weight:500">' +
'Open Automation Canvas &rarr;</a>' +
'</div>';
container.innerHTML = html;
})
.catch(function() {
container.innerHTML =
'<div style="text-align:center;padding:2rem">' +
'<p style="color:rgba(148,163,184,0.5)">Could not load automations.</p>' +
'<a href="' + basePath + 'reminders" ' +
'style="color:#8b5cf6;text-decoration:none;font-size:0.9rem">Open Automation Canvas &rarr;</a>' +
'</div>';
});
})();
</script>
<!-- How it works -->
<section class="rl-section">
<div class="rl-container">
<h2 style="text-align:center;font-size:1.5rem;margin-bottom:2rem;color:#e2e8f0">How it works</h2>
<div class="rl-grid-2">
<div class="rl-card" style="padding:2rem">
<h3 style="color:#f59e0b">Persistent Jobs</h3>
<p>Jobs are stored in Automerge documents &mdash; they survive container restarts and server reboots. No more lost crontabs.</p>
</div>
<div class="rl-card" style="padding:2rem">
<h3 style="color:#f97316">60-Second Tick Loop</h3>
<p>A lightweight in-process loop checks every 60 seconds for due jobs. No external scheduler process needed.</p>
</div>
</div>
</div>
</section>
<!-- Ecosystem integration -->
<section class="rl-section">
<div class="rl-container">
<h2 style="text-align:center;font-size:1.5rem;margin-bottom:2rem;color:#e2e8f0">Ecosystem Integration</h2>
<div class="rl-grid-3">
<div class="rl-card rl-card--center" style="padding:1.5rem">
<h3>rCal</h3>
<p>Create recurring calendar events automatically via the calendar-event action type.</p>
</div>
<div class="rl-card rl-card--center" style="padding:1.5rem">
<h3>rInbox</h3>
<p>Schedule email delivery through shared SMTP infrastructure.</p>
</div>
<div class="rl-card rl-card--center" style="padding:1.5rem">
<h3>Backlog</h3>
<p>Scan backlog tasks and generate automated priority briefings on any cadence.</p>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section class="rl-section" style="text-align:center;padding:4rem 0">
<h2 class="rl-heading" style="font-size:1.75rem;background:linear-gradient(to right,#f59e0b,#f97316);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
Stop managing crontabs. Start scheduling from rSpace.
</h2>
<p style="color:rgba(148,163,184,0.8);margin-top:1rem">
<a href="/" style="color:#f59e0b;text-decoration:none">&larr; Back to rSpace</a>
</p>
</section>
`;
}