@flanksource/clicky-ui 0.2.3 → 0.2.5

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.
Files changed (178) hide show
  1. package/dist/components/Combobox.cjs +291 -0
  2. package/dist/components/Combobox.cjs.map +1 -0
  3. package/dist/components/Combobox.d.ts +59 -0
  4. package/dist/components/Combobox.d.ts.map +1 -0
  5. package/dist/components/Combobox.js +291 -0
  6. package/dist/components/Combobox.js.map +1 -0
  7. package/dist/components/FilterBar.cjs +153 -124
  8. package/dist/components/FilterBar.cjs.map +1 -1
  9. package/dist/components/FilterBar.d.ts +25 -1
  10. package/dist/components/FilterBar.d.ts.map +1 -1
  11. package/dist/components/FilterBar.js +153 -124
  12. package/dist/components/FilterBar.js.map +1 -1
  13. package/dist/components/FormatOptionsDropdown.cjs +62 -0
  14. package/dist/components/FormatOptionsDropdown.cjs.map +1 -0
  15. package/dist/components/FormatOptionsDropdown.d.ts +37 -0
  16. package/dist/components/FormatOptionsDropdown.d.ts.map +1 -0
  17. package/dist/components/FormatOptionsDropdown.js +62 -0
  18. package/dist/components/FormatOptionsDropdown.js.map +1 -0
  19. package/dist/components/SplitButton.cjs +87 -0
  20. package/dist/components/SplitButton.cjs.map +1 -0
  21. package/dist/components/SplitButton.d.ts +32 -0
  22. package/dist/components/SplitButton.d.ts.map +1 -0
  23. package/dist/components/SplitButton.js +87 -0
  24. package/dist/components/SplitButton.js.map +1 -0
  25. package/dist/components/icon-menu-picker.cjs +14 -3
  26. package/dist/components/icon-menu-picker.cjs.map +1 -1
  27. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  28. package/dist/components/icon-menu-picker.js +15 -4
  29. package/dist/components/icon-menu-picker.js.map +1 -1
  30. package/dist/components.cjs +7 -0
  31. package/dist/components.cjs.map +1 -1
  32. package/dist/components.d.ts +3 -0
  33. package/dist/components.d.ts.map +1 -1
  34. package/dist/components.js +7 -0
  35. package/dist/components.js.map +1 -1
  36. package/dist/data/Badge.d.ts +1 -1
  37. package/dist/data/ProgressBar.cjs +4 -2
  38. package/dist/data/ProgressBar.cjs.map +1 -1
  39. package/dist/data/ProgressBar.d.ts +1 -1
  40. package/dist/data/ProgressBar.d.ts.map +1 -1
  41. package/dist/data/ProgressBar.js +4 -2
  42. package/dist/data/ProgressBar.js.map +1 -1
  43. package/dist/data/TaskManager.cjs +162 -0
  44. package/dist/data/TaskManager.cjs.map +1 -0
  45. package/dist/data/TaskManager.d.ts +21 -0
  46. package/dist/data/TaskManager.d.ts.map +1 -0
  47. package/dist/data/TaskManager.js +162 -0
  48. package/dist/data/TaskManager.js.map +1 -0
  49. package/dist/data/TaskProgress.cjs +125 -0
  50. package/dist/data/TaskProgress.cjs.map +1 -0
  51. package/dist/data/TaskProgress.d.ts +20 -0
  52. package/dist/data/TaskProgress.d.ts.map +1 -0
  53. package/dist/data/TaskProgress.js +125 -0
  54. package/dist/data/TaskProgress.js.map +1 -0
  55. package/dist/data/TaskSnapshot.cjs +23 -0
  56. package/dist/data/TaskSnapshot.cjs.map +1 -0
  57. package/dist/data/TaskSnapshot.d.ts +49 -0
  58. package/dist/data/TaskSnapshot.d.ts.map +1 -0
  59. package/dist/data/TaskSnapshot.js +23 -0
  60. package/dist/data/TaskSnapshot.js.map +1 -0
  61. package/dist/data/TimeseriesPanel.axes.cjs +39 -0
  62. package/dist/data/TimeseriesPanel.axes.cjs.map +1 -0
  63. package/dist/data/TimeseriesPanel.axes.d.ts +28 -0
  64. package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -0
  65. package/dist/data/TimeseriesPanel.axes.js +39 -0
  66. package/dist/data/TimeseriesPanel.axes.js.map +1 -0
  67. package/dist/data/TimeseriesPanel.cjs +324 -0
  68. package/dist/data/TimeseriesPanel.cjs.map +1 -0
  69. package/dist/data/TimeseriesPanel.d.ts +141 -0
  70. package/dist/data/TimeseriesPanel.d.ts.map +1 -0
  71. package/dist/data/TimeseriesPanel.js +324 -0
  72. package/dist/data/TimeseriesPanel.js.map +1 -0
  73. package/dist/data/Tree.cjs.map +1 -1
  74. package/dist/data/Tree.d.ts.map +1 -1
  75. package/dist/data/Tree.js.map +1 -1
  76. package/dist/data/TreeNode.cjs +44 -7
  77. package/dist/data/TreeNode.cjs.map +1 -1
  78. package/dist/data/TreeNode.d.ts +19 -1
  79. package/dist/data/TreeNode.d.ts.map +1 -1
  80. package/dist/data/TreeNode.js +44 -7
  81. package/dist/data/TreeNode.js.map +1 -1
  82. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs +66 -44
  83. package/dist/data/diagnostics/DiagnosticsDetailPanel.cjs.map +1 -1
  84. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts +21 -1
  85. package/dist/data/diagnostics/DiagnosticsDetailPanel.d.ts.map +1 -1
  86. package/dist/data/diagnostics/DiagnosticsDetailPanel.js +65 -43
  87. package/dist/data/diagnostics/DiagnosticsDetailPanel.js.map +1 -1
  88. package/dist/data/diagnostics/DiagnosticsTree.cjs +2 -1
  89. package/dist/data/diagnostics/DiagnosticsTree.cjs.map +1 -1
  90. package/dist/data/diagnostics/DiagnosticsTree.js +2 -1
  91. package/dist/data/diagnostics/DiagnosticsTree.js.map +1 -1
  92. package/dist/data/diagnostics/FrameSourceWindow.cjs +37 -0
  93. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -0
  94. package/dist/data/diagnostics/FrameSourceWindow.d.ts +15 -0
  95. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -0
  96. package/dist/data/diagnostics/FrameSourceWindow.js +37 -0
  97. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -0
  98. package/dist/data/diagnostics/JvmStackTrace.cjs +25 -12
  99. package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
  100. package/dist/data/diagnostics/JvmStackTrace.d.ts +8 -1
  101. package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
  102. package/dist/data/diagnostics/JvmStackTrace.js +26 -13
  103. package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
  104. package/dist/data/diagnostics/ThreadCard.cjs +9 -2
  105. package/dist/data/diagnostics/ThreadCard.cjs.map +1 -1
  106. package/dist/data/diagnostics/ThreadCard.d.ts +3 -1
  107. package/dist/data/diagnostics/ThreadCard.d.ts.map +1 -1
  108. package/dist/data/diagnostics/ThreadCard.js +9 -2
  109. package/dist/data/diagnostics/ThreadCard.js.map +1 -1
  110. package/dist/data/diagnostics/jvm-stacktrace.cjs +10 -1
  111. package/dist/data/diagnostics/jvm-stacktrace.cjs.map +1 -1
  112. package/dist/data/diagnostics/jvm-stacktrace.d.ts.map +1 -1
  113. package/dist/data/diagnostics/jvm-stacktrace.js +10 -1
  114. package/dist/data/diagnostics/jvm-stacktrace.js.map +1 -1
  115. package/dist/data/diagnostics/utils.cjs +0 -12
  116. package/dist/data/diagnostics/utils.cjs.map +1 -1
  117. package/dist/data/diagnostics/utils.d.ts +1 -1
  118. package/dist/data/diagnostics/utils.d.ts.map +1 -1
  119. package/dist/data/diagnostics/utils.js +0 -12
  120. package/dist/data/diagnostics/utils.js.map +1 -1
  121. package/dist/data/task-status.cjs +108 -0
  122. package/dist/data/task-status.cjs.map +1 -0
  123. package/dist/data/task-status.d.ts +21 -0
  124. package/dist/data/task-status.d.ts.map +1 -0
  125. package/dist/data/task-status.js +108 -0
  126. package/dist/data/task-status.js.map +1 -0
  127. package/dist/data.cjs +21 -0
  128. package/dist/data.cjs.map +1 -1
  129. package/dist/data.d.ts +7 -1
  130. package/dist/data.d.ts.map +1 -1
  131. package/dist/data.js +21 -0
  132. package/dist/data.js.map +1 -1
  133. package/dist/hooks/use-task-run.cjs +137 -0
  134. package/dist/hooks/use-task-run.cjs.map +1 -0
  135. package/dist/hooks/use-task-run.d.ts +35 -0
  136. package/dist/hooks/use-task-run.d.ts.map +1 -0
  137. package/dist/hooks/use-task-run.js +137 -0
  138. package/dist/hooks/use-task-run.js.map +1 -0
  139. package/dist/hooks.cjs +3 -0
  140. package/dist/hooks.cjs.map +1 -1
  141. package/dist/hooks.d.ts +1 -0
  142. package/dist/hooks.d.ts.map +1 -1
  143. package/dist/hooks.js +3 -0
  144. package/dist/hooks.js.map +1 -1
  145. package/dist/index.cjs +37 -0
  146. package/dist/index.cjs.map +1 -1
  147. package/dist/index.js +37 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/lib/color.cjs +33 -0
  150. package/dist/lib/color.cjs.map +1 -0
  151. package/dist/lib/color.d.ts +10 -0
  152. package/dist/lib/color.d.ts.map +1 -0
  153. package/dist/lib/color.js +33 -0
  154. package/dist/lib/color.js.map +1 -0
  155. package/dist/lib/format.cjs +77 -0
  156. package/dist/lib/format.cjs.map +1 -0
  157. package/dist/lib/format.d.ts +30 -0
  158. package/dist/lib/format.d.ts.map +1 -0
  159. package/dist/lib/format.js +77 -0
  160. package/dist/lib/format.js.map +1 -0
  161. package/dist/overlay/DropdownMenu.cjs +18 -2
  162. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  163. package/dist/overlay/DropdownMenu.d.ts +3 -1
  164. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  165. package/dist/overlay/DropdownMenu.js +18 -2
  166. package/dist/overlay/DropdownMenu.js.map +1 -1
  167. package/dist/rpc/types.cjs.map +1 -1
  168. package/dist/rpc/types.d.ts +4 -0
  169. package/dist/rpc/types.d.ts.map +1 -1
  170. package/dist/rpc/types.js.map +1 -1
  171. package/dist/styles.css +1 -1
  172. package/dist/utils.cjs +6 -0
  173. package/dist/utils.cjs.map +1 -1
  174. package/dist/utils.d.ts +1 -0
  175. package/dist/utils.d.ts.map +1 -1
  176. package/dist/utils.js +6 -0
  177. package/dist/utils.js.map +1 -1
  178. package/package.json +4 -1
