@revenexx/studio-integrations 1.1.1 → 1.3.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 (119) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/README.md +3 -1
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
  6. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
  7. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
  8. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
  9. package/dist/runtime/components/Integrations/ExpressionInput.vue +45 -19
  10. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
  11. package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
  12. package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
  13. package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
  14. package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
  15. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -2
  16. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
  17. package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
  18. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
  19. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
  20. package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
  21. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
  22. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
  23. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
  24. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
  25. package/dist/runtime/components/Integrations/RunActionsMenu.vue +2 -1
  26. package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
  27. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
  28. package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
  29. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
  30. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
  31. package/dist/runtime/components/Integrations/RunsTable.vue +129 -18
  32. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
  33. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
  34. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.d.vue.ts +16 -0
  35. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
  36. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
  37. package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -1
  38. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
  39. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
  40. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
  41. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
  42. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
  43. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
  44. package/dist/runtime/components/Integrations/WorkflowEditor.vue +78 -30
  45. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
  46. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
  47. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
  48. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
  49. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
  50. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
  51. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
  52. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
  53. package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
  54. package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
  55. package/dist/runtime/components/Integrations/canvasSides.js +22 -0
  56. package/dist/runtime/composables/useCredentialTypes.js +2 -6
  57. package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
  58. package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
  59. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
  60. package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
  61. package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
  62. package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
  63. package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
  64. package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
  65. package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
  66. package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
  67. package/dist/runtime/composables/useIntegrationsTemplates.js +7 -5
  68. package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
  69. package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -7
  70. package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
  71. package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
  72. package/dist/runtime/composables/useOAuthConnect.js +33 -0
  73. package/dist/runtime/data/expressionEditor.d.ts +17 -5
  74. package/dist/runtime/data/expressionEditor.js +11 -5
  75. package/dist/runtime/data/handleId.d.ts +61 -1
  76. package/dist/runtime/data/handleId.js +8 -1
  77. package/dist/runtime/data/integrationNodeCatalog.d.ts +25 -0
  78. package/dist/runtime/data/integrationNodeCatalog.js +8 -0
  79. package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
  80. package/dist/runtime/data/nodeVersionMove.js +65 -0
  81. package/dist/runtime/data/payloadForm.js +3 -2
  82. package/dist/runtime/i18n/messages/de.json +79 -12
  83. package/dist/runtime/i18n/messages/en.json +79 -12
  84. package/dist/runtime/pages/integrations/audit.vue +7 -6
  85. package/dist/runtime/pages/integrations/credentials.vue +9 -21
  86. package/dist/runtime/pages/integrations/index.vue +55 -8
  87. package/dist/runtime/pages/integrations/runs.vue +116 -7
  88. package/dist/runtime/pages/integrations/secrets.vue +2 -1
  89. package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
  90. package/dist/runtime/pages/integrations/templates/index.vue +18 -15
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +169 -24
  92. package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
  93. package/dist/runtime/types/integrations.d.ts +49 -1
  94. package/dist/runtime/utils/apiError.d.ts +21 -1
  95. package/dist/runtime/utils/apiError.js +1 -1
  96. package/dist/runtime/utils/buildSettle.d.ts +68 -0
  97. package/dist/runtime/utils/buildSettle.js +29 -0
  98. package/dist/runtime/utils/clipboard.d.ts +33 -0
  99. package/dist/runtime/utils/clipboard.js +11 -0
  100. package/dist/runtime/utils/deepClone.d.ts +41 -0
  101. package/dist/runtime/utils/deepClone.js +3 -0
  102. package/dist/runtime/utils/formatDuration.d.ts +44 -0
  103. package/dist/runtime/utils/formatDuration.js +13 -0
  104. package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
  105. package/dist/runtime/utils/nodeDisplayName.js +6 -0
  106. package/dist/runtime/utils/notify.d.ts +1 -1
  107. package/dist/runtime/utils/runEnvelope.d.ts +31 -0
  108. package/dist/runtime/utils/runEnvelope.js +12 -0
  109. package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
  110. package/dist/runtime/utils/runFeedQuery.js +66 -0
  111. package/dist/runtime/utils/runOutputs.d.ts +33 -0
  112. package/dist/runtime/utils/runOutputs.js +10 -0
  113. package/dist/runtime/utils/settingCondition.d.ts +48 -0
  114. package/dist/runtime/utils/settingCondition.js +84 -0
  115. package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
  116. package/dist/runtime/utils/stateNamespaceName.js +8 -0
  117. package/dist/runtime/utils/validationFindings.d.ts +26 -3
  118. package/dist/runtime/utils/validationFindings.js +18 -3
  119. package/package.json +1 -1
@@ -28,6 +28,7 @@
28
28
  "integration.audit.table.column.actor": "Actor",
29
29
  "integration.audit.table.column.resource": "Resource",
30
30
  "integration.audit.table.column.when": "When",
31
+ "integration.audit.toast.copyFailed": "Could not copy",
31
32
  "integration.audit.toast.copied": "Copied",
