@revenexx/studio-integrations 2.3.0 → 2.5.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 (40) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +30 -2
  4. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +50 -14
  5. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +27 -18
  6. package/dist/runtime/components/Integrations/ExpressionInput.vue +7 -4
  7. package/dist/runtime/components/Integrations/NodeInspector.vue +6 -2
  8. package/dist/runtime/components/Integrations/NodePalette.vue +5 -4
  9. package/dist/runtime/components/Integrations/NodeRetireDialog.d.vue.ts +19 -0
  10. package/dist/runtime/components/Integrations/NodeRetireDialog.vue +131 -0
  11. package/dist/runtime/components/Integrations/NodeRetireDialog.vue.d.ts +19 -0
  12. package/dist/runtime/components/Integrations/NodeVersionsPanel.d.vue.ts +5 -0
  13. package/dist/runtime/components/Integrations/NodeVersionsPanel.vue +134 -0
  14. package/dist/runtime/components/Integrations/NodeVersionsPanel.vue.d.ts +5 -0
  15. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +3 -2
  16. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +2 -0
  17. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +3 -2
  18. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +2 -0
  19. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +4 -3
  20. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +6 -3
  21. package/dist/runtime/components/Integrations/WorkflowEditor.vue +7 -27
  22. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +6 -3
  23. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +5 -4
  24. package/dist/runtime/composables/useIntegrationsCredentials.d.ts +6 -1
  25. package/dist/runtime/composables/useIntegrationsCredentials.js +6 -3
  26. package/dist/runtime/composables/useIntegrationsNodes.js +3 -0
  27. package/dist/runtime/composables/useNodeRetirement.d.ts +48 -0
  28. package/dist/runtime/composables/useNodeRetirement.js +31 -0
  29. package/dist/runtime/data/integrationNodeCatalog.d.ts +10 -1
  30. package/dist/runtime/data/integrationNodeCatalog.js +8 -5
  31. package/dist/runtime/data/triggerGlyphs.d.ts +8 -3
  32. package/dist/runtime/data/triggerGlyphs.js +2 -2
  33. package/dist/runtime/i18n/messages/de.json +30 -1
  34. package/dist/runtime/i18n/messages/en.json +30 -1
  35. package/dist/runtime/pages/integrations/workflows/[id].vue +29 -12
  36. package/dist/runtime/types/integrations.d.ts +7 -0
  37. package/dist/runtime/utils/cockpitSources.d.ts +10 -3
  38. package/dist/runtime/utils/credentialEdit.d.ts +16 -0
  39. package/dist/runtime/utils/credentialEdit.js +7 -0
  40. package/package.json +1 -1
@@ -53,6 +53,7 @@
53
53
  "integration.canvas.contextMenu.item.paste": "Einfügen",
54
54
  "integration.canvas.contextMenu.item.rename": "Umbenennen",
55
55
  "integration.canvas.contextMenu.item.runWorkflow": "Workflow ausführen",
56
+ "integration.canvas.contextMenu.item.saveAndRunWorkflow": "Speichern & Workflow ausführen",
56
57
  "integration.canvas.contextMenu.item.selectAll": "Alle auswählen",
57
58
  "integration.canvas.contextMenu.item.tidyUp": "Aufräumen",
58
59
  "integration.canvas.edge.action.delete": "Verbindung löschen",
@@ -169,9 +170,11 @@
169
170
  "integration.credentials.detail.field.typeSlug": "Typ-Slug",
170
171
  "integration.credentials.detail.field.updated": "Aktualisiert",
171
172
  "integration.credentials.detail.field.version": "Version",
173
+ "integration.credentials.detail.state.testedAt": "· geprüft {time}",
172
174
  "integration.credentials.detail.state.untestedHint": "Testen Sie die Verbindung, um zu prüfen, ob diese Zugangsdaten tatsächlich funktionieren.",
173
175
  "integration.credentials.detail.tab.credentials": "Zugangsdaten",
