@flanksource/clicky-ui 0.3.52 → 0.3.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/clipboard.cjs +76 -11
- package/dist/components/clipboard.cjs.map +1 -1
- package/dist/components/clipboard.d.ts +37 -6
- package/dist/components/clipboard.d.ts.map +1 -1
- package/dist/components/clipboard.js +75 -12
- package/dist/components/clipboard.js.map +1 -1
- package/dist/components.cjs +2 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +2 -2
- package/dist/data/MatrixTreeTable.cjs +94 -0
- package/dist/data/MatrixTreeTable.cjs.map +1 -0
- package/dist/data/MatrixTreeTable.d.ts +40 -0
- package/dist/data/MatrixTreeTable.d.ts.map +1 -0
- package/dist/data/MatrixTreeTable.js +92 -0
- package/dist/data/MatrixTreeTable.js.map +1 -0
- package/dist/data/TaskManager.cjs +39 -32
- package/dist/data/TaskManager.cjs.map +1 -1
- package/dist/data/TaskManager.d.ts +6 -1
- package/dist/data/TaskManager.d.ts.map +1 -1
- package/dist/data/TaskManager.js +39 -32
- package/dist/data/TaskManager.js.map +1 -1
- package/dist/data/TreeDataTable.cjs +52 -0
- package/dist/data/TreeDataTable.cjs.map +1 -0
- package/dist/data/TreeDataTable.d.ts +17 -0
- package/dist/data/TreeDataTable.d.ts.map +1 -0
- package/dist/data/TreeDataTable.js +52 -0
- package/dist/data/TreeDataTable.js.map +1 -0
- package/dist/data/clicky-export/ClickyExportDialog.cjs +4 -3
- package/dist/data/clicky-export/ClickyExportDialog.cjs.map +1 -1
- package/dist/data/clicky-export/ClickyExportDialog.d.ts +4 -1
- package/dist/data/clicky-export/ClickyExportDialog.d.ts.map +1 -1
- package/dist/data/clicky-export/ClickyExportDialog.js +4 -3
- package/dist/data/clicky-export/ClickyExportDialog.js.map +1 -1
- package/dist/data/test-runner/MatrixMarks.cjs +212 -0
- package/dist/data/test-runner/MatrixMarks.cjs.map +1 -0
- package/dist/data/test-runner/MatrixMarks.d.ts +76 -0
- package/dist/data/test-runner/MatrixMarks.d.ts.map +1 -0
- package/dist/data/test-runner/MatrixMarks.js +205 -0
- package/dist/data/test-runner/MatrixMarks.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +3 -0
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +3 -0
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/index.d.ts +2 -0
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/matrix-marks.cjs +119 -0
- package/dist/data/test-runner/matrix-marks.cjs.map +1 -0
- package/dist/data/test-runner/matrix-marks.d.ts +154 -0
- package/dist/data/test-runner/matrix-marks.d.ts.map +1 -0
- package/dist/data/test-runner/matrix-marks.js +113 -0
- package/dist/data/test-runner/matrix-marks.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +27 -0
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +6 -1
- package/dist/hooks/use-task-run.cjs +22 -9
- package/dist/hooks/use-task-run.cjs.map +1 -1
- package/dist/hooks/use-task-run.d.ts +2 -0
- package/dist/hooks/use-task-run.d.ts.map +1 -1
- package/dist/hooks/use-task-run.js +22 -9
- package/dist/hooks/use-task-run.js.map +1 -1
- package/dist/index.cjs +29 -0
- package/dist/index.js +7 -2
- package/dist/lib/tree-rows.cjs +63 -0
- package/dist/lib/tree-rows.cjs.map +1 -0
- package/dist/lib/tree-rows.d.ts +36 -0
- package/dist/lib/tree-rows.d.ts.map +1 -0
- package/dist/lib/tree-rows.js +60 -0
- package/dist/lib/tree-rows.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { UiChevronDown } from "../icons/components/UiChevronDown.js";
|
|
3
|
+
import { UiChevronRight } from "../icons/components/UiChevronRight.js";
|
|
4
|
+
import { MatrixTable } from "./MatrixTable.js";
|
|
5
|
+
import { useTreeRows } from "../lib/tree-rows.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
// src/data/MatrixTreeTable.tsx
|
|
8
|
+
/** The chevron that opens or closes a tree row; `label` names the row for assistive tech. */
|
|
9
|
+
function TreeRowToggle({ open, label, onToggle }) {
|
|
10
|
+
const Icon = open ? UiChevronDown : UiChevronRight;
|
|
11
|
+
return /* @__PURE__ */ jsx("button", {
|
|
12
|
+
type: "button",
|
|
13
|
+
"aria-expanded": open,
|
|
14
|
+
"aria-label": `${open ? "Collapse" : "Expand"} ${label}`,
|
|
15
|
+
className: "inline-flex size-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground",
|
|
16
|
+
onClick: (event) => {
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
onToggle();
|
|
19
|
+
},
|
|
20
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
var TREE_STEP_REM = 1.25;
|
|
24
|
+
/**
|
|
25
|
+
* The indent and chevron that lead a tree row's label. Parents get a toggle
|
|
26
|
+
* (`onToggle` + `label`); leaves get a chevron-sized spacer so they line up.
|
|
27
|
+
*/
|
|
28
|
+
function TreeRowLead({ depth, open = false, label, onToggle, children }) {
|
|
29
|
+
if (onToggle && label === void 0) throw new Error("TreeRowLead needs a label to name its toggle");
|
|
30
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
31
|
+
"data-tree-depth": depth,
|
|
32
|
+
className: "inline-flex min-w-0 items-center gap-1",
|
|
33
|
+
style: { paddingLeft: `${depth * TREE_STEP_REM}rem` },
|
|
34
|
+
children: [onToggle ? /* @__PURE__ */ jsx(TreeRowToggle, {
|
|
35
|
+
open,
|
|
36
|
+
label,
|
|
37
|
+
onToggle
|
|
38
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
39
|
+
"aria-hidden": true,
|
|
40
|
+
className: "size-4 shrink-0"
|
|
41
|
+
}), children]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A MatrixTable whose rows form a tree: parent nodes are collapsible group
|
|
46
|
+
* bands that span every column, leaves are data rows, both indented by depth.
|
|
47
|
+
*/
|
|
48
|
+
function MatrixTreeTable({ roots, getChildren, getKey, renderLabel, getAriaLabel, getCells, getGroupClassName, expanded, onExpandedChange, defaultExpanded, ...table }) {
|
|
49
|
+
const { rows, toggle } = useTreeRows(roots, {
|
|
50
|
+
getChildren,
|
|
51
|
+
getKey
|
|
52
|
+
}, {
|
|
53
|
+
...expanded !== void 0 ? { expanded } : {},
|
|
54
|
+
...onExpandedChange ? { onExpandedChange } : {},
|
|
55
|
+
...defaultExpanded !== void 0 ? { defaultExpanded } : {}
|
|
56
|
+
});
|
|
57
|
+
const matrixRows = rows.map((row) => {
|
|
58
|
+
const label = renderLabel(row.node, {
|
|
59
|
+
depth: row.depth,
|
|
60
|
+
open: row.open,
|
|
61
|
+
hasChildren: row.hasChildren
|
|
62
|
+
});
|
|
63
|
+
if (!row.hasChildren) return {
|
|
64
|
+
key: row.key,
|
|
65
|
+
label: /* @__PURE__ */ jsx(TreeRowLead, {
|
|
66
|
+
depth: row.depth,
|
|
67
|
+
children: label
|
|
68
|
+
}),
|
|
69
|
+
cells: getCells(row.node)
|
|
70
|
+
};
|
|
71
|
+
const className = cn(row.depth > 0 && "bg-muted/30 font-medium", getGroupClassName?.(row.node, row.depth));
|
|
72
|
+
return {
|
|
73
|
+
type: "group",
|
|
74
|
+
key: row.key,
|
|
75
|
+
...className ? { className } : {},
|
|
76
|
+
label: /* @__PURE__ */ jsx(TreeRowLead, {
|
|
77
|
+
depth: row.depth,
|
|
78
|
+
open: row.open,
|
|
79
|
+
label: getAriaLabel(row.node),
|
|
80
|
+
onToggle: () => toggle(row),
|
|
81
|
+
children: label
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
return /* @__PURE__ */ jsx(MatrixTable, {
|
|
86
|
+
...table,
|
|
87
|
+
rows: matrixRows
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export { MatrixTreeTable, TreeRowLead, TreeRowToggle };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=MatrixTreeTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MatrixTreeTable.js","names":[],"sources":["../../src/data/MatrixTreeTable.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { UiChevronDown, UiChevronRight } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { useTreeRows, type TreeExpansionProps, type TreeRowsAccessors } from \"../lib/tree-rows\";\nimport { MatrixTable, type MatrixTableGroupRow, type MatrixTableProps, type MatrixTableRow } from \"./MatrixTable\";\n\nexport type TreeLabelContext = { depth: number; open: boolean; hasChildren: boolean };\n\nexport type MatrixTreeTableProps<T> = Omit<MatrixTableProps, \"rows\"> &\n TreeRowsAccessors<T> &\n TreeExpansionProps<T> & {\n roots: T[];\n renderLabel: (node: T, context: TreeLabelContext) => ReactNode;\n /** Names a parent node in its expand/collapse button. */\n getAriaLabel: (node: T) => string;\n /** Cells of a leaf row, one per column. Parent nodes render as group bands. */\n getCells: (node: T) => ReactNode[];\n getGroupClassName?: (node: T, depth: number) => string | undefined;\n };\n\n/** The chevron that opens or closes a tree row; `label` names the row for assistive tech. */\nexport function TreeRowToggle({ open, label, onToggle }: { open: boolean; label: string; onToggle: () => void }) {\n const Icon = open ? UiChevronDown : UiChevronRight;\n return (\n <button\n type=\"button\"\n aria-expanded={open}\n aria-label={`${open ? \"Collapse\" : \"Expand\"} ${label}`}\n className=\"inline-flex size-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground\"\n onClick={(event) => { event.stopPropagation(); onToggle(); }}\n >\n <Icon className=\"size-3.5\" />\n </button>\n );\n}\n\n// One tree level: the chevron (1rem) plus its gap (0.25rem), so a child's text\n// starts one step right of its parent's text.\nconst TREE_STEP_REM = 1.25;\n\n/**\n * The indent and chevron that lead a tree row's label. Parents get a toggle\n * (`onToggle` + `label`); leaves get a chevron-sized spacer so they line up.\n */\nexport function TreeRowLead({ depth, open = false, label, onToggle, children }: { depth: number; open?: boolean; label?: string; onToggle?: () => void; children: ReactNode }) {\n if (onToggle && label === undefined) throw new Error(\"TreeRowLead needs a label to name its toggle\");\n return (\n <span data-tree-depth={depth} className=\"inline-flex min-w-0 items-center gap-1\" style={{ paddingLeft: `${depth * TREE_STEP_REM}rem` }}>\n {onToggle ? <TreeRowToggle open={open} label={label!} onToggle={onToggle} /> : <span aria-hidden className=\"size-4 shrink-0\" />}\n {children}\n </span>\n );\n}\n\n/**\n * A MatrixTable whose rows form a tree: parent nodes are collapsible group\n * bands that span every column, leaves are data rows, both indented by depth.\n */\nexport function MatrixTreeTable<T>({\n roots,\n getChildren,\n getKey,\n renderLabel,\n getAriaLabel,\n getCells,\n getGroupClassName,\n expanded,\n onExpandedChange,\n defaultExpanded,\n ...table\n}: MatrixTreeTableProps<T>) {\n const { rows, toggle } = useTreeRows(roots, { getChildren, getKey }, {\n ...(expanded !== undefined ? { expanded } : {}),\n ...(onExpandedChange ? { onExpandedChange } : {}),\n ...(defaultExpanded !== undefined ? { defaultExpanded } : {}),\n });\n const matrixRows = rows.map((row): MatrixTableRow | MatrixTableGroupRow => {\n const label = renderLabel(row.node, { depth: row.depth, open: row.open, hasChildren: row.hasChildren });\n if (!row.hasChildren) return { key: row.key, label: <TreeRowLead depth={row.depth}>{label}</TreeRowLead>, cells: getCells(row.node) };\n // MatrixTable's depth would indent a second time, so nested bands get its softer style here.\n const className = cn(row.depth > 0 && \"bg-muted/30 font-medium\", getGroupClassName?.(row.node, row.depth));\n return {\n type: \"group\",\n key: row.key,\n ...(className ? { className } : {}),\n label: <TreeRowLead depth={row.depth} open={row.open} label={getAriaLabel(row.node)} onToggle={() => toggle(row)}>{label}</TreeRowLead>,\n };\n });\n return <MatrixTable {...table} rows={matrixRows} />;\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,cAAc,EAAE,MAAM,OAAO,YAAoE;CAC/G,MAAM,OAAO,OAAO,gBAAgB;CACpC,OACE,oBAAC,UAAD;EACE,MAAK;EACL,iBAAe;EACf,cAAY,GAAG,OAAO,aAAa,SAAS,GAAG;EAC/C,WAAU;EACV,UAAU,UAAU;GAAE,MAAM,gBAAgB;GAAG,SAAS;EAAG;EAE3D,UAAA,oBAAC,MAAD,EAAM,WAAU,WAAY,CAAA;CACtB,CAAA;AAEZ;AAIA,IAAM,gBAAgB;;;;;AAMtB,SAAgB,YAAY,EAAE,OAAO,OAAO,OAAO,OAAO,UAAU,YAA2G;CAC7K,IAAI,YAAY,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,8CAA8C;CACnG,OACE,qBAAC,QAAD;EAAM,mBAAiB;EAAO,WAAU;EAAyC,OAAO,EAAE,aAAa,GAAG,QAAQ,cAAc,KAAK;EAArI,UAAA,CACG,WAAW,oBAAC,eAAD;GAAqB;GAAa;GAAkB;EAAW,CAAA,IAAI,oBAAC,QAAD;GAAM,eAAA;GAAY,WAAU;EAAmB,CAAA,GAC7H,QACG;;AAEV;;;;;AAMA,SAAgB,gBAAmB,EACjC,OACA,aACA,QACA,aACA,cACA,UACA,mBACA,UACA,kBACA,iBACA,GAAG,SACuB;CAC1B,MAAM,EAAE,MAAM,WAAW,YAAY,OAAO;EAAE;EAAa;CAAO,GAAG;EACnE,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;EAC/C,GAAI,oBAAoB,KAAA,IAAY,EAAE,gBAAgB,IAAI,CAAC;CAC7D,CAAC;CACD,MAAM,aAAa,KAAK,KAAK,QAA8C;EACzE,MAAM,QAAQ,YAAY,IAAI,MAAM;GAAE,OAAO,IAAI;GAAO,MAAM,IAAI;GAAM,aAAa,IAAI;EAAY,CAAC;EACtG,IAAI,CAAC,IAAI,aAAa,OAAO;GAAE,KAAK,IAAI;GAAK,OAAO,oBAAC,aAAD;IAAa,OAAO,IAAI;IAAQ,UAAA;GAAmB,CAAA;GAAG,OAAO,SAAS,IAAI,IAAI;EAAE;EAEpI,MAAM,YAAY,GAAG,IAAI,QAAQ,KAAK,2BAA2B,oBAAoB,IAAI,MAAM,IAAI,KAAK,CAAC;EACzG,OAAO;GACL,MAAM;GACN,KAAK,IAAI;GACT,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;GACjC,OAAO,oBAAC,aAAD;IAAa,OAAO,IAAI;IAAO,MAAM,IAAI;IAAM,OAAO,aAAa,IAAI,IAAI;IAAG,gBAAgB,OAAO,GAAG;IAAI,UAAA;GAAmB,CAAA;EACxI;CACF,CAAC;CACD,OAAO,oBAAC,aAAD;EAAa,GAAI;EAAO,MAAM;CAAa,CAAA;AACpD"}
|
|
@@ -110,7 +110,7 @@ var compactRunColumns = [
|
|
|
110
110
|
progressColumn,
|
|
111
111
|
startedColumn
|
|
112
112
|
];
|
|
113
|
-
function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra }) {
|
|
113
|
+
function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra, renderRunDetails, controlUrl, showTaskControls }) {
|
|
114
114
|
const [kindFilter, setKindFilter] = (0, react.useState)(kind ?? "");
|
|
115
115
|
const [statusFilter, setStatusFilter] = (0, react.useState)("");
|
|
116
116
|
const [labelFilter, setLabelFilter] = (0, react.useState)("");
|
|
@@ -263,7 +263,10 @@ function TaskManager({ basePath, kind, labels, pollMs, className, compact, selec
|
|
|
263
263
|
pollMs,
|
|
264
264
|
runsQueryKey,
|
|
265
265
|
extraTabs,
|
|
266
|
-
headerExtra
|
|
266
|
+
headerExtra,
|
|
267
|
+
renderRunDetails,
|
|
268
|
+
controlUrl,
|
|
269
|
+
showTaskControls
|
|
267
270
|
})
|
|
268
271
|
});
|
|
269
272
|
}
|
|
@@ -287,7 +290,7 @@ function applicationLabel(kind) {
|
|
|
287
290
|
other: "Other"
|
|
288
291
|
}[kind] ?? kind;
|
|
289
292
|
}
|
|
290
|
-
function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExtra }) {
|
|
293
|
+
function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExtra, renderRunDetails, controlUrl, showTaskControls }) {
|
|
291
294
|
const runId = run.id;
|
|
292
295
|
const { snapshots, status } = require_use_task_run.useTaskRun({
|
|
293
296
|
id: runId,
|
|
@@ -342,45 +345,49 @@ function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExt
|
|
|
342
345
|
return mutation;
|
|
343
346
|
}, [controlMutation.mutateAsync, controlMutation.reset]);
|
|
344
347
|
const control = (action, group) => mutateControl({
|
|
345
|
-
url: `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,
|
|
348
|
+
url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,
|
|
346
349
|
action,
|
|
347
350
|
target: group
|
|
348
351
|
});
|
|
349
352
|
const controlTask = (action, task) => mutateControl({
|
|
350
|
-
url: `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,
|
|
353
|
+
url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,
|
|
351
354
|
action,
|
|
352
355
|
target: task
|
|
353
356
|
});
|
|
354
357
|
const progress = require_task_work_progress.taskWorkProgress(run);
|
|
355
358
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
356
359
|
className: "space-y-3 bg-muted/20 px-4 py-4",
|
|
357
|
-
children: [
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
360
|
+
children: [
|
|
361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
|
+
className: "flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground",
|
|
363
|
+
children: [
|
|
364
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [progress.completed, " completed"] }),
|
|
365
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [progress.running, " running"] }),
|
|
366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [progress.failed, " failed"] }),
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [progress.counts.pending + progress.unstarted, " waiting"] }),
|
|
368
|
+
Object.entries(run.labels ?? {}).map(([key, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
369
|
+
className: "font-mono",
|
|
370
|
+
children: [
|
|
371
|
+
key,
|
|
372
|
+
"=",
|
|
373
|
+
value
|
|
374
|
+
]
|
|
375
|
+
}, key))
|
|
376
|
+
]
|
|
377
|
+
}),
|
|
378
|
+
renderRunDetails?.(run, snapshots),
|
|
379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_TaskProgress.TaskProgress, {
|
|
380
|
+
snapshots,
|
|
381
|
+
compact: true,
|
|
382
|
+
connectionStatus: status,
|
|
383
|
+
initialShowAllTasks: true,
|
|
384
|
+
onControl: control,
|
|
385
|
+
...showTaskControls?.(run) !== false ? { onTaskControl: controlTask } : {},
|
|
386
|
+
metricsBaseUrl: `${apiBase}/tasks/metrics/`,
|
|
387
|
+
...extraTabs ? { extraTabs } : {},
|
|
388
|
+
...headerExtra ? { headerExtra } : {}
|
|
389
|
+
})
|
|
390
|
+
]
|
|
384
391
|
});
|
|
385
392
|
}
|
|
386
393
|
async function postTaskControl({ url, action, target }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskManager.cjs","names":["taskWorkProgress","ProgressBar","taskSegments","cn","taskStatusColor","Icon","taskStatusIcon","useState","useQueryClient","useMemo","splitTagToken","useTaskRuns","taskQueryKeys","hashKey","DataTable","useResourceClock","formatDuration","useTaskRun","useRef","useMutation","useCallback","TaskProgress"],"sources":["../../src/data/TaskManager.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { hashKey, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type { QueryKey } from \"@tanstack/react-query\";\nimport { cn } from \"../lib/utils\";\nimport { formatDuration } from \"../lib/format\";\nimport { useResourceClock } from \"../hooks/use-resource-clock\";\nimport { useTaskRun, useTaskRuns } from \"../hooks/use-task-run\";\nimport type { FilterBarFilter } from \"../components/FilterBar\";\nimport { Icon } from \"./Icon\";\nimport { DataTable, type DataTableColumn } from \"./DataTable\";\nimport { splitTagToken } from \"./cells/tag-utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProgress } from \"./TaskProgress\";\nimport { taskWorkProgress } from \"./task-work-progress\";\nimport type { TaskControlAction, TaskRunMeta, TaskSnapshot } from \"./TaskSnapshot\";\nimport type { TaskHeaderExtra } from \"./TaskHeaderExtras\";\nimport type { TaskExtraTabs } from \"./TaskStreamTabs\";\nimport { taskQueryKeys } from \"./task-query-keys\";\nimport { taskSegments, taskStatusColor, taskStatusIcon } from \"./task-status\";\n\n// TaskManager is the generic clicky-ui task-manager view: it lists every run\n// (TaskRunMeta) from GET {basePath}/tasks with kind/status filters, and expands\n// each row into a live <TaskProgress> for that run id.\n//\n// Requires a react-query QueryClientProvider in the host app (like\n// TimeseriesPanel and CacheBrowser): control actions run through useMutation,\n// and the listing is mirrored into the query cache so sibling views can read it.\n\nexport interface TaskManagerProps {\n /** Base path of the clicky task API, e.g. \"/api/v1\". */\n basePath?: string;\n /** Restrict the listing to a single kind (also hides the kind filter). */\n kind?: string;\n /** Restrict the listing to runs whose labels contain every key/value pair. */\n labels?: Record<string, string>;\n pollMs?: number;\n className?: string;\n /** Keep the run columns usable in a narrow panel. */\n compact?: boolean;\n /**\n * Currently-selected (expanded) run id, e.g. driven from the URL. When set,\n * the matching run renders expanded and selection is controlled — otherwise\n * each row manages its own expand/collapse state.\n */\n selectedId?: string;\n /**\n * Called when a run row is toggled: the new selection id, or null when the\n * open row is collapsed. Pair with selectedId for URL-driven deep links.\n */\n onSelectRun?: (id: string | null) => void;\n /**\n * Contributes host-owned output panes to each task, alongside its own\n * stdout/stderr — an agent's session transcript, say.\n */\n extraTabs?: TaskExtraTabs;\n /**\n * Replaces the content beside a task's name in its header row. Returning null\n * for a task falls back to the default metadata chips.\n */\n headerExtra?: TaskHeaderExtra;\n}\n\ntype RunTableRow = Record<string, unknown> & {\n id: string;\n name: string;\n kind: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n labels?: Record<string, string>;\n run: TaskRunMeta;\n};\n\nconst progressColumn: DataTableColumn<RunTableRow> = {\n key: \"progress\", label: \"Progress\", minWidth: 180,\n accessor: (row) => taskWorkProgress(row.run).done,\n render: (_, row) => {\n const progress = taskWorkProgress(row.run);\n return (\n <div className=\"min-w-36 space-y-1\">\n <span className=\"text-xs tabular-nums text-muted-foreground\">{progress.label}</span>\n {progress.total > 0 && <ProgressBar segments={taskSegments(progress.counts)} total={progress.total} height=\"h-1.5\" />}\n </div>\n );\n },\n};\nconst startedColumn: DataTableColumn<RunTableRow> = { key: \"startedAt\", label: \"Started\", kind: \"timestamp\", sortable: true };\nconst durationColumn: DataTableColumn<RunTableRow> = {\n key: \"duration\", label: \"Duration\", sortable: true,\n accessor: (row) => row.startedAt ? Date.parse(row.finishedAt ?? new Date().toISOString()) - Date.parse(row.startedAt) : null,\n render: (_, row) => <RunDuration startedAt={row.startedAt} finishedAt={row.finishedAt} />,\n};\nconst runColumns: DataTableColumn<RunTableRow>[] = [\n { key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220 },\n {\n key: \"status\", label: \"Status\", sortable: true,\n render: (_, row) => (\n <span className={cn(\"inline-flex items-center gap-1.5 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} {...(row.status === \"running\" ? { className: \"animate-spin\" } : {})} />\n {row.status}\n </span>\n ),\n },\n durationColumn,\n progressColumn,\n { key: \"labels\", label: \"Labels\", kind: \"tags\", tags: { maxVisible: 2 }, minWidth: 160 },\n startedColumn,\n { key: \"finishedAt\", label: \"Finished\", kind: \"timestamp\", sortable: true },\n];\nconst compactRunColumns: DataTableColumn<RunTableRow>[] = [\n {\n key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220,\n render: (_, row) => (\n <div className=\"space-y-0.5\">\n <div className=\"font-medium\">{row.name}</div>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{applicationLabel(row.kind)}</span>\n <span className={cn(\"inline-flex items-center gap-1 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} />{row.status}\n </span>\n </div>\n </div>\n ),\n },\n progressColumn,\n startedColumn,\n];\n\nexport function TaskManager({\n basePath,\n kind,\n labels,\n pollMs,\n className,\n compact,\n selectedId,\n onSelectRun,\n extraTabs,\n headerExtra,\n}: TaskManagerProps) {\n const [kindFilter, setKindFilter] = useState<string>(kind ?? \"\");\n const [statusFilter, setStatusFilter] = useState<string>(\"\");\n const [labelFilter, setLabelFilter] = useState(\"\");\n const [filterOptions, setFilterOptions] = useState({ kinds: [] as string[], labels: [] as string[] });\n const [localSelectedId, setLocalSelectedId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const apiBase = basePath ?? \"/api/v1\";\n const activeKind = kind ?? (kindFilter || undefined);\n const activeLabels = useMemo(() => {\n if (!labelFilter) return labels;\n const selected = splitTagToken(labelFilter);\n return { ...labels, [selected.key]: selected.value };\n }, [labels, labelFilter]);\n const { runs, runsKey, status } = useTaskRuns({\n basePath: apiBase,\n kind: activeKind,\n labels: activeLabels,\n status: statusFilter || undefined,\n pollMs,\n });\n const runsQueryKey = useMemo(\n () => taskQueryKeys.runs({\n basePath: apiBase,\n kind: activeKind,\n status: statusFilter || undefined,\n labels: activeLabels,\n }),\n [activeKind, apiBase, activeLabels, statusFilter],\n );\n\n // Changing a filter re-subscribes the transport but leaves the previous\n // listing on screen until the next SSE/poll frame. Publishing that listing\n // under the new key would hand cache consumers rows that do not match their\n // filters, so mirror only once the delivered listing's own key agrees.\n useEffect(() => {\n if (!runsKey || hashKey(runsKey) !== hashKey(runsQueryKey)) return;\n queryClient.setQueryData(runsQueryKey, runs);\n }, [queryClient, runs, runsKey, runsQueryKey]);\n\n useEffect(() => {\n setFilterOptions((previous) => {\n const kinds = [...new Set([...previous.kinds, ...runs.flatMap((run) => run.kind ? [run.kind] : [])])].sort();\n const labels = [...new Set([...previous.labels, ...runs.flatMap((run) => Object.entries(run.labels ?? {}).map(([key, value]) => `${key}=${value}`))])].sort();\n if (kinds.length === previous.kinds.length && labels.length === previous.labels.length) return previous;\n return { kinds, labels };\n });\n }, [runs]);\n const filters: FilterBarFilter[] = [\n ...(!kind ? [{ key: \"kind\", kind: \"enum\" as const, label: \"Kind\", placeholder: \"All kinds\", value: kindFilter, options: filterOptions.kinds.map((value) => ({ value, label: applicationLabel(value) })), onChange: setKindFilter }] : []),\n { key: \"status\", kind: \"enum\", label: \"Status\", placeholder: \"Any status\", value: statusFilter, options: [\n { value: \"running\", label: \"Running\" },\n { value: \"pending\", label: \"Pending\" },\n { value: \"success\", label: \"Success\" },\n { value: \"failed\", label: \"Failed\" },\n { value: \"warning\", label: \"Warning\" },\n { value: \"canceled\", label: \"Canceled\" },\n ], onChange: setStatusFilter },\n { key: \"labels\", kind: \"enum\", label: \"Labels\", placeholder: \"Any label\", value: labelFilter,\n options: filterOptions.labels.filter((token) => !(splitTagToken(token).key in (labels ?? {}))).map((value) => ({ value })),\n onChange: setLabelFilter },\n ];\n const rows = useMemo<RunTableRow[]>(() => runs.map((run) => ({\n id: run.id,\n name: run.name,\n kind: run.kind || \"other\",\n status: run.status,\n ...(run.startedAt ? { startedAt: run.startedAt } : {}),\n ...(run.finishedAt ? { finishedAt: run.finishedAt } : {}),\n ...(run.labels ? { labels: run.labels } : {}),\n run,\n })), [runs]);\n const openId = onSelectRun ? selectedId : localSelectedId;\n\n return (\n <DataTable\n {...(className ? { className } : {})}\n data={rows}\n columns={compact ? compactRunColumns : runColumns}\n emptyMessage=\"No background tasks.\"\n externalFilters={filters}\n filterBarProps={{ trailing: <span className=\"text-xs text-muted-foreground\">{status}</span> }}\n getRowId={(row) => row.id}\n defaultSort={{ key: \"startedAt\", dir: \"desc\" }}\n expandedRowIds={openId ? [openId] : []}\n onExpandedRowIdsChange={(ids) => {\n const next = ids.find((id) => id !== openId) ?? null;\n if (onSelectRun) onSelectRun(next);\n else setLocalSelectedId(next);\n }}\n renderExpandedRow={(row) => (\n <ExpandedRun\n run={row.run}\n basePath={basePath}\n pollMs={pollMs}\n runsQueryKey={runsQueryKey}\n extraTabs={extraTabs}\n headerExtra={headerExtra}\n />\n )}\n />\n );\n}\n\nfunction RunDuration({ startedAt, finishedAt }: { startedAt: string | undefined; finishedAt: string | undefined }) {\n const now = useResourceClock(!!startedAt && !finishedAt);\n if (!startedAt) return <span className=\"text-muted-foreground\">—</span>;\n return <span className=\"tabular-nums\">{formatDuration(Math.max(0, (finishedAt ? Date.parse(finishedAt) : now) - Date.parse(startedAt)))}</span>;\n}\n\nfunction applicationLabel(kind: string): string {\n const names: Record<string, string> = {\n makerprint: \"MakerPrint\",\n openscad: \"OpenSCAD\",\n orca: \"Orca\",\n snapmaker: \"Snapmaker\",\n other: \"Other\",\n };\n return names[kind] ?? kind;\n}\n\nfunction ExpandedRun({\n run,\n basePath,\n pollMs,\n runsQueryKey,\n extraTabs,\n headerExtra,\n}: {\n run: TaskRunMeta;\n basePath: string | undefined;\n pollMs: number | undefined;\n runsQueryKey: QueryKey;\n extraTabs: TaskExtraTabs | undefined;\n headerExtra: TaskHeaderExtra | undefined;\n}) {\n const runId = run.id;\n const { snapshots, status } = useTaskRun({ id: runId, basePath, pollMs });\n const apiBase = basePath ?? \"/api/v1\";\n const queryClient = useQueryClient();\n const pendingControls = useRef(new Map<string, Promise<void>>());\n const cachedSnapshots = useRef<TaskSnapshot[] | undefined>(undefined);\n const runQueryKey = useMemo(\n () => taskQueryKeys.run({ basePath: apiBase, runId }),\n [apiBase, runId],\n );\n const controlMutation = useMutation({\n mutationKey: taskQueryKeys.control({ basePath: apiBase, runId }),\n mutationFn: postTaskControl,\n onSuccess: async () => {\n await Promise.all([\n queryClient.invalidateQueries({ queryKey: runsQueryKey, exact: true, refetchType: \"none\" }),\n queryClient.invalidateQueries({ queryKey: runQueryKey, exact: true, refetchType: \"none\" }),\n ]);\n },\n });\n\n useEffect(() => {\n const previous = cachedSnapshots.current;\n if (previous?.length === snapshots.length && previous.every((snapshot, index) => snapshot === snapshots[index])) {\n return;\n }\n cachedSnapshots.current = snapshots;\n queryClient.setQueryData(runQueryKey, snapshots);\n }, [queryClient, runQueryKey, snapshots]);\n\n const mutateControl = useCallback((request: TaskControlRequest) => {\n const key = `${request.url}:${request.action}`;\n const pending = pendingControls.current.get(key);\n if (pending) return pending;\n controlMutation.reset();\n const mutation = controlMutation.mutateAsync(request).finally(() => {\n if (pendingControls.current.get(key) === mutation) pendingControls.current.delete(key);\n });\n pendingControls.current.set(key, mutation);\n return mutation;\n }, [controlMutation.mutateAsync, controlMutation.reset]);\n const control = (action: TaskControlAction, group: TaskSnapshot) =>\n mutateControl({\n url: `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,\n action,\n target: group,\n });\n const controlTask = (action: TaskControlAction, task: TaskSnapshot) =>\n mutateControl({\n url: `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,\n action,\n target: task,\n });\n const progress = taskWorkProgress(run);\n return (\n <div className=\"space-y-3 bg-muted/20 px-4 py-4\">\n <div className=\"flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground\">\n <span>{progress.completed} completed</span>\n <span>{progress.running} running</span>\n <span>{progress.failed} failed</span>\n <span>{progress.counts.pending + progress.unstarted} waiting</span>\n {Object.entries(run.labels ?? {}).map(([key, value]) => (\n <span key={key} className=\"font-mono\">{key}={value}</span>\n ))}\n </div>\n <TaskProgress\n snapshots={snapshots}\n compact\n connectionStatus={status}\n initialShowAllTasks\n onControl={control}\n onTaskControl={controlTask}\n metricsBaseUrl={`${apiBase}/tasks/metrics/`}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n </div>\n );\n}\n\ninterface TaskControlRequest {\n url: string;\n action: TaskControlAction;\n target: TaskSnapshot;\n}\n\nasync function postTaskControl({ url, action, target }: TaskControlRequest) {\n const context = `Failed to ${action} ${target.type} \"${target.name}\"`;\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ action }),\n });\n } catch (cause) {\n throw new Error(`${context}: ${cause instanceof Error ? cause.message : \"request failed\"}`, { cause });\n }\n if (!response.ok) {\n const detail = (await response.text()).trim() || `HTTP ${response.status}`;\n throw new Error(`${context}: ${detail}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaskManager.cjs","names":["taskWorkProgress","ProgressBar","taskSegments","cn","taskStatusColor","Icon","taskStatusIcon","useState","useQueryClient","useMemo","splitTagToken","useTaskRuns","taskQueryKeys","hashKey","DataTable","useResourceClock","formatDuration","useTaskRun","useRef","useMutation","useCallback","TaskProgress"],"sources":["../../src/data/TaskManager.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { hashKey, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type { QueryKey } from \"@tanstack/react-query\";\nimport { cn } from \"../lib/utils\";\nimport { formatDuration } from \"../lib/format\";\nimport { useResourceClock } from \"../hooks/use-resource-clock\";\nimport { useTaskRun, useTaskRuns } from \"../hooks/use-task-run\";\nimport type { FilterBarFilter } from \"../components/FilterBar\";\nimport { Icon } from \"./Icon\";\nimport { DataTable, type DataTableColumn } from \"./DataTable\";\nimport { splitTagToken } from \"./cells/tag-utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProgress } from \"./TaskProgress\";\nimport { taskWorkProgress } from \"./task-work-progress\";\nimport type { TaskControlAction, TaskRunMeta, TaskSnapshot } from \"./TaskSnapshot\";\nimport type { TaskHeaderExtra } from \"./TaskHeaderExtras\";\nimport type { TaskExtraTabs } from \"./TaskStreamTabs\";\nimport { taskQueryKeys } from \"./task-query-keys\";\nimport { taskSegments, taskStatusColor, taskStatusIcon } from \"./task-status\";\n\n// TaskManager is the generic clicky-ui task-manager view: it lists every run\n// (TaskRunMeta) from GET {basePath}/tasks with kind/status filters, and expands\n// each row into a live <TaskProgress> for that run id.\n//\n// Requires a react-query QueryClientProvider in the host app (like\n// TimeseriesPanel and CacheBrowser): control actions run through useMutation,\n// and the listing is mirrored into the query cache so sibling views can read it.\n\nexport interface TaskManagerProps {\n /** Base path of the clicky task API, e.g. \"/api/v1\". */\n basePath?: string;\n /** Restrict the listing to a single kind (also hides the kind filter). */\n kind?: string;\n /** Restrict the listing to runs whose labels contain every key/value pair. */\n labels?: Record<string, string>;\n pollMs?: number;\n className?: string;\n /** Keep the run columns usable in a narrow panel. */\n compact?: boolean;\n /**\n * Currently-selected (expanded) run id, e.g. driven from the URL. When set,\n * the matching run renders expanded and selection is controlled — otherwise\n * each row manages its own expand/collapse state.\n */\n selectedId?: string;\n /**\n * Called when a run row is toggled: the new selection id, or null when the\n * open row is collapsed. Pair with selectedId for URL-driven deep links.\n */\n onSelectRun?: (id: string | null) => void;\n /**\n * Contributes host-owned output panes to each task, alongside its own\n * stdout/stderr — an agent's session transcript, say.\n */\n extraTabs?: TaskExtraTabs;\n /**\n * Replaces the content beside a task's name in its header row. Returning null\n * for a task falls back to the default metadata chips.\n */\n headerExtra?: TaskHeaderExtra;\n renderRunDetails?: (run: TaskRunMeta, snapshots: TaskSnapshot[]) => ReactNode;\n controlUrl?: (run: TaskRunMeta, action: TaskControlAction) => string | undefined;\n showTaskControls?: (run: TaskRunMeta) => boolean;\n}\n\ntype RunTableRow = Record<string, unknown> & {\n id: string;\n name: string;\n kind: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n labels?: Record<string, string>;\n run: TaskRunMeta;\n};\n\nconst progressColumn: DataTableColumn<RunTableRow> = {\n key: \"progress\", label: \"Progress\", minWidth: 180,\n accessor: (row) => taskWorkProgress(row.run).done,\n render: (_, row) => {\n const progress = taskWorkProgress(row.run);\n return (\n <div className=\"min-w-36 space-y-1\">\n <span className=\"text-xs tabular-nums text-muted-foreground\">{progress.label}</span>\n {progress.total > 0 && <ProgressBar segments={taskSegments(progress.counts)} total={progress.total} height=\"h-1.5\" />}\n </div>\n );\n },\n};\nconst startedColumn: DataTableColumn<RunTableRow> = { key: \"startedAt\", label: \"Started\", kind: \"timestamp\", sortable: true };\nconst durationColumn: DataTableColumn<RunTableRow> = {\n key: \"duration\", label: \"Duration\", sortable: true,\n accessor: (row) => row.startedAt ? Date.parse(row.finishedAt ?? new Date().toISOString()) - Date.parse(row.startedAt) : null,\n render: (_, row) => <RunDuration startedAt={row.startedAt} finishedAt={row.finishedAt} />,\n};\nconst runColumns: DataTableColumn<RunTableRow>[] = [\n { key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220 },\n {\n key: \"status\", label: \"Status\", sortable: true,\n render: (_, row) => (\n <span className={cn(\"inline-flex items-center gap-1.5 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} {...(row.status === \"running\" ? { className: \"animate-spin\" } : {})} />\n {row.status}\n </span>\n ),\n },\n durationColumn,\n progressColumn,\n { key: \"labels\", label: \"Labels\", kind: \"tags\", tags: { maxVisible: 2 }, minWidth: 160 },\n startedColumn,\n { key: \"finishedAt\", label: \"Finished\", kind: \"timestamp\", sortable: true },\n];\nconst compactRunColumns: DataTableColumn<RunTableRow>[] = [\n {\n key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220,\n render: (_, row) => (\n <div className=\"space-y-0.5\">\n <div className=\"font-medium\">{row.name}</div>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{applicationLabel(row.kind)}</span>\n <span className={cn(\"inline-flex items-center gap-1 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} />{row.status}\n </span>\n </div>\n </div>\n ),\n },\n progressColumn,\n startedColumn,\n];\n\nexport function TaskManager({\n basePath,\n kind,\n labels,\n pollMs,\n className,\n compact,\n selectedId,\n onSelectRun,\n extraTabs,\n headerExtra,\n renderRunDetails,\n controlUrl,\n showTaskControls,\n}: TaskManagerProps) {\n const [kindFilter, setKindFilter] = useState<string>(kind ?? \"\");\n const [statusFilter, setStatusFilter] = useState<string>(\"\");\n const [labelFilter, setLabelFilter] = useState(\"\");\n const [filterOptions, setFilterOptions] = useState({ kinds: [] as string[], labels: [] as string[] });\n const [localSelectedId, setLocalSelectedId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const apiBase = basePath ?? \"/api/v1\";\n const activeKind = kind ?? (kindFilter || undefined);\n const activeLabels = useMemo(() => {\n if (!labelFilter) return labels;\n const selected = splitTagToken(labelFilter);\n return { ...labels, [selected.key]: selected.value };\n }, [labels, labelFilter]);\n const { runs, runsKey, status } = useTaskRuns({\n basePath: apiBase,\n kind: activeKind,\n labels: activeLabels,\n status: statusFilter || undefined,\n pollMs,\n });\n const runsQueryKey = useMemo(\n () => taskQueryKeys.runs({\n basePath: apiBase,\n kind: activeKind,\n status: statusFilter || undefined,\n labels: activeLabels,\n }),\n [activeKind, apiBase, activeLabels, statusFilter],\n );\n\n // Changing a filter re-subscribes the transport but leaves the previous\n // listing on screen until the next SSE/poll frame. Publishing that listing\n // under the new key would hand cache consumers rows that do not match their\n // filters, so mirror only once the delivered listing's own key agrees.\n useEffect(() => {\n if (!runsKey || hashKey(runsKey) !== hashKey(runsQueryKey)) return;\n queryClient.setQueryData(runsQueryKey, runs);\n }, [queryClient, runs, runsKey, runsQueryKey]);\n\n useEffect(() => {\n setFilterOptions((previous) => {\n const kinds = [...new Set([...previous.kinds, ...runs.flatMap((run) => run.kind ? [run.kind] : [])])].sort();\n const labels = [...new Set([...previous.labels, ...runs.flatMap((run) => Object.entries(run.labels ?? {}).map(([key, value]) => `${key}=${value}`))])].sort();\n if (kinds.length === previous.kinds.length && labels.length === previous.labels.length) return previous;\n return { kinds, labels };\n });\n }, [runs]);\n const filters: FilterBarFilter[] = [\n ...(!kind ? [{ key: \"kind\", kind: \"enum\" as const, label: \"Kind\", placeholder: \"All kinds\", value: kindFilter, options: filterOptions.kinds.map((value) => ({ value, label: applicationLabel(value) })), onChange: setKindFilter }] : []),\n { key: \"status\", kind: \"enum\", label: \"Status\", placeholder: \"Any status\", value: statusFilter, options: [\n { value: \"running\", label: \"Running\" },\n { value: \"pending\", label: \"Pending\" },\n { value: \"success\", label: \"Success\" },\n { value: \"failed\", label: \"Failed\" },\n { value: \"warning\", label: \"Warning\" },\n { value: \"canceled\", label: \"Canceled\" },\n ], onChange: setStatusFilter },\n { key: \"labels\", kind: \"enum\", label: \"Labels\", placeholder: \"Any label\", value: labelFilter,\n options: filterOptions.labels.filter((token) => !(splitTagToken(token).key in (labels ?? {}))).map((value) => ({ value })),\n onChange: setLabelFilter },\n ];\n const rows = useMemo<RunTableRow[]>(() => runs.map((run) => ({\n id: run.id,\n name: run.name,\n kind: run.kind || \"other\",\n status: run.status,\n ...(run.startedAt ? { startedAt: run.startedAt } : {}),\n ...(run.finishedAt ? { finishedAt: run.finishedAt } : {}),\n ...(run.labels ? { labels: run.labels } : {}),\n run,\n })), [runs]);\n const openId = onSelectRun ? selectedId : localSelectedId;\n\n return (\n <DataTable\n {...(className ? { className } : {})}\n data={rows}\n columns={compact ? compactRunColumns : runColumns}\n emptyMessage=\"No background tasks.\"\n externalFilters={filters}\n filterBarProps={{ trailing: <span className=\"text-xs text-muted-foreground\">{status}</span> }}\n getRowId={(row) => row.id}\n defaultSort={{ key: \"startedAt\", dir: \"desc\" }}\n expandedRowIds={openId ? [openId] : []}\n onExpandedRowIdsChange={(ids) => {\n const next = ids.find((id) => id !== openId) ?? null;\n if (onSelectRun) onSelectRun(next);\n else setLocalSelectedId(next);\n }}\n renderExpandedRow={(row) => (\n <ExpandedRun\n run={row.run}\n basePath={basePath}\n pollMs={pollMs}\n runsQueryKey={runsQueryKey}\n extraTabs={extraTabs}\n headerExtra={headerExtra}\n renderRunDetails={renderRunDetails}\n controlUrl={controlUrl}\n showTaskControls={showTaskControls}\n />\n )}\n />\n );\n}\n\nfunction RunDuration({ startedAt, finishedAt }: { startedAt: string | undefined; finishedAt: string | undefined }) {\n const now = useResourceClock(!!startedAt && !finishedAt);\n if (!startedAt) return <span className=\"text-muted-foreground\">—</span>;\n return <span className=\"tabular-nums\">{formatDuration(Math.max(0, (finishedAt ? Date.parse(finishedAt) : now) - Date.parse(startedAt)))}</span>;\n}\n\nfunction applicationLabel(kind: string): string {\n const names: Record<string, string> = {\n makerprint: \"MakerPrint\",\n openscad: \"OpenSCAD\",\n orca: \"Orca\",\n snapmaker: \"Snapmaker\",\n other: \"Other\",\n };\n return names[kind] ?? kind;\n}\n\nfunction ExpandedRun({\n run,\n basePath,\n pollMs,\n runsQueryKey,\n extraTabs,\n headerExtra,\n renderRunDetails,\n controlUrl,\n showTaskControls,\n}: {\n run: TaskRunMeta;\n basePath: string | undefined;\n pollMs: number | undefined;\n runsQueryKey: QueryKey;\n extraTabs: TaskExtraTabs | undefined;\n headerExtra: TaskHeaderExtra | undefined;\n renderRunDetails: TaskManagerProps[\"renderRunDetails\"];\n controlUrl: TaskManagerProps[\"controlUrl\"];\n showTaskControls: TaskManagerProps[\"showTaskControls\"];\n}) {\n const runId = run.id;\n const { snapshots, status } = useTaskRun({ id: runId, basePath, pollMs });\n const apiBase = basePath ?? \"/api/v1\";\n const queryClient = useQueryClient();\n const pendingControls = useRef(new Map<string, Promise<void>>());\n const cachedSnapshots = useRef<TaskSnapshot[] | undefined>(undefined);\n const runQueryKey = useMemo(\n () => taskQueryKeys.run({ basePath: apiBase, runId }),\n [apiBase, runId],\n );\n const controlMutation = useMutation({\n mutationKey: taskQueryKeys.control({ basePath: apiBase, runId }),\n mutationFn: postTaskControl,\n onSuccess: async () => {\n await Promise.all([\n queryClient.invalidateQueries({ queryKey: runsQueryKey, exact: true, refetchType: \"none\" }),\n queryClient.invalidateQueries({ queryKey: runQueryKey, exact: true, refetchType: \"none\" }),\n ]);\n },\n });\n\n useEffect(() => {\n const previous = cachedSnapshots.current;\n if (previous?.length === snapshots.length && previous.every((snapshot, index) => snapshot === snapshots[index])) {\n return;\n }\n cachedSnapshots.current = snapshots;\n queryClient.setQueryData(runQueryKey, snapshots);\n }, [queryClient, runQueryKey, snapshots]);\n\n const mutateControl = useCallback((request: TaskControlRequest) => {\n const key = `${request.url}:${request.action}`;\n const pending = pendingControls.current.get(key);\n if (pending) return pending;\n controlMutation.reset();\n const mutation = controlMutation.mutateAsync(request).finally(() => {\n if (pendingControls.current.get(key) === mutation) pendingControls.current.delete(key);\n });\n pendingControls.current.set(key, mutation);\n return mutation;\n }, [controlMutation.mutateAsync, controlMutation.reset]);\n const control = (action: TaskControlAction, group: TaskSnapshot) =>\n mutateControl({\n url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,\n action,\n target: group,\n });\n const controlTask = (action: TaskControlAction, task: TaskSnapshot) =>\n mutateControl({\n url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,\n action,\n target: task,\n });\n const progress = taskWorkProgress(run);\n return (\n <div className=\"space-y-3 bg-muted/20 px-4 py-4\">\n <div className=\"flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground\">\n <span>{progress.completed} completed</span>\n <span>{progress.running} running</span>\n <span>{progress.failed} failed</span>\n <span>{progress.counts.pending + progress.unstarted} waiting</span>\n {Object.entries(run.labels ?? {}).map(([key, value]) => (\n <span key={key} className=\"font-mono\">{key}={value}</span>\n ))}\n </div>\n {renderRunDetails?.(run, snapshots)}\n <TaskProgress\n snapshots={snapshots}\n compact\n connectionStatus={status}\n initialShowAllTasks\n onControl={control}\n {...(showTaskControls?.(run) !== false ? { onTaskControl: controlTask } : {})}\n metricsBaseUrl={`${apiBase}/tasks/metrics/`}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n </div>\n );\n}\n\ninterface TaskControlRequest {\n url: string;\n action: TaskControlAction;\n target: TaskSnapshot;\n}\n\nasync function postTaskControl({ url, action, target }: TaskControlRequest) {\n const context = `Failed to ${action} ${target.type} \"${target.name}\"`;\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ action }),\n });\n } catch (cause) {\n throw new Error(`${context}: ${cause instanceof Error ? cause.message : \"request failed\"}`, { cause });\n }\n if (!response.ok) {\n const detail = (await response.text()).trim() || `HTTP ${response.status}`;\n throw new Error(`${context}: ${detail}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6EA,IAAM,iBAA+C;CACnD,KAAK;CAAY,OAAO;CAAY,UAAU;CAC9C,WAAW,QAAQA,2BAAAA,iBAAiB,IAAI,GAAG,CAAC,CAAC;CAC7C,SAAS,GAAG,QAAQ;EAClB,MAAM,WAAWA,2BAAAA,iBAAiB,IAAI,GAAG;EACzC,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;IAAM,WAAU;IAA8C,UAAA,SAAS;GAAY,CAAA,GAClF,SAAS,QAAQ,KAAK,iBAAA,GAAA,kBAAA,IAAA,CAACC,oBAAAA,aAAD;IAAa,UAAUC,oBAAAA,aAAa,SAAS,MAAM;IAAG,OAAO,SAAS;IAAO,QAAO;GAAS,CAAA,CACjH;;CAET;AACF;AACA,IAAM,gBAA8C;CAAE,KAAK;CAAa,OAAO;CAAW,MAAM;CAAa,UAAU;AAAK;AAM5H,IAAM,aAA6C;CACjD;EAAE,KAAK;EAAQ,OAAO;EAAO,UAAU;EAAM,MAAM;EAAM,UAAU;CAAI;CACvE;EACE,KAAK;EAAU,OAAO;EAAU,UAAU;EAC1C,SAAS,GAAG,QACV,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;GAAM,WAAWC,cAAAA,GAAG,+CAA+CC,oBAAAA,gBAAgB,IAAI,MAAM,CAAC;GAA9F,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,aAAAA,MAAD;IAAM,MAAMC,oBAAAA,eAAe,IAAI,MAAM;IAAG,GAAK,IAAI,WAAW,YAAY,EAAE,WAAW,eAAe,IAAI,CAAC;GAAK,CAAA,GAC7G,IAAI,MACD;;CAEV;CACA;EAfA,KAAK;EAAY,OAAO;EAAY,UAAU;EAC9C,WAAW,QAAQ,IAAI,YAAY,KAAK,MAAM,IAAI,+BAAc,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,IAAI,KAAK,MAAM,IAAI,SAAS,IAAI;EACxH,SAAS,GAAG,QAAQ,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAD;GAAa,WAAW,IAAI;GAAW,YAAY,IAAI;EAAa,CAAA;CAaxF;CACA;CACA;EAAE,KAAK;EAAU,OAAO;EAAU,MAAM;EAAQ,MAAM,EAAE,YAAY,EAAE;EAAG,UAAU;CAAI;CACvF;CACA;EAAE,KAAK;EAAc,OAAO;EAAY,MAAM;EAAa,UAAU;CAAK;AAC5E;AACA,IAAM,oBAAoD;CACxD;EACE,KAAK;EAAQ,OAAO;EAAO,UAAU;EAAM,MAAM;EAAM,UAAU;EACjE,SAAS,GAAG,QACV,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAAK,WAAU;IAAe,UAAA,IAAI;GAAU,CAAA,GAC5C,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD,EAAA,UAAO,iBAAiB,IAAI,IAAI,EAAQ,CAAA,GACxC,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;KAAM,WAAWH,cAAAA,GAAG,6CAA6CC,oBAAAA,gBAAgB,IAAI,MAAM,CAAC;KAA5F,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,aAAAA,MAAD,EAAM,MAAMC,oBAAAA,eAAe,IAAI,MAAM,EAAI,CAAA,GAAE,IAAI,MAC3C;IACH,CAAA,CAAA;GACF,CAAA,CAAA;;CAET;CACA;CACA;AACF;AAEA,SAAgB,YAAY,EAC1B,UACA,MACA,QACA,QACA,WACA,SACA,YACA,aACA,WACA,aACA,kBACA,YACA,oBACmB;CACnB,MAAM,CAAC,YAAY,kBAAA,GAAiBC,MAAAA,SAAAA,CAAiB,QAAQ,EAAE;CAC/D,MAAM,CAAC,cAAc,oBAAA,GAAmBA,MAAAA,SAAAA,CAAiB,EAAE;CAC3D,MAAM,CAAC,aAAa,mBAAA,GAAkBA,MAAAA,SAAAA,CAAS,EAAE;CACjD,MAAM,CAAC,eAAe,qBAAA,GAAoBA,MAAAA,SAAAA,CAAS;EAAE,OAAO,CAAC;EAAe,QAAQ,CAAC;CAAc,CAAC;CACpG,MAAM,CAAC,iBAAiB,uBAAA,GAAsBA,MAAAA,SAAAA,CAAwB,IAAI;CAC1E,MAAM,eAAA,GAAcC,sBAAAA,eAAAA,CAAe;CACnC,MAAM,UAAU,YAAY;CAC5B,MAAM,aAAa,SAAS,cAAc,KAAA;CAC1C,MAAM,gBAAA,GAAeC,MAAAA,QAAAA,OAAc;EACjC,IAAI,CAAC,aAAa,OAAO;EACzB,MAAM,WAAWC,kBAAAA,cAAc,WAAW;EAC1C,OAAO;GAAE,GAAG;IAAS,SAAS,MAAM,SAAS;EAAM;CACrD,GAAG,CAAC,QAAQ,WAAW,CAAC;CACxB,MAAM,EAAE,MAAM,SAAS,WAAWC,qBAAAA,YAAY;EAC5C,UAAU;EACV,MAAM;EACN,QAAQ;EACR,QAAQ,gBAAgB,KAAA;EACxB;CACF,CAAC;CACD,MAAM,gBAAA,GAAeF,MAAAA,QAAAA,OACbG,wBAAAA,cAAc,KAAK;EACvB,UAAU;EACV,MAAM;EACN,QAAQ,gBAAgB,KAAA;EACxB,QAAQ;CACV,CAAC,GACD;EAAC;EAAY;EAAS;EAAc;CAAY,CAClD;CAMA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,YAAA,GAAWC,sBAAAA,QAAAA,CAAQ,OAAO,OAAA,GAAMA,sBAAAA,QAAAA,CAAQ,YAAY,GAAG;EAC5D,YAAY,aAAa,cAAc,IAAI;CAC7C,GAAG;EAAC;EAAa;EAAM;EAAS;CAAY,CAAC;CAE7C,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,kBAAkB,aAAa;GAC7B,MAAM,QAAQ,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,SAAS,OAAO,GAAG,KAAK,SAAS,QAAQ,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;GAC3G,MAAM,SAAS,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,SAAS,QAAQ,GAAG,KAAK,SAAS,QAAQ,OAAO,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;GAC5J,IAAI,MAAM,WAAW,SAAS,MAAM,UAAU,OAAO,WAAW,SAAS,OAAO,QAAQ,OAAO;GAC/F,OAAO;IAAE;IAAO;GAAO;EACzB,CAAC;CACH,GAAG,CAAC,IAAI,CAAC;CACT,MAAM,UAA6B;EACjC,GAAI,CAAC,OAAO,CAAC;GAAE,KAAK;GAAQ,MAAM;GAAiB,OAAO;GAAQ,aAAa;GAAa,OAAO;GAAY,SAAS,cAAc,MAAM,KAAK,WAAW;IAAE;IAAO,OAAO,iBAAiB,KAAK;GAAE,EAAE;GAAG,UAAU;EAAc,CAAC,IAAI,CAAC;EACvO;GAAE,KAAK;GAAU,MAAM;GAAQ,OAAO;GAAU,aAAa;GAAc,OAAO;GAAc,SAAS;IACvG;KAAE,OAAO;KAAW,OAAO;IAAU;IACrC;KAAE,OAAO;KAAW,OAAO;IAAU;IACrC;KAAE,OAAO;KAAW,OAAO;IAAU;IACrC;KAAE,OAAO;KAAU,OAAO;IAAS;IACnC;KAAE,OAAO;KAAW,OAAO;IAAU;IACrC;KAAE,OAAO;KAAY,OAAO;IAAW;GACzC;GAAG,UAAU;EAAgB;EAC7B;GAAE,KAAK;GAAU,MAAM;GAAQ,OAAO;GAAU,aAAa;GAAa,OAAO;GAC/E,SAAS,cAAc,OAAO,QAAQ,UAAU,EAAEH,kBAAAA,cAAc,KAAK,CAAC,CAAC,QAAQ,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,WAAW,EAAE,MAAM,EAAE;GACzH,UAAU;EAAe;CAC7B;CACA,MAAM,QAAA,GAAOD,MAAAA,QAAAA,OAA6B,KAAK,KAAK,SAAS;EAC3D,IAAI,IAAI;EACR,MAAM,IAAI;EACV,MAAM,IAAI,QAAQ;EAClB,QAAQ,IAAI;EACZ,GAAI,IAAI,YAAY,EAAE,WAAW,IAAI,UAAU,IAAI,CAAC;EACpD,GAAI,IAAI,aAAa,EAAE,YAAY,IAAI,WAAW,IAAI,CAAC;EACvD,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;EAC3C;CACF,EAAE,GAAG,CAAC,IAAI,CAAC;CACX,MAAM,SAAS,cAAc,aAAa;CAE1C,OACE,iBAAA,GAAA,kBAAA,IAAA,CAACK,kBAAAA,WAAD;EACE,GAAK,YAAY,EAAE,UAAU,IAAI,CAAC;EAClC,MAAM;EACN,SAAS,UAAU,oBAAoB;EACvC,cAAa;EACb,iBAAiB;EACjB,gBAAgB,EAAE,UAAU,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,WAAU;GAAiC,UAAA;EAAa,CAAA,EAAE;EAC5F,WAAW,QAAQ,IAAI;EACvB,aAAa;GAAE,KAAK;GAAa,KAAK;EAAO;EAC7C,gBAAgB,SAAS,CAAC,MAAM,IAAI,CAAC;EACrC,yBAAyB,QAAQ;GAC/B,MAAM,OAAO,IAAI,MAAM,OAAO,OAAO,MAAM,KAAK;GAChD,IAAI,aAAa,YAAY,IAAI;QAC5B,mBAAmB,IAAI;EAC9B;EACA,oBAAoB,QAClB,iBAAA,GAAA,kBAAA,IAAA,CAAC,aAAD;GACE,KAAK,IAAI;GACC;GACF;GACM;GACH;GACE;GACK;GACN;GACM;EACnB,CAAA;CAEJ,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,cAAiF;CACjH,MAAM,MAAMC,2BAAAA,iBAAiB,CAAC,CAAC,aAAa,CAAC,UAAU;CACvD,IAAI,CAAC,WAAW,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;EAAM,WAAU;EAAwB,UAAA;CAAO,CAAA;CACtE,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;EAAM,WAAU;EAAgB,UAAAC,eAAAA,eAAe,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM,UAAU,IAAI,OAAO,KAAK,MAAM,SAAS,CAAC,CAAC;CAAQ,CAAA;AAChJ;AAEA,SAAS,iBAAiB,MAAsB;CAQ9C,OAAO;EANL,YAAY;EACZ,UAAU;EACV,MAAM;EACN,WAAW;EACX,OAAO;CAEF,EAAM,SAAS;AACxB;AAEA,SAAS,YAAY,EACnB,KACA,UACA,QACA,cACA,WACA,aACA,kBACA,YACA,oBAWC;CACD,MAAM,QAAQ,IAAI;CAClB,MAAM,EAAE,WAAW,WAAWC,qBAAAA,WAAW;EAAE,IAAI;EAAO;EAAU;CAAO,CAAC;CACxE,MAAM,UAAU,YAAY;CAC5B,MAAM,eAAA,GAAcT,sBAAAA,eAAAA,CAAe;CACnC,MAAM,mBAAA,GAAkBU,MAAAA,OAAAA,iBAAO,IAAI,IAA2B,CAAC;CAC/D,MAAM,mBAAA,GAAkBA,MAAAA,OAAAA,CAAmC,KAAA,CAAS;CACpE,MAAM,eAAA,GAAcT,MAAAA,QAAAA,OACZG,wBAAAA,cAAc,IAAI;EAAE,UAAU;EAAS;CAAM,CAAC,GACpD,CAAC,SAAS,KAAK,CACjB;CACA,MAAM,mBAAA,GAAkBO,sBAAAA,YAAAA,CAAY;EAClC,aAAaP,wBAAAA,cAAc,QAAQ;GAAE,UAAU;GAAS;EAAM,CAAC;EAC/D,YAAY;EACZ,WAAW,YAAY;GACrB,MAAM,QAAQ,IAAI,CAChB,YAAY,kBAAkB;IAAE,UAAU;IAAc,OAAO;IAAM,aAAa;GAAO,CAAC,GAC1F,YAAY,kBAAkB;IAAE,UAAU;IAAa,OAAO;IAAM,aAAa;GAAO,CAAC,CAC3F,CAAC;EACH;CACF,CAAC;CAED,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,MAAM,WAAW,gBAAgB;EACjC,IAAI,UAAU,WAAW,UAAU,UAAU,SAAS,OAAO,UAAU,UAAU,aAAa,UAAU,MAAM,GAC5G;EAEF,gBAAgB,UAAU;EAC1B,YAAY,aAAa,aAAa,SAAS;CACjD,GAAG;EAAC;EAAa;EAAa;CAAS,CAAC;CAExC,MAAM,iBAAA,GAAgBQ,MAAAA,YAAAA,EAAa,YAAgC;EACjE,MAAM,MAAM,GAAG,QAAQ,IAAI,GAAG,QAAQ;EACtC,MAAM,UAAU,gBAAgB,QAAQ,IAAI,GAAG;EAC/C,IAAI,SAAS,OAAO;EACpB,gBAAgB,MAAM;EACtB,MAAM,WAAW,gBAAgB,YAAY,OAAO,CAAC,CAAC,cAAc;GAClE,IAAI,gBAAgB,QAAQ,IAAI,GAAG,MAAM,UAAU,gBAAgB,QAAQ,OAAO,GAAG;EACvF,CAAC;EACD,gBAAgB,QAAQ,IAAI,KAAK,QAAQ;EACzC,OAAO;CACT,GAAG,CAAC,gBAAgB,aAAa,gBAAgB,KAAK,CAAC;CACvD,MAAM,WAAW,QAA2B,UAC1C,cAAc;EACZ,KAAK,aAAa,KAAK,MAAM,KAAK,GAAG,QAAQ,SAAS,mBAAmB,KAAK,EAAE;EAChF;EACA,QAAQ;CACV,CAAC;CACH,MAAM,eAAe,QAA2B,SAC9C,cAAc;EACZ,KAAK,aAAa,KAAK,MAAM,KAAK,GAAG,QAAQ,SAAS,mBAAmB,KAAK,EAAE,SAAS,mBAAmB,KAAK,EAAE,EAAE;EACrH;EACA,QAAQ;CACV,CAAC;CACH,MAAM,WAAWpB,2BAAAA,iBAAiB,GAAG;CACrC,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,SAAS,WAAU,YAAgB,EAAA,CAAA;KAC1C,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,SAAS,SAAQ,UAAc,EAAA,CAAA;KACtC,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,SAAS,QAAO,SAAa,EAAA,CAAA;KACpC,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD,EAAA,UAAA,CAAO,SAAS,OAAO,UAAU,SAAS,WAAU,UAAc,EAAA,CAAA;KACjE,OAAO,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,WAC3C,iBAAA,GAAA,kBAAA,KAAA,CAAC,QAAD;MAAgB,WAAU;MAA1B,UAAA;OAAuC;OAAI;OAAE;MAAY;KAA9C,GAAA,GAA8C,CAC1D;IACE;;GACJ,mBAAmB,KAAK,SAAS;GAClC,iBAAA,GAAA,kBAAA,IAAA,CAACqB,qBAAAA,cAAD;IACa;IACX,SAAA;IACA,kBAAkB;IAClB,qBAAA;IACA,WAAW;IACX,GAAK,mBAAmB,GAAG,MAAM,QAAQ,EAAE,eAAe,YAAY,IAAI,CAAC;IAC3E,gBAAgB,GAAG,QAAQ;IAC3B,GAAK,YAAY,EAAE,UAAU,IAAI,CAAC;IAClC,GAAK,cAAc,EAAE,YAAY,IAAI,CAAC;GACvC,CAAA;EACE;;AAET;AAQA,eAAe,gBAAgB,EAAE,KAAK,QAAQ,UAA8B;CAC1E,MAAM,UAAU,aAAa,OAAO,GAAG,OAAO,KAAK,IAAI,OAAO,KAAK;CACnE,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,KAAK;GAC1B,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;EACjC,CAAC;CACH,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,GAAG,QAAQ,IAAI,iBAAiB,QAAQ,MAAM,UAAU,oBAAoB,EAAE,MAAM,CAAC;CACvG;CACA,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,UAAU,MAAM,SAAS,KAAK,EAAA,CAAG,KAAK,KAAK,QAAQ,SAAS;EAClE,MAAM,IAAI,MAAM,GAAG,QAAQ,IAAI,QAAQ;CACzC;AACF"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TaskControlAction, TaskRunMeta, TaskSnapshot } from './TaskSnapshot.js';
|
|
1
3
|
import { TaskHeaderExtra } from './TaskHeaderExtras.js';
|
|
2
4
|
import { TaskExtraTabs } from './TaskStreamTabs.js';
|
|
3
5
|
export interface TaskManagerProps {
|
|
@@ -32,6 +34,9 @@ export interface TaskManagerProps {
|
|
|
32
34
|
* for a task falls back to the default metadata chips.
|
|
33
35
|
*/
|
|
34
36
|
headerExtra?: TaskHeaderExtra;
|
|
37
|
+
renderRunDetails?: (run: TaskRunMeta, snapshots: TaskSnapshot[]) => ReactNode;
|
|
38
|
+
controlUrl?: (run: TaskRunMeta, action: TaskControlAction) => string | undefined;
|
|
39
|
+
showTaskControls?: (run: TaskRunMeta) => boolean;
|
|
35
40
|
}
|
|
36
|
-
export declare function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra, }: TaskManagerProps): import("react").JSX.Element;
|
|
41
|
+
export declare function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra, renderRunDetails, controlUrl, showTaskControls, }: TaskManagerProps): import("react").JSX.Element;
|
|
37
42
|
//# sourceMappingURL=TaskManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskManager.d.ts","sourceRoot":"","sources":["../../src/data/TaskManager.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TaskManager.d.ts","sourceRoot":"","sources":["../../src/data/TaskManager.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAcvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAYtD,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,SAAS,CAAC;IAC9E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,KAAK,MAAM,GAAG,SAAS,CAAC;IACjF,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;CAClD;AAoED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,MAAM,EACN,SAAS,EACT,OAAO,EACP,UAAU,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,gBAAgB,GACjB,EAAE,gBAAgB,+BAyGlB"}
|
package/dist/data/TaskManager.js
CHANGED
|
@@ -110,7 +110,7 @@ var compactRunColumns = [
|
|
|
110
110
|
progressColumn,
|
|
111
111
|
startedColumn
|
|
112
112
|
];
|
|
113
|
-
function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra }) {
|
|
113
|
+
function TaskManager({ basePath, kind, labels, pollMs, className, compact, selectedId, onSelectRun, extraTabs, headerExtra, renderRunDetails, controlUrl, showTaskControls }) {
|
|
114
114
|
const [kindFilter, setKindFilter] = useState(kind ?? "");
|
|
115
115
|
const [statusFilter, setStatusFilter] = useState("");
|
|
116
116
|
const [labelFilter, setLabelFilter] = useState("");
|
|
@@ -263,7 +263,10 @@ function TaskManager({ basePath, kind, labels, pollMs, className, compact, selec
|
|
|
263
263
|
pollMs,
|
|
264
264
|
runsQueryKey,
|
|
265
265
|
extraTabs,
|
|
266
|
-
headerExtra
|
|
266
|
+
headerExtra,
|
|
267
|
+
renderRunDetails,
|
|
268
|
+
controlUrl,
|
|
269
|
+
showTaskControls
|
|
267
270
|
})
|
|
268
271
|
});
|
|
269
272
|
}
|
|
@@ -287,7 +290,7 @@ function applicationLabel(kind) {
|
|
|
287
290
|
other: "Other"
|
|
288
291
|
}[kind] ?? kind;
|
|
289
292
|
}
|
|
290
|
-
function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExtra }) {
|
|
293
|
+
function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExtra, renderRunDetails, controlUrl, showTaskControls }) {
|
|
291
294
|
const runId = run.id;
|
|
292
295
|
const { snapshots, status } = useTaskRun({
|
|
293
296
|
id: runId,
|
|
@@ -342,45 +345,49 @@ function ExpandedRun({ run, basePath, pollMs, runsQueryKey, extraTabs, headerExt
|
|
|
342
345
|
return mutation;
|
|
343
346
|
}, [controlMutation.mutateAsync, controlMutation.reset]);
|
|
344
347
|
const control = (action, group) => mutateControl({
|
|
345
|
-
url: `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,
|
|
348
|
+
url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,
|
|
346
349
|
action,
|
|
347
350
|
target: group
|
|
348
351
|
});
|
|
349
352
|
const controlTask = (action, task) => mutateControl({
|
|
350
|
-
url: `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,
|
|
353
|
+
url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,
|
|
351
354
|
action,
|
|
352
355
|
target: task
|
|
353
356
|
});
|
|
354
357
|
const progress = taskWorkProgress(run);
|
|
355
358
|
return /* @__PURE__ */ jsxs("div", {
|
|
356
359
|
className: "space-y-3 bg-muted/20 px-4 py-4",
|
|
357
|
-
children: [
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
360
|
+
children: [
|
|
361
|
+
/* @__PURE__ */ jsxs("div", {
|
|
362
|
+
className: "flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground",
|
|
363
|
+
children: [
|
|
364
|
+
/* @__PURE__ */ jsxs("span", { children: [progress.completed, " completed"] }),
|
|
365
|
+
/* @__PURE__ */ jsxs("span", { children: [progress.running, " running"] }),
|
|
366
|
+
/* @__PURE__ */ jsxs("span", { children: [progress.failed, " failed"] }),
|
|
367
|
+
/* @__PURE__ */ jsxs("span", { children: [progress.counts.pending + progress.unstarted, " waiting"] }),
|
|
368
|
+
Object.entries(run.labels ?? {}).map(([key, value]) => /* @__PURE__ */ jsxs("span", {
|
|
369
|
+
className: "font-mono",
|
|
370
|
+
children: [
|
|
371
|
+
key,
|
|
372
|
+
"=",
|
|
373
|
+
value
|
|
374
|
+
]
|
|
375
|
+
}, key))
|
|
376
|
+
]
|
|
377
|
+
}),
|
|
378
|
+
renderRunDetails?.(run, snapshots),
|
|
379
|
+
/* @__PURE__ */ jsx(TaskProgress, {
|
|
380
|
+
snapshots,
|
|
381
|
+
compact: true,
|
|
382
|
+
connectionStatus: status,
|
|
383
|
+
initialShowAllTasks: true,
|
|
384
|
+
onControl: control,
|
|
385
|
+
...showTaskControls?.(run) !== false ? { onTaskControl: controlTask } : {},
|
|
386
|
+
metricsBaseUrl: `${apiBase}/tasks/metrics/`,
|
|
387
|
+
...extraTabs ? { extraTabs } : {},
|
|
388
|
+
...headerExtra ? { headerExtra } : {}
|
|
389
|
+
})
|
|
390
|
+
]
|
|
384
391
|
});
|
|
385
392
|
}
|
|
386
393
|
async function postTaskControl({ url, action, target }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskManager.js","names":[],"sources":["../../src/data/TaskManager.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { hashKey, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type { QueryKey } from \"@tanstack/react-query\";\nimport { cn } from \"../lib/utils\";\nimport { formatDuration } from \"../lib/format\";\nimport { useResourceClock } from \"../hooks/use-resource-clock\";\nimport { useTaskRun, useTaskRuns } from \"../hooks/use-task-run\";\nimport type { FilterBarFilter } from \"../components/FilterBar\";\nimport { Icon } from \"./Icon\";\nimport { DataTable, type DataTableColumn } from \"./DataTable\";\nimport { splitTagToken } from \"./cells/tag-utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProgress } from \"./TaskProgress\";\nimport { taskWorkProgress } from \"./task-work-progress\";\nimport type { TaskControlAction, TaskRunMeta, TaskSnapshot } from \"./TaskSnapshot\";\nimport type { TaskHeaderExtra } from \"./TaskHeaderExtras\";\nimport type { TaskExtraTabs } from \"./TaskStreamTabs\";\nimport { taskQueryKeys } from \"./task-query-keys\";\nimport { taskSegments, taskStatusColor, taskStatusIcon } from \"./task-status\";\n\n// TaskManager is the generic clicky-ui task-manager view: it lists every run\n// (TaskRunMeta) from GET {basePath}/tasks with kind/status filters, and expands\n// each row into a live <TaskProgress> for that run id.\n//\n// Requires a react-query QueryClientProvider in the host app (like\n// TimeseriesPanel and CacheBrowser): control actions run through useMutation,\n// and the listing is mirrored into the query cache so sibling views can read it.\n\nexport interface TaskManagerProps {\n /** Base path of the clicky task API, e.g. \"/api/v1\". */\n basePath?: string;\n /** Restrict the listing to a single kind (also hides the kind filter). */\n kind?: string;\n /** Restrict the listing to runs whose labels contain every key/value pair. */\n labels?: Record<string, string>;\n pollMs?: number;\n className?: string;\n /** Keep the run columns usable in a narrow panel. */\n compact?: boolean;\n /**\n * Currently-selected (expanded) run id, e.g. driven from the URL. When set,\n * the matching run renders expanded and selection is controlled — otherwise\n * each row manages its own expand/collapse state.\n */\n selectedId?: string;\n /**\n * Called when a run row is toggled: the new selection id, or null when the\n * open row is collapsed. Pair with selectedId for URL-driven deep links.\n */\n onSelectRun?: (id: string | null) => void;\n /**\n * Contributes host-owned output panes to each task, alongside its own\n * stdout/stderr — an agent's session transcript, say.\n */\n extraTabs?: TaskExtraTabs;\n /**\n * Replaces the content beside a task's name in its header row. Returning null\n * for a task falls back to the default metadata chips.\n */\n headerExtra?: TaskHeaderExtra;\n}\n\ntype RunTableRow = Record<string, unknown> & {\n id: string;\n name: string;\n kind: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n labels?: Record<string, string>;\n run: TaskRunMeta;\n};\n\nconst progressColumn: DataTableColumn<RunTableRow> = {\n key: \"progress\", label: \"Progress\", minWidth: 180,\n accessor: (row) => taskWorkProgress(row.run).done,\n render: (_, row) => {\n const progress = taskWorkProgress(row.run);\n return (\n <div className=\"min-w-36 space-y-1\">\n <span className=\"text-xs tabular-nums text-muted-foreground\">{progress.label}</span>\n {progress.total > 0 && <ProgressBar segments={taskSegments(progress.counts)} total={progress.total} height=\"h-1.5\" />}\n </div>\n );\n },\n};\nconst startedColumn: DataTableColumn<RunTableRow> = { key: \"startedAt\", label: \"Started\", kind: \"timestamp\", sortable: true };\nconst durationColumn: DataTableColumn<RunTableRow> = {\n key: \"duration\", label: \"Duration\", sortable: true,\n accessor: (row) => row.startedAt ? Date.parse(row.finishedAt ?? new Date().toISOString()) - Date.parse(row.startedAt) : null,\n render: (_, row) => <RunDuration startedAt={row.startedAt} finishedAt={row.finishedAt} />,\n};\nconst runColumns: DataTableColumn<RunTableRow>[] = [\n { key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220 },\n {\n key: \"status\", label: \"Status\", sortable: true,\n render: (_, row) => (\n <span className={cn(\"inline-flex items-center gap-1.5 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} {...(row.status === \"running\" ? { className: \"animate-spin\" } : {})} />\n {row.status}\n </span>\n ),\n },\n durationColumn,\n progressColumn,\n { key: \"labels\", label: \"Labels\", kind: \"tags\", tags: { maxVisible: 2 }, minWidth: 160 },\n startedColumn,\n { key: \"finishedAt\", label: \"Finished\", kind: \"timestamp\", sortable: true },\n];\nconst compactRunColumns: DataTableColumn<RunTableRow>[] = [\n {\n key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220,\n render: (_, row) => (\n <div className=\"space-y-0.5\">\n <div className=\"font-medium\">{row.name}</div>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{applicationLabel(row.kind)}</span>\n <span className={cn(\"inline-flex items-center gap-1 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} />{row.status}\n </span>\n </div>\n </div>\n ),\n },\n progressColumn,\n startedColumn,\n];\n\nexport function TaskManager({\n basePath,\n kind,\n labels,\n pollMs,\n className,\n compact,\n selectedId,\n onSelectRun,\n extraTabs,\n headerExtra,\n}: TaskManagerProps) {\n const [kindFilter, setKindFilter] = useState<string>(kind ?? \"\");\n const [statusFilter, setStatusFilter] = useState<string>(\"\");\n const [labelFilter, setLabelFilter] = useState(\"\");\n const [filterOptions, setFilterOptions] = useState({ kinds: [] as string[], labels: [] as string[] });\n const [localSelectedId, setLocalSelectedId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const apiBase = basePath ?? \"/api/v1\";\n const activeKind = kind ?? (kindFilter || undefined);\n const activeLabels = useMemo(() => {\n if (!labelFilter) return labels;\n const selected = splitTagToken(labelFilter);\n return { ...labels, [selected.key]: selected.value };\n }, [labels, labelFilter]);\n const { runs, runsKey, status } = useTaskRuns({\n basePath: apiBase,\n kind: activeKind,\n labels: activeLabels,\n status: statusFilter || undefined,\n pollMs,\n });\n const runsQueryKey = useMemo(\n () => taskQueryKeys.runs({\n basePath: apiBase,\n kind: activeKind,\n status: statusFilter || undefined,\n labels: activeLabels,\n }),\n [activeKind, apiBase, activeLabels, statusFilter],\n );\n\n // Changing a filter re-subscribes the transport but leaves the previous\n // listing on screen until the next SSE/poll frame. Publishing that listing\n // under the new key would hand cache consumers rows that do not match their\n // filters, so mirror only once the delivered listing's own key agrees.\n useEffect(() => {\n if (!runsKey || hashKey(runsKey) !== hashKey(runsQueryKey)) return;\n queryClient.setQueryData(runsQueryKey, runs);\n }, [queryClient, runs, runsKey, runsQueryKey]);\n\n useEffect(() => {\n setFilterOptions((previous) => {\n const kinds = [...new Set([...previous.kinds, ...runs.flatMap((run) => run.kind ? [run.kind] : [])])].sort();\n const labels = [...new Set([...previous.labels, ...runs.flatMap((run) => Object.entries(run.labels ?? {}).map(([key, value]) => `${key}=${value}`))])].sort();\n if (kinds.length === previous.kinds.length && labels.length === previous.labels.length) return previous;\n return { kinds, labels };\n });\n }, [runs]);\n const filters: FilterBarFilter[] = [\n ...(!kind ? [{ key: \"kind\", kind: \"enum\" as const, label: \"Kind\", placeholder: \"All kinds\", value: kindFilter, options: filterOptions.kinds.map((value) => ({ value, label: applicationLabel(value) })), onChange: setKindFilter }] : []),\n { key: \"status\", kind: \"enum\", label: \"Status\", placeholder: \"Any status\", value: statusFilter, options: [\n { value: \"running\", label: \"Running\" },\n { value: \"pending\", label: \"Pending\" },\n { value: \"success\", label: \"Success\" },\n { value: \"failed\", label: \"Failed\" },\n { value: \"warning\", label: \"Warning\" },\n { value: \"canceled\", label: \"Canceled\" },\n ], onChange: setStatusFilter },\n { key: \"labels\", kind: \"enum\", label: \"Labels\", placeholder: \"Any label\", value: labelFilter,\n options: filterOptions.labels.filter((token) => !(splitTagToken(token).key in (labels ?? {}))).map((value) => ({ value })),\n onChange: setLabelFilter },\n ];\n const rows = useMemo<RunTableRow[]>(() => runs.map((run) => ({\n id: run.id,\n name: run.name,\n kind: run.kind || \"other\",\n status: run.status,\n ...(run.startedAt ? { startedAt: run.startedAt } : {}),\n ...(run.finishedAt ? { finishedAt: run.finishedAt } : {}),\n ...(run.labels ? { labels: run.labels } : {}),\n run,\n })), [runs]);\n const openId = onSelectRun ? selectedId : localSelectedId;\n\n return (\n <DataTable\n {...(className ? { className } : {})}\n data={rows}\n columns={compact ? compactRunColumns : runColumns}\n emptyMessage=\"No background tasks.\"\n externalFilters={filters}\n filterBarProps={{ trailing: <span className=\"text-xs text-muted-foreground\">{status}</span> }}\n getRowId={(row) => row.id}\n defaultSort={{ key: \"startedAt\", dir: \"desc\" }}\n expandedRowIds={openId ? [openId] : []}\n onExpandedRowIdsChange={(ids) => {\n const next = ids.find((id) => id !== openId) ?? null;\n if (onSelectRun) onSelectRun(next);\n else setLocalSelectedId(next);\n }}\n renderExpandedRow={(row) => (\n <ExpandedRun\n run={row.run}\n basePath={basePath}\n pollMs={pollMs}\n runsQueryKey={runsQueryKey}\n extraTabs={extraTabs}\n headerExtra={headerExtra}\n />\n )}\n />\n );\n}\n\nfunction RunDuration({ startedAt, finishedAt }: { startedAt: string | undefined; finishedAt: string | undefined }) {\n const now = useResourceClock(!!startedAt && !finishedAt);\n if (!startedAt) return <span className=\"text-muted-foreground\">—</span>;\n return <span className=\"tabular-nums\">{formatDuration(Math.max(0, (finishedAt ? Date.parse(finishedAt) : now) - Date.parse(startedAt)))}</span>;\n}\n\nfunction applicationLabel(kind: string): string {\n const names: Record<string, string> = {\n makerprint: \"MakerPrint\",\n openscad: \"OpenSCAD\",\n orca: \"Orca\",\n snapmaker: \"Snapmaker\",\n other: \"Other\",\n };\n return names[kind] ?? kind;\n}\n\nfunction ExpandedRun({\n run,\n basePath,\n pollMs,\n runsQueryKey,\n extraTabs,\n headerExtra,\n}: {\n run: TaskRunMeta;\n basePath: string | undefined;\n pollMs: number | undefined;\n runsQueryKey: QueryKey;\n extraTabs: TaskExtraTabs | undefined;\n headerExtra: TaskHeaderExtra | undefined;\n}) {\n const runId = run.id;\n const { snapshots, status } = useTaskRun({ id: runId, basePath, pollMs });\n const apiBase = basePath ?? \"/api/v1\";\n const queryClient = useQueryClient();\n const pendingControls = useRef(new Map<string, Promise<void>>());\n const cachedSnapshots = useRef<TaskSnapshot[] | undefined>(undefined);\n const runQueryKey = useMemo(\n () => taskQueryKeys.run({ basePath: apiBase, runId }),\n [apiBase, runId],\n );\n const controlMutation = useMutation({\n mutationKey: taskQueryKeys.control({ basePath: apiBase, runId }),\n mutationFn: postTaskControl,\n onSuccess: async () => {\n await Promise.all([\n queryClient.invalidateQueries({ queryKey: runsQueryKey, exact: true, refetchType: \"none\" }),\n queryClient.invalidateQueries({ queryKey: runQueryKey, exact: true, refetchType: \"none\" }),\n ]);\n },\n });\n\n useEffect(() => {\n const previous = cachedSnapshots.current;\n if (previous?.length === snapshots.length && previous.every((snapshot, index) => snapshot === snapshots[index])) {\n return;\n }\n cachedSnapshots.current = snapshots;\n queryClient.setQueryData(runQueryKey, snapshots);\n }, [queryClient, runQueryKey, snapshots]);\n\n const mutateControl = useCallback((request: TaskControlRequest) => {\n const key = `${request.url}:${request.action}`;\n const pending = pendingControls.current.get(key);\n if (pending) return pending;\n controlMutation.reset();\n const mutation = controlMutation.mutateAsync(request).finally(() => {\n if (pendingControls.current.get(key) === mutation) pendingControls.current.delete(key);\n });\n pendingControls.current.set(key, mutation);\n return mutation;\n }, [controlMutation.mutateAsync, controlMutation.reset]);\n const control = (action: TaskControlAction, group: TaskSnapshot) =>\n mutateControl({\n url: `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,\n action,\n target: group,\n });\n const controlTask = (action: TaskControlAction, task: TaskSnapshot) =>\n mutateControl({\n url: `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,\n action,\n target: task,\n });\n const progress = taskWorkProgress(run);\n return (\n <div className=\"space-y-3 bg-muted/20 px-4 py-4\">\n <div className=\"flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground\">\n <span>{progress.completed} completed</span>\n <span>{progress.running} running</span>\n <span>{progress.failed} failed</span>\n <span>{progress.counts.pending + progress.unstarted} waiting</span>\n {Object.entries(run.labels ?? {}).map(([key, value]) => (\n <span key={key} className=\"font-mono\">{key}={value}</span>\n ))}\n </div>\n <TaskProgress\n snapshots={snapshots}\n compact\n connectionStatus={status}\n initialShowAllTasks\n onControl={control}\n onTaskControl={controlTask}\n metricsBaseUrl={`${apiBase}/tasks/metrics/`}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n </div>\n );\n}\n\ninterface TaskControlRequest {\n url: string;\n action: TaskControlAction;\n target: TaskSnapshot;\n}\n\nasync function postTaskControl({ url, action, target }: TaskControlRequest) {\n const context = `Failed to ${action} ${target.type} \"${target.name}\"`;\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ action }),\n });\n } catch (cause) {\n throw new Error(`${context}: ${cause instanceof Error ? cause.message : \"request failed\"}`, { cause });\n }\n if (!response.ok) {\n const detail = (await response.text()).trim() || `HTTP ${response.status}`;\n throw new Error(`${context}: ${detail}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaskManager.js","names":[],"sources":["../../src/data/TaskManager.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { hashKey, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type { QueryKey } from \"@tanstack/react-query\";\nimport { cn } from \"../lib/utils\";\nimport { formatDuration } from \"../lib/format\";\nimport { useResourceClock } from \"../hooks/use-resource-clock\";\nimport { useTaskRun, useTaskRuns } from \"../hooks/use-task-run\";\nimport type { FilterBarFilter } from \"../components/FilterBar\";\nimport { Icon } from \"./Icon\";\nimport { DataTable, type DataTableColumn } from \"./DataTable\";\nimport { splitTagToken } from \"./cells/tag-utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProgress } from \"./TaskProgress\";\nimport { taskWorkProgress } from \"./task-work-progress\";\nimport type { TaskControlAction, TaskRunMeta, TaskSnapshot } from \"./TaskSnapshot\";\nimport type { TaskHeaderExtra } from \"./TaskHeaderExtras\";\nimport type { TaskExtraTabs } from \"./TaskStreamTabs\";\nimport { taskQueryKeys } from \"./task-query-keys\";\nimport { taskSegments, taskStatusColor, taskStatusIcon } from \"./task-status\";\n\n// TaskManager is the generic clicky-ui task-manager view: it lists every run\n// (TaskRunMeta) from GET {basePath}/tasks with kind/status filters, and expands\n// each row into a live <TaskProgress> for that run id.\n//\n// Requires a react-query QueryClientProvider in the host app (like\n// TimeseriesPanel and CacheBrowser): control actions run through useMutation,\n// and the listing is mirrored into the query cache so sibling views can read it.\n\nexport interface TaskManagerProps {\n /** Base path of the clicky task API, e.g. \"/api/v1\". */\n basePath?: string;\n /** Restrict the listing to a single kind (also hides the kind filter). */\n kind?: string;\n /** Restrict the listing to runs whose labels contain every key/value pair. */\n labels?: Record<string, string>;\n pollMs?: number;\n className?: string;\n /** Keep the run columns usable in a narrow panel. */\n compact?: boolean;\n /**\n * Currently-selected (expanded) run id, e.g. driven from the URL. When set,\n * the matching run renders expanded and selection is controlled — otherwise\n * each row manages its own expand/collapse state.\n */\n selectedId?: string;\n /**\n * Called when a run row is toggled: the new selection id, or null when the\n * open row is collapsed. Pair with selectedId for URL-driven deep links.\n */\n onSelectRun?: (id: string | null) => void;\n /**\n * Contributes host-owned output panes to each task, alongside its own\n * stdout/stderr — an agent's session transcript, say.\n */\n extraTabs?: TaskExtraTabs;\n /**\n * Replaces the content beside a task's name in its header row. Returning null\n * for a task falls back to the default metadata chips.\n */\n headerExtra?: TaskHeaderExtra;\n renderRunDetails?: (run: TaskRunMeta, snapshots: TaskSnapshot[]) => ReactNode;\n controlUrl?: (run: TaskRunMeta, action: TaskControlAction) => string | undefined;\n showTaskControls?: (run: TaskRunMeta) => boolean;\n}\n\ntype RunTableRow = Record<string, unknown> & {\n id: string;\n name: string;\n kind: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n labels?: Record<string, string>;\n run: TaskRunMeta;\n};\n\nconst progressColumn: DataTableColumn<RunTableRow> = {\n key: \"progress\", label: \"Progress\", minWidth: 180,\n accessor: (row) => taskWorkProgress(row.run).done,\n render: (_, row) => {\n const progress = taskWorkProgress(row.run);\n return (\n <div className=\"min-w-36 space-y-1\">\n <span className=\"text-xs tabular-nums text-muted-foreground\">{progress.label}</span>\n {progress.total > 0 && <ProgressBar segments={taskSegments(progress.counts)} total={progress.total} height=\"h-1.5\" />}\n </div>\n );\n },\n};\nconst startedColumn: DataTableColumn<RunTableRow> = { key: \"startedAt\", label: \"Started\", kind: \"timestamp\", sortable: true };\nconst durationColumn: DataTableColumn<RunTableRow> = {\n key: \"duration\", label: \"Duration\", sortable: true,\n accessor: (row) => row.startedAt ? Date.parse(row.finishedAt ?? new Date().toISOString()) - Date.parse(row.startedAt) : null,\n render: (_, row) => <RunDuration startedAt={row.startedAt} finishedAt={row.finishedAt} />,\n};\nconst runColumns: DataTableColumn<RunTableRow>[] = [\n { key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220 },\n {\n key: \"status\", label: \"Status\", sortable: true,\n render: (_, row) => (\n <span className={cn(\"inline-flex items-center gap-1.5 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} {...(row.status === \"running\" ? { className: \"animate-spin\" } : {})} />\n {row.status}\n </span>\n ),\n },\n durationColumn,\n progressColumn,\n { key: \"labels\", label: \"Labels\", kind: \"tags\", tags: { maxVisible: 2 }, minWidth: 160 },\n startedColumn,\n { key: \"finishedAt\", label: \"Finished\", kind: \"timestamp\", sortable: true },\n];\nconst compactRunColumns: DataTableColumn<RunTableRow>[] = [\n {\n key: \"name\", label: \"Run\", sortable: true, grow: true, minWidth: 220,\n render: (_, row) => (\n <div className=\"space-y-0.5\">\n <div className=\"font-medium\">{row.name}</div>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{applicationLabel(row.kind)}</span>\n <span className={cn(\"inline-flex items-center gap-1 capitalize\", taskStatusColor(row.status))}>\n <Icon icon={taskStatusIcon(row.status)} />{row.status}\n </span>\n </div>\n </div>\n ),\n },\n progressColumn,\n startedColumn,\n];\n\nexport function TaskManager({\n basePath,\n kind,\n labels,\n pollMs,\n className,\n compact,\n selectedId,\n onSelectRun,\n extraTabs,\n headerExtra,\n renderRunDetails,\n controlUrl,\n showTaskControls,\n}: TaskManagerProps) {\n const [kindFilter, setKindFilter] = useState<string>(kind ?? \"\");\n const [statusFilter, setStatusFilter] = useState<string>(\"\");\n const [labelFilter, setLabelFilter] = useState(\"\");\n const [filterOptions, setFilterOptions] = useState({ kinds: [] as string[], labels: [] as string[] });\n const [localSelectedId, setLocalSelectedId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const apiBase = basePath ?? \"/api/v1\";\n const activeKind = kind ?? (kindFilter || undefined);\n const activeLabels = useMemo(() => {\n if (!labelFilter) return labels;\n const selected = splitTagToken(labelFilter);\n return { ...labels, [selected.key]: selected.value };\n }, [labels, labelFilter]);\n const { runs, runsKey, status } = useTaskRuns({\n basePath: apiBase,\n kind: activeKind,\n labels: activeLabels,\n status: statusFilter || undefined,\n pollMs,\n });\n const runsQueryKey = useMemo(\n () => taskQueryKeys.runs({\n basePath: apiBase,\n kind: activeKind,\n status: statusFilter || undefined,\n labels: activeLabels,\n }),\n [activeKind, apiBase, activeLabels, statusFilter],\n );\n\n // Changing a filter re-subscribes the transport but leaves the previous\n // listing on screen until the next SSE/poll frame. Publishing that listing\n // under the new key would hand cache consumers rows that do not match their\n // filters, so mirror only once the delivered listing's own key agrees.\n useEffect(() => {\n if (!runsKey || hashKey(runsKey) !== hashKey(runsQueryKey)) return;\n queryClient.setQueryData(runsQueryKey, runs);\n }, [queryClient, runs, runsKey, runsQueryKey]);\n\n useEffect(() => {\n setFilterOptions((previous) => {\n const kinds = [...new Set([...previous.kinds, ...runs.flatMap((run) => run.kind ? [run.kind] : [])])].sort();\n const labels = [...new Set([...previous.labels, ...runs.flatMap((run) => Object.entries(run.labels ?? {}).map(([key, value]) => `${key}=${value}`))])].sort();\n if (kinds.length === previous.kinds.length && labels.length === previous.labels.length) return previous;\n return { kinds, labels };\n });\n }, [runs]);\n const filters: FilterBarFilter[] = [\n ...(!kind ? [{ key: \"kind\", kind: \"enum\" as const, label: \"Kind\", placeholder: \"All kinds\", value: kindFilter, options: filterOptions.kinds.map((value) => ({ value, label: applicationLabel(value) })), onChange: setKindFilter }] : []),\n { key: \"status\", kind: \"enum\", label: \"Status\", placeholder: \"Any status\", value: statusFilter, options: [\n { value: \"running\", label: \"Running\" },\n { value: \"pending\", label: \"Pending\" },\n { value: \"success\", label: \"Success\" },\n { value: \"failed\", label: \"Failed\" },\n { value: \"warning\", label: \"Warning\" },\n { value: \"canceled\", label: \"Canceled\" },\n ], onChange: setStatusFilter },\n { key: \"labels\", kind: \"enum\", label: \"Labels\", placeholder: \"Any label\", value: labelFilter,\n options: filterOptions.labels.filter((token) => !(splitTagToken(token).key in (labels ?? {}))).map((value) => ({ value })),\n onChange: setLabelFilter },\n ];\n const rows = useMemo<RunTableRow[]>(() => runs.map((run) => ({\n id: run.id,\n name: run.name,\n kind: run.kind || \"other\",\n status: run.status,\n ...(run.startedAt ? { startedAt: run.startedAt } : {}),\n ...(run.finishedAt ? { finishedAt: run.finishedAt } : {}),\n ...(run.labels ? { labels: run.labels } : {}),\n run,\n })), [runs]);\n const openId = onSelectRun ? selectedId : localSelectedId;\n\n return (\n <DataTable\n {...(className ? { className } : {})}\n data={rows}\n columns={compact ? compactRunColumns : runColumns}\n emptyMessage=\"No background tasks.\"\n externalFilters={filters}\n filterBarProps={{ trailing: <span className=\"text-xs text-muted-foreground\">{status}</span> }}\n getRowId={(row) => row.id}\n defaultSort={{ key: \"startedAt\", dir: \"desc\" }}\n expandedRowIds={openId ? [openId] : []}\n onExpandedRowIdsChange={(ids) => {\n const next = ids.find((id) => id !== openId) ?? null;\n if (onSelectRun) onSelectRun(next);\n else setLocalSelectedId(next);\n }}\n renderExpandedRow={(row) => (\n <ExpandedRun\n run={row.run}\n basePath={basePath}\n pollMs={pollMs}\n runsQueryKey={runsQueryKey}\n extraTabs={extraTabs}\n headerExtra={headerExtra}\n renderRunDetails={renderRunDetails}\n controlUrl={controlUrl}\n showTaskControls={showTaskControls}\n />\n )}\n />\n );\n}\n\nfunction RunDuration({ startedAt, finishedAt }: { startedAt: string | undefined; finishedAt: string | undefined }) {\n const now = useResourceClock(!!startedAt && !finishedAt);\n if (!startedAt) return <span className=\"text-muted-foreground\">—</span>;\n return <span className=\"tabular-nums\">{formatDuration(Math.max(0, (finishedAt ? Date.parse(finishedAt) : now) - Date.parse(startedAt)))}</span>;\n}\n\nfunction applicationLabel(kind: string): string {\n const names: Record<string, string> = {\n makerprint: \"MakerPrint\",\n openscad: \"OpenSCAD\",\n orca: \"Orca\",\n snapmaker: \"Snapmaker\",\n other: \"Other\",\n };\n return names[kind] ?? kind;\n}\n\nfunction ExpandedRun({\n run,\n basePath,\n pollMs,\n runsQueryKey,\n extraTabs,\n headerExtra,\n renderRunDetails,\n controlUrl,\n showTaskControls,\n}: {\n run: TaskRunMeta;\n basePath: string | undefined;\n pollMs: number | undefined;\n runsQueryKey: QueryKey;\n extraTabs: TaskExtraTabs | undefined;\n headerExtra: TaskHeaderExtra | undefined;\n renderRunDetails: TaskManagerProps[\"renderRunDetails\"];\n controlUrl: TaskManagerProps[\"controlUrl\"];\n showTaskControls: TaskManagerProps[\"showTaskControls\"];\n}) {\n const runId = run.id;\n const { snapshots, status } = useTaskRun({ id: runId, basePath, pollMs });\n const apiBase = basePath ?? \"/api/v1\";\n const queryClient = useQueryClient();\n const pendingControls = useRef(new Map<string, Promise<void>>());\n const cachedSnapshots = useRef<TaskSnapshot[] | undefined>(undefined);\n const runQueryKey = useMemo(\n () => taskQueryKeys.run({ basePath: apiBase, runId }),\n [apiBase, runId],\n );\n const controlMutation = useMutation({\n mutationKey: taskQueryKeys.control({ basePath: apiBase, runId }),\n mutationFn: postTaskControl,\n onSuccess: async () => {\n await Promise.all([\n queryClient.invalidateQueries({ queryKey: runsQueryKey, exact: true, refetchType: \"none\" }),\n queryClient.invalidateQueries({ queryKey: runQueryKey, exact: true, refetchType: \"none\" }),\n ]);\n },\n });\n\n useEffect(() => {\n const previous = cachedSnapshots.current;\n if (previous?.length === snapshots.length && previous.every((snapshot, index) => snapshot === snapshots[index])) {\n return;\n }\n cachedSnapshots.current = snapshots;\n queryClient.setQueryData(runQueryKey, snapshots);\n }, [queryClient, runQueryKey, snapshots]);\n\n const mutateControl = useCallback((request: TaskControlRequest) => {\n const key = `${request.url}:${request.action}`;\n const pending = pendingControls.current.get(key);\n if (pending) return pending;\n controlMutation.reset();\n const mutation = controlMutation.mutateAsync(request).finally(() => {\n if (pendingControls.current.get(key) === mutation) pendingControls.current.delete(key);\n });\n pendingControls.current.set(key, mutation);\n return mutation;\n }, [controlMutation.mutateAsync, controlMutation.reset]);\n const control = (action: TaskControlAction, group: TaskSnapshot) =>\n mutateControl({\n url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/control`,\n action,\n target: group,\n });\n const controlTask = (action: TaskControlAction, task: TaskSnapshot) =>\n mutateControl({\n url: controlUrl?.(run, action) ?? `${apiBase}/tasks/${encodeURIComponent(runId)}/tasks/${encodeURIComponent(task.id)}/control`,\n action,\n target: task,\n });\n const progress = taskWorkProgress(run);\n return (\n <div className=\"space-y-3 bg-muted/20 px-4 py-4\">\n <div className=\"flex flex-wrap gap-x-5 gap-y-1 text-xs text-muted-foreground\">\n <span>{progress.completed} completed</span>\n <span>{progress.running} running</span>\n <span>{progress.failed} failed</span>\n <span>{progress.counts.pending + progress.unstarted} waiting</span>\n {Object.entries(run.labels ?? {}).map(([key, value]) => (\n <span key={key} className=\"font-mono\">{key}={value}</span>\n ))}\n </div>\n {renderRunDetails?.(run, snapshots)}\n <TaskProgress\n snapshots={snapshots}\n compact\n connectionStatus={status}\n initialShowAllTasks\n onControl={control}\n {...(showTaskControls?.(run) !== false ? { onTaskControl: controlTask } : {})}\n metricsBaseUrl={`${apiBase}/tasks/metrics/`}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n </div>\n );\n}\n\ninterface TaskControlRequest {\n url: string;\n action: TaskControlAction;\n target: TaskSnapshot;\n}\n\nasync function postTaskControl({ url, action, target }: TaskControlRequest) {\n const context = `Failed to ${action} ${target.type} \"${target.name}\"`;\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ action }),\n });\n } catch (cause) {\n throw new Error(`${context}: ${cause instanceof Error ? cause.message : \"request failed\"}`, { cause });\n }\n if (!response.ok) {\n const detail = (await response.text()).trim() || `HTTP ${response.status}`;\n throw new Error(`${context}: ${detail}`);\n }\n}\n"],"mappings":"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"}
|