@runwisp/ui 1.5.1 → 1.7.1

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 (56) hide show
  1. package/package.json +5 -7
  2. package/src/lib/actions/fold-to-fit.test.ts +36 -0
  3. package/src/lib/actions/fold-to-fit.ts +89 -0
  4. package/src/lib/actions/modal-dialog.test.ts +47 -0
  5. package/src/lib/actions/modal-dialog.ts +48 -0
  6. package/src/lib/components/AccordionItem.svelte +16 -5
  7. package/src/lib/components/Badge.svelte +3 -24
  8. package/src/lib/components/Button.svelte +2 -9
  9. package/src/lib/components/CodeBlock.svelte +7 -6
  10. package/src/lib/components/Drawer.svelte +22 -30
  11. package/src/lib/components/DurationInput.svelte +6 -25
  12. package/src/lib/components/FilterBar.svelte +2 -6
  13. package/src/lib/components/Link.svelte +9 -4
  14. package/src/lib/components/LogConsole.svelte +95 -418
  15. package/src/lib/components/Modal.svelte +7 -30
  16. package/src/lib/components/Spinner.svelte +10 -5
  17. package/src/lib/components/StatusIndicator.svelte +37 -104
  18. package/src/lib/components/Tabs.svelte +15 -1
  19. package/src/lib/components/dashboard/RunActions.svelte +156 -0
  20. package/src/lib/components/dashboard/RunConsoleFrame.svelte +319 -0
  21. package/src/lib/components/dashboard/RunDetailPanel.svelte +130 -717
  22. package/src/lib/components/dashboard/RunFacts.svelte +119 -0
  23. package/src/lib/components/dashboard/RunFilterChips.svelte +52 -0
  24. package/src/lib/components/dashboard/RunFilterPopover.svelte +4 -5
  25. package/src/lib/components/dashboard/RunHeaderLine.svelte +295 -0
  26. package/src/lib/components/dashboard/RunListSkeleton.svelte +43 -0
  27. package/src/lib/components/dashboard/RunRow.svelte +202 -0
  28. package/src/lib/components/dashboard/RunsList.svelte +265 -543
  29. package/src/lib/components/dashboard/bulk-selection.svelte.test.ts +42 -0
  30. package/src/lib/components/dashboard/bulk-selection.svelte.ts +50 -0
  31. package/src/lib/components/dashboard/cron-format.test.ts +9 -0
  32. package/src/lib/components/dashboard/cron-format.ts +5 -1
  33. package/src/lib/components/dashboard/run-filters.test.ts +60 -0
  34. package/src/lib/components/dashboard/run-filters.ts +78 -6
  35. package/src/lib/components/dashboard/run-helpers.test.ts +122 -12
  36. package/src/lib/components/dashboard/run-helpers.ts +122 -6
  37. package/src/lib/components/dashboard/status-config.ts +18 -0
  38. package/src/lib/components/dashboard/types.ts +6 -0
  39. package/src/lib/index.ts +13 -2
  40. package/src/lib/log-console/AnsiLine.svelte +12 -0
  41. package/src/lib/log-console/FrameHistoryBlock.svelte +57 -0
  42. package/src/lib/log-console/LogCache.svelte.test.ts +135 -0
  43. package/src/lib/log-console/LogConsoleFooter.svelte +88 -0
  44. package/src/lib/log-console/LogFetcher.svelte.test.ts +159 -0
  45. package/src/lib/log-console/frame-history.svelte.test.ts +47 -0
  46. package/src/lib/log-console/frame-history.svelte.ts +71 -0
  47. package/src/lib/log-console/wrap-layout.svelte.test.ts +92 -0
  48. package/src/lib/log-console/wrap-layout.svelte.ts +188 -0
  49. package/src/lib/utils/format.test.ts +122 -0
  50. package/src/lib/utils/format.ts +97 -4
  51. package/src/lib/utils/toast.svelte.test.ts +46 -0
  52. package/src/lib/utils/typing-target.ts +10 -0
  53. package/src/stories/Accordion.stories.svelte +60 -1
  54. package/src/stories/CodeBlock.stories.svelte +13 -0
  55. package/src/stories/Link.stories.svelte +25 -1
  56. package/src/stories/Tabs.stories.svelte +17 -0
