@voyantjs/workflows-ui 0.37.1 → 0.38.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.
Files changed (48) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -3
  3. package/dist/client.d.ts +1 -1
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +1 -1
  6. package/dist/components/common.d.ts +30 -0
  7. package/dist/components/common.d.ts.map +1 -0
  8. package/dist/components/common.js +108 -0
  9. package/dist/components/workflow-run-actions.d.ts +7 -0
  10. package/dist/components/workflow-run-actions.d.ts.map +1 -0
  11. package/dist/components/workflow-run-actions.js +72 -0
  12. package/dist/components/workflow-run-detail-page.d.ts +9 -1
  13. package/dist/components/workflow-run-detail-page.d.ts.map +1 -1
  14. package/dist/components/workflow-run-detail-page.js +96 -1
  15. package/dist/components/workflow-runs-filters.d.ts +32 -0
  16. package/dist/components/workflow-runs-filters.d.ts.map +1 -0
  17. package/dist/components/workflow-runs-filters.js +97 -0
  18. package/dist/components/workflow-runs-page.d.ts +11 -1
  19. package/dist/components/workflow-runs-page.d.ts.map +1 -1
  20. package/dist/components/workflow-runs-page.js +132 -1
  21. package/dist/i18n/en.d.ts +3 -2
  22. package/dist/i18n/en.d.ts.map +1 -1
  23. package/dist/i18n/en.js +96 -2
  24. package/dist/i18n/index.d.ts +4 -2
  25. package/dist/i18n/index.d.ts.map +1 -1
  26. package/dist/i18n/index.js +3 -2
  27. package/dist/i18n/messages.d.ts +86 -0
  28. package/dist/i18n/messages.d.ts.map +1 -0
  29. package/dist/i18n/messages.js +1 -0
  30. package/dist/i18n/provider.d.ts +26 -0
  31. package/dist/i18n/provider.d.ts.map +1 -0
  32. package/dist/i18n/provider.js +44 -0
  33. package/dist/i18n/ro.d.ts +3 -2
  34. package/dist/i18n/ro.d.ts.map +1 -1
  35. package/dist/i18n/ro.js +98 -2
  36. package/dist/index.d.ts +6 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -2
  39. package/dist/types.d.ts +2 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +1 -0
  42. package/package.json +55 -68
  43. package/src/components/common.tsx +182 -0
  44. package/src/components/workflow-run-actions.tsx +160 -0
  45. package/src/components/workflow-run-detail-page.tsx +393 -0
  46. package/src/components/workflow-runs-filters.tsx +349 -0
  47. package/src/components/workflow-runs-page.tsx +357 -0
  48. package/src/styles.css +5 -3
