@acmekit/dashboard 2.13.34 → 2.13.35

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 (87) hide show
  1. package/dist/{api-key-management-create-4AG76FJV.mjs → api-key-management-create-U37VC624.mjs} +3 -3
  2. package/dist/{api-key-management-detail-T2TB4KST.mjs → api-key-management-detail-ZYKL4ATI.mjs} +10 -10
  3. package/dist/{api-key-management-edit-R44OHS7B.mjs → api-key-management-edit-TSZGMIBL.mjs} +3 -3
  4. package/dist/{api-key-management-list-QK4Q7Y5I.mjs → api-key-management-list-HCJFJWWB.mjs} +3 -3
  5. package/dist/app.css +31 -0
  6. package/dist/app.js +3726 -1386
  7. package/dist/app.mjs +240 -38
  8. package/dist/{chunk-GBFVWROS.mjs → chunk-5IEHCYJO.mjs} +1 -1
  9. package/dist/{chunk-DQCEH3X2.mjs → chunk-7F3CWXUH.mjs} +1 -1
  10. package/dist/chunk-A7ULKHDE.mjs +126 -0
  11. package/dist/{chunk-DN3MIYQH.mjs → chunk-FKTMBR44.mjs} +1 -1
  12. package/dist/chunk-GBPAZAJK.mjs +34 -0
  13. package/dist/{chunk-YRWSG3YM.mjs → chunk-HHPPTD3B.mjs} +1 -1
  14. package/dist/chunk-LP6CPB7N.mjs +213 -0
  15. package/dist/{chunk-EFRMWHRX.mjs → chunk-PFZQYK7R.mjs} +1 -1
  16. package/dist/{chunk-XIM7X4FB.mjs → chunk-SYACY6AL.mjs} +1 -1
  17. package/dist/{chunk-2U3RK3JG.mjs → chunk-VEI6HW6L.mjs} +3 -5
  18. package/dist/{chunk-ST2YB7JN.mjs → chunk-WLRJXEKL.mjs} +1 -1
  19. package/dist/{chunk-ULSPL3DR.mjs → chunk-XIP35KXF.mjs} +1 -1
  20. package/dist/{chunk-DTY37DDZ.mjs → chunk-YKIWIMJX.mjs} +1 -0
  21. package/dist/en.json +132 -3
  22. package/dist/{invite-XGPZZBUP.mjs → invite-3JSNOA2B.mjs} +3 -3
  23. package/dist/{login-GNP3QIPI.mjs → login-BEJ5EFGE.mjs} +9 -9
  24. package/dist/{profile-detail-YX27F7N6.mjs → profile-detail-QVTJC4JC.mjs} +3 -3
  25. package/dist/{profile-edit-2VRDU75O.mjs → profile-edit-MIO62TWH.mjs} +3 -3
  26. package/dist/{reset-password-TWRNZO6Z.mjs → reset-password-BN4KAJQL.mjs} +2 -2
  27. package/dist/{settings-3XWLL5LG.mjs → settings-GH5IWXHE.mjs} +3 -3
  28. package/dist/{translation-list-CCEQJNED.mjs → translation-list-JA22BUKN.mjs} +10 -10
  29. package/dist/{translations-edit-E57GVUFV.mjs → translations-edit-STTMANVT.mjs} +11 -11
  30. package/dist/{user-detail-KUSRRVNX.mjs → user-detail-WCXBFRGS.mjs} +3 -3
  31. package/dist/{user-edit-HTN3ZGCL.mjs → user-edit-XDVMJOS4.mjs} +3 -3
  32. package/dist/{user-invite-E3FAAU3V.mjs → user-invite-73ZDSDFC.mjs} +3 -3
  33. package/dist/{user-list-KNJ5S3IM.mjs → user-list-MPJXE3CA.mjs} +5 -5
  34. package/dist/{user-metadata-5GQK75DT.mjs → user-metadata-ADNTL3LT.mjs} +10 -10
  35. package/dist/workflow-analytics-4WCI4ODQ.mjs +152 -0
  36. package/dist/workflow-definition-detail-GI6CFBMG.mjs +94 -0
  37. package/dist/workflow-definition-list-GF3XAEPS.mjs +142 -0
  38. package/dist/workflow-execution-complete-step-WSRLO572.mjs +245 -0
  39. package/dist/workflow-execution-detail-3RH6EQSS.mjs +1411 -0
  40. package/dist/workflow-execution-list-AQEGAME4.mjs +596 -0
  41. package/dist/workflow-execution-rerun-WCYLYL3Q.mjs +138 -0
  42. package/dist/workflow-execution-run-MWN5KWNY.mjs +135 -0
  43. package/dist/workflow-scheduled-list-ZPXR7CZM.mjs +174 -0
  44. package/package.json +9 -9
  45. package/src/components/layout/main-layout/main-layout.tsx +28 -1
  46. package/src/dashboard-app/routes/get-route.map.tsx +71 -0
  47. package/src/hooks/api/workflow-definitions.tsx +79 -0
  48. package/src/hooks/api/workflow-executions.tsx +145 -1
  49. package/src/hooks/api/workflow-metrics.tsx +48 -0
  50. package/src/hooks/use-workflow-sse.tsx +78 -0
  51. package/src/i18n/translations/$schema.json +534 -4
  52. package/src/i18n/translations/en.json +132 -3
  53. package/src/routes/workflow-analytics/workflow-analytics.tsx +167 -0
  54. package/src/routes/workflow-definitions/workflow-definition-detail/workflow-definition-detail.tsx +98 -0
  55. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/use-workflow-definition-table-columns.tsx +78 -0
  56. package/src/routes/workflow-definitions/workflow-definition-list/components/workflow-definition-list-table/workflow-definition-list-table.tsx +65 -0
  57. package/src/routes/workflow-definitions/workflow-definition-list/workflow-definition-list.tsx +15 -0
  58. package/src/routes/workflow-executions/constants.ts +16 -0
  59. package/src/routes/workflow-executions/utils.ts +170 -14
  60. package/src/routes/workflow-executions/workflow-execution-complete-step/workflow-execution-complete-step.tsx +270 -0
  61. package/src/routes/workflow-executions/workflow-execution-detail/breadcrumb.tsx +7 -1
  62. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/index.ts +1 -0
  63. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-action-bar/workflow-execution-action-bar.tsx +212 -0
  64. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/index.ts +1 -0
  65. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-error-card/workflow-execution-error-card.tsx +59 -0
  66. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-history-section/workflow-execution-history-section.tsx +157 -6
  67. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-payload-section/workflow-execution-payload-section.tsx +122 -6
  68. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-timeline-section/workflow-execution-timeline-section.tsx +7 -1
  69. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/index.ts +1 -0
  70. package/src/routes/workflow-executions/workflow-execution-detail/components/workflow-execution-waiting-banner/workflow-execution-waiting-banner.tsx +63 -0
  71. package/src/routes/workflow-executions/workflow-execution-detail/workflow-detail.tsx +46 -1
  72. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-columns.tsx +7 -0
  73. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-filters.tsx +7 -1
  74. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/use-workflow-execution-table-query.tsx +4 -2
  75. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-auto-refresh.tsx +73 -0
  76. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-list-table.tsx +17 -1
  77. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-row-actions.tsx +116 -0
  78. package/src/routes/workflow-executions/workflow-execution-list/components/workflow-execution-list-table/workflow-execution-saved-views.tsx +84 -0
  79. package/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx +1 -1
  80. package/src/routes/workflow-executions/workflow-execution-rerun/workflow-execution-rerun.tsx +159 -0
  81. package/src/routes/workflow-executions/workflow-execution-run/workflow-execution-run.tsx +139 -0
  82. package/src/routes/workflow-scheduled/workflow-scheduled-list.tsx +269 -0
  83. package/dist/chunk-LKWTBYYC.mjs +0 -35
  84. package/dist/chunk-RPAL6FHW.mjs +0 -73
  85. package/dist/workflow-execution-detail-5O5VCXL3.mjs +0 -870
  86. package/dist/workflow-execution-list-DETG4MRT.mjs +0 -347
  87. /package/dist/{chunk-22YYMH6M.mjs → chunk-RISX76YT.mjs} +0 -0
