@happyvertical/smrt-jobs 0.30.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/AGENTS.md +71 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +151 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/background-policy.d.ts +121 -0
- package/dist/background-policy.d.ts.map +1 -0
- package/dist/chunks/runner-DV8FBO0y.js +1642 -0
- package/dist/chunks/runner-DV8FBO0y.js.map +1 -0
- package/dist/chunks/worker-liveness-DOTjoIjr.js +65 -0
- package/dist/chunks/worker-liveness-DOTjoIjr.js.map +1 -0
- package/dist/error-redaction.d.ts +48 -0
- package/dist/error-redaction.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +926 -0
- package/dist/index.js.map +1 -0
- package/dist/job-builder.d.ts +94 -0
- package/dist/job-builder.d.ts.map +1 -0
- package/dist/job-handle.d.ts +71 -0
- package/dist/job-handle.d.ts.map +1 -0
- package/dist/logger-extension.d.ts +58 -0
- package/dist/logger-extension.d.ts.map +1 -0
- package/dist/manifest.json +1327 -0
- package/dist/object-extension.d.ts +68 -0
- package/dist/object-extension.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +179 -0
- package/dist/playground.js.map +1 -0
- package/dist/runner.d.ts +189 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +15 -0
- package/dist/runner.js.map +1 -0
- package/dist/schedule-runner.d.ts +151 -0
- package/dist/schedule-runner.d.ts.map +1 -0
- package/dist/smrt-job-event.d.ts +54 -0
- package/dist/smrt-job-event.d.ts.map +1 -0
- package/dist/smrt-job.d.ts +215 -0
- package/dist/smrt-job.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +508 -0
- package/dist/smrt-worker.d.ts +72 -0
- package/dist/smrt-worker.d.ts.map +1 -0
- package/dist/stale-recovery.d.ts +34 -0
- package/dist/stale-recovery.d.ts.map +1 -0
- package/dist/svelte/components/JobActions.svelte +103 -0
- package/dist/svelte/components/JobActions.svelte.d.ts +23 -0
- package/dist/svelte/components/JobActions.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobDashboard.svelte +199 -0
- package/dist/svelte/components/JobDashboard.svelte.d.ts +27 -0
- package/dist/svelte/components/JobDashboard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobDetail.svelte +256 -0
- package/dist/svelte/components/JobDetail.svelte.d.ts +17 -0
- package/dist/svelte/components/JobDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobList.svelte +360 -0
- package/dist/svelte/components/JobList.svelte.d.ts +28 -0
- package/dist/svelte/components/JobList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobStats.svelte +242 -0
- package/dist/svelte/components/JobStats.svelte.d.ts +15 -0
- package/dist/svelte/components/JobStats.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobStatusBadge.svelte +23 -0
- package/dist/svelte/components/JobStatusBadge.svelte.d.ts +9 -0
- package/dist/svelte/components/JobStatusBadge.svelte.d.ts.map +1 -0
- package/dist/svelte/components/types.d.ts +9 -0
- package/dist/svelte/components/types.d.ts.map +1 -0
- package/dist/svelte/components/types.js +8 -0
- package/dist/svelte/i18n.d.ts +22 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +22 -0
- package/dist/svelte/index.d.ts +25 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +28 -0
- package/dist/svelte/playground.d.ts +329 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +174 -0
- package/dist/svelte/types.d.ts +191 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +87 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +69 -0
- package/dist/ui.js.map +1 -0
- package/dist/worker-liveness-thread.d.ts +2 -0
- package/dist/worker-liveness-thread.d.ts.map +1 -0
- package/dist/worker-liveness-thread.js +66 -0
- package/dist/worker-liveness-thread.js.map +1 -0
- package/dist/worker-liveness-ticker.d.ts +30 -0
- package/dist/worker-liveness-ticker.d.ts.map +1 -0
- package/dist/worker-liveness.d.ts +71 -0
- package/dist/worker-liveness.d.ts.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* JobList - Display a filterable, sortable list of background jobs
|
|
4
|
+
*/
|
|
5
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
import { M } from '../i18n.js';
|
|
8
|
+
import JobActions from './JobActions.svelte';
|
|
9
|
+
import JobStatusBadge from './JobStatusBadge.svelte';
|
|
10
|
+
import type { JobData, JobFilter, JobSort } from './types.js';
|
|
11
|
+
import { formatRelativeTime, getPriorityLabel } from './types.js';
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** Jobs to display */
|
|
17
|
+
jobs: JobData[];
|
|
18
|
+
/** Loading state */
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
/** Selectable rows */
|
|
21
|
+
selectable?: boolean;
|
|
22
|
+
/** Selected job IDs */
|
|
23
|
+
selected?: Set<string>;
|
|
24
|
+
/** Show actions column */
|
|
25
|
+
showActions?: boolean;
|
|
26
|
+
/** Callback when selection changes */
|
|
27
|
+
onSelectionChange?: (selected: Set<string>) => void;
|
|
28
|
+
/** Callback when job is clicked */
|
|
29
|
+
onJobClick?: (job: JobData) => void;
|
|
30
|
+
/** Callback when retry is clicked */
|
|
31
|
+
onRetry?: (job: JobData) => void;
|
|
32
|
+
/** Callback when cancel is clicked */
|
|
33
|
+
onCancel?: (job: JobData) => void;
|
|
34
|
+
/** Empty state snippet */
|
|
35
|
+
empty?: Snippet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
jobs = [],
|
|
40
|
+
loading = false,
|
|
41
|
+
selectable = false,
|
|
42
|
+
selected = $bindable(new Set<string>()),
|
|
43
|
+
showActions = true,
|
|
44
|
+
onSelectionChange,
|
|
45
|
+
onJobClick,
|
|
46
|
+
onRetry,
|
|
47
|
+
onCancel,
|
|
48
|
+
empty,
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
// Handle row click
|
|
52
|
+
function handleRowClick(job: JobData) {
|
|
53
|
+
onJobClick?.(job);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle row selection
|
|
57
|
+
function handleRowSelect(jobId: string, event: Event) {
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
const newSelected = new Set(selected);
|
|
60
|
+
if (newSelected.has(jobId)) {
|
|
61
|
+
newSelected.delete(jobId);
|
|
62
|
+
} else {
|
|
63
|
+
newSelected.add(jobId);
|
|
64
|
+
}
|
|
65
|
+
selected = newSelected;
|
|
66
|
+
onSelectionChange?.(newSelected);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle select all
|
|
70
|
+
function handleSelectAll() {
|
|
71
|
+
if (allSelected) {
|
|
72
|
+
selected = new Set();
|
|
73
|
+
} else {
|
|
74
|
+
selected = new Set(jobs.map((job) => job.id));
|
|
75
|
+
}
|
|
76
|
+
onSelectionChange?.(selected);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Selection state
|
|
80
|
+
const allSelected = $derived(
|
|
81
|
+
jobs.length > 0 && jobs.every((job) => selected.has(job.id)),
|
|
82
|
+
);
|
|
83
|
+
const someSelected = $derived(
|
|
84
|
+
jobs.some((job) => selected.has(job.id)) && !allSelected,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Action to set indeterminate state
|
|
88
|
+
function setIndeterminate(node: HTMLInputElement, value: boolean) {
|
|
89
|
+
node.indeterminate = value;
|
|
90
|
+
return {
|
|
91
|
+
update(newValue: boolean) {
|
|
92
|
+
node.indeterminate = newValue;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<div class="job-list-container">
|
|
99
|
+
<table class="job-list" class:loading>
|
|
100
|
+
<thead class="job-list__head">
|
|
101
|
+
<tr>
|
|
102
|
+
{#if selectable}
|
|
103
|
+
<th class="job-list__cell job-list__cell--checkbox">
|
|
104
|
+
<input
|
|
105
|
+
type="checkbox"
|
|
106
|
+
checked={allSelected}
|
|
107
|
+
use:setIndeterminate={someSelected}
|
|
108
|
+
onchange={handleSelectAll}
|
|
109
|
+
aria-label={t(M['jobs.job_list.select_all_jobs'])}
|
|
110
|
+
/>
|
|
111
|
+
</th>
|
|
112
|
+
{/if}
|
|
113
|
+
<th class="job-list__cell">Status</th>
|
|
114
|
+
<th class="job-list__cell">Queue</th>
|
|
115
|
+
<th class="job-list__cell">Object</th>
|
|
116
|
+
<th class="job-list__cell">Method</th>
|
|
117
|
+
<th class="job-list__cell">Priority</th>
|
|
118
|
+
<th class="job-list__cell">Attempts</th>
|
|
119
|
+
<th class="job-list__cell">Created</th>
|
|
120
|
+
<th class="job-list__cell">{t(M['jobs.job_list.run_at'])}</th>
|
|
121
|
+
{#if showActions}
|
|
122
|
+
<th class="job-list__cell">Actions</th>
|
|
123
|
+
{/if}
|
|
124
|
+
</tr>
|
|
125
|
+
</thead>
|
|
126
|
+
|
|
127
|
+
<tbody class="job-list__body">
|
|
128
|
+
{#if loading}
|
|
129
|
+
<tr class="job-list__row job-list__row--loading">
|
|
130
|
+
<td class="job-list__cell job-list__cell--loading" colspan="10">
|
|
131
|
+
<div class="job-list__loading">
|
|
132
|
+
<span class="job-list__spinner"></span>
|
|
133
|
+
<span>{t(M['jobs.job_list.loading_jobs'])}</span>
|
|
134
|
+
</div>
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
{:else if jobs.length === 0}
|
|
138
|
+
<tr class="job-list__row job-list__row--empty">
|
|
139
|
+
<td class="job-list__cell job-list__cell--empty" colspan="10">
|
|
140
|
+
{#if empty}
|
|
141
|
+
{@render empty()}
|
|
142
|
+
{:else}
|
|
143
|
+
<div class="job-list__empty">
|
|
144
|
+
<span>{t(M['jobs.job_list.no_jobs_found'])}</span>
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
</td>
|
|
148
|
+
</tr>
|
|
149
|
+
{:else}
|
|
150
|
+
{#each jobs as job (job.id)}
|
|
151
|
+
{@const isSelected = selected.has(job.id)}
|
|
152
|
+
<tr
|
|
153
|
+
class="job-list__row"
|
|
154
|
+
class:job-list__row--selected={isSelected}
|
|
155
|
+
onclick={() => handleRowClick(job)}
|
|
156
|
+
role={onJobClick ? 'button' : undefined}
|
|
157
|
+
tabindex={onJobClick ? 0 : undefined}
|
|
158
|
+
>
|
|
159
|
+
{#if selectable}
|
|
160
|
+
<td class="job-list__cell job-list__cell--checkbox">
|
|
161
|
+
<input
|
|
162
|
+
type="checkbox"
|
|
163
|
+
checked={isSelected}
|
|
164
|
+
onchange={(e) => handleRowSelect(job.id, e)}
|
|
165
|
+
aria-label={t(M['jobs.job_list.select_job'])}
|
|
166
|
+
/>
|
|
167
|
+
</td>
|
|
168
|
+
{/if}
|
|
169
|
+
<td class="job-list__cell">
|
|
170
|
+
<JobStatusBadge status={job.status} />
|
|
171
|
+
</td>
|
|
172
|
+
<td class="job-list__cell job-list__cell--queue">
|
|
173
|
+
{job.queue}
|
|
174
|
+
</td>
|
|
175
|
+
<td class="job-list__cell job-list__cell--object">
|
|
176
|
+
<span class="object-type">{job.objectType}</span>
|
|
177
|
+
{#if job.objectId}
|
|
178
|
+
<span class="object-id">#{job.objectId.slice(0, 8)}</span>
|
|
179
|
+
{/if}
|
|
180
|
+
</td>
|
|
181
|
+
<td class="job-list__cell job-list__cell--method">
|
|
182
|
+
<code>{job.method}</code>
|
|
183
|
+
</td>
|
|
184
|
+
<td class="job-list__cell job-list__cell--priority">
|
|
185
|
+
{getPriorityLabel(job.priority)}
|
|
186
|
+
</td>
|
|
187
|
+
<td class="job-list__cell job-list__cell--attempts">
|
|
188
|
+
{job.attempts}/{job.maxAttempts}
|
|
189
|
+
</td>
|
|
190
|
+
<td class="job-list__cell job-list__cell--date">
|
|
191
|
+
{formatRelativeTime(job.createdAt)}
|
|
192
|
+
</td>
|
|
193
|
+
<td class="job-list__cell job-list__cell--date">
|
|
194
|
+
{formatRelativeTime(job.runAt)}
|
|
195
|
+
</td>
|
|
196
|
+
{#if showActions}
|
|
197
|
+
<td class="job-list__cell job-list__cell--actions">
|
|
198
|
+
<JobActions
|
|
199
|
+
{job}
|
|
200
|
+
compact
|
|
201
|
+
showDelete={false}
|
|
202
|
+
{onRetry}
|
|
203
|
+
{onCancel}
|
|
204
|
+
/>
|
|
205
|
+
</td>
|
|
206
|
+
{/if}
|
|
207
|
+
</tr>
|
|
208
|
+
{/each}
|
|
209
|
+
{/if}
|
|
210
|
+
</tbody>
|
|
211
|
+
</table>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<style>
|
|
215
|
+
.job-list-container {
|
|
216
|
+
width: 100%;
|
|
217
|
+
overflow-x: auto;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.job-list {
|
|
221
|
+
width: 100%;
|
|
222
|
+
border-collapse: collapse;
|
|
223
|
+
border-spacing: 0;
|
|
224
|
+
font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
|
|
225
|
+
background: var(--smrt-color-surface, #ffffff);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.job-list.loading {
|
|
229
|
+
opacity: 0.7;
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.job-list__head {
|
|
234
|
+
background: var(--smrt-color-surface-container, #f3f4f6);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.job-list__head th {
|
|
238
|
+
padding: var(--smrt-spacing-sm, 0.5rem) var(--smrt-spacing-md, 1rem);
|
|
239
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
240
|
+
text-align: left;
|
|
241
|
+
white-space: nowrap;
|
|
242
|
+
border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.job-list__body tr {
|
|
246
|
+
border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
247
|
+
transition: background-color var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.job-list__body tr:hover:not(.job-list__row--loading):not(.job-list__row--empty) {
|
|
251
|
+
background: var(--smrt-color-surface-container-low, #f9fafb);
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.job-list__row--selected {
|
|
256
|
+
background: var(--smrt-color-primary-container, #d6e3ff) !important;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@media (prefers-reduced-motion: reduce) {
|
|
260
|
+
.job-list__body tr {
|
|
261
|
+
transition: none;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.job-list__spinner {
|
|
265
|
+
animation: none;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.job-list__cell {
|
|
270
|
+
padding: var(--smrt-spacing-sm, 0.5rem) var(--smrt-spacing-md, 1rem);
|
|
271
|
+
vertical-align: middle;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.job-list__cell--checkbox {
|
|
275
|
+
width: 40px;
|
|
276
|
+
text-align: center;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.job-list__cell--checkbox input {
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.job-list__cell--queue {
|
|
284
|
+
font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
|
|
285
|
+
font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.job-list__cell--object {
|
|
289
|
+
max-width: 200px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.object-type {
|
|
293
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.object-id {
|
|
297
|
+
margin-left: var(--smrt-spacing-xs, 0.25rem);
|
|
298
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
299
|
+
font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
|
|
300
|
+
font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.job-list__cell--method code {
|
|
304
|
+
padding: 0.125rem 0.375rem;
|
|
305
|
+
background: var(--smrt-color-surface-container, #f3f4f6);
|
|
306
|
+
border-radius: var(--smrt-radius-small, 0.25rem);
|
|
307
|
+
font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.job-list__cell--priority {
|
|
311
|
+
text-align: center;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.job-list__cell--attempts {
|
|
315
|
+
text-align: center;
|
|
316
|
+
font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.job-list__cell--date {
|
|
320
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
321
|
+
white-space: nowrap;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.job-list__cell--actions {
|
|
325
|
+
white-space: nowrap;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.job-list__cell--loading,
|
|
329
|
+
.job-list__cell--empty {
|
|
330
|
+
padding: var(--smrt-spacing-xl, 2rem);
|
|
331
|
+
text-align: center;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.job-list__loading {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
gap: var(--smrt-spacing-sm, 0.5rem);
|
|
339
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.job-list__spinner {
|
|
343
|
+
width: 20px;
|
|
344
|
+
height: 20px;
|
|
345
|
+
border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
346
|
+
border-top-color: var(--smrt-color-primary, #005ac1);
|
|
347
|
+
border-radius: var(--smrt-radius-full, 9999px);
|
|
348
|
+
animation: spin 0.8s linear infinite;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@keyframes spin {
|
|
352
|
+
to {
|
|
353
|
+
transform: rotate(360deg);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.job-list__empty {
|
|
358
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
359
|
+
}
|
|
360
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { JobData } from './types.js';
|
|
3
|
+
export interface Props {
|
|
4
|
+
/** Jobs to display */
|
|
5
|
+
jobs: JobData[];
|
|
6
|
+
/** Loading state */
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
/** Selectable rows */
|
|
9
|
+
selectable?: boolean;
|
|
10
|
+
/** Selected job IDs */
|
|
11
|
+
selected?: Set<string>;
|
|
12
|
+
/** Show actions column */
|
|
13
|
+
showActions?: boolean;
|
|
14
|
+
/** Callback when selection changes */
|
|
15
|
+
onSelectionChange?: (selected: Set<string>) => void;
|
|
16
|
+
/** Callback when job is clicked */
|
|
17
|
+
onJobClick?: (job: JobData) => void;
|
|
18
|
+
/** Callback when retry is clicked */
|
|
19
|
+
onRetry?: (job: JobData) => void;
|
|
20
|
+
/** Callback when cancel is clicked */
|
|
21
|
+
onCancel?: (job: JobData) => void;
|
|
22
|
+
/** Empty state snippet */
|
|
23
|
+
empty?: Snippet;
|
|
24
|
+
}
|
|
25
|
+
declare const JobList: import("svelte").Component<Props, {}, "selected">;
|
|
26
|
+
type JobList = ReturnType<typeof JobList>;
|
|
27
|
+
export default JobList;
|
|
28
|
+
//# sourceMappingURL=JobList.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JobList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/JobList.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,OAAO,KAAK,EAAE,OAAO,EAAsB,MAAM,YAAY,CAAC;AAI9D,MAAM,WAAW,KAAK;IACpB,sBAAsB;IACtB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sBAAsB;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sCAAsC;IACtC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACpD,mCAAmC;IACnC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,qCAAqC;IACrC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAClC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA2KD,QAAA,MAAM,OAAO,mDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* JobStats - Display job statistics overview
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
7
|
+
import { Card } from '@happyvertical/smrt-ui/ui';
|
|
8
|
+
import { M } from '../i18n.js';
|
|
9
|
+
import type { JobStats, QueueStats } from './types.js';
|
|
10
|
+
import { formatDuration } from './types.js';
|
|
11
|
+
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
|
|
14
|
+
export interface Props {
|
|
15
|
+
/** Statistics data */
|
|
16
|
+
stats: JobStats;
|
|
17
|
+
/** Queue breakdown */
|
|
18
|
+
queues?: QueueStats[];
|
|
19
|
+
/** Loading state */
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
/** Compact mode */
|
|
22
|
+
compact?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let { stats, queues = [], loading = false, compact = false }: Props = $props();
|
|
26
|
+
|
|
27
|
+
const successRateFormatted = $derived(
|
|
28
|
+
stats.successRate !== null ? `${(stats.successRate * 100).toFixed(1)}%` : '-',
|
|
29
|
+
);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class="job-stats" class:compact class:loading>
|
|
33
|
+
<div class="job-stats__grid">
|
|
34
|
+
<!-- Total Jobs -->
|
|
35
|
+
<Card padding="sm">
|
|
36
|
+
<div class="stat-card">
|
|
37
|
+
<div class="stat-card__value">{stats.total.toLocaleString()}</div>
|
|
38
|
+
<div class="stat-card__label">{t(M['jobs.job_stats.total_jobs'])}</div>
|
|
39
|
+
</div>
|
|
40
|
+
</Card>
|
|
41
|
+
|
|
42
|
+
<!-- Pending -->
|
|
43
|
+
<Card padding="sm">
|
|
44
|
+
<div class="stat-card stat-card--pending">
|
|
45
|
+
<div class="stat-card__value">{stats.pending.toLocaleString()}</div>
|
|
46
|
+
<div class="stat-card__label">Pending</div>
|
|
47
|
+
</div>
|
|
48
|
+
</Card>
|
|
49
|
+
|
|
50
|
+
<!-- Running -->
|
|
51
|
+
<Card padding="sm">
|
|
52
|
+
<div class="stat-card stat-card--running">
|
|
53
|
+
<div class="stat-card__value">{stats.running.toLocaleString()}</div>
|
|
54
|
+
<div class="stat-card__label">Running</div>
|
|
55
|
+
</div>
|
|
56
|
+
</Card>
|
|
57
|
+
|
|
58
|
+
<!-- Completed -->
|
|
59
|
+
<Card padding="sm">
|
|
60
|
+
<div class="stat-card stat-card--completed">
|
|
61
|
+
<div class="stat-card__value">{stats.completed.toLocaleString()}</div>
|
|
62
|
+
<div class="stat-card__label">Completed</div>
|
|
63
|
+
</div>
|
|
64
|
+
</Card>
|
|
65
|
+
|
|
66
|
+
<!-- Failed -->
|
|
67
|
+
<Card padding="sm">
|
|
68
|
+
<div class="stat-card stat-card--failed">
|
|
69
|
+
<div class="stat-card__value">{stats.failed.toLocaleString()}</div>
|
|
70
|
+
<div class="stat-card__label">Failed</div>
|
|
71
|
+
</div>
|
|
72
|
+
</Card>
|
|
73
|
+
|
|
74
|
+
<!-- Success Rate -->
|
|
75
|
+
<Card padding="sm">
|
|
76
|
+
<div class="stat-card">
|
|
77
|
+
<div class="stat-card__value">{successRateFormatted}</div>
|
|
78
|
+
<div class="stat-card__label">{t(M['jobs.job_stats.success_rate'])}</div>
|
|
79
|
+
</div>
|
|
80
|
+
</Card>
|
|
81
|
+
|
|
82
|
+
<!-- Avg Duration -->
|
|
83
|
+
<Card padding="sm">
|
|
84
|
+
<div class="stat-card">
|
|
85
|
+
<div class="stat-card__value">{formatDuration(stats.avgDuration)}</div>
|
|
86
|
+
<div class="stat-card__label">{t(M['jobs.job_stats.avg_duration'])}</div>
|
|
87
|
+
</div>
|
|
88
|
+
</Card>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{#if queues.length > 0 && !compact}
|
|
92
|
+
<div class="job-stats__queues">
|
|
93
|
+
<h3>Queues</h3>
|
|
94
|
+
<div class="queue-list">
|
|
95
|
+
{#each queues as queue (queue.name)}
|
|
96
|
+
<Card padding="sm">
|
|
97
|
+
<div class="queue-card">
|
|
98
|
+
<div class="queue-card__header">
|
|
99
|
+
<span class="queue-card__name">{queue.name}</span>
|
|
100
|
+
<span class="queue-card__running">{queue.running} running</span>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="queue-card__stats">
|
|
103
|
+
<div class="queue-stat">
|
|
104
|
+
<span class="queue-stat__value">{queue.pending}</span>
|
|
105
|
+
<span class="queue-stat__label">pending</span>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="queue-stat">
|
|
108
|
+
<span class="queue-stat__value">{queue.completed24h}</span>
|
|
109
|
+
<span class="queue-stat__label">completed/24h</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="queue-stat">
|
|
112
|
+
<span class="queue-stat__value">{queue.failed24h}</span>
|
|
113
|
+
<span class="queue-stat__label">failed/24h</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="queue-stat">
|
|
116
|
+
<span class="queue-stat__value"
|
|
117
|
+
>{formatDuration(queue.avgDuration)}</span
|
|
118
|
+
>
|
|
119
|
+
<span class="queue-stat__label">avg</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</Card>
|
|
124
|
+
{/each}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<style>
|
|
131
|
+
.job-stats {
|
|
132
|
+
width: 100%;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.job-stats.loading {
|
|
136
|
+
opacity: 0.7;
|
|
137
|
+
pointer-events: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.job-stats__grid {
|
|
141
|
+
display: grid;
|
|
142
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
143
|
+
gap: var(--smrt-spacing-md, 1rem);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.compact .job-stats__grid {
|
|
147
|
+
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
148
|
+
gap: var(--smrt-spacing-sm, 0.5rem);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.stat-card {
|
|
152
|
+
text-align: center;
|
|
153
|
+
padding: var(--smrt-spacing-sm, 0.5rem);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.stat-card__value {
|
|
157
|
+
font: var(--smrt-typography-headline-small-font, 700 1.5rem / 1.2 sans-serif);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.compact .stat-card__value {
|
|
161
|
+
font: var(--smrt-typography-title-large-font, 1.25rem / 1.25 sans-serif);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.stat-card__label {
|
|
165
|
+
font: var(--smrt-typography-body-small-font, 0.875rem / 1.25 sans-serif);
|
|
166
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
167
|
+
margin-top: var(--smrt-spacing-xs, 0.25rem);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.stat-card--pending .stat-card__value {
|
|
171
|
+
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.stat-card--running .stat-card__value {
|
|
175
|
+
color: var(--smrt-color-primary, #005ac1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.stat-card--completed .stat-card__value {
|
|
179
|
+
color: var(--smrt-color-tertiary, #006c4f);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.stat-card--failed .stat-card__value {
|
|
183
|
+
color: var(--smrt-color-error, #ba1a1a);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.job-stats__queues {
|
|
187
|
+
margin-top: var(--smrt-spacing-lg, 1.5rem);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.job-stats__queues h3 {
|
|
191
|
+
margin: 0 0 var(--smrt-spacing-md, 1rem) 0;
|
|
192
|
+
font: var(--smrt-typography-title-medium-font, 600 1rem / 1.5 sans-serif);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.queue-list {
|
|
196
|
+
display: grid;
|
|
197
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
198
|
+
gap: var(--smrt-spacing-md, 1rem);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.queue-card {
|
|
202
|
+
padding: var(--smrt-spacing-xs, 0.25rem);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.queue-card__header {
|
|
206
|
+
display: flex;
|
|
207
|
+
justify-content: space-between;
|
|
208
|
+
align-items: center;
|
|
209
|
+
margin-bottom: var(--spacing-sm, 0.5rem);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.queue-card__name {
|
|
213
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
214
|
+
font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.queue-card__running {
|
|
218
|
+
font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
|
|
219
|
+
color: var(--smrt-color-primary, #005ac1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.queue-card__stats {
|
|
223
|
+
display: flex;
|
|
224
|
+
gap: var(--smrt-spacing-md, 1rem);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.queue-stat {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
align-items: center;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.queue-stat__value {
|
|
234
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
235
|
+
font: var(--smrt-typography-title-medium-font, 1rem / 1.5 sans-serif);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.queue-stat__label {
|
|
239
|
+
font: var(--smrt-typography-body-small-font, 0.75rem / 1.25 sans-serif);
|
|
240
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
241
|
+
}
|
|
242
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JobStats, QueueStats } from './types.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** Statistics data */
|
|
4
|
+
stats: JobStats;
|
|
5
|
+
/** Queue breakdown */
|
|
6
|
+
queues?: QueueStats[];
|
|
7
|
+
/** Loading state */
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
/** Compact mode */
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const JobStats: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type JobStats = ReturnType<typeof JobStats>;
|
|
14
|
+
export default JobStats;
|
|
15
|
+
//# sourceMappingURL=JobStats.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JobStats.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/JobStats.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,sBAAsB;IACtB,KAAK,EAAE,QAAQ,CAAC;IAChB,sBAAsB;IACtB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwHD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* JobStatusBadge - Display job status with appropriate styling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Badge } from '@happyvertical/smrt-ui/ui';
|
|
7
|
+
import type { JobStatus } from './types.js';
|
|
8
|
+
import { getStatusVariant } from './types.js';
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
status: JobStatus;
|
|
12
|
+
size?: 'sm' | 'md';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { status, size = 'sm' }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const variant = $derived(getStatusVariant(status));
|
|
18
|
+
const label = $derived(status.charAt(0).toUpperCase() + status.slice(1));
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Badge {variant} {size}>
|
|
22
|
+
{label}
|
|
23
|
+
</Badge>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JobStatus } from './types.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
status: JobStatus;
|
|
4
|
+
size?: 'sm' | 'md';
|
|
5
|
+
}
|
|
6
|
+
declare const JobStatusBadge: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type JobStatusBadge = ReturnType<typeof JobStatusBadge>;
|
|
8
|
+
export default JobStatusBadge;
|
|
9
|
+
//# sourceMappingURL=JobStatusBadge.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JobStatusBadge.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/JobStatusBadge.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AAqBD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|