@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
@@ -2,21 +2,52 @@
2
2
  import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as TrashBin } from "#solar-icons";
3
3
  import { Badge, Button } from "@revenexx/studio";
4
4
  import { toast } from "vue-sonner";
5
+ import LoadingAnnouncement from "../../components/Integrations/LoadingAnnouncement.vue";
5
6
  import RunsTable from "../../components/Integrations/RunsTable.vue";
6
7
  import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
7
8
  import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
9
+ import { errorReason } from "../../utils/apiError";
8
10
  const t = useIntegrationsT();
9
11
  useHead({ title: () => t("integration.runs.list.head.title") });
10
12
  const route = useRoute();
11
- const { runs, loading, load } = useIntegrationsRuns();
13
+ const { runs, loading, error, load, query, setQuery, narrowedTotal } = useIntegrationsRuns();
12
14
  const {
13
15
  globalItems: missed,
14
16
  globalLoading: missedLoading,
15
17
  pendingCount,
18
+ globalPage: missedPage,
19
+ globalLastPage: missedLastPage,
20
+ goGlobalPage,
21
+ resetGlobalPage,
16
22
  loadGlobal,
17
23
  replay,
18
- discard
24
+ discard,
25
+ summary: missedSummary
19
26
  } = useIntegrationsDeadLetters();
27
+ const page = computed({
28
+ get: () => query.value.page,
29
+ // Only when it actually moved. The shared table returns to its first page
30
+ // whenever the search or the page size changes, and reports that as a page
31
+ // change too — so an unguarded setter would send a second request for the page
32
+ // the list is already on, beside the one the search itself asks for.
33
+ set: (next) => {
34
+ if (next !== query.value.page) void setQuery({ page: next });
35
+ }
36
+ });
37
+ function onNarrow(patch) {
38
+ void setQuery(patch);
39
+ }
40
+ function onReorder(order) {
41
+ void setQuery(order);
42
+ }
43
+ let searchTimer;
44
+ function onSearch(text) {
45
+ clearTimeout(searchTimer);
46
+ if (text === query.value.search) return;
47
+ searchTimer = setTimeout(() => {
48
+ void setQuery({ search: text });
49
+ }, 250);
50
+ }
20
51
  const tab = ref(route.query.tab === "missed" ? "missed" : "runs");
