@runwisp/ui 0.4.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/LICENSE +215 -0
- package/README.md +20 -0
- package/package.json +128 -0
- package/src/app.d.ts +8 -0
- package/src/app.html +12 -0
- package/src/astro/CTABand.astro +32 -0
- package/src/astro/Hero.astro +58 -0
- package/src/astro/SiteFooter.astro +47 -0
- package/src/astro/SiteHeader.astro +46 -0
- package/src/lib/actions/portal.ts +16 -0
- package/src/lib/assets/runwisp-logo.svg +16 -0
- package/src/lib/components/Accordion.svelte +20 -0
- package/src/lib/components/AccordionItem.svelte +74 -0
- package/src/lib/components/Alert.svelte +107 -0
- package/src/lib/components/AlertDialog.svelte +63 -0
- package/src/lib/components/Avatar.svelte +88 -0
- package/src/lib/components/Badge.svelte +131 -0
- package/src/lib/components/Breadcrumb.svelte +58 -0
- package/src/lib/components/Button.svelte +69 -0
- package/src/lib/components/Card.svelte +70 -0
- package/src/lib/components/Checkbox.svelte +90 -0
- package/src/lib/components/CodeBlock.svelte +66 -0
- package/src/lib/components/CronInput.svelte +130 -0
- package/src/lib/components/DataGrid.svelte +368 -0
- package/src/lib/components/Drawer.svelte +156 -0
- package/src/lib/components/Dropdown.svelte +195 -0
- package/src/lib/components/DurationInput.svelte +170 -0
- package/src/lib/components/EmptyState.svelte +46 -0
- package/src/lib/components/ErrorState.svelte +73 -0
- package/src/lib/components/FeatureCard.svelte +35 -0
- package/src/lib/components/FeatureGrid.svelte +37 -0
- package/src/lib/components/FormField.svelte +53 -0
- package/src/lib/components/Heading.svelte +46 -0
- package/src/lib/components/Input.svelte +114 -0
- package/src/lib/components/Kbd.svelte +31 -0
- package/src/lib/components/Link.svelte +56 -0
- package/src/lib/components/LinkButton.svelte +55 -0
- package/src/lib/components/LogConsole.svelte +424 -0
- package/src/lib/components/Logo.svelte +34 -0
- package/src/lib/components/Modal.svelte +138 -0
- package/src/lib/components/PageContainer.svelte +38 -0
- package/src/lib/components/Pagination.svelte +96 -0
- package/src/lib/components/Popover.svelte +130 -0
- package/src/lib/components/Progress.svelte +68 -0
- package/src/lib/components/Prose.svelte +43 -0
- package/src/lib/components/Radio.svelte +82 -0
- package/src/lib/components/RadioGroup.svelte +53 -0
- package/src/lib/components/Select.svelte +454 -0
- package/src/lib/components/Separator.svelte +30 -0
- package/src/lib/components/Skeleton.svelte +75 -0
- package/src/lib/components/Sparkline.svelte +58 -0
- package/src/lib/components/Spinner.svelte +44 -0
- package/src/lib/components/StatusIndicator.svelte +203 -0
- package/src/lib/components/Table.svelte +19 -0
- package/src/lib/components/TableBody.svelte +17 -0
- package/src/lib/components/TableCell.svelte +34 -0
- package/src/lib/components/TableHead.svelte +19 -0
- package/src/lib/components/TableRow.svelte +22 -0
- package/src/lib/components/Tabs.svelte +148 -0
- package/src/lib/components/Text.svelte +57 -0
- package/src/lib/components/Textarea.svelte +81 -0
- package/src/lib/components/ThemeToggle.svelte +47 -0
- package/src/lib/components/TimezoneSelect.svelte +127 -0
- package/src/lib/components/ToastContainer.svelte +84 -0
- package/src/lib/components/Toggle.svelte +84 -0
- package/src/lib/components/Tooltip.svelte +56 -0
- package/src/lib/components/button-styles.ts +51 -0
- package/src/lib/components/dashboard/RunDetailPanel.svelte +252 -0
- package/src/lib/components/dashboard/RunsList.svelte +331 -0
- package/src/lib/components/dashboard/run-helpers.ts +12 -0
- package/src/lib/components/dashboard/status-config.ts +141 -0
- package/src/lib/components/dashboard/types.ts +35 -0
- package/src/lib/index.ts +122 -0
- package/src/lib/log-console/LogCache.svelte.ts +93 -0
- package/src/lib/log-console/LogFetcher.svelte.ts +179 -0
- package/src/lib/log-console/types.ts +24 -0
- package/src/lib/theme-tokens.css +235 -0
- package/src/lib/theme.css +276 -0
- package/src/lib/utils/error.ts +18 -0
- package/src/lib/utils/format.ts +76 -0
- package/src/lib/utils/id.ts +6 -0
- package/src/lib/utils/toast.svelte.ts +87 -0
- package/src/lib/utils/toast.ts +60 -0
- package/src/routes/+layout.svelte +10 -0
- package/src/routes/+page.svelte +5 -0
- package/src/routes/layout.css +3 -0
- package/src/stories/Alert.stories.svelte +64 -0
- package/src/stories/Avatar.stories.svelte +76 -0
- package/src/stories/Badge.stories.svelte +76 -0
- package/src/stories/Breadcrumb.stories.svelte +64 -0
- package/src/stories/Button.stories.svelte +101 -0
- package/src/stories/Card.stories.svelte +106 -0
- package/src/stories/Checkbox.stories.svelte +77 -0
- package/src/stories/DataGrid.stories.svelte +130 -0
- package/src/stories/Dropdown.stories.svelte +81 -0
- package/src/stories/EmptyState.stories.svelte +74 -0
- package/src/stories/Input.stories.svelte +88 -0
- package/src/stories/Logo.stories.svelte +82 -0
- package/src/stories/Modal.stories.svelte +101 -0
- package/src/stories/Progress.stories.svelte +65 -0
- package/src/stories/Select.stories.svelte +158 -0
- package/src/stories/Tabs.stories.svelte +103 -0
- package/src/stories/Textarea.stories.svelte +45 -0
- package/src/stories/Toggle.stories.svelte +74 -0
- package/src/stories/Tooltip.stories.svelte +77 -0
- package/src/stories/Welcome.mdx +241 -0
- package/src/stories/assets/runwisp_c.svg +16 -0
- package/src/svelte-shims.d.ts +18 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
|
|
2
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
3
|
+
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import {
|
|
6
|
+
Clock,
|
|
7
|
+
Timer,
|
|
8
|
+
ChevronLeft,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
ArrowUpDown,
|
|
11
|
+
Search,
|
|
12
|
+
Funnel,
|
|
13
|
+
} from "@lucide/svelte";
|
|
14
|
+
import Button from "../Button.svelte";
|
|
15
|
+
import Badge from "../Badge.svelte";
|
|
16
|
+
import type { Run } from "./types.js";
|
|
17
|
+
import type { RunStatus } from "@runwisp/common";
|
|
18
|
+
import { getRunStatusConfig, runDisplayStatus } from "./status-config.js";
|
|
19
|
+
import { runDuration } from "./run-helpers.js";
|
|
20
|
+
import { formatRelativeTime } from "../../utils/format.js";
|
|
21
|
+
import { formatShortId } from "../../utils/id.js";
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
runs,
|
|
25
|
+
selectedRunId = $bindable(null),
|
|
26
|
+
onselect,
|
|
27
|
+
showFilters = false,
|
|
28
|
+
showTaskName = false,
|
|
29
|
+
headerLabel = "Run History",
|
|
30
|
+
emptyText = "No runs yet",
|
|
31
|
+
}: {
|
|
32
|
+
runs: Run[];
|
|
33
|
+
selectedRunId?: string | null;
|
|
34
|
+
onselect?: (runId: string) => void;
|
|
35
|
+
showFilters?: boolean;
|
|
36
|
+
showTaskName?: boolean;
|
|
37
|
+
headerLabel?: string;
|
|
38
|
+
emptyText?: string;
|
|
39
|
+
} = $props();
|
|
40
|
+
|
|
41
|
+
function selectRun(runId: string) {
|
|
42
|
+
selectedRunId = runId;
|
|
43
|
+
onselect?.(runId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let searchQuery = $state("");
|
|
47
|
+
let statusFilter = $state<RunStatus | "all">("all");
|
|
48
|
+
let sortDirection = $state<"asc" | "desc">("desc");
|
|
49
|
+
let currentPage = $state(1);
|
|
50
|
+
const pageSize = 10;
|
|
51
|
+
|
|
52
|
+
let filteredRuns = $derived(
|
|
53
|
+
showFilters
|
|
54
|
+
? runs.filter((r: Run) => {
|
|
55
|
+
if (statusFilter !== "all" && runDisplayStatus(r) !== statusFilter) return false;
|
|
56
|
+
if (searchQuery.trim()) {
|
|
57
|
+
const q = searchQuery.toLowerCase();
|
|
58
|
+
return (
|
|
59
|
+
r.id.toLowerCase().includes(q) || r.task_name.toLowerCase().includes(q)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
})
|
|
64
|
+
: runs,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
let sortedRuns = $derived(
|
|
68
|
+
[...filteredRuns].sort((a: Run, b: Run) => {
|
|
69
|
+
const dateA = new Date(a.created_at).getTime();
|
|
70
|
+
const dateB = new Date(b.created_at).getTime();
|
|
71
|
+
return sortDirection === "desc" ? dateB - dateA : dateA - dateB;
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
let totalPages = $derived(Math.max(1, Math.ceil(filteredRuns.length / pageSize)));
|
|
76
|
+
let paginatedRuns = $derived(
|
|
77
|
+
sortedRuns.slice((currentPage - 1) * pageSize, currentPage * pageSize),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (!selectedRunId) return;
|
|
82
|
+
const idx = sortedRuns.findIndex((r: Run) => r.id === selectedRunId);
|
|
83
|
+
if (idx < 0) return;
|
|
84
|
+
const targetPage = Math.floor(idx / pageSize) + 1;
|
|
85
|
+
if (targetPage !== currentPage) currentPage = targetPage;
|
|
86
|
+
});
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<div
|
|
90
|
+
class="flex flex-col overflow-hidden rounded-xl border border-outline bg-surface-raised shadow-sm md:col-span-4 lg:col-span-3"
|
|
91
|
+
>
|
|
92
|
+
{#if showFilters}
|
|
93
|
+
<!-- Filter Header -->
|
|
94
|
+
<div class="shrink-0 space-y-3 border-b border-outline-faint bg-surface-sunken p-3">
|
|
95
|
+
<div class="flex items-center justify-between">
|
|
96
|
+
<span class="text-xs font-semibold tracking-wider text-on-surface-muted uppercase"
|
|
97
|
+
>{headerLabel}</span
|
|
98
|
+
>
|
|
99
|
+
<Badge
|
|
100
|
+
variant="default"
|
|
101
|
+
size="sm"
|
|
102
|
+
class="bg-outline text-on-surface hover:bg-outline-faint"
|
|
103
|
+
>{filteredRuns.length}</Badge
|
|
104
|
+
>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="relative">
|
|
108
|
+
<Search
|
|
109
|
+
class="absolute top-1/2 left-2.5 -translate-y-1/2 text-on-surface-faint"
|
|
110
|
+
size={14}
|
|
111
|
+
/>
|
|
112
|
+
<input
|
|
113
|
+
type="text"
|
|
114
|
+
bind:value={searchQuery}
|
|
115
|
+
placeholder="Search task or ID..."
|
|
116
|
+
class="h-8 w-full rounded-md border border-outline bg-surface-raised pr-3 pl-8 text-sm transition-all placeholder:text-on-surface-faint focus:border-ring focus:ring-2 focus:ring-ring/20 focus:outline-none"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="flex items-center gap-2">
|
|
121
|
+
<div class="relative flex-1">
|
|
122
|
+
<select
|
|
123
|
+
bind:value={statusFilter}
|
|
124
|
+
class="h-7 w-full appearance-none rounded-md border border-outline bg-surface-raised pr-6 pl-2 text-xs text-on-surface-muted focus:border-ring focus:outline-none"
|
|
125
|
+
>
|
|
126
|
+
<option value="all">All Statuses</option>
|
|
127
|
+
<option value="running">Running</option>
|
|
128
|
+
<option value="success">Success</option>
|
|
129
|
+
<option value="failed">Failed</option>
|
|
130
|
+
<option value="crashed">Crashed</option>
|
|
131
|
+
<option value="skipped">Skipped</option>
|
|
132
|
+
</select>
|
|
133
|
+
<Funnel
|
|
134
|
+
class="pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 text-on-surface-faint"
|
|
135
|
+
size={12}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<Button
|
|
140
|
+
variant="ghost"
|
|
141
|
+
size="xs"
|
|
142
|
+
class="h-7 w-7 px-0"
|
|
143
|
+
onclick={() => (sortDirection = sortDirection === "desc" ? "asc" : "desc")}
|
|
144
|
+
title="Toggle Sort Order"
|
|
145
|
+
>
|
|
146
|
+
{#snippet icon()}
|
|
147
|
+
<ArrowUpDown
|
|
148
|
+
size={14}
|
|
149
|
+
class="text-on-surface-muted {sortDirection === 'asc'
|
|
150
|
+
? 'rotate-180'
|
|
151
|
+
: ''}"
|
|
152
|
+
/>
|
|
153
|
+
{/snippet}
|
|
154
|
+
</Button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
{:else}
|
|
158
|
+
<!-- Simple Header -->
|
|
159
|
+
<div
|
|
160
|
+
class="flex shrink-0 items-center justify-between border-b border-outline-faint bg-surface-sunken p-3"
|
|
161
|
+
>
|
|
162
|
+
<div class="flex items-center gap-2">
|
|
163
|
+
<span class="text-xs font-semibold tracking-wider text-on-surface-muted uppercase"
|
|
164
|
+
>{headerLabel}</span
|
|
165
|
+
>
|
|
166
|
+
<Badge variant="default" size="sm">{runs.length}</Badge>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="flex items-center gap-1">
|
|
169
|
+
<Button
|
|
170
|
+
variant="ghost"
|
|
171
|
+
size="xs"
|
|
172
|
+
onclick={() => (sortDirection = sortDirection === "desc" ? "asc" : "desc")}
|
|
173
|
+
title="Sort by Date"
|
|
174
|
+
>
|
|
175
|
+
{#snippet icon()}
|
|
176
|
+
<ArrowUpDown
|
|
177
|
+
size={14}
|
|
178
|
+
class="text-on-surface-muted {sortDirection === 'asc'
|
|
179
|
+
? 'rotate-180'
|
|
180
|
+
: ''}"
|
|
181
|
+
/>
|
|
182
|
+
{/snippet}
|
|
183
|
+
</Button>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
{/if}
|
|
187
|
+
|
|
188
|
+
<div class="min-h-0 flex-1 space-y-1 overflow-y-auto p-2">
|
|
189
|
+
{#each paginatedRuns as run (run.id)}
|
|
190
|
+
{@const isActive = selectedRunId === run.id}
|
|
191
|
+
{@const config = getRunStatusConfig(runDisplayStatus(run))}
|
|
192
|
+
{@const Icon = config.icon}
|
|
193
|
+
{@const duration = runDuration(run)}
|
|
194
|
+
<button
|
|
195
|
+
class="group duration-normal relative w-full rounded-lg border p-3 text-left transition-all select-none
|
|
196
|
+
{isActive
|
|
197
|
+
? 'border-primary-soft bg-primary-soft/50 shadow-sm'
|
|
198
|
+
: 'border-transparent bg-surface-raised hover:border-outline hover:bg-surface-sunken'}"
|
|
199
|
+
onclick={() => selectRun(run.id)}
|
|
200
|
+
onkeydown={(e) => e.key === "Enter" && selectRun(run.id)}
|
|
201
|
+
>
|
|
202
|
+
{#if showTaskName}
|
|
203
|
+
<!-- Runs variant: task name as primary, shortId secondary -->
|
|
204
|
+
<div class="mb-1 flex items-center justify-between">
|
|
205
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
206
|
+
<div class="{config.color} shrink-0">
|
|
207
|
+
<Icon
|
|
208
|
+
size={16}
|
|
209
|
+
class={run.status === "running" ? "animate-spin" : ""}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
<span class="truncate text-sm font-semibold text-on-surface">
|
|
213
|
+
{run.task_name}{#if run.instance_index > 0}<span
|
|
214
|
+
class="text-on-surface-muted">#{run.instance_index}</span
|
|
215
|
+
>{/if}
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
<span
|
|
219
|
+
class="shrink-0 text-[10px] {isActive
|
|
220
|
+
? 'font-medium text-primary'
|
|
221
|
+
: 'text-on-surface-faint'}"
|
|
222
|
+
>
|
|
223
|
+
{formatRelativeTime(run.start_at || run.created_at)}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="flex items-center justify-between pl-6 text-xs">
|
|
228
|
+
<div
|
|
229
|
+
class="flex items-center gap-1 font-mono text-[11px] text-on-surface-muted"
|
|
230
|
+
>
|
|
231
|
+
#{formatShortId(run.id)}
|
|
232
|
+
</div>
|
|
233
|
+
{#if duration}
|
|
234
|
+
<div
|
|
235
|
+
class="flex items-center gap-1 text-on-surface-faint {isActive
|
|
236
|
+
? 'text-primary'
|
|
237
|
+
: ''}"
|
|
238
|
+
>
|
|
239
|
+
<Timer size={10} />
|
|
240
|
+
<span class="font-mono">{duration}</span>
|
|
241
|
+
</div>
|
|
242
|
+
{/if}
|
|
243
|
+
</div>
|
|
244
|
+
{:else}
|
|
245
|
+
<!-- Task variant: shortId as primary, status secondary -->
|
|
246
|
+
<div class="mb-1.5 flex items-center justify-between">
|
|
247
|
+
<div class="flex items-center gap-2">
|
|
248
|
+
<div class={config.color}>
|
|
249
|
+
<Icon
|
|
250
|
+
size={16}
|
|
251
|
+
class={run.status === "running" ? "animate-spin" : ""}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
<span class="font-mono text-xs font-medium text-on-surface">
|
|
255
|
+
#{formatShortId(run.id)}
|
|
256
|
+
</span>
|
|
257
|
+
</div>
|
|
258
|
+
<span
|
|
259
|
+
class="text-[10px] {isActive
|
|
260
|
+
? 'font-medium text-primary'
|
|
261
|
+
: 'text-on-surface-faint'}"
|
|
262
|
+
>
|
|
263
|
+
{formatRelativeTime(run.start_at || run.created_at)}
|
|
264
|
+
</span>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="flex items-center justify-between text-xs">
|
|
268
|
+
<div class="flex items-center gap-2 text-on-surface-muted">
|
|
269
|
+
<span class="capitalize">{runDisplayStatus(run)}</span>
|
|
270
|
+
{#if run.instance_index > 0}
|
|
271
|
+
<span class="font-mono text-[11px] text-on-surface-faint"
|
|
272
|
+
>instance #{run.instance_index}</span
|
|
273
|
+
>
|
|
274
|
+
{/if}
|
|
275
|
+
</div>
|
|
276
|
+
{#if duration}
|
|
277
|
+
<div
|
|
278
|
+
class="flex items-center gap-1 text-on-surface-faint {isActive
|
|
279
|
+
? 'text-primary'
|
|
280
|
+
: ''}"
|
|
281
|
+
>
|
|
282
|
+
<Timer size={10} />
|
|
283
|
+
<span class="font-mono">{duration}</span>
|
|
284
|
+
</div>
|
|
285
|
+
{/if}
|
|
286
|
+
</div>
|
|
287
|
+
{/if}
|
|
288
|
+
|
|
289
|
+
{#if isActive}
|
|
290
|
+
<div
|
|
291
|
+
class="absolute top-1/2 left-0 h-8 w-1 -translate-y-1/2 rounded-r-full bg-primary"
|
|
292
|
+
></div>
|
|
293
|
+
{/if}
|
|
294
|
+
</button>
|
|
295
|
+
{/each}
|
|
296
|
+
|
|
297
|
+
{#if paginatedRuns.length === 0}
|
|
298
|
+
<div class="flex flex-col items-center gap-3 p-8 text-center text-on-surface-faint">
|
|
299
|
+
<div class="rounded-full bg-surface-sunken p-3">
|
|
300
|
+
<Clock size={24} class="opacity-50" />
|
|
301
|
+
</div>
|
|
302
|
+
<span class="text-sm">{emptyText}</span>
|
|
303
|
+
</div>
|
|
304
|
+
{/if}
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<!-- Pagination Footer -->
|
|
308
|
+
<div
|
|
309
|
+
class="flex shrink-0 items-center justify-between border-t border-outline-faint bg-surface-sunken p-2"
|
|
310
|
+
>
|
|
311
|
+
<Button
|
|
312
|
+
variant="ghost"
|
|
313
|
+
size="xs"
|
|
314
|
+
disabled={currentPage === 1}
|
|
315
|
+
onclick={() => currentPage--}
|
|
316
|
+
>
|
|
317
|
+
{#snippet icon()}<ChevronLeft size={14} />{/snippet}
|
|
318
|
+
</Button>
|
|
319
|
+
<span class="text-[10px] font-medium tracking-wide text-on-surface-muted uppercase">
|
|
320
|
+
Page {currentPage} of {totalPages}
|
|
321
|
+
</span>
|
|
322
|
+
<Button
|
|
323
|
+
variant="ghost"
|
|
324
|
+
size="xs"
|
|
325
|
+
disabled={currentPage === totalPages}
|
|
326
|
+
onclick={() => currentPage++}
|
|
327
|
+
>
|
|
328
|
+
{#snippet icon()}<ChevronRight size={14} />{/snippet}
|
|
329
|
+
</Button>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Run } from "@runwisp/common";
|
|
5
|
+
import { formatDuration } from "../../utils/format.js";
|
|
6
|
+
|
|
7
|
+
export function runDuration(run: Pick<Run, "start_at" | "end_at">): string | undefined {
|
|
8
|
+
if (!run.start_at) return undefined;
|
|
9
|
+
const start = new Date(run.start_at).getTime();
|
|
10
|
+
const end = run.end_at ? new Date(run.end_at).getTime() : Date.now();
|
|
11
|
+
return formatDuration(end - start);
|
|
12
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Copyright (c) PoppyCake, s.r.o. SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CircleCheck,
|
|
5
|
+
CircleX,
|
|
6
|
+
CircleAlert,
|
|
7
|
+
LoaderCircle,
|
|
8
|
+
Clock,
|
|
9
|
+
CircleStop,
|
|
10
|
+
TimerOff,
|
|
11
|
+
CircleDashed,
|
|
12
|
+
SkipForward,
|
|
13
|
+
FileExclamationPoint,
|
|
14
|
+
} from "@lucide/svelte";
|
|
15
|
+
import type { Component } from "svelte";
|
|
16
|
+
import { displayStatus, type RunStatus } from "@runwisp/common";
|
|
17
|
+
import type { Run } from "@runwisp/common";
|
|
18
|
+
|
|
19
|
+
export interface RunStatusConfig {
|
|
20
|
+
icon: Component;
|
|
21
|
+
color: string;
|
|
22
|
+
bg: string;
|
|
23
|
+
border: string;
|
|
24
|
+
dot: string;
|
|
25
|
+
badge: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const RUN_STATUS_CONFIG: Record<RunStatus, RunStatusConfig> = {
|
|
29
|
+
running: {
|
|
30
|
+
icon: LoaderCircle,
|
|
31
|
+
color: "text-info-surface",
|
|
32
|
+
bg: "bg-info-soft",
|
|
33
|
+
border: "border-info-soft",
|
|
34
|
+
dot: "bg-info-surface animate-pulse",
|
|
35
|
+
badge: "bg-info-soft text-info-soft-text",
|
|
36
|
+
},
|
|
37
|
+
success: {
|
|
38
|
+
icon: CircleCheck,
|
|
39
|
+
color: "text-success-surface",
|
|
40
|
+
bg: "bg-success-soft",
|
|
41
|
+
border: "border-success-soft",
|
|
42
|
+
dot: "bg-success-surface",
|
|
43
|
+
badge: "bg-success-soft text-success-soft-text",
|
|
44
|
+
},
|
|
45
|
+
failed: {
|
|
46
|
+
icon: CircleX,
|
|
47
|
+
color: "text-danger-surface",
|
|
48
|
+
bg: "bg-danger-soft",
|
|
49
|
+
border: "border-danger-soft",
|
|
50
|
+
dot: "bg-danger-surface",
|
|
51
|
+
badge: "bg-danger-soft text-danger-soft-text",
|
|
52
|
+
},
|
|
53
|
+
crashed: {
|
|
54
|
+
icon: CircleAlert,
|
|
55
|
+
color: "text-danger-surface",
|
|
56
|
+
bg: "bg-danger-soft",
|
|
57
|
+
border: "border-danger-soft",
|
|
58
|
+
dot: "bg-danger-surface",
|
|
59
|
+
badge: "bg-danger-soft text-danger-soft-text",
|
|
60
|
+
},
|
|
61
|
+
stopped: {
|
|
62
|
+
icon: CircleStop,
|
|
63
|
+
color: "text-warning-surface",
|
|
64
|
+
bg: "bg-warning-soft",
|
|
65
|
+
border: "border-warning-soft",
|
|
66
|
+
dot: "bg-warning-surface",
|
|
67
|
+
badge: "bg-warning-soft text-warning-soft-text",
|
|
68
|
+
},
|
|
69
|
+
timeout: {
|
|
70
|
+
icon: TimerOff,
|
|
71
|
+
color: "text-warning-surface",
|
|
72
|
+
bg: "bg-warning-soft",
|
|
73
|
+
border: "border-warning-soft",
|
|
74
|
+
dot: "bg-warning-surface",
|
|
75
|
+
badge: "bg-warning-soft text-warning-soft-text",
|
|
76
|
+
},
|
|
77
|
+
skipped: {
|
|
78
|
+
icon: SkipForward,
|
|
79
|
+
color: "text-on-surface-muted",
|
|
80
|
+
bg: "bg-surface-sunken",
|
|
81
|
+
border: "border-outline-faint",
|
|
82
|
+
dot: "bg-on-surface-faint",
|
|
83
|
+
badge: "bg-surface-sunken text-on-surface",
|
|
84
|
+
},
|
|
85
|
+
log_overflow: {
|
|
86
|
+
icon: FileExclamationPoint,
|
|
87
|
+
color: "text-danger-surface",
|
|
88
|
+
bg: "bg-danger-soft",
|
|
89
|
+
border: "border-danger-soft",
|
|
90
|
+
dot: "bg-danger-surface",
|
|
91
|
+
badge: "bg-danger-soft text-danger-soft-text",
|
|
92
|
+
},
|
|
93
|
+
queue_full: {
|
|
94
|
+
icon: SkipForward,
|
|
95
|
+
color: "text-warning-surface",
|
|
96
|
+
bg: "bg-warning-soft",
|
|
97
|
+
border: "border-warning-soft",
|
|
98
|
+
dot: "bg-warning-surface",
|
|
99
|
+
badge: "bg-warning-soft text-warning-soft-text",
|
|
100
|
+
},
|
|
101
|
+
dst_skipped: {
|
|
102
|
+
icon: SkipForward,
|
|
103
|
+
color: "text-on-surface-muted",
|
|
104
|
+
bg: "bg-surface-sunken",
|
|
105
|
+
border: "border-outline-faint",
|
|
106
|
+
dot: "bg-on-surface-faint",
|
|
107
|
+
badge: "bg-surface-sunken text-on-surface",
|
|
108
|
+
},
|
|
109
|
+
daemon_stopped: {
|
|
110
|
+
icon: CircleStop,
|
|
111
|
+
color: "text-warning-surface",
|
|
112
|
+
bg: "bg-warning-soft",
|
|
113
|
+
border: "border-warning-soft",
|
|
114
|
+
dot: "bg-warning-surface",
|
|
115
|
+
badge: "bg-warning-soft text-warning-soft-text",
|
|
116
|
+
},
|
|
117
|
+
pending: {
|
|
118
|
+
icon: Clock,
|
|
119
|
+
color: "text-on-surface-muted",
|
|
120
|
+
bg: "bg-surface-sunken",
|
|
121
|
+
border: "border-outline",
|
|
122
|
+
dot: "bg-on-surface-faint",
|
|
123
|
+
badge: "bg-surface-sunken text-on-surface",
|
|
124
|
+
},
|
|
125
|
+
ended: {
|
|
126
|
+
icon: CircleDashed,
|
|
127
|
+
color: "text-on-surface-faint",
|
|
128
|
+
bg: "bg-surface-sunken",
|
|
129
|
+
border: "border-outline-faint",
|
|
130
|
+
dot: "bg-on-surface-faint",
|
|
131
|
+
badge: "bg-surface-sunken text-on-surface",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export function getRunStatusConfig(status: RunStatus): RunStatusConfig {
|
|
136
|
+
return RUN_STATUS_CONFIG[status];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function runDisplayStatus(run: Pick<Run, "status" | "end_reason">): RunStatus {
|
|
140
|
+
return displayStatus(run.status, run.end_reason);
|
|
141
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
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
|
+
export interface DaemonStats {
|
|
31
|
+
cpuUsage: number;
|
|
32
|
+
memUsage: number;
|
|
33
|
+
activeTasks: number;
|
|
34
|
+
successRate: number;
|
|
35
|
+
}
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Theme
|
|
5
|
+
export { default as theme } from "./theme.css?inline";
|
|
6
|
+
|
|
7
|
+
// Brand
|
|
8
|
+
export { default as Logo } from "./components/Logo.svelte";
|
|
9
|
+
|
|
10
|
+
// Layout
|
|
11
|
+
export { default as PageContainer } from "./components/PageContainer.svelte";
|
|
12
|
+
|
|
13
|
+
// Typography
|
|
14
|
+
export { default as Heading } from "./components/Heading.svelte";
|
|
15
|
+
export { default as Prose } from "./components/Prose.svelte";
|
|
16
|
+
export { default as Text } from "./components/Text.svelte";
|
|
17
|
+
|
|
18
|
+
// Inline
|
|
19
|
+
export { default as Kbd } from "./components/Kbd.svelte";
|
|
20
|
+
export { default as Link } from "./components/Link.svelte";
|
|
21
|
+
export { default as Separator } from "./components/Separator.svelte";
|
|
22
|
+
export { default as Spinner } from "./components/Spinner.svelte";
|
|
23
|
+
|
|
24
|
+
// Form
|
|
25
|
+
export { default as Button } from "./components/Button.svelte";
|
|
26
|
+
export { default as LinkButton } from "./components/LinkButton.svelte";
|
|
27
|
+
export {
|
|
28
|
+
BUTTON_BASE,
|
|
29
|
+
BUTTON_SIZES,
|
|
30
|
+
BUTTON_VARIANTS,
|
|
31
|
+
type ButtonSize,
|
|
32
|
+
type ButtonVariant,
|
|
33
|
+
} from "./components/button-styles.js";
|
|
34
|
+
export { default as Checkbox } from "./components/Checkbox.svelte";
|
|
35
|
+
export { default as CronInput } from "./components/CronInput.svelte";
|
|
36
|
+
export { default as DurationInput } from "./components/DurationInput.svelte";
|
|
37
|
+
export { default as FormField } from "./components/FormField.svelte";
|
|
38
|
+
export { default as Input } from "./components/Input.svelte";
|
|
39
|
+
export { default as Radio } from "./components/Radio.svelte";
|
|
40
|
+
export { default as RadioGroup } from "./components/RadioGroup.svelte";
|
|
41
|
+
export { default as Select } from "./components/Select.svelte";
|
|
42
|
+
export { default as Textarea } from "./components/Textarea.svelte";
|
|
43
|
+
export { default as TimezoneSelect } from "./components/TimezoneSelect.svelte";
|
|
44
|
+
export { default as Toggle } from "./components/Toggle.svelte";
|
|
45
|
+
|
|
46
|
+
// Navigation
|
|
47
|
+
export { default as Accordion } from "./components/Accordion.svelte";
|
|
48
|
+
export { default as AccordionItem } from "./components/AccordionItem.svelte";
|
|
49
|
+
export { default as Breadcrumb } from "./components/Breadcrumb.svelte";
|
|
50
|
+
export { default as Pagination } from "./components/Pagination.svelte";
|
|
51
|
+
export { default as Tabs } from "./components/Tabs.svelte";
|
|
52
|
+
export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
|
|
53
|
+
|
|
54
|
+
// Data display
|
|
55
|
+
export { default as Avatar } from "./components/Avatar.svelte";
|
|
56
|
+
export { default as Badge } from "./components/Badge.svelte";
|
|
57
|
+
export { default as Card } from "./components/Card.svelte";
|
|
58
|
+
export { default as DataGrid } from "./components/DataGrid.svelte";
|
|
59
|
+
export { default as Progress } from "./components/Progress.svelte";
|
|
60
|
+
export { default as Skeleton } from "./components/Skeleton.svelte";
|
|
61
|
+
export { default as Sparkline } from "./components/Sparkline.svelte";
|
|
62
|
+
export { default as StatusIndicator } from "./components/StatusIndicator.svelte";
|
|
63
|
+
export { default as Table } from "./components/Table.svelte";
|
|
64
|
+
export { default as TableBody } from "./components/TableBody.svelte";
|
|
65
|
+
export { default as TableCell } from "./components/TableCell.svelte";
|
|
66
|
+
export { default as TableHead } from "./components/TableHead.svelte";
|
|
67
|
+
export { default as TableRow } from "./components/TableRow.svelte";
|
|
68
|
+
|
|
69
|
+
// Feedback / overlay
|
|
70
|
+
export { default as Alert } from "./components/Alert.svelte";
|
|
71
|
+
export { default as AlertDialog } from "./components/AlertDialog.svelte";
|
|
72
|
+
export { default as Drawer } from "./components/Drawer.svelte";
|
|
73
|
+
export { default as Dropdown } from "./components/Dropdown.svelte";
|
|
74
|
+
export { default as EmptyState } from "./components/EmptyState.svelte";
|
|
75
|
+
export { default as ErrorState } from "./components/ErrorState.svelte";
|
|
76
|
+
export { default as Modal } from "./components/Modal.svelte";
|
|
77
|
+
export { default as Popover } from "./components/Popover.svelte";
|
|
78
|
+
export { default as ToastContainer } from "./components/ToastContainer.svelte";
|
|
79
|
+
export { default as Tooltip } from "./components/Tooltip.svelte";
|
|
80
|
+
|
|
81
|
+
// Generic
|
|
82
|
+
export { default as CodeBlock } from "./components/CodeBlock.svelte";
|
|
83
|
+
export { default as FeatureCard } from "./components/FeatureCard.svelte";
|
|
84
|
+
export { default as FeatureGrid } from "./components/FeatureGrid.svelte";
|
|
85
|
+
|
|
86
|
+
// Log console
|
|
87
|
+
export { default as LogConsole } from "./components/LogConsole.svelte";
|
|
88
|
+
export type {
|
|
89
|
+
FetchLogsFn,
|
|
90
|
+
LineNumber,
|
|
91
|
+
LogEvent,
|
|
92
|
+
LogMessage,
|
|
93
|
+
LogSlice,
|
|
94
|
+
} 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
|
+
|
|
99
|
+
// Dashboard
|
|
100
|
+
export { default as RunDetailPanel } from "./components/dashboard/RunDetailPanel.svelte";
|
|
101
|
+
export { default as RunsList } from "./components/dashboard/RunsList.svelte";
|
|
102
|
+
export {
|
|
103
|
+
getRunStatusConfig,
|
|
104
|
+
runDisplayStatus,
|
|
105
|
+
RUN_STATUS_CONFIG,
|
|
106
|
+
type RunStatusConfig,
|
|
107
|
+
} from "./components/dashboard/status-config.js";
|
|
108
|
+
export { runDuration } from "./components/dashboard/run-helpers.js";
|
|
109
|
+
export type { DaemonState, DaemonStats } from "./components/dashboard/types.js";
|
|
110
|
+
|
|
111
|
+
// Utilities
|
|
112
|
+
export { toast, type Toast, type ToastType } from "./utils/toast.svelte.js";
|
|
113
|
+
export { extractErrorMessage } from "./utils/error.js";
|
|
114
|
+
export {
|
|
115
|
+
formatBytes,
|
|
116
|
+
formatRelativeTime,
|
|
117
|
+
formatRelativeTimeWithAbsolute,
|
|
118
|
+
formatDateTime,
|
|
119
|
+
formatDuration,
|
|
120
|
+
formatFullDateTime,
|
|
121
|
+
} from "./utils/format.js";
|
|
122
|
+
export { formatShortId } from "./utils/id.js";
|