32
33
  "integration.canvas.badge.trigger": "Trigger",
33
34
  "integration.canvas.color.amber": "Amber",
@@ -70,9 +71,9 @@
70
71
  "integration.canvas.node.action.duplicate": "Duplicate",
71
72
  "integration.canvas.node.action.duplicateAria": "Duplicate node",
72
73
  "integration.canvas.node.badge.invalidConfig": "Config incomplete — {n} setting still needs fixing: {fields} | Config incomplete — {n} settings still need fixing: {fields}",
74
+ "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",
73
75
  "integration.canvas.node.badge.noBuiltCode": "Version {version} is not ready to use — this step cannot be tested",
74
76
  "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",
76
77
  "integration.canvas.node.handle.input": "Input",
77
78
  "integration.canvas.node.meta.versionTitle": "Version {version}",
78
79
  "integration.canvas.node.note.heading": "Note",
@@ -229,6 +230,7 @@
229
230
  "integration.credentials.sftpImport.empty": "No active platform SFTP accounts. Create one under Integration Studio → SFTP first.",
230
231
  "integration.credentials.sftpImport.error.importFailed": "Import failed.",
231
232
  "integration.credentials.sftpImport.error.readPassword": "Could not read the account password.",
233
+ "integration.credentials.sftpImport.state.loading": "Loading the SFTP accounts…",
232
234
  "integration.credentials.sftpImport.title": "Import platform SFTP account",
233
235
  "integration.credentials.shared.action.testConnection": "Connect",
234
236
  "integration.credentials.shared.authKind.apiKey": "API Key",
@@ -263,6 +265,7 @@
263
265
  "integration.dashboard.attention.healthy": "All workflows healthy.",
264
266
  "integration.dashboard.attention.missedRuns": "{n} missed run | {n} missed runs",
265
267
  "integration.dashboard.attention.missedRuns.hint": "Triggers fired while their workflow couldn't run — review & replay.",
268
+ "integration.dashboard.attention.state.loading": "Checking what needs attention…",
266
269
  "integration.dashboard.attention.title": "Needs attention",
267
270
  "integration.dashboard.head.title": "Dashboard · Integration Studio",
268
271
  "integration.dashboard.header.description": "Everything that connects Revenue Cloud to the outside world.",
@@ -276,7 +279,9 @@
276
279
  "integration.dashboard.kpi.runningNow.sub": "live executions in flight",
277
280
  "integration.dashboard.kpi.scheduled.label": "Scheduled",
278
281
  "integration.dashboard.kpi.scheduled.sub": "scheduled triggers active",
282
+ "integration.dashboard.kpi.state.loading": "Loading the key figures…",
279
283
  "integration.dashboard.runs.empty": "No runs yet.",
284
+ "integration.dashboard.runs.state.loading": "Loading the latest executions…",
280
285
  "integration.dashboard.runs.title": "Latest executions",
281
286
  "integration.dashboard.surface.events.hint": "Live platform events that workflows subscribe to.",
282
287
  "integration.dashboard.surface.events.label": "Event Stream",
@@ -288,6 +293,7 @@
288
293
  "integration.dashboard.surface.webhooks.hint": "Inbound HTTP endpoints that trigger workflows.",
289
294
  "integration.dashboard.surface.webhooks.label": "Webhooks",
290
295
  "integration.dashboard.templates.empty": "No templates registered yet.",
296
+ "integration.dashboard.templates.state.loading": "Loading templates…",
291
297
  "integration.dashboard.templates.title": "Start from a template",
292
298
  "integration.deadLetters.action.discard": "Discard",
293
299
  "integration.deadLetters.action.replay": "Replay",
@@ -300,6 +306,7 @@
300
306
  "integration.deadLetters.panel.title": "Missed runs",
301
307
  "integration.deadLetters.reason.fallback": "No reason recorded.",
302
308
  "integration.deadLetters.state.empty": "No missed runs. Every trigger firing made it into a run.",
309
+ "integration.deadLetters.state.loading": "Loading the missed runs…",
303
310
  "integration.deadLetters.status.discarded": "discarded",
304
311
  "integration.deadLetters.status.pending": "waiting",
305
312
  "integration.deadLetters.status.replayed": "replayed",
@@ -317,7 +324,8 @@
317
324
  "integration.editor.action.discard.label": "Cancel",
318
325
  "integration.editor.action.discard.title": "Discard unsaved changes",
319
326
  "integration.editor.action.duplicate.label": "Duplicate",
320
- "integration.editor.action.duplicate.title": "Duplicate this workflow",
327
+ "integration.editor.action.more.aria": "More actions for this workflow",
328
+ "integration.editor.action.more.label": "Actions",
321
329
  "integration.editor.action.pause.title": "Pause this workflow",
322
330
  "integration.editor.action.retry": "Retry",
323
331
  "integration.editor.action.save.label": "Save",
@@ -433,6 +441,7 @@
433
441
  "integration.editor.tab.editor": "Editor",
