@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.
Files changed (108) hide show
  1. package/LICENSE +215 -0
  2. package/README.md +20 -0
  3. package/package.json +128 -0
  4. package/src/app.d.ts +8 -0
  5. package/src/app.html +12 -0
  6. package/src/astro/CTABand.astro +32 -0
  7. package/src/astro/Hero.astro +58 -0
  8. package/src/astro/SiteFooter.astro +47 -0
  9. package/src/astro/SiteHeader.astro +46 -0
  10. package/src/lib/actions/portal.ts +16 -0
  11. package/src/lib/assets/runwisp-logo.svg +16 -0
  12. package/src/lib/components/Accordion.svelte +20 -0
  13. package/src/lib/components/AccordionItem.svelte +74 -0
  14. package/src/lib/components/Alert.svelte +107 -0
  15. package/src/lib/components/AlertDialog.svelte +63 -0
  16. package/src/lib/components/Avatar.svelte +88 -0
  17. package/src/lib/components/Badge.svelte +131 -0
  18. package/src/lib/components/Breadcrumb.svelte +58 -0
  19. package/src/lib/components/Button.svelte +69 -0
  20. package/src/lib/components/Card.svelte +70 -0
  21. package/src/lib/components/Checkbox.svelte +90 -0
  22. package/src/lib/components/CodeBlock.svelte +66 -0
  23. package/src/lib/components/CronInput.svelte +130 -0
  24. package/src/lib/components/DataGrid.svelte +368 -0
  25. package/src/lib/components/Drawer.svelte +156 -0
  26. package/src/lib/components/Dropdown.svelte +195 -0
  27. package/src/lib/components/DurationInput.svelte +170 -0
  28. package/src/lib/components/EmptyState.svelte +46 -0
  29. package/src/lib/components/ErrorState.svelte +73 -0
  30. package/src/lib/components/FeatureCard.svelte +35 -0
  31. package/src/lib/components/FeatureGrid.svelte +37 -0
  32. package/src/lib/components/FormField.svelte +53 -0
  33. package/src/lib/components/Heading.svelte +46 -0
  34. package/src/lib/components/Input.svelte +114 -0
  35. package/src/lib/components/Kbd.svelte +31 -0
  36. package/src/lib/components/Link.svelte +56 -0
  37. package/src/lib/components/LinkButton.svelte +55 -0
  38. package/src/lib/components/LogConsole.svelte +424 -0
  39. package/src/lib/components/Logo.svelte +34 -0
  40. package/src/lib/components/Modal.svelte +138 -0
  41. package/src/lib/components/PageContainer.svelte +38 -0
  42. package/src/lib/components/Pagination.svelte +96 -0
  43. package/src/lib/components/Popover.svelte +130 -0
  44. package/src/lib/components/Progress.svelte +68 -0
  45. package/src/lib/components/Prose.svelte +43 -0
  46. package/src/lib/components/Radio.svelte +82 -0
  47. package/src/lib/components/RadioGroup.svelte +53 -0
  48. package/src/lib/components/Select.svelte +454 -0
  49. package/src/lib/components/Separator.svelte +30 -0
  50. package/src/lib/components/Skeleton.svelte +75 -0
  51. package/src/lib/components/Sparkline.svelte +58 -0
  52. package/src/lib/components/Spinner.svelte +44 -0
  53. package/src/lib/components/StatusIndicator.svelte +203 -0
  54. package/src/lib/components/Table.svelte +19 -0
  55. package/src/lib/components/TableBody.svelte +17 -0
  56. package/src/lib/components/TableCell.svelte +34 -0
  57. package/src/lib/components/TableHead.svelte +19 -0
  58. package/src/lib/components/TableRow.svelte +22 -0
  59. package/src/lib/components/Tabs.svelte +148 -0
  60. package/src/lib/components/Text.svelte +57 -0
  61. package/src/lib/components/Textarea.svelte +81 -0
  62. package/src/lib/components/ThemeToggle.svelte +47 -0
  63. package/src/lib/components/TimezoneSelect.svelte +127 -0
  64. package/src/lib/components/ToastContainer.svelte +84 -0
  65. package/src/lib/components/Toggle.svelte +84 -0
  66. package/src/lib/components/Tooltip.svelte +56 -0
  67. package/src/lib/components/button-styles.ts +51 -0
  68. package/src/lib/components/dashboard/RunDetailPanel.svelte +252 -0
  69. package/src/lib/components/dashboard/RunsList.svelte +331 -0
  70. package/src/lib/components/dashboard/run-helpers.ts +12 -0
  71. package/src/lib/components/dashboard/status-config.ts +141 -0
  72. package/src/lib/components/dashboard/types.ts +35 -0
  73. package/src/lib/index.ts +122 -0
  74. package/src/lib/log-console/LogCache.svelte.ts +93 -0
  75. package/src/lib/log-console/LogFetcher.svelte.ts +179 -0
  76. package/src/lib/log-console/types.ts +24 -0
  77. package/src/lib/theme-tokens.css +235 -0
  78. package/src/lib/theme.css +276 -0
  79. package/src/lib/utils/error.ts +18 -0
  80. package/src/lib/utils/format.ts +76 -0
  81. package/src/lib/utils/id.ts +6 -0
  82. package/src/lib/utils/toast.svelte.ts +87 -0
  83. package/src/lib/utils/toast.ts +60 -0
  84. package/src/routes/+layout.svelte +10 -0
  85. package/src/routes/+page.svelte +5 -0
  86. package/src/routes/layout.css +3 -0
  87. package/src/stories/Alert.stories.svelte +64 -0
  88. package/src/stories/Avatar.stories.svelte +76 -0
  89. package/src/stories/Badge.stories.svelte +76 -0
  90. package/src/stories/Breadcrumb.stories.svelte +64 -0
  91. package/src/stories/Button.stories.svelte +101 -0
  92. package/src/stories/Card.stories.svelte +106 -0
  93. package/src/stories/Checkbox.stories.svelte +77 -0
  94. package/src/stories/DataGrid.stories.svelte +130 -0
  95. package/src/stories/Dropdown.stories.svelte +81 -0
  96. package/src/stories/EmptyState.stories.svelte +74 -0
  97. package/src/stories/Input.stories.svelte +88 -0
  98. package/src/stories/Logo.stories.svelte +82 -0
  99. package/src/stories/Modal.stories.svelte +101 -0
  100. package/src/stories/Progress.stories.svelte +65 -0
  101. package/src/stories/Select.stories.svelte +158 -0
  102. package/src/stories/Tabs.stories.svelte +103 -0
  103. package/src/stories/Textarea.stories.svelte +45 -0
  104. package/src/stories/Toggle.stories.svelte +74 -0
  105. package/src/stories/Tooltip.stories.svelte +77 -0
  106. package/src/stories/Welcome.mdx +241 -0
  107. package/src/stories/assets/runwisp_c.svg +16 -0
  108. 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
+ }
@@ -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";