174
176
  "integration.credentials.detail.tab.details": "Details",
177
+ "integration.credentials.detail.test.editNotSupported": "Der Dienst kann nur gespeicherte Werte testen, diese Werte wurden also nicht geprüft. Speichern Sie sie, um sie zu testen.",
175
178
  "integration.credentials.error.delete": "Zugangsdaten konnten nicht gelöscht werden.",
176
179
  "integration.credentials.error.loadFailed": "Zugangsdaten konnten nicht geladen werden.",
177
180
  "integration.credentials.error.loadTypes": "Zugangsdatentypen konnten nicht geladen werden.",
@@ -219,6 +222,7 @@
219
222
  "integration.credentials.revenexxSource.error.createSmtp": "Die Mail-Zugangsdaten konnten nicht erstellt werden.",
220
223
  "integration.credentials.revenexxSource.error.loadResources": "revenexx-Ressourcen konnten nicht geladen werden.",
221
224
  "integration.credentials.revenexxSource.error.mailCredentialGone": "Diese Mail-Zugangsdaten sind nicht mehr vorhanden.",
225
+ "integration.credentials.revenexxSource.error.mailCredentialNoSecret": "Diese Mail-Zugangsdaten haben keinen Key, deshalb wurde nichts übernommen. Wählen Sie andere aus oder prüfen Sie diese unter Mail.",
222
226
  "integration.credentials.revenexxSource.error.mailCredentialNotReturned": "Die Mail-Zugangsdaten wurden nicht zurückgegeben — bitte aus der Liste wählen, sobald sie erscheinen.",
223
227
  "integration.credentials.revenexxSource.error.noTenant": "Kein Mandant ausgewählt.",
224
228
  "integration.credentials.revenexxSource.error.provisioningFailed": "Bereitstellung fehlgeschlagen.",
@@ -439,6 +443,7 @@
439
443
  "integration.editor.run.error.syncFailed": "Synchroner Run fehlgeschlagen",
440
444
  "integration.editor.run.error.syncTimeout": "Synchroner Run: Zeitüberschreitung",
441
445
  "integration.editor.run.loading": "Run wird geladen…",
446
+ "integration.editor.run.saveAndRun": "Speichern & Run",
442
447
  "integration.editor.run.waitingForBuild": "Warten auf Build…",
443
448
  "integration.editor.runPayload.action.latestEvent": "Letztes reales Event",
444
449
  "integration.editor.runPayload.action.latestEvent.title": "Letztes empfangenes Event dieses Topics einfügen",
@@ -1348,5 +1353,29 @@
1348
1353
  "integration.workflows.variables.row.aria": "Variable {key}",
1349
1354
  "integration.workflows.variables.state.empty": "Noch keine Variablen — fügen Sie die erste unten hinzu.",
1350
1355
  "integration.workflows.variables.toast.copied": "Ausdruck kopiert",