package/dist/i18n/ro.js CHANGED
@@ -1,2 +1,98 @@
1
- export * from "@voyantjs/workflow-runs-ui/i18n/ro";
2
- export { workflowRunsUiRo as workflowsUiRo } from "@voyantjs/workflow-runs-ui/i18n/ro";
1
+ export const workflowRunsUiRo = {
2
+ page: {
3
+ title: "Rulari workflow",
4
+ subtitle: "Executii recente si pasi inregistrati.",
5
+ filterTitle: "Filtru",
6
+ workflowLabel: "Workflow",
7
+ workflowPlaceholder: "checkout-finalize",
8
+ workflowEmpty: "Nu exista workflow-uri in rularile vizibile.",
9
+ searchLabel: "Cautare",
10
+ searchPlaceholder: "Corelatie, eticheta, eroare, input...",
11
+ statusLabel: "Status",
12
+ tagLabel: "Eticheta",
13
+ tagPlaceholder: "bookingId:bk_...",
14
+ tagEmpty: "Nu exista etichete in rularile vizibile.",
15
+ addTag: "Adauga",
16
+ removeTag: "Sterge eticheta",
17
+ timeRangeLabel: "Pornit",
18
+ live: "Live",
19
+ clearFilters: "Curata",
20
+ anyStatus: "Oricare",
21
+ empty: "Nu exista rulari.",
22
+ selectPrompt: "Alege o rulare din lista pentru a vedea pasii si payload-urile.",
23
+ loading: "Se incarca...",
24
+ loadError: "Rularile workflow nu au putut fi incarcate.",
25
+ runCount: (count) => `${count} ${count === 1 ? "rulare" : "rulari"}`,
26
+ filteredRunCount: (filtered, total) => filtered === total
27
+ ? `${total} ${total === 1 ? "rulare" : "rulari"}`
28
+ : `${filtered}/${total} rulari`,
29
+ timeRanges: {
30
+ "15m": "15m",
31
+ "1h": "1h",
32
+ "24h": "24h",
33
+ "7d": "7z",
34
+ all: "Toate",
35
+ },
36
+ },
37
+ status: {
38
+ running: "In rulare",
39
+ succeeded: "Reusita",
40
+ failed: "Esuata",
41
+ cancelled: "Anulata",
42
+ },
43
+ stepStatus: {
44
+ running: "In rulare",
45
+ succeeded: "Reusit",
46
+ failed: "Esuat",
47
+ skipped: "Sarit",
48
+ compensated: "Compensat",
49
+ },
50
+ detail: {
51
+ trigger: "Declansator",
52
+ correlation: "Corelatie",
53
+ parent: "Parinte",
54
+ triggeredBy: "Declansat de",
55
+ tags: "Etichete",
56
+ started: "Pornit",
57
+ finished: "Finalizat",
58
+ steps: "Pasi",
59
+ noSteps: "Nu exista pasi inregistrati.",
60
+ input: "Input",
61
+ output: "Output",
62
+ result: "Rezultat",
63
+ runError: "Eroare rulare",
64
+ stackTrace: "Stack trace",
65
+ copy: "Copiaza",
66
+ copied: "Copiat",
67
+ code: "cod",
68
+ step: "pas",
69
+ reruns: "Reluari ale acestei rulari",
70
+ resumedAt: (step) => `reluat @ ${step}`,
71
+ durationUnavailable: "-",
72
+ },
73
+ actions: {
74
+ rerun: "Ruleaza din nou",
75
+ rerunBusy: "Se reia",
76
+ resume: "Reia de la pasul esuat",
77
+ resumeBusy: "Se reia",
78
+ waitForCompletion: "Asteapta finalizarea acestei rulari inainte de reluare.",
79
+ rerunDescription: "Porneste o rulare noua cu acelasi input inregistrat.",
80
+ resumeDescription: "Sare pasii deja finalizati si reincearca de la pasul esuat.",
81
+ resumeUnavailable: "Reluarea este disponibila doar pentru rulari esuate.",
82
+ actionFailed: "Actiunea a esuat.",
83
+ rerunStarted: "Reluarea a pornit; se deschide rularea noua.",
84
+ resumeStarted: (step) => `Reluat de la pasul "${step}"; se deschide rularea noua.`,
85
+ runnerMissing: "Nu exista runner inregistrat pentru acest workflow. Inregistreaza un WorkflowRunner pentru reluare.",
86
+ rerunBlocked: "Reluarea a fost blocata de regula de siguranta a runnerului.",
87
+ incompletePriorStep: "Nu se poate relua deoarece un pas anterior este incomplet.",
88
+ confirmTitle: "Confirma reluarea",
89
+ confirmBody: "Acest workflow are efecte secundare care se executa din nou la reluare. Rularea noua porneste de la primul pas cu acelasi input.",
90
+ confirmTip: "Daca rularea initiala a esuat la mijloc, foloseste reluarea de la pasul esuat pentru a sari lucrul finalizat.",
91
+ cancel: "Anuleaza",
92
+ rerunAnyway: "Ruleaza oricum",
93
+ },
94
+ format: {
95
+ relativeNow: "acum",
96
+ },
97
+ };
98
+ export const workflowsUiRo = workflowRunsUiRo;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
- export * from "@voyantjs/workflow-runs-ui";
2
- export { getWorkflowRunsUiI18n as getWorkflowsUiI18n, resolveWorkflowRunsUiMessages as resolveWorkflowsUiMessages, useWorkflowRunsUiI18n as useWorkflowsUiI18n, useWorkflowRunsUiI18nOrDefault as useWorkflowsUiI18nOrDefault, useWorkflowRunsUiMessages as useWorkflowsUiMessages, useWorkflowRunsUiMessagesOrDefault as useWorkflowsUiMessagesOrDefault, type WorkflowRunsUiMessageOverrides as WorkflowsUiMessageOverrides, type WorkflowRunsUiMessages as WorkflowsUiMessages, WorkflowRunsUiMessagesProvider as WorkflowsUiMessagesProvider, workflowRunsUiEn as workflowsUiEn, workflowRunsUiMessageDefinitions as workflowsUiMessageDefinitions, workflowRunsUiRo as workflowsUiRo, } from "@voyantjs/workflow-runs-ui";
1
+ export type { WorkflowRunsApiClientOptions } from "./client.js";
2
+ export { createWorkflowRunsApiClient } from "./client.js";
3
+ export { WorkflowRunDetailPage, type WorkflowRunDetailPageProps, } from "./components/workflow-run-detail-page.js";
4
+ export { WorkflowRunsPage, type WorkflowRunsPageProps, WorkflowRunsPageSkeleton, } from "./components/workflow-runs-page.js";
5
+ export { getWorkflowRunsUiI18n, getWorkflowRunsUiI18n as getWorkflowsUiI18n, resolveWorkflowRunsUiMessages, resolveWorkflowRunsUiMessages as resolveWorkflowsUiMessages, useWorkflowRunsUiI18n, useWorkflowRunsUiI18n as useWorkflowsUiI18n, useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiI18nOrDefault as useWorkflowsUiI18nOrDefault, useWorkflowRunsUiMessages, useWorkflowRunsUiMessages as useWorkflowsUiMessages, useWorkflowRunsUiMessagesOrDefault, useWorkflowRunsUiMessagesOrDefault as useWorkflowsUiMessagesOrDefault, type WorkflowRunsUiMessageOverrides, type WorkflowRunsUiMessageOverrides as WorkflowsUiMessageOverrides, type WorkflowRunsUiMessages, type WorkflowRunsUiMessages as WorkflowsUiMessages, WorkflowRunsUiMessagesProvider, WorkflowRunsUiMessagesProvider as WorkflowsUiMessagesProvider, workflowRunsUiEn, workflowRunsUiEn as workflowsUiEn, workflowRunsUiMessageDefinitions, workflowRunsUiMessageDefinitions as workflowsUiMessageDefinitions, workflowRunsUiRo, workflowRunsUiRo as workflowsUiRo, } from "./i18n/index.js";
6
+ export type { ListWorkflowRunsQuery, ListWorkflowRunsResponse, WorkflowRun, WorkflowRunActionError, WorkflowRunActionResponse, WorkflowRunActionResult, WorkflowRunDetailResponse, WorkflowRunErrorPayload, WorkflowRunStatus, WorkflowRunStep, WorkflowRunStepStatus, WorkflowRunsApi, } from "./types.js";
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAE1C,OAAO,EACL,qBAAqB,IAAI,kBAAkB,EAC3C,6BAA6B,IAAI,0BAA0B,EAC3D,qBAAqB,IAAI,kBAAkB,EAC3C,8BAA8B,IAAI,2BAA2B,EAC7D,yBAAyB,IAAI,sBAAsB,EACnD,kCAAkC,IAAI,+BAA+B,EACrE,KAAK,8BAA8B,IAAI,2BAA2B,EAClE,KAAK,sBAAsB,IAAI,mBAAmB,EAClD,8BAA8B,IAAI,2BAA2B,EAC7D,gBAAgB,IAAI,aAAa,EACjC,gCAAgC,IAAI,6BAA6B,EACjE,gBAAgB,IAAI,aAAa,GAClC,MAAM,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,wBAAwB,GACzB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACL,qBAAqB,EACrB,qBAAqB,IAAI,kBAAkB,EAC3C,6BAA6B,EAC7B,6BAA6B,IAAI,0BAA0B,EAC3D,qBAAqB,EACrB,qBAAqB,IAAI,kBAAkB,EAC3C,8BAA8B,EAC9B,8BAA8B,IAAI,2BAA2B,EAC7D,yBAAyB,EACzB,yBAAyB,IAAI,sBAAsB,EACnD,kCAAkC,EAClC,kCAAkC,IAAI,+BAA+B,EACrE,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,IAAI,2BAA2B,EAClE,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,IAAI,mBAAmB,EAClD,8BAA8B,EAC9B,8BAA8B,IAAI,2BAA2B,EAC7D,gBAAgB,EAChB,gBAAgB,IAAI,aAAa,EACjC,gCAAgC,EAChC,gCAAgC,IAAI,6BAA6B,EACjE,gBAAgB,EAChB,gBAAgB,IAAI,aAAa,GAClC,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
- export * from "@voyantjs/workflow-runs-ui";
2
- export { getWorkflowRunsUiI18n as getWorkflowsUiI18n, resolveWorkflowRunsUiMessages as resolveWorkflowsUiMessages, useWorkflowRunsUiI18n as useWorkflowsUiI18n, useWorkflowRunsUiI18nOrDefault as useWorkflowsUiI18nOrDefault, useWorkflowRunsUiMessages as useWorkflowsUiMessages, useWorkflowRunsUiMessagesOrDefault as useWorkflowsUiMessagesOrDefault, WorkflowRunsUiMessagesProvider as WorkflowsUiMessagesProvider, workflowRunsUiEn as workflowsUiEn, workflowRunsUiMessageDefinitions as workflowsUiMessageDefinitions, workflowRunsUiRo as workflowsUiRo, } from "@voyantjs/workflow-runs-ui";
1
+ export { createWorkflowRunsApiClient } from "./client.js";
2
+ export { WorkflowRunDetailPage, } from "./components/workflow-run-detail-page.js";
3
+ export { WorkflowRunsPage, WorkflowRunsPageSkeleton, } from "./components/workflow-runs-page.js";
4
+ export { getWorkflowRunsUiI18n, getWorkflowRunsUiI18n as getWorkflowsUiI18n, resolveWorkflowRunsUiMessages, resolveWorkflowRunsUiMessages as resolveWorkflowsUiMessages, useWorkflowRunsUiI18n, useWorkflowRunsUiI18n as useWorkflowsUiI18n, useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiI18nOrDefault as useWorkflowsUiI18nOrDefault, useWorkflowRunsUiMessages, useWorkflowRunsUiMessages as useWorkflowsUiMessages, useWorkflowRunsUiMessagesOrDefault, useWorkflowRunsUiMessagesOrDefault as useWorkflowsUiMessagesOrDefault, WorkflowRunsUiMessagesProvider, WorkflowRunsUiMessagesProvider as WorkflowsUiMessagesProvider, workflowRunsUiEn, workflowRunsUiEn as workflowsUiEn, workflowRunsUiMessageDefinitions, workflowRunsUiMessageDefinitions as workflowsUiMessageDefinitions, workflowRunsUiRo, workflowRunsUiRo as workflowsUiRo, } from "./i18n/index.js";
@@ -0,0 +1,2 @@
1
+ export type { ListWorkflowRunsQuery, ListWorkflowRunsResponse, WorkflowRun, WorkflowRunActionError, WorkflowRunActionResponse, WorkflowRunActionResult, WorkflowRunDetailResponse, WorkflowRunErrorPayload, WorkflowRunStatus, WorkflowRunStep, WorkflowRunStepStatus, WorkflowRunsApi, } from "@voyantjs/workflows-react/workflow-runs-client";
2
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,gDAAgD,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/workflows-ui",
3
- "version": "0.37.1",
3
+ "version": "0.38.1",
4
4
  "description": "Importable React admin UI for Voyant workflow run observability.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -11,90 +11,77 @@
