@revenexx/studio-integrations 1.0.0 → 1.1.0
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/CHANGELOG.md +217 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +2 -6
- package/dist/runtime/components/Integrations/NodeInspector.vue +40 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +9 -6
- package/dist/runtime/components/Integrations/RunsTable.vue +2 -2
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +27 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +9 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +38 -3
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -6
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +9 -4
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +9 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +27 -18
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +30 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +70 -29
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +7 -15
- package/dist/runtime/composables/useIntegrationsSchedules.js +80 -35
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +8 -1
- package/dist/runtime/data/loopBody.d.ts +129 -0
- package/dist/runtime/data/loopBody.js +76 -0
- package/dist/runtime/i18n/messages/de.json +42 -30
- package/dist/runtime/i18n/messages/en.json +12 -0
- package/dist/runtime/pages/integrations/index.vue +16 -17
- package/dist/runtime/pages/integrations/runs.vue +22 -10
- package/dist/runtime/pages/integrations/schedules.vue +64 -7
- package/dist/runtime/types/integrations.d.ts +39 -0
- package/dist/runtime/utils/statusStyle.d.ts +52 -0
- package/dist/runtime/utils/statusStyle.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { NodeManifest, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which nodes repeat, and which edges are walked once per element (PO-277).
|
|
4
|
+
*
|
|
5
|
+
* The iterator draws its two outputs like any other pair of ports, so nothing
|
|
6
|
+
* on the canvas said that everything hanging off `item` runs once per element
|
|
7
|
+
* while everything off `done` runs once, afterwards. This module computes the
|
|
8
|
+
* repeating part; the canvases mark it (`specs/editor-loop-body.md`).
|
|
9
|
+
*
|
|
10
|
+
* **The set is the engine's, not a tidier one.** `getLoopBodyNodes`
|
|
11
|
+
* (`worker/src/dagUtils.ts`) walks forward from the `item` port and follows
|
|
12
|
+
* EVERY outgoing edge it meets — an error route, a nested iterator's `done`,
|
|
13
|
+
* an edge the author meant as "and afterwards, do this". There is no stop
|
|
14
|
+
* condition, because the loop body has no end anywhere in the workflow. The
|
|
15
|
+
* walk below is that walk, deliberately: a UI that computed the body the
|
|
16
|
+
* author *meant* would draw nothing on the node that surprises them, which is
|
|
17
|
+
* the one case this is for.
|
|
18
|
+
*
|
|
19
|
+
* **Nesting needs no special case.** The engine narrows an inner iterator's
|
|
20
|
+
* body to its outer one (`getLoopBodyNodes(inner, edges, loopBodyIds)`), but a
|
|
21
|
+
* body is closed under reachability and the inner iterator is in the outer
|
|
22
|
+
* body — so everything reachable from the inner `item` is already in the outer
|
|
23
|
+
* one, and the narrowing never removes anything. A node inside two loops is
|
|
24
|
+
* therefore simply a member of both bodies, which is what `depth` counts.
|
|
25
|
+
*/
|
|
26
|
+
/** The iterator's repeating output — the port the body hangs off. */
|
|
27
|
+
export declare const ITEM_PORT = "item";
|
|
28
|
+
/** The iterator's post-loop output — everything off it runs once, at the end. */
|
|
29
|
+
export declare const DONE_PORT = "done";
|
|
30
|
+
/**
|
|
31
|
+
* Nodes known to iterate while no manifest says so.
|
|
32
|
+
*
|
|
33
|
+
* The worker learns which nodes iterate at bundle-build time by duck-typing the
|
|
34
|
+
* implementation (`builder/src/generateEntry.ts` → `MANIFEST.iterationNodeIds`),
|
|
35
|
+
* and nothing of that reaches the studio: `GET /nodes` publishes the manifest,
|
|
36
|
+
* and no field in it says "this node repeats what is wired to it". Until one
|
|
37
|
+
* does — {@link NodeManifest.iteration}, read first by {@link manifestIterates}
|
|
38
|
+
* — the editor has to know the published iterator by name. One slug, in one
|
|
39
|
+
* place, rather than the same string spelled out at every call site.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ITERATOR_SLUGS: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Whether a manifest describes a node that runs its `item` side per element.
|
|
44
|
+
*
|
|
45
|
+
* A manifest that carries the field at all is believed either way — `false`
|
|
46
|
+
* defeats the slug fallback. Otherwise the day the service starts publishing it,
|
|
47
|
+
* a published `iteration: false` on the very slug below would be overruled by
|
|
48
|
+
* this file, which is the one answer the field exists to give.
|
|
49
|
+
*/
|
|
50
|
+
export declare function manifestIterates(manifest: NodeManifest | null | undefined): boolean;
|
|
51
|
+
/** The blob's iterator nodes, in blob order. */
|
|
52
|
+
export declare function iteratorNodeIds(nodes: readonly WorkflowNode[]): string[];
|
|
53
|
+
/** One iterator's body: what repeats, and where it overlaps the `done` side. */
|
|
54
|
+
export interface LoopBody {
|
|
55
|
+
/** The iterator whose `item` port this body hangs off. */
|
|
56
|
+
iteratorId: string;
|
|
57
|
+
/** Every node the engine would run once per element. */
|
|
58
|
+
nodeIds: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Body nodes that are ALSO reachable from `done` — the author wired the body
|
|
61
|
+
* into the part meant to run after the loop, and the engine repeats it.
|
|
62
|
+
*
|
|
63
|
+
* Only the join case is provable. Where `done` is wired nowhere, a node
|
|
64
|
+
* appended to the body is indistinguishable from a node meant to be in it, so
|
|
65
|
+
* nothing here reports it (`specs/editor-loop-body.md` § Gaps). The mark on
|
|
66
|
+
* the card is what covers that case: it says "this repeats" wherever it is
|
|
67
|
+
* true, whether or not anything can tell it was a mistake.
|
|
68
|
+
*/
|
|
69
|
+
joinedWithDone: Set<string>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One body per iterator id, in the order given.
|
|
73
|
+
*
|
|
74
|
+
* Pure on purpose — the catalogue lookup that decides what an iterator IS lives
|
|
75
|
+
* in {@link iteratorNodeIds}, so the walk can be tested against a graph alone.
|
|
76
|
+
*/
|
|
77
|
+
export declare function computeLoopBodies(iteratorIds: readonly string[], edges: readonly WorkflowEdge[]): LoopBody[];
|
|
78
|
+
/** What one node card has to say about repeating. */
|
|
79
|
+
export interface LoopBodyMark {
|
|
80
|
+
/**
|
|
81
|
+
* How many bodies this node is in — 1 for a plain loop, 2 inside a nested
|
|
82
|
+
* one. The card shows the number from 2 up: "repeats" is the news at depth 1,
|
|
83
|
+
* "repeats per element of both" is the news above it.
|
|
84
|
+
*/
|
|
85
|
+
depth: number;
|
|
86
|
+
/** The iterators, outermost first (a larger body encloses a smaller one). */
|
|
87
|
+
iteratorIds: string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Marks for a whole canvas: which nodes repeat, and the bodies behind them.
|
|
91
|
+
*
|
|
92
|
+
* **Nodes only, deliberately — the connections carry nothing.** Tinting the
|
|
93
|
+
* body's edges was the obvious other half, and it is the one thing this could
|
|
94
|
+
* not do: `specs/editor-output-paths.md` AC-2 promises that the lines carry no
|
|
95
|
+
* meaning and stay neutral, and colour and dashing on an edge are both reserved
|
|
96
|
+
* for a run that actually happened (`docs/editor-path-colors.md`). A body drawn
|
|
97
|
+
* in a colour of its own would take that promise back for every workflow with a
|
|
98
|
+
* loop in it.
|
|
99
|
+
*/
|
|
100
|
+
export interface LoopBodyMarks {
|
|
101
|
+
byNode: Map<string, LoopBodyMark>;
|
|
102
|
+
bodies: LoopBody[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The whole computation for one blob, ready to hand to a canvas.
|
|
106
|
+
*
|
|
107
|
+
* Called by all three canvases (editor, run replay, history preview) on every
|
|
108
|
+
* sync: the marks depend on the edges, so anything less would let a rewired
|
|
109
|
+
* body keep an old mark. The cost is one pass over the edges to build the
|
|
110
|
+
* adjacency map, then two breadth-first walks per iterator over it — and a
|
|
111
|
+
* workflow with no iterator in it costs one filter over the nodes.
|
|
112
|
+
*/
|
|
113
|
+
export declare function loopBodyMarks(nodes: readonly WorkflowNode[], edges: readonly WorkflowEdge[]): LoopBodyMarks;
|
|
114
|
+
/** What one canvas hands a node card about repetition. */
|
|
115
|
+
export interface NodeLoopData {
|
|
116
|
+
/** The iterators whose body this node is in, outermost first, by label. */
|
|
117
|
+
inside?: string[];
|
|
118
|
+
/** Set on an iterator itself: how many nodes hang off its `item` port. */
|
|
119
|
+
bodySize?: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One call per canvas sync: node id → what its card says about repeating.
|
|
123
|
+
*
|
|
124
|
+
* Names rather than ids, because the card shows the sentence — and the same
|
|
125
|
+
* three steps a banner uses to name a node ({@link nodeDisplayLabel}), so a
|
|
126
|
+
* loop the user renamed is named that everywhere. Absent from the map means
|
|
127
|
+
* nothing to say, which is the common case and costs the card nothing.
|
|
128
|
+
*/
|
|
129
|
+
export declare function nodeLoopData(nodes: readonly WorkflowNode[], edges: readonly WorkflowEdge[]): Map<string, NodeLoopData>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { nodeDisplayLabel } from "../utils/validationFindings.js";
|
|
2
|
+
import { findManifest } from "./integrationNodeCatalog.js";
|
|
3
|
+
export const ITEM_PORT = "item";
|
|
4
|
+
export const DONE_PORT = "done";
|
|
5
|
+
export const ITERATOR_SLUGS = ["revenexx:iterator"];
|
|
6
|
+
export function manifestIterates(manifest) {
|
|
7
|
+
if (!manifest) return false;
|
|
8
|
+
if (manifest.iteration !== void 0) return manifest.iteration;
|
|
9
|
+
return ITERATOR_SLUGS.includes(manifest.slug);
|
|
10
|
+
}
|
|
11
|
+
export function iteratorNodeIds(nodes) {
|
|
12
|
+
return nodes.filter((n) => manifestIterates(findManifest(n.nodeSlug, n.nodeVersion))).map((n) => n.id);
|
|
13
|
+
}
|
|
14
|
+
function successors(edges) {
|
|
15
|
+
const out = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const edge of edges) {
|
|
17
|
+
const list = out.get(edge.from.nodeId);
|
|
18
|
+
if (list) list.push(edge.to.nodeId);
|
|
19
|
+
else out.set(edge.from.nodeId, [edge.to.nodeId]);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function reachableFromPort(fromNodeId, output, edges, next = successors(edges)) {
|
|
24
|
+
const visited = /* @__PURE__ */ new Set();
|
|
25
|
+
const queue = [];
|
|
26
|
+
for (const edge of edges) {
|
|
27
|
+
if (edge.from.nodeId === fromNodeId && edge.from.output === output) queue.push(edge.to.nodeId);
|
|
28
|
+
}
|
|
29
|
+
for (let head = 0; head < queue.length; head++) {
|
|
30
|
+
const nodeId = queue[head];
|
|
31
|
+
if (visited.has(nodeId)) continue;
|
|
32
|
+
visited.add(nodeId);
|
|
33
|
+
for (const target of next.get(nodeId) ?? []) {
|
|
34
|
+
if (!visited.has(target)) queue.push(target);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return visited;
|
|
38
|
+
}
|
|
39
|
+
export function computeLoopBodies(iteratorIds, edges) {
|
|
40
|
+
const next = successors(edges);
|
|
41
|
+
return iteratorIds.map((iteratorId) => {
|
|
42
|
+
const nodeIds = reachableFromPort(iteratorId, ITEM_PORT, edges, next);
|
|
43
|
+
const afterLoop = reachableFromPort(iteratorId, DONE_PORT, edges, next);
|
|
44
|
+
const joinedWithDone = new Set([...nodeIds].filter((id) => afterLoop.has(id)));
|
|
45
|
+
return { iteratorId, nodeIds, joinedWithDone };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function loopBodyMarks(nodes, edges) {
|
|
49
|
+
const bodies = computeLoopBodies(iteratorNodeIds(nodes), edges);
|
|
50
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
51
|
+
const outermostFirst = [...bodies].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
|
|
52
|
+
for (const body of outermostFirst) {
|
|
53
|
+
for (const nodeId of body.nodeIds) {
|
|
54
|
+
const mark = byNode.get(nodeId) ?? { depth: 0, iteratorIds: [] };
|
|
55
|
+
mark.depth += 1;
|
|
56
|
+
mark.iteratorIds.push(body.iteratorId);
|
|
57
|
+
byNode.set(nodeId, mark);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { byNode, bodies };
|
|
61
|
+
}
|
|
62
|
+
export function nodeLoopData(nodes, edges) {
|
|
63
|
+
const marks = loopBodyMarks(nodes, edges);
|
|
64
|
+
if (!marks.bodies.length) return /* @__PURE__ */ new Map();
|
|
65
|
+
const labelOf = new Map(nodes.map((n) => [n.id, nodeDisplayLabel(n)]));
|
|
66
|
+
const out = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const [nodeId, mark] of marks.byNode) {
|
|
68
|
+
out.set(nodeId, { inside: mark.iteratorIds.map((id) => labelOf.get(id) ?? id) });
|
|
69
|
+
}
|
|
70
|
+
for (const body of marks.bodies) {
|
|
71
|
+
const entry = out.get(body.iteratorId) ?? {};
|
|
72
|
+
entry.bodySize = body.nodeIds.size;
|
|
73
|
+
out.set(body.iteratorId, entry);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
"integration.canvas.node.action.duplicateAria": "Node duplizieren",
|
|
72
72
|
"integration.canvas.node.badge.invalidConfig": "Konfiguration unvollständig — {n} Einstellung muss noch behoben werden: {fields} | Konfiguration unvollständig — {n} Einstellungen müssen noch behoben werden: {fields}",
|
|
73
73
|
"integration.canvas.node.badge.noBuiltCode": "Version {version} ist nicht einsatzbereit — dieser Schritt lässt sich nicht testen",
|
|
74
|
+
"integration.canvas.node.badge.repeats": "Wiederholt sich — läuft einmal pro Element von {iterators} | Wiederholt sich — läuft einmal pro Element jeder der {n} Schleifen, in denen er liegt: {iterators}",
|
|
75
|
+
"integration.canvas.node.badge.loopBody": "Schleife — am Ausgang „Element“ hängt noch nichts, es wiederholt sich also nichts | Schleife — wiederholt den einen Schritt am Ausgang „Element“, einmal pro Element | Schleife — wiederholt die {n} Schritte am Ausgang „Element“, einmal pro Element",
|
|
74
76
|
"integration.canvas.node.handle.input": "Eingang",
|
|
75
77
|
"integration.canvas.node.meta.versionTitle": "Version {version}",
|
|
76
78
|
"integration.canvas.node.note.heading": "Notiz",
|
|
@@ -259,7 +261,7 @@
|
|
|
259
261
|
"integration.dashboard.action.newWorkflow": "Workflow anlegen",
|
|
260
262
|
"integration.dashboard.attention.failed": "Fehlgeschlagen.",
|
|
261
263
|
"integration.dashboard.attention.healthy": "Alle Workflows in Ordnung.",
|
|
262
|
-
"integration.dashboard.attention.missedRuns": "{n}
|
|
264
|
+
"integration.dashboard.attention.missedRuns": "{n} verpasster Run | {n} verpasste Runs",
|
|
263
265
|
"integration.dashboard.attention.missedRuns.hint": "Trigger haben ausgelöst, während ihr Workflow nicht laufen konnte — prüfen und erneut ausführen.",
|
|
264
266
|
"integration.dashboard.attention.title": "Erfordert Aufmerksamkeit",
|
|
265
267
|
"integration.dashboard.head.title": "Dashboard · Integration Studio",
|
|
@@ -268,14 +270,14 @@
|
|
|
268
270
|
"integration.dashboard.kpi.activeWorkflows.label": "Aktive Workflows",
|
|
269
271
|
"integration.dashboard.kpi.activeWorkflows.sub": "{n} pausiert oder Entwurf | {n} pausiert oder Entwürfe",
|
|
270
272
|
"integration.dashboard.kpi.aria.group": "Kennzahlen",
|
|
271
|
-
"integration.dashboard.kpi.missedRuns.label": "Verpasste
|
|
272
|
-
"integration.dashboard.kpi.missedRuns.sub": "Trigger-Auslösungen ohne
|
|
273
|
+
"integration.dashboard.kpi.missedRuns.label": "Verpasste Runs",
|
|
274
|
+
"integration.dashboard.kpi.missedRuns.sub": "Trigger-Auslösungen ohne Run",
|
|
273
275
|
"integration.dashboard.kpi.runningNow.label": "Läuft gerade",
|
|
274
|
-
"integration.dashboard.kpi.runningNow.sub": "laufende
|
|
276
|
+
"integration.dashboard.kpi.runningNow.sub": "laufende Runs",
|
|
275
277
|
"integration.dashboard.kpi.scheduled.label": "Geplant",
|
|
276
278
|
"integration.dashboard.kpi.scheduled.sub": "aktive Zeitplan-Trigger",
|
|
277
|
-
"integration.dashboard.runs.empty": "Noch keine
|
|
278
|
-
"integration.dashboard.runs.title": "Letzte
|
|
279
|
+
"integration.dashboard.runs.empty": "Noch keine Runs.",
|
|
280
|
+
"integration.dashboard.runs.title": "Letzte Runs",
|
|
279
281
|
"integration.dashboard.surface.events.hint": "Live-Events der Plattform, die Workflows abonnieren.",
|
|
280
282
|
"integration.dashboard.surface.events.label": "Event-Stream",
|
|
281
283
|
"integration.dashboard.surface.io.hint": "Profile, die Katalog- und Entitätsdaten hinein- und hinausbewegen.",
|
|
@@ -298,12 +300,17 @@
|
|
|
298
300
|
"integration.deadLetters.panel.title": "Verpasste Runs",
|
|
299
301
|
"integration.deadLetters.reason.fallback": "Kein Grund erfasst.",
|
|
300
302
|
"integration.deadLetters.state.empty": "Keine verpassten Runs. Jeder Trigger hat einen Run ausgelöst.",
|
|
303
|
+
"integration.deadLetters.status.discarded": "verworfen",
|
|
304
|
+
"integration.deadLetters.status.pending": "wartet",
|
|
305
|
+
"integration.deadLetters.status.replayed": "wiederholt",
|
|
306
|
+
"integration.deadLetters.status.unknown": "unbekannt ({status})",
|
|
301
307
|
"integration.deadLetters.tabs.missed.label": "Verpasste Runs",
|
|
302
308
|
"integration.deadLetters.toast.discard.done": "Verworfen",
|
|
303
309
|
"integration.deadLetters.toast.discard.error": "Verwerfen fehlgeschlagen.",
|
|
304
310
|
"integration.deadLetters.toast.replay.done": "Wiederholt",
|
|
305
311
|
"integration.deadLetters.toast.replay.error": "Wiederholen fehlgeschlagen.",
|
|
306
312
|
"integration.deadLetters.toast.replay.started": "Wiederholt — Run #{id} gestartet",
|
|
313
|
+
"integration.deadLetters.workflow.fallback": "Workflow #{id}",
|
|
307
314
|
"integration.editor.action.activate.title": "Workflow aktivieren",
|
|
308
315
|
"integration.editor.action.activate.title.invalid": "{n} Node-Einstellung vor der Aktivierung beheben | {n} Node-Einstellungen vor der Aktivierung beheben",
|
|
309
316
|
"integration.editor.action.backToWorkflow": "Zurück zum Workflow",
|
|
@@ -326,12 +333,12 @@
|
|
|
326
333
|
"integration.editor.breadcrumb.workflows": "Workflows",
|
|
327
334
|
"integration.editor.build.chip.building": "Build wird erstellt…",
|
|
328
335
|
"integration.editor.build.chip.queued": "Build in Warteschlange…",
|
|
329
|
-
"integration.editor.build.notice.building": "Der Workflow-Build läuft noch —
|
|
336
|
+
"integration.editor.build.notice.building": "Der Workflow-Build läuft noch — Runs sind erst möglich, wenn er fertig ist.",
|
|
330
337
|
"integration.editor.build.notice.failed": "Der Workflow-Build ist fehlgeschlagen. Speichern Sie den Workflow erneut, um den Build neu zu starten.",
|
|
331
338
|
"integration.editor.build.notice.failedWithError": "Der Workflow-Build ist fehlgeschlagen: {error} Speichern Sie den Workflow erneut, um den Build neu zu starten.",
|
|
332
|
-
"integration.editor.build.notice.pending": "Der Build des Workflows ist in der Warteschlange —
|
|
333
|
-
"integration.editor.build.toast.failed": "Der Workflow-Build ist fehlgeschlagen —
|
|
334
|
-
"integration.editor.build.toast.timeout": "Der Build dauert ungewöhnlich lange — versuchen Sie
|
|
339
|
+
"integration.editor.build.notice.pending": "Der Build des Workflows ist in der Warteschlange — Runs sind erst möglich, wenn er fertig ist.",
|
|
340
|
+
"integration.editor.build.toast.failed": "Der Workflow-Build ist fehlgeschlagen — der Run wurde nicht gestartet.",
|
|
341
|
+
"integration.editor.build.toast.timeout": "Der Build dauert ungewöhnlich lange — versuchen Sie den Run erneut, sobald er bereit ist.",
|
|
335
342
|
"integration.editor.build.tooltip.failed": "Der Workflow-Build ist fehlgeschlagen.",
|
|
336
343
|
"integration.editor.clipboard.error.empty": "Die Zwischenablage enthält keine Nodes.",
|
|
337
344
|
"integration.editor.clipboard.error.read": "Zwischenablage konnte nicht gelesen werden.",
|
|
@@ -348,13 +355,13 @@
|
|
|
348
355
|
"integration.editor.emptyState.hint": "Ziehen Sie einen Trigger und eine Aktion aus dem Panel links.",
|
|
349
356
|
"integration.editor.emptyState.title": "Leere Arbeitsfläche",
|
|
350
357
|
"integration.editor.event.toast.noneReceived": "Noch kein {topic}-Event empfangen.",
|
|
351
|
-
"integration.editor.executionMode.async.hint": "
|
|
358
|
+
"integration.editor.executionMode.async.hint": "Runs werden immer in die Warteschlange gestellt; der Trigger antwortet sofort.",
|
|
352
359
|
"integration.editor.executionMode.async.label": "Nur asynchron",
|
|
353
360
|
"integration.editor.executionMode.async.short": "Asynchron",
|
|
354
|
-
"integration.editor.executionMode.callerDecides.hint": "Der Aufrufer entscheidet pro
|
|
361
|
+
"integration.editor.executionMode.callerDecides.hint": "Der Aufrufer entscheidet pro Run zwischen synchron und asynchron.",
|
|
355
362
|
"integration.editor.executionMode.callerDecides.label": "Aufrufer entscheidet",
|
|
356
363
|
"integration.editor.executionMode.error.generic": "Der Ausführungsmodus konnte nicht aktualisiert werden.",
|
|
357
|
-
"integration.editor.executionMode.sync.hint": "
|
|
364
|
+
"integration.editor.executionMode.sync.hint": "Runs blockieren, bis sie abgeschlossen sind, und liefern das Ergebnis direkt zurück.",
|
|
358
365
|
"integration.editor.executionMode.sync.label": "Nur synchron",
|
|
359
366
|
"integration.editor.executionMode.sync.short": "Synchron",
|
|
360
367
|
"integration.editor.group.defaultLabel": "Gruppe {n}",
|
|
@@ -387,15 +394,15 @@
|
|
|
387
394
|
"integration.editor.quickConnect.toast": "Node auswählen — er wird dort platziert und ist bereits verbunden.",
|
|
388
395
|
"integration.editor.reauth.action.fix": "in den Zugangsdaten beheben",
|
|
389
396
|
"integration.editor.reauth.banner": "{n} hier verwendeter Zugangsdaten-Eintrag muss neu verbunden werden | {n} hier verwendete Zugangsdaten-Einträge müssen neu verbunden werden",
|
|
390
|
-
"integration.editor.retry.error.generic": "
|
|
397
|
+
"integration.editor.retry.error.generic": "Der Run konnte nicht wiederholt werden.",
|
|
391
398
|
"integration.editor.retry.toast.requested": "Wiederholung angefordert",
|
|
392
399
|
"integration.editor.retry.toast.started": "Run #{id} gestartet",
|
|
393
400
|
"integration.editor.run.button": "Run",
|
|
394
|
-
"integration.editor.run.error.couldNotStart": "
|
|
395
|
-
"integration.editor.run.error.loadOpenRun": "
|
|
401
|
+
"integration.editor.run.error.couldNotStart": "Der Run konnte nicht gestartet werden.",
|
|
402
|
+
"integration.editor.run.error.loadOpenRun": "Der Run konnte nicht geladen werden.",
|
|
396
403
|
"integration.editor.run.error.refusedForSettings": "Kann nicht ausgeführt werden — {summary}{where}",
|
|
397
|
-
"integration.editor.run.error.syncFailed": "
|
|
398
|
-
"integration.editor.run.error.syncTimeout": "
|
|
404
|
+
"integration.editor.run.error.syncFailed": "Synchroner Run fehlgeschlagen",
|
|
405
|
+
"integration.editor.run.error.syncTimeout": "Synchroner Run: Zeitüberschreitung",
|
|
399
406
|
"integration.editor.run.loading": "Run wird geladen…",
|
|
400
407
|
"integration.editor.run.waitingForBuild": "Warten auf Build…",
|
|
401
408
|
"integration.editor.runPayload.action.latestEvent": "Letztes reales Event",
|
|
@@ -404,25 +411,25 @@
|
|
|
404
411
|
"integration.editor.runPayload.action.sampleEvent": "Beispiel-Event",
|
|
405
412
|
"integration.editor.runPayload.action.sampleEvent.titleAvailable": "Beispiel-Payload aus dem Katalog einfügen",
|
|
406
413
|
"integration.editor.runPayload.action.sampleEvent.titleUnavailable": "Der Event-Katalog enthält kein Beispiel für dieses Topic.",
|
|
407
|
-
"integration.editor.runPayload.dialog.description": "Startet
|
|
414
|
+
"integration.editor.runPayload.dialog.description": "Startet einen manuellen Run mit diesem Trigger-Payload. Das Ergebnis erscheint im Tab Runs.",
|
|
408
415
|
"integration.editor.runPayload.dialog.title": "Mit Payload ausführen",
|
|
409
416
|
"integration.editor.runPayload.prefill.label": "Vorbelegen mit",
|
|
410
417
|
"integration.editor.runView.details": "Details",
|
|
411
418
|
"integration.editor.runView.replay": "Replay",
|
|
412
419
|
"integration.editor.runView.timeline": "Zeitleiste",
|
|
413
|
-
"integration.editor.runs.empty": "Noch keine
|
|
414
|
-
"integration.editor.runs.error.load": "
|
|
415
|
-
"integration.editor.runs.pagination.count": "{n}
|
|
420
|
+
"integration.editor.runs.empty": "Noch keine Runs — im Editor auf Run klicken.",
|
|
421
|
+
"integration.editor.runs.error.load": "Runs konnten nicht geladen werden.",
|
|
422
|
+
"integration.editor.runs.pagination.count": "{n} Run | {n} Runs",
|
|
416
423
|
"integration.editor.runs.pagination.newer": "← Neuer",
|
|
417
424
|
"integration.editor.runs.pagination.older": "Älter →",
|
|
418
425
|
"integration.editor.runs.pagination.page": "Seite {page} / {lastPage}",
|
|
419
426
|
"integration.editor.save.error.generic": "Speichern fehlgeschlagen.",
|
|
420
|
-
"integration.editor.splice.error.terminal": "{name} beendet
|
|
427
|
+
"integration.editor.splice.error.terminal": "{name} beendet den Run und kann nicht in eine Verbindung eingefügt werden.",
|
|
421
428
|
"integration.editor.splice.toast": "Node aus der Palette auswählen — er wird in die Verbindung eingefügt.",
|
|
422
429
|
"integration.editor.state.loading": "Wird geladen…",
|
|
423
430
|
"integration.editor.status.unsavedChanges": "Nicht gespeicherte Änderungen",
|
|
424
431
|
"integration.editor.sticky.starterContent": "## Ich bin eine Notiz\n**Doppelklicken**, um mich zu bearbeiten. **Fett**, *kursiv*, `Code`, [Links](https://revenexx.com) und\n- Listen funktionieren",
|
|
425
|
-
"integration.editor.tab.deadLetters": "Verpasste
|
|
432
|
+
"integration.editor.tab.deadLetters": "Verpasste Runs",
|
|
426
433
|
"integration.editor.tab.editor": "Editor",
|
|
427
434
|
"integration.editor.tab.history": "Verlauf",
|
|
428
435
|
"integration.editor.tab.runs": "Runs",
|
|
@@ -435,7 +442,7 @@
|
|
|
435
442
|
"integration.editor.tool.fitView.tooltip": "Ansicht einpassen",
|
|
436
443
|
"integration.editor.tool.hand": "Hand (Verschieben)",
|
|
437
444
|
"integration.editor.tool.run.tooltip": "Workflow ausführen",
|
|
438
|
-
"integration.editor.tool.run.tooltipWaiting": "Der Build läuft noch —
|
|
445
|
+
"integration.editor.tool.run.tooltipWaiting": "Der Build läuft noch — der Run startet, sobald er bereit ist",
|
|
439
446
|
"integration.editor.tool.select": "Auswählen",
|
|
440
447
|
"integration.editor.tool.settings.label": "Einstellungen",
|
|
441
448
|
"integration.editor.tool.settings.tooltip": "Workflow-Einstellungen",
|
|
@@ -622,6 +629,10 @@
|
|
|
622
629
|
"integration.nodeInspector.notice.versionNotReady.none": "Version {version} dieses Nodes ist nicht mehr einsatzbereit, und es liegt keine einsatzbereite Version vor. Dieser Schritt lässt sich nicht testen, und Auswahllisten in seinen Einstellungen bleiben leer.",
|
|
623
630
|
"integration.nodeInspector.notice.versionNotReady.upgrade": "Version {version} dieses Nodes ist nicht mehr einsatzbereit. Um den Node weiter zu verwenden, ist ein Upgrade auf Version {ready} notwendig.",
|
|
624
631
|
"integration.nodeInspector.output.description": "Was nachgelagerte Nodes von diesem erhalten. Deklariert vom Node-Typ; wird mit echten Werten gefüllt, sobald der Schritt gelaufen ist.",
|
|
632
|
+
"integration.nodeInspector.output.loopBody.heading": "Der Schleifenkörper",
|
|
633
|
+
"integration.nodeInspector.output.loopBody.size": "Am Ausgang „Element“ hängt noch nichts, diese Schleife wiederholt also nichts. | Ein Schritt hängt am Ausgang „Element“ und läuft einmal pro Element. | {n} Schritte hängen am Ausgang „Element“ und laufen jeder einmal pro Element.",
|
|
634
|
+
"integration.nodeInspector.output.loopBody.names": "Diese wiederholen sich: {names}",
|
|
635
|
+
"integration.nodeInspector.output.loopBody.joined": "Einer davon hängt zugleich hinter der Schleife, am Ausgang „Fertig“ — er wiederholt sich also mit dem Schleifenkörper, statt einmal zu laufen: {names} | {n} davon hängen zugleich hinter der Schleife, am Ausgang „Fertig“ — sie wiederholen sich also mit dem Schleifenkörper, statt einmal zu laufen: {names}",
|
|
625
636
|
"integration.nodeInspector.output.pin.editAction": "Angeheftetes Beispiel bearbeiten",
|
|
626
637
|
"integration.nodeInspector.output.pin.editorAria": "Angeheftete Beispielausgabe",
|
|
627
638
|
"integration.nodeInspector.output.pin.hint": "Ein angeheftetes Beispiel steht anstelle von Run-Daten, während Sie nachgelagerte Felder zuordnen.",
|
|
@@ -801,8 +812,8 @@
|
|
|
801
812
|
"integration.replay.debug.stepCounter": "Schritt {current} / {total}",
|
|
802
813
|
"integration.replay.debug.titleFallback": "Debug",
|
|
803
814
|
"integration.replay.error.loadStepsFailed": "Die Run-Schritte konnten nicht geladen werden.",
|
|
804
|
-
"integration.replay.runsList.description": "Klicken Sie sich durch die
|
|
805
|
-
"integration.replay.runsList.empty": "Noch keine Runs —
|
|
815
|
+
"integration.replay.runsList.description": "Klicken Sie sich durch die Runs, um sie auf dem Canvas abzuspielen.",
|
|
816
|
+
"integration.replay.runsList.empty": "Noch keine Runs — im Editor auf Run klicken.",
|
|
806
817
|
"integration.replay.runsList.heading": "Runs",
|
|
807
818
|
"integration.replay.runsList.loadMore": "Ältere Runs laden",
|
|
808
819
|
"integration.replay.state.noSteps": "Für diesen Run wurden keine Schritte aufgezeichnet.",
|
|
@@ -948,6 +959,7 @@
|
|
|
948
959
|
"integration.schedules.cron.weekday.tuesday": "Dienstags",
|
|
949
960
|
"integration.schedules.cron.weekday.wednesday": "Mittwochs",
|
|
950
961
|
"integration.schedules.cron.weeklyAt": "{days} um {time}",
|
|
962
|
+
"integration.schedules.error.loadFailed": "Zeitpläne konnten nicht geladen werden.",
|
|
951
963
|
"integration.schedules.head.title": "Zeitpläne · Integration Studio",
|
|
952
964
|
"integration.schedules.header.description": "Jeder Zeitplan-Trigger aus allen Ihren Workflows. Klicken Sie auf eine Zeile, um den Workflow zu öffnen.",
|
|
953
965
|
"integration.schedules.header.title": "Zeitpläne",
|
|
@@ -956,8 +968,8 @@
|
|
|
956
968
|
"integration.schedules.state.empty": "Noch keine Zeitpläne. Fügen Sie einem Workflow einen Zeitplan-Trigger hinzu.",
|
|
957
969
|
"integration.schedules.status.live": "aktiv",
|
|
958
970
|
"integration.schedules.status.paused": "pausiert",
|
|
959
|
-
"integration.schedules.table.column.lastRun": "
|
|
960
|
-
"integration.schedules.table.column.nextRun": "
|
|
971
|
+
"integration.schedules.table.column.lastRun": "Letzter Run",
|
|
972
|
+
"integration.schedules.table.column.nextRun": "Nächster Run",
|
|
961
973
|
"integration.schedules.table.column.status": "Status",
|
|
962
974
|
"integration.schedules.table.column.timezone": "Zeitzone",
|
|
963
975
|
"integration.schedules.table.column.when": "Wann",
|
|
@@ -972,7 +984,7 @@
|
|
|
972
984
|
"integration.secrets.cell.updated.aria": "Aktualisiert für {key}: {date}",
|
|
973
985
|
"integration.secrets.dialog.create.description": "Der Wert wird nach dem Speichern mit AES-256-GCM verschlüsselt und nie wieder ausgegeben. Sie können ihn später überschreiben, aber um ihn zu lesen, müssen Sie ihn bereits kennen.",
|
|
974
986
|
"integration.secrets.dialog.create.title": "Secret anlegen",
|
|
975
|
-
"integration.secrets.dialog.delete.body": "wird endgültig entfernt. Workflows, die darauf verweisen, funktionieren beim nächsten
|
|
987
|
+
"integration.secrets.dialog.delete.body": "wird endgültig entfernt. Workflows, die darauf verweisen, funktionieren beim nächsten Run nicht mehr.",
|
|
976
988
|
"integration.secrets.dialog.delete.title": "Secret löschen?",
|
|
977
989
|
"integration.secrets.error.invalidKey": "Der Schlüssel muss dem Muster ^[A-Z0-9_]+$ entsprechen.",
|
|
978
990
|
"integration.secrets.error.loadFailed": "Secrets konnten nicht geladen werden.",
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
"integration.canvas.node.action.duplicateAria": "Duplicate node",
|
|
72
72
|
"integration.canvas.node.badge.invalidConfig": "Config incomplete — {n} setting still needs fixing: {fields} | Config incomplete — {n} settings still need fixing: {fields}",
|
|
73
73
|
"integration.canvas.node.badge.noBuiltCode": "Version {version} is not ready to use — this step cannot be tested",
|
|
74
|
+
"integration.canvas.node.badge.repeats": "Repeats — runs once per element of {iterators} | Repeats — runs once per element of each of the {n} loops it sits inside: {iterators}",
|
|
75
|
+
"integration.canvas.node.badge.loopBody": "Loop — nothing is wired to Item yet, so nothing repeats | Loop — repeats the one step wired to Item, once per element | Loop — repeats the {n} steps wired to Item, once per element",
|
|
74
76
|
"integration.canvas.node.handle.input": "Input",
|
|
75
77
|
"integration.canvas.node.meta.versionTitle": "Version {version}",
|
|
76
78
|
"integration.canvas.node.note.heading": "Note",
|
|
@@ -298,12 +300,17 @@
|
|
|
298
300
|
"integration.deadLetters.panel.title": "Missed runs",
|
|
299
301
|
"integration.deadLetters.reason.fallback": "No reason recorded.",
|
|
300
302
|
"integration.deadLetters.state.empty": "No missed runs. Every trigger firing made it into a run.",
|
|
303
|
+
"integration.deadLetters.status.discarded": "discarded",
|
|
304
|
+
"integration.deadLetters.status.pending": "waiting",
|
|
305
|
+
"integration.deadLetters.status.replayed": "replayed",
|
|
306
|
+
"integration.deadLetters.status.unknown": "unknown ({status})",
|
|
301
307
|
"integration.deadLetters.tabs.missed.label": "Missed runs",
|
|
302
308
|
"integration.deadLetters.toast.discard.done": "Discarded",
|
|
303
309
|
"integration.deadLetters.toast.discard.error": "Could not discard.",
|
|
304
310
|
"integration.deadLetters.toast.replay.done": "Replayed",
|
|
305
311
|
"integration.deadLetters.toast.replay.error": "Could not replay.",
|
|
306
312
|
"integration.deadLetters.toast.replay.started": "Replayed — started run #{id}",
|
|
313
|
+
"integration.deadLetters.workflow.fallback": "Workflow #{id}",
|
|
307
314
|
"integration.editor.action.activate.title": "Activate this workflow",
|
|
308
315
|
"integration.editor.action.activate.title.invalid": "Fix {n} node setting before activating | Fix {n} node settings before activating",
|
|
309
316
|
"integration.editor.action.backToWorkflow": "Back to Workflow",
|
|
@@ -622,6 +629,10 @@
|
|
|
622
629
|
"integration.nodeInspector.notice.versionNotReady.none": "Version {version} of this node is no longer usable, and no version of it is. This step cannot be tested, and the choice lists in its settings stay empty.",
|
|
623
630
|
"integration.nodeInspector.notice.versionNotReady.upgrade": "Version {version} of this node is no longer usable. To keep using this step, it has to be upgraded to version {ready}.",
|
|
624
631
|
"integration.nodeInspector.output.description": "What downstream nodes receive from this one. Declared by the node type; filled with real values once the step has run.",
|
|
632
|
+
"integration.nodeInspector.output.loopBody.heading": "The loop body",
|
|
633
|
+
"integration.nodeInspector.output.loopBody.size": "Nothing is wired to Item yet, so this loop repeats nothing. | One step is wired to Item and runs once per element. | {n} steps are wired to Item and each runs once per element.",
|
|
634
|
+
"integration.nodeInspector.output.loopBody.names": "These repeat: {names}",
|
|
635
|
+
"integration.nodeInspector.output.loopBody.joined": "One of them is wired after the loop as well, on the Done side — so it repeats with the body rather than running once: {names} | {n} of them are wired after the loop as well, on the Done side — so they repeat with the body rather than running once: {names}",
|
|
625
636
|
"integration.nodeInspector.output.pin.editAction": "Edit pinned sample",
|
|
626
637
|
"integration.nodeInspector.output.pin.editorAria": "Pinned sample output",
|
|
627
638
|
"integration.nodeInspector.output.pin.hint": "A pinned sample stands in for run data while you map downstream fields.",
|
|
@@ -948,6 +959,7 @@
|
|
|
948
959
|
"integration.schedules.cron.weekday.tuesday": "Tuesdays",
|
|
949
960
|
"integration.schedules.cron.weekday.wednesday": "Wednesdays",
|
|
950
961
|
"integration.schedules.cron.weeklyAt": "{days} at {time}",
|
|
962
|
+
"integration.schedules.error.loadFailed": "Schedules could not be loaded.",
|
|
951
963
|
"integration.schedules.head.title": "Schedules · Integration Studio",
|
|
952
964
|
"integration.schedules.header.description": "Every scheduled trigger across your workflows. Click a row to open the workflow.",
|
|
953
965
|
"integration.schedules.header.title": "Schedules",
|
|
@@ -6,38 +6,37 @@ import WorkflowCreateDialog from "../../components/Integrations/WorkflowCreateDi
|
|
|
6
6
|
import { Pulse as Activity, DangerTriangle as AlertTriangle, ArrowRight, Box as Boxes, ClockCircle as Clock, Database, Layers, AddCircle as Plus, Radio, MagicStick as Sparkles, LinkRound as Webhook, Programming as Workflow } from "#solar-icons";
|
|
7
7
|
import { Badge, Button } from "@revenexx/studio";
|
|
8
8
|
import { servedPaths } from "../../utils/servedPaths";
|
|
9
|
-
import {
|
|
9
|
+
import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
|
|
10
10
|
const t = useIntegrationsT();
|
|
11
11
|
useHead({ title: () => t("integration.dashboard.head.title") });
|
|
12
12
|
const { workflows, activeCount: activeWorkflows, loading: workflowsLoading, load: loadWorkflows } = useIntegrationsWorkflows();
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
latestPerWorkflow: latestRunPerWorkflow,
|
|
15
|
+
runningCount,
|
|
16
|
+
latestLoading: runsLoading,
|
|
17
|
+
loadSummary: loadRunSummary,
|
|
18
|
+
loadLatestPerWorkflow
|
|
19
|
+
} = useIntegrationsRuns();
|
|
14
20
|
const { startingPoints, loading: templatesLoading, load: loadTemplates, ensureDetails } = useIntegrationsTemplates();
|
|
15
21
|
watch(() => startingPoints.value.map((t2) => t2.slug).join(","), () => {
|
|
16
22
|
if (startingPoints.value.length) void ensureDetails(startingPoints.value.map((t2) => t2.slug));
|
|
17
23
|
}, { immediate: true });
|
|
18
24
|
const { activeCount: schedulesActive, load: loadSchedules } = useIntegrationsSchedules();
|
|
19
25
|
const { pendingCount: missedRuns, globalLoading: missedLoading, loadGlobal: loadMissedRuns } = useIntegrationsDeadLetters();
|
|
20
|
-
const kpisLoading = computed(() => workflowsLoading.value && !workflows.value.length || runsLoading.value && !
|
|
21
|
-
const latestLoading = computed(() => runsLoading.value && !
|
|
26
|
+
const kpisLoading = computed(() => workflowsLoading.value && !workflows.value.length || runsLoading.value && !latestRunPerWorkflow.value.length);
|
|
27
|
+
const latestLoading = computed(() => runsLoading.value && !latestRunPerWorkflow.value.length);
|
|
22
28
|
const createOpen = ref(false);
|
|
23
29
|
const NuxtLinkComp = resolveComponent("NuxtLink");
|
|
24
30
|
onMounted(() => {
|
|
25
31
|
void loadWorkflows();
|
|
26
|
-
void
|
|
32
|
+
void loadRunSummary();
|
|
33
|
+
void loadLatestPerWorkflow();
|
|
27
34
|
void loadTemplates();
|
|
28
35
|
void loadSchedules();
|
|
29
|
-
void loadMissedRuns()
|
|
30
|
-
});
|
|
31
|
-
const latestByWorkflow = computed(() => {
|
|
32
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33
|
-
const out = [];
|
|
34
|
-
for (const r of runs.value) {
|
|
35
|
-
if (seen.has(r.workflowId)) continue;
|
|
36
|
-
seen.add(r.workflowId);
|
|
37
|
-
out.push(r);
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
36
|
+
void loadMissedRuns().catch(() => {
|
|
37
|
+
});
|
|
40
38
|
});
|
|
39
|
+
const latestByWorkflow = computed(() => latestRunPerWorkflow.value);
|
|
41
40
|
const latestPerWorkflow = computed(() => latestByWorkflow.value.slice(0, 10));
|
|
42
41
|
const failedRecent = computed(() => latestByWorkflow.value.filter((r) => r.status === "failed").slice(0, 3));
|
|
43
42
|
function formatRelative(iso) {
|
|
@@ -180,7 +179,7 @@ function surfaceCards() {
|
|
|
180
179
|
:to="{ path: `/integrations/workflows/${r.workflowId}`, query: { run: r.id } }"
|
|
181
180
|
class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 hover:bg-muted/30"
|
|
182
181
|
>
|
|
183
|
-
<Badge variant="outline" :class="['border text-xs font-medium uppercase',
|
|
182
|
+
<Badge variant="outline" :class="['border text-xs font-medium uppercase', runStatusClass(r.status)]">
|
|
184
183
|
{{ runStatusLabel(r.status) }}
|
|
185
184
|
</Badge>
|
|
186
185
|
<div class="truncate">
|
|
@@ -4,7 +4,7 @@ import { Badge, Button } from "@revenexx/studio";
|
|
|
4
4
|
import { toast } from "vue-sonner";
|
|
5
5
|
import RunsTable from "../../components/Integrations/RunsTable.vue";
|
|
6
6
|
import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
7
|
-
import {
|
|
7
|
+
import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
|
|
8
8
|
const t = useIntegrationsT();
|
|
9
9
|
useHead({ title: () => t("integration.runs.list.head.title") });
|
|
10
10
|
const route = useRoute();
|
|
@@ -37,7 +37,7 @@ watch([runTick, workflowTick], () => {
|
|
|
37
37
|
let pollTimer = null;
|
|
38
38
|
onMounted(() => {
|
|
39
39
|
void load(true);
|
|
40
|
-
void
|
|
40
|
+
void reloadMissedRuns();
|
|
41
41
|
pollTimer = setInterval(() => {
|
|
42
42
|
if (hasActiveRuns.value && !loading.value && budget.due(delivering.value)) void load(true);
|
|
43
43
|
}, POLL_MS);
|
|
@@ -49,31 +49,43 @@ onUnmounted(() => {
|
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
const busyId = ref(null);
|
|
52
|
+
async function reloadMissedRuns() {
|
|
53
|
+
try {
|
|
54
|
+
await loadGlobal(true);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
52
58
|
async function onReplay(dl) {
|
|
53
59
|
busyId.value = dl.id;
|
|
54
60
|
try {
|
|
55
61
|
const res = await replay(dl.id);
|
|
56
62
|
const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
|
|
57
63
|
toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
|
|
58
|
-
await loadGlobal(true);
|
|
59
|
-
reload();
|
|
60
64
|
} catch (e) {
|
|
61
65
|
toast.error(e?.message ?? t("integration.deadLetters.toast.replay.error"));
|
|
62
|
-
} finally {
|
|
63
66
|
busyId.value = null;
|
|
67
|
+
return;
|
|
64
68
|
}
|
|
69
|
+
await reloadMissedRuns();
|
|
70
|
+
reload();
|
|
71
|
+
busyId.value = null;
|
|
65
72
|
}
|
|
66
73
|
async function onDiscard(dl) {
|
|
67
74
|
busyId.value = dl.id;
|
|
68
75
|
try {
|
|
69
76
|
await discard(dl.id);
|
|
70
77
|
toast.success(t("integration.deadLetters.toast.discard.done"));
|
|
71
|
-
await loadGlobal(true);
|
|
72
78
|
} catch (e) {
|
|
73
79
|
toast.error(e?.message ?? t("integration.deadLetters.toast.discard.error"));
|
|
74
|
-
} finally {
|
|
75
80
|
busyId.value = null;
|
|
81
|
+
return;
|
|
76
82
|
}
|
|
83
|
+
await reloadMissedRuns();
|
|
84
|
+
busyId.value = null;
|
|
85
|
+
}
|
|
86
|
+
function workflowNameOf(dl) {
|
|
87
|
+
const name = dl.workflow_name;
|
|
88
|
+
return typeof name === "string" && name.trim() !== "" ? name : t("integration.deadLetters.workflow.fallback", { id: dl.workflow_id });
|
|
77
89
|
}
|
|
78
90
|
function fmt(iso) {
|
|
79
91
|
if (!iso) return "\u2014";
|
|
@@ -104,7 +116,7 @@ function fmt(iso) {
|
|
|
104
116
|
<p class="text-sm text-muted-foreground">
|
|
105
117
|
{{ t("integration.deadLetters.list.description") }}
|
|
106
118
|
</p>
|
|
107
|
-
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() =>
|
|
119
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() => reloadMissedRuns()">
|
|
108
120
|
<Loader2 v-if="missedLoading" :size="12" class="animate-spin" />
|
|
109
121
|
{{ t("integration.common.action.refresh") }}
|
|
110
122
|
</Button>
|
|
@@ -130,10 +142,10 @@ function fmt(iso) {
|
|
|
130
142
|
:key="dl.id"
|
|
131
143
|
class="flex items-start gap-3 px-3 py-2.5"
|
|
132
144
|
>
|
|
133
|
-
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5',
|
|
145
|
+
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5', deadLetterStatusClass(dl.status)]">{{ deadLetterStatusLabel(dl.status) }}</Badge>
|
|
134
146
|
<div class="min-w-0 flex-1">
|
|
135
147
|
<NuxtLink :to="`/integrations/workflows/${dl.workflow_id}`" class="text-sm font-medium truncate transition-colors hover:text-primary">
|
|
136
|
-
{{ dl
|
|
148
|
+
{{ workflowNameOf(dl) }}
|
|
137
149
|
</NuxtLink>
|
|
138
150
|
<div class="text-sm break-words">{{ dl.reason || t("integration.deadLetters.reason.fallback") }}</div>
|
|
139
151
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
|