1351
- "integration.workflows.variables.toast.copyFailed": "Kopieren fehlgeschlagen"
1356
+ "integration.workflows.variables.toast.copyFailed": "Kopieren fehlgeschlagen",
1357
+ "integration.nodeVersions.retire.dialog.title": "Diese Version ausmustern?",
1358
+ "integration.nodeVersions.retire.dialog.description": "wird nicht mehr angeboten: Niemand kann sie mehr in einen Workflow einfügen. Workflows, die sie schon nutzen, laufen weiter.",
1359
+ "integration.nodeVersions.retire.confirm.label": "Zum Fortfahren {version} eintippen",
1360
+ "integration.nodeVersions.retire.confirm.aria": "Auszumusternde Version, ausgeschrieben",
1361
+ "integration.nodeVersions.retire.reason.label": "Grund (optional)",
1362
+ "integration.nodeVersions.retire.reason.placeholder": "z. B. der Anbieter hat API v1 abgeschaltet",
1363
+ "integration.nodeVersions.retire.usage.none": "Kein Workflow und kein Template nutzt diese Version.",
1364
+ "integration.nodeVersions.retire.usage.counts": "Genutzt von {workflows} Workflow(s), davon {active} aktiv, in {tenants} Mandant(en), und von {templates} Template(s).",
1365
+ "integration.nodeVersions.retire.usage.keepWorking": "Sie laufen unverändert weiter. Die Version wird nur nicht mehr angeboten und kann keinem Workflow neu hinzugefügt werden.",
1366
+ "integration.nodeVersions.retire.action.check": "Nutzung prüfen",
1367
+ "integration.nodeVersions.retire.action.retire": "Version ausmustern",
1368
+ "integration.nodeVersions.retire.error.generic": "Die Version konnte nicht ausgemustert werden.",
1369
+ "integration.nodeVersions.restore.error.generic": "Die Version konnte nicht wiederhergestellt werden.",
1370
+ "integration.nodeVersions.error.load": "Die Node-Versionen konnten nicht geladen werden.",
1371
+ "integration.nodeVersions.empty.noPackage": "Diese Integration hat noch kein Node-Paket veröffentlicht.",
1372
+ "integration.nodeVersions.empty.noNodes": "Dieses Paket registriert keine Nodes.",
1373
+ "integration.nodeVersions.loading": "Node-Versionen werden geladen…",
1374
+ "integration.nodeVersions.column.version": "Version",
1375
+ "integration.nodeVersions.column.state": "Status",
1376
+ "integration.nodeVersions.badge.retired": "Ausgemustert",
1377
+ "integration.nodeVersions.badge.offered": "Angeboten",
1378
+ "integration.nodeVersions.badge.notBuilt": "Nicht gebaut",
1379
+ "integration.nodeVersions.action.retire": "Ausmustern",
1380
+ "integration.nodeVersions.action.restore": "Wiederherstellen"
1352
1381
  }
@@ -53,6 +53,7 @@
53
53
  "integration.canvas.contextMenu.item.paste": "Paste",
54
54
  "integration.canvas.contextMenu.item.rename": "Rename",
55
55
  "integration.canvas.contextMenu.item.runWorkflow": "Run workflow",
56
+ "integration.canvas.contextMenu.item.saveAndRunWorkflow": "Save & run workflow",
56
57
  "integration.canvas.contextMenu.item.selectAll": "Select all",
57
58
  "integration.canvas.contextMenu.item.tidyUp": "Tidy up",
58
59
  "integration.canvas.edge.action.delete": "Delete this connection",
@@ -169,9 +170,11 @@
169
170
  "integration.credentials.detail.field.typeSlug": "Type slug",
170
171
  "integration.credentials.detail.field.updated": "Updated",
171
172
  "integration.credentials.detail.field.version": "Version",
173
+ "integration.credentials.detail.state.testedAt": "· verified {time}",
172
174
  "integration.credentials.detail.state.untestedHint": "Run a test to verify these credentials actually connect.",
173
175
  "integration.credentials.detail.tab.credentials": "Credentials",
174
176
  "integration.credentials.detail.tab.details": "Details",
177
+ "integration.credentials.detail.test.editNotSupported": "The service can test only what is saved, so these values were not tried. Save them to test them.",
175
178
  "integration.credentials.error.delete": "Could not delete credential.",
176
179
  "integration.credentials.error.loadFailed": "Could not load credentials.",
177
180
  "integration.credentials.error.loadTypes": "Could not load credential types.",
@@ -219,6 +222,7 @@
219
222
  "integration.credentials.revenexxSource.error.createSmtp": "Could not create the mail credential.",
220
223
  "integration.credentials.revenexxSource.error.loadResources": "Could not load revenexx resources.",
221
224
  "integration.credentials.revenexxSource.error.mailCredentialGone": "That mail credential is no longer there.",
