@revenexx/studio-integrations 0.6.0 → 0.7.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +31 -0
  3. package/dist/module.d.mts +17 -1
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +10 -2
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +29 -28
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
  8. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
  9. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
  10. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
  11. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
  12. package/dist/runtime/components/Integrations/ExpressionInput.vue +16 -15
  13. package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
  14. package/dist/runtime/components/Integrations/Layout.vue +28 -3
  15. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +15 -14
  16. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
  17. package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
  18. package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
  19. package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
  20. package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
  21. package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
  22. package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
  23. package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
  24. package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
  25. package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
  26. package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
  27. package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
  28. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
  29. package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
  30. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
  31. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
  32. package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
  33. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
  34. package/dist/runtime/components/Integrations/StickyNoteView.vue +37 -23
  35. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
  36. package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
  37. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
  38. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
  39. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
  40. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
  41. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
  42. package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
  43. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
  44. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
  45. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
  46. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
  47. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
  48. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
  49. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
  50. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
  51. package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
  52. package/dist/runtime/composables/integrationsApi.js +4 -2
  53. package/dist/runtime/composables/useCredentialTypes.js +4 -2
  54. package/dist/runtime/composables/useIntegrationsCredentials.js +6 -4
  55. package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
  56. package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
  57. package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
  58. package/dist/runtime/composables/useIntegrationsNodes.js +3 -1
  59. package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
  60. package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
  61. package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
  62. package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
  63. package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
  64. package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
  65. package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
  66. package/dist/runtime/composables/useIntegrationsT.js +9 -0
  67. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
  68. package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
  69. package/dist/runtime/composables/useIntegrationsTriggers.js +4 -2
  70. package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
  71. package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
  72. package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
  73. package/dist/runtime/data/integrationNodeCatalog.js +14 -0
  74. package/dist/runtime/data/integrationTemplates.d.ts +9 -0
  75. package/dist/runtime/data/integrationTemplates.js +7 -1
  76. package/dist/runtime/data/payloadForm.js +2 -1
  77. package/dist/runtime/data/workflowIconCatalog.js +2 -1
  78. package/dist/runtime/i18n/messages/de.json +1207 -0
  79. package/dist/runtime/i18n/messages/en.json +1207 -0
  80. package/dist/runtime/i18n/translate.d.ts +83 -0
  81. package/dist/runtime/i18n/translate.js +41 -0
  82. package/dist/runtime/pages/integrations/audit.vue +52 -34
  83. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
  84. package/dist/runtime/pages/integrations/credentials.vue +70 -52
  85. package/dist/runtime/pages/integrations/index.vue +50 -56
  86. package/dist/runtime/pages/integrations/runs.vue +33 -21
  87. package/dist/runtime/pages/integrations/schedules.vue +49 -28
  88. package/dist/runtime/pages/integrations/secrets.vue +34 -34
  89. package/dist/runtime/pages/integrations/templates/[slug].vue +77 -68
  90. package/dist/runtime/pages/integrations/templates/index.vue +35 -27
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
  92. package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
  93. package/dist/runtime/plugins/locale.d.ts +10 -0
  94. package/dist/runtime/plugins/locale.js +6 -0
  95. package/dist/runtime/plugins/realtime.client.d.ts +32 -0
  96. package/dist/runtime/plugins/realtime.client.js +4 -0
  97. package/dist/runtime/utils/credentialState.d.ts +24 -1
  98. package/dist/runtime/utils/credentialState.js +26 -11
  99. package/dist/runtime/utils/localize.d.ts +18 -5
  100. package/dist/runtime/utils/localize.js +32 -9
  101. package/dist/runtime/utils/pollBudget.d.ts +72 -0
  102. package/dist/runtime/utils/pollBudget.js +17 -0
  103. package/dist/runtime/utils/schedule.d.ts +0 -25
  104. package/dist/runtime/utils/schedule.js +37 -25
  105. package/dist/runtime/utils/statusStyle.d.ts +26 -2
  106. package/dist/runtime/utils/statusStyle.js +9 -1
  107. package/dist/runtime/utils/validationFindings.d.ts +2 -2
  108. package/dist/runtime/utils/validationFindings.js +4 -3
  109. package/dist/types.d.mts +2 -6
  110. package/package.json +3 -2