11
11
  "type": "module",
12
12
  "sideEffects": false,
13
13
  "exports": {
14
- ".": "./src/index.ts",
15
- "./client": "./src/client.ts",
16
- "./styles.css": "./src/styles.css",
17
- "./i18n": "./src/i18n/index.ts",
18
- "./i18n/en": "./src/i18n/en.ts",
19
- "./i18n/ro": "./src/i18n/ro.ts",
20
- "./components/workflow-run-detail-page": "./src/components/workflow-run-detail-page.ts",
21
- "./components/workflow-runs-page": "./src/components/workflow-runs-page.ts"
22
- },
23
- "scripts": {
24
- "build": "tsc -p tsconfig.build.json",
25
- "clean": "rm -rf dist tsconfig.tsbuildinfo",
26
- "prepack": "pnpm run build",
27
- "typecheck": "tsc --noEmit",
28
- "lint": "biome check src/",
29
- "test": "vitest run --passWithNoTests"
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./client": {
20
+ "types": "./dist/client.d.ts",
21
+ "import": "./dist/client.js",
22
+ "default": "./dist/client.js"
23
+ },
24
+ "./styles.css": {
25
+ "default": "./src/styles.css"
26
+ },
27
+ "./i18n": {
28
+ "types": "./dist/i18n/index.d.ts",
29
+ "import": "./dist/i18n/index.js",
30
+ "default": "./dist/i18n/index.js"
31
+ },
32
+ "./i18n/en": {
33
+ "types": "./dist/i18n/en.d.ts",
34
+ "import": "./dist/i18n/en.js",
35
+ "default": "./dist/i18n/en.js"
36
+ },
37
+ "./i18n/ro": {
38
+ "types": "./dist/i18n/ro.d.ts",
39
+ "import": "./dist/i18n/ro.js",
40
+ "default": "./dist/i18n/ro.js"
41
+ },
42
+ "./components/*": {
43
+ "types": "./dist/components/*.d.ts",
44
+ "import": "./dist/components/*.js",
45
+ "default": "./dist/components/*.js"
46
+ }
30
47
  },