225
+ "integration.credentials.revenexxSource.error.mailCredentialNoSecret": "That mail credential has no key, so nothing was copied. Pick another one, or check this one in Mail.",
222
226
  "integration.credentials.revenexxSource.error.mailCredentialNotReturned": "The mail credential was not returned — pick it from the list once it appears.",
223
227
  "integration.credentials.revenexxSource.error.noTenant": "No tenant selected.",
224
228
  "integration.credentials.revenexxSource.error.provisioningFailed": "Provisioning failed.",
@@ -439,6 +443,7 @@
439
443
  "integration.editor.run.error.syncFailed": "Sync run failed",
440
444
  "integration.editor.run.error.syncTimeout": "Sync run timed out",
441
445
  "integration.editor.run.loading": "Loading run…",
446
+ "integration.editor.run.saveAndRun": "Save & run",
442
447
  "integration.editor.run.waitingForBuild": "Waiting for build…",
443
448
  "integration.editor.runPayload.action.latestEvent": "Latest real event",
444
449
  "integration.editor.runPayload.action.latestEvent.title": "Insert the latest received event of this topic",
@@ -1348,5 +1353,29 @@
1348
1353
  "integration.workflows.variables.row.aria": "Variable {key}",
1349
1354
  "integration.workflows.variables.state.empty": "No variables yet — add the first one below.",
1350
1355
  "integration.workflows.variables.toast.copied": "Expression copied",
1351
- "integration.workflows.variables.toast.copyFailed": "Could not copy"
1356
+ "integration.workflows.variables.toast.copyFailed": "Could not copy",
1357
+ "integration.nodeVersions.retire.dialog.title": "Retire this version?",
1358
+ "integration.nodeVersions.retire.dialog.description": "stops being offered: nobody can add it to a workflow any more. Workflows that already use it keep working.",
1359
+ "integration.nodeVersions.retire.confirm.label": "Type {version} to continue",
1360
+ "integration.nodeVersions.retire.confirm.aria": "Version to retire, typed out",
1361
+ "integration.nodeVersions.retire.reason.label": "Reason (optional)",
1362
+ "integration.nodeVersions.retire.reason.placeholder": "e.g. the vendor switched off API v1",
1363
+ "integration.nodeVersions.retire.usage.none": "No workflow and no template uses this version.",
1364
+ "integration.nodeVersions.retire.usage.counts": "Used by {workflows} workflow(s), {active} of them active, in {tenants} tenant(s), and by {templates} template(s).",
1365
+ "integration.nodeVersions.retire.usage.keepWorking": "They keep working as they are. The version is only no longer offered, and cannot be added to a workflow again.",
1366
+ "integration.nodeVersions.retire.action.check": "Check usage",
1367
+ "integration.nodeVersions.retire.action.retire": "Retire version",
1368
+ "integration.nodeVersions.retire.error.generic": "The version could not be retired.",
1369
+ "integration.nodeVersions.restore.error.generic": "The version could not be restored.",
1370
+ "integration.nodeVersions.error.load": "The node versions could not be loaded.",
1371
+ "integration.nodeVersions.empty.noPackage": "This integration has not published a node package yet.",
1372
+ "integration.nodeVersions.empty.noNodes": "This package registers no nodes.",
1373
+ "integration.nodeVersions.loading": "Loading node versions…",
1374
+ "integration.nodeVersions.column.version": "Version",
1375
+ "integration.nodeVersions.column.state": "State",
1376
+ "integration.nodeVersions.badge.retired": "Retired",
1377
+ "integration.nodeVersions.badge.offered": "Offered",
1378
+ "integration.nodeVersions.badge.notBuilt": "Not built",
1379
+ "integration.nodeVersions.action.retire": "Retire",
1380
+ "integration.nodeVersions.action.restore": "Restore"
1352
1381
  }
@@ -400,7 +400,11 @@ async function run(mode) {
400
400
  }