@@ -4,11 +4,13 @@ import { Download as DownloadIcon, RefreshCircle as RefreshIcon } from "#solar-i
4
4
  import { Badge, Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@revenexx/studio";
5
5
  import { runStatusLabel, statusClass } from "../../utils/statusStyle";
6
6
  import { IntegrationsApiError } from "../../utils/apiError";
7
+ import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
7
8
  const props = defineProps({
8
9
  run: { type: Object, required: true },
9
10
  view: { type: String, required: false, default: "details" }
10
11
  });
11
12
  const emit = defineEmits(["changed"]);
13
+ const t = useIntegrationsT();
12
14
  const {
13
15
  getRun,
14
16
  getDetails,
@@ -23,6 +25,9 @@ const {
23
25
  const DETAILS_POLL_MS = 3e3;
24
26
  const HISTORY_POLL_MS = 1e4;
25
27
  const HISTORY_PAGE_SIZE = 50;
28
+ const { delivering: realtimeDelivering, runTick, lastRunEvent } = useIntegrationsRealtime();
29
+ const summaryBudget = createPollBudget({ intervalMs: DETAILS_POLL_MS, liveIntervalMs: SAFETY_POLL_MS });
30
+ const historyBudget = createPollBudget({ intervalMs: HISTORY_POLL_MS, liveIntervalMs: SAFETY_POLL_MS });
26
31
  const COCKPIT_TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
27
32
  const API_TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "terminated", "timed_out"]);
28
33
  const runData = ref(null);
@@ -127,9 +132,9 @@ function eventAttributes(ev) {
127
132
  return pick(ev, ["attributes"]);
128
133
  }
129
134
  function eventColor(type) {
130
- const t = type.toLowerCase();
131
- if (t.includes("failed") || t.includes("terminated")) return "text-rose-600";
132
- if (t.includes("completed")) return "text-emerald-600";
135
+ const t2 = type.toLowerCase();
136
+ if (t2.includes("failed") || t2.includes("terminated")) return "text-rose-600";
137
+ if (t2.includes("completed")) return "text-emerald-600";
133
138
  return "text-foreground";
134
139
  }
135
140
  function statusOf(e) {
@@ -141,29 +146,29 @@ function genericMsg(e, fallback) {
141
146
  }
142
147
  function detailsErrMsg(e) {
143
148
  const s = statusOf(e);
144
- if (s === 410) return "History is no longer available (retention expired and no snapshot).";
145
- if (s === 503) return "Temporal is currently unavailable.";
146
- return genericMsg(e, "Could not load Temporal details.");
149
+ if (s === 410) return t("integration.runs.detail.error.details.expired");
150
+ if (s === 503) return t("integration.runs.detail.error.details.unavailable");
151
+ return genericMsg(e, t("integration.runs.detail.error.details.generic"));
147
152
  }
148
153
  function historyErrMsg(e) {
149
154
  const s = statusOf(e);
150
- if (s === 410) return "History is no longer available.";
151
- if (s === 422) return "Pagination cursor invalidated \u2014 reset.";
152
- return genericMsg(e, "Could not load history.");
155
+ if (s === 410) return t("integration.runs.detail.error.history.expired");
156
+ if (s === 422) return t("integration.runs.detail.error.history.cursorInvalid");
157
+ return genericMsg(e, t("integration.runs.detail.error.history.generic"));
153
158
  }
154
159
  function stackErrMsg(e) {
155
160
  const s = statusOf(e);
156
- if (s === 409) return "Workflow is no longer running.";
157
- if (s === 410) return "Unknown to Temporal.";
158
- if (s === 503) return "Temporal unavailable.";
159
- return genericMsg(e, "Could not load stack trace.");
161
+ if (s === 409) return t("integration.runs.detail.error.stack.notRunning");
162
+ if (s === 410) return t("integration.runs.detail.error.stack.unknown");
163
+ if (s === 503) return t("integration.runs.detail.error.stack.unavailable");
164
+ return genericMsg(e, t("integration.runs.detail.error.stack.generic"));
160
165
  }
161
166
  function stepsErrMsg(e) {
162
167
  const s = statusOf(e);
163
- if (s === 409) return "Run has not been handed off to Temporal yet.";
164
- if (s === 410) return "Execution history is no longer available.";
165
- if (s === 503) return "Temporal is currently unavailable.";
166
- return genericMsg(e, "Could not load steps.");
168
+ if (s === 409) return t("integration.runs.detail.notHandedOff");
169
+ if (s === 410) return t("integration.runs.detail.error.steps.expired");
170
+ if (s === 503) return t("integration.runs.detail.error.details.unavailable");
171
+ return genericMsg(e, t("integration.runs.detail.error.steps.generic"));
167
172
  }
168
173
  function fmtMs(ms) {
169
174
  if (ms == null) return "\u2014";
@@ -174,7 +179,7 @@ function fmtMs(ms) {
174
179
  function activityName(a) {
175
180
  const at = a ? a.activity_type ?? a.activityType : void 0;
176
181
  if (typeof at === "string") return at;
177
- return pickStr(at, ["name"]) ?? "Activity";
182
+ return pickStr(at, ["name"]) ?? t("integration.runs.detail.timeline.label.activity");
178
183
  }
179
184
  const timeline = computed(() => {
180
185
  const evs = timelineEvents.value;
@@ -189,7 +194,7 @@ const timeline = computed(() => {
189
194
  const ts = timeOf(ev);
190
195
  if (!Number.isFinite(ts)) continue;
191
196
  if (type === "workflow_execution_started") {
192
- wf = { label: "Workflow", kind: "workflow", sched: ts, start: ts, end: null, ok: true, events: [ev] };
197
+ wf = { label: t("integration.runs.detail.timeline.label.workflow"), kind: "workflow", sched: ts, start: ts, end: null, ok: true, events: [ev] };
193
198
  } else if (type.startsWith("workflow_execution_")) {
194
199
  if (wf) {
195
200
  wf.end = ts;
@@ -199,7 +204,7 @@ const timeline = computed(() => {
199
204
  } else if (type === "activity_task_scheduled") {
200
205
  cycles.set(eventId(ev), { label: activityName(attrsOf(ev)), kind: "activity", sched: ts, start: null, end: null, ok: true, events: [ev] });
201
206
  } else if (type === "workflow_task_scheduled") {
202
- cycles.set(eventId(ev), { label: "Workflow task", kind: "wf-task", sched: ts, start: null, end: null, ok: true, events: [ev] });
207
+ cycles.set(eventId(ev), { label: t("integration.runs.detail.timeline.label.workflowTask"), kind: "wf-task", sched: ts, start: null, end: null, ok: true, events: [ev] });
203
208
  } else if (type === "activity_task_started" || type === "workflow_task_started") {
204
209
  const c = cycles.get(refOf(ev, ["scheduled_event_id", "scheduledEventId"]));
205
210
  if (c) {
@@ -214,7 +219,7 @@ const timeline = computed(() => {
214
219
  c.events.push(ev);
215
220
  }
216
221
  } else if (type === "timer_started") {
217
- cycles.set(eventId(ev), { label: "Timer", kind: "timer", sched: ts, start: ts, end: null, ok: true, events: [ev] });
222
+ cycles.set(eventId(ev), { label: t("integration.runs.detail.timeline.label.timer"), kind: "timer", sched: ts, start: ts, end: null, ok: true, events: [ev] });
218
223
  } else if (type === "timer_fired" || type === "timer_canceled") {
219
224
  const c = cycles.get(refOf(ev, ["started_event_id", "startedEventId"]));
220
225
  if (c) {
@@ -266,8 +271,8 @@ function timelineBarClass(row) {
266
271
  }
267
272
  const timelineDetail = ref(null);
268
273
  function timelineRowStatus(row) {
269
- if (row.durationMs == null) return "running";
270
- return row.ok ? "succeeded" : "failed";
274
+ if (row.durationMs == null) return t("integration.runs.status.running");
275
+ return t(row.ok ? "integration.runs.status.succeeded" : "integration.runs.status.failed");
271
276
  }
272
277
  function fmtClock(ms) {
273
278
  try {
@@ -277,6 +282,7 @@ function fmtClock(ms) {
277
282
  }
278
283
  }
279
284
  async function loadSummary() {
285
+ summaryBudget.spend();
280
286
  summaryLoading.value = true;
281
287
  summaryError.value = null;
282
288
  try {
@@ -353,6 +359,7 @@ async function loadTimelineEvents() {
353
359
  historyError.value = null;
354
360
  return;
355
361
  }
362
+ historyBudget.spend();
356
363
  if (!timelineEvents.value.length) historyLoading.value = true;
357
364
  historyError.value = null;
358
365
  try {
@@ -389,6 +396,7 @@ function startPollingIfNeeded() {
389
396
  stopPolling();
390
397
  if (isTerminal.value) return;
391
398
  summaryTimer = setInterval(() => {
399
+ if (!summaryBudget.due(realtimeDelivering.value)) return;
392
400
  void loadSummary().then(() => {
393
401
  if (isTerminal.value) {
394
402
  stopPolling();
@@ -399,9 +407,23 @@ function startPollingIfNeeded() {
399
407
  });
400
408
  }, DETAILS_POLL_MS);
401
409
  historyTimer = setInterval(() => {
410
+ if (!historyBudget.due(realtimeDelivering.value)) return;
402
411
  void loadTimelineEvents();
403
412
  }, HISTORY_POLL_MS);
404
413
  }
414
+ function refreshFromEvent() {
415
+ void loadSummary().then(() => {
416
+ if (isTerminal.value) stopPolling();
417
+ void loadDetails();
418
+ void loadSteps();
419
+ });
420
+ void loadTimelineEvents();
421
+ }
422
+ watch(runTick, () => {
423
+ const event = lastRunEvent.value;
424
+ if (event && event.topicId !== props.run.id) return;
425
+ refreshFromEvent();
426
+ });
405
427
  function resetState() {
406
428
  runData.value = null;
407
429
  summaryError.value = null;
@@ -422,12 +444,42 @@ function resetState() {
422
444
  actionError.value = null;
423
445
  actionNotice.value = null;
424
446
  }
425
- const ACTION_LABELS = {
426
- cancel: "Cancel",
427
- terminate: "Terminate",
428
- retry: "Retry",
429
- resume: "Resume"
430
- };
447
+ function actionLabel(a) {
448
+ switch (a) {
449
+ case "cancel":
450
+ return t("integration.runs.action.cancel");
451
+ case "terminate":
452
+ return t("integration.runs.action.terminate");
453
+ case "retry":
454
+ return t("integration.runs.action.retry");
455
+ case "resume":
456
+ return t("integration.runs.action.resume");
457
+ }
458
+ }
459
+ function confirmQuestion(a) {
460
+ switch (a) {
461
+ case "cancel":
462
+ return t("integration.runs.confirm.cancel");
463
+ case "terminate":
464
+ return t("integration.runs.confirm.terminate");
465
+ case "retry":
466
+ return t("integration.runs.confirm.retry");
467
+ case "resume":
468
+ return t("integration.runs.confirm.resume");
469
+ }
470
+ }
471
+ function actionErrorFallback(a) {
472
+ switch (a) {
473
+ case "cancel":
474
+ return t("integration.runs.error.cancel");
475
+ case "terminate":
476
+ return t("integration.runs.error.terminate");
477
+ case "retry":
478
+ return t("integration.runs.error.retry");
479
+ case "resume":
480
+ return t("integration.runs.error.resume");
481
+ }
482
+ }
431
483
  const apiStatusRaw = computed(() => pickStr(runData.value, ["status"]));
432
484
  const isActive = computed(() => !isTerminal.value);
433
485
  const canStop = computed(() => isActive.value && hasTemporal.value);
@@ -476,13 +528,13 @@ async function confirmAction() {
476
528
  break;
477
529
  }
478
530
  const newId = pickStr(res, ["id"]);
479
- actionNotice.value = (a === "retry" || a === "resume") && newId && newId !== String(props.run.id) ? `Started run #${newId}.` : `${ACTION_LABELS[a]} requested.`;
531
+ actionNotice.value = (a === "retry" || a === "resume") && newId && newId !== String(props.run.id) ? t("integration.runs.notice.started", { id: newId }) : t("integration.runs.notice.requested", { action: actionLabel(a) });
480
532
  armed.value = null;
481
533
  await loadAll();
482
534
  startPollingIfNeeded();
483
535
  emit("changed");
484
536
  } catch (e) {
485
- actionError.value = genericMsg(e, `Could not ${ACTION_LABELS[a].toLowerCase()} the run.`);
537
+ actionError.value = genericMsg(e, actionErrorFallback(a));
486
538
  } finally {
487
539
  actionBusy.value = false;
488
540
  }
@@ -498,7 +550,7 @@ async function confirmAction() {
498
550
  <div v-if="hasActions" class="flex items-center gap-2">
499
551
  <!-- confirm step: one click arms, second confirms -->
500
552
  <template v-if="armed">
501
- <span class="text-sm text-muted-foreground">{{ ACTION_LABELS[armed] }} this run?</span>
553
+ <span class="text-sm text-muted-foreground">{{ confirmQuestion(armed) }}</span>
502
554
  <Button
503
555
  size="sm"
504
556
  :class="armed === 'terminate' ? 'bg-rose-600 hover:bg-rose-700 text-white' : ''"
@@ -506,14 +558,14 @@ async function confirmAction() {
506
558
  @click="confirmAction"
507
559
  >
508
560
  <RefreshIcon v-if="actionBusy" :size="14" class="mr-1 animate-spin" />
509
- Confirm
561
+ {{ t("integration.common.action.confirm") }}
510
562
  </Button>
511
- <Button variant="ghost" size="sm" :disabled="actionBusy" @click="disarm">Back</Button>
563
+ <Button variant="ghost" size="sm" :disabled="actionBusy" @click="disarm">{{ t("integration.runs.action.back") }}</Button>
512
564
  </template>
513
565
  <template v-else>
514
- <Button v-if="canStop" variant="outline" size="sm" @click="arm('cancel')">Cancel run</Button>
515
- <Button v-if="canStop" variant="outline" size="sm" class="text-rose-600 hover:bg-rose-500/10" @click="arm('terminate')">Terminate</Button>
516
- <Button v-if="canResume" variant="outline" size="sm" @click="arm('resume')">Resume</Button>
566
+ <Button v-if="canStop" variant="outline" size="sm" @click="arm('cancel')">{{ actionLabel("cancel") }}</Button>
567
+ <Button v-if="canStop" variant="outline" size="sm" class="text-rose-600 hover:bg-rose-500/10" @click="arm('terminate')">{{ actionLabel("terminate") }}</Button>
568
+ <Button v-if="canResume" variant="outline" size="sm" @click="arm('resume')">{{ actionLabel("resume") }}</Button>
517
569
  </template>
518
570
  </div>
519
571
 
@@ -529,7 +581,7 @@ async function confirmAction() {
529
581
  <section class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
530
582
  <div class="grid gap-x-6 gap-y-3 sm:grid-cols-2 xl:grid-cols-4">
531
583
  <div>
532
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Status</div>
584
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.status") }}</div>
533
585
  <div class="mt-0.5">
534
586
  <Badge variant="outline" :class="['border text-xs font-medium uppercase', statusClass(dStatus ?? run.status)]">
535
587
  {{ runStatusLabel(dStatus ?? run.status) }}
@@ -537,31 +589,31 @@ async function confirmAction() {
537
589
  </div>
538
590
  </div>
539
591
  <div>
540
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Started</div>
592
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.started") }}</div>
541
593
  <div class="tabular-nums">{{ fmt(startedAt) }}</div>
542
594
  </div>
543
595
  <div>
544
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Completed</div>
596
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.completed") }}</div>
545
597
  <div class="tabular-nums">{{ fmt(completedAt) }}</div>
546
598
  </div>
547
599
  <div>
548
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">History Length</div>
600
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.historyLength") }}</div>
549
601
  <div class="tabular-nums">{{ dHistoryLength ?? "\u2014" }}</div>
550
602
  </div>
551
603
  <div class="min-w-0">
552
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Workflow</div>
604
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflow") }}</div>
553
605
  <div class="truncate font-mono text-xs leading-5" :title="temporalWorkflowId ?? '\u2014'">{{ temporalWorkflowId ?? "\u2014" }}</div>
554
606
  </div>
555
607
  <div class="min-w-0">
556
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Run ID</div>
608
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.runId") }}</div>
557
609
  <div class="truncate font-mono text-xs leading-5" :title="temporalRunId ?? '\u2014'">{{ temporalRunId ?? "\u2014" }}</div>
558
610
  </div>
559
611
  <div class="min-w-0">
560
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Workflow Type</div>
612
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.workflowType") }}</div>
561
613
  <div class="truncate font-mono text-xs leading-5">{{ dWorkflowType ?? "\u2014" }}</div>
562
614
  </div>
563
615
  <div class="min-w-0">
564
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Task Queue</div>
616
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.field.taskQueue") }}</div>
565
617
  <div class="truncate font-mono text-xs leading-5">{{ dTaskQueue ?? "\u2014" }}</div>
566
618
  </div>
567
619
  </div>
@@ -578,7 +630,7 @@ async function confirmAction() {
578
630
 
579
631
  <!-- Result — full width below the overview. -->
580
632
  <section v-if="hasResult" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-2">
581
- <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Result</h4>
633
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.result.title") }}</h4>
582
634
  <div v-if="resultLinks.length" class="flex flex-wrap gap-1.5">
583
635
  <a
584
636
  v-for="link in resultLinks"
@@ -598,18 +650,18 @@ async function confirmAction() {
598
650
  <!-- Stack Trace (non-terminal runs only, lazy) -->
599
651
  <section v-if="!isTerminal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
600
652
  <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
601
- <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Stack Trace</h4>
653
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.stackTrace.title") }}</h4>
602
654
  <div class="flex-1" />
603
655
  <Button variant="outline" size="sm" :disabled="stackLoading" @click="loadStack">
604
- {{ stackEnabled ? "\u21BB Reload" : "Load" }}
656
+ {{ stackEnabled ? t("integration.runs.detail.stackTrace.action.reload") : t("integration.runs.detail.stackTrace.action.load") }}
605
657
  </Button>
606
658
  </div>
607
659
 
608
660
  <p v-if="!stackEnabled" class="text-xs text-muted-foreground">
609
- Load to query the running worker for its current call stack.
661
+ {{ t("integration.runs.detail.stackTrace.hint") }}
610
662
  </p>
611
663
  <div v-else-if="stackLoading" class="flex items-center gap-2 text-muted-foreground">
612
- <RefreshIcon :size="14" class="animate-spin" /> Querying worker…
664
+ <RefreshIcon :size="14" class="animate-spin" /> {{ t("integration.runs.detail.stackTrace.loading") }}
613
665
  </div>
614
666
  <p v-else-if="stackError" class="text-rose-700">{{ stackError }}</p>
615
667
  <pre
@@ -624,18 +676,18 @@ async function confirmAction() {
624
676
  <!-- Steps (per-node projection) -->
625
677
  <section v-if="hasTemporal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
626
678
  <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
627
- <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Steps</h4>
679
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.steps.title") }}</h4>
628
680
  <div class="flex-1" />
629
681
  <Button variant="outline" size="sm" :disabled="stepsLoading" @click="toggleStepPayloads">
630
- {{ stepsWithPayloads ? "Hide payloads" : "Show payloads" }}
682
+ {{ stepsWithPayloads ? t("integration.runs.detail.steps.action.hidePayloads") : t("integration.runs.detail.steps.action.showPayloads") }}
631
683
  </Button>
632
684
  </div>
633
685
 
634
686
  <p v-if="stepsError" class="text-rose-700">{{ stepsError }}</p>
635
687
  <div v-else-if="stepsLoading && !steps.length" class="flex items-center gap-2 text-muted-foreground">
636
- <RefreshIcon :size="14" class="animate-spin" /> Loading steps…
688
+ <RefreshIcon :size="14" class="animate-spin" /> {{ t("integration.runs.detail.steps.loading") }}
637
689
  </div>
638
- <p v-else-if="!steps.length" class="text-muted-foreground">No steps projected yet.</p>
690
+ <p v-else-if="!steps.length" class="text-muted-foreground">{{ t("integration.runs.detail.steps.empty") }}</p>
639
691
  <ol v-else class="space-y-1">
640
692
  <li
641
693
  v-for="(st, i) in steps"
@@ -651,7 +703,7 @@ async function confirmAction() {
651
703
  <div class="min-w-0 flex-1">
652
704
  <div class="font-mono text-xs truncate">{{ st.node_slug || st.node_id }}</div>
653
705
  <div class="text-[10px] text-muted-foreground truncate">
654
- {{ st.node_id }}<template v-if="st.branch"> · branch {{ st.branch }}</template><template v-if="st.attempt"> · attempt {{ st.attempt + 1 }}</template>
706
+ {{ st.node_id }}<template v-if="st.branch"> · {{ t("integration.runs.detail.steps.branchLabel") }} {{ st.branch }}</template><template v-if="st.attempt"> · {{ t("integration.runs.detail.steps.attemptLabel") }} {{ st.attempt + 1 }}</template>
655
707
  </div>
656
708
  </div>
657
709
  <span class="text-xs text-muted-foreground tabular-nums shrink-0">{{ fmtMs(st.duration_ms) }}</span>
@@ -663,16 +715,16 @@ async function confirmAction() {
663
715
  <div v-if="expandedStep === i" class="px-3 pb-3 space-y-2">
664
716
  <template v-if="stepsWithPayloads">
665
717
  <div class="space-y-1">
666
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Input</div>
718
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.steps.field.input") }}</div>
667
719
  <pre class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-40">{{ st.input === void 0 ? "\u2014" : json(st.input) }}</pre>
668
720
  </div>
669
721
  <div class="space-y-1">
670
- <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Output</div>
722
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.steps.field.output") }}</div>
671
723
  <pre class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-40">{{ st.output === void 0 ? "\u2014" : json(st.output) }}</pre>
672
724
  </div>
673
725
  </template>
674
726
  <p v-else class="text-xs text-muted-foreground">
675
- Enable “Show payloads” to load this step's input and output.
727
+ {{ t("integration.runs.detail.steps.payloadHint") }}
676
728
  </p>
677
729
  </div>
678
730
  </li>
@@ -683,14 +735,14 @@ async function confirmAction() {
683
735
  opens the underlying events in a lightbox. -->
684
736
  <section v-if="hasTemporal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
685
737
  <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
686
- <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">History</h4>
738
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">{{ t("integration.runs.detail.history.title") }}</h4>
687
739
  </div>
688
740
 
689
741
  <p v-if="historyError" class="text-rose-700">{{ historyError }}</p>
690
742
  <div v-else-if="historyLoading && !timeline" class="flex items-center gap-2 text-muted-foreground">
691
- <RefreshIcon :size="14" class="animate-spin" /> Loading history…
743
+ <RefreshIcon :size="14" class="animate-spin" /> {{ t("integration.runs.detail.history.loading") }}
692
744
  </div>
693
- <p v-else-if="!timeline" class="text-muted-foreground">No history events.</p>
745
+ <p v-else-if="!timeline" class="text-muted-foreground">{{ t("integration.runs.detail.history.empty") }}</p>
694
746
  <div v-else class="rounded-md border border-black/[0.05] bg-slate-50/60 p-2">
695
747
  <div class="relative">
696
748
  <div class="pointer-events-none absolute inset-y-0 left-1/4 border-l border-black/[0.05]" />
@@ -735,7 +787,7 @@ async function confirmAction() {
735
787
  </section>
736
788
 
737
789
  <p v-if="!hasTemporal" class="text-muted-foreground">
738
- Run has not been handed off to Temporal yet.
790
+ {{ t("integration.runs.detail.notHandedOff") }}
739
791
  </p>
740
792
  </template>
741
793
 
@@ -1,12 +1,13 @@
1
1
  <script setup>
2
2
  import { computed, ref, useId, watch } from "vue";
3
- import { Paperclip, TrashBinMinimalistic as Trash2 } from "#solar-icons";
3
+ import { Paperclip, TrashBinMinimalistic as TrashBin } from "#solar-icons";
4
4
  import { Input, Switch, Textarea } from "@revenexx/studio";
5
5
  import { buildPayload, payloadFieldsFromBlob, splitPayload } from "../../data/payloadForm";
6
6
  const props = defineProps({
7
7
  blob: { type: Object, required: true }
8
8
  });
9
9
  const model = defineModel({ type: String, ...{ default: "{}" } });
10
+ const t = useIntegrationsT();
10
11
  const fields = computed(() => payloadFieldsFromBlob(props.blob));
11
12
  const uid = useId();
12
13
  const fieldId = (path) => `${uid}-${path}`;
@@ -47,7 +48,7 @@ function toggleExpert(on) {
47
48
  return;
48
49
  }
49
50
  if (!hydrateForm()) {
50
- parseNote.value = "The JSON does not parse \u2014 fix it before switching back to the form.";
51
+ parseNote.value = t("integration.runs.payload.parseError");
51
52
  return;
52
53
  }
53
54
  parseNote.value = null;
@@ -96,17 +97,17 @@ function prettyBytes(n) {
96
97
  <div class="space-y-2">
97
98
  <div v-if="fields.length" class="flex items-center justify-between">
98
99
  <span class="text-xs font-medium text-muted-foreground">
99
- {{ expert ? "Trigger payload (JSON)" : "Trigger payload" }}
100
+ {{ expert ? t("integration.runs.payload.label.json") : t("integration.runs.payload.label.form") }}
100
101
  </span>
101
102
  <!-- The Switch is a `button role="switch"`, and HTML-AAM does not name a
102
103
  button from a wrapping <label> — Chromium does, other engines need
103
104
  not. The explicit name is what the promise rests on. -->
104
105
  <label class="flex items-center gap-1.5 text-xs text-muted-foreground">
105
- <Switch aria-label="Expert (JSON)" :model-value="expert" @update:model-value="toggleExpert" />
106
- Expert (JSON)
106
+ <Switch :aria-label="t('integration.runs.payload.expertToggle.label')" :model-value="expert" @update:model-value="toggleExpert" />
107
+ {{ t("integration.runs.payload.expertToggle.label") }}
107
108
  </label>
108
109
  </div>
109
- <span v-else class="text-xs font-medium text-muted-foreground">Trigger payload (JSON)</span>
110
+ <span v-else class="text-xs font-medium text-muted-foreground">{{ t("integration.runs.payload.label.json") }}</span>
110
111
 
111
112
  <!-- ── Form mode: fields derived from the blob's payload references ──── -->
112
113
  <div v-if="!expert && fields.length" class="space-y-3">
@@ -127,7 +128,7 @@ function prettyBytes(n) {
127
128
  @drop.prevent="onDrop(field.path, $event)"
128
129
  >
129
130
  <Paperclip :size="14" />
130
- Drop a file here or click to choose
131
+ {{ t("integration.runs.payload.file.dropHint") }}
131
132
  <input
132
133
  type="file"
133
134
  :aria-label="field.label"
@@ -142,15 +143,15 @@ function prettyBytes(n) {
142
143
  <button
143
144
  type="button"
144
145
  class="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:bg-muted/60 hover:text-rose-600"
145
- title="Remove file"
146
- :aria-label="`Remove the file for ${field.label}`"
146
+ :title="t('integration.runs.payload.file.remove.title')"
147
+ :aria-label="t('integration.runs.payload.file.remove.aria', { field: field.label })"
147
148
  @click="detach(field.path)"
148
149
  >
149
- <Trash2 :size="13" />
150
+ <TrashBin :size="13" />
150
151
  </button>
151
152
  </div>
152
153
  <p v-if="files[field.path] && files[field.path].size > WARN_BYTES" class="text-xs text-amber-700">
153
- Large file — payloads beyond ~25 MB can exceed the workflow bus limits. Prefer the SFTP or IO routes for big files.
154
+ {{ t("integration.runs.payload.file.tooLarge") }}
154
155
  </p>
155
156
  </template>
156
157
 
@@ -167,7 +168,7 @@ function prettyBytes(n) {
167
168
  </template>
168
169
  </div>
169
170
  <p v-if="Object.keys(extras).length" class="text-xs text-muted-foreground">
170
- Additional payload keys from the expert view are kept: {{ Object.keys(extras).join(", ") }}
171
+ {{ t("integration.runs.payload.extrasNote", { keys: Object.keys(extras).join(", ") }) }}
171
172
  </p>
172
173
  </div>
173
174
 
@@ -175,7 +176,7 @@ function prettyBytes(n) {
175
176
  <template v-else>
176
177
  <Textarea
177
178
  v-model="model"
178
- aria-label="Trigger payload (JSON)"
179
+ :aria-label="t('integration.runs.payload.label.json')"
179
180
  :rows="8"
180
181
  spellcheck="false"
181
182
  class="font-mono text-xs"
@@ -17,6 +17,7 @@ defineProps({
17
17
  runId: { type: [String, null], required: false }
18
18
  });
19
19
  const emit = defineEmits(["update:open", "view-run"]);
20
+ const t = useIntegrationsT();
20
21
  </script>
21
22
 
22
23
  <template>
@@ -25,16 +26,16 @@ const emit = defineEmits(["update:open", "view-run"]);
25
26
  JSON <pre> scrolling INSIDE instead of blowing the dialog open. -->
26
27
  <DialogContent class="max-w-[94vw] overflow-hidden sm:max-w-4xl">
27
28
  <DialogHeader>
28
- <DialogTitle>{{ mode === "async" ? "Run started" : "Run response" }}</DialogTitle>
29
+ <DialogTitle>{{ mode === "async" ? t("integration.runs.response.title.async") : t("integration.runs.response.title.sync") }}</DialogTitle>
29
30
  <DialogDescription class="font-mono text-sm">
30
- {{ mode === "async" ? "Queued for background execution" : "Synchronous run result" }}
31
+ {{ mode === "async" ? t("integration.runs.response.description.async") : t("integration.runs.response.description.sync") }}
31
32
  </DialogDescription>
32
33
  </DialogHeader>
33
34
 
34
35
  <div class="min-w-0 max-w-full text-sm">
35
36
  <div v-if="loading" class="flex items-center gap-2 py-6 text-muted-foreground">
36
37
  <Loader2 :size="16" class="animate-spin" />
37
- {{ mode === "async" ? "Starting\u2026" : "Running\u2026" }}
38
+ {{ mode === "async" ? t("integration.runs.response.loading.async") : t("integration.runs.response.loading.sync") }}
38
39
  </div>
39
40
 
40
41
  <template v-else-if="error">
@@ -43,7 +44,7 @@ const emit = defineEmits(["update:open", "view-run"]);
43
44
  </div>
44
45
  <div v-if="runId" class="mt-3 flex justify-end">
45
46
  <Button class="h-8 text-sm" @click="emit('view-run', runId)">
46
- View run #{{ runId }}
47
+ {{ t("integration.runs.response.action.viewRunWithId", { id: runId }) }}
47
48
  </Button>
48
49
  </div>
49
50
  </template>
@@ -51,15 +52,15 @@ const emit = defineEmits(["update:open", "view-run"]);
51
52
  <template v-else-if="mode === 'async'">
52
53
  <div class="flex items-center gap-2 rounded-md border border-emerald-500/20 bg-emerald-500/5 px-3 py-2 text-emerald-700">
53
54
  <CheckCircle2 :size="15" class="shrink-0" />
54
- <span v-if="runId">Run #{{ runId }} started and is executing in the background.</span>
55
- <span v-else>Run started and is executing in the background.</span>
55
+ <span v-if="runId">{{ t("integration.runs.response.async.startedWithId", { id: runId }) }}</span>
56
+ <span v-else>{{ t("integration.runs.response.async.started") }}</span>
56
57
  </div>
57
58
  <div class="mt-3 flex justify-end gap-2">
58
59
  <Button variant="outline" class="h-8 text-sm" @click="emit('update:open', false)">
59
- Close
60
+ {{ t("integration.common.action.close") }}
60
61
  </Button>
61
62
  <Button v-if="runId" class="h-8 text-sm" @click="emit('view-run', runId)">
62
- View run
63
+ {{ t("integration.runs.response.action.viewRun") }}
63
64
  </Button>
64
65
  </div>
65
66
  </template>
@@ -70,13 +71,13 @@ const emit = defineEmits(["update:open", "view-run"]);
70
71
  >{{ JSON.stringify(response, null, 2) }}</pre>
71
72
  <div v-if="runId" class="mt-3 flex justify-end">
72
73
  <Button variant="outline" class="h-8 text-sm" @click="emit('view-run', runId)">
73
- View run
74
+ {{ t("integration.runs.response.action.viewRun") }}
74
75
  </Button>
75
76
  </div>
76
77
  </template>
77
78
 
78
79
  <p v-else class="py-6 text-muted-foreground">
79
- No response.
80
+ {{ t("integration.runs.response.empty") }}
80
81
  </p>
81
82
  </div>
82
83
  </DialogContent>
@@ -14,7 +14,6 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {
14
14
  onChanged?: (() => any) | undefined;
15
15
  }>, {
16
16
  loading: boolean;
17
- emptyText: string;
18
17
  pagination: boolean;
19
18
  showWorkflow: boolean;
20
19
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;