31
48
  "dependencies": {
32
- "@voyantjs/workflow-runs-ui": "workspace:*"
49
+ "@voyantjs/i18n": "0.38.1",
50
+ "@voyantjs/workflows-react": "0.38.1"
33
51
  },
34
52
  "peerDependencies": {
35
- "@voyantjs/ui": "workspace:*",
36
53
  "lucide-react": "^0.475.0 || ^1.0.0",
37
54
  "react": "^19.0.0",
38
- "react-dom": "^19.0.0"
55
+ "react-dom": "^19.0.0",
56
+ "@voyantjs/ui": "0.38.1"
39
57
  },
40
58
  "devDependencies": {
41
59
  "@types/react": "^19.2.14",
42
60
  "@types/react-dom": "^19.2.3",
43
- "@voyantjs/ui": "workspace:*",
44
- "@voyantjs/voyant-typescript-config": "workspace:*",
45
61
  "lucide-react": "^1.7.0",
46
62
  "react": "^19.2.4",
47
63
  "react-dom": "^19.2.4",
48
64
  "typescript": "^6.0.2",
49
- "vitest": "^4.1.2"
65
+ "vitest": "^4.1.2",
66
+ "@voyantjs/i18n": "0.38.1",
67
+ "@voyantjs/ui": "0.38.1",
68
+ "@voyantjs/voyant-typescript-config": "0.1.0"
50
69
  },