401
401
  const payloadDriven = computed(() => payloadFieldsFromBlob(shownBlob.value ?? { nodes: [] }).length > 0);
402
402
  async function runFromEditor() {
403
- if (!workflow.value) return;
403
+ if (!workflow.value || saving.value) return;
404
+ if (isDirty.value) {
405
+ await save();
406
+ if (isDirty.value) return;
407
+ }
404
408
  if (payloadDriven.value || workflow.value.execution_mode === "caller_decides") {
405
409
  runPayloadOpen.value = true;
406
410
  return;
@@ -817,14 +821,12 @@ async function confirmDeactivate() {
817
821
  <WorkflowIconView :icon="shownIcon" :size="16" />
818
822
  </span>
819
823
  <span class="truncate" :title="shownName">{{ shownName }}</span>
820
- <!-- The SAVED mode, beside a drafted name, and the difference is not an
821
- oversight. A name changes nothing about what happens when Run is
822
- pressed, so the header may show what the operator just entered. The
823
- dispatch mode decides what a run does — and a run goes to the
824
- workflow the platform holds, not to the draft. A badge following the
825
- draft would read SYNC over a button that fires an async run. -->
826
- <Badge variant="outline" class="ml-1.5 shrink-0 border text-xs font-medium uppercase text-muted-foreground" :title="t('integration.editor.header.executionMode.title', { mode: modeLabel(workflow.execution_mode) })">
827
- {{ modeLabel(workflow.execution_mode) }}
824
+ <!-- The DRAFTED mode, like the name beside it: Run over a draft saves
825
+ it first (PO-567), so this is the mode the next run goes out in. The
826
+ saved one would read ASYNC over a button about to fire a sync run;
827
+ the unsaved marker says the platform does not have it yet. -->
828
+ <Badge variant="outline" class="ml-1.5 shrink-0 border text-xs font-medium uppercase text-muted-foreground" :title="t('integration.editor.header.executionMode.title', { mode: modeLabel(draftedMode) })">
829
+ {{ modeLabel(draftedMode) }}
828
830
  </Badge>
829
831
  </template>
830
832
  </span>
@@ -927,8 +929,23 @@ async function confirmDeactivate() {
927
929
  <Save v-else :size="12" />
928
930
  {{ saving ? t("integration.editor.action.save.saving") : t("integration.editor.action.save.label") }}
929
931
  </Button>
932
+ <!-- Run stands beside Save because a run goes to the workflow the
933
+ platform holds, not to the draft on the canvas: with the two at
934
+ opposite ends of the screen, a changed value was run as the old one
935
+ and nothing said so. While there is a draft the press saves it first,
936
+ and the name says that it will (PO-567). -->
937
+ <Button
938
+ class="h-7 bg-emerald-600 text-sm text-white hover:bg-emerald-700"
939
+ :disabled="saving || running || syncLoading || waitingForBuild"
940
+ :title="waitingForBuild ? t('integration.editor.tool.run.tooltipWaiting') : t('integration.editor.tool.run.tooltip')"
941
+ @click="runFromEditor"
942
+ >
943
+ <Loader2 v-if="running || syncLoading || waitingForBuild" :size="12" class="animate-spin" />
944
+ <Play v-else :size="12" />
945
+ {{ waitingForBuild ? t("integration.editor.run.waitingForBuild") : isDirty ? t("integration.editor.run.saveAndRun") : t("integration.editor.run.button") }}
946
+ </Button>
930
947
  <!-- Activation is a standing state, not a transport control. The play
931
- symbol belongs to Run, 600 px away in the canvas's control pane,
948
+ symbol belongs to Run, the button just before this one,
932
949
  where pressing it makes one thing happen now; arming a workflow's
933
950
  triggers looks like nothing happening at all. So this states the
934
951
  state in the words the workflows list uses — Active, Paused — and
@@ -1064,8 +1081,8 @@ async function confirmDeactivate() {
1064
1081
  ref="editorRef"
1065
1082
  :workflow="workflow"
1066
1083
  :draft="draft?.blob ?? null"
1067
- :run-busy="running || syncLoading || waitingForBuild"
1068
- :run-waiting="waitingForBuild"
1084
+ :run-busy="saving || running || syncLoading || waitingForBuild"
1085
+ :run-saves="isDirty"
1069
1086
  :overlay-open="settingsOpen || duplicateOpen || importOpen || !!exporter?.open || deleteOpen || syncOpen || runPayloadOpen || deactivationOpen || leaveOpen"
1070
1087
  @change="onChange"
1071
1088
  @run="runFromEditor"
@@ -425,6 +425,13 @@ export interface NodeManifest {
425
425
  * does not make every node disappear.
426
426
  */
427
427
  __hasBundle?: boolean;
428
+ /**
429
+ * Whether the publisher retired this version (editor-side enrichment from
430
+ * `GET /nodes`; not manifest data, PO-549). A retired version still draws and
431
+ * runs for the workflows already on it, but is offered nowhere new, and the
432
+ * platform refuses a save that newly uses it.
433
+ */
434
+ __retired?: boolean;
428
435
  }
429
436
  export interface SecretRecord {
430
437
  key: string;
@@ -205,14 +205,21 @@ export interface CockpitSftpAccount {
205
205
  /**
206
206
  * The members of the cockpit's `MailCredential` this module reads.
207
207
  *
208
- * The SMTP pair is optional on the wire: the mail engine returns it on `show` and
209
- * on create, but the type does not promise it, which is why every read of it here
210
- * falls back to an empty string rather than assuming.
208
+ * The SMTP pair is optional on the wire, and the mail engine leaves it out: its
209
+ * `show` and create answers carry the name and the key. So the login is read the
210
+ * way the cockpit's Mail tab reads it — `smtp_username` or else the name, and
211
+ * `smtp_password` or else the key — and a credential with neither secret copies
212
+ * nothing rather than an empty password (PO-602).
211
213
  */
212
214
  export interface CockpitMailCredential {
213
215
  uuid: string;
214
216
  name: string;
215
217
  type?: string;
218
+ /**
219
+ * The credential's secret. The mail engine authenticates SMTP by it alone, and
220
+ * where `smtp_password` is absent it is the password (PO-602).
221
+ */
222
+ key?: string;
216
223
  smtp_username?: string;
217
224
  smtp_password?: string;
218
225
  }
@@ -0,0 +1,16 @@
1
+ import type { CredentialTypeField } from '../types/integrations.js';
2
+ /**
3
+ * Whether the values an edit would send differ from what the credential holds, as
4
+ * far as the studio can see it (PO-535).
5
+ *
6
+ * `payload` is what a save would send: every non-secret field, and only the secrets
7
+ * that were typed — a blank secret means "keep the stored one" and is never sent.
8
+ * So a secret in the payload is a change by definition, since the stored one is never
9
+ * shown; a non-secret is a change when it reads differently from `public_config`.
10
+ *
11
+ * The platform judges this again on the merged values before it records a test, so
12
+ * this only decides whether to ask for a test of the edit at all — which takes the
13
+ * right to change the credential, where a test of the stored one takes only the
14
+ * right to read it.
15
+ */
16
+ export declare function credentialEditDiffers(fields: CredentialTypeField[], payload: Record<string, unknown>, publicConfig: Record<string, unknown>): boolean;
@@ -0,0 +1,7 @@
1
+ export function credentialEditDiffers(fields, payload, publicConfig) {
2
+ const secret = new Set(fields.filter((f) => f.type === "secret").map((f) => f.key));
3
+ return Object.entries(payload).some(([key, value]) => secret.has(key) || readsAs(value) !== readsAs(publicConfig[key]));
4
+ }
5
+ function readsAs(value) {
6
+ return value === void 0 || value === null ? "" : String(value);
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {