feat(ui): TDS migration stage 2 — six page views and page modules
- Dashboard, Workers, Workflows, Executions, Quick Command, Scheduler pages on the shared layout, all behaviour via Pulse delegated actions (no inline handlers) - Executions: server-driven Manual/Automated views, compare mode, detail modal with all log types, ?open= deep link, cross-page re-run via sessionStorage - Workflows: run modal (dry-run available for every workflow), create modal pre-filled with the example definition - Fix EJS comment in page-header partial that broke rendering Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HamVMDrA8RqhyxmUHgiqRp
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<%#
|
||||
Executions page (WP-E).
|
||||
|
||||
Owns DOM id prefix `ex-` and action namespace `ex:*`.
|
||||
All behaviour lives in /assets/pages/executions.js (auto-loaded by the layout);
|
||||
styling additions in /assets/pages/executions.css (auto-linked).
|
||||
|
||||
The page-header is written out inline rather than through the shared partial
|
||||
because the Clear Completed button is admin-only and the partial takes its
|
||||
actions as a pre-rendered string.
|
||||
%>
|
||||
<div class="lt-page-header">
|
||||
<div>
|
||||
<h1 class="lt-page-title">Executions</h1>
|
||||
<p class="lt-page-subtitle">Workflow and quick-command run history</p>
|
||||
</div>
|
||||
<div class="lt-page-actions">
|
||||
<div class="lt-btn-group">
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="ex:refresh">↻ Refresh</button>
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" id="ex-compare-toggle"
|
||||
data-action="ex:compare-toggle" aria-pressed="false">▤ Compare Mode</button>
|
||||
<button type="button" class="lt-btn lt-btn-primary lt-btn-sm is-hidden" id="ex-compare-run"
|
||||
data-action="ex:compare-run">⚖ Compare Selected</button>
|
||||
<% if (user && user.isAdmin) { %>
|
||||
<button type="button" class="lt-btn lt-btn-danger lt-btn-sm" data-action="ex:clear-completed">✖ Clear Completed</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-frame">
|
||||
<span class="lt-frame-bl"></span><span class="lt-frame-br"></span>
|
||||
|
||||
<!-- Manual / Automated sub-tabs. base.js initTabs wires the click/keyboard
|
||||
behaviour and persists lt_activeTab_/executions; the ex:view-tab action
|
||||
keeps pulse_executionView (the authoritative store) in sync. -->
|
||||
<div class="lt-tab-bar" role="tablist" aria-label="Execution view">
|
||||
<button type="button" class="lt-tab active" role="tab" id="ex-tabbtn-manual"
|
||||
aria-selected="true" aria-controls="ex-tab-manual"
|
||||
data-tab="ex-tab-manual" data-action="ex:view-tab" data-view="manual">
|
||||
👤 Manual Runs <span class="lt-badge lt-badge-sm" id="ex-count-manual"></span>
|
||||
</button>
|
||||
<button type="button" class="lt-tab" role="tab" id="ex-tabbtn-automated"
|
||||
aria-selected="false" aria-controls="ex-tab-automated"
|
||||
data-tab="ex-tab-automated" data-action="ex:view-tab" data-view="automated">
|
||||
🤖 Automated <span class="lt-badge lt-badge-sm" id="ex-count-automated"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="lt-alert lt-alert--warning is-hidden" id="ex-compare-hint">
|
||||
<span class="lt-alert-icon">▤</span>
|
||||
<div class="lt-alert-body">
|
||||
<div class="lt-alert-title">Compare mode</div>
|
||||
<div class="lt-alert-msg">Select 2–5 executions to compare their outputs. Click a row to toggle selection.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-toolbar">
|
||||
<div class="lt-toolbar-left">
|
||||
<div class="lt-search">
|
||||
<input type="search" id="ex-search" class="lt-input lt-search-input"
|
||||
placeholder="Search by command, execution ID, or workflow name…"
|
||||
autocomplete="off" aria-label="Search executions"
|
||||
data-input-action="ex:search">
|
||||
</div>
|
||||
<select id="ex-status" class="lt-select" aria-label="Status filter" data-change-action="ex:filter-status">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="running">Running</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="failed">Failed</option>
|
||||
<option value="waiting">Waiting</option>
|
||||
</select>
|
||||
<button type="button" class="lt-btn lt-btn-ghost lt-btn-sm" data-action="ex:clear-filters">Clear Filters</button>
|
||||
</div>
|
||||
<div class="lt-toolbar-right">
|
||||
<span id="ex-filter-stats" class="pulse-meta"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-tab-panels">
|
||||
<div class="lt-tab-panel active" id="ex-tab-manual" role="tabpanel" aria-labelledby="ex-tabbtn-manual" tabindex="-1">
|
||||
<div id="ex-list-manual" class="ex-list">
|
||||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-title">Loading…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-tab-panel" id="ex-tab-automated" role="tabpanel" aria-labelledby="ex-tabbtn-automated" tabindex="-1">
|
||||
<div id="ex-list-automated" class="ex-list">
|
||||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-title">Loading…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== EXECUTION DETAIL MODAL ===================== -->
|
||||
<div class="lt-modal-overlay" id="ex-detail-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="ex-detail-title">
|
||||
<div class="lt-modal lt-modal-lg">
|
||||
<div class="lt-modal-header">
|
||||
<span class="lt-modal-title" id="ex-detail-title">Execution Details</span>
|
||||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="lt-modal-body" id="ex-detail-body"></div>
|
||||
<div class="lt-modal-footer" id="ex-detail-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== EXECUTION COMPARE MODAL ==================== -->
|
||||
<div class="lt-modal-overlay" id="ex-compare-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="ex-compare-title">
|
||||
<div class="lt-modal lt-modal-lg">
|
||||
<div class="lt-modal-header">
|
||||
<span class="lt-modal-title" id="ex-compare-title">⚖ Execution Comparison</span>
|
||||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="lt-modal-body" id="ex-compare-body"></div>
|
||||
<div class="lt-modal-footer">
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-modal-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,89 @@
|
||||
<%#
|
||||
Quick Command page (WP-F). Ad-hoc command execution against one or more
|
||||
workers. All data loaded client-side by /assets/pages/quick.js.
|
||||
%>
|
||||
<%- include('../partials/page-header', {
|
||||
title: 'Quick Command',
|
||||
subtitle: 'Execute a command on selected workers instantly',
|
||||
actions: '<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="qc:templates-open">📋 Templates</button>' +
|
||||
'<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="qc:history-open">🕐 History</button>'
|
||||
}) %>
|
||||
|
||||
<div class="lt-frame">
|
||||
<form id="qc-form" data-submit-action="qc:execute">
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label">Execution Mode</label>
|
||||
<div class="qc-mode-row">
|
||||
<label class="qc-radio-label">
|
||||
<input type="radio" class="lt-radio" name="qc-exec-mode" id="qc-mode-single" value="single" checked data-change-action="qc:mode">
|
||||
<span>Single Worker</span>
|
||||
</label>
|
||||
<label class="qc-radio-label">
|
||||
<input type="radio" class="lt-radio" name="qc-exec-mode" id="qc-mode-multi" value="multi" data-change-action="qc:mode">
|
||||
<span>Multiple Workers</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-form-group" id="qc-single-mode">
|
||||
<label class="lt-label" for="qc-worker">Select Worker</label>
|
||||
<select class="lt-select" id="qc-worker">
|
||||
<option value="">Loading workers…</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="lt-form-group" id="qc-multi-mode" hidden>
|
||||
<label class="lt-label">Select Workers</label>
|
||||
<div id="qc-worker-list" class="qc-worker-list">
|
||||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-title">Loading…</div></div>
|
||||
</div>
|
||||
<div class="lt-btn-group qc-worker-actions">
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="qc:select-all">Select All</button>
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="qc:select-online">Online Only</button>
|
||||
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="qc:clear-all">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="qc-command">Command</label>
|
||||
<textarea class="lt-textarea" id="qc-command" rows="4" placeholder="Enter command to execute (e.g. 'uptime' or 'df -h')"></textarea>
|
||||
<div class="lt-field-hint">Ctrl+Enter to execute</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="lt-btn lt-btn-primary" id="qc-execute-btn">▶ Execute Command</button>
|
||||
</form>
|
||||
|
||||
<div id="qc-result" class="qc-result"></div>
|
||||
</div>
|
||||
|
||||
<!-- Command Templates Modal -->
|
||||
<div class="lt-modal-overlay" id="qc-templates-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="qc-templates-title">
|
||||
<div class="lt-modal lt-modal-lg">
|
||||
<div class="lt-modal-header">
|
||||
<span class="lt-modal-title" id="qc-templates-title">Command Templates</span>
|
||||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="lt-modal-body">
|
||||
<div id="qc-templates-list" class="qc-scroll-list"></div>
|
||||
</div>
|
||||
<div class="lt-modal-footer">
|
||||
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command History Modal -->
|
||||
<div class="lt-modal-overlay" id="qc-history-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="qc-history-title">
|
||||
<div class="lt-modal lt-modal-lg">
|
||||
<div class="lt-modal-header">
|
||||
<span class="lt-modal-title" id="qc-history-title">Command History</span>
|
||||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="lt-modal-body">
|
||||
<div id="qc-history-list" class="qc-scroll-list"></div>
|
||||
</div>
|
||||
<div class="lt-modal-footer">
|
||||
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<%#
|
||||
Scheduler page (WP-F). List of scheduled commands + admin create/toggle/delete.
|
||||
All data loaded client-side by /assets/pages/scheduler.js.
|
||||
%>
|
||||
<%- include('../partials/page-header', {
|
||||
title: 'Scheduler',
|
||||
subtitle: 'Automate command execution with flexible scheduling',
|
||||
actions: '<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="app:refresh">↻ Refresh</button>' +
|
||||
(pageConfig && pageConfig.isAdmin ? '<button type="button" class="lt-btn lt-btn-primary lt-btn-sm" data-action="sc:create-open">➕ Create Schedule</button>' : '')
|
||||
}) %>
|
||||
|
||||
<div class="lt-frame">
|
||||
<div id="sc-wrap">
|
||||
<div class="lt-empty-state" id="sc-loading">
|
||||
<div class="lt-empty-state-title">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Schedule Modal -->
|
||||
<div class="lt-modal-overlay" id="sc-create-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="sc-create-title">
|
||||
<div class="lt-modal">
|
||||
<div class="lt-modal-header">
|
||||
<span class="lt-modal-title" id="sc-create-title">Create Scheduled Command</span>
|
||||
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||
</div>
|
||||
<form id="sc-create-form" data-submit-action="sc:create-submit">
|
||||
<div class="lt-modal-body">
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="sc-name">Schedule Name</label>
|
||||
<input type="text" id="sc-name" class="lt-input" placeholder="e.g. Daily System Check">
|
||||
</div>
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="sc-command">Command</label>
|
||||
<textarea id="sc-command" class="lt-textarea" rows="3" placeholder="Enter command to execute"></textarea>
|
||||
</div>
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label">Target Workers</label>
|
||||
<div id="sc-worker-list" class="qc-worker-list">
|
||||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-title">Loading…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="sc-type">Schedule Type</label>
|
||||
<select id="sc-type" class="lt-select" data-change-action="sc:type">
|
||||
<option value="interval">Every X Minutes</option>
|
||||
<option value="hourly">Every X Hours</option>
|
||||
<option value="daily">Daily at Time</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="lt-form-group" id="sc-value-container">
|
||||
<label class="lt-label" for="sc-value">Interval (minutes)</label>
|
||||
<input type="number" id="sc-value" class="lt-input" placeholder="e.g. 30" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-modal-footer">
|
||||
<button type="submit" class="lt-btn lt-btn-primary">Create Schedule</button>
|
||||
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="wf-create-definition">Definition (JSON)</label>
|
||||
<textarea id="wf-create-definition" name="definition" class="lt-textarea wf-json-textarea" required></textarea>
|
||||
<span class="lt-field-hint">Steps run in order against the selected targets.</span>
|
||||
<span class="lt-form-hint">Steps run in order against the selected targets.</span>
|
||||
</div>
|
||||
<div class="lt-form-group">
|
||||
<label class="lt-label" for="wf-create-webhook">Webhook URL (optional)</label>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<%#
|
||||
Page header partial.
|
||||
Locals: title (string), subtitle (optional string), actions (optional raw HTML).
|
||||
Usage from a page view:
|
||||
<%- include('../partials/page-header', { title: 'Workers', subtitle: '', actions: '' }) %>
|
||||
Usage from a page view: include('../partials/page-header', { title, subtitle, actions })
|
||||
%>
|
||||
<div class="lt-page-header">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user