@voyantjs/workflows-react 0.106.0 → 0.107.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/README.md +43 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1 -0
- package/dist/components/common.d.ts +30 -0
- package/dist/components/common.d.ts.map +1 -0
- package/dist/components/common.js +108 -0
- package/dist/components/workflow-run-actions.d.ts +7 -0
- package/dist/components/workflow-run-actions.d.ts.map +1 -0
- package/dist/components/workflow-run-actions.js +72 -0
- package/dist/components/workflow-run-detail-page.d.ts +10 -0
- package/dist/components/workflow-run-detail-page.d.ts.map +1 -0
- package/dist/components/workflow-run-detail-page.js +96 -0
- package/dist/components/workflow-runs-filters.d.ts +32 -0
- package/dist/components/workflow-runs-filters.d.ts.map +1 -0
- package/dist/components/workflow-runs-filters.js +97 -0
- package/dist/components/workflow-runs-page.d.ts +12 -0
- package/dist/components/workflow-runs-page.d.ts.map +1 -0
- package/dist/components/workflow-runs-page.js +132 -0
- package/dist/components/workflow-schedules-page.d.ts +21 -0
- package/dist/components/workflow-schedules-page.d.ts.map +1 -0
- package/dist/components/workflow-schedules-page.js +144 -0
- package/dist/i18n/en.d.ts +4 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +135 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +125 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +26 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +4 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +137 -0
- package/dist/schedules-client.d.ts +2 -0
- package/dist/schedules-client.d.ts.map +1 -0
- package/dist/schedules-client.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/ui.d.ts +9 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +6 -0
- package/package.json +69 -21
- package/src/client.ts +4 -0
- package/src/components/common.tsx +182 -0
- package/src/components/workflow-run-actions.tsx +160 -0
- package/src/components/workflow-run-detail-page.tsx +393 -0
- package/src/components/workflow-runs-filters.tsx +349 -0
- package/src/components/workflow-runs-page.tsx +357 -0
- package/src/components/workflow-schedules-page.tsx +398 -0
- package/src/i18n/en.ts +142 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/messages.ts +125 -0
- package/src/i18n/provider.tsx +96 -0
- package/src/i18n/ro.ts +144 -0
- package/src/schedules-client.ts +8 -0
- package/src/styles.css +11 -0
- package/src/types.ts +14 -0
- package/src/ui.ts +63 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@voyantjs/ui/components/badge"
|
|
4
|
+
import { Button } from "@voyantjs/ui/components/button"
|
|
5
|
+
import { Card, CardContent } from "@voyantjs/ui/components/card"
|
|
6
|
+
import { AlertTriangle, CalendarClock, RefreshCw } from "lucide-react"
|
|
7
|
+
import { useCallback, useEffect, useMemo, useState } from "react"
|
|
8
|
+
|
|
9
|
+
import { useWorkflowRunsUiMessagesOrDefault, type WorkflowRunsUiMessages } from "../i18n/index.js"
|
|
10
|
+
import type {
|
|
11
|
+
ListWorkflowSchedulesResponse,
|
|
12
|
+
WorkflowScheduleDecl,
|
|
13
|
+
WorkflowScheduleSummary,
|
|
14
|
+
WorkflowSchedulesApi,
|
|
15
|
+
} from "../schedules-client.js"
|
|
16
|
+
import type { WorkflowRun, WorkflowRunStatus, WorkflowRunsApi } from "../types.js"
|
|
17
|
+
import { formatRelative, StatusIcon } from "./common.js"
|
|
18
|
+
|
|
19
|
+
type SchedulesMessages = WorkflowRunsUiMessages["schedules"]
|
|
20
|
+
|
|
21
|
+
export interface WorkflowSchedulesPageProps {
|
|
22
|
+
/** Schedules API — backed by `/api/schedules/:env`. */
|
|
23
|
+
schedulesApi: WorkflowSchedulesApi
|
|
24
|
+
/** Optional runs API — when provided, the page joins each row with the most recent matching run. */
|
|
25
|
+
runsApi?: WorkflowRunsApi
|
|
26
|
+
/**
|
|
27
|
+
* Optional trigger callback. When provided, each row renders a
|
|
28
|
+
* "Trigger now" button that calls this with the workflow id + the
|
|
29
|
+
* schedule's recorded `input` payload (if any).
|
|
30
|
+
*/
|
|
31
|
+
onTriggerNow?: (workflowId: string, input: unknown) => Promise<void>
|
|
32
|
+
/** Manifest environment to inspect. Defaults to "production". */
|
|
33
|
+
environment?: "production" | "preview" | "development"
|
|
34
|
+
/** Auto-refresh interval (ms). Defaults to 30s. Pass 0 to disable. */
|
|
35
|
+
pollIntervalMs?: number
|
|
36
|
+
className?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function WorkflowSchedulesPage({
|
|
40
|
+
schedulesApi,
|
|
41
|
+
runsApi,
|
|
42
|
+
onTriggerNow,
|
|
43
|
+
environment = "production",
|
|
44
|
+
pollIntervalMs = 30_000,
|
|
45
|
+
className,
|
|
46
|
+
}: WorkflowSchedulesPageProps) {
|
|
47
|
+
const rootMessages = useWorkflowRunsUiMessagesOrDefault()
|
|
48
|
+
const messages = rootMessages.schedules
|
|
49
|
+
const [response, setResponse] = useState<ListWorkflowSchedulesResponse | null>(null)
|
|
50
|
+
const [lastRuns, setLastRuns] = useState<Record<string, WorkflowRun | null>>({})
|
|
51
|
+
const [loading, setLoading] = useState(false)
|
|
52
|
+
const [error, setError] = useState<string | null>(null)
|
|
53
|
+
const [triggering, setTriggering] = useState<Record<string, boolean>>({})
|
|
54
|
+
const [triggerNotice, setTriggerNotice] = useState<{
|
|
55
|
+
kind: "success" | "error"
|
|
56
|
+
text: string
|
|
57
|
+
} | null>(null)
|
|
58
|
+
|
|
59
|
+
const refresh = useCallback(async () => {
|
|
60
|
+
setLoading(true)
|
|
61
|
+
try {
|
|
62
|
+
const next = await schedulesApi.listSchedules(environment)
|
|
63
|
+
setResponse(next)
|
|
64
|
+
setError(null)
|
|
65
|
+
if (runsApi) {
|
|
66
|
+
const uniqueIds = Array.from(new Set(next.data.map((entry) => entry.workflowId)))
|
|
67
|
+
const results = await Promise.all(
|
|
68
|
+
uniqueIds.map(async (workflowId) => {
|
|
69
|
+
try {
|
|
70
|
+
const runs = await runsApi.listRuns({ workflowName: workflowId, limit: 1 })
|
|
71
|
+
return [workflowId, runs.data[0] ?? null] as const
|
|
72
|
+
} catch {
|
|
73
|
+
return [workflowId, null] as const
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
setLastRuns(Object.fromEntries(results))
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
setError(err instanceof Error ? err.message : messages.loadError)
|
|
81
|
+
} finally {
|
|
82
|
+
setLoading(false)
|
|
83
|
+
}
|
|
84
|
+
}, [environment, messages.loadError, runsApi, schedulesApi])
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
void refresh()
|
|
88
|
+
if (!pollIntervalMs) return
|
|
89
|
+
const interval = setInterval(() => void refresh(), pollIntervalMs)
|
|
90
|
+
return () => clearInterval(interval)
|
|
91
|
+
}, [pollIntervalMs, refresh])
|
|
92
|
+
|
|
93
|
+
const showEnvFlag = response?.schedulesEnabledByEnv !== undefined
|
|
94
|
+
const envFlagOn = response?.schedulesEnabledByEnv === true
|
|
95
|
+
|
|
96
|
+
const triggerRow = useCallback(
|
|
97
|
+
async (entry: WorkflowScheduleSummary) => {
|
|
98
|
+
if (!onTriggerNow) return
|
|
99
|
+
setTriggering((prev) => ({ ...prev, [entry.scheduleId]: true }))
|
|
100
|
+
setTriggerNotice(null)
|
|
101
|
+
try {
|
|
102
|
+
await onTriggerNow(entry.workflowId, entry.schedule.input)
|
|
103
|
+
setTriggerNotice({ kind: "success", text: messages.triggerSuccess })
|
|
104
|
+
} catch (err) {
|
|
105
|
+
setTriggerNotice({
|
|
106
|
+
kind: "error",
|
|
107
|
+
text: err instanceof Error ? err.message : messages.triggerFailed,
|
|
108
|
+
})
|
|
109
|
+
} finally {
|
|
110
|
+
setTriggering((prev) => ({ ...prev, [entry.scheduleId]: false }))
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[messages.triggerFailed, messages.triggerSuccess, onTriggerNow],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const rows = useMemo(() => response?.data ?? [], [response])
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className={`flex min-h-screen flex-col bg-background ${className ?? ""}`}>
|
|
120
|
+
<header className="sticky top-0 z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
121
|
+
<div className="container mx-auto flex flex-wrap items-center gap-3 px-4 py-3">
|
|
122
|
+
<CalendarClock className="h-5 w-5" />
|
|
123
|
+
<div className="min-w-0">
|
|
124
|
+
<h1 className="font-semibold text-base">{messages.title}</h1>
|
|
125
|
+
<p className="text-muted-foreground text-xs">{messages.subtitle}</p>
|
|
126
|
+
</div>
|
|
127
|
+
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
|
|
128
|
+
<span>
|
|
129
|
+
{messages.environmentLabel}: <span className="font-mono">{environment}</span>
|
|
130
|
+
</span>
|
|
131
|
+
{response?.versionId ? (
|
|
132
|
+
<span>
|
|
133
|
+
{messages.versionLabel}:{" "}
|
|
134
|
+
<span className="font-mono">{response.versionId.slice(0, 12)}</span>
|
|
135
|
+
</span>
|
|
136
|
+
) : null}
|
|
137
|
+
<Button
|
|
138
|
+
type="button"
|
|
139
|
+
variant="outline"
|
|
140
|
+
size="sm"
|
|
141
|
+
onClick={() => void refresh()}
|
|
142
|
+
disabled={loading}
|
|
143
|
+
>
|
|
144
|
+
<RefreshCw
|
|
145
|
+
className={`h-3.5 w-3.5 ${loading ? "animate-spin" : ""}`}
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
/>
|
|
148
|
+
{messages.refresh}
|
|
149
|
+
</Button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</header>
|
|
153
|
+
|
|
154
|
+
<main className="container mx-auto flex flex-1 flex-col gap-4 px-4 py-6">
|
|
155
|
+
{showEnvFlag && !envFlagOn ? (
|
|
156
|
+
<Card className="border-amber-500/40 bg-amber-500/5">
|
|
157
|
+
<CardContent className="flex items-center gap-2 pt-4 text-amber-700 text-sm dark:text-amber-300">
|
|
158
|
+
<AlertTriangle className="h-4 w-4" aria-hidden="true" />
|
|
159
|
+
{messages.envFlagOff}
|
|
160
|
+
</CardContent>
|
|
161
|
+
</Card>
|
|
162
|
+
) : null}
|
|
163
|
+
|
|
164
|
+
{triggerNotice ? (
|
|
165
|
+
<Card
|
|
166
|
+
className={
|
|
167
|
+
triggerNotice.kind === "success"
|
|
168
|
+
? "border-emerald-500/40 bg-emerald-500/5"
|
|
169
|
+
: "border-destructive/40 bg-destructive/5"
|
|
170
|
+
}
|
|
171
|
+
>
|
|
172
|
+
<CardContent
|
|
173
|
+
className={`pt-4 text-sm ${
|
|
174
|
+
triggerNotice.kind === "success"
|
|
175
|
+
? "text-emerald-600 dark:text-emerald-300"
|
|
176
|
+
: "text-destructive"
|
|
177
|
+
}`}
|
|
178
|
+
>
|
|
179
|
+
{triggerNotice.text}
|
|
180
|
+
</CardContent>
|
|
181
|
+
</Card>
|
|
182
|
+
) : null}
|
|
183
|
+
|
|
184
|
+
{error ? (
|
|
185
|
+
<Card className="border-destructive/40">
|
|
186
|
+
<CardContent className="pt-4 text-destructive text-sm">{error}</CardContent>
|
|
187
|
+
</Card>
|
|
188
|
+
) : null}
|
|
189
|
+
|
|
190
|
+
{!error && rows.length === 0 && !loading ? (
|
|
191
|
+
<Card>
|
|
192
|
+
<CardContent className="pt-4 text-muted-foreground text-sm">
|
|
193
|
+
{messages.empty}
|
|
194
|
+
</CardContent>
|
|
195
|
+
</Card>
|
|
196
|
+
) : null}
|
|
197
|
+
|
|
198
|
+
{rows.length > 0 ? (
|
|
199
|
+
<div className="overflow-hidden rounded-md border">
|
|
200
|
+
<table className="w-full text-sm">
|
|
201
|
+
<thead className="bg-muted/40 text-left text-xs uppercase">
|
|
202
|
+
<tr>
|
|
203
|
+
<th className="px-3 py-2 font-medium">{messages.workflowColumn}</th>
|
|
204
|
+
<th className="px-3 py-2 font-medium">{messages.scheduleColumn}</th>
|
|
205
|
+
<th className="px-3 py-2 font-medium">{messages.nextRunColumn}</th>
|
|
206
|
+
<th className="px-3 py-2 font-medium">{messages.lastRunColumn}</th>
|
|
207
|
+
<th className="px-3 py-2 font-medium">{messages.statusColumn}</th>
|
|
208
|
+
{onTriggerNow ? (
|
|
209
|
+
<th className="px-3 py-2 font-medium">{messages.actionsColumn}</th>
|
|
210
|
+
) : null}
|
|
211
|
+
</tr>
|
|
212
|
+
</thead>
|
|
213
|
+
<tbody>
|
|
214
|
+
{rows.map((row) => (
|
|
215
|
+
<ScheduleRow
|
|
216
|
+
key={row.scheduleId}
|
|
217
|
+
row={row}
|
|
218
|
+
lastRun={lastRuns[row.workflowId] ?? null}
|
|
219
|
+
triggering={!!triggering[row.scheduleId]}
|
|
220
|
+
onTriggerNow={onTriggerNow ? () => void triggerRow(row) : undefined}
|
|
221
|
+
envFlagDisabled={showEnvFlag && !envFlagOn}
|
|
222
|
+
rootMessages={rootMessages}
|
|
223
|
+
/>
|
|
224
|
+
))}
|
|
225
|
+
</tbody>
|
|
226
|
+
</table>
|
|
227
|
+
</div>
|
|
228
|
+
) : null}
|
|
229
|
+
</main>
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function ScheduleRow({
|
|
235
|
+
row,
|
|
236
|
+
lastRun,
|
|
237
|
+
triggering,
|
|
238
|
+
onTriggerNow,
|
|
239
|
+
envFlagDisabled,
|
|
240
|
+
rootMessages,
|
|
241
|
+
}: {
|
|
242
|
+
row: WorkflowScheduleSummary
|
|
243
|
+
lastRun: WorkflowRun | null
|
|
244
|
+
triggering: boolean
|
|
245
|
+
onTriggerNow?: () => void
|
|
246
|
+
envFlagDisabled: boolean
|
|
247
|
+
rootMessages: WorkflowRunsUiMessages
|
|
248
|
+
}) {
|
|
249
|
+
const messages = rootMessages.schedules
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<tr className="border-t">
|
|
253
|
+
<td className="px-3 py-2 font-mono text-xs">{row.workflowId}</td>
|
|
254
|
+
<td className="px-3 py-2 font-mono text-xs">{formatScheduleDecl(row.schedule, messages)}</td>
|
|
255
|
+
<td className="px-3 py-2 text-xs text-muted-foreground">
|
|
256
|
+
{formatNextRun(row, messages, rootMessages)}
|
|
257
|
+
</td>
|
|
258
|
+
<td className="px-3 py-2 text-xs">{formatLastRun(lastRun, row, messages, rootMessages)}</td>
|
|
259
|
+
<td className="px-3 py-2">
|
|
260
|
+
<StatusPill row={row} envFlagDisabled={envFlagDisabled} messages={messages} />
|
|
261
|
+
</td>
|
|
262
|
+
{onTriggerNow ? (
|
|
263
|
+
<td className="px-3 py-2">
|
|
264
|
+
<Button
|
|
265
|
+
type="button"
|
|
266
|
+
size="sm"
|
|
267
|
+
variant="outline"
|
|
268
|
+
onClick={onTriggerNow}
|
|
269
|
+
disabled={triggering}
|
|
270
|
+
>
|
|
271
|
+
{triggering ? messages.triggering : messages.triggerNow}
|
|
272
|
+
</Button>
|
|
273
|
+
</td>
|
|
274
|
+
) : null}
|
|
275
|
+
</tr>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function StatusPill({
|
|
280
|
+
row,
|
|
281
|
+
envFlagDisabled,
|
|
282
|
+
messages,
|
|
283
|
+
}: {
|
|
284
|
+
row: WorkflowScheduleSummary
|
|
285
|
+
envFlagDisabled: boolean
|
|
286
|
+
messages: SchedulesMessages
|
|
287
|
+
}) {
|
|
288
|
+
if (envFlagDisabled) {
|
|
289
|
+
return (
|
|
290
|
+
<Badge variant="outline" className="border-muted-foreground/30 text-muted-foreground">
|
|
291
|
+
{messages.disabledByEnvFlag}
|
|
292
|
+
</Badge>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
if (row.disabledReason === "registration_disabled") {
|
|
296
|
+
return (
|
|
297
|
+
<Badge variant="outline" className="border-muted-foreground/30 text-muted-foreground">
|
|
298
|
+
{messages.disabledByRegistration}
|
|
299
|
+
</Badge>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
if (row.disabledReason === "env_filtered") {
|
|
303
|
+
return (
|
|
304
|
+
<Badge variant="outline" className="border-muted-foreground/30 text-muted-foreground">
|
|
305
|
+
{messages.disabledByEnvironment}
|
|
306
|
+
</Badge>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
return (
|
|
310
|
+
<Badge
|
|
311
|
+
variant="outline"
|
|
312
|
+
className="border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300"
|
|
313
|
+
>
|
|
314
|
+
{messages.enabled}
|
|
315
|
+
</Badge>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function formatScheduleDecl(decl: WorkflowScheduleDecl, messages: SchedulesMessages): string {
|
|
320
|
+
if (decl.cron) return messages.cron(decl.cron, decl.timezone ?? "UTC")
|
|
321
|
+
if (decl.every !== undefined) return messages.every(String(decl.every))
|
|
322
|
+
if (decl.at) return messages.at(decl.at)
|
|
323
|
+
return messages.eventDriven
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatNextRun(
|
|
327
|
+
row: WorkflowScheduleSummary,
|
|
328
|
+
messages: SchedulesMessages,
|
|
329
|
+
rootMessages: WorkflowRunsUiMessages,
|
|
330
|
+
): string {
|
|
331
|
+
if (!row.enabled || row.nextRunAt === null) return messages.notScheduled
|
|
332
|
+
const delta = row.nextRunAt - Date.now()
|
|
333
|
+
const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages)
|
|
334
|
+
return delta >= 0 ? messages.inFuture(relative) : messages.inPast(relative)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function formatLastRun(
|
|
338
|
+
lastRun: WorkflowRun | null,
|
|
339
|
+
row: WorkflowScheduleSummary,
|
|
340
|
+
messages: SchedulesMessages,
|
|
341
|
+
rootMessages: WorkflowRunsUiMessages,
|
|
342
|
+
) {
|
|
343
|
+
if (lastRun) {
|
|
344
|
+
const relative = formatRelative(lastRun.startedAt, rootMessages)
|
|
345
|
+
const label = lastRunLabel(lastRun.status, relative, messages)
|
|
346
|
+
return (
|
|
347
|
+
<span className="inline-flex items-center gap-1.5">
|
|
348
|
+
<StatusIcon status={lastRun.status} />
|
|
349
|
+
{label}
|
|
350
|
+
</span>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
if (row.lastError && row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
354
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
|
|
355
|
+
return (
|
|
356
|
+
<span className="inline-flex items-center gap-1.5" title={row.lastError}>
|
|
357
|
+
<StatusIcon status="failed" />
|
|
358
|
+
{messages.lastRunFailed(relative)}
|
|
359
|
+
</span>
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
if (row.lastSuccessfulRunAt !== undefined && row.lastSuccessfulRunAt !== null) {
|
|
363
|
+
const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages)
|
|
364
|
+
return (
|
|
365
|
+
<span className="inline-flex items-center gap-1.5">
|
|
366
|
+
<StatusIcon status="succeeded" />
|
|
367
|
+
{messages.lastRunSucceeded(relative)}
|
|
368
|
+
</span>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
if (row.lastFireAt !== undefined && row.lastFireAt !== null) {
|
|
372
|
+
const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
|
|
373
|
+
return (
|
|
374
|
+
<span className="inline-flex items-center gap-1.5">
|
|
375
|
+
<CalendarClock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
|
376
|
+
{messages.lastFireRecorded(relative)}
|
|
377
|
+
</span>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
return <span className="text-muted-foreground">{messages.lastRunNone}</span>
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function lastRunLabel(
|
|
384
|
+
status: WorkflowRunStatus,
|
|
385
|
+
relative: string,
|
|
386
|
+
messages: SchedulesMessages,
|
|
387
|
+
): string {
|
|
388
|
+
switch (status) {
|
|
389
|
+
case "succeeded":
|
|
390
|
+
return messages.lastRunSucceeded(relative)
|
|
391
|
+
case "failed":
|
|
392
|
+
return messages.lastRunFailed(relative)
|
|
393
|
+
case "running":
|
|
394
|
+
return messages.lastRunRunning
|
|
395
|
+
case "cancelled":
|
|
396
|
+
return messages.lastRunCancelled(relative)
|
|
397
|
+
}
|
|
398
|
+
}
|
package/src/i18n/en.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { WorkflowRunsUiMessages } from "./messages.js"
|
|
2
|
+
|
|
3
|
+
export const workflowRunsUiEn: WorkflowRunsUiMessages = {
|
|
4
|
+
page: {
|
|
5
|
+
title: "Workflow runs",
|
|
6
|
+
subtitle: "Recent workflow executions and recorded steps.",
|
|
7
|
+
filterTitle: "Filter",
|
|
8
|
+
workflowLabel: "Workflow",
|
|
9
|
+
workflowPlaceholder: "checkout-finalize",
|
|
10
|
+
workflowEmpty: "No workflows in the visible runs.",
|
|
11
|
+
searchLabel: "Search",
|
|
12
|
+
searchPlaceholder: "Correlation, tag, error, input...",
|
|
13
|
+
statusLabel: "Status",
|
|
14
|
+
tagLabel: "Tag",
|
|
15
|
+
tagPlaceholder: "bookingId:bk_...",
|
|
16
|
+
tagEmpty: "No tags in the visible runs.",
|
|
17
|
+
addTag: "Add",
|
|
18
|
+
removeTag: "Remove tag",
|
|
19
|
+
timeRangeLabel: "Started",
|
|
20
|
+
live: "Live",
|
|
21
|
+
clearFilters: "Clear",
|
|
22
|
+
anyStatus: "Any",
|
|
23
|
+
empty: "No runs yet.",
|
|
24
|
+
selectPrompt: "Pick a run from the list to see its steps and payloads.",
|
|
25
|
+
loading: "Loading...",
|
|
26
|
+
loadError: "Could not load workflow runs.",
|
|
27
|
+
runCount: (count) => `${count} ${count === 1 ? "run" : "runs"}`,
|
|
28
|
+
filteredRunCount: (filtered, total) =>
|
|
29
|
+
filtered === total ? `${total} ${total === 1 ? "run" : "runs"}` : `${filtered}/${total} runs`,
|
|
30
|
+
timeRanges: {
|
|
31
|
+
"15m": "15m",
|
|
32
|
+
"1h": "1h",
|
|
33
|
+
"24h": "24h",
|
|
34
|
+
"7d": "7d",
|
|
35
|
+
all: "All",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
status: {
|
|
39
|
+
running: "Running",
|
|
40
|
+
succeeded: "Succeeded",
|
|
41
|
+
failed: "Failed",
|
|
42
|
+
cancelled: "Cancelled",
|
|
43
|
+
},
|
|
44
|
+
stepStatus: {
|
|
45
|
+
running: "Running",
|
|
46
|
+
succeeded: "Succeeded",
|
|
47
|
+
failed: "Failed",
|
|
48
|
+
skipped: "Skipped",
|
|
49
|
+
compensated: "Compensated",
|
|
50
|
+
},
|
|
51
|
+
detail: {
|
|
52
|
+
trigger: "Trigger",
|
|
53
|
+
correlation: "Correlation",
|
|
54
|
+
parent: "Parent",
|
|
55
|
+
triggeredBy: "Triggered by",
|
|
56
|
+
tags: "Tags",
|
|
57
|
+
started: "Started",
|
|
58
|
+
finished: "Finished",
|
|
59
|
+
steps: "Steps",
|
|
60
|
+
noSteps: "No steps recorded.",
|
|
61
|
+
input: "Input",
|
|
62
|
+
output: "Output",
|
|
63
|
+
result: "Result",
|
|
64
|
+
runError: "Run error",
|
|
65
|
+
stackTrace: "Stack trace",
|
|
66
|
+
copy: "Copy",
|
|
67
|
+
copied: "Copied",
|
|
68
|
+
code: "code",
|
|
69
|
+
step: "step",
|
|
70
|
+
reruns: "Reruns of this run",
|
|
71
|
+
resumedAt: (step) => `resumed @ ${step}`,
|
|
72
|
+
durationUnavailable: "-",
|
|
73
|
+
},
|
|
74
|
+
actions: {
|
|
75
|
+
rerun: "Rerun",
|
|
76
|
+
rerunBusy: "Rerunning",
|
|
77
|
+
resume: "Resume from failed step",
|
|
78
|
+
resumeBusy: "Resuming",
|
|
79
|
+
waitForCompletion: "Wait for this run to finish before rerunning.",
|
|
80
|
+
rerunDescription: "Start a new run with the same recorded input.",
|
|
81
|
+
resumeDescription: "Skip already-completed steps and retry from the failed step.",
|
|
82
|
+
resumeUnavailable: "Resume is only available for failed runs.",
|
|
83
|
+
actionFailed: "Action failed.",
|
|
84
|
+
rerunStarted: "Rerun started; opening new run.",
|
|
85
|
+
resumeStarted: (step) => `Resumed from step "${step}"; opening new run.`,
|
|
86
|
+
runnerMissing:
|
|
87
|
+
"No runner is registered for this workflow. Register a WorkflowRunner before rerun is available.",
|
|
88
|
+
rerunBlocked: "Rerun was blocked by the runner safety predicate.",
|
|
89
|
+
incompletePriorStep: "Cannot resume because an earlier step is incomplete.",
|
|
90
|
+
confirmTitle: "Confirm rerun",
|
|
91
|
+
confirmBody:
|
|
92
|
+
"This workflow has side effects that re-fire on rerun. The new run starts from the first step with the same recorded input.",
|
|
93
|
+
confirmTip:
|
|
94
|
+
"If the original failed mid-way, prefer Resume from failed step because it skips completed work.",
|
|
95
|
+
cancel: "Cancel",
|
|
96
|
+
rerunAnyway: "Rerun anyway",
|
|
97
|
+
},
|
|
98
|
+
format: {
|
|
99
|
+
relativeNow: "now",
|
|
100
|
+
},
|
|
101
|
+
schedules: {
|
|
102
|
+
title: "Workflow schedules",
|
|
103
|
+
subtitle: "Registered cron, interval, and one-shot schedules.",
|
|
104
|
+
environmentLabel: "Environment",
|
|
105
|
+
versionLabel: "Manifest",
|
|
106
|
+
workflowColumn: "Workflow",
|
|
107
|
+
scheduleColumn: "Schedule",
|
|
108
|
+
nextRunColumn: "Next run",
|
|
109
|
+
lastRunColumn: "Last run",
|
|
110
|
+
statusColumn: "Status",
|
|
111
|
+
actionsColumn: "Actions",
|
|
112
|
+
enabled: "Enabled",
|
|
113
|
+
disabledByRegistration: "Disabled",
|
|
114
|
+
disabledByEnvironment: "Disabled (env filtered)",
|
|
115
|
+
disabledByEnvFlag: "Disabled (env flag)",
|
|
116
|
+
envFlagOff: "Schedules disabled by environment flag — no schedules will fire.",
|
|
117
|
+
envFlagOn: "Schedules enabled.",
|
|
118
|
+
eventDriven: "event-driven",
|
|
119
|
+
cron: (expr, timezone) => `${expr} (${timezone})`,
|
|
120
|
+
every: (interval) => `every ${interval}`,
|
|
121
|
+
at: (timestamp) => `at ${timestamp}`,
|
|
122
|
+
triggerNow: "Trigger now",
|
|
123
|
+
triggering: "Triggering...",
|
|
124
|
+
triggerSuccess: "Run started.",
|
|
125
|
+
triggerFailed: "Trigger failed.",
|
|
126
|
+
refresh: "Refresh",
|
|
127
|
+
loading: "Loading schedules...",
|
|
128
|
+
loadError: "Could not load workflow schedules.",
|
|
129
|
+
empty: "No schedules registered for this environment.",
|
|
130
|
+
inFuture: (relative) => `in ${relative}`,
|
|
131
|
+
inPast: (relative) => `${relative} ago`,
|
|
132
|
+
notScheduled: "—",
|
|
133
|
+
lastRunSucceeded: (relative) => `succeeded ${relative} ago`,
|
|
134
|
+
lastRunFailed: (relative) => `failed ${relative} ago`,
|
|
135
|
+
lastRunRunning: "running",
|
|
136
|
+
lastRunCancelled: (relative) => `cancelled ${relative} ago`,
|
|
137
|
+
lastFireRecorded: (relative) => `fired ${relative} ago`,
|
|
138
|
+
lastRunNone: "never run",
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const workflowsUiEn = workflowRunsUiEn
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { workflowRunsUiEn, workflowRunsUiEn as workflowsUiEn } from "./en.js"
|
|
2
|
+
export type {
|
|
3
|
+
WorkflowRunsUiMessages,
|
|
4
|
+
WorkflowRunsUiMessages as WorkflowsUiMessages,
|
|
5
|
+
} from "./messages.js"
|
|
6
|
+
export {
|
|
7
|
+
getWorkflowRunsUiI18n,
|
|
8
|
+
getWorkflowRunsUiI18n as getWorkflowsUiI18n,
|
|
9
|
+
resolveWorkflowRunsUiMessages,
|
|
10
|
+
resolveWorkflowRunsUiMessages as resolveWorkflowsUiMessages,
|
|
11
|
+
useWorkflowRunsUiI18n,
|
|
12
|
+
useWorkflowRunsUiI18n as useWorkflowsUiI18n,
|
|
13
|
+
useWorkflowRunsUiI18nOrDefault,
|
|
14
|
+
useWorkflowRunsUiI18nOrDefault as useWorkflowsUiI18nOrDefault,
|
|
15
|
+
useWorkflowRunsUiMessages,
|
|
16
|
+
useWorkflowRunsUiMessages as useWorkflowsUiMessages,
|
|
17
|
+
useWorkflowRunsUiMessagesOrDefault,
|
|
18
|
+
useWorkflowRunsUiMessagesOrDefault as useWorkflowsUiMessagesOrDefault,
|
|
19
|
+
type WorkflowRunsUiMessageOverrides,
|
|
20
|
+
type WorkflowRunsUiMessageOverrides as WorkflowsUiMessageOverrides,
|
|
21
|
+
WorkflowRunsUiMessagesProvider,
|
|
22
|
+
WorkflowRunsUiMessagesProvider as WorkflowsUiMessagesProvider,
|
|
23
|
+
workflowRunsUiMessageDefinitions,
|
|
24
|
+
workflowRunsUiMessageDefinitions as workflowsUiMessageDefinitions,
|
|
25
|
+
} from "./provider.js"
|
|
26
|
+
export { workflowRunsUiRo, workflowRunsUiRo as workflowsUiRo } from "./ro.js"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { WorkflowRunStatus, WorkflowRunStepStatus } from "../types.js"
|
|
2
|
+
|
|
3
|
+
export type WorkflowRunsUiMessages = {
|
|
4
|
+
page: {
|
|
5
|
+
title: string
|
|
6
|
+
subtitle: string
|
|
7
|
+
filterTitle: string
|
|
8
|
+
workflowLabel: string
|
|
9
|
+
workflowPlaceholder: string
|
|
10
|
+
workflowEmpty: string
|
|
11
|
+
searchLabel: string
|
|
12
|
+
searchPlaceholder: string
|
|
13
|
+
statusLabel: string
|
|
14
|
+
tagLabel: string
|
|
15
|
+
tagPlaceholder: string
|
|
16
|
+
tagEmpty: string
|
|
17
|
+
addTag: string
|
|
18
|
+
removeTag: string
|
|
19
|
+
timeRangeLabel: string
|
|
20
|
+
live: string
|
|
21
|
+
clearFilters: string
|
|
22
|
+
anyStatus: string
|
|
23
|
+
empty: string
|
|
24
|
+
selectPrompt: string
|
|
25
|
+
loading: string
|
|
26
|
+
loadError: string
|
|
27
|
+
runCount: (count: number) => string
|
|
28
|
+
filteredRunCount: (filtered: number, total: number) => string
|
|
29
|
+
timeRanges: {
|
|
30
|
+
"15m": string
|
|
31
|
+
"1h": string
|
|
32
|
+
"24h": string
|
|
33
|
+
"7d": string
|
|
34
|
+
all: string
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
status: Record<WorkflowRunStatus, string>
|
|
38
|
+
stepStatus: Record<WorkflowRunStepStatus, string>
|
|
39
|
+
detail: {
|
|
40
|
+
trigger: string
|
|
41
|
+
correlation: string
|
|
42
|
+
parent: string
|
|
43
|
+
triggeredBy: string
|
|
44
|
+
tags: string
|
|
45
|
+
started: string
|
|
46
|
+
finished: string
|
|
47
|
+
steps: string
|
|
48
|
+
noSteps: string
|
|
49
|
+
input: string
|
|
50
|
+
output: string
|
|
51
|
+
result: string
|
|
52
|
+
runError: string
|
|
53
|
+
stackTrace: string
|
|
54
|
+
copy: string
|
|
55
|
+
copied: string
|
|
56
|
+
code: string
|
|
57
|
+
step: string
|
|
58
|
+
reruns: string
|
|
59
|
+
resumedAt: (step: string) => string
|
|
60
|
+
durationUnavailable: string
|
|
61
|
+
}
|
|
62
|
+
actions: {
|
|
63
|
+
rerun: string
|
|
64
|
+
rerunBusy: string
|
|
65
|
+
resume: string
|
|
66
|
+
resumeBusy: string
|
|
67
|
+
waitForCompletion: string
|
|
68
|
+
rerunDescription: string
|
|
69
|
+
resumeDescription: string
|
|
70
|
+
resumeUnavailable: string
|
|
71
|
+
actionFailed: string
|
|
72
|
+
rerunStarted: string
|
|
73
|
+
resumeStarted: (step: string) => string
|
|
74
|
+
runnerMissing: string
|
|
75
|
+
rerunBlocked: string
|
|
76
|
+
incompletePriorStep: string
|
|
77
|
+
confirmTitle: string
|
|
78
|
+
confirmBody: string
|
|
79
|
+
confirmTip: string
|
|
80
|
+
cancel: string
|
|
81
|
+
rerunAnyway: string
|
|
82
|
+
}
|
|
83
|
+
format: {
|
|
84
|
+
relativeNow: string
|
|
85
|
+
}
|
|
86
|
+
schedules: {
|
|
87
|
+
title: string
|
|
88
|
+
subtitle: string
|
|
89
|
+
environmentLabel: string
|
|
90
|
+
versionLabel: string
|
|
91
|
+
workflowColumn: string
|
|
92
|
+
scheduleColumn: string
|
|
93
|
+
nextRunColumn: string
|
|
94
|
+
lastRunColumn: string
|
|
95
|
+
statusColumn: string
|
|
96
|
+
actionsColumn: string
|
|
97
|
+
enabled: string
|
|
98
|
+
disabledByRegistration: string
|
|
99
|
+
disabledByEnvironment: string
|
|
100
|
+
disabledByEnvFlag: string
|
|
101
|
+
envFlagOff: string
|
|
102
|
+
envFlagOn: string
|
|
103
|
+
eventDriven: string
|
|
104
|
+
cron: (expr: string, timezone: string) => string
|
|
105
|
+
every: (interval: string) => string
|
|
106
|
+
at: (timestamp: string) => string
|
|
107
|
+
triggerNow: string
|
|
108
|
+
triggering: string
|
|
109
|
+
triggerSuccess: string
|
|
110
|
+
triggerFailed: string
|
|
111
|
+
refresh: string
|
|
112
|
+
loading: string
|
|
113
|
+
loadError: string
|
|
114
|
+
empty: string
|
|
115
|
+
inFuture: (relative: string) => string
|
|
116
|
+
inPast: (relative: string) => string
|
|
117
|
+
notScheduled: string
|
|
118
|
+
lastRunSucceeded: (relative: string) => string
|
|
119
|
+
lastRunFailed: (relative: string) => string
|
|
120
|
+
lastRunRunning: string
|
|
121
|
+
lastRunCancelled: (relative: string) => string
|
|
122
|
+
lastFireRecorded: (relative: string) => string
|
|
123
|
+
lastRunNone: string
|
|
124
|
+
}
|
|
125
|
+
}
|