@@ -52,6 +52,12 @@ export const useWorkflowExecutionTableFilters = (): Filter[] => {
52
52
  { key: "updated_at", label: t("fields.updatedAt"), type: "date" },
53
53
  ]
54
54
 
55
- return [stateFilter, ...dateFilters]
55
+ const workflowIdFilter: Filter = {
56
+ key: "workflow_id",
57
+ label: t("workflowExecutions.workflowIdLabel"),
58
+ type: "string",
59
+ }
60
+
61
+ return [stateFilter, workflowIdFilter, ...dateFilters]
56
62
  }
57
63
 
@@ -9,17 +9,19 @@ export const useWorkflowExecutionTableQuery = ({
9
9
  prefix?: string
10
10
  }) => {
11
11
  const raw = useQueryParams(
12
- ["q", "offset", "order", "state", "created_at", "updated_at"],
12
+ ["q", "offset", "order", "state", "workflow_id", "created_at", "updated_at"],
13
13
  prefix
14
14
  )
15
15
 
16
- const { offset, order, state, created_at, updated_at, ...rest } = raw
16
+ const { offset, order, state, workflow_id, created_at, updated_at, ...rest } =
17
+ raw
17
18
 
18
19
  const searchParams: HttpTypes.AdminGetWorkflowExecutionsParams = {
19
20
  limit: pageSize,
20
21
  offset: offset ? parseInt(offset) : 0,
21
22
  order: order ?? "-created_at",
22
23
  state: state ? state.split(",") : undefined,
24
+ workflow_id: workflow_id ? workflow_id.split(",") : undefined,
23
25
  created_at: created_at ? JSON.parse(created_at) : undefined,
24
26
  updated_at: updated_at ? JSON.parse(updated_at) : undefined,
25
27
  ...rest,
@@ -0,0 +1,73 @@
1
+ import { ArrowPathMini } from "@acmekit/icons"
2
+ import { DropdownMenu, IconButton } from "@acmekit/ui"
3
+ import { useQueryClient } from "@tanstack/react-query"
4
+ import { useCallback, useEffect, useRef, useState } from "react"
5
+ import { workflowExecutionsQueryKeys } from "../../../../../hooks/api/workflow-executions"
6
+
7
+ const INTERVALS = [
8
+ { label: "Off", value: 0 },
9
+ { label: "5s", value: 5000 },
10
+ { label: "10s", value: 10000 },
11
+ { label: "30s", value: 30000 },
12
+ ]
13
+
14
+ export const WorkflowExecutionAutoRefresh = () => {
15
+ const queryClient = useQueryClient()
16
+ const [interval, setInterval_] = useState(0)
17
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
18
+
19
+ const refresh = useCallback(() => {
20
+ queryClient.invalidateQueries({
21
+ queryKey: workflowExecutionsQueryKeys.lists(),
22
+ })
23
+ }, [queryClient])
24
+
25
+ useEffect(() => {
26
+ if (intervalRef.current) {
27
+ clearInterval(intervalRef.current)
28
+ intervalRef.current = null
29
+ }
30
+
31
+ if (interval > 0) {
32
+ intervalRef.current = setInterval(refresh, interval)
33
+ }
34
+
35
+ return () => {
36
+ if (intervalRef.current) {
37
+ clearInterval(intervalRef.current)
38
+ }
39
+ }
40
+ }, [interval, refresh])
41
+
42
+ const activeLabel = INTERVALS.find((i) => i.value === interval)?.label
43
+
44
+ return (
45
+ <div className="flex items-center gap-x-1">
46
+ <IconButton
47
+ size="small"
48
+ variant="transparent"
49
+ onClick={refresh}
50
+ >
51
+ <ArrowPathMini />
52
+ </IconButton>
53
+ <DropdownMenu>
54
+ <DropdownMenu.Trigger asChild>
55
+ <button className="txt-compact-small text-ui-fg-muted hover:text-ui-fg-subtle rounded px-1.5 py-0.5 transition-colors">
56
+ {activeLabel}
57
+ </button>
58
+ </DropdownMenu.Trigger>
59
+ <DropdownMenu.Content align="end">
60
+ {INTERVALS.map((opt) => (
61
+ <DropdownMenu.Item
62
+ key={opt.value}
63
+ onClick={() => setInterval_(opt.value)}
64
+ className={interval === opt.value ? "font-medium" : ""}
65
+ >
66
+ {opt.label}
67
+ </DropdownMenu.Item>
68
+ ))}
69
+ </DropdownMenu.Content>
70
+ </DropdownMenu>
71
+ </div>
72
+ )
73
+ }
@@ -1,9 +1,13 @@
1
- import { Container, Heading, Text } from "@acmekit/ui"
1
+ import { PlayMiniSolid } from "@acmekit/icons"
2
+ import { Button, Container, Heading, Text } from "@acmekit/ui"
2
3
  import { keepPreviousData } from "@tanstack/react-query"
3
4
  import { useTranslation } from "react-i18next"
5
+ import { Link } from "react-router-dom"
4
6
  import { _DataTable } from "../../../../../components/table/data-table"
5
7
  import { useWorkflowExecutions } from "../../../../../hooks/api/workflow-executions"
6
8
  import { useDataTable } from "../../../../../hooks/use-data-table"
9
+ import { WorkflowExecutionAutoRefresh } from "./workflow-execution-auto-refresh"
10
+ import { WorkflowExecutionSavedViews } from "./workflow-execution-saved-views"
7
11
  import { useWorkflowExecutionTableColumns } from "./use-workflow-execution-table-columns"
8
12
  import { useWorkflowExecutionTableFilters } from "./use-workflow-execution-table-filters"
9
13
  import { useWorkflowExecutionTableQuery } from "./use-workflow-execution-table-query"
@@ -52,6 +56,18 @@ export const WorkflowExecutionListTable = () => {
52
56
  {t(`workflowExecutions.subtitle`)}
53
57
  </Text>
54
58
  </div>
59
+ <div className="flex items-center gap-x-2">
60
+ <WorkflowExecutionAutoRefresh />
61
+ <Button variant="secondary" size="small" asChild>
62
+ <Link to="run">
63
+ <PlayMiniSolid className="mr-1" />
64
+ {t("workflowExecutions.actions.runWorkflow")}
65
+ </Link>
66
+ </Button>
67
+ </div>
68
+ </div>
69
+ <div className="border-ui-border-base border-b px-6 py-2">
70
+ <WorkflowExecutionSavedViews />
55
71
  </div>
56
72
  <_DataTable
57
73
  table={table}
@@ -0,0 +1,116 @@
1
+ import { ArrowPathMini, Eye, XMark } from "@acmekit/icons"
2
+ import { HttpTypes } from "@acmekit/types"
3
+ import { toast, usePrompt } from "@acmekit/ui"
4
+ import { useTranslation } from "react-i18next"
5
+ import {
6
+ Action,
7
+ ActionMenu,
8
+ } from "../../../../../components/common/action-menu"
9
+ import {
10
+ useCancelWorkflowExecution,
11
+ useRunWorkflow,
12
+ } from "../../../../../hooks/api/workflow-executions"
13
+ import {
14
+ TRANSACTION_ERROR_STATES,
15
+ TRANSACTION_IN_PROGRESS_STATES,
16
+ } from "../../../constants"
17
+ import { TransactionState } from "../../../types"
18
+
19
+ type WorkflowExecutionRowActionsProps = {
20
+ execution: HttpTypes.AdminWorkflowExecutionResponse["workflow_execution"]
21
+ }
22
+
23
+ export const WorkflowExecutionRowActions = ({
24
+ execution,
25
+ }: WorkflowExecutionRowActionsProps) => {
26
+ const { t } = useTranslation()
27
+ const prompt = usePrompt()
28
+
29
+ const state = execution.state as TransactionState
30
+ const isFailed = TRANSACTION_ERROR_STATES.includes(state)
31
+ const isRunning = TRANSACTION_IN_PROGRESS_STATES.includes(state)
32
+
33
+ const workflowId = execution.workflow_id
34
+ const transactionId = execution.transaction_id
35
+
36
+ const { mutateAsync: runAsync } = useRunWorkflow(workflowId)
37
+ const { mutateAsync: cancelAsync } = useCancelWorkflowExecution(
38
+ workflowId,
39
+ transactionId
40
+ )
41
+
42
+ const handleRetry = async () => {
43
+ const payload = (execution as any)?.context?.data?.payload
44
+ const input =
45
+ payload && typeof payload === "object"
46
+ ? (payload as Record<string, unknown>)
47
+ : {}
48
+ await runAsync(
49
+ { input },
50
+ {
51
+ onSuccess: () => {
52
+ toast.success(t("workflowExecutions.actions.retrySuccess"))
53
+ },
54
+ onError: (err) => {
55
+ toast.error(err.message)
56
+ },
57
+ }
58
+ )
59
+ }
60
+
61
+ const handleCancel = async () => {
62
+ const res = await prompt({
63
+ title: t("workflowExecutions.actions.cancelConfirmTitle"),
64
+ description: t(
65
+ "workflowExecutions.actions.cancelConfirmDescription",
66
+ { workflow_id: workflowId }
67
+ ),
68
+ confirmText: t("workflowExecutions.actions.cancel"),
69
+ cancelText: t("actions.cancel"),
70
+ })
71
+
72
+ if (!res) return
73
+
74
+ await cancelAsync(undefined, {
75
+ onSuccess: () => {
76
+ toast.success(t("workflowExecutions.actions.cancelSuccess"))
77
+ },
78
+ onError: (err) => {
79
+ toast.error(err.message)
80
+ },
81
+ })
82
+ }
83
+
84
+ const secondaryActions: Action[] = []
85
+ if (isFailed) {
86
+ secondaryActions.push({
87
+ icon: <ArrowPathMini />,
88
+ label: t("workflowExecutions.actions.retry"),
89
+ onClick: handleRetry,
90
+ })
91
+ }
92
+ if (isRunning) {
93
+ secondaryActions.push({
94
+ icon: <XMark />,
95
+ label: t("workflowExecutions.actions.cancel"),
96
+ onClick: handleCancel,
97
+ })
98
+ }
99
+
100
+ const groups = [
101
+ {
102
+ actions: [
103
+ {
104
+ icon: <Eye />,
105
+ label: t("workflowExecutions.actions.viewDetails"),
106
+ to: `${execution.id}`,
107
+ } as Action,
108
+ ],
109
+ },
110
+ ...(secondaryActions.length > 0
111
+ ? [{ actions: secondaryActions }]
112
+ : []),
113
+ ]
114
+
115
+ return <ActionMenu groups={groups} />
116
+ }
@@ -0,0 +1,84 @@
1
+ import { Button, clx } from "@acmekit/ui"
2
+ import { useCallback, useMemo } from "react"
3
+ import { useTranslation } from "react-i18next"
4
+ import { useSearchParams } from "react-router-dom"
5
+
6
+ type SavedView = {
7
+ label: string
8
+ params: Record<string, string>
9
+ }
10
+
11
+ export const WorkflowExecutionSavedViews = () => {
12
+ const { t } = useTranslation()
13
+ const [searchParams, setSearchParams] = useSearchParams()
14
+
15
+ const views: SavedView[] = useMemo(
16
+ () => [
17
+ {
18
+ label: t("workflowExecutions.filters.allExecutions"),
19
+ params: {} as Record<string, string>,
20
+ },
21
+ {
22
+ label: t("workflowExecutions.filters.running"),
23
+ params: { state: "invoking" } as Record<string, string>,
24
+ },
25
+ {
26
+ label: t("workflowExecutions.filters.failedToday"),
27
+ params: {
28
+ state: "failed",
29
+ created_at: JSON.stringify({
30
+ $gte: new Date(
31
+ new Date().setHours(0, 0, 0, 0)
32
+ ).toISOString(),
33
+ }),
34
+ } as Record<string, string>,
35
+ },
36
+ ],
37
+ [t]
38
+ )
39
+
40
+ const isActive = useCallback(
41
+ (view: SavedView) => {
42
+ const viewKeys = Object.keys(view.params)
43
+ if (viewKeys.length === 0) {
44
+ return !searchParams.has("state") && !searchParams.has("created_at")
45
+ }
46
+ return viewKeys.every(
47
+ (key) => searchParams.get(key) === view.params[key]
48
+ )
49
+ },
50
+ [searchParams]
51
+ )
52
+
53
+ const handleClick = useCallback(
54
+ (view: SavedView) => {
55
+ const newParams = new URLSearchParams()
56
+ for (const [key, value] of Object.entries(view.params)) {
57
+ newParams.set(key, value)
58
+ }
59
+ setSearchParams(newParams)
60
+ },
61
+ [setSearchParams]
62
+ )
63
+
64
+ return (
65
+ <div className="flex items-center gap-x-1">
66
+ {views.map((view) => (
67
+ <Button
68
+ key={view.label}
69
+ variant="transparent"
70
+ size="small"
71
+ onClick={() => handleClick(view)}
72
+ className={clx(
73
+ "txt-compact-small-plus",
74
+ isActive(view)
75
+ ? "bg-ui-bg-base shadow-elevation-card-rest text-ui-fg-base"
76
+ : "text-ui-fg-muted"
77
+ )}
78
+ >
79
+ {view.label}
80
+ </Button>
81
+ ))}
82
+ </div>
83
+ )
84
+ }
@@ -12,7 +12,7 @@ export const WorkflowExcecutionList = () => {
12
12
  after: getWidgets("workflow.list.after"),
13
13
  before: getWidgets("workflow.list.before"),
14
14
  }}
15
- hasOutlet={false}
15
+ hasOutlet
16
16
  >
17
17
  <WorkflowExecutionListTable />
18
18
  </SingleColumnPage>
@@ -0,0 +1,159 @@
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
@@ -0,0 +1,139 @@
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