// ========== BUNDLED WORKFLOWS ========== (function() { const WORKFLOW_DEFINITIONS = { 'auto-restart-on-crash': { name: 'Auto-Restart on Crash', icon: '🔄', description: 'Automatically restart a container when it goes down', trigger: 'container-down', actions: 'restart + notify' }, 'backup-before-update': { name: 'Backup Before Update', icon: '💾', description: 'Create a backup before any app update', trigger: 'pre-update', actions: 'backup + notify' }, 'health-check-on-interval': { name: 'Periodic Health Check', icon: '🏥', description: 'Run health checks every 15 minutes and alert if degraded', trigger: 'scheduled (15m)', actions: 'health-check + alert' }, 'disk-space-alert': { name: 'Disk Space Alert', icon: '⚠️', description: 'Alert when disk usage exceeds 80%', trigger: 'resource-alert', actions: 'notify' }, 'weekly-container-report': { name: 'Weekly Container Report', icon: '📊', description: 'Send a weekly summary of container status and resource usage', trigger: 'scheduled (weekly)', actions: 'collect metrics + report' } }; let isPremium = false; // === CHECK PREMIUM === async function checkPremium() { try { const resp = await fetch('/api/v1/license/feature/workflows'); const data = await resp.json(); isPremium = data.available; } catch { isPremium = false; } return isPremium; } // === RENDER WORKFLOW CARD === function renderWorkflowCard(workflow) { const def = WORKFLOW_DEFINITIONS[workflow.id] || { name: workflow.name || workflow.id, icon: '⚡', description: workflow.description || '', trigger: workflow.trigger || 'unknown', actions: workflow.actions ? workflow.actions.map(a => a.type).join(' + ') : '' }; const locked = !isPremium; const cardClass = locked ? 'workflow-card locked' : 'workflow-card'; return `
${def.icon} ${escapeHtml(def.name)}

${escapeHtml(def.description)}

⚡ Trigger: ${escapeHtml(def.trigger)} ▶ Actions: ${escapeHtml(def.actions)}
${locked ? `
🔒 Upgrade to Enable
` : ''}
`; } // === LOAD WORKFLOWS TAB === async function loadWorkflowsTab() { const container = document.getElementById('workflow-list-container'); if (!container) return; container.innerHTML = '
Loading workflows...
'; try { const resp = await fetch('/api/v1/workflows'); const data = await resp.json(); if (data.success && data.workflows) { if (data.workflows.length === 0) { container.innerHTML = '
No workflows configured
'; return; } let html = ''; for (const workflow of data.workflows) { html += renderWorkflowCard(workflow); } // Add non-premium banner if (!isPremium) { html += `
🔒 Upgrade to unlock automated workflows
`; } container.innerHTML = `
${html}
`; wireWorkflowEvents(); } else { container.innerHTML = '
⚠️Failed to load workflows
'; } } catch (error) { container.innerHTML = '
⚠️Error loading workflows: ' + escapeHtml(error.message) + '
'; } } // === LOAD HISTORY TAB === async function loadHistoryTab() { const container = document.getElementById('workflow-history-body'); if (!container) return; container.innerHTML = ' Loading history...'; try { const resp = await fetch('/api/v1/workflows/history?limit=100'); const data = await resp.json(); if (data.success && data.history && data.history.length > 0) { let html = ''; for (const entry of data.history) { const time = new Date(entry.timestamp).toLocaleString(); const duration = entry.duration ? entry.duration + 'ms' : '-'; const resultClass = entry.success ? 'result-success' : 'result-failure'; const resultText = entry.success ? '✓ Success' : '✗ Failed'; html += ` ${escapeHtml(time)} ${escapeHtml(entry.workflowName || entry.workflowId)} ${escapeHtml(entry.trigger || 'manual')} ${resultText} ${escapeHtml(duration)} `; } container.innerHTML = html; } else { container.innerHTML = 'No workflow history yet'; } } catch (error) { container.innerHTML = 'Error loading history'; } } // === WIRE WORKFLOW EVENTS === function wireWorkflowEvents() { // Toggle switches document.querySelectorAll('.workflow-toggle').forEach(toggle => { toggle.addEventListener('change', async function() { const workflowId = this.dataset.workflow; const enabled = this.checked; const endpoint = enabled ? 'enable' : 'disable'; try { const resp = await fetch(`/api/v1/workflows/${workflowId}/${endpoint}`, { method: 'POST' }); const data = await resp.json(); if (!data.success) { showNotification(`Failed to ${endpoint} workflow`, 'error', 3000); this.checked = !enabled; // revert } } catch (error) { showNotification(`Error: ${error.message}`, 'error', 3000); this.checked = !enabled; // revert } }); }); // Run Now buttons document.querySelectorAll('.btn-run-now').forEach(btn => { btn.addEventListener('click', async function() { const workflowId = this.dataset.workflow; const originalText = this.innerHTML; this.innerHTML = ''; this.disabled = true; try { const resp = await fetch(`/api/v1/workflows/${workflowId}/run`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ trigger: 'manual' }) }); const data = await resp.json(); if (data.success) { showNotification(`Workflow "${WORKFLOW_DEFINITIONS[workflowId]?.name || workflowId}" executed successfully`, 'success', 3000); } else { showNotification(`Workflow failed: ${data.error || 'Unknown error'}`, 'error', 4000); } } catch (error) { showNotification(`Error: ${error.message}`, 'error', 3000); } finally { this.innerHTML = originalText; this.disabled = false; } }); }); } // === TAB SWITCHING === function setupTabSwitching() { document.querySelectorAll('.workflows-tab-btn').forEach(btn => { btn.addEventListener('click', function() { const panelId = this.dataset.panel; // Update tab buttons document.querySelectorAll('.workflows-tab-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); // Update panels document.querySelectorAll('.workflow-panel').forEach(p => p.classList.remove('active')); document.getElementById(panelId)?.classList.add('active'); // Load data for the active panel if (panelId === 'workflows-list-panel') { loadWorkflowsTab(); } else if (panelId === 'workflows-history-panel') { loadHistoryTab(); } }); }); } // === INJECT MODAL HTML === injectModal('bundled-workflows-modal', `

⚡ Bundled Workflows

Loading workflows...
Time Workflow Trigger Result Duration
`); const modal = document.getElementById('bundled-workflows-modal'); const openBtn = document.getElementById('bundled-workflows-btn'); const cancelBtn = document.getElementById('workflows-cancel'); // === STYLES === const style = document.createElement('style'); style.textContent = ` .workflow-list { display: flex; flex-direction: column; gap: 12px; } .workflow-card { position: relative; padding: 16px; background: var(--card-base); border: 1px solid var(--border); border-radius: 10px; transition: all 0.2s ease; } .workflow-card:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .workflow-card.locked { opacity: 0.7; } .workflow-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .workflow-name { font-weight: 600; font-size: 0.95rem; } .workflow-desc { font-size: 0.82rem; color: var(--muted); margin: 0 0 10px 0; line-height: 1.4; } .workflow-meta { display: flex; flex-wrap: wrap; gap: 8px; font-size: 0.75rem; color: var(--muted); margin-bottom: 12px; } .workflow-meta span { padding: 2px 8px; background: var(--base); border-radius: 4px; } .workflow-locked-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); border-radius: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; } .lock-icon { font-size: 1.5rem; } .lock-text { font-size: 0.8rem; font-weight: 600; color: #f39c12; } .workflow-history { max-height: 400px; overflow-y: auto; } .workflow-history table { width: 100%; border-collapse: collapse; font-size: 0.82rem; } .workflow-history th { text-align: left; padding: 8px 10px; background: var(--base); border-bottom: 1px solid var(--border); font-weight: 600; position: sticky; top: 0; } .workflow-history td { padding: 8px 10px; border-bottom: 1px solid var(--border); } .result-success { color: var(--ok-fg); font-weight: 500; } .result-failure { color: var(--bad-fg); font-weight: 500; } .workflow-panel { display: none; } .workflow-panel.active { display: block; } .accent-info-box { padding: 12px 16px; background: color-mix(in srgb, var(--accent) 10%, transparent); border: 1px solid var(--accent); border-radius: 8px; margin-bottom: 12px; } `; document.head.appendChild(style); // === MODAL EVENTS === openBtn?.addEventListener('click', async function() { modal.classList.add('show'); await checkPremium(); loadWorkflowsTab(); }); setupTabSwitching(); // Close on cancel cancelBtn?.addEventListener('click', function() { modal.classList.remove('show'); }); // Wire modal escape key / click outside wireModal(modal, cancelBtn); })();