434
442
  "integration.editor.tab.history": "History",
435
443
  "integration.editor.tab.runs": "Runs",
444
+ "integration.editor.toast.deleted": "Deleted “{name}”",
436
445
  "integration.editor.toast.duplicated": "Duplicated as “{name}”",
437
446
  "integration.editor.tool.addGroup.label": "Group",
438
447
  "integration.editor.tool.addGroup.tooltip": "Add group frame — nodes dropped inside join it",
@@ -446,6 +455,8 @@
446
455
  "integration.editor.tool.select": "Select",
447
456
  "integration.editor.tool.settings.label": "Settings",
448
457
  "integration.editor.tool.settings.tooltip": "Workflow settings",
458
+ "integration.editor.tool.state.label": "State",
459
+ "integration.editor.tool.state.tooltip": "What this workflow remembers between runs",
449
460
  "integration.editor.tool.tidyUp": "Tidy up",
450
461
  "integration.editor.tool.variables.label": "Variables",
451
462
  "integration.editor.tool.variables.tooltip": "Workflow variables",
@@ -472,6 +483,7 @@
472
483
  "integration.history.preview.title.current": "Current version · revision {revision}",
473
484
  "integration.history.preview.title.revision": "Revision {revision} (read-only)",
474
485
  "integration.history.state.empty": "No archived revisions yet — save a change to create the first snapshot.",
486
+ "integration.history.state.loading": "Loading the version history…",
475
487
  "integration.history.toast.loadRevisionFailed": "Could not load the revision.",
476
488
  "integration.history.toast.restoreFailed": "Could not restore the revision.",
477
489
  "integration.history.toast.restored": "Revision {revision} restored",
@@ -519,8 +531,8 @@
519
531
  "integration.nodeConfig.dynamicSchema.reloading": "reloading fields…",
520
532
  "integration.nodeConfig.dynamicSchema.resolving": "Resolving fields…",
521
533
  "integration.nodeConfig.expression.badge.title": "Accepts ${{ … }} expressions",
522
- "integration.nodeConfig.expression.insertTitle": "Insert expression",
523
534
  "integration.nodeConfig.expression.insertAria": "Insert expression — available data for this setting",
535
+ "integration.nodeConfig.expression.insertTitle": "Insert expression",
524
536
  "integration.nodeConfig.expression.picker.aria": "Available data for this setting",
525
537
  "integration.nodeConfig.expression.picker.empty": "Nothing matches “{query}”.",
526
538
  "integration.nodeConfig.expression.picker.filterAria": "Filter available data",
@@ -586,6 +598,11 @@
586
598
  "integration.nodeConfig.resolve.error.generic": "Resolve failed",
587
599
  "integration.nodeConfig.secretRef.placeholder": "Pick a secret",
588
600
  "integration.nodeConfig.select.placeholder": "Pick one",
601
+ "integration.nodeConfig.state.action.new": "Declare a new namespace…",
602
+ "integration.nodeConfig.state.create.description": "It is added to this workflow's declarations as a {role} namespace and selected here.",
603
+ "integration.nodeConfig.state.create.title": "Declare a state namespace",
604
+ "integration.nodeConfig.state.empty": "This workflow declares none of this kind yet.",
605
+ "integration.nodeConfig.state.placeholder": "Choose a namespace…",
589
606
  "integration.nodeConfig.validation.max": "Must be ≤ {max}",
590
607
  "integration.nodeConfig.validation.maxLength": "Must be at most {max} characters",
591
608
  "integration.nodeConfig.validation.min": "Must be ≥ {min}",
@@ -631,9 +648,9 @@
631
648
  "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}.",
632
649
  "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.",
633
650
  "integration.nodeInspector.output.loopBody.heading": "The loop body",
634
- "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.",
635
- "integration.nodeInspector.output.loopBody.names": "These repeat: {names}",
636
651
  "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}",
652
+ "integration.nodeInspector.output.loopBody.names": "These repeat: {names}",
653
+ "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.",
637
654
  "integration.nodeInspector.output.pin.editAction": "Edit pinned sample",
638
655
  "integration.nodeInspector.output.pin.editorAria": "Pinned sample output",
639
656
  "integration.nodeInspector.output.pin.hint": "A pinned sample stands in for run data while you map downstream fields.",
@@ -728,6 +745,7 @@
728
745
  "integration.nodeInspector.toast.configCopyFailed": "Could not copy config",
729
746
  "integration.nodeInspector.toast.configPasted": "Config pasted",
730
747
  "integration.nodeInspector.toast.expressionCopied": "Expression copied. Click into a field first to insert it directly.",
748
+ "integration.nodeInspector.toast.expressionCopyFailed": "Could not copy the expression. Click into a field to insert it directly.",
731
749
  "integration.nodeInspector.toast.rotateFailed": "Could not rotate the secret.",
732
750
  "integration.nodeInspector.toast.rotateNoSecret": "The endpoint did not return a secret.",
733
751
  "integration.nodeInspector.toast.samplePinned": "Sample output pinned",
