@runwisp/ui 1.3.0 → 1.5.0
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/package.json +5 -6
- package/src/lib/actions/floating.ts +73 -0
- package/src/lib/actions/portal.ts +5 -1
- package/src/lib/components/Accordion.svelte +6 -2
- package/src/lib/components/AccordionItem.svelte +80 -23
- package/src/lib/components/Avatar.svelte +0 -1
- package/src/lib/components/CodeBlock.svelte +84 -35
- package/src/lib/components/CronInput.svelte +5 -2
- package/src/lib/components/DataGrid.svelte +95 -133
- package/src/lib/components/Drawer.svelte +2 -6
- package/src/lib/components/Dropdown.svelte +19 -57
- package/src/lib/components/EmptyState.svelte +2 -1
- package/src/lib/components/ErrorState.svelte +2 -1
- package/src/lib/components/Heading.svelte +19 -12
- package/src/lib/components/Kbd.svelte +25 -10
- package/src/lib/components/Link.svelte +7 -1
- package/src/lib/components/LogConsole.svelte +37 -34
- package/src/lib/components/Modal.svelte +44 -33
- package/src/lib/components/NotificationBell.svelte +2 -1
- package/src/lib/components/PageContainer.svelte +1 -10
- package/src/lib/components/PageHeader.svelte +8 -4
- package/src/lib/components/Popover.svelte +9 -51
- package/src/lib/components/Progress.svelte +19 -6
- package/src/lib/components/Radio.svelte +16 -4
- package/src/lib/components/RadioGroup.svelte +15 -2
- package/src/lib/components/Select.svelte +13 -45
- package/src/lib/components/Skeleton.svelte +6 -6
- package/src/lib/components/Table.svelte +13 -4
- package/src/lib/components/TableBody.svelte +2 -1
- package/src/lib/components/TableCell.svelte +38 -8
- package/src/lib/components/TableHead.svelte +12 -4
- package/src/lib/components/TableRow.svelte +23 -3
- package/src/lib/components/TaskCard.svelte +1 -1
- package/src/lib/components/TimezoneSelect.svelte +2 -2
- package/src/lib/components/accordion.svelte.ts +19 -0
- package/src/lib/components/dashboard/RunDetailPanel.svelte +141 -67
- package/src/lib/components/dashboard/RunFilterPopover.svelte +7 -16
- package/src/lib/components/dashboard/RunsList.svelte +57 -37
- package/src/lib/components/dashboard/cron-format.ts +1 -1
- package/src/lib/components/dashboard/run-filters.test.ts +2 -58
- package/src/lib/components/dashboard/run-filters.ts +5 -53
- package/src/lib/components/dashboard/run-helpers.test.ts +17 -7
- package/src/lib/components/dashboard/run-helpers.ts +12 -1
- package/src/lib/components/dashboard/status-config.ts +38 -83
- package/src/lib/components/dashboard/types.ts +0 -26
- package/src/lib/components/radio-group.ts +11 -0
- package/src/lib/index.ts +8 -43
- package/src/lib/log-console/LogFetcher.svelte.ts +3 -14
- package/src/lib/log-console/highlight-scroll.test.ts +59 -0
- package/src/lib/log-console/highlight-scroll.ts +41 -0
- package/src/lib/theme.css +10 -4
- package/src/lib/utils/clipboard.svelte.test.ts +92 -0
- package/src/lib/utils/clipboard.svelte.ts +73 -0
- package/src/lib/utils/format.ts +65 -94
- package/src/lib/utils/id.test.ts +3 -7
- package/src/lib/utils/id.ts +3 -2
- package/src/lib/utils/theme.svelte.test.ts +0 -6
- package/src/lib/utils/theme.svelte.ts +3 -5
- package/src/lib/utils/toast.svelte.ts +3 -11
- package/src/stories/Accordion.stories.svelte +61 -0
- package/src/stories/Button.stories.svelte +2 -2
- package/src/stories/CodeBlock.stories.svelte +55 -0
- package/src/stories/DataGrid.stories.svelte +2 -7
- package/src/stories/Dropdown.stories.svelte +3 -3
- package/src/stories/Heading.stories.svelte +31 -0
- package/src/stories/Kbd.stories.svelte +36 -0
- package/src/stories/Link.stories.svelte +38 -0
- package/src/stories/PageHeader.stories.svelte +31 -0
- package/src/stories/Progress.stories.svelte +26 -0
- package/src/stories/RadioGroup.stories.svelte +43 -0
- package/src/stories/Table.stories.svelte +69 -0
- package/src/svelte-shims.d.ts +0 -5
- package/src/lib/components/dashboard/status-config.test.ts +0 -18
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
Check,
|
|
9
9
|
Terminal as TerminalIcon,
|
|
10
10
|
Download,
|
|
11
|
-
|
|
11
|
+
Trash,
|
|
12
12
|
Play,
|
|
13
13
|
Square,
|
|
14
14
|
MousePointerClick,
|
|
15
15
|
RotateCw,
|
|
16
16
|
RefreshCcw,
|
|
17
17
|
ChevronDown,
|
|
18
|
-
|
|
18
|
+
ArrowLeft,
|
|
19
19
|
PanelLeftClose,
|
|
20
|
+
PanelLeftOpen,
|
|
20
21
|
SlidersHorizontal,
|
|
21
22
|
Maximize2,
|
|
22
23
|
Minimize2,
|
|
@@ -25,24 +26,27 @@
|
|
|
25
26
|
} from "@lucide/svelte";
|
|
26
27
|
import Button from "../Button.svelte";
|
|
27
28
|
import EmptyState from "../EmptyState.svelte";
|
|
29
|
+
import Kbd from "../Kbd.svelte";
|
|
28
30
|
import LogConsole from "../LogConsole.svelte";
|
|
29
31
|
import Popover from "../Popover.svelte";
|
|
30
32
|
import Tooltip from "../Tooltip.svelte";
|
|
31
33
|
import { portal } from "../../actions/portal.js";
|
|
32
34
|
import { prefersReducedMotion } from "../../actions/row-motion.js";
|
|
33
35
|
import type { RunMotion } from "../../utils/run-motion.js";
|
|
34
|
-
import type { Run } from "./types.js";
|
|
35
36
|
import { isLogEvent, type LogEvent, type LogSlice } from "../../log-console/types.js";
|
|
36
|
-
import { formatClockTime, formatCalendarDate } from "../../utils/format.js";
|
|
37
|
+
import { formatBytes, formatClockTime, formatCalendarDate } from "../../utils/format.js";
|
|
37
38
|
import { formatShortId } from "../../utils/id.js";
|
|
38
39
|
import { TickingNow } from "../../utils/ticking-now.svelte.js";
|
|
39
|
-
import {
|
|
40
|
+
import { CopyFeedback } from "../../utils/clipboard.svelte.js";
|
|
41
|
+
import { displayStatus, type ResourceUsage, type Run } from "@runwisp/common";
|
|
42
|
+
import { RUN_STATUS_CONFIG } from "./status-config.js";
|
|
40
43
|
import {
|
|
41
44
|
runDuration,
|
|
42
45
|
runStartDelay,
|
|
43
46
|
runVerdict,
|
|
44
47
|
formatTriggeredByLabel,
|
|
45
48
|
runRetryLabel,
|
|
49
|
+
runUsageLabel,
|
|
46
50
|
instanceSuffix,
|
|
47
51
|
} from "./run-helpers.js";
|
|
48
52
|
|
|
@@ -61,12 +65,15 @@
|
|
|
61
65
|
onRestartService,
|
|
62
66
|
serviceStopped = false,
|
|
63
67
|
serviceBusy = false,
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
onBack,
|
|
69
|
+
onToggleList,
|
|
70
|
+
listVisible = true,
|
|
66
71
|
highlightLine = null,
|
|
67
72
|
getInstanceCount = () => 1,
|
|
73
|
+
getLiveUsage = () => undefined,
|
|
68
74
|
motion,
|
|
69
75
|
notFound = false,
|
|
76
|
+
loading = false,
|
|
70
77
|
}: {
|
|
71
78
|
run: Run | undefined;
|
|
72
79
|
fetchLogs: (
|
|
@@ -99,19 +106,25 @@
|
|
|
99
106
|
// cold-start path so a never-run task is still launchable from here.
|
|
100
107
|
onRunTask?: (() => void) | undefined;
|
|
101
108
|
// Service lifecycle controls. When either is set the cluster swaps the
|
|
102
|
-
// Run control for Stop
|
|
109
|
+
// Run control for Stop / Restart (chosen by serviceStopped).
|
|
103
110
|
onStopService?: (() => void) | undefined;
|
|
104
111
|
onRestartService?: (() => void) | undefined;
|
|
105
112
|
serviceStopped?: boolean;
|
|
106
113
|
serviceBusy?: boolean;
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
// Return to the run list when the panel replaces it (a phone), shown as
|
|
115
|
+
// a back arrow in the header's top-left corner.
|
|
116
|
+
onBack?: (() => void) | undefined;
|
|
117
|
+
// Fold the run list away (or back) when it sits beside the panel on a
|
|
118
|
+
// screen too narrow to keep both comfortably, in the same corner.
|
|
119
|
+
onToggleList?: (() => void) | undefined;
|
|
120
|
+
listVisible?: boolean;
|
|
111
121
|
highlightLine?: number | null;
|
|
112
122
|
// Resolves a task's currently configured instance count so multi-instance
|
|
113
123
|
// services render a 1-based #N suffix. Defaults to single-instance.
|
|
114
124
|
getInstanceCount?: (taskName: string) => number;
|
|
125
|
+
// Live CPU and memory of a running run, by run ID; undefined when it
|
|
126
|
+
// isn't measured. Shown in the header while the run is running.
|
|
127
|
+
getLiveUsage?: (runId: string) => ResourceUsage | undefined;
|
|
115
128
|
// Which runs arrived or were removed live moments ago. The panel eases in
|
|
116
129
|
// when a new run takes it (triggered, or a scheduled run auto-selected)
|
|
117
130
|
// or when its run was deleted and another takes its place; picking a
|
|
@@ -121,6 +134,9 @@
|
|
|
121
134
|
// or never existed). The empty state then says so plainly instead of the
|
|
122
135
|
// generic "Select a run" — the caller must not silently substitute another.
|
|
123
136
|
notFound?: boolean;
|
|
137
|
+
// True while there is no run to show *yet* (the run list or a deep-linked
|
|
138
|
+
// run is still loading), so the empty state doesn't claim "No runs yet".
|
|
139
|
+
loading?: boolean;
|
|
124
140
|
} = $props();
|
|
125
141
|
|
|
126
142
|
function easeInFresh(node: HTMLElement, runId: string) {
|
|
@@ -144,7 +160,7 @@
|
|
|
144
160
|
|
|
145
161
|
let canDelete = $derived.by(() => {
|
|
146
162
|
if (!run || !onDelete) return false;
|
|
147
|
-
const status =
|
|
163
|
+
const status = displayStatus(run.status, run.endReason);
|
|
148
164
|
return status !== "running" && status !== "pending";
|
|
149
165
|
});
|
|
150
166
|
|
|
@@ -170,6 +186,10 @@
|
|
|
170
186
|
// changes, not on every run object reference swap from SSE array updates.
|
|
171
187
|
let runId = $derived(run?.id);
|
|
172
188
|
|
|
189
|
+
// The run whose seed fetch has settled. Until it matches the shown run the
|
|
190
|
+
// console says it's loading, not "No output yet".
|
|
191
|
+
let seededRunId = $state<string | null>(null);
|
|
192
|
+
|
|
173
193
|
// Fetches the batched tail page a run's console opens on (see the effect
|
|
174
194
|
// below) and paints it into the console. Resolves the line the live
|
|
175
195
|
// stream should resume from, or null when there's nothing left to stream
|
|
@@ -201,7 +221,10 @@
|
|
|
201
221
|
if (!id) return;
|
|
202
222
|
|
|
203
223
|
const stream = streamLogs;
|
|
204
|
-
if (!stream)
|
|
224
|
+
if (!stream) {
|
|
225
|
+
seededRunId = id;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
205
228
|
|
|
206
229
|
logFetchError = null;
|
|
207
230
|
|
|
@@ -217,7 +240,9 @@
|
|
|
217
240
|
// (its disk backfill from that anchor closes the fetch↔stream
|
|
218
241
|
// race, and the daemon dedupes anything already shown).
|
|
219
242
|
const seeded = await seedConsoleTail(id, () => cancelled);
|
|
220
|
-
if (cancelled
|
|
243
|
+
if (cancelled) return;
|
|
244
|
+
seededRunId = id;
|
|
245
|
+
if (!seeded) return;
|
|
221
246
|
cleanup = stream(
|
|
222
247
|
id,
|
|
223
248
|
(event: LogEvent) => {
|
|
@@ -253,19 +278,7 @@
|
|
|
253
278
|
let confirmDeleteOpen = $state(false);
|
|
254
279
|
// Dropdown half of the Run split button (the "reuse parameters" variant).
|
|
255
280
|
let runMenuOpen = $state(false);
|
|
256
|
-
|
|
257
|
-
let copyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
258
|
-
|
|
259
|
-
async function copyRunId(id: string) {
|
|
260
|
-
try {
|
|
261
|
-
await navigator.clipboard.writeText(id);
|
|
262
|
-
copiedId = true;
|
|
263
|
-
if (copyTimer) clearTimeout(copyTimer);
|
|
264
|
-
copyTimer = setTimeout(() => (copiedId = false), 1200);
|
|
265
|
-
} catch {
|
|
266
|
-
// Clipboard blocked (insecure context / denied) — leave the chip as-is.
|
|
267
|
-
}
|
|
268
|
-
}
|
|
281
|
+
const runIdCopy = new CopyFeedback(1200);
|
|
269
282
|
|
|
270
283
|
// The accent for a status that means "something to triage", or undefined
|
|
271
284
|
// when there is nothing wrong. Tint is reserved for alarms (DESIGN.md) so a
|
|
@@ -349,8 +362,8 @@
|
|
|
349
362
|
<svelte:window onkeydown={handleConsoleKeydown} />
|
|
350
363
|
|
|
351
364
|
{#if run}
|
|
352
|
-
{@const status =
|
|
353
|
-
{@const config =
|
|
365
|
+
{@const status = displayStatus(run.status, run.endReason)}
|
|
366
|
+
{@const config = RUN_STATUS_CONFIG[status]}
|
|
354
367
|
{@const DetailIcon = config.icon}
|
|
355
368
|
{@const duration = runDuration(
|
|
356
369
|
run,
|
|
@@ -359,6 +372,8 @@
|
|
|
359
372
|
{@const startDelay = runStartDelay(run)}
|
|
360
373
|
{@const startedAt = run.startedAt ?? run.createdAt}
|
|
361
374
|
{@const retry = runRetryLabel(run)}
|
|
375
|
+
{@const usage = runUsageLabel(run)}
|
|
376
|
+
{@const live = run.status === "running" ? getLiveUsage(run.id) : undefined}
|
|
362
377
|
{@const paramEntries = run.params ? Object.entries(run.params) : []}
|
|
363
378
|
{@const suffix = instanceSuffix(run.instanceIndex, getInstanceCount(run.taskName))}
|
|
364
379
|
{@const spine = config.dot.replace(" animate-pulse", "")}
|
|
@@ -403,8 +418,36 @@
|
|
|
403
418
|
style="--rw-oc: {alarm ?? 'var(--color-surface-raised)'}"
|
|
404
419
|
>
|
|
405
420
|
<div class="pt-[18px] pr-[22px] pb-[16px] pl-[26px]">
|
|
406
|
-
|
|
407
|
-
|
|
421
|
+
<!-- The actions wrap onto their own row rather than squeeze the
|
|
422
|
+
readout below a legible width on a narrow panel. -->
|
|
423
|
+
<div class="flex flex-wrap items-start gap-x-3 gap-y-3">
|
|
424
|
+
{#if onBack}
|
|
425
|
+
<button
|
|
426
|
+
type="button"
|
|
427
|
+
onclick={() => onBack()}
|
|
428
|
+
class="-mt-1 -ml-2 shrink-0 rounded-[3px] p-1.5 text-on-surface-muted hover:bg-surface-sunken hover:text-primary"
|
|
429
|
+
title="Back to runs"
|
|
430
|
+
aria-label="Back to runs"
|
|
431
|
+
>
|
|
432
|
+
<ArrowLeft size={20} />
|
|
433
|
+
</button>
|
|
434
|
+
{:else if onToggleList}
|
|
435
|
+
<button
|
|
436
|
+
type="button"
|
|
437
|
+
onclick={() => onToggleList()}
|
|
438
|
+
class="-mt-1 -ml-2 shrink-0 rounded-[3px] p-1.5 text-on-surface-muted hover:bg-surface-sunken hover:text-primary"
|
|
439
|
+
title={listVisible ? "Hide run list" : "Show run list"}
|
|
440
|
+
aria-label={listVisible ? "Hide run list" : "Show run list"}
|
|
441
|
+
aria-expanded={listVisible}
|
|
442
|
+
>
|
|
443
|
+
{#if listVisible}
|
|
444
|
+
<PanelLeftClose size={20} />
|
|
445
|
+
{:else}
|
|
446
|
+
<PanelLeftOpen size={20} />
|
|
447
|
+
{/if}
|
|
448
|
+
</button>
|
|
449
|
+
{/if}
|
|
450
|
+
<div class="min-w-60 flex-1">
|
|
408
451
|
<!-- Identity, but only where the page around the panel isn't
|
|
409
452
|
already saying it: the cross-task runs list needs the task
|
|
410
453
|
name, a task's own page already has it in the breadcrumb. -->
|
|
@@ -531,16 +574,35 @@
|
|
|
531
574
|
</div>
|
|
532
575
|
</Popover>
|
|
533
576
|
{/if}
|
|
577
|
+
{#if live}
|
|
578
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
579
|
+
<span
|
|
580
|
+
data-testid="run-live-usage"
|
|
581
|
+
class="text-on-surface-muted"
|
|
582
|
+
title="Live CPU (100% = one core) and memory of the run's processes"
|
|
583
|
+
>CPU {Math.round(live.cpuPercent)}% · {formatBytes(
|
|
584
|
+
live.memoryBytes,
|
|
585
|
+
)}</span
|
|
586
|
+
>
|
|
587
|
+
{/if}
|
|
588
|
+
{#if usage}
|
|
589
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
590
|
+
<span
|
|
591
|
+
data-testid="run-usage"
|
|
592
|
+
title="Peak memory and CPU time of the run's processes"
|
|
593
|
+
>{usage}</span
|
|
594
|
+
>
|
|
595
|
+
{/if}
|
|
534
596
|
<!-- Run-id chip: click to copy the full ULID. Last, because
|
|
535
597
|
it is the fact you reach for least and the only one you
|
|
536
598
|
interact with. -->
|
|
537
599
|
<button
|
|
538
600
|
type="button"
|
|
539
|
-
onclick={() =>
|
|
601
|
+
onclick={() => void runIdCopy.copy(run.id)}
|
|
540
602
|
title="Copy run ID"
|
|
541
603
|
class="inline-flex items-center gap-1 rounded-[3px] border border-outline-faint bg-surface-sunken px-1.5 text-on-surface-faint hover:border-outline-hover hover:text-primary"
|
|
542
604
|
>
|
|
543
|
-
{#if
|
|
605
|
+
{#if runIdCopy.copied}
|
|
544
606
|
<Check size={11} class="text-success-surface" />Copied
|
|
545
607
|
{:else}
|
|
546
608
|
<Hash size={11} />{run.id}
|
|
@@ -549,27 +611,9 @@
|
|
|
549
611
|
</div>
|
|
550
612
|
</div>
|
|
551
613
|
|
|
552
|
-
<!-- Actions:
|
|
614
|
+
<!-- Actions: run (split) / service lifecycle ·
|
|
553
615
|
stop · download · delete (delete behind an inline confirm) -->
|
|
554
|
-
<div class="flex
|
|
555
|
-
{#if onToggleHistory}
|
|
556
|
-
<button
|
|
557
|
-
type="button"
|
|
558
|
-
onclick={() => onToggleHistory()}
|
|
559
|
-
class="inline-flex items-center justify-center rounded-[3px] border border-outline-faint bg-surface-raised p-2 text-on-surface-muted hover:border-outline-hover hover:bg-surface-sunken hover:text-primary"
|
|
560
|
-
title={historyVisible ? "Hide run history" : "Show run history"}
|
|
561
|
-
aria-label={historyVisible
|
|
562
|
-
? "Hide run history"
|
|
563
|
-
: "Show run history"}
|
|
564
|
-
>
|
|
565
|
-
{#if historyVisible}
|
|
566
|
-
<PanelLeftClose size={15} />
|
|
567
|
-
{:else}
|
|
568
|
-
<RotateCcwClock size={15} />
|
|
569
|
-
{/if}
|
|
570
|
-
</button>
|
|
571
|
-
{/if}
|
|
572
|
-
|
|
616
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
573
617
|
{#if onStopService || onRestartService}
|
|
574
618
|
<!-- Service lifecycle replaces the run control.
|
|
575
619
|
Restart is always available (it cancels + respawns
|
|
@@ -591,7 +635,7 @@
|
|
|
591
635
|
{:else}
|
|
592
636
|
<RefreshCcw size={15} />
|
|
593
637
|
{/if}
|
|
594
|
-
<span class="@max-
|
|
638
|
+
<span class="@max-xs:hidden"
|
|
595
639
|
>{serviceStopped ? "Start" : "Restart"}</span
|
|
596
640
|
>
|
|
597
641
|
</button>
|
|
@@ -605,7 +649,7 @@
|
|
|
605
649
|
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-[3px] border border-danger-soft-border bg-surface-raised px-3 font-mono text-sm font-medium text-danger-surface hover:bg-danger-soft active:translate-y-px disabled:cursor-not-allowed disabled:opacity-60"
|
|
606
650
|
>
|
|
607
651
|
<Square size={15} fill="currentColor" stroke="none" />
|
|
608
|
-
<span class="@max-
|
|
652
|
+
<span class="@max-xs:hidden">Stop</span>
|
|
609
653
|
</button>
|
|
610
654
|
{/if}
|
|
611
655
|
{:else}
|
|
@@ -622,7 +666,7 @@
|
|
|
622
666
|
class="inline-flex h-9 cursor-pointer items-center gap-1.5 bg-surface-raised px-3 font-mono text-sm font-medium text-primary hover:bg-primary-soft active:translate-y-px"
|
|
623
667
|
>
|
|
624
668
|
<Play size={15} fill="currentColor" stroke="none" />
|
|
625
|
-
<span class="@max-
|
|
669
|
+
<span class="@max-xs:hidden">Run</span>
|
|
626
670
|
</button>
|
|
627
671
|
{#if onRunAgain}
|
|
628
672
|
<Popover bind:open={runMenuOpen} placement="bottom-end">
|
|
@@ -668,7 +712,7 @@
|
|
|
668
712
|
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-[3px] border border-danger-soft-border bg-surface-raised px-3 font-mono text-sm font-medium text-danger-surface hover:bg-danger-soft active:translate-y-px"
|
|
669
713
|
>
|
|
670
714
|
<Square size={15} fill="currentColor" stroke="none" />
|
|
671
|
-
<span class="@max-
|
|
715
|
+
<span class="@max-xs:hidden">Stop</span>
|
|
672
716
|
</button>
|
|
673
717
|
{/if}
|
|
674
718
|
{/if}
|
|
@@ -691,7 +735,7 @@
|
|
|
691
735
|
title="Delete this run"
|
|
692
736
|
aria-label="Delete run"
|
|
693
737
|
>
|
|
694
|
-
<
|
|
738
|
+
<Trash size={15} />
|
|
695
739
|
</span>
|
|
696
740
|
{/snippet}
|
|
697
741
|
<div class="w-60">
|
|
@@ -745,7 +789,7 @@
|
|
|
745
789
|
: "ml-1 flex min-h-[300px] flex-1 flex-col overflow-hidden border-t border-[var(--rw-con-gutter)] bg-[var(--rw-con-bg)]"}
|
|
746
790
|
>
|
|
747
791
|
<div
|
|
748
|
-
class="flex shrink-0 items-center justify-between gap-3 border-b border-[var(--rw-con-gutter)] bg-[var(--rw-con-panel)] px-3.5 py-[9px] font-mono text-[11.5px] text-[var(--rw-con-dim)]"
|
|
792
|
+
class="@container flex shrink-0 items-center justify-between gap-3 border-b border-[var(--rw-con-gutter)] bg-[var(--rw-con-panel)] px-3.5 py-[9px] font-mono text-[11.5px] text-[var(--rw-con-dim)]"
|
|
749
793
|
>
|
|
750
794
|
<div class="flex min-w-0 items-center gap-3">
|
|
751
795
|
{#if consoleMaximized}
|
|
@@ -760,12 +804,12 @@
|
|
|
760
804
|
</span>
|
|
761
805
|
{:else}
|
|
762
806
|
<span
|
|
763
|
-
class="flex items-center gap-2 font-semibold text-[var(--rw-con-text)]"
|
|
807
|
+
class="flex items-center gap-2 font-semibold whitespace-nowrap text-[var(--rw-con-text)]"
|
|
764
808
|
>
|
|
765
809
|
<TerminalIcon size={14} class="opacity-70" />
|
|
766
810
|
Console output
|
|
767
811
|
</span>
|
|
768
|
-
<span class="hidden text-[var(--rw-con-gutter)]
|
|
812
|
+
<span class="hidden text-[var(--rw-con-gutter)] @lg:inline"
|
|
769
813
|
>stdout + stderr</span
|
|
770
814
|
>
|
|
771
815
|
{/if}
|
|
@@ -782,7 +826,7 @@
|
|
|
782
826
|
aria-label="Toggle line wrapping"
|
|
783
827
|
>
|
|
784
828
|
<TextWrap size={13} />
|
|
785
|
-
<span class="hidden sm:inline">Wrap</span>
|
|
829
|
+
<span class="hidden @sm:inline">Wrap</span>
|
|
786
830
|
</button>
|
|
787
831
|
<button
|
|
788
832
|
type="button"
|
|
@@ -793,10 +837,7 @@
|
|
|
793
837
|
>
|
|
794
838
|
{#if consoleMaximized}
|
|
795
839
|
<Minimize2 size={13} />
|
|
796
|
-
<
|
|
797
|
-
class="rounded-[3px] border border-[var(--rw-con-gutter)] px-1.5 text-[10px] tracking-wide"
|
|
798
|
-
>Esc</span
|
|
799
|
-
>
|
|
840
|
+
<Kbd keys="Esc" size="xs" tone="console" />
|
|
800
841
|
{:else}
|
|
801
842
|
<Maximize2 size={13} />
|
|
802
843
|
Expand
|
|
@@ -817,10 +858,43 @@
|
|
|
817
858
|
{endLabel}
|
|
818
859
|
{endTone}
|
|
819
860
|
error={logFetchError}
|
|
861
|
+
loading={seededRunId !== run.id}
|
|
820
862
|
/>
|
|
821
863
|
{/key}
|
|
822
864
|
</div>
|
|
823
865
|
</div>
|
|
866
|
+
{:else if loading && !notFound}
|
|
867
|
+
<!-- Loading: the panel's own frame (verdict header over the console) with
|
|
868
|
+
placeholders where the run's facts go, so nothing jumps when it lands. -->
|
|
869
|
+
<div
|
|
870
|
+
class="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"
|
|
871
|
+
role="status"
|
|
872
|
+
aria-label="Loading run"
|
|
873
|
+
>
|
|
874
|
+
<div class="shrink-0 border-b border-outline-faint bg-surface-raised">
|
|
875
|
+
<div class="pt-[18px] pr-[22px] pb-[16px] pl-[26px]">
|
|
876
|
+
<div class="flex h-[33px] items-center gap-2.5">
|
|
877
|
+
<span class="size-[18px] animate-pulse rounded-full bg-outline-hover"></span>
|
|
878
|
+
<span class="h-[18px] w-40 animate-pulse rounded-[3px] bg-outline-hover"></span>
|
|
879
|
+
</div>
|
|
880
|
+
<div class="mt-2.5 flex h-[17px] items-center">
|
|
881
|
+
<span class="h-3 w-64 max-w-full animate-pulse rounded-[3px] bg-outline-hover"
|
|
882
|
+
></span>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
<div
|
|
887
|
+
class="ml-1 flex min-h-[300px] flex-1 flex-col overflow-hidden border-t border-[var(--rw-con-gutter)] bg-[var(--rw-con-bg)]"
|
|
888
|
+
>
|
|
889
|
+
<div
|
|
890
|
+
class="flex shrink-0 items-center gap-2 border-b border-[var(--rw-con-gutter)] bg-[var(--rw-con-panel)] px-3.5 py-[9px] font-mono text-[11.5px] font-semibold text-[var(--rw-con-text)]"
|
|
891
|
+
>
|
|
892
|
+
<TerminalIcon size={14} class="opacity-70" />
|
|
893
|
+
Console output
|
|
894
|
+
</div>
|
|
895
|
+
<LogConsole fetchLogs={() => undefined} loading class="min-h-0 flex-1" />
|
|
896
|
+
</div>
|
|
897
|
+
</div>
|
|
824
898
|
{:else}
|
|
825
899
|
<div
|
|
826
900
|
class="flex min-w-0 flex-1 flex-col items-center justify-center gap-4 bg-surface-sunken/30"
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
dayStartIso,
|
|
41
41
|
dayEndIso,
|
|
42
42
|
isExitCodeExprValid,
|
|
43
|
-
exitCodePropChanged,
|
|
44
43
|
type RunsListFilters,
|
|
45
44
|
} from "./run-filters.js";
|
|
46
45
|
|
|
@@ -48,13 +47,10 @@
|
|
|
48
47
|
filters = $bindable(),
|
|
49
48
|
showTask = false,
|
|
50
49
|
tasks = [],
|
|
51
|
-
compact = false,
|
|
52
50
|
}: {
|
|
53
51
|
filters: RunsListFilters;
|
|
54
52
|
showTask?: boolean;
|
|
55
53
|
tasks?: { name: string }[];
|
|
56
|
-
// Icon-only trigger for narrow rails where a "Filter" label wouldn't fit.
|
|
57
|
-
compact?: boolean;
|
|
58
54
|
} = $props();
|
|
59
55
|
|
|
60
56
|
// The individual statuses behind the "Advanced" expander, grouped the way
|
|
@@ -121,18 +117,14 @@
|
|
|
121
117
|
filters = { ...filters, triggeredBy: value || undefined };
|
|
122
118
|
}
|
|
123
119
|
|
|
124
|
-
// Exit
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// buffer does NOT re-seed on its own — it must explicitly resync whenever
|
|
129
|
-
// `filters.exitCode` changes from outside (a chip removal, "clear
|
|
130
|
-
// filters"), without clobbering in-progress typing when some unrelated
|
|
131
|
-
// filter dimension changes instead.
|
|
120
|
+
// Exit-code text is edited in a local buffer and committed on change. The
|
|
121
|
+
// component outlives the popover, so the buffer resyncs only when the
|
|
122
|
+
// committed `exitCode` itself moves (chip removed, filters cleared); an
|
|
123
|
+
// update to another dimension must not clobber in-progress typing.
|
|
132
124
|
let exitCodeInput = $state(filters.exitCode ?? "");
|
|
133
125
|
let lastExitCode = filters.exitCode;
|
|
134
126
|
$effect(() => {
|
|
135
|
-
if (
|
|
127
|
+
if (lastExitCode !== filters.exitCode) {
|
|
136
128
|
exitCodeInput = filters.exitCode ?? "";
|
|
137
129
|
}
|
|
138
130
|
lastExitCode = filters.exitCode;
|
|
@@ -152,8 +144,8 @@
|
|
|
152
144
|
<Popover placement="right-start" mobileSheet>
|
|
153
145
|
{#snippet trigger()}
|
|
154
146
|
<span
|
|
155
|
-
class="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-[3px] border font-mono text-xs font-medium {
|
|
156
|
-
|
|
147
|
+
class="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-[3px] border font-mono text-xs font-medium {count ===
|
|
148
|
+
0
|
|
157
149
|
? 'w-7 justify-center px-0'
|
|
158
150
|
: 'px-2'} {count > 0
|
|
159
151
|
? 'border-primary-soft-border bg-primary-soft text-primary-soft-text'
|
|
@@ -161,7 +153,6 @@
|
|
|
161
153
|
title="Filter runs"
|
|
162
154
|
>
|
|
163
155
|
<Funnel size={13} />
|
|
164
|
-
{#if !compact}Filter{/if}
|
|
165
156
|
{#if count > 0}
|
|
166
157
|
<Badge variant="primary" size="sm" class="px-1.5 py-0">{count}</Badge>
|
|
167
158
|
{/if}
|