@runwisp/ui 1.4.0 → 1.5.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.
- package/package.json +4 -5
- package/src/lib/actions/floating.ts +73 -0
- package/src/lib/actions/focusTrap.ts +3 -3
- 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/Badge.svelte +1 -1
- package/src/lib/components/CodeBlock.svelte +84 -35
- package/src/lib/components/CronInput.svelte +5 -2
- package/src/lib/components/DataGrid.svelte +100 -138
- 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/FilterBar.svelte +1 -1
- package/src/lib/components/FilterPill.svelte +2 -2
- package/src/lib/components/FormField.svelte +1 -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 +40 -42
- 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/SearchInput.svelte +1 -1
- package/src/lib/components/Select.svelte +16 -47
- package/src/lib/components/Sparkline.svelte +2 -2
- package/src/lib/components/StatusDot.svelte +1 -1
- 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/Tooltip.svelte +1 -1
- package/src/lib/components/accordion.svelte.ts +19 -0
- package/src/lib/components/dashboard/RunDetailPanel.svelte +106 -79
- package/src/lib/components/dashboard/RunFilterPopover.svelte +12 -21
- package/src/lib/components/dashboard/RunsList.svelte +17 -26
- package/src/lib/components/dashboard/cron-format.test.ts +1 -1
- package/src/lib/components/dashboard/cron-format.ts +3 -3
- package/src/lib/components/dashboard/run-filters.test.ts +4 -60
- package/src/lib/components/dashboard/run-filters.ts +29 -144
- package/src/lib/components/dashboard/run-helpers.test.ts +25 -6
- package/src/lib/components/dashboard/run-helpers.ts +40 -39
- package/src/lib/components/dashboard/status-config.ts +38 -83
- package/src/lib/components/dashboard/types.ts +0 -26
- package/src/lib/components/data-grid-selection.ts +2 -9
- package/src/lib/components/filter-spec.ts +3 -3
- package/src/lib/components/radio-group.ts +11 -0
- package/src/lib/index.ts +8 -43
- package/src/lib/log-console/LogCache.svelte.ts +1 -1
- package/src/lib/log-console/LogFetcher.svelte.ts +3 -14
- package/src/lib/log-console/ansi.ts +1 -1
- 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-tokens.css +21 -21
- package/src/lib/theme.css +22 -16
- package/src/lib/utils/clipboard.svelte.test.ts +92 -0
- package/src/lib/utils/clipboard.svelte.ts +73 -0
- package/src/lib/utils/format.test.ts +3 -3
- 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 +5 -14
- package/src/stories/Accordion.stories.svelte +61 -0
- package/src/stories/CodeBlock.stories.svelte +55 -0
- package/src/stories/DataGrid.stories.svelte +0 -5
- 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/LogConsole.stories.svelte +2 -2
- 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
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import type { Run, RunStatus } from "@runwisp/common";
|
|
5
|
-
import { formatDuration } from "../../utils/format.js";
|
|
4
|
+
import type { APIPaths, Run, RunStatus, Trigger } from "@runwisp/common";
|
|
5
|
+
import { formatBytes, formatDuration } from "../../utils/format.js";
|
|
6
6
|
|
|
7
7
|
export interface RunVerdict {
|
|
8
8
|
/** Verb phrase for the outcome, ending in its preposition when `timed`. */
|
|
9
9
|
verb: string;
|
|
10
|
-
/**
|
|
11
|
-
* Whether the phrase expects a duration after it. False for statuses that
|
|
12
|
-
* never produced one (nothing ran, or nothing has yet), so the caller
|
|
13
|
-
* renders the verb alone rather than "skipped after —".
|
|
14
|
-
*/
|
|
10
|
+
/** Whether a duration follows the verb; false when nothing ran yet. */
|
|
15
11
|
timed: boolean;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
* The run's outcome as a verb phrase, so a detail view can state it as one
|
|
20
|
-
* sentence ("succeeded in 933ms") instead of a status badge competing with a
|
|
21
|
-
* separate duration readout for the same glance.
|
|
22
|
-
*/
|
|
14
|
+
// The run's outcome as a verb phrase, read as one sentence: "succeeded in 933ms".
|
|
23
15
|
const RUN_VERDICTS: Record<RunStatus, RunVerdict> = {
|
|
24
16
|
succeeded: { verb: "succeeded in", timed: true },
|
|
25
17
|
failed: { verb: "failed after", timed: true },
|
|
@@ -54,12 +46,8 @@ export function runDuration(
|
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* within a second of each other. This is the visible face of `jitter`: a
|
|
60
|
-
* jittered run is created at its tick but starts later inside the window, and
|
|
61
|
-
* this is by how much. It also surfaces queue-wait, since a queued run is
|
|
62
|
-
* created when it joins the line and started when the line clears.
|
|
49
|
+
* How long a run waited between `createdAt` and `startedAt` (jitter or queue
|
|
50
|
+
* wait), or undefined when it started within a second.
|
|
63
51
|
*/
|
|
64
52
|
export function runStartDelay(run: Pick<Run, "createdAt" | "startedAt">): string | undefined {
|
|
65
53
|
if (!run.startedAt) return undefined;
|
|
@@ -68,14 +56,7 @@ export function runStartDelay(run: Pick<Run, "createdAt" | "startedAt">): string
|
|
|
68
56
|
return formatDuration(delay);
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
/**
|
|
72
|
-
* Display suffix for a run's instance slot. A service configured with more than
|
|
73
|
-
* one instance gets a 1-based suffix (`#1`, `#2`, …) on every one of its runs;
|
|
74
|
-
* a single-instance task (or any non-service, where `instanceCount` is 1)
|
|
75
|
-
* returns an empty string so the bare task name is shown. `instanceIndex` is
|
|
76
|
-
* the stored 0-based slot; `instanceCount` is the task's currently configured
|
|
77
|
-
* instance count.
|
|
78
|
-
*/
|
|
59
|
+
/** 1-based `#N` suffix for a multi-instance service run, else "". */
|
|
79
60
|
export function instanceSuffix(instanceIndex: number, instanceCount: number): string {
|
|
80
61
|
if (instanceCount > 1) {
|
|
81
62
|
return `#${String(instanceIndex + 1)}`;
|
|
@@ -83,23 +64,32 @@ export function instanceSuffix(instanceIndex: number, instanceCount: number): st
|
|
|
83
64
|
return "";
|
|
84
65
|
}
|
|
85
66
|
|
|
67
|
+
// Typed against the generated paths so a route rename fails the build.
|
|
68
|
+
const RAW_LOG_PATH: keyof APIPaths = "/api/runs/{runId}/log/raw";
|
|
69
|
+
|
|
70
|
+
/** The full log of a run as one plain-text download. */
|
|
71
|
+
export function runLogDownloadUrl(runId: string): string {
|
|
72
|
+
return RAW_LOG_PATH.replace("{runId}", encodeURIComponent(runId));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Labels per trigger source: `short` for the row badge, `long` for descriptions. */
|
|
76
|
+
export const TRIGGER_LABELS: Record<Trigger, { short: string; long: string }> = {
|
|
77
|
+
cron: { short: "Cron", long: "Scheduled (cron)" },
|
|
78
|
+
api: { short: "API", long: "REST API" },
|
|
79
|
+
ui: { short: "UI", long: "UI" },
|
|
80
|
+
cli: { short: "CLI", long: "CLI" },
|
|
81
|
+
station: { short: "Station", long: "Control plane" },
|
|
82
|
+
service: { short: "Service", long: "Service auto-start" },
|
|
83
|
+
startup: { short: "Startup", long: "On daemon start" },
|
|
84
|
+
hook: { short: "Hook", long: "Hook" },
|
|
85
|
+
};
|
|
86
|
+
|
|
86
87
|
/** Human label for why a run fired (the `triggeredBy` source). */
|
|
87
88
|
export function formatTriggeredByLabel(triggeredBy: Run["triggeredBy"]): string {
|
|
88
|
-
|
|
89
|
-
if (triggeredBy === "ui") return "UI";
|
|
90
|
-
if (triggeredBy === "cli") return "CLI";
|
|
91
|
-
if (triggeredBy === "cron") return "Cron";
|
|
92
|
-
if (triggeredBy === "service") return "Service";
|
|
93
|
-
if (triggeredBy === "startup") return "Startup";
|
|
94
|
-
if (triggeredBy === "hook") return "Hook";
|
|
95
|
-
return "Station";
|
|
89
|
+
return TRIGGER_LABELS[triggeredBy].short;
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
/**
|
|
99
|
-
* "retry #N" label when a run is a retry of an earlier one, else undefined.
|
|
100
|
-
* A run is a retry if it carries a positive attempt number or points back at
|
|
101
|
-
* the run it re-attempts.
|
|
102
|
-
*/
|
|
92
|
+
/** "retry #N" when the run re-attempts an earlier one, else undefined. */
|
|
103
93
|
export function runRetryLabel(run: Pick<Run, "retryAttempt" | "retryOfRunId">): string | undefined {
|
|
104
94
|
if (run.retryAttempt > 0 || run.retryOfRunId) {
|
|
105
95
|
return `retry #${String(run.retryAttempt)}`;
|
|
@@ -107,6 +97,17 @@ export function runRetryLabel(run: Pick<Run, "retryAttempt" | "retryOfRunId">):
|
|
|
107
97
|
return undefined;
|
|
108
98
|
}
|
|
109
99
|
|
|
100
|
+
/**
|
|
101
|
+
* "peak 48 MB · CPU 1s" for a run whose resources were measured (shell runs
|
|
102
|
+
* only), else undefined.
|
|
103
|
+
*/
|
|
104
|
+
export function runUsageLabel(run: Pick<Run, "peakMemoryBytes" | "cpuTimeMs">): string | undefined {
|
|
105
|
+
const parts: string[] = [];
|
|
106
|
+
if (run.peakMemoryBytes !== undefined) parts.push("peak " + formatBytes(run.peakMemoryBytes));
|
|
107
|
+
if (run.cpuTimeMs !== undefined) parts.push("CPU " + formatDuration(run.cpuTimeMs));
|
|
108
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
110
111
|
/**
|
|
111
112
|
* Monotonic position of a run's status in its lifecycle. Used to reject stale
|
|
112
113
|
* updates (e.g. a `pending` HTTP response arriving after an SSE already
|
|
@@ -16,25 +16,44 @@ import {
|
|
|
16
16
|
HeartCrack,
|
|
17
17
|
} from "@lucide/svelte";
|
|
18
18
|
import type { Component } from "svelte";
|
|
19
|
-
import {
|
|
19
|
+
import type { RunStatus } from "@runwisp/common";
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
interface RunStatusConfig {
|
|
22
22
|
icon: Component;
|
|
23
23
|
color: string;
|
|
24
24
|
bg: string;
|
|
25
|
-
border: string;
|
|
26
25
|
dot: string;
|
|
27
26
|
badge: string;
|
|
28
27
|
/** One-sentence explanation of what this status means, for tooltips. */
|
|
29
28
|
description: string;
|
|
30
29
|
}
|
|
31
30
|
|
|
31
|
+
// Full literal class strings (not built from a tone name) so Tailwind's
|
|
32
|
+
// scanner picks them up.
|
|
33
|
+
const DANGER = {
|
|
34
|
+
color: "text-danger-surface",
|
|
35
|
+
bg: "bg-danger-soft",
|
|
36
|
+
dot: "bg-danger-surface",
|
|
37
|
+
badge: "bg-danger-soft text-danger-soft-text",
|
|
38
|
+
};
|
|
39
|
+
const WARNING = {
|
|
40
|
+
color: "text-warning-surface",
|
|
41
|
+
bg: "bg-warning-soft",
|
|
42
|
+
dot: "bg-warning-surface",
|
|
43
|
+
badge: "bg-warning-soft text-warning-soft-text",
|
|
44
|
+
};
|
|
45
|
+
const NEUTRAL = {
|
|
46
|
+
color: "text-on-surface-muted",
|
|
47
|
+
bg: "bg-surface-sunken",
|
|
48
|
+
dot: "bg-on-surface-faint",
|
|
49
|
+
badge: "bg-surface-sunken text-on-surface",
|
|
50
|
+
};
|
|
51
|
+
|
|
32
52
|
export const RUN_STATUS_CONFIG: Record<RunStatus, RunStatusConfig> = {
|
|
33
53
|
running: {
|
|
34
54
|
icon: LoaderCircle,
|
|
35
55
|
color: "text-info-surface",
|
|
36
56
|
bg: "bg-info-soft",
|
|
37
|
-
border: "border-info-soft",
|
|
38
57
|
dot: "bg-info-surface animate-pulse",
|
|
39
58
|
badge: "bg-info-soft text-info-soft-text",
|
|
40
59
|
description: "This run is executing right now.",
|
|
@@ -43,151 +62,87 @@ export const RUN_STATUS_CONFIG: Record<RunStatus, RunStatusConfig> = {
|
|
|
43
62
|
icon: CircleCheck,
|
|
44
63
|
color: "text-success-surface",
|
|
45
64
|
bg: "bg-success-soft",
|
|
46
|
-
border: "border-success-soft",
|
|
47
65
|
dot: "bg-success-surface",
|
|
48
66
|
badge: "bg-success-soft text-success-soft-text",
|
|
49
67
|
description: "The run finished with exit code 0 - everything OK.",
|
|
50
68
|
},
|
|
51
69
|
failed: {
|
|
70
|
+
...DANGER,
|
|
52
71
|
icon: CircleX,
|
|
53
|
-
color: "text-danger-surface",
|
|
54
|
-
bg: "bg-danger-soft",
|
|
55
|
-
border: "border-danger-soft",
|
|
56
|
-
dot: "bg-danger-surface",
|
|
57
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
58
72
|
description: "The run exited with a non-zero code.",
|
|
59
73
|
},
|
|
60
74
|
crashed: {
|
|
75
|
+
...DANGER,
|
|
61
76
|
icon: CircleAlert,
|
|
62
|
-
color: "text-danger-surface",
|
|
63
|
-
bg: "bg-danger-soft",
|
|
64
|
-
border: "border-danger-soft",
|
|
65
|
-
dot: "bg-danger-surface",
|
|
66
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
67
77
|
description:
|
|
68
78
|
"The process was killed, or the daemon found it still 'running' after a hard crash and marked it crashed. It was not resumed.",
|
|
69
79
|
},
|
|
70
80
|
stopped: {
|
|
81
|
+
...WARNING,
|
|
71
82
|
icon: CircleStop,
|
|
72
|
-
color: "text-warning-surface",
|
|
73
|
-
bg: "bg-warning-soft",
|
|
74
|
-
border: "border-warning-soft",
|
|
75
|
-
dot: "bg-warning-surface",
|
|
76
|
-
badge: "bg-warning-soft text-warning-soft-text",
|
|
77
83
|
description: "A human or an external script manually stopped this run before it finished.",
|
|
78
84
|
},
|
|
79
85
|
timeout: {
|
|
86
|
+
...WARNING,
|
|
80
87
|
icon: TimerOff,
|
|
81
|
-
color: "text-warning-surface",
|
|
82
|
-
bg: "bg-warning-soft",
|
|
83
|
-
border: "border-warning-soft",
|
|
84
|
-
dot: "bg-warning-surface",
|
|
85
|
-
badge: "bg-warning-soft text-warning-soft-text",
|
|
86
88
|
description:
|
|
87
89
|
"The run exceeded its configured timeout and was terminated. Timeout duration can be changed.",
|
|
88
90
|
},
|
|
89
91
|
skipped: {
|
|
92
|
+
...NEUTRAL,
|
|
90
93
|
icon: SkipForward,
|
|
91
|
-
color: "text-on-surface-muted",
|
|
92
|
-
bg: "bg-surface-sunken",
|
|
93
|
-
border: "border-outline-faint",
|
|
94
|
-
dot: "bg-on-surface-faint",
|
|
95
|
-
badge: "bg-surface-sunken text-on-surface",
|
|
96
94
|
description:
|
|
97
95
|
'Skipped by the concurrency policy (on_overlap = "skip") because a previous run was still going.',
|
|
98
96
|
},
|
|
99
97
|
log_overflow: {
|
|
98
|
+
...DANGER,
|
|
100
99
|
icon: FileExclamationPoint,
|
|
101
|
-
color: "text-danger-surface",
|
|
102
|
-
bg: "bg-danger-soft",
|
|
103
|
-
border: "border-danger-soft",
|
|
104
|
-
dot: "bg-danger-surface",
|
|
105
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
106
100
|
description: "The run hit log_max_size and was handled because of log_on_full.",
|
|
107
101
|
},
|
|
108
102
|
queue_full: {
|
|
103
|
+
...WARNING,
|
|
109
104
|
icon: SkipForward,
|
|
110
|
-
color: "text-warning-surface",
|
|
111
|
-
bg: "bg-warning-soft",
|
|
112
|
-
border: "border-warning-soft",
|
|
113
|
-
dot: "bg-warning-surface",
|
|
114
|
-
badge: "bg-warning-soft text-warning-soft-text",
|
|
115
105
|
description: "Skipped because the task's queue was already at max_queued.",
|
|
116
106
|
},
|
|
117
107
|
dst_skipped: {
|
|
108
|
+
...NEUTRAL,
|
|
118
109
|
icon: SkipForward,
|
|
119
|
-
color: "text-on-surface-muted",
|
|
120
|
-
bg: "bg-surface-sunken",
|
|
121
|
-
border: "border-outline-faint",
|
|
122
|
-
dot: "bg-on-surface-faint",
|
|
123
|
-
badge: "bg-surface-sunken text-on-surface",
|
|
124
110
|
description:
|
|
125
111
|
"Skipped: this cron tick was the duplicate half of a DST fall-back, so it was recorded but not run.",
|
|
126
112
|
},
|
|
127
113
|
daemon_stopped: {
|
|
114
|
+
...WARNING,
|
|
128
115
|
icon: CircleStop,
|
|
129
|
-
color: "text-warning-surface",
|
|
130
|
-
bg: "bg-warning-soft",
|
|
131
|
-
border: "border-warning-soft",
|
|
132
|
-
dot: "bg-warning-surface",
|
|
133
|
-
badge: "bg-warning-soft text-warning-soft-text",
|
|
134
116
|
description:
|
|
135
117
|
"The daemon shut down while this run was in flight and it exceeded shutdown_timeout; it was not resumed.",
|
|
136
118
|
},
|
|
137
119
|
missed: {
|
|
120
|
+
...DANGER,
|
|
138
121
|
icon: CalendarX,
|
|
139
|
-
color: "text-danger-surface",
|
|
140
|
-
bg: "bg-danger-soft",
|
|
141
|
-
border: "border-danger-soft",
|
|
142
|
-
dot: "bg-danger-surface",
|
|
143
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
144
122
|
description:
|
|
145
123
|
"A scheduled run never happened because the daemon was down. Detected and recorded on restart.",
|
|
146
124
|
},
|
|
147
125
|
start_failed: {
|
|
126
|
+
...DANGER,
|
|
148
127
|
icon: OctagonX,
|
|
149
|
-
color: "text-danger-surface",
|
|
150
|
-
bg: "bg-danger-soft",
|
|
151
|
-
border: "border-danger-soft",
|
|
152
|
-
dot: "bg-danger-surface",
|
|
153
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
154
128
|
description:
|
|
155
129
|
"The run (or service instance) kept failing; after restart_attempts consecutive failures, RunWisp gave up restarting it automatically.",
|
|
156
130
|
},
|
|
157
131
|
unhealthy: {
|
|
132
|
+
...DANGER,
|
|
158
133
|
icon: HeartCrack,
|
|
159
|
-
color: "text-danger-surface",
|
|
160
|
-
bg: "bg-danger-soft",
|
|
161
|
-
border: "border-danger-soft",
|
|
162
|
-
dot: "bg-danger-surface",
|
|
163
|
-
badge: "bg-danger-soft text-danger-soft-text",
|
|
164
134
|
description:
|
|
165
135
|
"The service instance kept failing its health_check, so RunWisp stopped it. Its restart policy decides what happens next.",
|
|
166
136
|
},
|
|
167
137
|
pending: {
|
|
138
|
+
...NEUTRAL,
|
|
168
139
|
icon: Clock,
|
|
169
|
-
color: "text-on-surface-muted",
|
|
170
|
-
bg: "bg-surface-sunken",
|
|
171
|
-
border: "border-outline",
|
|
172
|
-
dot: "bg-on-surface-faint",
|
|
173
|
-
badge: "bg-surface-sunken text-on-surface",
|
|
174
140
|
description: "Queued and waiting to start.",
|
|
175
141
|
},
|
|
176
142
|
ended: {
|
|
177
|
-
|
|
143
|
+
...NEUTRAL,
|
|
178
144
|
color: "text-on-surface-faint",
|
|
179
|
-
|
|
180
|
-
border: "border-outline-faint",
|
|
181
|
-
dot: "bg-on-surface-faint",
|
|
182
|
-
badge: "bg-surface-sunken text-on-surface",
|
|
145
|
+
icon: CircleDashed,
|
|
183
146
|
description: "The run finished. No specific end reason was recorded.",
|
|
184
147
|
},
|
|
185
148
|
};
|
|
186
|
-
|
|
187
|
-
export function getRunStatusConfig(status: RunStatus): RunStatusConfig {
|
|
188
|
-
return RUN_STATUS_CONFIG[status];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function runDisplayStatus(run: Pick<Run, "status" | "endReason">): RunStatus {
|
|
192
|
-
return displayStatus(run.status, run.endReason);
|
|
193
|
-
}
|
|
@@ -1,32 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import type { Task as CommonTask, Run as CommonRun } from "@runwisp/common";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Web UI component types.
|
|
8
|
-
* Source of truth for status values: {@link RunStatus} from `@runwisp/common`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export type Task = CommonTask;
|
|
12
|
-
|
|
13
|
-
export type Run = CommonRun;
|
|
14
|
-
|
|
15
|
-
export interface DaemonState {
|
|
16
|
-
name: string;
|
|
17
|
-
version: string;
|
|
18
|
-
uptime: string;
|
|
19
|
-
status: "connected" | "disconnected";
|
|
20
|
-
host: string;
|
|
21
|
-
backendUrl: string;
|
|
22
|
-
cpus: number;
|
|
23
|
-
memory: string;
|
|
24
|
-
os: string;
|
|
25
|
-
arch: string;
|
|
26
|
-
workDir: string;
|
|
27
|
-
fingerprint: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
4
|
export interface DaemonStats {
|
|
31
5
|
cpuUsage: number;
|
|
32
6
|
memUsage: number;
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
// DataGrid's header checkbox (checked / indeterminate / unchecked) must
|
|
5
|
-
// reflect which rows on the current page are ACTUALLY selected, not merely
|
|
6
|
-
// how many rows are selected. A bare count comparison is wrong the moment
|
|
7
|
-
// `selectedRows` holds a different set of rows than `pagedRows` but happens
|
|
8
|
-
// to be the same size — e.g. rows selected on a page the grid has since
|
|
9
|
-
// paged/filtered away from. Extracted so the membership check is unit
|
|
10
|
-
// testable without a component-render harness.
|
|
11
|
-
|
|
12
4
|
export interface SelectionState {
|
|
13
5
|
allSelected: boolean;
|
|
14
6
|
someSelected: boolean;
|
|
@@ -17,7 +9,8 @@ export interface SelectionState {
|
|
|
17
9
|
/**
|
|
18
10
|
* Header checkbox state for `pagedRows`: fully checked only when every row
|
|
19
11
|
* currently on the page is selected, indeterminate when some (but not all)
|
|
20
|
-
* of them are. Matches rows by `rowKey`, not by
|
|
12
|
+
* of them are. Matches rows by `rowKey`, not by count: the selection can
|
|
13
|
+
* hold rows from a page the grid has since moved away from.
|
|
21
14
|
*/
|
|
22
15
|
export function selectionState<T>(
|
|
23
16
|
pagedRows: readonly T[],
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// Declarative filter spec. A `FilterField[]` + one bound `FilterValues` object
|
|
5
5
|
// drives <FilterBar>, the client engine (`applyFilters`), and the URL sync
|
|
6
|
-
// helper
|
|
6
|
+
// helper, the same way DataGrid's `Column[]` drives the table. Add a field
|
|
7
7
|
// type here, and every table that adopts it renders identically.
|
|
8
8
|
|
|
9
9
|
import type { SelectOption } from "./Select.svelte";
|
|
@@ -44,7 +44,7 @@ export type FilterField =
|
|
|
44
44
|
primary?: boolean;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
// All values are strings
|
|
47
|
+
// All values are strings, DOM inputs are string-valued and URL params are
|
|
48
48
|
// strings; the client engine coerces (e.g. Number()) where a field needs it.
|
|
49
49
|
export type FilterValues = Record<string, string>;
|
|
50
50
|
|
|
@@ -65,7 +65,7 @@ export function isBlank(v: string | undefined): boolean {
|
|
|
65
65
|
return !v || v === "all";
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// Which value keys a field owns
|
|
68
|
+
// Which value keys a field owns, daterange owns two, everything else one.
|
|
69
69
|
// Used by chips, active-state detection, clear, and URL (de)serialization so
|
|
70
70
|
// none of them has to special-case the range type.
|
|
71
71
|
export function fieldKeys(field: FilterField): string[] {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export const RADIO_GROUP_KEY = Symbol("radio-group");
|
|
5
|
+
|
|
6
|
+
// What a RadioGroup hands its Radio children: the shared input name and the
|
|
7
|
+
// selected value, read and written through the group's bindable `value`.
|
|
8
|
+
export interface RadioGroupContext {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
export { default as theme } from "./theme.css?inline";
|
|
5
|
-
|
|
6
4
|
export { default as Logo } from "./components/Logo.svelte";
|
|
7
5
|
|
|
8
6
|
export { default as PageContainer } from "./components/PageContainer.svelte";
|
|
@@ -19,13 +17,6 @@ export { default as Spinner } from "./components/Spinner.svelte";
|
|
|
19
17
|
|
|
20
18
|
export { default as Button } from "./components/Button.svelte";
|
|
21
19
|
export { default as LinkButton } from "./components/LinkButton.svelte";
|
|
22
|
-
export {
|
|
23
|
-
BUTTON_BASE,
|
|
24
|
-
BUTTON_SIZES,
|
|
25
|
-
BUTTON_VARIANTS,
|
|
26
|
-
type ButtonSize,
|
|
27
|
-
type ButtonVariant,
|
|
28
|
-
} from "./components/button-styles.js";
|
|
29
20
|
export { default as Checkbox } from "./components/Checkbox.svelte";
|
|
30
21
|
export { default as CronInput } from "./components/CronInput.svelte";
|
|
31
22
|
export { default as DurationInput } from "./components/DurationInput.svelte";
|
|
@@ -33,7 +24,7 @@ export { default as FormField } from "./components/FormField.svelte";
|
|
|
33
24
|
export { default as Input } from "./components/Input.svelte";
|
|
34
25
|
export { default as Radio } from "./components/Radio.svelte";
|
|
35
26
|
export { default as RadioGroup } from "./components/RadioGroup.svelte";
|
|
36
|
-
export { default as Select
|
|
27
|
+
export { default as Select } from "./components/Select.svelte";
|
|
37
28
|
export { default as Textarea } from "./components/Textarea.svelte";
|
|
38
29
|
export { default as TimezoneSelect } from "./components/TimezoneSelect.svelte";
|
|
39
30
|
export { default as Toggle } from "./components/Toggle.svelte";
|
|
@@ -88,71 +79,45 @@ export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
|
|
|
88
79
|
|
|
89
80
|
export { default as CodeBlock } from "./components/CodeBlock.svelte";
|
|
90
81
|
export { default as TaskCard } from "./components/TaskCard.svelte";
|
|
91
|
-
export type { TaskCardAccent } from "./components/TaskCard.svelte";
|
|
92
82
|
|
|
93
83
|
export { default as LogConsole } from "./components/LogConsole.svelte";
|
|
94
84
|
export type { FetchLogsFn, LogEvent, LogSlice } from "./log-console/types.js";
|
|
95
|
-
export { isLogEvent } from "./log-console/types.js";
|
|
96
|
-
export { LogCache } from "./log-console/LogCache.svelte.js";
|
|
97
|
-
export { LogFetcher } from "./log-console/LogFetcher.svelte.js";
|
|
98
85
|
|
|
99
86
|
export { default as RunDetailPanel } from "./components/dashboard/RunDetailPanel.svelte";
|
|
100
87
|
export { default as RunsList } from "./components/dashboard/RunsList.svelte";
|
|
101
88
|
export { default as RunFilterPopover } from "./components/dashboard/RunFilterPopover.svelte";
|
|
102
89
|
export type { RunOutputMatch } from "./components/dashboard/RunsList.svelte";
|
|
103
90
|
export {
|
|
91
|
+
activeFilterCount,
|
|
104
92
|
emptyRunFilters,
|
|
105
93
|
FAILURE_STATUS_TOKEN,
|
|
106
|
-
NEEDS_ATTENTION_STATUSES,
|
|
107
|
-
isNeedsAttention,
|
|
108
|
-
activeFilterCount,
|
|
109
|
-
activeDimensions,
|
|
110
|
-
dimensionActive,
|
|
111
|
-
clearDimension,
|
|
112
|
-
clearPopoverFilters,
|
|
113
94
|
humanizeStatus,
|
|
114
|
-
statusChipLabel,
|
|
115
95
|
exitCodeRange,
|
|
116
96
|
type ExitCodeRange,
|
|
117
97
|
type RunsListFilters,
|
|
118
|
-
type RunsListSortDirection,
|
|
119
|
-
type FilterDimension,
|
|
120
98
|
} from "./components/dashboard/run-filters.js";
|
|
121
|
-
export {
|
|
122
|
-
getRunStatusConfig,
|
|
123
|
-
runDisplayStatus,
|
|
124
|
-
RUN_STATUS_CONFIG,
|
|
125
|
-
type RunStatusConfig,
|
|
126
|
-
} from "./components/dashboard/status-config.js";
|
|
99
|
+
export { RUN_STATUS_CONFIG } from "./components/dashboard/status-config.js";
|
|
127
100
|
export {
|
|
128
101
|
runDuration,
|
|
129
|
-
runStartDelay,
|
|
130
102
|
runPhaseOrder,
|
|
131
103
|
formatTriggeredByLabel,
|
|
132
|
-
runRetryLabel,
|
|
133
104
|
instanceSuffix,
|
|
134
105
|
} from "./components/dashboard/run-helpers.js";
|
|
135
|
-
export { humanizeCron
|
|
136
|
-
export type {
|
|
106
|
+
export { humanizeCron } from "./components/dashboard/cron-format.js";
|
|
107
|
+
export type { DaemonStats } from "./components/dashboard/types.js";
|
|
137
108
|
|
|
138
|
-
export { toast
|
|
139
|
-
export {
|
|
140
|
-
themeStore,
|
|
141
|
-
THEME_STORAGE_KEY,
|
|
142
|
-
type ThemePreference,
|
|
143
|
-
type ResolvedTheme,
|
|
144
|
-
} from "./utils/theme.svelte.js";
|
|
109
|
+
export { toast } from "./utils/toast.svelte.js";
|
|
145
110
|
export { extractErrorMessage } from "./utils/error.js";
|
|
146
111
|
export {
|
|
147
112
|
formatBytes,
|
|
148
113
|
formatRelativeTime,
|
|
149
114
|
formatRelativeTimeWithAbsolute,
|
|
150
|
-
formatDateTime,
|
|
151
115
|
formatDuration,
|
|
152
116
|
formatFullDateTime,
|
|
153
117
|
} from "./utils/format.js";
|
|
154
118
|
export { formatShortId } from "./utils/id.js";
|
|
155
119
|
export { debounce } from "./utils/debounce.js";
|
|
156
120
|
export { TickingNow } from "./utils/ticking-now.svelte.js";
|
|
121
|
+
export { CopyFeedback, copyText } from "./utils/clipboard.svelte.js";
|
|
157
122
|
export { RunMotion } from "./utils/run-motion.js";
|
|
158
|
-
export { arrival, leave, shift
|
|
123
|
+
export { arrival, leave, shift } from "./actions/row-motion.js";
|
|
@@ -14,7 +14,7 @@ import { visibleColumns } from "./ansi.js";
|
|
|
14
14
|
*/
|
|
15
15
|
// Streams pinned to the front of the overlay, in render order. The companion
|
|
16
16
|
// Set backs the membership test in `overlayRows`. Both are module-level,
|
|
17
|
-
// immutable, and non-reactive
|
|
17
|
+
// immutable, and non-reactive, a fixed lookup table, not component state.
|
|
18
18
|
const PREFERRED_STREAM_ORDER = ["stdout", "stderr"] as const;
|
|
19
19
|
const PREFERRED_STREAMS: ReadonlySet<string> = new Set(PREFERRED_STREAM_ORDER);
|
|
20
20
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createLogger } from "@runwisp/common";
|
|
5
5
|
import { SvelteSet } from "svelte/reactivity";
|
|
6
6
|
import type { LogCache } from "./LogCache.svelte.js";
|
|
7
7
|
import type { FetchLogsFn } from "./types.js";
|
|
8
8
|
import { isLogEvent } from "./types.js";
|
|
9
9
|
|
|
10
|
-
const createLogger = createLoggerFactory();
|
|
11
10
|
const logger = createLogger("LogFetcher");
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -20,8 +19,8 @@ const logger = createLogger("LogFetcher");
|
|
|
20
19
|
*/
|
|
21
20
|
export class LogFetcher {
|
|
22
21
|
private readonly cache: LogCache;
|
|
23
|
-
private fetchLogsFn: FetchLogsFn | undefined;
|
|
24
|
-
private chunkSize
|
|
22
|
+
private readonly fetchLogsFn: FetchLogsFn | undefined;
|
|
23
|
+
private readonly chunkSize = 4096;
|
|
25
24
|
|
|
26
25
|
readonly MAX_INFLIGHT = 2;
|
|
27
26
|
readonly MIN_REQUEST_INTERVAL_MS = 140;
|
|
@@ -38,23 +37,13 @@ export class LogFetcher {
|
|
|
38
37
|
constructor(
|
|
39
38
|
cache: LogCache,
|
|
40
39
|
fetchLogsFn: FetchLogsFn | undefined,
|
|
41
|
-
chunkSize: number = 4096,
|
|
42
40
|
onDataLoaded?: (min: number, max: number) => void,
|
|
43
41
|
) {
|
|
44
42
|
this.cache = cache;
|
|
45
43
|
this.fetchLogsFn = fetchLogsFn;
|
|
46
|
-
this.chunkSize = Math.max(1, Math.floor(chunkSize));
|
|
47
44
|
this.onDataLoaded = onDataLoaded;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
setFetchLogsFn(fn: FetchLogsFn | undefined) {
|
|
51
|
-
this.fetchLogsFn = fn;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
setChunkSize(size: number) {
|
|
55
|
-
this.chunkSize = Math.max(1, Math.floor(size));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
47
|
enqueue(from: number, to: number) {
|
|
59
48
|
if (!this.fetchLogsFn) return;
|
|
60
49
|
|
|
@@ -45,7 +45,7 @@ const ANSI_ESCAPE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
|
45
45
|
const TAB_WIDTH = 8;
|
|
46
46
|
|
|
47
47
|
// Count the visible columns a log line occupies once ANSI escape sequences
|
|
48
|
-
// are stripped. Tabs are charged the full tab width
|
|
48
|
+
// are stripped. Tabs are charged the full tab width, an over-estimate that
|
|
49
49
|
// only adds trailing slack to the horizontal scroll surface, never clips.
|
|
50
50
|
export function visibleColumns(text: string): number {
|
|
51
51
|
let columns = 0;
|