@runwisp/ui 0.13.0 → 0.15.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 +39 -47
- package/src/astro/CTABand.astro +19 -12
- package/src/astro/Hero.astro +3 -3
- package/src/astro/SiteFooter.astro +4 -4
- package/src/astro/SiteHeader.astro +4 -4
- package/src/lib/actions/portal.ts +0 -3
- package/src/lib/assets/runwisp-logo.svg +1 -8
- package/src/lib/components/AccordionItem.svelte +4 -10
- package/src/lib/components/Alert.svelte +11 -11
- package/src/lib/components/Avatar.svelte +1 -1
- package/src/lib/components/Badge.svelte +13 -13
- package/src/lib/components/Breadcrumb.svelte +3 -3
- package/src/lib/components/Button.svelte +2 -2
- package/src/lib/components/Card.svelte +4 -4
- package/src/lib/components/Checkbox.svelte +12 -10
- package/src/lib/components/CodeBlock.svelte +7 -13
- package/src/lib/components/CronInput.svelte +12 -15
- package/src/lib/components/DataGrid.svelte +14 -13
- package/src/lib/components/Drawer.svelte +9 -9
- package/src/lib/components/Dropdown.svelte +12 -20
- package/src/lib/components/DurationInput.svelte +10 -14
- package/src/lib/components/EmptyState.svelte +1 -1
- package/src/lib/components/ErrorState.svelte +4 -4
- package/src/lib/components/FeatureCard.svelte +4 -4
- package/src/lib/components/FormField.svelte +4 -6
- package/src/lib/components/Heading.svelte +4 -1
- package/src/lib/components/Input.svelte +10 -12
- package/src/lib/components/Kbd.svelte +1 -1
- package/src/lib/components/Link.svelte +2 -3
- package/src/lib/components/LinkButton.svelte +2 -2
- package/src/lib/components/LogConsole.svelte +22 -21
- package/src/lib/components/Logo.svelte +3 -1
- package/src/lib/components/Modal.svelte +9 -11
- package/src/lib/components/PageContainer.svelte +1 -1
- package/src/lib/components/PageHeader.svelte +1 -1
- package/src/lib/components/Pagination.svelte +4 -4
- package/src/lib/components/Popover.svelte +5 -7
- package/src/lib/components/Progress.svelte +6 -4
- package/src/lib/components/Prose.svelte +4 -4
- package/src/lib/components/Radio.svelte +11 -10
- package/src/lib/components/RadioGroup.svelte +3 -5
- package/src/lib/components/Select.svelte +23 -36
- package/src/lib/components/Separator.svelte +4 -1
- package/src/lib/components/Skeleton.svelte +6 -6
- package/src/lib/components/StatusIndicator.svelte +7 -18
- package/src/lib/components/Table.svelte +1 -1
- package/src/lib/components/TableCell.svelte +1 -1
- package/src/lib/components/TableRow.svelte +1 -5
- package/src/lib/components/Tabs.svelte +10 -13
- package/src/lib/components/TaskCard.svelte +7 -4
- package/src/lib/components/Textarea.svelte +10 -11
- package/src/lib/components/ThemeToggle.svelte +1 -1
- package/src/lib/components/ToastContainer.svelte +3 -8
- package/src/lib/components/Toggle.svelte +2 -3
- package/src/lib/components/Tooltip.svelte +12 -10
- package/src/lib/components/button-styles.ts +17 -18
- package/src/lib/components/dashboard/RunDetailPanel.svelte +160 -249
- package/src/lib/components/dashboard/RunFilterPopover.svelte +31 -25
- package/src/lib/components/dashboard/RunsList.svelte +55 -50
- package/src/lib/components/dashboard/run-filters.test.ts +31 -33
- package/src/lib/components/dashboard/run-filters.ts +33 -33
- package/src/lib/components/dashboard/run-helpers.test.ts +42 -17
- package/src/lib/components/dashboard/run-helpers.ts +53 -17
- package/src/lib/components/dashboard/status-config.ts +9 -8
- package/src/lib/theme-tokens.css +179 -114
- package/src/lib/theme.css +49 -35
- package/src/lib/utils/error.ts +0 -3
- package/src/lib/utils/format.test.ts +2 -6
- package/src/lib/utils/format.ts +28 -3
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
RotateCw,
|
|
16
16
|
RefreshCcw,
|
|
17
17
|
ChevronDown,
|
|
18
|
-
|
|
18
|
+
RotateCcwClock,
|
|
19
19
|
PanelLeftClose,
|
|
20
20
|
SlidersHorizontal,
|
|
21
21
|
Maximize2,
|
|
@@ -31,19 +31,14 @@
|
|
|
31
31
|
import { portal } from "../../actions/portal.js";
|
|
32
32
|
import type { Run } from "./types.js";
|
|
33
33
|
import type { LogEvent, LogSlice } from "../../log-console/types.js";
|
|
34
|
-
import {
|
|
35
|
-
formatDateTime,
|
|
36
|
-
formatFullDateTime,
|
|
37
|
-
formatClockTime,
|
|
38
|
-
formatCalendarDate,
|
|
39
|
-
formatTimeHM,
|
|
40
|
-
} from "../../utils/format.js";
|
|
34
|
+
import { formatClockTime, formatCalendarDate } from "../../utils/format.js";
|
|
41
35
|
import { formatShortId } from "../../utils/id.js";
|
|
42
36
|
import { TickingNow } from "../../utils/ticking-now.svelte.js";
|
|
43
37
|
import { getRunStatusConfig, runDisplayStatus } from "./status-config.js";
|
|
44
38
|
import {
|
|
45
39
|
runDuration,
|
|
46
40
|
runStartDelay,
|
|
41
|
+
runVerdict,
|
|
47
42
|
formatTriggeredByLabel,
|
|
48
43
|
runRetryLabel,
|
|
49
44
|
instanceSuffix,
|
|
@@ -204,41 +199,12 @@
|
|
|
204
199
|
}
|
|
205
200
|
}
|
|
206
201
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (displayed === "failed") return "non-zero";
|
|
214
|
-
if (displayed === "stopped" || displayed === "daemon_stopped") return "stopped before exit";
|
|
215
|
-
if (displayed === "timeout") return "timed out";
|
|
216
|
-
if (displayed === "crashed") return "killed";
|
|
217
|
-
return "no exit code";
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// One-word context line under each Trigger value, mirroring the source.
|
|
221
|
-
function triggerSub(triggeredBy: Run["triggered_by"]): string {
|
|
222
|
-
if (triggeredBy === "cron") return "schedule";
|
|
223
|
-
if (triggeredBy === "api") return "manual";
|
|
224
|
-
if (triggeredBy === "service") return "supervised";
|
|
225
|
-
if (triggeredBy === "startup") return "on boot";
|
|
226
|
-
return "remote";
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// The status's accent color as a runtime CSS variable, used to wash the
|
|
230
|
-
// header in a faint tint of the outcome (the "black-box readout" look).
|
|
231
|
-
// Neutral statuses resolve to the surface itself, so the tint vanishes.
|
|
232
|
-
function accentVar(displayed: string): string {
|
|
233
|
-
if (displayed === "running" || displayed === "pending") return "var(--color-info)";
|
|
234
|
-
if (displayed === "success") return "var(--color-success-surface)";
|
|
235
|
-
if (
|
|
236
|
-
displayed === "stopped" ||
|
|
237
|
-
displayed === "timeout" ||
|
|
238
|
-
displayed === "daemon_stopped" ||
|
|
239
|
-
displayed === "queue_full"
|
|
240
|
-
)
|
|
241
|
-
return "var(--color-warning-surface)";
|
|
202
|
+
// The accent for a status that means "something to triage", or undefined
|
|
203
|
+
// when there is nothing wrong. Tint is reserved for alarms (DESIGN.md) so a
|
|
204
|
+
// failure is the one lit thing on the page — a deliberate stop or a skip is
|
|
205
|
+
// not a failure, and success least of all. Doubles as the switch for the
|
|
206
|
+
// verdict phrase's colour, so wash and wording light up together.
|
|
207
|
+
function alarmAccent(displayed: string): string | undefined {
|
|
242
208
|
if (
|
|
243
209
|
displayed === "failed" ||
|
|
244
210
|
displayed === "crashed" ||
|
|
@@ -247,7 +213,9 @@
|
|
|
247
213
|
displayed === "start_failed"
|
|
248
214
|
)
|
|
249
215
|
return "var(--color-danger-surface)";
|
|
250
|
-
|
|
216
|
+
if (displayed === "timeout" || displayed === "daemon_stopped" || displayed === "queue_full")
|
|
217
|
+
return "var(--color-warning-surface)";
|
|
218
|
+
return undefined;
|
|
251
219
|
}
|
|
252
220
|
|
|
253
221
|
function handleConsoleKeydown(event: KeyboardEvent) {
|
|
@@ -320,15 +288,16 @@
|
|
|
320
288
|
run.status === "running" ? durationTicker.now.getTime() : undefined,
|
|
321
289
|
)}
|
|
322
290
|
{@const startDelay = runStartDelay(run)}
|
|
323
|
-
{@const startedAt = run.
|
|
291
|
+
{@const startedAt = run.startAt ?? run.createdAt}
|
|
324
292
|
{@const retry = runRetryLabel(run)}
|
|
325
293
|
{@const paramEntries = run.params ? Object.entries(run.params) : []}
|
|
326
|
-
{@const suffix = instanceSuffix(run.
|
|
294
|
+
{@const suffix = instanceSuffix(run.instanceIndex, getInstanceCount(run.taskName))}
|
|
327
295
|
{@const spine = config.dot.replace(" animate-pulse", "")}
|
|
328
296
|
{@const isRunning = run.status === "running"}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
{@const
|
|
297
|
+
<!-- A code is worth the ink only when it is news: `exit 0` restates
|
|
298
|
+
"succeeded", while a non-zero code is the first thing to triage on. -->
|
|
299
|
+
{@const showCode = status === "failed"}
|
|
300
|
+
{@const verdict = runVerdict(status)}
|
|
332
301
|
{@const endLabel =
|
|
333
302
|
status === "stopped"
|
|
334
303
|
? "run stopped by operator"
|
|
@@ -341,7 +310,7 @@
|
|
|
341
310
|
status === "stopped" || status === "daemon_stopped" || status === "timeout"
|
|
342
311
|
? "warn"
|
|
343
312
|
: "muted"}
|
|
344
|
-
{@const
|
|
313
|
+
{@const alarm = alarmAccent(status)}
|
|
345
314
|
<!-- The panel: a status spine runs the full left edge across both the header
|
|
346
315
|
readout and the console below, hugging the rail divider (artifact
|
|
347
316
|
".detail .spine"). -->
|
|
@@ -352,102 +321,125 @@
|
|
|
352
321
|
class="absolute inset-y-0 left-0 z-[3] w-1 {spine} {isRunning ? 'spine-flow' : ''}"
|
|
353
322
|
aria-hidden="true"
|
|
354
323
|
></div>
|
|
355
|
-
<!-- Detailed header: a
|
|
356
|
-
run
|
|
324
|
+
<!-- Detailed header: a readout whose ink scales with how much went wrong.
|
|
325
|
+
Every run states its outcome in one phrase over one quiet fact line;
|
|
326
|
+
only a run worth triaging colours the phrase and tints the surface.
|
|
327
|
+
Nothing here is a fixed slot — every fact renders only when it is
|
|
328
|
+
true, so the header's height is itself a signal. -->
|
|
357
329
|
<div
|
|
358
330
|
class="head-region @container relative shrink-0 border-b border-outline-faint"
|
|
359
|
-
style="--rw-oc: {
|
|
331
|
+
style="--rw-oc: {alarm ?? 'var(--color-surface-raised)'}"
|
|
360
332
|
>
|
|
361
|
-
<div class="pt-[18px] pr-[22px] pb-[
|
|
333
|
+
<div class="pt-[18px] pr-[22px] pb-[16px] pl-[26px]">
|
|
362
334
|
<div class="flex items-start gap-4">
|
|
363
|
-
<!-- Verdict tile -->
|
|
364
|
-
<div
|
|
365
|
-
class="flex h-[38px] w-[38px] shrink-0 items-center justify-center rounded-[11px] {config.bg} {config.color} ring-1 ring-current/25 ring-inset"
|
|
366
|
-
>
|
|
367
|
-
<DetailIcon size={20} class={isRunning ? "animate-spin" : ""} />
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
335
|
<div class="min-w-0 flex-1">
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
336
|
+
<!-- Identity, but only where the page around the panel isn't
|
|
337
|
+
already saying it: the cross-task runs list needs the task
|
|
338
|
+
name, a task's own page already has it in the breadcrumb. -->
|
|
339
|
+
{#if showTaskName || suffix}
|
|
340
|
+
<div
|
|
341
|
+
class="mb-1.5 font-mono text-[11.5px] font-medium tracking-[0.06em] text-on-surface-muted"
|
|
342
|
+
>
|
|
343
|
+
{showTaskName ? `${run.taskName}${suffix}` : `instance ${suffix}`}
|
|
344
|
+
</div>
|
|
345
|
+
{/if}
|
|
346
|
+
|
|
347
|
+
<!-- Verdict: the outcome as one sentence, and the only large
|
|
348
|
+
type in the panel. It is prose, so the phrase is sans and
|
|
349
|
+
only the tokens inside it — the duration, a failing exit —
|
|
350
|
+
are mono (DESIGN.md's mono-vs-sans rule). The glyph is bare
|
|
351
|
+
— no plate, no ring — so it reads as part of the sentence,
|
|
352
|
+
and it alone carries the outcome colour on a healthy run:
|
|
353
|
+
green words are ink spent on nothing being wrong. -->
|
|
354
|
+
<Tooltip content={config.description} position="right" wide>
|
|
355
|
+
<h2
|
|
356
|
+
class="flex flex-wrap items-center gap-x-2.5 text-[22px] @max-2xl:text-[18px] @max-xs:text-[16px]"
|
|
357
|
+
data-testid="run-verdict"
|
|
358
|
+
data-status={status}
|
|
359
|
+
>
|
|
360
|
+
<DetailIcon
|
|
361
|
+
size={18}
|
|
362
|
+
strokeWidth={2.25}
|
|
363
|
+
class="shrink-0 {config.color} {isRunning
|
|
364
|
+
? 'animate-spin'
|
|
365
|
+
: ''}"
|
|
366
|
+
/>
|
|
367
|
+
<span
|
|
368
|
+
class="font-sans font-semibold tracking-[-0.01em] {alarm
|
|
369
|
+
? config.color
|
|
370
|
+
: 'text-on-surface'}">{verdict.verb}</span
|
|
371
|
+
>
|
|
372
|
+
{#if verdict.timed && duration}
|
|
373
|
+
<span
|
|
374
|
+
class="font-mono text-[0.86em] font-medium text-on-surface tabular-nums"
|
|
375
|
+
data-testid="run-duration">{duration}</span
|
|
376
|
+
>
|
|
377
|
+
{/if}
|
|
378
|
+
{#if showCode}
|
|
379
|
+
<!-- Grouped with its own separator so a wrap can
|
|
380
|
+
never leave the dot dangling at a line end. -->
|
|
381
|
+
<span
|
|
382
|
+
class="inline-flex items-center gap-x-2.5 font-mono text-[0.86em] font-medium tabular-nums"
|
|
383
|
+
>
|
|
384
|
+
<span class="text-on-surface-faint" aria-hidden="true"
|
|
385
|
+
>·</span
|
|
383
386
|
>
|
|
384
|
-
|
|
387
|
+
<span class="text-danger-surface" data-testid="run-exit"
|
|
388
|
+
>exit {run.exitCode}</span
|
|
389
|
+
>
|
|
390
|
+
</span>
|
|
385
391
|
{/if}
|
|
386
392
|
</h2>
|
|
387
|
-
|
|
388
|
-
outcome, so stopped reads amber, not red. -->
|
|
389
|
-
<Tooltip content={config.description} position="bottom" wide>
|
|
390
|
-
<span
|
|
391
|
-
class="rounded-full border px-2.5 py-0.5 text-2xs font-bold tracking-wider uppercase {config.color} {config.bg} {config.border}"
|
|
392
|
-
>
|
|
393
|
-
{status.toUpperCase()}
|
|
394
|
-
</span>
|
|
395
|
-
</Tooltip>
|
|
396
|
-
</div>
|
|
393
|
+
</Tooltip>
|
|
397
394
|
|
|
395
|
+
<!-- Facts: when it happened and what shaped it. Queue wait,
|
|
396
|
+
retries and parameters are absent on an ordinary run and
|
|
397
|
+
appear inline the moment they exist. -->
|
|
398
398
|
<div
|
|
399
|
-
class="mt-
|
|
399
|
+
class="mt-2.5 flex flex-wrap items-center gap-x-2 gap-y-1 font-mono text-[11.5px] text-on-surface-faint tabular-nums"
|
|
400
400
|
>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{formatTimeHM(startedAt)}
|
|
408
|
-
</span>
|
|
409
|
-
{/if}
|
|
410
|
-
<!-- Run-id chip: click to copy the full ULID -->
|
|
411
|
-
<button
|
|
412
|
-
type="button"
|
|
413
|
-
onclick={() => copyRunId(run.id)}
|
|
414
|
-
title="Copy run ID"
|
|
415
|
-
class="inline-flex items-center gap-1.5 rounded-md border border-outline-faint bg-surface-sunken px-1.5 py-0.5 font-mono text-2xs text-on-surface-faint transition-colors hover:border-outline hover:text-on-surface-muted"
|
|
401
|
+
<!-- No hover-for-the-full-timestamp here: the date sits
|
|
402
|
+
right beside the clock time, so a tooltip could only
|
|
403
|
+
repeat it. The line wraps at narrow widths instead of
|
|
404
|
+
dropping the date and hiding it behind a hover. -->
|
|
405
|
+
<span data-testid="run-started" class="text-on-surface-muted"
|
|
406
|
+
>{run.startAt ? formatClockTime(run.startAt) : "—"}</span
|
|
416
407
|
>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
408
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
409
|
+
<span>{formatCalendarDate(startedAt)}</span>
|
|
410
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
411
|
+
<span>via {formatTriggeredByLabel(run.triggeredBy).toLowerCase()}</span>
|
|
412
|
+
{#if startDelay}
|
|
413
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
414
|
+
<span
|
|
415
|
+
class="text-warning-soft-text"
|
|
416
|
+
title="Waited between its scheduled tick and actually starting"
|
|
417
|
+
>{startDelay} queued</span
|
|
418
|
+
>
|
|
419
|
+
{/if}
|
|
423
420
|
{#if retry}
|
|
424
|
-
<span class="
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
<span class="text-on-surface-faint"
|
|
430
|
-
>of run {formatShortId(run.retry_of_run_id)}</span
|
|
431
|
-
>{/if}
|
|
421
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
422
|
+
<span class="inline-flex items-center gap-1">
|
|
423
|
+
<RotateCw size={11} />
|
|
424
|
+
retry #{run.retryAttempt}{#if run.retryOfRunId} of
|
|
425
|
+
{formatShortId(run.retryOfRunId)}{/if}
|
|
432
426
|
</span>
|
|
433
427
|
{/if}
|
|
434
428
|
{#if paramEntries.length > 0}
|
|
435
|
-
<span class="
|
|
436
|
-
></span>
|
|
429
|
+
<span class="text-outline-hover" aria-hidden="true">·</span>
|
|
437
430
|
<Popover placement="bottom-start">
|
|
438
431
|
{#snippet trigger()}
|
|
439
432
|
<span
|
|
440
|
-
class="inline-flex cursor-pointer items-center gap-1
|
|
441
|
-
title="View run parameters"
|
|
433
|
+
class="inline-flex cursor-pointer items-center gap-1 border-b border-dotted border-outline-hover text-on-surface-muted hover:text-primary"
|
|
442
434
|
>
|
|
443
|
-
<SlidersHorizontal size={
|
|
435
|
+
<SlidersHorizontal size={11} />
|
|
444
436
|
{paramEntries.length}
|
|
445
437
|
{paramEntries.length === 1 ? "parameter" : "parameters"}
|
|
446
438
|
</span>
|
|
447
439
|
{/snippet}
|
|
448
440
|
<div class="max-w-md min-w-48">
|
|
449
441
|
<span
|
|
450
|
-
class="text-xs font-semibold tracking-
|
|
442
|
+
class="font-mono text-xs font-semibold tracking-[0.14em] text-on-surface-faint uppercase"
|
|
451
443
|
>Parameters</span
|
|
452
444
|
>
|
|
453
445
|
<dl
|
|
@@ -467,6 +459,21 @@
|
|
|
467
459
|
</div>
|
|
468
460
|
</Popover>
|
|
469
461
|
{/if}
|
|
462
|
+
<!-- Run-id chip: click to copy the full ULID. Last, because
|
|
463
|
+
it is the fact you reach for least and the only one you
|
|
464
|
+
interact with. -->
|
|
465
|
+
<button
|
|
466
|
+
type="button"
|
|
467
|
+
onclick={() => copyRunId(run.id)}
|
|
468
|
+
title="Copy run ID"
|
|
469
|
+
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"
|
|
470
|
+
>
|
|
471
|
+
{#if copiedId}
|
|
472
|
+
<Check size={11} class="text-success-surface" />Copied
|
|
473
|
+
{:else}
|
|
474
|
+
<Hash size={11} />{run.id}
|
|
475
|
+
{/if}
|
|
476
|
+
</button>
|
|
470
477
|
</div>
|
|
471
478
|
</div>
|
|
472
479
|
|
|
@@ -477,7 +484,7 @@
|
|
|
477
484
|
<button
|
|
478
485
|
type="button"
|
|
479
486
|
onclick={() => onToggleHistory()}
|
|
480
|
-
class="inline-flex items-center justify-center rounded-
|
|
487
|
+
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"
|
|
481
488
|
title={historyVisible ? "Hide run history" : "Show run history"}
|
|
482
489
|
aria-label={historyVisible
|
|
483
490
|
? "Hide run history"
|
|
@@ -486,7 +493,7 @@
|
|
|
486
493
|
{#if historyVisible}
|
|
487
494
|
<PanelLeftClose size={15} />
|
|
488
495
|
{:else}
|
|
489
|
-
<
|
|
496
|
+
<RotateCcwClock size={15} />
|
|
490
497
|
{/if}
|
|
491
498
|
</button>
|
|
492
499
|
{/if}
|
|
@@ -505,7 +512,7 @@
|
|
|
505
512
|
title={serviceStopped
|
|
506
513
|
? "Starts the service"
|
|
507
514
|
: "Cancels and respawns every instance"}
|
|
508
|
-
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-
|
|
515
|
+
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-[3px] border border-primary-soft-border bg-surface-raised px-3 font-mono text-sm font-medium text-primary hover:bg-primary-soft active:translate-y-px disabled:cursor-not-allowed disabled:opacity-60"
|
|
509
516
|
>
|
|
510
517
|
{#if serviceStopped}
|
|
511
518
|
<Play size={15} fill="currentColor" stroke="none" />
|
|
@@ -523,7 +530,7 @@
|
|
|
523
530
|
onclick={() => onStopService()}
|
|
524
531
|
disabled={serviceBusy}
|
|
525
532
|
title="Stops the service for the rest of the daemon's lifetime"
|
|
526
|
-
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-
|
|
533
|
+
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"
|
|
527
534
|
>
|
|
528
535
|
<Square size={15} fill="currentColor" stroke="none" />
|
|
529
536
|
<span class="@max-md:hidden">Stop Service</span>
|
|
@@ -534,13 +541,13 @@
|
|
|
534
541
|
<!-- Split button: Run (defaults) + an arrow for the
|
|
535
542
|
reuse-parameters variant, shown only when there is one. -->
|
|
536
543
|
<div
|
|
537
|
-
class="inline-flex overflow-hidden rounded-
|
|
544
|
+
class="inline-flex overflow-hidden rounded-[3px] border border-primary-soft-border"
|
|
538
545
|
>
|
|
539
546
|
<button
|
|
540
547
|
type="button"
|
|
541
548
|
onclick={() => onRun()}
|
|
542
549
|
title="Run this task now with default options"
|
|
543
|
-
class="inline-flex h-9 cursor-pointer items-center gap-1.5 bg-surface-raised px-3 text-sm font-medium text-primary
|
|
550
|
+
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"
|
|
544
551
|
>
|
|
545
552
|
<Play size={15} fill="currentColor" stroke="none" />
|
|
546
553
|
<span class="@max-md:hidden">Run</span>
|
|
@@ -549,7 +556,7 @@
|
|
|
549
556
|
<Popover bind:open={runMenuOpen} placement="bottom-end">
|
|
550
557
|
{#snippet trigger()}
|
|
551
558
|
<span
|
|
552
|
-
class="flex h-9 cursor-pointer items-center border-l border-primary-soft bg-surface-raised px-2 text-primary
|
|
559
|
+
class="flex h-9 cursor-pointer items-center border-l border-primary-soft-border bg-surface-raised px-2 text-primary hover:bg-primary-soft"
|
|
553
560
|
title="More run options"
|
|
554
561
|
aria-label="More run options"
|
|
555
562
|
>
|
|
@@ -570,7 +577,7 @@
|
|
|
570
577
|
/>
|
|
571
578
|
<span class="flex flex-col">
|
|
572
579
|
<span
|
|
573
|
-
class="text-sm font-medium text-on-surface"
|
|
580
|
+
class="font-mono text-sm font-medium text-on-surface"
|
|
574
581
|
>Run again</span
|
|
575
582
|
>
|
|
576
583
|
<span class="text-xs text-on-surface-muted"
|
|
@@ -586,7 +593,7 @@
|
|
|
586
593
|
<button
|
|
587
594
|
type="button"
|
|
588
595
|
onclick={() => onStop(run.id)}
|
|
589
|
-
class="inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-
|
|
596
|
+
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"
|
|
590
597
|
>
|
|
591
598
|
<Square size={15} fill="currentColor" stroke="none" />
|
|
592
599
|
<span class="@max-md:hidden">Stop</span>
|
|
@@ -595,10 +602,10 @@
|
|
|
595
602
|
{/if}
|
|
596
603
|
<a
|
|
597
604
|
href="/api/tasks/{encodeURIComponent(
|
|
598
|
-
run.
|
|
605
|
+
run.taskName,
|
|
599
606
|
)}/runs/{encodeURIComponent(run.id)}/log/raw"
|
|
600
|
-
download="{run.
|
|
601
|
-
class="inline-flex items-center justify-center rounded-
|
|
607
|
+
download="{run.taskName}-{run.id}.log"
|
|
608
|
+
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"
|
|
602
609
|
title="Download the full log (rotated and current parts as one file)"
|
|
603
610
|
aria-label="Download log"
|
|
604
611
|
>
|
|
@@ -608,7 +615,7 @@
|
|
|
608
615
|
<Popover bind:open={confirmDeleteOpen} placement="bottom-end">
|
|
609
616
|
{#snippet trigger()}
|
|
610
617
|
<span
|
|
611
|
-
class="flex cursor-pointer items-center justify-center rounded-
|
|
618
|
+
class="flex cursor-pointer items-center justify-center rounded-[3px] border border-outline-faint bg-surface-raised p-2 text-on-surface-muted hover:border-danger-soft-border hover:bg-danger-soft hover:text-danger-surface"
|
|
612
619
|
title="Delete this run"
|
|
613
620
|
aria-label="Delete run"
|
|
614
621
|
>
|
|
@@ -616,7 +623,7 @@
|
|
|
616
623
|
</span>
|
|
617
624
|
{/snippet}
|
|
618
625
|
<div class="w-60">
|
|
619
|
-
<div class="text-sm font-semibold text-on-surface">
|
|
626
|
+
<div class="font-mono text-sm font-semibold text-on-surface">
|
|
620
627
|
Delete this run?
|
|
621
628
|
</div>
|
|
622
629
|
<div class="mt-1 text-xs leading-relaxed text-on-surface-muted">
|
|
@@ -647,105 +654,6 @@
|
|
|
647
654
|
{/if}
|
|
648
655
|
</div>
|
|
649
656
|
</div>
|
|
650
|
-
|
|
651
|
-
<!-- Instrument cluster: the run's vital readout — value over a one-word
|
|
652
|
-
context line, hairline-divided. -->
|
|
653
|
-
<div
|
|
654
|
-
class="mt-4 flex flex-wrap overflow-hidden rounded-xl border border-outline-faint bg-surface-raised"
|
|
655
|
-
>
|
|
656
|
-
<div
|
|
657
|
-
class="flex min-w-30 flex-1 flex-col gap-0.5 border-l border-outline-faint px-4 py-2.5 first:border-l-0"
|
|
658
|
-
>
|
|
659
|
-
<span
|
|
660
|
-
class="text-2xs font-bold tracking-wider text-on-surface-faint uppercase"
|
|
661
|
-
>Queued</span
|
|
662
|
-
>
|
|
663
|
-
<span
|
|
664
|
-
class="font-mono text-base font-medium tracking-tight whitespace-nowrap text-on-surface tabular-nums"
|
|
665
|
-
title={formatFullDateTime(run.created_at)}
|
|
666
|
-
>
|
|
667
|
-
{formatClockTime(run.created_at)}
|
|
668
|
-
</span>
|
|
669
|
-
<span class="text-2xs text-on-surface-muted"
|
|
670
|
-
>+{startDelay ?? "0s"} queued</span
|
|
671
|
-
>
|
|
672
|
-
</div>
|
|
673
|
-
|
|
674
|
-
<div
|
|
675
|
-
class="flex min-w-30 flex-1 flex-col gap-0.5 border-l border-outline-faint px-4 py-2.5 first:border-l-0"
|
|
676
|
-
>
|
|
677
|
-
<span
|
|
678
|
-
class="text-2xs font-bold tracking-wider text-on-surface-faint uppercase"
|
|
679
|
-
>Started</span
|
|
680
|
-
>
|
|
681
|
-
<span
|
|
682
|
-
class="font-mono text-base font-medium tracking-tight whitespace-nowrap text-on-surface tabular-nums"
|
|
683
|
-
>
|
|
684
|
-
{run.start_at ? formatClockTime(run.start_at) : "—"}
|
|
685
|
-
</span>
|
|
686
|
-
<span class="text-2xs text-on-surface-muted">
|
|
687
|
-
{run.start_at ? formatCalendarDate(run.start_at) : "not started"}
|
|
688
|
-
</span>
|
|
689
|
-
</div>
|
|
690
|
-
|
|
691
|
-
<div
|
|
692
|
-
class="flex min-w-30 flex-1 flex-col gap-0.5 border-l border-outline-faint px-4 py-2.5 first:border-l-0"
|
|
693
|
-
>
|
|
694
|
-
<span
|
|
695
|
-
class="text-2xs font-bold tracking-wider text-on-surface-faint uppercase"
|
|
696
|
-
>Ran for</span
|
|
697
|
-
>
|
|
698
|
-
<span
|
|
699
|
-
class="font-mono text-base font-medium tracking-tight whitespace-nowrap text-on-surface tabular-nums"
|
|
700
|
-
>
|
|
701
|
-
{duration ?? "—"}
|
|
702
|
-
</span>
|
|
703
|
-
<span class="text-2xs text-on-surface-muted">
|
|
704
|
-
{isRunning ? "and counting" : run.end_at ? "wall clock" : "—"}
|
|
705
|
-
</span>
|
|
706
|
-
</div>
|
|
707
|
-
|
|
708
|
-
<div
|
|
709
|
-
class="flex min-w-30 flex-1 flex-col gap-0.5 border-l border-outline-faint px-4 py-2.5 first:border-l-0"
|
|
710
|
-
>
|
|
711
|
-
<span
|
|
712
|
-
class="text-2xs font-bold tracking-wider text-on-surface-faint uppercase"
|
|
713
|
-
>Exit</span
|
|
714
|
-
>
|
|
715
|
-
<span
|
|
716
|
-
class="flex items-baseline gap-1.5 font-mono text-base font-medium tracking-tight tabular-nums {exitClean
|
|
717
|
-
? 'text-success-surface'
|
|
718
|
-
: exitFail
|
|
719
|
-
? 'text-danger-surface'
|
|
720
|
-
: 'text-on-surface'}"
|
|
721
|
-
>
|
|
722
|
-
<span>{showCode ? run.exit_code : "—"}</span>
|
|
723
|
-
{#if exitFail}
|
|
724
|
-
<span
|
|
725
|
-
class="rounded bg-danger-soft px-1 py-px text-[9px] font-bold tracking-wide text-danger-surface uppercase"
|
|
726
|
-
>fail</span
|
|
727
|
-
>
|
|
728
|
-
{/if}
|
|
729
|
-
</span>
|
|
730
|
-
<span class="text-2xs text-on-surface-muted">{exitSubLabel(status)}</span>
|
|
731
|
-
</div>
|
|
732
|
-
|
|
733
|
-
<div
|
|
734
|
-
class="flex min-w-30 flex-1 flex-col gap-0.5 border-l border-outline-faint px-4 py-2.5 first:border-l-0"
|
|
735
|
-
>
|
|
736
|
-
<span
|
|
737
|
-
class="text-2xs font-bold tracking-wider text-on-surface-faint uppercase"
|
|
738
|
-
>Trigger</span
|
|
739
|
-
>
|
|
740
|
-
<span
|
|
741
|
-
class="font-mono text-base font-medium tracking-tight whitespace-nowrap text-on-surface tabular-nums"
|
|
742
|
-
>{formatTriggeredByLabel(run.triggered_by)}</span
|
|
743
|
-
>
|
|
744
|
-
<span class="text-2xs text-on-surface-muted"
|
|
745
|
-
>{triggerSub(run.triggered_by)}</span
|
|
746
|
-
>
|
|
747
|
-
</div>
|
|
748
|
-
</div>
|
|
749
657
|
</div>
|
|
750
658
|
</div>
|
|
751
659
|
|
|
@@ -761,25 +669,27 @@
|
|
|
761
669
|
<div
|
|
762
670
|
use:maximizePortal={consoleMaximized}
|
|
763
671
|
class={consoleMaximized
|
|
764
|
-
? "console-zoom fixed inset-3 z-50 flex flex-col overflow-hidden rounded-
|
|
765
|
-
: "ml-1 flex min-h-[300px] flex-1 flex-col overflow-hidden border-t border-[
|
|
672
|
+
? "console-zoom fixed inset-3 z-50 flex flex-col overflow-hidden rounded-[4px] bg-[var(--rw-con-bg)] shadow-2xl ring-1 ring-[var(--rw-con-gutter)] sm:inset-6"
|
|
673
|
+
: "ml-1 flex min-h-[300px] flex-1 flex-col overflow-hidden border-t border-[var(--rw-con-gutter)] bg-[var(--rw-con-bg)]"}
|
|
766
674
|
>
|
|
767
675
|
<div
|
|
768
|
-
class="flex shrink-0 items-center justify-between gap-3 border-b border-[
|
|
676
|
+
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)]"
|
|
769
677
|
>
|
|
770
678
|
<div class="flex min-w-0 items-center gap-3">
|
|
771
679
|
{#if consoleMaximized}
|
|
772
680
|
<!-- Focus-mode identity: which run is filling the screen -->
|
|
773
681
|
<span class="flex min-w-0 items-center gap-2">
|
|
774
682
|
<span class="h-2 w-2 shrink-0 rounded-full {spine}"></span>
|
|
775
|
-
<span class="truncate font-medium text-[
|
|
776
|
-
{run.
|
|
683
|
+
<span class="truncate font-medium text-[var(--rw-con-text)]">
|
|
684
|
+
{run.taskName}{suffix}
|
|
777
685
|
</span>
|
|
778
686
|
<span class="text-[var(--rw-con-gutter)]">·</span>
|
|
779
687
|
<span class="font-semibold capitalize {config.color}">{status}</span>
|
|
780
688
|
</span>
|
|
781
689
|
{:else}
|
|
782
|
-
<span
|
|
690
|
+
<span
|
|
691
|
+
class="flex items-center gap-2 font-semibold text-[var(--rw-con-text)]"
|
|
692
|
+
>
|
|
783
693
|
<TerminalIcon size={14} class="opacity-70" />
|
|
784
694
|
Console output
|
|
785
695
|
</span>
|
|
@@ -792,8 +702,8 @@
|
|
|
792
702
|
<button
|
|
793
703
|
type="button"
|
|
794
704
|
onclick={() => (consoleWrap = !consoleWrap)}
|
|
795
|
-
class="flex cursor-pointer items-center gap-1.5 rounded-
|
|
796
|
-
? 'text-[
|
|
705
|
+
class="flex cursor-pointer items-center gap-1.5 rounded-[3px] px-2 py-1 hover:bg-[var(--rw-con-gutter)]/30 hover:text-[var(--rw-con-text)] {consoleWrap
|
|
706
|
+
? 'text-[var(--rw-con-text)]'
|
|
797
707
|
: 'text-[var(--rw-con-dim)]'}"
|
|
798
708
|
title={consoleWrap ? "Unwrap lines" : "Wrap long lines"}
|
|
799
709
|
aria-pressed={consoleWrap}
|
|
@@ -805,14 +715,14 @@
|
|
|
805
715
|
<button
|
|
806
716
|
type="button"
|
|
807
717
|
onclick={() => (consoleMaximized = !consoleMaximized)}
|
|
808
|
-
class="flex cursor-pointer items-center gap-1.5 rounded-
|
|
718
|
+
class="flex cursor-pointer items-center gap-1.5 rounded-[3px] px-2 py-1 text-[var(--rw-con-dim)] hover:bg-[var(--rw-con-gutter)]/30 hover:text-[var(--rw-con-text)]"
|
|
809
719
|
title={consoleMaximized ? "Restore console (Esc)" : "Maximize console · F"}
|
|
810
720
|
aria-label={consoleMaximized ? "Restore console" : "Maximize console"}
|
|
811
721
|
>
|
|
812
722
|
{#if consoleMaximized}
|
|
813
723
|
<Minimize2 size={13} />
|
|
814
724
|
<span
|
|
815
|
-
class="rounded border border-[var(--rw-con-gutter)] px-1.5 text-[10px] tracking-wide"
|
|
725
|
+
class="rounded-[3px] border border-[var(--rw-con-gutter)] px-1.5 text-[10px] tracking-wide"
|
|
816
726
|
>Esc</span
|
|
817
727
|
>
|
|
818
728
|
{:else}
|
|
@@ -855,7 +765,7 @@
|
|
|
855
765
|
<button
|
|
856
766
|
type="button"
|
|
857
767
|
onclick={() => onRunTask()}
|
|
858
|
-
class="inline-flex cursor-pointer items-center gap-1.5 rounded-
|
|
768
|
+
class="inline-flex cursor-pointer items-center gap-1.5 rounded-[3px] border border-primary-soft-border bg-surface-raised px-3 py-2 font-mono text-sm font-medium text-primary hover:bg-primary-soft active:translate-y-px"
|
|
859
769
|
>
|
|
860
770
|
<Play size={15} fill="currentColor" stroke="none" />
|
|
861
771
|
Run task
|
|
@@ -865,16 +775,17 @@
|
|
|
865
775
|
{/if}
|
|
866
776
|
|
|
867
777
|
<style>
|
|
868
|
-
/*
|
|
869
|
-
|
|
870
|
-
mix collapses to a plain surface
|
|
778
|
+
/* Only a run worth triaging is washed in its outcome colour (--rw-oc, set
|
|
779
|
+
inline). Everything else — success included — passes the surface itself so
|
|
780
|
+
the mix collapses to a plain surface, which is what lets a genuine failure
|
|
781
|
+
be the one lit thing on the page. */
|
|
871
782
|
.head-region {
|
|
872
|
-
background: color-mix(in srgb, var(--rw-oc)
|
|
783
|
+
background: color-mix(in srgb, var(--rw-oc) 7%, var(--color-surface-raised));
|
|
873
784
|
}
|
|
874
785
|
|
|
875
786
|
/* Gentle fade for the maximize backdrop; disabled under reduced motion. */
|
|
876
787
|
.console-backdrop {
|
|
877
|
-
animation: console-backdrop-in 150ms
|
|
788
|
+
animation: console-backdrop-in 150ms;
|
|
878
789
|
}
|
|
879
790
|
@keyframes console-backdrop-in {
|
|
880
791
|
from {
|