diff --git a/backend/src/routes/runtimeRoutes.js b/backend/src/routes/runtimeRoutes.js index 6824b5a..560be59 100644 --- a/backend/src/routes/runtimeRoutes.js +++ b/backend/src/routes/runtimeRoutes.js @@ -53,4 +53,17 @@ router.post( }) ); +router.post( + '/activities/clear-recent', + asyncHandler(async (req, res) => { + logger.info('post:runtime:activities:clear-recent', { reqId: req.reqId }); + const result = runtimeActivityService.clearRecent(); + res.json({ + ok: true, + removed: Number(result?.removed || 0), + snapshot: result?.snapshot || runtimeActivityService.getSnapshot() + }); + }) +); + module.exports = router; diff --git a/backend/src/services/runtimeActivityService.js b/backend/src/services/runtimeActivityService.js index 6e274b0..bdba99b 100644 --- a/backend/src/services/runtimeActivityService.js +++ b/backend/src/services/runtimeActivityService.js @@ -183,6 +183,19 @@ class RuntimeActivityService { return this.buildSnapshot(); } + clearRecent() { + const removed = this.recent.length; + if (removed === 0) { + return { removed: 0, snapshot: this.buildSnapshot() }; + } + this.recent = []; + this.broadcastSnapshot(); + return { + removed, + snapshot: this.buildSnapshot() + }; + } + setControls(activityId, handlers = {}) { const id = normalizeNumber(activityId); if (!id || !this.active.has(id)) { diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 4868a74..16d5dd9 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -250,6 +250,12 @@ export const api = { body: JSON.stringify(payload || {}) }); }, + clearRuntimeRecentActivities() { + return request('/runtime/activities/clear-recent', { + method: 'POST', + body: JSON.stringify({}) + }); + }, async analyzeDisc() { const result = await request('/pipeline/analyze', { method: 'POST' diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index e12d6e0..937a37d 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -562,6 +562,7 @@ export default function DashboardPage({ const [runtimeActivities, setRuntimeActivities] = useState(() => normalizeRuntimeActivitiesPayload(null)); const [runtimeLoading, setRuntimeLoading] = useState(false); const [runtimeActionBusyKeys, setRuntimeActionBusyKeys] = useState(() => new Set()); + const [runtimeRecentClearing, setRuntimeRecentClearing] = useState(false); const [jobsLoading, setJobsLoading] = useState(false); const [dashboardJobs, setDashboardJobs] = useState([]); const [expandedJobId, setExpandedJobId] = useState(undefined); @@ -1438,6 +1439,37 @@ export default function DashboardPage({ } }; + const handleClearRuntimeRecent = async () => { + if (runtimeRecentClearing || runtimeRecentItems.length === 0) { + return; + } + setRuntimeRecentClearing(true); + try { + const response = await api.clearRuntimeRecentActivities(); + if (response?.snapshot) { + setRuntimeActivities(normalizeRuntimeActivitiesPayload(response.snapshot)); + } else { + const fresh = await api.getRuntimeActivities(); + setRuntimeActivities(normalizeRuntimeActivitiesPayload(fresh)); + } + toastRef.current?.show({ + severity: 'success', + summary: 'Abgeschlossene Liste', + detail: `Einträge entfernt: ${Number(response?.removed || 0)}`, + life: 2200 + }); + } catch (error) { + toastRef.current?.show({ + severity: 'error', + summary: 'Liste leeren', + detail: error?.message || 'Leeren fehlgeschlagen.', + life: 3200 + }); + } finally { + setRuntimeRecentClearing(false); + } + }; + const runtimeActiveItems = Array.isArray(runtimeActivities?.active) ? runtimeActivities.active : []; const runtimeRecentItems = Array.isArray(runtimeActivities?.recent) ? runtimeActivities.recent.slice(0, 8) @@ -1783,20 +1815,33 @@ export default function DashboardPage({ -
+
0 ? 'warning' : 'success'} /> +
{runtimeLoading && runtimeActiveItems.length === 0 && runtimeRecentItems.length === 0 ? (

Aktivitäten werden geladen ...

) : ( -
-
+
+

Aktiv

{runtimeActiveItems.length === 0 ? ( - Keine laufenden Skript-/Ketten-/Cron-Ausführungen. + Keine laufenden Skript-/Ketten-/Cron-Ausführungen. ) : (
{runtimeActiveItems.map((item, index) => { @@ -1806,7 +1851,7 @@ export default function DashboardPage({ const cancelBusy = isRuntimeActionBusy(item?.id, 'cancel'); const nextStepBusy = isRuntimeActionBusy(item?.id, 'next-step'); return ( -
+
{item?.name || '-'}
@@ -1864,10 +1909,10 @@ export default function DashboardPage({ )}
-
+

Zuletzt abgeschlossen

{runtimeRecentItems.length === 0 ? ( - Keine abgeschlossenen Einträge vorhanden. + Keine abgeschlossenen Einträge vorhanden. ) : (
{runtimeRecentItems.map((item, index) => { diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index d5e8127..5443542 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2900,32 +2900,46 @@ body { .runtime-activity-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.9rem; + gap: 0.75rem; } .runtime-activity-col { - display: flex; - flex-direction: column; - gap: 0.6rem; + border: 1px solid var(--rip-border); + border-radius: 0.5rem; + padding: 0.55rem 0.6rem; + background: var(--rip-panel-soft); + display: grid; + align-content: start; + grid-auto-rows: min-content; + gap: 0.45rem; +} + +.runtime-activity-col h4 { + margin: 0; } .runtime-activity-list { display: flex; flex-direction: column; - gap: 0.55rem; + gap: 0.45rem; } .runtime-activity-item { - border: 1px solid var(--surface-border, #d8d3c6); - border-radius: 8px; - padding: 0.6rem 0.75rem; - background: var(--surface-card, #f8f6ef); - display: flex; - flex-direction: column; - gap: 0.25rem; + border: 1px dashed var(--rip-border); + border-radius: 0.45rem; + padding: 0.45rem 0.55rem; + background: var(--rip-panel); + display: grid; + gap: 0.2rem; +} + +.runtime-activity-item.running { + border-style: solid; } .runtime-activity-item.done { + border-style: dotted; + background: var(--rip-surface); opacity: 0.94; } @@ -2936,6 +2950,13 @@ body { gap: 0.5rem; } +.runtime-activity-head strong { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .runtime-activity-tags { display: flex; flex-wrap: wrap; @@ -2946,7 +2967,7 @@ body { display: flex; flex-wrap: wrap; gap: 0.45rem; - margin-top: 0.25rem; + margin-top: 0.15rem; } .runtime-activity-details {