@@ -789,6 +807,20 @@
789
807
  "integration.nodeInspector.trigger.webhook.username.hint": "Optional — pins the Basic-auth username. The password is the endpoint secret.",
790
808
  "integration.nodeInspector.trigger.webhook.username.label": "Username",
791
809
  "integration.nodeInspector.trigger.webhook.username.placeholder": "Any username",
810
+ "integration.nodeInspector.version.available": "Version {version} is available.",
811
+ "integration.nodeInspector.version.change": "Change version",
812
+ "integration.nodeInspector.version.move.confirm": "Change version",
813
+ "integration.nodeInspector.version.move.description": "The step keeps its connections, its name and its note. What changes is the version — and any setting the chosen version does not have.",
814
+ "integration.nodeInspector.version.move.dropped": "Will be removed",
815
+ "integration.nodeInspector.version.move.lossless": "Every setting travels unchanged.",
816
+ "integration.nodeInspector.version.move.notComparable": "Version {version} is no longer in the catalogue, so what differs between the two cannot be compared. The list below is what arrives, not what was checked.",
817
+ "integration.nodeInspector.version.move.reason.changed": "changed between the versions",
818
+ "integration.nodeInspector.version.move.reason.nowRequired": "required, and not answered yet",
819
+ "integration.nodeInspector.version.move.review": "Worth checking afterwards",
820
+ "integration.nodeInspector.version.move.same": "This is the version the step is already on.",
821
+ "integration.nodeInspector.version.move.targetAria": "Version to change this step to",
822
+ "integration.nodeInspector.version.move.title": "Change this step's version",
823
+ "integration.nodeInspector.version.moveTo": "Change to version {version}",
792
824
  "integration.nodes.category.action": "Actions",
793
825
  "integration.nodes.category.control": "Control flow",
794
826
  "integration.nodes.category.io": "I/O",
@@ -908,13 +940,15 @@
908
940
  "integration.runs.payload.parseError": "The JSON does not parse — fix it before switching back to the form.",
909
941
  "integration.runs.response.action.viewRun": "View run",
910
942
  "integration.runs.response.action.viewRunWithId": "View run #{id}",
911
- "integration.runs.response.async.started": "Run started and is executing in the background.",
912
- "integration.runs.response.async.startedWithId": "Run #{id} started and is executing in the background.",
913
- "integration.runs.response.description.async": "Queued for background execution",
943
+ "integration.runs.response.async.started": "Run started.",
944
+ "integration.runs.response.async.startedWithId": "Run #{id} started.",
945
+ "integration.runs.response.description.async": "Started in the background",
914
946
  "integration.runs.response.description.sync": "Synchronous run result",
915
- "integration.runs.response.empty": "No response.",
947
+ "integration.runs.response.following": "Waiting for the run to finish…",
916
948
  "integration.runs.response.loading.async": "Starting…",
917
949
  "integration.runs.response.loading.sync": "Running…",
950
+ "integration.runs.response.nothingReturned": "The run returned nothing.",
951
+ "integration.runs.response.runNumber": "Run #{id}",
918
952
  "integration.runs.response.title.async": "Run started",
919
953
  "integration.runs.response.title.sync": "Run response",
920
954
  "integration.runs.status.cancelled": "cancelled",
@@ -929,6 +963,8 @@
929
963
  "integration.runs.table.column.trigger": "Trigger",
930
964
  "integration.runs.table.column.workflow": "Workflow",
931
965
  "integration.runs.table.empty": "No runs yet — kick off a workflow to populate this view.",
966
+ "integration.runs.table.empty.narrowed": "No runs match this narrowing.",
967
+ "integration.runs.table.filter.all": "All",
932
968
  "integration.runs.table.search.placeholder": "Search runs…",
933
969
  "integration.runs.tabs.runs.label": "Runs",
934
970
  "integration.runs.toast.action.requested": "{action} requested",
@@ -991,6 +1027,7 @@
991
1027
  "integration.secrets.error.loadFailed": "Could not load secrets.",
992
1028
  "integration.secrets.error.valueRequired": "Value is required.",
993
1029
  "integration.secrets.form.error.invalidKey": "Key must match ^[A-Z0-9_]+$ (uppercase letters, digits, underscores).",
1030
+ "integration.secrets.form.error.saveFailed": "Could not save the secret.",
994
1031
  "integration.secrets.form.key.label": "Key",
995
1032
  "integration.secrets.form.key.patternLabel": "Pattern:",
996
1033
  "integration.secrets.form.value.hide": "Hide value",
@@ -1129,7 +1166,12 @@
1129
1166
  "integration.workflows.create.template.noMatch": "Nothing matches “{query}”.",
1130
1167
  "integration.workflows.create.template.search.aria": "Search templates",
1131
1168
  "integration.workflows.create.template.search.placeholder": "Search templates…",
1169
+ "integration.workflows.create.template.state.loading": "Loading templates…",
1132
1170
  "integration.workflows.create.toast.templateError": "Could not create the workflow from this template.",
1171
+ "integration.workflows.delete.action.deleting": "Deleting…",
1172
+ "integration.workflows.delete.dialog.description": "and all its revision history will be permanently deleted.",
1173
+ "integration.workflows.delete.dialog.title": "Delete workflow?",
1174
+ "integration.workflows.delete.error.generic": "The workflow could not be deleted.",
1133
1175
  "integration.workflows.duplicate.dialog.description": "Creates a paused copy of “{name}” with all triggers off.",
1134
1176
  "integration.workflows.duplicate.dialog.title": "Duplicate workflow",
1135
1177
  "integration.workflows.duplicate.error.generic": "Could not duplicate the workflow.",
@@ -1167,6 +1209,34 @@
1167
1209
  "integration.workflows.settings.section.appearance": "Appearance",
1168
1210
  "integration.workflows.settings.section.execution": "Execution",
1169
1211
  "integration.workflows.settings.section.metadata": "Metadata",
1212
+ "integration.workflows.state.action.add": "Declare",
1213
+ "integration.workflows.state.action.done": "Done",
1214
+ "integration.workflows.state.action.remove": "Remove declaration",
1215
+ "integration.workflows.state.action.remove.hint": "Removing the declaration takes this workflow's access away. The stored entries stay where they are.",
1216
+ "integration.workflows.state.dialog.description": "What this workflow may remember between runs. It can only reach the namespaces declared here — anything else is refused when the workflow runs.",
1217
+ "integration.workflows.state.dialog.title": "State namespaces",
1218
+ "integration.workflows.state.error.invalidName": "Use lowercase letters, digits and . - _, starting with a letter — for example article or crm.customers.",
1219
+ "integration.workflows.state.error.nameExists": "This workflow already declares that namespace.",
1220
+ "integration.workflows.state.field.newName.label": "Declare a namespace",
1221
+ "integration.workflows.state.field.newName.placeholder": "article",
1222
+ "integration.workflows.state.field.newRole.label": "Role",
1223
+ "integration.workflows.state.field.newVisibility.label": "Visible to",
1224
+ "integration.workflows.state.field.visibility.label": "Visible to",
1225
+ "integration.workflows.state.role.cursor.hint": "Remembers how far an incremental import read. It only moves on when the run finishes cleanly.",
1226
+ "integration.workflows.state.role.cursor.label": "Cursor",
1227
+ "integration.workflows.state.role.dedupe.hint": "Marks something as already processed, so a repeated delivery is not booked twice.",
1228
+ "integration.workflows.state.role.dedupe.label": "Dedupe",
1229
+ "integration.workflows.state.role.digest.hint": "Remembers what was last written, so unchanged records can be skipped.",
1230
+ "integration.workflows.state.role.digest.label": "Digest",
1231
+ "integration.workflows.state.role.fixed": "The role is fixed once the namespace is declared: entries written with one meaning cannot be read back with another.",
1232
+ "integration.workflows.state.role.mapping.hint": "Correlates an id on each side, so a later run knows whether to create or update.",
1233
+ "integration.workflows.state.role.mapping.label": "Mapping",
1234
+ "integration.workflows.state.row.aria": "State namespace {namespace}",
1235
+ "integration.workflows.state.empty": "This workflow remembers nothing between runs yet.",
1236
+ "integration.workflows.state.visibility.private.hint": "Only this workflow reads and writes it, and it is deleted along with the workflow.",
1237
+ "integration.workflows.state.visibility.private.label": "This workflow",
1238
+ "integration.workflows.state.visibility.shared.hint": "Every workflow in this tenant reads and writes these entries, and the namespace stays when this workflow is deleted.",
1239
+ "integration.workflows.state.visibility.shared.label": "Every workflow",
1170
1240
  "integration.workflows.table.action.groupByTags": "Group by tags",
1171
1241
  "integration.workflows.table.column.build": "Build",
1172
1242
  "integration.workflows.table.column.lastRun": "Last run",
@@ -1175,9 +1245,6 @@
1175
1245
  "integration.workflows.table.column.status": "Status",
1176
1246
  "integration.workflows.table.column.triggers": "Triggers",
1177
1247
  "integration.workflows.table.column.updated": "Updated",
1178
- "integration.workflows.table.deleteDialog.action.deleting": "Deleting…",
1179
- "integration.workflows.table.deleteDialog.description": "and all its revision history will be permanently deleted.",
1180
- "integration.workflows.table.deleteDialog.title": "Delete workflow?",
1181
1248
  "integration.workflows.table.group.noTags": "No tags",
1182
1249
  "integration.workflows.table.lastRun.aria.never": "{name} last run: Never run",
1183
1250
  "integration.workflows.table.menu.action.activate": "Activate",
@@ -10,7 +10,7 @@ import {
10
10
  Play as Run
11
11
  } from "#solar-icons";
12
12
  import { Badge, Button } from "@revenexx/studio";