21
52
  const tabs = computed(() => [
22
53
  { key: "runs", label: t("integration.runs.tabs.runs.label") },
@@ -36,7 +67,8 @@ watch([runTick, workflowTick], () => {
36
67
  });
37
68
  let pollTimer = null;
38
69
  onMounted(() => {
39
- void load(true);
70
+ void setQuery({ search: "", page: 1 });
71
+ resetGlobalPage();
40
72
  void reloadMissedRuns();
41
73
  pollTimer = setInterval(() => {
42
74
  if (hasActiveRuns.value && !loading.value && budget.due(delivering.value)) void load(true);
@@ -47,8 +79,15 @@ onUnmounted(() => {
47
79
  clearInterval(pollTimer);
48
80
  pollTimer = null;
49
81
  }
82
+ clearTimeout(searchTimer);
50
83
  });
51
84
  const busyId = ref(null);
85
+ async function goMissedPage(page2) {
86
+ try {
87
+ await goGlobalPage(page2);
88
+ } catch {
89
+ }
90
+ }
52
91
  async function reloadMissedRuns() {
53
92
  try {
54
93
  await loadGlobal(true);
@@ -62,7 +101,7 @@ async function onReplay(dl) {
62
101
  const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
63
102
  toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
64
103
  } catch (e) {
65
- toast.error(e?.message ?? t("integration.deadLetters.toast.replay.error"));
104
+ toast.error(errorReason(e, t("integration.deadLetters.toast.replay.error")));
66
105
  busyId.value = null;
67
106
  return;
68
107
  }
@@ -76,7 +115,7 @@ async function onDiscard(dl) {
76
115
  await discard(dl.id);
77
116
  toast.success(t("integration.deadLetters.toast.discard.done"));
78
117
  } catch (e) {
79
- toast.error(e?.message ?? t("integration.deadLetters.toast.discard.error"));
118
+ toast.error(errorReason(e, t("integration.deadLetters.toast.discard.error")));
80
119
  busyId.value = null;
81
120
  return;
82
121
  }
@@ -107,7 +146,49 @@ function fmt(iso) {
107
146
  <TabBar v-model="tab" :tabs="tabs" class="px-4" />
108
147
 
109
148
  <div class="p-4 space-y-4">
110
- <RunsTable v-if="tab === 'runs'" :runs="runs" :loading="loading" @changed="reload" />
149
+ <!-- Above the tab chain on purpose: the missed-runs half is drawn by a
150
+ `v-else`, so a region placed inside it would mount together with the
151
+ placeholders it speaks for, and a live region that arrives with its own
152
+ content is one nothing was listening to. Out here it is on the page from
153
+ the first render and only its text moves (PO-251). -->
154
+ <LoadingAnnouncement
155
+ :label="t('integration.deadLetters.tabs.missed.label')"
156
+ :loading="tab === 'missed' && missedLoading && !missed.length"
157
+ :text="t('integration.deadLetters.state.loading')"
158
+ />
159
+
160
+ <!--
161
+ A refused read, said plainly. Without this the page kept the rows it had
162
+ while `query` had already moved on, so a failed page turn drew rows 1–25
163
+ under a footer reading "Showing 26–50", and a refused narrowing left the
164
+ old rows under the new selection — both reading as an answer.
165
+ -->
166
+ <div
167
+ v-if="tab === 'runs' && error"
168
+ class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700"
169
+ role="alert"
170
+ >
171
+ {{ error }}
172
+ </div>
173
+
174
+ <RunsTable
175
+ v-if="tab === 'runs'"
176
+ v-model:page="page"
177
+ :runs="runs"
178
+ :loading="loading"
179
+ server-mode
180
+ :total="narrowedTotal"
181
+ :per-page="query.perPage"
182
+ :status="query.status"
183
+ :trigger-type="query.triggerType"
184
+ :sort="query.sort"
185
+ :direction="query.direction"
186
+ @narrow="onNarrow"
187
+ @search="onSearch"
188
+ @page-size="(size) => setQuery({ perPage: size })"
189
+ @reorder="onReorder"
190
+ @changed="reload"
191
+ />
111
192
 
112
193
  <!-- Missed runs — trigger firings that never produced a run (the
113
194
  workflow wasn't runnable when its schedule/event fired). -->
@@ -122,7 +203,7 @@ function fmt(iso) {
122
203
  </Button>
123
204
  </div>
124
205
 
125
- <div v-if="missedLoading && !missed.length"><div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
206
+ <div v-if="missedLoading && !missed.length" aria-hidden="true"><div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
126
207
  <div v-for="n in 4" :key="`sk-${n}`" class="flex items-start gap-3 px-3 py-3">
127
208
  <div class="h-5 w-16 shrink-0 rounded bg-black/[0.07] animate-pulse" />
128
209
  <div class="min-w-0 flex-1 space-y-2">
@@ -168,6 +249,34 @@ function fmt(iso) {
168
249
  </div>
169
250
  </div>
170
251
  </div>
252
+
253
+ <!--
254
+ Server pagination — the endpoint pages, so the whole backlog is
255
+ reachable. It used to read one page of a hundred and offer no way past
256
+ it, while `has_more` came back on every read and was shown to nobody
257
+ (PO-369).
258
+ -->
259
+ <div v-if="missedLastPage > 1" class="flex items-center justify-between text-sm text-muted-foreground">
260
+ <span class="tabular-nums">
261
+ {{ t("integration.deadLetters.pagination.summary", { page: missedPage, lastPage: missedLastPage, total: missedSummary.total }) }}
262
+ </span>
263
+ <div class="flex gap-2">
264
+ <Button
265
+ variant="outline" size="sm" class="h-7 text-sm"
266
+ :disabled="missedPage <= 1 || missedLoading"
267
+ @click="goMissedPage(missedPage - 1)"
268
+ >
269
+ {{ t("integration.deadLetters.pagination.newer") }}
270
+ </Button>
271
+ <Button
272
+ variant="outline" size="sm" class="h-7 text-sm"
273
+ :disabled="missedPage >= missedLastPage || missedLoading"
274
+ @click="goMissedPage(missedPage + 1)"
275
+ >
276
+ {{ t("integration.deadLetters.pagination.older") }}
277
+ </Button>
278
+ </div>
279
+ </div>
171
280
  </template>
172
281
  </div>
173
282
  </IntegrationsLayout>
@@ -24,6 +24,7 @@ import {
24
24
  Input,
25
25
  Label
26
26
  } from "@revenexx/studio";
27
+ import { errorReason } from "../../utils/apiError";
27
28
  const t = useIntegrationsT();
28
29
  useHead({ title: () => t("integration.secrets.head.title") });
29
30
  const { secrets, loading, error, isValidKey, upsert, remove, load } = useIntegrationsSecrets();
@@ -58,7 +59,7 @@ async function submitCreate() {
58
59
  await upsert(key, newValue.value);
59
60
  createOpen.value = false;
60
61
  } catch (e) {
61
- formError.value = e.message;
62
+ formError.value = errorReason(e, t("integration.secrets.form.error.saveFailed"));
62
63
  } finally {
63
64
  saving.value = false;
64
65
  }
@@ -24,6 +24,7 @@ import TemplateFlowPreview from "../../../components/Integrations/TemplateFlowPr
24
24
  import NodeMark from "../../../components/Integrations/NodeMark.vue";
25
25
  import { templateCategoryLabel, templatePackageLabel } from "../../../data/integrationTemplates";
26
26
  import { describeCron } from "../../../utils/schedule";
27
+ import { errorReason } from "../../../utils/apiError";
27
28
  const SELECT_LATER = "__later__";
28
29
  const t = useIntegrationsT();
29
30
  const route = useRoute();
@@ -165,7 +166,7 @@ async function confirmInstall() {
165
166
  });
166
167
  if (id) await router.push(`/integrations/workflows/${id}`);
167
168
  } catch (e) {
168
- installError.value = e.message || t("integration.templates.detail.install.error.fallback");
169
+ installError.value = errorReason(e, t("integration.templates.detail.install.error.fallback"));
169
170
  } finally {
170
171
  installing.value = false;
171
172
  }
@@ -2,6 +2,7 @@
2
2
  import { computed, ref, watch } from "vue";
3
3
  import { Magnifier as Search, Layers, DangerTriangle as AlertTriangle } from "#solar-icons";
4
4
  import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@revenexx/studio";
5
+ import LoadingAnnouncement from "../../../components/Integrations/LoadingAnnouncement.vue";
5
6
  import TemplateCard from "../../../components/Integrations/TemplateCard.vue";
6
7
  import { templateCategoryLabel, templatePackageLabel } from "../../../data/integrationTemplates";
7
8
  const t = useIntegrationsT();
@@ -55,7 +56,6 @@ watch(() => templates.value.length, () => {
55
56
  if (templates.value.length) void ensureDetails(templates.value.map((t2) => t2.slug));
56
57
  });
57
58
  const awaitingCatalogue = computed(() => loading.value && !templates.value.length);
58
- const statusText = computed(() => awaitingCatalogue.value ? t("integration.templates.gallery.state.loading") : "");
59
59
  </script>
60
60
 
61
61
  <template>
@@ -115,22 +115,25 @@ const statusText = computed(() => awaitingCatalogue.value ? t("integration.templ
115
115
  </PageHeader>
116
116
 
117
117
  <div class="p-4">
118
- <!-- What the page is doing, in words (specs/templates.md AC-9).
119
- The shimmer says "still working" only to somebody who can see it, and
120
- six blank tiles carry no text, no name and no state — so waiting and
121
- "nothing is registered" were the same silence, which are the two states
122
- that ask for opposite reactions.
118
+ <!-- What the page is doing, in words (specs/templates.md AC-9). The mechanics are
119
+ in LoadingAnnouncement; three things about this page in particular are not.
123
120
 
124
- The region is OUTSIDE the v-if chain below and always in the DOM, with
125
- only its text changing. A live region that appears together with its
126
- content is a region assistive technology may never have been watching:
127
- what is announced is a change inside an existing region.
121
+ The region is NAMED because every card draws a status region of its own while
122
+ its flow miniature loads, so an unnamed one here would be indistinguishable
123
+ from six of those.
128
124
 
129
- Named, because it is not the only `role="status"` on this page: every
130
- card draws one while its flow miniature loads ("Loading the flow"). Two
131
- unnamed status regions on one page are two things a reader — and a test —
132
- cannot tell apart, and this one is about the gallery rather than a card. -->
133
- <p role="status" aria-live="polite" :aria-label="t('integration.templates.gallery.state.regionLabel')" class="sr-only">{{ statusText }}</p>
125
+ Only the WAIT gets words. The empty registry and a filter without a match say
126
+ which they are in copy the reader arrives at or already has focus in, so
127
+ announcing them too would tell each of them twice.
128
+
129
+ A refused catalogue IS announced, but not from here: it is said by the block
130
+ that shows it, which carries `role="alert"`. Saying it here as well would put
131
+ the same sentence twice in the DOM, heard twice by anybody walking the page. -->
132
+ <LoadingAnnouncement
133
+ :label="t('integration.templates.gallery.state.regionLabel')"
134
+ :loading="awaitingCatalogue"
135
+ :text="t('integration.templates.gallery.state.loading')"
136
+ />
134
137
 
135
138
  <!-- The placeholders are taken out of the accessibility tree rather than
136
139
  marked busy. `aria-busy` only asks assistive technology to defer