@acmekit/dashboard 2.13.35 → 2.13.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/src/components/layout/main-layout/main-layout.tsx +1 -28
- package/src/dashboard-app/routes/get-route.map.tsx +0 -71
- package/src/hooks/api/workflow-executions.tsx +1 -145
- package/src/i18n/translations/$schema.json +4 -534
- package/src/i18n/translations/en.json +3 -132
- package/src/routes/workflow-executions/constants.ts +0 -16
- package/src/routes/workflow-executions/utils.ts +14 -170
- package/src/routes/workflow-executions/workflow-execution-detail/breadcrumb.tsx +1 -7
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-history-section/workflow-execution-history-section.tsx +6 -157
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-payload-section/workflow-execution-payload-section.tsx +6 -122
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-timeline-section/workflow-execution-timeline-section.tsx +1 -7
- package/src/routes/workflow-executions/workflow-execution-detail/workflow-detail.tsx +1 -46
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-columns.tsx +0 -7
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-filters.tsx +1 -7
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-query.tsx +2 -4
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-list-table.tsx +1 -17
- package/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx +1 -1
- package/src/hooks/api/workflow-definitions.tsx +0 -79
- package/src/hooks/api/workflow-metrics.tsx +0 -48
- package/src/hooks/use-workflow-sse.tsx +0 -78
- package/src/routes/workflow-analytics/workflow-analytics.tsx +0 -167
- package/src/routes/workflow-definitions/workflow-definition-detail/workflow-definition-detail.tsx +0 -98
- package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/use-workflow-definition-table-columns.tsx +0 -78
- package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/workflow-definition-list-table.tsx +0 -65
- package/src/routes/workflow-definitions/workflow-definition-list/workflow-definition-list.tsx +0 -15
- package/src/routes/workflow-executions/workflow-execution-complete-step/workflow-execution-complete-step.tsx +0 -270
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/index.ts +0 -1
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/workflow-execution-action-bar.tsx +0 -212
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/index.ts +0 -1
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/workflow-execution-error-card.tsx +0 -59
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/index.ts +0 -1
- package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/workflow-execution-waiting-banner.tsx +0 -63
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-auto-refresh.tsx +0 -73
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-row-actions.tsx +0 -116
- package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-saved-views.tsx +0 -84
- package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx +0 -159
- package/src/routes/workflow-executions/workflow-execution-run/workflow-execution-run.tsx +0 -139
- package/src/routes/workflow-scheduled/workflow-scheduled-list.tsx +0 -269
package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { zodResolver } from "@hookform/resolvers/zod"
|
|
2
|
-
import { Button, Heading, Textarea, toast } from "@acmekit/ui"
|
|
3
|
-
import { useForm } from "react-hook-form"
|
|
4
|
-
import { useTranslation } from "react-i18next"
|
|
5
|
-
import { useParams } from "react-router-dom"
|
|
6
|
-
import * as zod from "zod"
|
|
7
|
-
|
|
8
|
-
import { Form } from "../../../components/common/form"
|
|
9
|
-
import { RouteDrawer, useRouteModal } from "../../../components/modals"
|
|
10
|
-
import { KeyboundForm } from "../../../components/utilities/keybound-form"
|
|
11
|
-
import {
|
|
12
|
-
useRunWorkflow,
|
|
13
|
-
useWorkflowExecution,
|
|
14
|
-
} from "../../../hooks/api/workflow-executions"
|
|
15
|
-
|
|
16
|
-
const RerunSchema = zod.object({
|
|
17
|
-
input: zod.string().refine(
|
|
18
|
-
(val) => {
|
|
19
|
-
try {
|
|
20
|
-
JSON.parse(val)
|
|
21
|
-
return true
|
|
22
|
-
} catch {
|
|
23
|
-
return false
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
{ message: "Must be valid JSON" }
|
|
27
|
-
),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
export const WorkflowExecutionRerun = () => {
|
|
31
|
-
const { t } = useTranslation()
|
|
32
|
-
const { id } = useParams()
|
|
33
|
-
const { handleSuccess } = useRouteModal()
|
|
34
|
-
|
|
35
|
-
const {
|
|
36
|
-
workflow_execution,
|
|
37
|
-
isPending: isLoading,
|
|
38
|
-
isError,
|
|
39
|
-
error,
|
|
40
|
-
} = useWorkflowExecution(id!)
|
|
41
|
-
|
|
42
|
-
if (isError) {
|
|
43
|
-
throw error
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (isLoading || !workflow_execution) {
|
|
47
|
-
return (
|
|
48
|
-
<RouteDrawer>
|
|
49
|
-
<RouteDrawer.Header>
|
|
50
|
-
<Heading>{t("workflowExecutions.rerun.title")}</Heading>
|
|
51
|
-
</RouteDrawer.Header>
|
|
52
|
-
</RouteDrawer>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<RouteDrawer>
|
|
58
|
-
<RouteDrawer.Header>
|
|
59
|
-
<Heading>{t("workflowExecutions.rerun.title")}</Heading>
|
|
60
|
-
</RouteDrawer.Header>
|
|
61
|
-
<RerunForm
|
|
62
|
-
workflowId={workflow_execution.workflow_id}
|
|
63
|
-
defaultInput={
|
|
64
|
-
(workflow_execution as any)?.context?.data?.payload || {}
|
|
65
|
-
}
|
|
66
|
-
onSuccess={handleSuccess}
|
|
67
|
-
/>
|
|
68
|
-
</RouteDrawer>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const RerunForm = ({
|
|
73
|
-
workflowId,
|
|
74
|
-
defaultInput,
|
|
75
|
-
onSuccess,
|
|
76
|
-
}: {
|
|
77
|
-
workflowId: string
|
|
78
|
-
defaultInput: unknown
|
|
79
|
-
onSuccess: () => void
|
|
80
|
-
}) => {
|
|
81
|
-
const { t } = useTranslation()
|
|
82
|
-
|
|
83
|
-
const form = useForm<zod.infer<typeof RerunSchema>>({
|
|
84
|
-
defaultValues: {
|
|
85
|
-
input: JSON.stringify(defaultInput, null, 2),
|
|
86
|
-
},
|
|
87
|
-
resolver: zodResolver(RerunSchema),
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const { mutateAsync, isPending } = useRunWorkflow(workflowId)
|
|
91
|
-
|
|
92
|
-
const handleSubmit = form.handleSubmit(async (values) => {
|
|
93
|
-
const parsed = JSON.parse(values.input)
|
|
94
|
-
await mutateAsync(
|
|
95
|
-
{ input: parsed },
|
|
96
|
-
{
|
|
97
|
-
onSuccess: () => {
|
|
98
|
-
toast.success(t("workflowExecutions.rerun.success"))
|
|
99
|
-
onSuccess()
|
|
100
|
-
},
|
|
101
|
-
onError: (err) => {
|
|
102
|
-
toast.error(err.message)
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<RouteDrawer.Form form={form}>
|
|
110
|
-
<KeyboundForm
|
|
111
|
-
onSubmit={handleSubmit}
|
|
112
|
-
className="flex flex-1 flex-col overflow-hidden"
|
|
113
|
-
>
|
|
114
|
-
<RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-4 overflow-y-auto">
|
|
115
|
-
<div className="bg-ui-bg-field border-ui-border-base rounded-lg border px-3 py-2">
|
|
116
|
-
<span className="txt-compact-small text-ui-fg-muted">
|
|
117
|
-
{workflowId}
|
|
118
|
-
</span>
|
|
119
|
-
</div>
|
|
120
|
-
<Form.Field
|
|
121
|
-
control={form.control}
|
|
122
|
-
name="input"
|
|
123
|
-
render={({ field }) => {
|
|
124
|
-
return (
|
|
125
|
-
<Form.Item>
|
|
126
|
-
<Form.Label>
|
|
127
|
-
{t("workflowExecutions.rerun.inputLabel")}
|
|
128
|
-
</Form.Label>
|
|
129
|
-
<Form.Control>
|
|
130
|
-
<Textarea
|
|
131
|
-
{...field}
|
|
132
|
-
className="font-mono text-xs"
|
|
133
|
-
rows={16}
|
|
134
|
-
/>
|
|
135
|
-
</Form.Control>
|
|
136
|
-
<Form.ErrorMessage />
|
|
137
|
-
</Form.Item>
|
|
138
|
-
)
|
|
139
|
-
}}
|
|
140
|
-
/>
|
|
141
|
-
</RouteDrawer.Body>
|
|
142
|
-
<RouteDrawer.Footer>
|
|
143
|
-
<div className="flex items-center justify-end gap-x-2">
|
|
144
|
-
<RouteDrawer.Close asChild>
|
|
145
|
-
<Button size="small" variant="secondary">
|
|
146
|
-
{t("actions.cancel")}
|
|
147
|
-
</Button>
|
|
148
|
-
</RouteDrawer.Close>
|
|
149
|
-
<Button size="small" type="submit" isLoading={isPending}>
|
|
150
|
-
{t("workflowExecutions.rerun.submit")}
|
|
151
|
-
</Button>
|
|
152
|
-
</div>
|
|
153
|
-
</RouteDrawer.Footer>
|
|
154
|
-
</KeyboundForm>
|
|
155
|
-
</RouteDrawer.Form>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export const Component = WorkflowExecutionRerun
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { zodResolver } from "@hookform/resolvers/zod"
|
|
2
|
-
import { Button, Heading, Input, Textarea, toast } from "@acmekit/ui"
|
|
3
|
-
import { useForm } from "react-hook-form"
|
|
4
|
-
import { useTranslation } from "react-i18next"
|
|
5
|
-
import * as zod from "zod"
|
|
6
|
-
|
|
7
|
-
import { Form } from "../../../components/common/form"
|
|
8
|
-
import { RouteDrawer, useRouteModal } from "../../../components/modals"
|
|
9
|
-
import { KeyboundForm } from "../../../components/utilities/keybound-form"
|
|
10
|
-
import { useRunWorkflow } from "../../../hooks/api/workflow-executions"
|
|
11
|
-
|
|
12
|
-
const RunWorkflowSchema = zod.object({
|
|
13
|
-
workflow_id: zod.string().min(1, "Workflow ID is required"),
|
|
14
|
-
input: zod.string().refine(
|
|
15
|
-
(val) => {
|
|
16
|
-
if (!val.trim()) return true
|
|
17
|
-
try {
|
|
18
|
-
JSON.parse(val)
|
|
19
|
-
return true
|
|
20
|
-
} catch {
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
{ message: "Must be valid JSON" }
|
|
25
|
-
),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export const WorkflowExecutionRun = () => {
|
|
29
|
-
const { t } = useTranslation()
|
|
30
|
-
const { handleSuccess } = useRouteModal()
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<RouteDrawer>
|
|
34
|
-
<RouteDrawer.Header>
|
|
35
|
-
<Heading>{t("workflowExecutions.actions.runWorkflow")}</Heading>
|
|
36
|
-
</RouteDrawer.Header>
|
|
37
|
-
<RunWorkflowForm onSuccess={handleSuccess} />
|
|
38
|
-
</RouteDrawer>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const RunWorkflowForm = ({ onSuccess }: { onSuccess: () => void }) => {
|
|
43
|
-
const { t } = useTranslation()
|
|
44
|
-
|
|
45
|
-
const form = useForm<zod.infer<typeof RunWorkflowSchema>>({
|
|
46
|
-
defaultValues: {
|
|
47
|
-
workflow_id: "",
|
|
48
|
-
input: "{}",
|
|
49
|
-
},
|
|
50
|
-
resolver: zodResolver(RunWorkflowSchema),
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const workflowId = form.watch("workflow_id")
|
|
54
|
-
|
|
55
|
-
const { mutateAsync, isPending } = useRunWorkflow(workflowId || "_placeholder")
|
|
56
|
-
|
|
57
|
-
const handleSubmit = form.handleSubmit(async (values) => {
|
|
58
|
-
const parsed = values.input.trim() ? JSON.parse(values.input) : {}
|
|
59
|
-
await mutateAsync(
|
|
60
|
-
{ input: parsed },
|
|
61
|
-
{
|
|
62
|
-
onSuccess: () => {
|
|
63
|
-
toast.success(t("workflowExecutions.rerun.success"))
|
|
64
|
-
onSuccess()
|
|
65
|
-
},
|
|
66
|
-
onError: (err) => {
|
|
67
|
-
toast.error(err.message)
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<RouteDrawer.Form form={form}>
|
|
75
|
-
<KeyboundForm
|
|
76
|
-
onSubmit={handleSubmit}
|
|
77
|
-
className="flex flex-1 flex-col overflow-hidden"
|
|
78
|
-
>
|
|
79
|
-
<RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-4 overflow-y-auto">
|
|
80
|
-
<Form.Field
|
|
81
|
-
control={form.control}
|
|
82
|
-
name="workflow_id"
|
|
83
|
-
render={({ field }) => {
|
|
84
|
-
return (
|
|
85
|
-
<Form.Item>
|
|
86
|
-
<Form.Label>
|
|
87
|
-
{t("workflowExecutions.workflowIdLabel")}
|
|
88
|
-
</Form.Label>
|
|
89
|
-
<Form.Control>
|
|
90
|
-
<Input
|
|
91
|
-
{...field}
|
|
92
|
-
placeholder="my-workflow-id"
|
|
93
|
-
/>
|
|
94
|
-
</Form.Control>
|
|
95
|
-
<Form.ErrorMessage />
|
|
96
|
-
</Form.Item>
|
|
97
|
-
)
|
|
98
|
-
}}
|
|
99
|
-
/>
|
|
100
|
-
<Form.Field
|
|
101
|
-
control={form.control}
|
|
102
|
-
name="input"
|
|
103
|
-
render={({ field }) => {
|
|
104
|
-
return (
|
|
105
|
-
<Form.Item>
|
|
106
|
-
<Form.Label>
|
|
107
|
-
{t("workflowExecutions.rerun.inputLabel")}
|
|
108
|
-
</Form.Label>
|
|
109
|
-
<Form.Control>
|
|
110
|
-
<Textarea
|
|
111
|
-
{...field}
|
|
112
|
-
className="font-mono text-xs"
|
|
113
|
-
rows={12}
|
|
114
|
-
/>
|
|
115
|
-
</Form.Control>
|
|
116
|
-
<Form.ErrorMessage />
|
|
117
|
-
</Form.Item>
|
|
118
|
-
)
|
|
119
|
-
}}
|
|
120
|
-
/>
|
|
121
|
-
</RouteDrawer.Body>
|
|
122
|
-
<RouteDrawer.Footer>
|
|
123
|
-
<div className="flex items-center justify-end gap-x-2">
|
|
124
|
-
<RouteDrawer.Close asChild>
|
|
125
|
-
<Button size="small" variant="secondary">
|
|
126
|
-
{t("actions.cancel")}
|
|
127
|
-
</Button>
|
|
128
|
-
</RouteDrawer.Close>
|
|
129
|
-
<Button size="small" type="submit" isLoading={isPending}>
|
|
130
|
-
{t("workflowExecutions.rerun.submit")}
|
|
131
|
-
</Button>
|
|
132
|
-
</div>
|
|
133
|
-
</RouteDrawer.Footer>
|
|
134
|
-
</KeyboundForm>
|
|
135
|
-
</RouteDrawer.Form>
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const Component = WorkflowExecutionRun
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { Badge, Button, Container, Heading, Text, toast } from "@acmekit/ui"
|
|
2
|
-
import { keepPreviousData } from "@tanstack/react-query"
|
|
3
|
-
import { formatDistanceToNow } from "date-fns"
|
|
4
|
-
import { useTranslation } from "react-i18next"
|
|
5
|
-
import { SingleColumnPage } from "../../components/layout/pages"
|
|
6
|
-
import {
|
|
7
|
-
useWorkflowDefinitions,
|
|
8
|
-
} from "../../hooks/api/workflow-definitions"
|
|
9
|
-
import { useRunWorkflow } from "../../hooks/api/workflow-executions"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Very small cron → human-readable description utility.
|
|
13
|
-
* Handles the most common patterns without an external library.
|
|
14
|
-
*/
|
|
15
|
-
function describeCron(cron: string): string {
|
|
16
|
-
const parts = cron.trim().split(/\s+/)
|
|
17
|
-
if (parts.length < 5) return cron
|
|
18
|
-
|
|
19
|
-
const [minute, hour, dom, month, dow] = parts
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
minute === "*" &&
|
|
23
|
-
hour === "*" &&
|
|
24
|
-
dom === "*" &&
|
|
25
|
-
month === "*" &&
|
|
26
|
-
dow === "*"
|
|
27
|
-
) {
|
|
28
|
-
return "Every minute"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
minute !== "*" &&
|
|
33
|
-
hour === "*" &&
|
|
34
|
-
dom === "*" &&
|
|
35
|
-
month === "*" &&
|
|
36
|
-
dow === "*"
|
|
37
|
-
) {
|
|
38
|
-
return `Every hour at minute ${minute}`
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
minute !== "*" &&
|
|
43
|
-
hour !== "*" &&
|
|
44
|
-
dom === "*" &&
|
|
45
|
-
month === "*" &&
|
|
46
|
-
dow === "*"
|
|
47
|
-
) {
|
|
48
|
-
const h = parseInt(hour, 10)
|
|
49
|
-
const m = parseInt(minute, 10)
|
|
50
|
-
const ampm = h >= 12 ? "PM" : "AM"
|
|
51
|
-
const h12 = h % 12 || 12
|
|
52
|
-
return `Daily at ${h12}:${String(m).padStart(2, "0")} ${ampm}`
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
minute !== "*" &&
|
|
57
|
-
hour !== "*" &&
|
|
58
|
-
dom === "*" &&
|
|
59
|
-
month === "*" &&
|
|
60
|
-
dow !== "*"
|
|
61
|
-
) {
|
|
62
|
-
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
63
|
-
const dayNames = dow
|
|
64
|
-
.split(",")
|
|
65
|
-
.map((d) => days[parseInt(d, 10)] ?? d)
|
|
66
|
-
.join(", ")
|
|
67
|
-
const h = parseInt(hour, 10)
|
|
68
|
-
const m = parseInt(minute, 10)
|
|
69
|
-
const ampm = h >= 12 ? "PM" : "AM"
|
|
70
|
-
const h12 = h % 12 || 12
|
|
71
|
-
return `${dayNames} at ${h12}:${String(m).padStart(2, "0")} ${ampm}`
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
minute !== "*" &&
|
|
76
|
-
hour !== "*" &&
|
|
77
|
-
dom !== "*" &&
|
|
78
|
-
month === "*" &&
|
|
79
|
-
dow === "*"
|
|
80
|
-
) {
|
|
81
|
-
return `Monthly on day ${dom} at ${hour}:${String(parseInt(minute, 10)).padStart(2, "0")}`
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return cron
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Rough next-run estimate using date-fns (not exact; just indicative).
|
|
89
|
-
*/
|
|
90
|
-
function getNextRunLabel(cron: string): string {
|
|
91
|
-
try {
|
|
92
|
-
const parts = cron.trim().split(/\s+/)
|
|
93
|
-
if (parts.length < 5) return "Unknown"
|
|
94
|
-
|
|
95
|
-
const [minuteStr, hourStr] = parts
|
|
96
|
-
const now = new Date()
|
|
97
|
-
const next = new Date(now)
|
|
98
|
-
|
|
99
|
-
if (minuteStr === "*") {
|
|
100
|
-
// every minute
|
|
101
|
-
next.setSeconds(0, 0)
|
|
102
|
-
next.setMinutes(now.getMinutes() + 1)
|
|
103
|
-
} else if (hourStr === "*") {
|
|
104
|
-
// every hour at :MM
|
|
105
|
-
const m = parseInt(minuteStr, 10)
|
|
106
|
-
next.setMinutes(m, 0, 0)
|
|
107
|
-
if (next <= now) next.setHours(now.getHours() + 1)
|
|
108
|
-
} else {
|
|
109
|
-
// daily at HH:MM (simplified)
|
|
110
|
-
const h = parseInt(hourStr, 10)
|
|
111
|
-
const m = parseInt(minuteStr, 10)
|
|
112
|
-
next.setHours(h, m, 0, 0)
|
|
113
|
-
if (next <= now) next.setDate(now.getDate() + 1)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return formatDistanceToNow(next, { addSuffix: true })
|
|
117
|
-
} catch {
|
|
118
|
-
return "Unknown"
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
type WorkflowDef = {
|
|
123
|
-
id: string
|
|
124
|
-
handler_count: number
|
|
125
|
-
steps: Array<{ action: string; depth: number; noCompensation: boolean }>
|
|
126
|
-
options: Record<string, unknown>
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const ScheduledRow = ({ def }: { def: WorkflowDef }) => {
|
|
130
|
-
const { t } = useTranslation()
|
|
131
|
-
|
|
132
|
-
const schedule = def.options?.schedule as
|
|
133
|
-
| { cron?: string; interval?: string }
|
|
134
|
-
| string
|
|
135
|
-
| undefined
|
|
136
|
-
|
|
137
|
-
const cronStr =
|
|
138
|
-
typeof schedule === "string"
|
|
139
|
-
? schedule
|
|
140
|
-
: typeof schedule === "object"
|
|
141
|
-
? schedule?.cron ?? String(schedule?.interval ?? "")
|
|
142
|
-
: ""
|
|
143
|
-
|
|
144
|
-
const humanSchedule = cronStr ? describeCron(cronStr) : "—"
|
|
145
|
-
const nextRun = cronStr ? getNextRunLabel(cronStr) : "—"
|
|
146
|
-
|
|
147
|
-
const { mutateAsync: runWorkflow, isPending } = useRunWorkflow(def.id)
|
|
148
|
-
|
|
149
|
-
const handleRunNow = async () => {
|
|
150
|
-
await runWorkflow(
|
|
151
|
-
{ input: {} },
|
|
152
|
-
{
|
|
153
|
-
onSuccess: () => {
|
|
154
|
-
toast.success(t("workflowExecutions.actions.retrySuccess"))
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<div className="grid grid-cols-[1fr_1fr_1fr_auto] items-center gap-x-4 px-6 py-3">
|
|
162
|
-
<div className="flex flex-col gap-y-0.5">
|
|
163
|
-
<Text size="small" weight="plus" leading="compact">
|
|
164
|
-
{def.id}
|
|
165
|
-
</Text>
|
|
166
|
-
<Text size="xsmall" className="text-ui-fg-muted">
|
|
167
|
-
{def.handler_count}{" "}
|
|
168
|
-
{t("workflowExecutions.definitions.handlers")}
|
|
169
|
-
</Text>
|
|
170
|
-
</div>
|
|
171
|
-
<div>
|
|
172
|
-
<Badge size="2xsmall" className="font-mono">
|
|
173
|
-
{cronStr || "—"}
|
|
174
|
-
</Badge>
|
|
175
|
-
<Text size="xsmall" className="text-ui-fg-subtle mt-0.5">
|
|
176
|
-
{humanSchedule}
|
|
177
|
-
</Text>
|
|
178
|
-
</div>
|
|
179
|
-
<Text size="small" className="text-ui-fg-subtle">
|
|
180
|
-
{nextRun}
|
|
181
|
-
</Text>
|
|
182
|
-
<Button
|
|
183
|
-
size="small"
|
|
184
|
-
variant="secondary"
|
|
185
|
-
isLoading={isPending}
|
|
186
|
-
onClick={handleRunNow}
|
|
187
|
-
>
|
|
188
|
-
{t("workflowExecutions.scheduled.runNow")}
|
|
189
|
-
</Button>
|
|
190
|
-
</div>
|
|
191
|
-
)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export const WorkflowScheduledList = () => {
|
|
195
|
-
const { t } = useTranslation()
|
|
196
|
-
|
|
197
|
-
const { workflow_definitions, isPending } = useWorkflowDefinitions(
|
|
198
|
-
undefined,
|
|
199
|
-
{ placeholderData: keepPreviousData }
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
const scheduled = (workflow_definitions ?? []).filter((def) => {
|
|
203
|
-
const opts = def.options
|
|
204
|
-
return opts && (opts.schedule || opts.cron)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<SingleColumnPage widgets={{ before: [], after: [] }}>
|
|
209
|
-
<Container className="divide-y p-0">
|
|
210
|
-
<div className="flex items-center justify-between px-6 py-4">
|
|
211
|
-
<div>
|
|
212
|
-
<Heading>{t("workflowExecutions.scheduled.domain")}</Heading>
|
|
213
|
-
<Text className="text-ui-fg-subtle" size="small">
|
|
214
|
-
{t("workflowExecutions.scheduled.subtitle")}
|
|
215
|
-
</Text>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
{/* Table header */}
|
|
220
|
-
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-x-4 border-b border-ui-border-base px-6 py-2">
|
|
221
|
-
<Text
|
|
222
|
-
size="xsmall"
|
|
223
|
-
weight="plus"
|
|
224
|
-
className="text-ui-fg-subtle uppercase"
|
|
225
|
-
>
|
|
226
|
-
Workflow
|
|
227
|
-
</Text>
|
|
228
|
-
<Text
|
|
229
|
-
size="xsmall"
|
|
230
|
-
weight="plus"
|
|
231
|
-
className="text-ui-fg-subtle uppercase"
|
|
232
|
-
>
|
|
233
|
-
{t("workflowExecutions.scheduled.schedule")}
|
|
234
|
-
</Text>
|
|
235
|
-
<Text
|
|
236
|
-
size="xsmall"
|
|
237
|
-
weight="plus"
|
|
238
|
-
className="text-ui-fg-subtle uppercase"
|
|
239
|
-
>
|
|
240
|
-
{t("workflowExecutions.scheduled.nextRun")}
|
|
241
|
-
</Text>
|
|
242
|
-
<div />
|
|
243
|
-
</div>
|
|
244
|
-
|
|
245
|
-
{isPending && (
|
|
246
|
-
<div className="px-6 py-4">
|
|
247
|
-
<Text size="small" className="text-ui-fg-muted">
|
|
248
|
-
Loading...
|
|
249
|
-
</Text>
|
|
250
|
-
</div>
|
|
251
|
-
)}
|
|
252
|
-
|
|
253
|
-
{!isPending && scheduled.length === 0 && (
|
|
254
|
-
<div className="px-6 py-8 text-center">
|
|
255
|
-
<Text size="small" className="text-ui-fg-muted">
|
|
256
|
-
{t("workflowExecutions.scheduled.noRecordsMessage")}
|
|
257
|
-
</Text>
|
|
258
|
-
</div>
|
|
259
|
-
)}
|
|
260
|
-
|
|
261
|
-
{scheduled.map((def) => (
|
|
262
|
-
<ScheduledRow key={def.id} def={def} />
|
|
263
|
-
))}
|
|
264
|
-
</Container>
|
|
265
|
-
</SingleColumnPage>
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export const Component = WorkflowScheduledList
|