@@ -0,0 +1,162 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useMemo } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { useTaskRuns, useTaskRun } from "../hooks/use-task-run.js";
5
+ import { ProgressBar } from "./ProgressBar.js";
6
+ import { Timestamp } from "./cells/Timestamp.js";
7
+ import { TaskProgress } from "./TaskProgress.js";
8
+ import { taskStatusColor, taskStatusIcon, taskStatusBg, taskSegments } from "./task-status.js";
9
+ function TaskManager({
10
+ basePath,
11
+ kind,
12
+ pollMs,
13
+ className,
14
+ selectedId,
15
+ onSelectRun
16
+ }) {
17
+ const [kindFilter, setKindFilter] = useState(kind ?? "");
18
+ const [statusFilter, setStatusFilter] = useState("");
19
+ const { runs, status } = useTaskRuns({
20
+ basePath,
21
+ kind: kind ?? (kindFilter || void 0),
22
+ status: statusFilter || void 0,
23
+ pollMs
24
+ });
25
+ const kinds = useMemo(() => {
26
+ const set = /* @__PURE__ */ new Set();
27
+ for (const r of runs) if (r.kind) set.add(r.kind);
28
+ return [...set].sort();
29
+ }, [runs]);
30
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
31
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
32
+ !kind && /* @__PURE__ */ jsxs(
33
+ "select",
34
+ {
35
+ className: "rounded-md border bg-background px-2 py-1 text-xs",
36
+ value: kindFilter,
37
+ onChange: (e) => setKindFilter(e.target.value),
38
+ children: [
39
+ /* @__PURE__ */ jsx("option", { value: "", children: "All kinds" }),
40
+ kinds.map((k) => /* @__PURE__ */ jsx("option", { value: k, children: k }, k))
41
+ ]
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsxs(
45
+ "select",
46
+ {
47
+ className: "rounded-md border bg-background px-2 py-1 text-xs",
48
+ value: statusFilter,
49
+ onChange: (e) => setStatusFilter(e.target.value),
50
+ children: [
51
+ /* @__PURE__ */ jsx("option", { value: "", children: "Any status" }),
52
+ /* @__PURE__ */ jsx("option", { value: "running", children: "Running" }),
53
+ /* @__PURE__ */ jsx("option", { value: "pending", children: "Pending" }),
54
+ /* @__PURE__ */ jsx("option", { value: "success", children: "Success" }),
55
+ /* @__PURE__ */ jsx("option", { value: "failed", children: "Failed" }),
56
+ /* @__PURE__ */ jsx("option", { value: "warning", children: "Warning" })
57
+ ]
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: status })
61
+ ] }),
62
+ runs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground", children: "No background tasks." }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border bg-card", children: runs.map((run) => /* @__PURE__ */ jsx(
63
+ RunRow,
64
+ {
65
+ run,
66
+ basePath,
67
+ pollMs,
68
+ selectedId,
69
+ onSelectRun
70
+ },
71
+ run.id
72
+ )) })
73
+ ] });
74
+ }
75
+ function RunRow({
76
+ run,
77
+ basePath,
78
+ pollMs,
79
+ selectedId,
80
+ onSelectRun
81
+ }) {
82
+ const [localOpen, setLocalOpen] = useState(false);
83
+ const controlled = onSelectRun !== void 0;
84
+ const open = controlled ? selectedId === run.id : localOpen;
85
+ const toggle = () => {
86
+ if (controlled) onSelectRun(open ? null : run.id);
87
+ else setLocalOpen((v) => !v);
88
+ };
89
+ const isTerminal = run.status !== "running" && run.status !== "pending";
90
+ return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-0", children: [
91
+ /* @__PURE__ */ jsxs(
92
+ "button",
93
+ {
94
+ type: "button",
95
+ className: "flex w-full items-center justify-between gap-4 px-4 py-3 text-left hover:bg-muted/50",
96
+ onClick: toggle,
97
+ children: [
98
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
100
+ /* @__PURE__ */ jsx("iconify-icon", { icon: taskStatusIcon(run.status), class: taskStatusColor(run.status) }),
101
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: run.name }),
102
+ run.kind && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground", children: run.kind }),
103
+ isTerminal && /* @__PURE__ */ jsx(
104
+ "span",
105
+ {
106
+ className: cn(
107
+ "rounded-full px-2 py-0.5 text-xs",
108
+ taskStatusColor(run.status),
109
+ taskStatusBg(run.status)
110
+ ),
111
+ children: run.status
112
+ }
113
+ )
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground", children: [
116
+ run.startedAt && /* @__PURE__ */ jsx(Timestamp, { value: run.startedAt, format: "relative" }),
117
+ /* @__PURE__ */ jsxs("span", { children: [
118
+ run.completed + run.failed,
119
+ "/",
120
+ run.total,
121
+ " done",
122
+ run.failed > 0 ? `, ${run.failed} failed` : ""
123
+ ] }),
124
+ Object.entries(run.labels ?? {}).map(([k, v]) => /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
125
+ k,
126
+ "=",
127
+ v
128
+ ] }, k))
129
+ ] })
130
+ ] }),
131
+ /* @__PURE__ */ jsx("div", { className: "w-40 shrink-0", children: /* @__PURE__ */ jsx(
132
+ ProgressBar,
133
+ {
134
+ segments: taskSegments({
135
+ ok: run.completed,
136
+ warn: 0,
137
+ fail: run.failed,
138
+ run: run.running,
139
+ pending: Math.max(0, run.total - run.completed - run.failed - run.running)
140
+ }),
141
+ total: run.total || 1,
142
+ height: "h-1.5"
143
+ }
144
+ ) })
145
+ ]
146
+ }
147
+ ),
148
+ open && /* @__PURE__ */ jsx(ExpandedRun, { runId: run.id, basePath, pollMs })
149
+ ] });
150
+ }
151
+ function ExpandedRun({
152
+ runId,
153
+ basePath,
154
+ pollMs
155
+ }) {
156
+ const { snapshots } = useTaskRun({ id: runId, basePath, pollMs });
157
+ return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/30 px-4 py-3", children: /* @__PURE__ */ jsx(TaskProgress, { snapshots, compact: true }) });
158
+ }
159
+ export {
160
+ TaskManager
161
+ };
162
+ //# sourceMappingURL=TaskManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskManager.js","sources":["../../src/data/TaskManager.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useTaskRun, useTaskRuns } from \"../hooks/use-task-run\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { Timestamp } from \"./cells/Timestamp\";\nimport { TaskProgress } from \"./TaskProgress\";\nimport type { TaskRunMeta } from \"./TaskSnapshot\";\nimport { taskSegments, taskStatusBg, 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. Fully generic — no\n// application concepts.\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 pollMs?: number;\n className?: string;\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\nexport function TaskManager({\n basePath,\n kind,\n pollMs,\n className,\n selectedId,\n onSelectRun,\n}: TaskManagerProps) {\n const [kindFilter, setKindFilter] = useState<string>(kind ?? \"\");\n const [statusFilter, setStatusFilter] = useState<string>(\"\");\n const { runs, status } = useTaskRuns({\n basePath,\n kind: kind ?? (kindFilter || undefined),\n status: statusFilter || undefined,\n pollMs,\n });\n\n const kinds = useMemo(() => {\n const set = new Set<string>();\n for (const r of runs) if (r.kind) set.add(r.kind);\n return [...set].sort();\n }, [runs]);\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"flex flex-wrap items-center gap-2\">\n {!kind && (\n <select\n className=\"rounded-md border bg-background px-2 py-1 text-xs\"\n value={kindFilter}\n onChange={(e) => setKindFilter(e.target.value)}\n >\n <option value=\"\">All kinds</option>\n {kinds.map((k) => (\n <option key={k} value={k}>\n {k}\n </option>\n ))}\n </select>\n )}\n <select\n className=\"rounded-md border bg-background px-2 py-1 text-xs\"\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value)}\n >\n <option value=\"\">Any status</option>\n <option value=\"running\">Running</option>\n <option value=\"pending\">Pending</option>\n <option value=\"success\">Success</option>\n <option value=\"failed\">Failed</option>\n <option value=\"warning\">Warning</option>\n </select>\n <span className=\"text-xs text-muted-foreground\">{status}</span>\n </div>\n\n {runs.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground\">\n No background tasks.\n </div>\n ) : (\n <div className=\"overflow-hidden rounded-lg border bg-card\">\n {runs.map((run) => (\n <RunRow\n key={run.id}\n run={run}\n basePath={basePath}\n pollMs={pollMs}\n selectedId={selectedId}\n onSelectRun={onSelectRun}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction RunRow({\n run,\n basePath,\n pollMs,\n selectedId,\n onSelectRun,\n}: {\n run: TaskRunMeta;\n basePath: string | undefined;\n pollMs: number | undefined;\n selectedId: string | undefined;\n onSelectRun: ((id: string | null) => void) | undefined;\n}) {\n const [localOpen, setLocalOpen] = useState(false);\n // Controlled by selectedId when a selection handler is wired; otherwise the\n // row owns its expand state (backward compatible for router-less consumers).\n const controlled = onSelectRun !== undefined;\n const open = controlled ? selectedId === run.id : localOpen;\n const toggle = () => {\n if (controlled) onSelectRun(open ? null : run.id);\n else setLocalOpen((v) => !v);\n };\n const isTerminal = run.status !== \"running\" && run.status !== \"pending\";\n\n return (\n <div className=\"border-b last:border-0\">\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between gap-4 px-4 py-3 text-left hover:bg-muted/50\"\n onClick={toggle}\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <iconify-icon icon={taskStatusIcon(run.status)} class={taskStatusColor(run.status)} />\n <span className=\"truncate font-medium\">{run.name}</span>\n {run.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {run.kind}\n </span>\n )}\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(run.status),\n taskStatusBg(run.status),\n )}\n >\n {run.status}\n </span>\n )}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground\">\n {run.startedAt && <Timestamp value={run.startedAt} format=\"relative\" />}\n <span>\n {run.completed + run.failed}/{run.total} done\n {run.failed > 0 ? `, ${run.failed} failed` : \"\"}\n </span>\n {Object.entries(run.labels ?? {}).map(([k, v]) => (\n <span key={k} className=\"font-mono\">\n {k}={v}\n </span>\n ))}\n </div>\n </div>\n <div className=\"w-40 shrink-0\">\n <ProgressBar\n segments={taskSegments({\n ok: run.completed,\n warn: 0,\n fail: run.failed,\n run: run.running,\n pending: Math.max(0, run.total - run.completed - run.failed - run.running),\n })}\n total={run.total || 1}\n height=\"h-1.5\"\n />\n </div>\n </button>\n {open && <ExpandedRun runId={run.id} basePath={basePath} pollMs={pollMs} />}\n </div>\n );\n}\n\nfunction ExpandedRun({\n runId,\n basePath,\n pollMs,\n}: {\n runId: string;\n basePath: string | undefined;\n pollMs: number | undefined;\n}) {\n const { snapshots } = useTaskRun({ id: runId, basePath, pollMs });\n return (\n <div className=\"border-t bg-muted/30 px-4 py-3\">\n <TaskProgress snapshots={snapshots} compact />\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const utils = require("../lib/utils.cjs");
6
+ const ProgressBar = require("./ProgressBar.cjs");
7
+ const taskStatus = require("./task-status.cjs");
8
+ const MAX_COMPLETED = 5;
9
+ const MAX_PENDING = 3;
10
+ function TaskProgress({ snapshots, title, compact, className }) {
11
+ const groups = snapshots.filter((s) => s.type === "group");
12
+ const tasks = snapshots.filter((s) => s.type === "task");
13
+ if (groups.length === 0) {
14
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("text-sm text-muted-foreground", className), children: [
15
+ title ? `${title}: ` : "",
16
+ "No tasks yet."
17
+ ] });
18
+ }
19
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn(compact ? "space-y-2" : "space-y-3", className), children: [
20
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: title }),
21
+ groups.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
22
+ TaskGroupCard,
23
+ {
24
+ group: g,
25
+ tasks: tasks.filter((t) => t.groupId === g.groupId || t.group === g.id),
26
+ compact
27
+ },
28
+ g.groupId || g.id
29
+ ))
30
+ ] });
31
+ }
32
+ function isFailedOrWarn(t) {
33
+ return t.status === "failed" || t.status === "FAIL" || t.status === "ERR" || t.status === "warning";
34
+ }
35
+ function TaskGroupCard({
36
+ group: g,
37
+ tasks,
38
+ compact
39
+ }) {
40
+ const [showAll, setShowAll] = react.useState(false);
41
+ const total = g.total ?? tasks.length;
42
+ const counts = taskStatus.bucketTasks(tasks);
43
+ const done = counts.ok + counts.warn + counts.fail;
44
+ const progress = total > 0 ? `${done}/${total}` : "";
45
+ const running = tasks.filter((t) => t.status === "running");
46
+ const pending = tasks.filter((t) => t.status === "pending");
47
+ const completed = tasks.filter((t) => t.status !== "running" && t.status !== "pending");
48
+ const alwaysShow = completed.filter(isFailedOrWarn);
49
+ const collapsible = completed.filter((t) => !isFailedOrWarn(t));
50
+ const hiddenCompleted = collapsible.length - MAX_COMPLETED;
51
+ const collapseCompleted = !showAll && hiddenCompleted > 0;
52
+ const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;
53
+ const hiddenPending = pending.length - MAX_PENDING;
54
+ const collapsePending = !showAll && hiddenPending > 0;
55
+ const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;
56
+ const isTerminal = g.status !== "running" && g.status !== "pending";
57
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("rounded-lg border bg-card", compact ? "p-3" : "p-4"), children: [
58
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
59
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [
60
+ /* @__PURE__ */ jsxRuntime.jsx("iconify-icon", { icon: taskStatus.taskStatusIcon(g.status), class: taskStatus.taskStatusColor(g.status) }),
61
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: g.name }),
62
+ progress && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: progress }),
63
+ g.kind && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground", children: g.kind })
64
+ ] }),
65
+ isTerminal && /* @__PURE__ */ jsxRuntime.jsx(
66
+ "span",
67
+ {
68
+ className: utils.cn(
69
+ "rounded-full px-2 py-0.5 text-xs",
70
+ taskStatus.taskStatusColor(g.status),
71
+ taskStatus.taskStatusBg(g.status)
72
+ ),
73
+ children: g.status
74
+ }
75
+ )
76
+ ] }),
77
+ total > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(ProgressBar.ProgressBar, { segments: taskStatus.taskSegments(counts), total, height: "h-1.5" }) }),
78
+ hiddenCompleted > 0 || hiddenPending > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
79
+ "button",
80
+ {
81
+ type: "button",
82
+ className: "border-b py-1.5 text-xs text-muted-foreground hover:text-foreground",
83
+ onClick: () => setShowAll((v) => !v),
84
+ children: showAll ? "▲ collapse" : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`
85
+ }
86
+ ) : null,
87
+ [...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => /* @__PURE__ */ jsxRuntime.jsx(TaskRow, { task: t }, t.id))
88
+ ] });
89
+ }
90
+ function TaskRow({ task: t }) {
91
+ const [expanded, setExpanded] = react.useState(false);
92
+ const logs = t.logs ?? [];
93
+ const hasLogs = logs.length > 0;
94
+ const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
95
+ return /* @__PURE__ */ jsxRuntime.jsxs(
96
+ "div",
97
+ {
98
+ className: utils.cn(
99
+ "flex items-start gap-3 border-b py-2 last:border-0",
100
+ hasLogs && "-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50"
101
+ ),
102
+ onClick: hasLogs ? () => setExpanded((v) => !v) : void 0,
103
+ children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx("iconify-icon", { icon: taskStatus.taskStatusIcon(t.status), class: utils.cn(taskStatus.taskStatusColor(t.status), "mt-0.5 text-lg") }),
105
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
106
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
107
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium", children: [
108
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: t.name }),
109
+ t.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs font-normal text-red-500", children: t.error }),
110
+ latestWarn && /* @__PURE__ */ jsxRuntime.jsx("span", { className: utils.cn("truncate text-xs font-normal", taskStatus.logLevelColor("warn")), children: latestWarn.message }),
111
+ hasLogs && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: logs.length })
112
+ ] }),
113
+ t.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
114
+ ] }),
115
+ expanded && hasLogs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("text-xs", taskStatus.logLevelColor(l.level)), children: [
116
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
117
+ l.message
118
+ ] }, i)) })
119
+ ] })
120
+ ]
121
+ }
122
+ );
123
+ }
124
+ exports.TaskProgress = TaskProgress;
125
+ //# sourceMappingURL=TaskProgress.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskProgress.cjs","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["cn","jsxs","jsx","useState","bucketTasks","taskStatusIcon","taskStatusColor","taskStatusBg","ProgressBar","taskSegments","logLevelColor"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { TaskSnapshot } from './TaskSnapshot';
2
+ declare global {
3
+ namespace JSX {
4
+ interface IntrinsicElements {
5
+ "iconify-icon": {
6
+ icon: string;
7
+ class?: string;
8
+ };
9
+ }
10
+ }
11
+ }
12
+ export interface TaskProgressProps {
13
+ snapshots: TaskSnapshot[];
14
+ title?: string;
15
+ /** Tighter spacing for embedding in a panel. */
16
+ compact?: boolean;
17
+ className?: string;
18
+ }
19
+ export declare function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps): import("react/jsx-runtime").JSX.Element;
20
+ //# sourceMappingURL=TaskProgress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskProgress.d.ts","sourceRoot":"","sources":["../../src/data/TaskProgress.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAiB7D,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,cAAc,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC;SAClD;KACF;CACF;AAKD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAyBvF"}
@@ -0,0 +1,125 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { ProgressBar } from "./ProgressBar.js";
5
+ import { bucketTasks, taskStatusColor, taskStatusIcon, taskStatusBg, taskSegments, logLevelColor } from "./task-status.js";
6
+ const MAX_COMPLETED = 5;
7
+ const MAX_PENDING = 3;
8
+ function TaskProgress({ snapshots, title, compact, className }) {
9
+ const groups = snapshots.filter((s) => s.type === "group");
10
+ const tasks = snapshots.filter((s) => s.type === "task");
11
+ if (groups.length === 0) {
12
+ return /* @__PURE__ */ jsxs("div", { className: cn("text-sm text-muted-foreground", className), children: [
13
+ title ? `${title}: ` : "",
14
+ "No tasks yet."
15
+ ] });
16
+ }
17
+ return /* @__PURE__ */ jsxs("div", { className: cn(compact ? "space-y-2" : "space-y-3", className), children: [
18
+ title && /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: title }),
19
+ groups.map((g) => /* @__PURE__ */ jsx(
20
+ TaskGroupCard,
21
+ {
22
+ group: g,
23
+ tasks: tasks.filter((t) => t.groupId === g.groupId || t.group === g.id),
24
+ compact
25
+ },
26
+ g.groupId || g.id
27
+ ))
28
+ ] });
29
+ }
30
+ function isFailedOrWarn(t) {
31
+ return t.status === "failed" || t.status === "FAIL" || t.status === "ERR" || t.status === "warning";
32
+ }
33
+ function TaskGroupCard({
34
+ group: g,
35
+ tasks,
36
+ compact
37
+ }) {
38
+ const [showAll, setShowAll] = useState(false);
39
+ const total = g.total ?? tasks.length;
40
+ const counts = bucketTasks(tasks);
41
+ const done = counts.ok + counts.warn + counts.fail;
42
+ const progress = total > 0 ? `${done}/${total}` : "";
43
+ const running = tasks.filter((t) => t.status === "running");
44
+ const pending = tasks.filter((t) => t.status === "pending");
45
+ const completed = tasks.filter((t) => t.status !== "running" && t.status !== "pending");
46
+ const alwaysShow = completed.filter(isFailedOrWarn);
47
+ const collapsible = completed.filter((t) => !isFailedOrWarn(t));
48
+ const hiddenCompleted = collapsible.length - MAX_COMPLETED;
49
+ const collapseCompleted = !showAll && hiddenCompleted > 0;
50
+ const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;
51
+ const hiddenPending = pending.length - MAX_PENDING;
52
+ const collapsePending = !showAll && hiddenPending > 0;
53
+ const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;
54
+ const isTerminal = g.status !== "running" && g.status !== "pending";
55
+ return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border bg-card", compact ? "p-3" : "p-4"), children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
57
+ /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [
58
+ /* @__PURE__ */ jsx("iconify-icon", { icon: taskStatusIcon(g.status), class: taskStatusColor(g.status) }),
59
+ /* @__PURE__ */ jsx("span", { children: g.name }),
60
+ progress && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: progress }),
61
+ g.kind && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground", children: g.kind })
62
+ ] }),
63
+ isTerminal && /* @__PURE__ */ jsx(
64
+ "span",
65
+ {
66
+ className: cn(
67
+ "rounded-full px-2 py-0.5 text-xs",
68
+ taskStatusColor(g.status),
69
+ taskStatusBg(g.status)
70
+ ),
71
+ children: g.status
72
+ }
73
+ )
74
+ ] }),
75
+ total > 0 && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(ProgressBar, { segments: taskSegments(counts), total, height: "h-1.5" }) }),
76
+ hiddenCompleted > 0 || hiddenPending > 0 ? /* @__PURE__ */ jsx(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ className: "border-b py-1.5 text-xs text-muted-foreground hover:text-foreground",
81
+ onClick: () => setShowAll((v) => !v),
82
+ children: showAll ? "▲ collapse" : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`
83
+ }
84
+ ) : null,
85
+ [...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => /* @__PURE__ */ jsx(TaskRow, { task: t }, t.id))
86
+ ] });
87
+ }
88
+ function TaskRow({ task: t }) {
89
+ const [expanded, setExpanded] = useState(false);
90
+ const logs = t.logs ?? [];
91
+ const hasLogs = logs.length > 0;
92
+ const latestWarn = t.error ? void 0 : logs.filter((l) => l.level === "warn").at(-1);
93
+ return /* @__PURE__ */ jsxs(
94
+ "div",
95
+ {
96
+ className: cn(
97
+ "flex items-start gap-3 border-b py-2 last:border-0",
98
+ hasLogs && "-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50"
99
+ ),
100
+ onClick: hasLogs ? () => setExpanded((v) => !v) : void 0,
101
+ children: [
102
+ /* @__PURE__ */ jsx("iconify-icon", { icon: taskStatusIcon(t.status), class: cn(taskStatusColor(t.status), "mt-0.5 text-lg") }),
103
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
104
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
105
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium", children: [
106
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: t.name }),
107
+ t.error && /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-normal text-red-500", children: t.error }),
108
+ latestWarn && /* @__PURE__ */ jsx("span", { className: cn("truncate text-xs font-normal", logLevelColor("warn")), children: latestWarn.message }),
109
+ hasLogs && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: logs.length })
110
+ ] }),
111
+ t.duration && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: t.duration })
112
+ ] }),
113
+ expanded && hasLogs && /* @__PURE__ */ jsx("div", { className: "mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2", children: logs.map((l, i) => /* @__PURE__ */ jsxs("div", { className: cn("text-xs", logLevelColor(l.level)), children: [
114
+ /* @__PURE__ */ jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
115
+ l.message
116
+ ] }, i)) })
117
+ ] })
118
+ ]
119
+ }
120
+ );
121
+ }
122
+ export {
123
+ TaskProgress
124
+ };
125
+ //# sourceMappingURL=TaskProgress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport type { LogEntry, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\n// Iconify web component, loaded by the host app (same as clicky's task UI). The\n// intrinsic element keeps TaskProgress free of clicky-ui's icon registry.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n \"iconify-icon\": { icon: string; class?: string };\n }\n }\n}\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n}\n\nexport function TaskProgress({ snapshots, title, compact, className }: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => t.groupId === g.groupId || t.group === g.id)}\n compact={compact}\n />\n ))}\n </div>\n );\n}\n\nfunction isFailedOrWarn(t: TaskSnapshot): boolean {\n return t.status === \"failed\" || t.status === \"FAIL\" || t.status === \"ERR\" || t.status === \"warning\";\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n}) {\n const [showAll, setShowAll] = useState(false);\n const total = g.total ?? tasks.length;\n const counts = bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail;\n const progress = total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n <iconify-icon icon={taskStatusIcon(g.status)} class={taskStatusColor(g.status)} />\n <span>{g.name}</span>\n {progress && <span className=\"text-xs text-muted-foreground\">{progress}</span>}\n {g.kind && (\n <span className=\"rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow key={t.id} task={t} />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({ task: t }: { task: TaskSnapshot }) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 border-b py-2 last:border-0\",\n hasLogs && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={hasLogs ? () => setExpanded((v) => !v) : undefined}\n >\n <iconify-icon icon={taskStatusIcon(t.status)} class={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\")} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {hasLogs && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length}\n </span>\n )}\n </span>\n {t.duration && <span className=\"shrink-0 text-xs text-muted-foreground\">{t.duration}</span>}\n </div>\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
4
+ "success",
5
+ "failed",
6
+ "warning",
7
+ "canceled",
8
+ "PASS",
9
+ "FAIL",
10
+ "ERR",
11
+ "SKIP"
12
+ ]);
13
+ function isTerminalStatus(status) {
14
+ return !!status && TERMINAL_STATUSES.has(status);
15
+ }
16
+ function allGroupsTerminal(snapshots) {
17
+ const groups = snapshots.filter((s) => s.type === "group");
18
+ if (groups.length === 0) return false;
19
+ return groups.every((g) => isTerminalStatus(g.status));
20
+ }
21
+ exports.allGroupsTerminal = allGroupsTerminal;
22
+ exports.isTerminalStatus = isTerminalStatus;
23
+ //# sourceMappingURL=TaskSnapshot.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;AA2DA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;;;"}
@@ -0,0 +1,49 @@
1
+ export interface LogEntry {
2
+ level: string;
3
+ message: string;
4
+ }
5
+ export interface TaskSnapshot {
6
+ id: string;
7
+ name: string;
8
+ /** "task" or "group". */
9
+ type: "task" | "group";
10
+ /** Parent group NAME (groups: empty). */
11
+ group?: string;
12
+ status: string;
13
+ duration?: string;
14
+ error?: string;
15
+ /** Latest log line. */
16
+ message?: string;
17
+ logs?: LogEntry[];
18
+ /** Group aggregates. */
19
+ total?: number;
20
+ completed?: number;
21
+ failed?: number;
22
+ running?: number;
23
+ /** Stable parent-run id (tasks) or the run's own id (groups). */
24
+ groupId?: string;
25
+ kind?: string;
26
+ labels?: Record<string, string>;
27
+ owner?: string;
28
+ /** RFC3339. */
29
+ startedAt?: string;
30
+ /** RFC3339. */
31
+ finishedAt?: string;
32
+ }
33
+ export interface TaskRunMeta {
34
+ id: string;
35
+ name: string;
36
+ kind?: string;
37
+ labels?: Record<string, string>;
38
+ owner?: string;
39
+ status: string;
40
+ startedAt?: string;
41
+ finishedAt?: string;
42
+ total: number;
43
+ completed: number;
44
+ failed: number;
45
+ running: number;
46
+ }
47
+ export declare function isTerminalStatus(status: string | undefined): boolean;
48
+ export declare function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean;
49
+ //# sourceMappingURL=TaskSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;IAClB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAID,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAIpE"}
@@ -0,0 +1,23 @@
1
+ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
2
+ "success",
3
+ "failed",
4
+ "warning",
5
+ "canceled",
6
+ "PASS",
7
+ "FAIL",
8
+ "ERR",
9
+ "SKIP"
10
+ ]);
11
+ function isTerminalStatus(status) {
12
+ return !!status && TERMINAL_STATUSES.has(status);
13
+ }
14
+ function allGroupsTerminal(snapshots) {
15
+ const groups = snapshots.filter((s) => s.type === "group");
16
+ if (groups.length === 0) return false;
17
+ return groups.every((g) => isTerminalStatus(g.status));
18
+ }
19
+ export {
20
+ allGroupsTerminal,
21
+ isTerminalStatus
22
+ };
23
+ //# sourceMappingURL=TaskSnapshot.js.map