51
70
  "files": [
52
71
  "dist",
72
+ "src/components",
53
73
  "src/styles.css"
54
74
  ],
55
75
  "publishConfig": {
56
- "access": "public",
57
- "exports": {
58
- ".": {
59
- "types": "./dist/index.d.ts",
60
- "import": "./dist/index.js",
61
- "default": "./dist/index.js"
62
- },
63
- "./client": {
64
- "types": "./dist/client.d.ts",
65
- "import": "./dist/client.js",
66
- "default": "./dist/client.js"
67
- },
68
- "./styles.css": {
69
- "default": "./src/styles.css"
70
- },
71
- "./i18n": {
72
- "types": "./dist/i18n/index.d.ts",
73
- "import": "./dist/i18n/index.js",
74
- "default": "./dist/i18n/index.js"
75
- },
76
- "./i18n/en": {
77
- "types": "./dist/i18n/en.d.ts",
78
- "import": "./dist/i18n/en.js",
79
- "default": "./dist/i18n/en.js"
80
- },
81
- "./i18n/ro": {
82
- "types": "./dist/i18n/ro.d.ts",
83
- "import": "./dist/i18n/ro.js",
84
- "default": "./dist/i18n/ro.js"
85
- },
86
- "./components/workflow-run-detail-page": {
87
- "types": "./dist/components/workflow-run-detail-page.d.ts",
88
- "import": "./dist/components/workflow-run-detail-page.js",
89
- "default": "./dist/components/workflow-run-detail-page.js"
90
- },
91
- "./components/workflow-runs-page": {
92
- "types": "./dist/components/workflow-runs-page.d.ts",
93
- "import": "./dist/components/workflow-runs-page.js",
94
- "default": "./dist/components/workflow-runs-page.js"
95
- }
96
- },
97
- "main": "./dist/index.js",
98
- "types": "./dist/index.d.ts"
99
- }
100
- }
76
+ "access": "public"
77
+ },
78
+ "scripts": {
79
+ "build": "tsc -p tsconfig.build.json",
80
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
81
+ "typecheck": "tsc --noEmit",
82
+ "lint": "biome check src/",
83
+ "test": "vitest run --passWithNoTests"
84
+ },
85
+ "main": "./dist/index.js",
86
+ "types": "./dist/index.d.ts"
87
+ }
@@ -0,0 +1,182 @@
1
+ "use client"
2
+
3
+ import { Badge } from "@voyantjs/ui/components/badge"
4
+ import { AlertCircle, CheckCircle2, Clock, Copy, RefreshCw, XCircle } from "lucide-react"
5
+ import { useMemo, useState } from "react"
6
+
7
+ import { useWorkflowRunsUiMessagesOrDefault, type WorkflowRunsUiMessages } from "../i18n/index.js"
8
+ import type { WorkflowRun, WorkflowRunStep, WorkflowRunStepStatus } from "../types.js"
9
+
10
+ export function StatusIcon({ status }: { status: WorkflowRun["status"] }) {
11
+ switch (status) {
12
+ case "succeeded":
13
+ return <CheckCircle2 className="h-4 w-4 text-emerald-500" />
14
+ case "failed":
15
+ return <XCircle className="h-4 w-4 text-destructive" />
16
+ case "cancelled":
17
+ return <AlertCircle className="h-4 w-4 text-amber-500" />
18
+ case "running":
19
+ return <Clock className="h-4 w-4 animate-pulse text-blue-500" />
20
+ }
21
+ }
22
+
23
+ export function StepStatusIcon({ status }: { status: WorkflowRunStepStatus }) {
24
+ switch (status) {
25
+ case "succeeded":
26
+ return <CheckCircle2 className="h-4 w-4 text-emerald-500" />
27
+ case "failed":
28
+ return <XCircle className="h-4 w-4 text-destructive" />
29
+ case "running":
30
+ return <Clock className="h-4 w-4 animate-pulse text-blue-500" />
31
+ case "skipped":
32
+ return <AlertCircle className="h-4 w-4 text-muted-foreground" />
33
+ case "compensated":
34
+ return <RefreshCw className="h-4 w-4 text-amber-500" />
35
+ }
36
+ }
37
+
38
+ export function StatusBadge({
39
+ status,
40
+ messages,
41
+ }: {
42
+ status: WorkflowRun["status"]
43
+ messages: WorkflowRunsUiMessages
44
+ }) {
45
+ useWorkflowRunsUiMessagesOrDefault()
46
+ const className = {
47
+ succeeded: "border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300", // i18n-literal-ok: CSS classes
48
+ failed: "border-destructive/40 bg-destructive/10 text-destructive", // i18n-literal-ok: CSS classes
49
+ cancelled: "border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-300", // i18n-literal-ok: CSS classes
50
+ running: "border-blue-500/40 bg-blue-500/10 text-blue-600 dark:text-blue-300", // i18n-literal-ok: CSS classes
51
+ }[status]
52
+ return (
53
+ <Badge variant="outline" className={`text-[11px] ${className}`}>
54
+ {messages.status[status]}
55
+ </Badge>
56
+ )
57
+ }
58
+
59
+ export function TagChip({ tag }: { tag: string }) {
60
+ const colonIdx = tag.indexOf(":")
61
+ if (colonIdx < 0) {
62
+ return (
63
+ <Badge variant="outline" className="font-mono text-[10px]">
64
+ {tag}
65
+ </Badge>
66
+ )
67
+ }
68
+ const key = tag.slice(0, colonIdx)
69
+ const value = tag.slice(colonIdx + 1)
70
+ return (
71
+ <Badge variant="outline" className="gap-1 font-mono text-[10px]" title={tag}>
72
+ <span className="text-muted-foreground">{key}</span>
73
+ <span className="max-w-[18ch] truncate">{value}</span>
74
+ </Badge>
75
+ )
76
+ }
77
+
78
+ export function CopyableId({
79
+ id,
80
+ copiedLabel,
81
+ className,
82
+ }: {
83
+ id: string
84
+ copiedLabel: string
85
+ className?: string
86
+ }) {
87
+ const [copied, setCopied] = useState(false)
88
+ const onCopy = async () => {
89
+ try {
90
+ await navigator.clipboard.writeText(id)
91
+ setCopied(true)
92
+ setTimeout(() => setCopied(false), 1500)
93
+ } catch {
94
+ /* clipboard blocked */
95
+ }
96
+ }
97
+ return (
98
+ <button
99
+ type="button"
100
+ onClick={onCopy}
101
+ className={`group inline-flex items-center gap-1.5 rounded border bg-muted/40 px-2 py-1 font-mono text-[11px] hover:bg-muted ${
102
+ className ?? ""
103
+ }`}
104
+ title={id}
105
+ >
106
+ <span className="max-w-[16ch] truncate">{id}</span>
107
+ <Copy className="h-3 w-3 opacity-60 group-hover:opacity-100" />
108
+ {copied ? <span className="text-emerald-500 text-[10px]">{copiedLabel}</span> : null}
109
+ </button>
110
+ )
111
+ }
112
+
113
+ export function PayloadBlock({
114
+ title,
115
+ value,
116
+ messages,
117
+ hideTitle = false,
118
+ }: {
119
+ title: string
120
+ value: Record<string, unknown>
121
+ messages: WorkflowRunsUiMessages
122
+ hideTitle?: boolean
123
+ }) {
124
+ const [copied, setCopied] = useState(false)
125
+ const json = useMemo(() => JSON.stringify(value, null, 2), [value])
126
+
127
+ const onCopy = async () => {
128
+ try {
129
+ await navigator.clipboard.writeText(json)
130
+ setCopied(true)
131
+ setTimeout(() => setCopied(false), 1500)
132
+ } catch {
133
+ /* clipboard blocked */
134
+ }
135
+ }
136
+
137
+ return (
138
+ <div className="space-y-1">
139
+ {hideTitle ? null : (
140
+ <div className="flex items-center justify-between gap-2">
141
+ <span className="text-muted-foreground text-xs">{title}</span>
142
+ <button
143
+ type="button"
144
+ onClick={onCopy}
145
+ className="flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground"
146
+ >
147
+ <Copy className="h-3 w-3" />
148
+ {copied ? messages.detail.copied : messages.detail.copy}
149
+ </button>
150
+ </div>
151
+ )}
152
+ <pre className="max-h-[24rem] overflow-auto rounded bg-muted/40 p-3 font-mono text-[11px] leading-relaxed">
153
+ {json}
154
+ </pre>
155
+ </div>
156
+ )
157
+ }
158
+
159
+ export function formatRelative(iso: string, messages: WorkflowRunsUiMessages): string {
160
+ const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000)
161
+ if (Math.abs(seconds) < 5) return messages.format.relativeNow
162
+ const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" })
163
+ if (Math.abs(seconds) < 60) return formatter.format(-seconds, "second")
164
+ const minutes = Math.round(seconds / 60)
165
+ if (Math.abs(minutes) < 60) return formatter.format(-minutes, "minute")
166
+ const hours = Math.round(minutes / 60)
167
+ if (Math.abs(hours) < 24) return formatter.format(-hours, "hour")
168
+ const days = Math.round(hours / 24)
169
+ return formatter.format(-days, "day")
170
+ }
171
+
172
+ export function formatDuration(ms: number): string {
173
+ if (ms < 1000) return `${ms}ms`
174
+ if (ms < 60_000) return `${(ms / 1000).toFixed(2)}s`
175
+ const minutes = Math.floor(ms / 60_000)
176
+ const seconds = Math.round((ms % 60_000) / 1000)
177
+ return `${minutes}m ${seconds}s`
178
+ }
179
+
180
+ export function runHasLiveStatus(run: WorkflowRun | WorkflowRunStep): boolean {
181
+ return run.status === "running"
182
+ }
@@ -0,0 +1,160 @@
1
+ "use client"
2
+
3
+ import { Button } from "@voyantjs/ui/components/button"
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components/card"
5
+ import { AlertTriangle, Play, RotateCw } from "lucide-react"
6
+ import { useCallback, useState } from "react"
7
+
8
+ import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js"
9
+ import type { WorkflowRun, WorkflowRunActionError, WorkflowRunsApi } from "../types.js"
10
+ import { runHasLiveStatus } from "./common.js"
11
+
12
+ export function WorkflowRunActionsCard({
13
+ api,
14
+ run,
15
+ onOpenRun,
16
+ }: {
17
+ api: WorkflowRunsApi
18
+ run: WorkflowRun
19
+ onOpenRun?: (id: string) => void
20
+ }) {
21
+ const messages = useWorkflowRunsUiMessagesOrDefault()
22
+ const [busy, setBusy] = useState<"rerun" | "resume" | null>(null)
23
+ const [feedback, setFeedback] = useState<{ kind: "info" | "error"; message: string } | null>(null)
24
+ const [confirmOpen, setConfirmOpen] = useState(false)
25
+
26
+ const explainError = useCallback(
27
+ (err: WorkflowRunActionError): string => {
28
+ if (err.error === "runner_not_registered") return messages.actions.runnerMissing
29
+ if (err.error === "rerun_blocked") return err.detail ?? messages.actions.rerunBlocked
30
+ if (err.error === "incomplete_prior_step") {
31
+ return err.detail ?? messages.actions.incompletePriorStep
32
+ }
33
+ return err.detail ?? err.error ?? messages.actions.actionFailed
34
+ },
35
+ [messages],
36
+ )
37
+
38
+ const doRerun = async (confirm: boolean): Promise<void> => {
39
+ setBusy("rerun")
40
+ setFeedback(null)
41
+ try {
42
+ const result = await api.rerunRun(run.id, { confirm })
43
+ if (result.ok) {
44
+ setFeedback({ kind: "info", message: messages.actions.rerunStarted })
45
+ onOpenRun?.(result.data.runId)
46
+ setConfirmOpen(false)
47
+ } else if (result.error.error === "confirmation_required") {
48
+ setConfirmOpen(true)
49
+ } else {
50
+ setFeedback({ kind: "error", message: explainError(result.error) })
51
+ }
52
+ } finally {
53
+ setBusy(null)
54
+ }
55
+ }
56
+
57
+ const doResume = async (): Promise<void> => {
58
+ setBusy("resume")
59
+ setFeedback(null)
60
+ try {
61
+ const result = await api.resumeRun(run.id)
62
+ if (result.ok) {
63
+ setFeedback({
64
+ kind: "info",
65
+ message: messages.actions.resumeStarted(result.data.resumeFromStep ?? ""),
66
+ })
67
+ onOpenRun?.(result.data.runId)
68
+ } else {
69
+ setFeedback({ kind: "error", message: explainError(result.error) })
70
+ }
71
+ } finally {
72
+ setBusy(null)
73
+ }
74
+ }
75
+
76
+ const canResume = run.status === "failed"
77
+ const isRunning = runHasLiveStatus(run)
78
+
79
+ return (
80
+ <Card>
81
+ <CardContent className="flex flex-wrap items-center gap-2 pt-4">
82
+ <Button
83
+ variant="default"
84
+ size="sm"
85
+ onClick={() => void doRerun(false)}
86
+ disabled={busy !== null || isRunning}
87
+ title={isRunning ? messages.actions.waitForCompletion : messages.actions.rerunDescription}
88
+ >
89
+ <RotateCw className={`mr-1.5 h-3.5 w-3.5 ${busy === "rerun" ? "animate-spin" : ""}`} />
90
+ {busy === "rerun" ? messages.actions.rerunBusy : messages.actions.rerun}
91
+ </Button>
92
+ <Button
93
+ variant="secondary"
94
+ size="sm"
95
+ onClick={() => void doResume()}
96
+ disabled={busy !== null || !canResume}
97
+ title={
98
+ canResume ? messages.actions.resumeDescription : messages.actions.resumeUnavailable
99
+ }
100
+ >
101
+ <Play className={`mr-1.5 h-3.5 w-3.5 ${busy === "resume" ? "animate-pulse" : ""}`} />
102
+ {busy === "resume" ? messages.actions.resumeBusy : messages.actions.resume}
103
+ </Button>
104
+ {feedback ? (
105
+ <span
106
+ className={`ml-auto text-xs ${
107
+ feedback.kind === "error" ? "text-destructive" : "text-muted-foreground"
108
+ }`}
109
+ >
110
+ {feedback.message}
111
+ </span>
112
+ ) : null}
113
+ </CardContent>
114
+ {confirmOpen ? (
115
+ <ConfirmRerunDialog
116
+ onCancel={() => setConfirmOpen(false)}
117
+ onConfirm={() => void doRerun(true)}
118
+ busy={busy === "rerun"}
119
+ />
120
+ ) : null}
121
+ </Card>
122
+ )
123
+ }
124
+
125
+ function ConfirmRerunDialog({
126
+ onCancel,
127
+ onConfirm,
128
+ busy,
129
+ }: {
130
+ onCancel: () => void
131
+ onConfirm: () => void
132
+ busy: boolean
133
+ }) {
134
+ const messages = useWorkflowRunsUiMessagesOrDefault()
135
+ return (
136
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
137
+ <Card className="w-full max-w-md border-amber-500/40">
138
+ <CardHeader className="pb-3">
139
+ <CardTitle className="flex items-center gap-2 text-base">
140
+ <AlertTriangle className="h-4 w-4 text-amber-500" />
141
+ {messages.actions.confirmTitle}
142
+ </CardTitle>
143
+ </CardHeader>
144
+ <CardContent className="space-y-3 text-sm">
145
+ <p>{messages.actions.confirmBody}</p>
146
+ <p className="text-muted-foreground text-xs">{messages.actions.confirmTip}</p>
147
+ <div className="flex justify-end gap-2 pt-2">
148
+ <Button variant="ghost" size="sm" onClick={onCancel} disabled={busy}>
149
+ {messages.actions.cancel}
150
+ </Button>
151
+ <Button variant="default" size="sm" onClick={onConfirm} disabled={busy}>
152
+ <RotateCw className={`mr-1.5 h-3.5 w-3.5 ${busy ? "animate-spin" : ""}`} />
153
+ {messages.actions.rerunAnyway}
154
+ </Button>
155
+ </div>
156
+ </CardContent>
157
+ </Card>
158
+ </div>
159
+ )
160
+ }