13
- import { toast } from "vue-sonner";
13
+ import { copyToClipboard } from "../../utils/clipboard";
14
14
  const t = useIntegrationsT();
15
15
  useHead({ title: () => t("integration.audit.head.title") });
16
16
  const { entries, loading, error, hasMore, load, loadMore } = useIntegrationsAudit();
@@ -76,11 +76,12 @@ const columns = computed(() => [
76
76
  { key: "when", label: t("integration.audit.table.column.when"), width: "180px", sortable: true, sortGetter: (e) => e.created_at }
77
77
  ]);
78
78
  const selected = ref(null);
79
- function copy(t2) {
80
- if (t2 && navigator.clipboard) {
81
- navigator.clipboard.writeText(t2);
82
- toast.success(t("integration.audit.toast.copied"));
83
- }
79
+ function copy(text) {
80
+ if (!text) return;
81
+ void copyToClipboard(text, {
82
+ copied: t("integration.audit.toast.copied"),
83
+ failed: t("integration.audit.toast.copyFailed")
84
+ });
84
85
  }
85
86
  const panelEl = ref(null);
86
87
  let opener = null;
@@ -31,7 +31,7 @@ import {
31
31
  SelectValue
32
32
  } from "@revenexx/studio";
33
33
  import { toast } from "../../utils/notify";
34
- import { apiFieldErrors, refusalReason } from "../../utils/apiError";
34
+ import { apiFieldErrors, errorReason } from "../../utils/apiError";
35
35
  import { CREDENTIAL_STATES, credentialState, credentialStateLabel } from "../../utils/credentialState";
36
36
  import { hasRevenexxSource } from "../../utils/revenexxCredentialSource";
37
37
  import { resolveLocalized } from "../../utils/localize";
@@ -177,35 +177,23 @@ async function submit() {
177
177
  }
178
178
  } catch (e) {
179
179
  fieldErrors.value = apiFieldErrors(e);
180
- saveError.value = refusalReason(e, t("integration.credentials.error.save"));
180
+ saveError.value = errorReason(e, t("integration.credentials.error.save"));
181
181
  toast.error(saveError.value);
182
182
  } finally {
183
183
  saving.value = false;
184
184
  }
185
185
  }
186
186
  const connecting = ref(null);