@@ -0,0 +1,202 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ import { displayStatus, type Run } from "@runwisp/common";
6
+ import { RUN_STATUS_CONFIG } from "./status-config.js";
7
+ import {
8
+ formatTriggeredByLabel,
9
+ runRetryLabel,
10
+ runRowReadout,
11
+ runUsageAmounts,
12
+ runUsageLabel,
13
+ usageLevel,
14
+ highlightParts,
15
+ type RunUsageAmounts,
16
+ type RunUsageScale,
17
+ } from "./run-helpers.js";
18
+ import type { RunOutputMatch } from "./types.js";
19
+ import {
20
+ formatDateTime,
21
+ formatFullDateTime,
22
+ formatTimeHM,
23
+ formatDayMonth,
24
+ formatBytes,
25
+ formatDuration,
26
+ } from "../../utils/format.js";
27
+
28
+ let {
29
+ run,
30
+ active,
31
+ suffix,
32
+ showTaskName,
33
+ match,
34
+ outputQuery,
35
+ bulkActions,
36
+ selectionActive,
37
+ usage,
38
+ onselect,
39
+ }: {
40
+ run: Run;
41
+ active: boolean;
42
+ /** Instance suffix of a multi-instance service ("" otherwise). */
43
+ suffix: string;
44
+ showTaskName: boolean;
45
+ /** First output line that matched the active search, if any. */
46
+ match: RunOutputMatch | undefined;
47
+ outputQuery: string;
48
+ bulkActions: boolean;
49
+ selectionActive: boolean;
50
+ /**
51
+ * The task's usage scale when the rail shows CPU/RAM bars, null while
52
+ * no run has usage yet (rows keep the bars' room), undefined for none.
53
+ */
54
+ usage: RunUsageScale | null | undefined;
55
+ onselect: (runId: string) => void;
56
+ } = $props();
57
+
58
+ const dstatus = $derived(displayStatus(run.status, run.endReason));
59
+ const config = $derived(RUN_STATUS_CONFIG[dstatus]);
60
+ const running = $derived(run.status === "running");
61
+ const startedAt = $derived(run.startedAt ?? run.createdAt);
62
+ const retry = $derived(runRetryLabel(run));
63
+ const amounts = $derived(usage ? runUsageAmounts(run) : undefined);
64
+ const tight = $derived(usage === undefined ? "" : "@max-[19rem]:hidden");
65
+ const LEVEL_COLOR = ["bg-success-surface", "bg-warning-surface", "bg-danger-surface"];
66
+ // With bulk actions on, the status dot fades out on row hover, or whenever a
67
+ // selection exists, so the row checkbox can take its place over it.
68
+ const dotFade = $derived(
69
+ bulkActions ? (selectionActive ? "opacity-0" : "group-hover/row:opacity-0") : "",
70
+ );
71
+ </script>
72
+
73
+ <button
74
+ class="btn-scale group @container relative w-full rounded-[3px] border text-left select-none {showTaskName
75
+ ? 'p-3'
76
+ : 'px-3 py-[11px]'} {active
77
+ ? 'border-outline bg-surface-raised shadow-sm'
78
+ : 'border-transparent hover:border-outline-hover hover:bg-surface-sunken'}"
79
+ onclick={() => onselect(run.id)}
80
+ >
81
+ {#if showTaskName}
82
+ <!-- Cross-task /runs variant: the same readout language as the task
83
+ rail, status dot, status-colored outcome, mono right readout,
84
+ with the task name carried as the primary. -->
85
+ <div class="flex items-center gap-2.5">
86
+ {@render dot()}
87
+ <span class="flex min-w-0 flex-1 flex-col gap-0.5">
88
+ <span class="flex items-center gap-1.5">
89
+ <span class="truncate font-mono text-[13px] font-semibold text-on-surface">
90
+ {run.taskName}{#if suffix}<span class="text-on-surface-muted">{suffix}</span
91
+ >{/if}
92
+ </span>
93
+ <span class="shrink-0 text-on-surface-faint">·</span>
94
+ <span
95
+ class="shrink-0 font-mono text-[12px] font-semibold capitalize {config.color}"
96
+ >{dstatus}</span
97
+ >
98
+ </span>
99
+ <span
100
+ class="flex min-w-0 items-center gap-1.5 font-mono text-2xs text-on-surface-faint"
101
+ title={formatFullDateTime(startedAt)}
102
+ >
103
+ <span class="truncate">{formatDateTime(startedAt)}</span>
104
+ <span class="shrink-0">· {formatTriggeredByLabel(run.triggeredBy)}</span>
105
+ {#if retry}
106
+ <span class="shrink-0 rounded bg-surface-sunken px-1 font-mono"
107
+ >{retry}</span
108
+ >
109
+ {/if}
110
+ </span>
111
+ </span>
112
+ <span
113
+ class="shrink-0 self-start pt-0.5 font-mono text-[11.5px] text-on-surface-faint tabular-nums"
114
+ title={retry ?? undefined}
115
+ >
116
+ {runRowReadout(run, dstatus)}
117
+ </span>
118
+ </div>
119
+ {:else}
120
+ <!-- Task-rail variant (artifact ".run"): a single dense line,
121
+ time · date · outcome, with a mono exit/duration readout.
122
+ leading-tight matches the artifact's ~1.2 line-height so the
123
+ (descender-less) text optically centers instead of riding high
124
+ inside Tailwind's default 1.5 line box. -->
125
+ <div class="flex items-center gap-[11px] leading-tight">
126
+ {@render dot()}
127
+ <span class="flex min-w-0 flex-1 items-center gap-1.5 truncate">
128
+ <span
129
+ class="font-mono text-[12.5px] font-semibold tracking-tight text-on-surface tabular-nums"
130
+ title={formatFullDateTime(startedAt)}
131
+ >
132
+ {formatTimeHM(startedAt)} · {formatDayMonth(startedAt)}
133
+ </span>
134
+ <!-- With usage bars a tight rail drops the status word; the dot
135
+ keeps its colour. -->
136
+ <span class="text-on-surface-faint {tight}">·</span>
137
+ <span
138
+ class="font-mono text-[12.5px] font-semibold capitalize {config.color} {tight}"
139
+ >{dstatus}</span
140
+ >
141
+ {#if suffix}
142
+ <span class="font-mono text-2xs text-on-surface-faint">{suffix}</span>
143
+ {/if}
144
+ </span>
145
+ {#if usage && amounts}
146
+ <span
147
+ class="flex w-6 shrink-0 cursor-help flex-col gap-0.5"
148
+ title="{runUsageLabel(run)}. Usual for this task: CPU {formatDuration(
149
+ usage.usual.cpu,
150
+ )} · RAM {formatBytes(usage.usual.ram)}"
151
+ data-testid="run-usage-bars"
152
+ >
153
+ {@render bar(amounts.cpu, usage.max.cpu, usage.usual.cpu, "cpu")}
154
+ {@render bar(amounts.ram, usage.max.ram, usage.usual.ram, "ram")}
155
+ </span>
156
+ {:else if usage !== undefined}
157
+ <span class="w-6 shrink-0" aria-hidden="true"></span>
158
+ {/if}
159
+ <span
160
+ class="shrink-0 font-mono text-[11.5px] text-on-surface-faint tabular-nums"
161
+ title={retry ?? undefined}
162
+ >
163
+ {runRowReadout(run, dstatus)}
164
+ </span>
165
+ </div>
166
+ {#if match}
167
+ {@const hl = highlightParts(match.text, outputQuery)}
168
+ <div
169
+ class="mt-1.5 truncate rounded-[3px] border border-outline-faint bg-surface-sunken px-2 py-1 font-mono text-2xs text-on-surface-muted"
170
+ >
171
+ {hl.before}<mark class="rounded-[3px] bg-primary-soft px-0.5 text-primary-soft-text"
172
+ >{hl.match}</mark
173
+ >{hl.after}
174
+ </div>
175
+ {/if}
176
+ {/if}
177
+
178
+ <div
179
+ class="absolute inset-y-2 left-[-6px] w-[3px] rounded-[3px] {config.solidDot} {active
180
+ ? 'opacity-100'
181
+ : 'opacity-0'}"
182
+ aria-hidden="true"
183
+ ></div>
184
+ </button>
185
+
186
+ {#snippet bar(value: number, max: number, usual: number, kind: keyof RunUsageAmounts)}
187
+ <span class="block h-[3px] overflow-hidden rounded-[1px] bg-outline-faint">
188
+ <span
189
+ class="block h-full {LEVEL_COLOR[usageLevel(value, usual, kind)]}"
190
+ style="width: {String(max > 0 ? Math.max(4, Math.round((100 * value) / max)) : 4)}%"
191
+ ></span>
192
+ </span>
193
+ {/snippet}
194
+
195
+ {#snippet dot()}
196
+ <span
197
+ class="{config.color} size-[9px] shrink-0 rounded-full bg-current ring-[3px] ring-current/20 {running
198
+ ? 'animate-pulse'
199
+ : ''} {dotFade}"
200
+ aria-hidden="true"
201
+ ></span>
202
+ {/snippet}