@revenexx/studio-integrations 0.6.0 → 1.0.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 +643 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +32 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +45 -38
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +21 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +30 -16
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -8
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +45 -42
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +315 -221
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +37 -10
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +40 -35
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +82 -34
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +18 -8
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +22 -12
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +41 -39
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +131 -87
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +27 -24
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +76 -46
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -13
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +79 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +14 -2
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +21 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +30 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +4 -2
- package/dist/runtime/data/integrationNodeCatalog.d.ts +63 -1
- package/dist/runtime/data/integrationNodeCatalog.js +22 -4
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1224 -0
- package/dist/runtime/i18n/messages/en.json +1224 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +73 -52
- package/dist/runtime/pages/integrations/index.vue +66 -73
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +89 -72
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +351 -197
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +25 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +30 -2
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +52 -5
- package/dist/runtime/utils/localize.js +47 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
|
@@ -11,9 +11,7 @@ import {
|
|
|
11
11
|
AltArrowRight as Next,
|
|
12
12
|
Play,
|
|
13
13
|
Pause,
|
|
14
|
-
RefreshCircle as Loader2
|
|
15
|
-
ClockCircle as Clock,
|
|
16
|
-
LinkRound as Webhook
|
|
14
|
+
RefreshCircle as Loader2
|
|
17
15
|
} from "#solar-icons";
|
|
18
16
|
import { Badge, Button } from "@revenexx/studio";
|
|
19
17
|
import { toast } from "vue-sonner";
|
|
@@ -26,6 +24,8 @@ import StickyNoteView from "./StickyNoteView.vue";
|
|
|
26
24
|
import GroupFrameView from "./GroupFrameView.vue";
|
|
27
25
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
28
26
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
27
|
+
import { triggerGlyph } from "../../data/triggerGlyphs";
|
|
28
|
+
import { SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
29
29
|
import { runStatusLabel, statusClass } from "../../utils/statusStyle";
|
|
30
30
|
const props = defineProps({
|
|
31
31
|
workflow: { type: Object, required: true },
|
|
@@ -33,9 +33,14 @@ const props = defineProps({
|
|
|
33
33
|
single: { type: Boolean, required: false }
|
|
34
34
|
});
|
|
35
35
|
const { loadForWorkflow, getSteps } = useIntegrationsRuns();
|
|
36
|
+
const t = useIntegrationsT();
|
|
36
37
|
const TERMINAL = /* @__PURE__ */ new Set(["completed", "succeeded", "failed", "cancelled", "terminated"]);
|
|
37
38
|
const STEPS_POLL_MS = 4e3;
|
|
38
39
|
const PLAY_INTERVAL_MS = 3e3;
|
|
40
|
+
const { delivering: realtimeDelivering, runTick, lastRunEvent } = useIntegrationsRealtime();
|
|
41
|
+
function stepsPollDelay() {
|
|
42
|
+
return realtimeDelivering.value ? SAFETY_POLL_MS : STEPS_POLL_MS;
|
|
43
|
+
}
|
|
39
44
|
const runs = ref([]);
|
|
40
45
|
const runsPage = ref(1);
|
|
41
46
|
const runsLastPage = ref(1);
|
|
@@ -53,7 +58,7 @@ async function loadRuns(reset = false) {
|
|
|
53
58
|
runsPage.value = res.page;
|
|
54
59
|
runsLastPage.value = res.lastPage;
|
|
55
60
|
} catch (e) {
|
|
56
|
-
toast.error(e?.message ?? "
|
|
61
|
+
toast.error(e?.message ?? t("integration.replay.toast.loadRunsFailed"));
|
|
57
62
|
} finally {
|
|
58
63
|
runsLoading.value = false;
|
|
59
64
|
}
|
|
@@ -91,16 +96,23 @@ async function loadSteps(runId, isPoll = false) {
|
|
|
91
96
|
if (runStatus.value && !TERMINAL.has(runStatus.value)) {
|
|
92
97
|
pollTimer = window.setTimeout(() => {
|
|
93
98
|
void loadSteps(runId, true);
|
|
94
|
-
},
|
|
99
|
+
}, stepsPollDelay());
|
|
95
100
|
}
|
|
96
101
|
} catch (e) {
|
|
97
102
|
if (selectedRunId.value === runId) {
|
|
98
|
-
stepsError.value = e?.message ?? "
|
|
103
|
+
stepsError.value = e?.message ?? t("integration.replay.error.loadStepsFailed");
|
|
99
104
|
}
|
|
100
105
|
} finally {
|
|
101
106
|
if (!isPoll) stepsLoading.value = false;
|
|
102
107
|
}
|
|
103
108
|
}
|
|
109
|
+
watch(runTick, () => {
|
|
110
|
+
const runId = selectedRunId.value;
|
|
111
|
+
if (!runId) return;
|
|
112
|
+
const event = lastRunEvent.value;
|
|
113
|
+
if (event && event.topicId !== runId) return;
|
|
114
|
+
void loadSteps(runId, true);
|
|
115
|
+
});
|
|
104
116
|
function selectRun(id) {
|
|
105
117
|
if (selectedRunId.value === id) return;
|
|
106
118
|
selectedRunId.value = id;
|
|
@@ -173,7 +185,7 @@ const finalStatus = computed(() => {
|
|
|
173
185
|
return m;
|
|
174
186
|
});
|
|
175
187
|
const runNodeIds = computed(() => new Set(steps.value.map((s) => s.node_id)));
|
|
176
|
-
const triggerIds = computed(() => new Set(props.workflow.blob.triggers.map((
|
|
188
|
+
const triggerIds = computed(() => new Set(props.workflow.blob.triggers.map((t2) => t2.id)));
|
|
177
189
|
function edgeColorByKind(kind) {
|
|
178
190
|
return kind === "error" ? "#cbd5e1" : "#94a3b8";
|
|
179
191
|
}
|
|
@@ -230,7 +242,7 @@ const vfNodes = computed(() => {
|
|
|
230
242
|
const posOf = (id) => {
|
|
231
243
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
232
244
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
233
|
-
const ti = blob.triggers.findIndex((
|
|
245
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
234
246
|
if (ti >= 0) return triggerPos(id, ti);
|
|
235
247
|
return { x: 0, y: 0 };
|
|
236
248
|
};
|
|
@@ -244,17 +256,17 @@ const vfNodes = computed(() => {
|
|
|
244
256
|
zIndex: -100 + i,
|
|
245
257
|
draggable: false,
|
|
246
258
|
class: "replay-dim",
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
// No callbacks: that absence is what makes the note read-only — it then
|
|
260
|
+
// draws no toolbar, no resize handle and does not open for typing (PO-284).
|
|
261
|
+
data: { sticky: s }
|
|
250
262
|
})),
|
|
251
|
-
...blob.triggers.map((
|
|
252
|
-
id:
|
|
263
|
+
...blob.triggers.map((t2, i) => ({
|
|
264
|
+
id: t2.id,
|
|
253
265
|
type: "trigger",
|
|
254
|
-
position: triggerPos(
|
|
266
|
+
position: triggerPos(t2.id, i),
|
|
255
267
|
draggable: false,
|
|
256
|
-
class: replayNodeClass(
|
|
257
|
-
data:
|
|
268
|
+
class: replayNodeClass(t2.id, "trigger"),
|
|
269
|
+
data: t2
|
|
258
270
|
})),
|
|
259
271
|
...blob.nodes.map((n) => ({
|
|
260
272
|
id: n.id,
|
|
@@ -274,7 +286,7 @@ const vfEdges = computed(() => {
|
|
|
274
286
|
const nodePos = (id) => {
|
|
275
287
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
276
288
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
277
|
-
const ti = blob.triggers.findIndex((
|
|
289
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
278
290
|
if (ti >= 0) return blob.triggerPositions?.[id] ?? { x: -40, y: 60 + ti * 140 };
|
|
279
291
|
return { x: 0, y: 0 };
|
|
280
292
|
};
|
|
@@ -395,7 +407,7 @@ function pretty(v) {
|
|
|
395
407
|
}
|
|
396
408
|
}
|
|
397
409
|
function triggerIcon(type) {
|
|
398
|
-
return type
|
|
410
|
+
return triggerGlyph(type);
|
|
399
411
|
}
|
|
400
412
|
const activeStepTitle = computed(() => {
|
|
401
413
|
const s = activeStep.value;
|
|
@@ -458,12 +470,12 @@ watch(() => props.initialRunId, (id) => {
|
|
|
458
470
|
<!-- Which run is on the canvas -->
|
|
459
471
|
<div class="absolute top-3 left-3 z-10 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-white/95 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur-sm">
|
|
460
472
|
<Play :size="13" class="text-muted-foreground" />
|
|
461
|
-
<span class="font-medium">{{ selectedRunId ?
|
|
473
|
+
<span class="font-medium">{{ selectedRunId ? t("integration.replay.canvas.runLabel", { id: selectedRunId }) : t("integration.replay.canvas.pickRun") }}</span>
|
|
462
474
|
<Badge v-if="runStatus" variant="outline" class="border text-xs font-medium uppercase" :class="statusClass(runStatus)">
|
|
463
475
|
{{ runStatusLabel(runStatus) }}
|
|
464
476
|
</Badge>
|
|
465
477
|
<Badge v-if="stepsSource" variant="outline" class="border text-xs font-medium uppercase text-muted-foreground">
|
|
466
|
-
{{ stepsSource === "temporal" ? "
|
|
478
|
+
{{ stepsSource === "temporal" ? t("integration.replay.badge.live") : t("integration.replay.badge.snapshot") }}
|
|
467
479
|
</Badge>
|
|
468
480
|
<Loader2 v-if="stepsLoading" :size="13" class="animate-spin text-muted-foreground" />
|
|
469
481
|
</div>
|
|
@@ -479,14 +491,14 @@ watch(() => props.initialRunId, (id) => {
|
|
|
479
491
|
<div class="flex items-center gap-1">
|
|
480
492
|
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor === 0" @click="seek(safeCursor - 1)">
|
|
481
493
|
<Prev :size="13" />
|
|
482
|
-
|
|
494
|
+
{{ t("integration.replay.transport.action.prev") }}
|
|
483
495
|
</Button>
|
|
484
496
|
<Button size="sm" class="h-7" @click="togglePlay">
|
|
485
497
|
<component :is="playing ? Pause : Play" :size="13" class="mr-1" />
|
|
486
|
-
{{ playing ? "
|
|
498
|
+
{{ playing ? t("integration.replay.transport.action.pause") : t("integration.replay.transport.action.play") }}
|
|
487
499
|
</Button>
|
|
488
500
|
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor >= steps.length - 1" @click="seek(safeCursor + 1)">
|
|
489
|
-
|
|
501
|
+
{{ t("integration.replay.transport.action.next") }}
|
|
490
502
|
<Next :size="13" />
|
|
491
503
|
</Button>
|
|
492
504
|
</div>
|
|
@@ -497,17 +509,17 @@ watch(() => props.initialRunId, (id) => {
|
|
|
497
509
|
:max="steps.length - 1"
|
|
498
510
|
:value="safeCursor"
|
|
499
511
|
class="h-1.5 flex-1 cursor-pointer accent-primary"
|
|
500
|
-
aria-label="
|
|
512
|
+
:aria-label="t('integration.replay.transport.scrubberAria')"
|
|
501
513
|
@input="seek(Number($event.target.value))"
|
|
502
514
|
>
|
|
503
515
|
|
|
504
516
|
<span class="shrink-0 text-sm tabular-nums text-muted-foreground">
|
|
505
|
-
|
|
517
|
+
{{ t("integration.replay.transport.stepCounter", { current: safeCursor + 1, total: steps.length }) }}
|
|
506
518
|
<span class="font-mono">· {{ activeStepTitle }}</span>
|
|
507
519
|
</span>
|
|
508
520
|
</template>
|
|
509
521
|
<p v-else-if="!stepsLoading" class="text-sm text-muted-foreground">
|
|
510
|
-
{{ selectedRunId ? "
|
|
522
|
+
{{ selectedRunId ? t("integration.replay.state.noSteps") : t("integration.replay.state.pickRunHint") }}
|
|
511
523
|
</p>
|
|
512
524
|
</div>
|
|
513
525
|
</div>
|
|
@@ -517,10 +529,10 @@ watch(() => props.initialRunId, (id) => {
|
|
|
517
529
|
<aside class="flex w-[32rem] shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
518
530
|
<div class="flex items-center gap-2 border-b border-black/[0.06] px-3 py-2">
|
|
519
531
|
<div class="min-w-0 flex-1">
|
|
520
|
-
<div class="truncate font-mono text-sm font-medium">{{ activeStepTitle || "
|
|
532
|
+
<div class="truncate font-mono text-sm font-medium">{{ activeStepTitle || t("integration.replay.debug.titleFallback") }}</div>
|
|
521
533
|
<div class="text-xs tabular-nums text-muted-foreground">
|
|
522
|
-
<template v-if="steps.length">
|
|
523
|
-
<template v-else>
|
|
534
|
+
<template v-if="steps.length">{{ t("integration.replay.debug.stepCounter", { current: safeCursor + 1, total: steps.length }) }}</template>
|
|
535
|
+
<template v-else>{{ t("integration.replay.debug.noStepSelected") }}</template>
|
|
524
536
|
</div>
|
|
525
537
|
</div>
|
|
526
538
|
<Badge v-if="activeStep" variant="outline" class="shrink-0 border text-xs font-medium uppercase" :class="statusClass(activeStep.status)">
|
|
@@ -531,59 +543,59 @@ watch(() => props.initialRunId, (id) => {
|
|
|
531
543
|
<div v-if="activeStep" class="min-h-0 flex-1 space-y-4 overflow-y-auto p-3">
|
|
532
544
|
<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
|
|
533
545
|
<div>
|
|
534
|
-
<div class="text-mono text-muted-foreground">
|
|
546
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.attempt") }}</div>
|
|
535
547
|
<div class="tabular-nums">{{ activeStep.attempt + 1 }}</div>
|
|
536
548
|
</div>
|
|
537
549
|
<div>
|
|
538
|
-
<div class="text-mono text-muted-foreground">
|
|
550
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.duration") }}</div>
|
|
539
551
|
<div class="tabular-nums">{{ formatDuration(activeStep.duration_ms) }}</div>
|
|
540
552
|
</div>
|
|
541
553
|
<div>
|
|
542
|
-
<div class="text-mono text-muted-foreground">
|
|
554
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.started") }}</div>
|
|
543
555
|
<div class="tabular-nums">{{ formatTime(activeStep.started_at) }}</div>
|
|
544
556
|
</div>
|
|
545
557
|
<div>
|
|
546
|
-
<div class="text-mono text-muted-foreground">
|
|
558
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.completed") }}</div>
|
|
547
559
|
<div class="tabular-nums">{{ formatTime(activeStep.completed_at) }}</div>
|
|
548
560
|
</div>
|
|
549
561
|
</div>
|
|
550
562
|
|
|
551
563
|
<div v-if="activeStep.branch" class="text-sm">
|
|
552
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
564
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.branch") }}</div>
|
|
553
565
|
<code class="rounded bg-black/[0.05] px-1.5 py-0.5 font-mono">{{ activeStep.branch }}</code>
|
|
554
566
|
</div>
|
|
555
567
|
|
|
556
568
|
<div v-if="activeStep.error" class="text-sm">
|
|
557
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
569
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.error") }}</div>
|
|
558
570
|
<pre class="max-h-40 overflow-auto rounded-md border border-rose-500/20 bg-rose-500/5 p-2 font-mono text-sm text-rose-700">{{ activeStep.error }}</pre>
|
|
559
571
|
</div>
|
|
560
572
|
|
|
561
573
|
<div v-if="activeStep.input !== void 0 && activeStep.input !== null" class="text-sm">
|
|
562
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
574
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.input") }}</div>
|
|
563
575
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.input) }}</pre>
|
|
564
576
|
</div>
|
|
565
577
|
|
|
566
578
|
<div v-if="activeStep.config !== void 0 && activeStep.config !== null" class="text-sm">
|
|
567
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
579
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.config") }}</div>
|
|
568
580
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.config) }}</pre>
|
|
569
581
|
</div>
|
|
570
582
|
|
|
571
583
|
<div v-if="activeStep.output !== void 0 && activeStep.output !== null" class="text-sm">
|
|
572
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
584
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.output") }}</div>
|
|
573
585
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.output) }}</pre>
|
|
574
586
|
</div>
|
|
575
587
|
</div>
|
|
576
588
|
<p v-else class="p-4 text-sm text-muted-foreground">
|
|
577
|
-
|
|
589
|
+
{{ t("integration.replay.debug.emptyHint") }}
|
|
578
590
|
</p>
|
|
579
591
|
</aside>
|
|
580
592
|
|
|
581
593
|
<!-- Run list — hidden in single mode (the run detail replays ONE run). -->
|
|
582
594
|
<aside v-if="!single" class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
583
595
|
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
584
|
-
<div class="text-sm font-semibold">
|
|
596
|
+
<div class="text-sm font-semibold">{{ t("integration.replay.runsList.heading") }}</div>
|
|
585
597
|
<p class="text-sm text-muted-foreground">
|
|
586
|
-
|
|
598
|
+
{{ t("integration.replay.runsList.description") }}
|
|
587
599
|
</p>
|
|
588
600
|
</div>
|
|
589
601
|
|
|
@@ -612,7 +624,7 @@ watch(() => props.initialRunId, (id) => {
|
|
|
612
624
|
</button>
|
|
613
625
|
|
|
614
626
|
<p v-if="!runs.length && !runsLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
615
|
-
|
|
627
|
+
{{ t("integration.replay.runsList.empty") }}
|
|
616
628
|
</p>
|
|
617
629
|
|
|
618
630
|
<div v-if="runsLoading" class="flex justify-center py-3">
|
|
@@ -625,7 +637,7 @@ watch(() => props.initialRunId, (id) => {
|
|
|
625
637
|
class="w-full text-sm"
|
|
626
638
|
@click="loadMoreRuns"
|
|
627
639
|
>
|
|
628
|
-
|
|
640
|
+
{{ t("integration.replay.runsList.loadMore") }}
|
|
629
641
|
</Button>
|
|
630
642
|
</div>
|
|
631
643
|
</aside>
|
|
@@ -4,33 +4,32 @@ export interface ExecutionModeOption {
|
|
|
4
4
|
label: string;
|
|
5
5
|
hint: string;
|
|
6
6
|
}
|
|
7
|
+
/** What this dialog edits — the values as they stand, saved or not. */
|
|
8
|
+
export interface WorkflowSettingsValues {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
icon: WorkflowIcon | null;
|
|
13
|
+
executionMode: string;
|
|
14
|
+
}
|
|
7
15
|
type __VLS_Props = {
|
|
8
16
|
open: boolean;
|
|
9
17
|
workflow: Workflow;
|
|
10
18
|
modes: ExecutionModeOption[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The values to edit: the editor's unsaved ones where it has any, the saved ones
|
|
21
|
+
* otherwise. Reopening the dialog therefore shows what the operator last entered
|
|
22
|
+
* rather than what the platform still holds.
|
|
23
|
+
*/
|
|
24
|
+
current: WorkflowSettingsValues;
|
|
14
25
|
/** Tag suggestions (tags already used across the tenant's workflows). */
|
|
15
26
|
tagSuggestions?: string[];
|
|
16
27
|
};
|
|
17
28
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
29
|
"update:open": (args_0: boolean) => any;
|
|
19
|
-
|
|
20
|
-
"save-meta": (meta: {
|
|
21
|
-
name: string;
|
|
22
|
-
description: string;
|
|
23
|
-
tags: string[];
|
|
24
|
-
icon: WorkflowIcon | null;
|
|
25
|
-
}) => any;
|
|
30
|
+
apply: (values: WorkflowSettingsValues) => any;
|
|
26
31
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
27
32
|
"onUpdate:open"?: ((args_0: boolean) => any) | undefined;
|
|
28
|
-
|
|
29
|
-
"onSave-meta"?: ((meta: {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
tags: string[];
|
|
33
|
-
icon: WorkflowIcon | null;
|
|
34
|
-
}) => any) | undefined;
|
|
33
|
+
onApply?: ((values: WorkflowSettingsValues) => any) | undefined;
|
|
35
34
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
36
35
|
export default _default;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, watch } from "vue";
|
|
3
|
-
import { RefreshCircle as Loader2 } from "#solar-icons";
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
4
3
|
import {
|
|
5
4
|
Button,
|
|
6
5
|
Dialog,
|
|
7
6
|
DialogContent,
|
|
8
7
|
DialogDescription,
|
|
8
|
+
DialogFooter,
|
|
9
9
|
DialogHeader,
|
|
10
10
|
DialogTitle,
|
|
11
11
|
Input,
|
|
@@ -23,23 +23,35 @@ const props = defineProps({
|
|
|
23
23
|
open: { type: Boolean, required: true },
|
|
24
24
|
workflow: { type: Object, required: true },
|
|
25
25
|
modes: { type: Array, required: true },
|
|
26
|
-
|
|
27
|
-
savingMode: { type: Boolean, required: false },
|
|
28
|
-
savingMeta: { type: Boolean, required: false },
|
|
26
|
+
current: { type: Object, required: true },
|
|
29
27
|
tagSuggestions: { type: Array, required: false }
|
|
30
28
|
});
|
|
31
|
-
const emit = defineEmits(["update:open", "
|
|
29
|
+
const emit = defineEmits(["update:open", "apply"]);
|
|
30
|
+
const t = useIntegrationsT();
|
|
32
31
|
const nameDraft = ref("");
|
|
33
32
|
const descriptionDraft = ref("");
|
|
34
33
|
const tagsDraft = ref([]);
|
|
35
34
|
const iconDraft = ref(null);
|
|
35
|
+
const modeDraft = ref("");
|
|
36
36
|
watch(() => props.open, (v) => {
|
|
37
37
|
if (!v) return;
|
|
38
|
-
nameDraft.value = props.
|
|
39
|
-
descriptionDraft.value = props.
|
|
40
|
-
tagsDraft.value = [...props.
|
|
41
|
-
iconDraft.value = props.
|
|
38
|
+
nameDraft.value = props.current.name;
|
|
39
|
+
descriptionDraft.value = props.current.description;
|
|
40
|
+
tagsDraft.value = [...props.current.tags];
|
|
41
|
+
iconDraft.value = props.current.icon;
|
|
42
|
+
modeDraft.value = props.current.executionMode;
|
|
42
43
|
}, { immediate: true });
|
|
44
|
+
const edited = computed(() => ({
|
|
45
|
+
name: nameDraft.value.trim(),
|
|
46
|
+
description: descriptionDraft.value.trim(),
|
|
47
|
+
tags: tagsDraft.value,
|
|
48
|
+
icon: iconDraft.value,
|
|
49
|
+
executionMode: modeDraft.value
|
|
50
|
+
}));
|
|
51
|
+
function applyAndClose() {
|
|
52
|
+
if (JSON.stringify(edited.value) !== JSON.stringify(props.current)) emit("apply", edited.value);
|
|
53
|
+
emit("update:open", false);
|
|
54
|
+
}
|
|
43
55
|
function formatDateTime(iso) {
|
|
44
56
|
if (!iso) return "\u2014";
|
|
45
57
|
try {
|
|
@@ -54,25 +66,26 @@ function formatDateTime(iso) {
|
|
|
54
66
|
<Dialog :open="open" @update:open="(v) => emit('update:open', v)">
|
|
55
67
|
<DialogContent class="sm:max-w-lg">
|
|
56
68
|
<DialogHeader>
|
|
57
|
-
<DialogTitle>
|
|
69
|
+
<DialogTitle>{{ t("integration.workflows.settings.dialog.title") }}</DialogTitle>
|
|
58
70
|
<DialogDescription class="text-sm">
|
|
59
|
-
|
|
71
|
+
<!-- The name as it stands, drafted included: this dialog is where a rename is
|
|
72
|
+
typed, so naming the workflow by its stored name here contradicts the
|
|
73
|
+
field directly below. -->
|
|
74
|
+
{{ t("integration.workflows.settings.dialog.description", { name: current.name }) }}
|
|
60
75
|
</DialogDescription>
|
|
61
76
|
</DialogHeader>
|
|
62
77
|
|
|
63
78
|
<div class="space-y-4 text-sm">
|
|
64
79
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-3">
|
|
65
|
-
<div class="text-mono text-muted-foreground">
|
|
80
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.execution") }}</div>
|
|
66
81
|
<div class="flex items-center gap-2">
|
|
67
82
|
<Select
|
|
68
|
-
|
|
69
|
-
:disabled="savingMode"
|
|
70
|
-
@update:model-value="(v) => emit('change-mode', v)"
|
|
83
|
+
v-model="modeDraft"
|
|
71
84
|
>
|
|
72
85
|
<!-- Named explicitly: the section caption above says "Execution",
|
|
73
86
|
which is not a name the control itself carries. -->
|
|
74
|
-
<SelectTrigger aria-label="
|
|
75
|
-
<SelectValue placeholder="
|
|
87
|
+
<SelectTrigger :aria-label="t('integration.workflows.settings.field.executionMode.aria')" class="h-8 w-full bg-white text-sm">
|
|
88
|
+
<SelectValue :placeholder="t('integration.workflows.settings.field.executionMode.placeholder')" />
|
|
76
89
|
</SelectTrigger>
|
|
77
90
|
<SelectContent>
|
|
78
91
|
<SelectItem v-for="m in modes" :key="m.value" :value="m.value">
|
|
@@ -80,7 +93,6 @@ function formatDateTime(iso) {
|
|
|
80
93
|
</SelectItem>
|
|
81
94
|
</SelectContent>
|
|
82
95
|
</Select>
|
|
83
|
-
<Loader2 v-if="savingMode" :size="14" class="shrink-0 animate-spin text-muted-foreground" />
|
|
84
96
|
</div>
|
|
85
97
|
<p class="text-xs text-muted-foreground">
|
|
86
98
|
{{ modes.find((m2) => m2.value === modeDraft)?.hint }}
|
|
@@ -88,55 +100,55 @@ function formatDateTime(iso) {
|
|
|
88
100
|
</div>
|
|
89
101
|
|
|
90
102
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-3">
|
|
91
|
-
<div class="text-mono text-muted-foreground">
|
|
103
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.appearance") }}</div>
|
|
92
104
|
<!-- Real labels, not styled captions: each one names its own control
|
|
93
105
|
(`for`/`id`), otherwise a screen reader reads four unlabelled
|
|
94
106
|
fields in a row. -->
|
|
95
107
|
<div class="space-y-1.5">
|
|
96
|
-
<Label for="wf-set-name" class="text-sm font-normal text-muted-foreground">
|
|
97
|
-
<Input id="wf-set-name" v-model="nameDraft" class="bg-white" placeholder="
|
|
108
|
+
<Label for="wf-set-name" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.name.label") }}</Label>
|
|
109
|
+
<Input id="wf-set-name" v-model="nameDraft" class="bg-white" :placeholder="t('integration.workflows.settings.field.name.placeholder')" />
|
|
98
110
|
</div>
|
|
99
111
|
<div class="space-y-1.5">
|
|
100
|
-
<Label for="wf-set-desc" class="text-sm font-normal text-muted-foreground">
|
|
101
|
-
<Textarea id="wf-set-desc" v-model="descriptionDraft" rows="2" class="bg-white" placeholder="
|
|
112
|
+
<Label for="wf-set-desc" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.description.label") }}</Label>
|
|
113
|
+
<Textarea id="wf-set-desc" v-model="descriptionDraft" rows="2" class="bg-white" :placeholder="t('integration.workflows.settings.field.description.placeholder')" />
|
|
102
114
|
</div>
|
|
103
115
|
<div class="space-y-1.5">
|
|
104
|
-
<Label for="wf-set-tags" class="text-sm font-normal text-muted-foreground">
|
|
116
|
+
<Label for="wf-set-tags" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.tags.label") }}</Label>
|
|
105
117
|
<WorkflowTagsInput v-model="tagsDraft" input-id="wf-set-tags" :suggestions="tagSuggestions ?? []" />
|
|
106
118
|
</div>
|
|
107
119
|
<div class="space-y-1.5">
|
|
108
|
-
<Label id="wf-set-icon-label" class="text-sm font-normal text-muted-foreground">
|
|
120
|
+
<Label id="wf-set-icon-label" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.icon.label") }}</Label>
|
|
109
121
|
<WorkflowIconPicker v-model="iconDraft" trigger-labelledby="wf-set-icon-label" />
|
|
110
122
|
</div>
|
|
111
|
-
<div class="flex justify-end">
|
|
112
|
-
<Button
|
|
113
|
-
size="sm"
|
|
114
|
-
class="h-7 text-sm"
|
|
115
|
-
:disabled="savingMeta || !nameDraft.trim()"
|
|
116
|
-
@click="emit('save-meta', { name: nameDraft.trim(), description: descriptionDraft.trim(), tags: tagsDraft, icon: iconDraft })"
|
|
117
|
-
>
|
|
118
|
-
<Loader2 v-if="savingMeta" :size="12" class="animate-spin" />
|
|
119
|
-
Save changes
|
|
120
|
-
</Button>
|
|
121
|
-
</div>
|
|
122
123
|
</div>
|
|
123
124
|
|
|
124
125
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-2">
|
|
125
|
-
<div class="text-mono text-muted-foreground">
|
|
126
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.metadata") }}</div>
|
|
126
127
|
<div class="grid grid-cols-[110px_1fr] gap-x-3 gap-y-2">
|
|
127
|
-
<span class="text-muted-foreground">
|
|
128
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.id") }}</span>
|
|
128
129
|
<span class="font-mono">{{ workflow.id }}</span>
|
|
129
|
-
<span class="text-muted-foreground">
|
|
130
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.definition") }}</span>
|
|
130
131
|
<span class="font-mono">{{ workflow.blob_definition_version }}</span>
|
|
131
|
-
<span class="text-muted-foreground">
|
|
132
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.revision") }}</span>
|
|
132
133
|
<span class="font-mono">r{{ workflow.revision }}</span>
|
|
133
|
-
<span class="text-muted-foreground">
|
|
134
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.created") }}</span>
|
|
134
135
|
<span>{{ formatDateTime(workflow.created_at) }}</span>
|
|
135
|
-
<span class="text-muted-foreground">
|
|
136
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.updated") }}</span>
|
|
136
137
|
<span>{{ formatDateTime(workflow.updated_at) }}</span>
|
|
137
138
|
</div>
|
|
138
139
|
</div>
|
|
139
140
|
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Cancel / Done, the pair every dialog in the editor closes on. Done reaches
|
|
143
|
+
the workflow's unsaved state; the header's Save reaches the platform. -->
|
|
144
|
+
<DialogFooter>
|
|
145
|
+
<Button variant="outline" size="sm" class="h-8" @click="emit('update:open', false)">
|
|
146
|
+
{{ t("integration.common.action.cancel") }}
|
|
147
|
+
</Button>
|
|
148
|
+
<Button size="sm" class="h-8" :disabled="!nameDraft.trim()" @click="applyAndClose">
|
|
149
|
+
{{ t("integration.workflows.settings.action.done") }}
|
|
150
|
+
</Button>
|
|
151
|
+
</DialogFooter>
|
|
140
152
|
</DialogContent>
|
|
141
153
|
</Dialog>
|
|
142
154
|
</template>
|
|
@@ -4,33 +4,32 @@ export interface ExecutionModeOption {
|
|
|
4
4
|
label: string;
|
|
5
5
|
hint: string;
|
|
6
6
|
}
|
|
7
|
+
/** What this dialog edits — the values as they stand, saved or not. */
|
|
8
|
+
export interface WorkflowSettingsValues {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
icon: WorkflowIcon | null;
|
|
13
|
+
executionMode: string;
|
|
14
|
+
}
|
|
7
15
|
type __VLS_Props = {
|
|
8
16
|
open: boolean;
|
|
9
17
|
workflow: Workflow;
|
|
10
18
|
modes: ExecutionModeOption[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The values to edit: the editor's unsaved ones where it has any, the saved ones
|
|
21
|
+
* otherwise. Reopening the dialog therefore shows what the operator last entered
|
|
22
|
+
* rather than what the platform still holds.
|
|
23
|
+
*/
|
|
24
|
+
current: WorkflowSettingsValues;
|
|
14
25
|
/** Tag suggestions (tags already used across the tenant's workflows). */
|
|
15
26
|
tagSuggestions?: string[];
|
|
16
27
|
};
|
|
17
28
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
29
|
"update:open": (args_0: boolean) => any;
|
|
19
|
-
|
|
20
|
-
"save-meta": (meta: {
|
|
21
|
-
name: string;
|
|
22
|
-
description: string;
|
|
23
|
-
tags: string[];
|
|
24
|
-
icon: WorkflowIcon | null;
|
|
25
|
-
}) => any;
|
|
30
|
+
apply: (values: WorkflowSettingsValues) => any;
|
|
26
31
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
27
32
|
"onUpdate:open"?: ((args_0: boolean) => any) | undefined;
|
|
28
|
-
|
|
29
|
-
"onSave-meta"?: ((meta: {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
tags: string[];
|
|
33
|
-
icon: WorkflowIcon | null;
|
|
34
|
-
}) => any) | undefined;
|
|
33
|
+
onApply?: ((values: WorkflowSettingsValues) => any) | undefined;
|
|
35
34
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
36
35
|
export default _default;
|
|
@@ -5,6 +5,7 @@ const props = defineProps({
|
|
|
5
5
|
suggestions: { type: Array, required: false, default: () => [] },
|
|
6
6
|
inputId: { type: String, required: false }
|
|
7
7
|
});
|
|
8
|
+
const t = useIntegrationsT();
|
|
8
9
|
const draft = ref("");
|
|
9
10
|
const inputRef = ref(null);
|
|
10
11
|
const listId = useId();
|
|
@@ -15,7 +16,7 @@ function commit() {
|
|
|
15
16
|
if (!model.value.includes(tag)) model.value = [...model.value, tag];
|
|
16
17
|
}
|
|
17
18
|
function remove(tag) {
|
|
18
|
-
model.value = model.value.filter((
|
|
19
|
+
model.value = model.value.filter((t2) => t2 !== tag);
|
|
19
20
|
}
|
|
20
21
|
function onKeydown(e) {
|
|
21
22
|
if (e.key === "Enter" || e.key === ",") {
|
|
@@ -33,17 +34,17 @@ function onKeydown(e) {
|
|
|
33
34
|
@click="inputRef?.focus()"
|
|
34
35
|
>
|
|
35
36
|
<span
|
|
36
|
-
v-for="
|
|
37
|
-
:key="
|
|
37
|
+
v-for="tag in model"
|
|
38
|
+
:key="tag"
|
|
38
39
|
class="inline-flex items-center gap-1 rounded-md border border-black/[0.06] bg-muted/50 px-1.5 py-0.5 text-xs text-foreground"
|
|
39
40
|
>
|
|
40
|
-
{{
|
|
41
|
+
{{ tag }}
|
|
41
42
|
<button
|
|
42
43
|
type="button"
|
|
43
44
|
class="text-muted-foreground transition hover:text-foreground"
|
|
44
|
-
:title="
|
|
45
|
-
:aria-label="
|
|
46
|
-
@click.stop="remove(
|
|
45
|
+
:title="t('integration.workflows.tagsInput.action.remove', { tag })"
|
|
46
|
+
:aria-label="t('integration.workflows.tagsInput.action.remove', { tag })"
|
|
47
|
+
@click.stop="remove(tag)"
|
|
47
48
|
>
|
|
48
49
|
×
|
|
49
50
|
</button>
|
|
@@ -57,7 +58,7 @@ function onKeydown(e) {
|
|
|
57
58
|
type="text"
|
|
58
59
|
:list="listId"
|
|
59
60
|
class="h-6 min-w-24 flex-1 bg-transparent text-sm focus:outline-none"
|
|
60
|
-
:placeholder="model.length ? '
|
|
61
|
+
:placeholder="model.length ? t('integration.workflows.tagsInput.placeholder.add') : t('integration.workflows.tagsInput.placeholder.addFirst')"
|
|
61
62
|
@keydown="onKeydown"
|
|
62
63
|
@blur="commit()"
|
|
63
64
|
>
|