187
+ const { open: openAuthorisation } = useOAuthConnect(async (outcome) => {
188
+ await load(true);
189
+ if (outcome === "completed") toast.success(t("integration.credentials.toast.connected"));
190
+ });
187
191
  async function connect(c) {
188
192
  connecting.value = c.id;
189
193
  try {
190
- const authorizeUrl = await oauthAuthorizeUrl(c.id);
191
- const popup = window.open(authorizeUrl, "oauth-connect", "width=600,height=720");
192
- const onMessage = async (ev) => {
193
- if (ev.data?.type === "integrations-oauth-complete") {
194
- window.removeEventListener("message", onMessage);
195
- await load(true);
196
- toast.success(t("integration.credentials.toast.connected"));
197
- }
198
- };
199
- window.addEventListener("message", onMessage);
200
- const poll = window.setInterval(async () => {
201
- if (popup?.closed) {
202
- window.clearInterval(poll);
203
- window.removeEventListener("message", onMessage);
204
- await load(true);
205
- }
206
- }, 1e3);
194
+ openAuthorisation(await oauthAuthorizeUrl(c.id));
207
195
  } catch (e) {
208
- toast.error(e?.message ?? t("integration.credentials.error.oauthConnect"));
196
+ toast.error(errorReason(e, t("integration.credentials.error.oauthConnect")));
209
197
  } finally {
210
198
  connecting.value = null;
211
199
  }
@@ -221,7 +209,7 @@ async function confirmDelete() {
221
209
  toast.success(t("integration.credentials.toast.deleted"));
222
210
  pendingDelete.value = null;
223
211
  } catch (e) {
224
- toast.error(e?.message ?? t("integration.credentials.error.delete"));
212
+ toast.error(errorReason(e, t("integration.credentials.error.delete")));
225
213
  } finally {
226
214
  deleting.value = false;
227
215
  }
@@ -1,10 +1,12 @@
1
1
  <script setup>
2
2
  import { computed, ref, watch } from "vue";
3
+ import LoadingAnnouncement from "../../components/Integrations/LoadingAnnouncement.vue";
3
4
  import SurfaceCard from "../../components/Integrations/SurfaceCard.vue";
4
5
  import TemplateCard from "../../components/Integrations/TemplateCard.vue";
5
6
  import WorkflowCreateDialog from "../../components/Integrations/WorkflowCreateDialog.vue";
6
7
  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
8
  import { Badge, Button } from "@revenexx/studio";
9
+ import { formatDuration } from "../../utils/formatDuration";
8
10
  import { servedPaths } from "../../utils/servedPaths";
9
11
  import { RUN_STATUS_COLUMN_WIDTH, runStatusClass, runStatusLabel } from "../../utils/statusStyle";
10
12
  const LATEST_RUN_COLUMNS = `${RUN_STATUS_COLUMN_WIDTH} minmax(0,1fr) 100px 70px`;
@@ -22,10 +24,19 @@ const { startingPoints, loading: templatesLoading, load: loadTemplates, ensureDe
22
24
  watch(() => startingPoints.value.map((t2) => t2.slug).join(","), () => {
23
25
  if (startingPoints.value.length) void ensureDetails(startingPoints.value.map((t2) => t2.slug));
24
26
  }, { immediate: true });
25
- const { activeCount: schedulesActive, load: loadSchedules } = useIntegrationsSchedules();
27
+ const { activeCount: schedulesActive, loading: schedulesLoading, load: loadSchedules } = useIntegrationsSchedules();
26
28
  const { pendingCount: missedRuns, globalLoading: missedLoading, loadGlobal: loadMissedRuns } = useIntegrationsDeadLetters();
27
- const kpisLoading = computed(() => workflowsLoading.value && !workflows.value.length || runsLoading.value && !latestRunPerWorkflow.value.length);
29
+ const kpiWaits = computed(() => ({
30
+ activeWorkflows: workflowsLoading.value && !workflows.value.length,
31
+ runningNow: runsLoading.value && !latestRunPerWorkflow.value.length,
32
+ scheduled: schedulesLoading.value && schedulesActive.value === 0,
33
+ missedRuns: missedLoading.value && missedRuns.value === 0
34
+ }));
28
35
  const latestLoading = computed(() => runsLoading.value && !latestRunPerWorkflow.value.length);
36
+ const awaitingKpis = computed(() => Object.values(kpiWaits.value).some(Boolean));
37
+ const awaitingLatestRuns = computed(() => latestLoading.value && !latestRunPerWorkflow.value.length);
38
+ const awaitingAttention = computed(() => (missedLoading.value || latestLoading.value) && !missedRuns.value && !failedRecent.value.length);
39
+ const awaitingStartingPoints = computed(() => templatesLoading.value && !startingPoints.value.length);
29
40
  const createOpen = ref(false);
30
41
  const NuxtLinkComp = resolveComponent("NuxtLink");
31
42
  onMounted(() => {
@@ -51,6 +62,7 @@ function formatRelative(iso) {
51
62
  const kpis = computed(() => [
52
63
  {
53
64
  key: "activeWorkflows",
65
+ waiting: kpiWaits.value.activeWorkflows,
54
66
  label: t("integration.dashboard.kpi.activeWorkflows.label"),
55
67
  value: activeWorkflows.value,
56
68
  // A plural key, so the count is the second argument. Called without one it
@@ -61,6 +73,7 @@ const kpis = computed(() => [
61
73
  },
62
74
  {
63
75
  key: "runningNow",
76
+ waiting: kpiWaits.value.runningNow,
64
77
  label: t("integration.dashboard.kpi.runningNow.label"),
65
78
  value: runningCount.value,
66
79
  sub: t("integration.dashboard.kpi.runningNow.sub"),
@@ -69,6 +82,7 @@ const kpis = computed(() => [
69
82
  },
70
83
  {
71
84
  key: "scheduled",
85
+ waiting: kpiWaits.value.scheduled,
72
86
  label: t("integration.dashboard.kpi.scheduled.label"),
73
87
  value: schedulesActive.value,
74
88
  sub: t("integration.dashboard.kpi.scheduled.sub"),
@@ -77,6 +91,7 @@ const kpis = computed(() => [
77
91
  },
78
92
  {
79
93
  key: "missedRuns",
94
+ waiting: kpiWaits.value.missedRuns,
80
95
  label: t("integration.dashboard.kpi.missedRuns.label"),
81
96
  value: missedRuns.value,
82
97
  sub: t("integration.dashboard.kpi.missedRuns.sub"),
@@ -124,13 +139,22 @@ function surfaceCards() {
124
139
  contents — named "Missed runs" alone it would announce less than the
125
140
  three tiles beside it. -->
126
141
  <div role="group" :aria-label="t('integration.dashboard.kpi.aria.group')" class="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
142
+ <!-- Four panels on this page wait on four sources, so each says so under its
143
+ own name (PO-251). The tiles' own names drop their figure while this is
144
+ true: a tile named "Active workflows: 0" from an empty list does not read
145
+ as waiting, it reads as an answer. -->
146
+ <LoadingAnnouncement
147
+ :label="t('integration.dashboard.kpi.aria.group')"
148
+ :loading="awaitingKpis"
149
+ :text="t('integration.dashboard.kpi.state.loading')"
150
+ />
127
151
  <component
128
152
  :is="k.to ? NuxtLinkComp : 'div'"
129
153
  v-for="k in kpis"
130
154
  :key="k.key"
131
155
  :to="k.to"
132
156
  :role="k.to ? void 0 : 'group'"
133
- :aria-label="`${k.label}: ${k.value}`"
157
+ :aria-label="k.waiting ? k.label : `${k.label}: ${k.value}`"
134
158
  class="block rounded-lg border border-black/[0.06] bg-white p-3"
135
159
  :class="k.to ? 'transition hover:border-black/20' : ''"
136
160
  >
@@ -139,7 +163,7 @@ function surfaceCards() {
139
163
  <div class="text-mono text-muted-foreground truncate">
140
164
  {{ k.label }}
141
165
  </div>
142
- <div v-if="kpisLoading" class="my-1.5 h-6 w-12 rounded bg-black/[0.07] animate-pulse" />
166
+ <div v-if="k.waiting" aria-hidden="true" class="my-1.5 h-6 w-12 rounded bg-black/[0.07] animate-pulse" />
143
167
  <div v-else class="text-2xl font-semibold tabular-nums text-[#0B0522]">
144
168
  {{ k.value }}
145
169
  </div>
@@ -169,6 +193,11 @@ function surfaceCards() {
169
193
  {{ t("integration.common.action.viewAll") }} <ArrowRight :size="11" />
170
194
  </NuxtLink>
171
195
  </div>
196
+ <LoadingAnnouncement
197
+ :label="t('integration.dashboard.runs.title')"
198
+ :loading="awaitingLatestRuns"
199
+ :text="t('integration.dashboard.runs.state.loading')"
200
+ />
172
201
  <div>
173
202
  <!-- An entry is a link, not a div that answers a click: that is what
174
203
  makes it reachable by keyboard, and it is what gives it a name —
@@ -199,14 +228,22 @@ function surfaceCards() {
199
228
  <div class="text-sm text-muted-foreground tabular-nums truncate">
200
229
  {{ formatRelative(r.startedAt) }}
201
230
  </div>
231
+ <!-- The ellipsis is this panel's own answer and not the formatter's em
232
+ dash: an entry here is the latest run of a workflow, so a missing
233
+ duration means it is still going, where the runs table counts up a
234
+ live figure instead. The figure itself is the shared spelling
235
+ (PO-383) — this cell used to divide by a thousand on the spot and
236
+ print two decimals, which said `0.90s` for 900 ms and `63.00s` for a
237
+ step the run record called `1m 3s`. -->
202
238
  <div class="text-sm text-muted-foreground tabular-nums text-right">
203
- {{ r.durationMs == null ? "\u2026" : `${(r.durationMs / 1e3).toFixed(2)}s` }}
239
+ {{ r.durationMs == null ? "\u2026" : formatDuration(r.durationMs) }}
204
240
  </div>
205
241
  </NuxtLink>
206
- <template v-if="latestLoading && !latestPerWorkflow.length">
242
+ <template v-if="awaitingLatestRuns">
207
243
  <div
208
244
  v-for="n in 6"
209
245
  :key="`sk-run-${n}`"
246
+ aria-hidden="true"
210
247
  class="grid items-center gap-2 border-b border-black/[0.04] px-3 py-2.5 last:border-b-0"
211
248
  :style="{ gridTemplateColumns: LATEST_RUN_COLUMNS }"
212
249
  >
@@ -235,7 +272,12 @@ function surfaceCards() {
235
272
  <h3 class="text-sm font-semibold">{{ t("integration.dashboard.attention.title") }}</h3>
236
273
  </div>
237
274
  </div>
238
- <div v-if="(missedLoading || latestLoading) && !missedRuns && !failedRecent.length" class="space-y-2 p-3">
275
+ <LoadingAnnouncement
276
+ :label="t('integration.dashboard.attention.title')"
277
+ :loading="awaitingAttention"
278
+ :text="t('integration.dashboard.attention.state.loading')"
279
+ />
280
+ <div v-if="awaitingAttention" aria-hidden="true" class="space-y-2 p-3">
239
281
  <div v-for="n in 3" :key="`sk-att-${n}`" class="h-3 rounded bg-black/[0.07] animate-pulse" :class="n === 1 ? 'w-2/3' : n === 2 ? 'w-1/2' : 'w-3/5'" />
240
282
  </div>
241
283
  <div v-else-if="missedRuns || failedRecent.length">
@@ -305,7 +347,12 @@ function surfaceCards() {
305
347
  {{ t("integration.common.action.seeAll") }} <ArrowRight :size="11" />
306
348
  </NuxtLink>
307
349
  </div>
308
- <div v-if="templatesLoading && !startingPoints.length" class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
350
+ <LoadingAnnouncement
351
+ :label="t('integration.dashboard.templates.title')"
352
+ :loading="awaitingStartingPoints"
353
+ :text="t('integration.dashboard.templates.state.loading')"
354
+ />
355
+ <div v-if="awaitingStartingPoints" aria-hidden="true" class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
309
356
  <div v-for="n in 4" :key="`sk-tpl-${n}`" class="rounded-md border border-black/[0.06] bg-white p-3 space-y-2">
310
357
  <div class="h-5 w-20 rounded bg-black/[0.07] animate-pulse" />
311
358
  <div class="h-3 w-3/4 rounded bg-black/[0.07] animate-pulse" />