@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.
- package/LICENSE +201 -0
- package/README.md +2 -3
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -1
- 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 +9 -1
- package/dist/components/workflow-run-detail-page.d.ts.map +1 -1
- package/dist/components/workflow-run-detail-page.js +96 -1
- 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 +11 -1
- package/dist/components/workflow-runs-page.d.ts.map +1 -1
- package/dist/components/workflow-runs-page.js +132 -1
- package/dist/i18n/en.d.ts +3 -2
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +96 -2
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +3 -2
- package/dist/i18n/messages.d.ts +86 -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 +3 -2
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +98 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +55 -68
- 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/styles.css +5 -3
package/dist/i18n/ro.js
CHANGED
|
@@ -1,2 +1,98 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
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
|
|
2
|
-
export {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
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
|
|
2
|
-
export {
|
|
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";
|
package/dist/types.d.ts
ADDED
|
@@ -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.
|
|
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
|
-
".":
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"./
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
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/
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
}
|