@revenexx/studio-integrations 3.0.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +3 -2
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +3 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +1 -1
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +3 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +1 -1
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue +55 -33
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +14 -2
- package/dist/runtime/composables/useIntegrationsAudit.d.ts +7 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +9 -5
- package/dist/runtime/composables/useIntegrationsRuns.js +8 -4
- package/dist/runtime/composables/useIntegrationsSchedules.js +6 -3
- package/dist/runtime/composables/useIntegrationsSecrets.d.ts +6 -5
- package/dist/runtime/composables/useIntegrationsSecrets.js +3 -2
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -1
- package/dist/runtime/composables/useIntegrationsTemplates.js +20 -15
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -4
- package/dist/runtime/composables/useNodeRetirement.js +5 -5
- package/dist/runtime/i18n/messages/de.json +20 -3
- package/dist/runtime/i18n/messages/en.json +20 -3
- package/dist/runtime/pages/integrations/audit.vue +43 -9
- package/dist/runtime/pages/integrations/credentials.vue +3 -2
- package/dist/runtime/pages/integrations/runs.vue +3 -1
- package/dist/runtime/pages/integrations/secrets.vue +3 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +22 -23
- package/dist/runtime/types/integrations.d.ts +25 -7
- package/dist/runtime/utils/apiEnvelope.d.ts +15 -5
- package/dist/runtime/utils/apiEnvelope.js +11 -8
- package/dist/runtime/utils/credentialTest.d.ts +17 -0
- package/dist/runtime/utils/credentialTest.js +14 -0
- package/dist/runtime/utils/runEnvelope.d.ts +22 -24
- package/dist/runtime/utils/runEnvelope.js +2 -4
- package/dist/runtime/utils/stepError.d.ts +13 -0
- package/dist/runtime/utils/stepError.js +24 -0
- package/package.json +1 -1
- package/dist/runtime/data/integrationWorkflowSamples.d.ts +0 -18
- package/dist/runtime/data/integrationWorkflowSamples.js +0 -318
|
@@ -3,7 +3,7 @@ import { useIntegrationsApi } from "./integrationsApi.js";
|
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
4
|
import { assignTriggerHandles, pausedTriggers } from "../utils/workflowFile.js";
|
|
5
5
|
import { errorReason } from "../utils/apiError.js";
|
|
6
|
-
import {
|
|
6
|
+
import { bareRecord, unwrapList } from "../utils/apiEnvelope.js";
|
|
7
7
|
import { useLoadGate } from "./useLoadGate.js";
|
|
8
8
|
import { eventConfigForPlatform } from "../data/eventTrigger.js";
|
|
9
9
|
const BLOB_VERSION = "v0-draft";
|
|
@@ -140,7 +140,7 @@ export function useIntegrationsWorkflows() {
|
|
|
140
140
|
}
|
|
141
141
|
async function get(id, { strict = false } = {}) {
|
|
142
142
|
try {
|
|
143
|
-
const wf = ingest(
|
|
143
|
+
const wf = ingest(bareRecord(await api.get(`/workflows/${id}`)));
|
|
144
144
|
upsertCache(wf);
|
|
145
145
|
return wf;
|
|
146
146
|
} catch (e) {
|
|
@@ -149,7 +149,7 @@ export function useIntegrationsWorkflows() {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
async function create(input) {
|
|
152
|
-
const wf = ingest(
|
|
152
|
+
const wf = ingest(bareRecord(await api.post("/workflows", toPayload({
|
|
153
153
|
name: input.name,
|
|
154
154
|
description: input.description,
|
|
155
155
|
blob: input.blob,
|
|
@@ -182,7 +182,7 @@ export function useIntegrationsWorkflows() {
|
|
|
182
182
|
throw lastError instanceof Error ? lastError : new Error(t("integration.workflows.error.duplicateFailed"));
|
|
183
183
|
}
|
|
184
184
|
async function update(id, patch) {
|
|
185
|
-
const wf = ingest(
|
|
185
|
+
const wf = ingest(bareRecord(await api.put(`/workflows/${id}`, toPayload({
|
|
186
186
|
name: patch.name,
|
|
187
187
|
description: patch.description,
|
|
188
188
|
blob: patch.blob,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
2
|
-
import {
|
|
2
|
+
import { bareRecord, unwrapList } from "../utils/apiEnvelope.js";
|
|
3
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
4
4
|
function versionPath(slug, version) {
|
|
5
5
|
return `/nodes/${encodeURIComponent(slug)}/${encodeURIComponent(version)}`;
|
|
@@ -11,7 +11,7 @@ export function useNodeRetirement() {
|
|
|
11
11
|
return all.filter((n) => n.package?.name === packageName);
|
|
12
12
|
}
|
|
13
13
|
async function usage(slug, version) {
|
|
14
|
-
const answer =
|
|
14
|
+
const answer = bareRecord(await api.get(`${versionPath(slug, version)}/usage`));
|
|
15
15
|
const counts = answer?.usage;
|
|
16
16
|
if (!counts || typeof counts.workflows !== "number") {
|
|
17
17
|
throw new IntegrationsApiError(200, answer, "");
|
|
@@ -19,13 +19,13 @@ export function useNodeRetirement() {
|
|
|
19
19
|
return counts;
|
|
20
20
|
}
|
|
21
21
|
async function retire(slug, version, reason) {
|
|
22
|
-
return
|
|
22
|
+
return bareRecord(await api.post(`${versionPath(slug, version)}/retire`, {
|
|
23
23
|
confirm: `${slug}@${version}`,
|
|
24
24
|
...reason ? { reason } : {}
|
|
25
|
-
})
|
|
25
|
+
}));
|
|
26
26
|
}
|
|
27
27
|
async function restore(slug, version) {
|
|
28
|
-
return
|
|
28
|
+
return bareRecord(await api.del(`${versionPath(slug, version)}/retire`));
|
|
29
29
|
}
|
|
30
30
|
return { versionsOfPackage, usage, retire, restore };
|
|
31
31
|
}
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
"integration.audit.action.create": "erstellt",
|
|
4
4
|
"integration.audit.action.delete": "gelöscht",
|
|
5
5
|
"integration.audit.action.loadMore": "Weitere laden",
|
|
6
|
+
"integration.audit.action.restore": "wiederhergestellt",
|
|
7
|
+
"integration.audit.action.retire": "ausgemustert",
|
|
8
|
+
"integration.audit.action.runCancelled": "Run abgebrochen",
|
|
6
9
|
"integration.audit.action.runFailed": "Run fehlgeschlagen",
|
|
7
10
|
"integration.audit.action.runFinished": "Run beendet",
|
|
11
|
+
"integration.audit.action.runResumed": "Run fortgesetzt",
|
|
8
12
|
"integration.audit.action.runStarted": "Run gestartet",
|
|
9
13
|
"integration.audit.action.update": "aktualisiert",
|
|
10
14
|
"integration.audit.actor.system": "System",
|
|
@@ -14,13 +18,18 @@
|
|
|
14
18
|
"integration.audit.detail.id": "ID",
|
|
15
19
|
"integration.audit.detail.tenant": "Mandant",
|
|
16
20
|
"integration.audit.head.title": "Audit-Log · Integration Studio",
|
|
17
|
-
"integration.audit.header.description": "Wer hat was geändert, und wann. Nur anfügend, pro Mandant — Zugangsdaten, Secrets, Workflows, Trigger und
|
|
21
|
+
"integration.audit.header.description": "Wer hat was geändert, und wann. Nur anfügend, pro Mandant — Zugangsdaten, Secrets, Workflows, Trigger, Runs, Node-Versionen und gespeicherter Zustand.",
|
|
18
22
|
"integration.audit.header.title": "Audit-Log",
|
|
19
23
|
"integration.audit.panel.close.aria": "Details schließen",
|
|
20
24
|
"integration.audit.panel.dismiss.aria": "Details schließen",
|
|
21
25
|
"integration.audit.panel.title.aria": "Details zum Audit-Eintrag",
|
|
26
|
+
"integration.audit.resource.run": "Run von Workflow #{workflow}",
|
|
27
|
+
"integration.audit.resource.trigger": "{type}-Trigger von Workflow #{workflow}",
|
|
22
28
|
"integration.audit.resourceType.credential": "Zugangsdaten",
|
|
29
|
+
"integration.audit.resourceType.nodeVersion": "Node-Version",
|
|
23
30
|
"integration.audit.resourceType.secret": "Secret",
|
|
31
|
+
"integration.audit.resourceType.stateEntry": "Zustandseintrag",
|
|
32
|
+
"integration.audit.resourceType.stateNamespace": "Zustands-Namespace",
|
|
24
33
|
"integration.audit.resourceType.trigger": "Trigger",
|
|
25
34
|
"integration.audit.resourceType.workflow": "Workflow",
|
|
26
35
|
"integration.audit.resourceType.workflowRun": "Workflow-Run",
|
|
@@ -285,6 +294,9 @@
|
|
|
285
294
|
"integration.credentials.shared.field.select.placeholder": "Auswählen",
|
|
286
295
|
"integration.credentials.shared.oauthHint": "Nach dem Speichern öffnet sich ein Fenster, um die Verbindung mit dem Anbieter herzustellen.",
|
|
287
296
|
"integration.credentials.shared.test.failed": "Verbindung fehlgeschlagen.",
|
|
297
|
+
"integration.credentials.shared.test.notAllowed": "Nur wer diese Zugangsdaten ändern darf, kann Änderungen daran testen.",
|
|
298
|
+
"integration.credentials.shared.test.notTested": "Nicht getestet — die Plattform konnte den Test nicht ausführen. Über die Verbindung ist nichts bekannt.",
|
|
299
|
+
"integration.credentials.shared.test.notTestedBecause": "Nicht getestet — {reason}",
|
|
288
300
|
"integration.credentials.shared.test.ok": "Verbindung ok.",
|
|
289
301
|
"integration.credentials.state.connected": "Verbunden",
|
|
290
302
|
"integration.credentials.state.failed": "Fehlgeschlagen",
|
|
@@ -908,6 +920,7 @@
|
|
|
908
920
|
"integration.replay.debug.field.config": "Konfiguration",
|
|
909
921
|
"integration.replay.debug.field.duration": "Dauer",
|
|
910
922
|
"integration.replay.debug.field.error": "Fehler",
|
|
923
|
+
"integration.replay.debug.field.frames": "Stelle im Code des Nodes",
|
|
911
924
|
"integration.replay.debug.field.input": "Eingabe",
|
|
912
925
|
"integration.replay.debug.field.output": "Ausgabe",
|
|
913
926
|
"integration.replay.debug.field.started": "Gestartet",
|
|
@@ -1096,10 +1109,10 @@
|
|
|
1096
1109
|
"integration.secrets.dialog.create.title": "Secret anlegen",
|
|
1097
1110
|
"integration.secrets.dialog.delete.body": "wird endgültig entfernt. Workflows, die darauf verweisen, funktionieren beim nächsten Run nicht mehr.",
|
|
1098
1111
|
"integration.secrets.dialog.delete.title": "Secret löschen?",
|
|
1099
|
-
"integration.secrets.error.invalidKey": "Der Schlüssel muss dem Muster ^[A-
|
|
1112
|
+
"integration.secrets.error.invalidKey": "Der Schlüssel muss dem Muster ^[A-Za-z0-9_-]+$ entsprechen und darf höchstens 191 Zeichen lang sein.",
|
|
1100
1113
|
"integration.secrets.error.loadFailed": "Secrets konnten nicht geladen werden.",
|
|
1101
1114
|
"integration.secrets.error.valueRequired": "Der Wert ist erforderlich.",
|
|
1102
|
-
"integration.secrets.form.error.invalidKey": "Der Schlüssel muss ^[A-
|
|
1115
|
+
"integration.secrets.form.error.invalidKey": "Der Schlüssel muss ^[A-Za-z0-9_-]+$ entsprechen (Buchstaben, Ziffern, Unterstriche, Bindestriche).",
|
|
1103
1116
|
"integration.secrets.form.error.saveFailed": "Das Secret konnte nicht gespeichert werden.",
|
|
1104
1117
|
"integration.secrets.form.key.label": "Schlüssel",
|
|
1105
1118
|
"integration.secrets.form.key.patternLabel": "Muster:",
|
|
@@ -1156,9 +1169,11 @@
|
|
|
1156
1169
|
"integration.templates.detail.install.secretsDescriptionSuffix": ".",
|
|
1157
1170
|
"integration.templates.detail.install.secretsHeader": "Secrets",
|
|
1158
1171
|
"integration.templates.detail.install.selectLater": "Später auswählen",
|
|
1172
|
+
"integration.templates.detail.install.settingLabel": "Node {id} · {setting}",
|
|
1159
1173
|
"integration.templates.detail.install.state.loading": "Es wird geprüft, was diese Vorlage benötigt…",
|
|
1160
1174
|
"integration.templates.detail.install.submit": "Installieren",
|
|
1161
1175
|
"integration.templates.detail.install.submitting": "Wird eingerichtet…",
|
|
1176
|
+
"integration.templates.detail.install.stillInstallable": "Sie können sie trotzdem installieren und die Zugangsdaten im Editor verbinden.",
|
|
1162
1177
|
"integration.templates.detail.publisher.kind.community": "Community",
|
|
1163
1178
|
"integration.templates.detail.publisher.kind.partner": "Partner",
|
|
1164
1179
|
"integration.templates.detail.publisher.kind.revenexx": "Offiziell",
|
|
@@ -1176,6 +1191,8 @@
|
|
|
1176
1191
|
"integration.templates.detail.setup.missingCredentials": "{n} Zugangsdatensatz zu verbinden | {n} Zugangsdatensätze zu verbinden",
|
|
1177
1192
|
"integration.templates.detail.setup.missingSecrets": "{n} Secret fehlt | {n} Secrets fehlen",
|
|
1178
1193
|
"integration.templates.detail.setup.ready": "Alles ist bereit für die Installation.",
|
|
1194
|
+
"integration.templates.detail.setup.unchecked": "Es ließ sich nicht prüfen, was diese Vorlage benötigt: {reason}",
|
|
1195
|
+
"integration.templates.detail.setup.uncheckedFallback": "Die Plattform hat nicht geantwortet.",
|
|
1179
1196
|
"integration.templates.detail.state.loading": "Vorlage wird geladen…",
|
|
1180
1197
|
"integration.templates.detail.state.notFound": "Vorlage nicht gefunden.",
|
|
1181
1198
|
"integration.templates.detail.steps.empty": "Für diese Vorlage gibt es noch keine Schritt-für-Schritt-Übersicht.",
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
"integration.audit.action.create": "create",
|
|
4
4
|
"integration.audit.action.delete": "delete",
|
|
5
5
|
"integration.audit.action.loadMore": "Load more",
|
|
6
|
+
"integration.audit.action.restore": "restore",
|
|
7
|
+
"integration.audit.action.retire": "retire",
|
|
8
|
+
"integration.audit.action.runCancelled": "run cancelled",
|
|
6
9
|
"integration.audit.action.runFailed": "run failed",
|
|
7
10
|
"integration.audit.action.runFinished": "run finished",
|
|
11
|
+
"integration.audit.action.runResumed": "run resumed",
|
|
8
12
|
"integration.audit.action.runStarted": "run started",
|
|
9
13
|
"integration.audit.action.update": "update",
|
|
10
14
|
"integration.audit.actor.system": "system",
|
|
@@ -14,13 +18,18 @@
|
|
|
14
18
|
"integration.audit.detail.id": "ID",
|
|
15
19
|
"integration.audit.detail.tenant": "Tenant",
|
|
16
20
|
"integration.audit.head.title": "Audit Log · Integration Studio",
|
|
17
|
-
"integration.audit.header.description": "Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers and
|
|
21
|
+
"integration.audit.header.description": "Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers, runs, node versions and stored state.",
|
|
18
22
|
"integration.audit.header.title": "Audit Log",
|
|
19
23
|
"integration.audit.panel.close.aria": "Close entry details",
|
|
20
24
|
"integration.audit.panel.dismiss.aria": "Dismiss entry details",
|
|
21
25
|
"integration.audit.panel.title.aria": "Audit entry details",
|
|
26
|
+
"integration.audit.resource.run": "Run of workflow #{workflow}",
|
|
27
|
+
"integration.audit.resource.trigger": "{type} trigger of workflow #{workflow}",
|
|
22
28
|
"integration.audit.resourceType.credential": "credential",
|
|
29
|
+
"integration.audit.resourceType.nodeVersion": "node version",
|
|
23
30
|
"integration.audit.resourceType.secret": "secret",
|
|
31
|
+
"integration.audit.resourceType.stateEntry": "state entry",
|
|
32
|
+
"integration.audit.resourceType.stateNamespace": "state namespace",
|
|
24
33
|
"integration.audit.resourceType.trigger": "trigger",
|
|
25
34
|
"integration.audit.resourceType.workflow": "workflow",
|
|
26
35
|
"integration.audit.resourceType.workflowRun": "workflow run",
|
|
@@ -285,6 +294,9 @@
|
|
|
285
294
|
"integration.credentials.shared.field.select.placeholder": "Pick one",
|
|
286
295
|
"integration.credentials.shared.oauthHint": "After saving, a window opens to connect with the provider.",
|
|
287
296
|
"integration.credentials.shared.test.failed": "Connection failed.",
|
|
297
|
+
"integration.credentials.shared.test.notAllowed": "Only someone who may change this credential can test changes to it.",
|
|
298
|
+
"integration.credentials.shared.test.notTested": "Not tested — the platform could not run the test. Nothing is known about the connection.",
|
|
299
|
+
"integration.credentials.shared.test.notTestedBecause": "Not tested — {reason}",
|
|
288
300
|
"integration.credentials.shared.test.ok": "Connection ok.",
|
|
289
301
|
"integration.credentials.state.connected": "Connected",
|
|
290
302
|
"integration.credentials.state.failed": "Failed",
|
|
@@ -908,6 +920,7 @@
|
|
|
908
920
|
"integration.replay.debug.field.config": "Config",
|
|
909
921
|
"integration.replay.debug.field.duration": "Duration",
|
|
910
922
|
"integration.replay.debug.field.error": "Error",
|
|
923
|
+
"integration.replay.debug.field.frames": "Where in the node's code",
|
|
911
924
|
"integration.replay.debug.field.input": "Input",
|
|
912
925
|
"integration.replay.debug.field.output": "Output",
|
|
913
926
|
"integration.replay.debug.field.started": "Started",
|
|
@@ -1096,10 +1109,10 @@
|
|
|
1096
1109
|
"integration.secrets.dialog.create.title": "Create a secret",
|
|
1097
1110
|
"integration.secrets.dialog.delete.body": "will be permanently removed. Workflows that reference it will break the next time they run.",
|
|
1098
1111
|
"integration.secrets.dialog.delete.title": "Delete secret?",
|
|
1099
|
-
"integration.secrets.error.invalidKey": "Key must match ^[A-
|
|
1112
|
+
"integration.secrets.error.invalidKey": "Key must match ^[A-Za-z0-9_-]+$ and be at most 191 characters",
|
|
1100
1113
|
"integration.secrets.error.loadFailed": "Could not load secrets.",
|
|
1101
1114
|
"integration.secrets.error.valueRequired": "Value is required.",
|
|
1102
|
-
"integration.secrets.form.error.invalidKey": "Key must match ^[A-
|
|
1115
|
+
"integration.secrets.form.error.invalidKey": "Key must match ^[A-Za-z0-9_-]+$ (letters, digits, underscores, hyphens).",
|
|
1103
1116
|
"integration.secrets.form.error.saveFailed": "Could not save the secret.",
|
|
1104
1117
|
"integration.secrets.form.key.label": "Key",
|
|
1105
1118
|
"integration.secrets.form.key.patternLabel": "Pattern:",
|
|
@@ -1156,9 +1169,11 @@
|
|
|
1156
1169
|
"integration.templates.detail.install.secretsDescriptionSuffix": "page.",
|
|
1157
1170
|
"integration.templates.detail.install.secretsHeader": "Secrets",
|
|
1158
1171
|
"integration.templates.detail.install.selectLater": "Select later",
|
|
1172
|
+
"integration.templates.detail.install.settingLabel": "node {id} · {setting}",
|
|
1159
1173
|
"integration.templates.detail.install.state.loading": "Checking what this template needs…",
|
|
1160
1174
|
"integration.templates.detail.install.submit": "Install",
|
|
1161
1175
|
"integration.templates.detail.install.submitting": "Setting up…",
|
|
1176
|
+
"integration.templates.detail.install.stillInstallable": "You can still install it and connect its credentials in the editor.",
|
|
1162
1177
|
"integration.templates.detail.publisher.kind.community": "Community",
|
|
1163
1178
|
"integration.templates.detail.publisher.kind.partner": "Partner",
|
|
1164
1179
|
"integration.templates.detail.publisher.kind.revenexx": "Official",
|
|
@@ -1176,6 +1191,8 @@
|
|
|
1176
1191
|
"integration.templates.detail.setup.missingCredentials": "{n} credential to connect | {n} credentials to connect",
|
|
1177
1192
|
"integration.templates.detail.setup.missingSecrets": "{n} secret missing | {n} secrets missing",
|
|
1178
1193
|
"integration.templates.detail.setup.ready": "Everything is ready to install.",
|
|
1194
|
+
"integration.templates.detail.setup.unchecked": "Could not check what this template needs: {reason}",
|
|
1195
|
+
"integration.templates.detail.setup.uncheckedFallback": "The platform did not answer.",
|
|
1179
1196
|
"integration.templates.detail.state.loading": "Loading template…",
|
|
1180
1197
|
"integration.templates.detail.state.notFound": "Template not found.",
|
|
1181
1198
|
"integration.templates.detail.steps.empty": "No step-by-step outline for this template yet.",
|
|
@@ -7,20 +7,33 @@ import {
|
|
|
7
7
|
Key as Secret,
|
|
8
8
|
Programming as Workflow,
|
|
9
9
|
Bolt as Trigger,
|
|
10
|
-
Play as Run
|
|
10
|
+
Play as Run,
|
|
11
|
+
Box as NodeVersion,
|
|
12
|
+
Database as StateNamespace,
|
|
13
|
+
Document as StateEntry
|
|
11
14
|
} from "#solar-icons";
|
|
12
15
|
import { Badge, Button } from "@revenexx/studio";
|
|
13
16
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
14
17
|
import { isBareIdentifier, personLabel } from "../../utils/personLabel";
|
|
18
|
+
import { triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
15
19
|
const t = useIntegrationsT();
|
|
16
20
|
useHead({ title: () => t("integration.audit.head.title") });
|
|
17
21
|
const { entries, loading, error, hasMore, load, loadMore } = useIntegrationsAudit();
|
|
18
|
-
const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run"];
|
|
19
|
-
const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed"];
|
|
22
|
+
const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run", "node_version", "state_namespace", "state_entry"];
|
|
23
|
+
const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed", "run.cancelled", "run.resumed", "retire", "restore"];
|
|
20
24
|
onMounted(() => {
|
|
21
25
|
void load();
|
|
22
26
|
});
|
|
23
|
-
const resourceIcon = {
|
|
27
|
+
const resourceIcon = {
|
|
28
|
+
credential: Credential,
|
|
29
|
+
secret: Secret,
|
|
30
|
+
workflow: Workflow,
|
|
31
|
+
trigger: Trigger,
|
|
32
|
+
workflow_run: Run,
|
|
33
|
+
node_version: NodeVersion,
|
|
34
|
+
state_namespace: StateNamespace,
|
|
35
|
+
state_entry: StateEntry
|
|
36
|
+
};
|
|
24
37
|
function actionTone(a) {
|
|
25
38
|
if (a === "create" || a === "run.finished") return "bg-emerald-500/10 text-emerald-700 border-emerald-500/20";
|
|
26
39
|
if (a === "update") return "bg-amber-500/10 text-amber-700 border-amber-500/20";
|
|
@@ -32,7 +45,10 @@ const resourceTypeKeys = {
|
|
|
32
45
|
secret: "integration.audit.resourceType.secret",
|
|
33
46
|
workflow: "integration.audit.resourceType.workflow",
|
|
34
47
|
trigger: "integration.audit.resourceType.trigger",
|
|
35
|
-
workflow_run: "integration.audit.resourceType.workflowRun"
|
|
48
|
+
workflow_run: "integration.audit.resourceType.workflowRun",
|
|
49
|
+
node_version: "integration.audit.resourceType.nodeVersion",
|
|
50
|
+
state_namespace: "integration.audit.resourceType.stateNamespace",
|
|
51
|
+
state_entry: "integration.audit.resourceType.stateEntry"
|
|
36
52
|
};
|
|
37
53
|
const actionKeys = {
|
|
38
54
|
create: "integration.audit.action.create",
|
|
@@ -40,7 +56,11 @@ const actionKeys = {
|
|
|
40
56
|
delete: "integration.audit.action.delete",
|
|
41
57
|
"run.started": "integration.audit.action.runStarted",
|
|
42
58
|
"run.finished": "integration.audit.action.runFinished",
|
|
43
|
-
"run.failed": "integration.audit.action.runFailed"
|
|
59
|
+
"run.failed": "integration.audit.action.runFailed",
|
|
60
|
+
"run.cancelled": "integration.audit.action.runCancelled",
|
|
61
|
+
"run.resumed": "integration.audit.action.runResumed",
|
|
62
|
+
retire: "integration.audit.action.retire",
|
|
63
|
+
restore: "integration.audit.action.restore"
|
|
44
64
|
};
|
|
45
65
|
function prettyAction(a) {
|
|
46
66
|
const key = actionKeys[a];
|
|
@@ -51,7 +71,18 @@ function prettyType(rt) {
|
|
|
51
71
|
return key ? t(key) : rt.replace("_", " ");
|
|
52
72
|
}
|
|
53
73
|
function resourceName(e) {
|
|
54
|
-
|
|
74
|
+
const m = e.metadata ?? {};
|
|
75
|
+
const text = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
76
|
+
const workflow = m.workflow_id != null ? String(m.workflow_id) : void 0;
|
|
77
|
+
const name = text(m.name);
|
|
78
|
+
if (name) return name;
|
|
79
|
+
if (e.resource_type === "trigger" && workflow && text(m.type)) {
|
|
80
|
+
return t("integration.audit.resource.trigger", { type: triggerTypeLabel(text(m.type)), workflow });
|
|
81
|
+
}
|
|
82
|
+
if (e.resource_type === "workflow_run" && workflow) return t("integration.audit.resource.run", { workflow });
|
|
83
|
+
if (e.resource_type === "state_namespace" && text(m.namespace)) return text(m.namespace);
|
|
84
|
+
if (e.resource_type === "state_entry" && text(m.key)) return [text(m.namespace), text(m.key)].filter(Boolean).join(" \xB7 ");
|
|
85
|
+
return e.resource_id || "\u2014";
|
|
55
86
|
}
|
|
56
87
|
function fmtDate(iso) {
|
|
57
88
|
return iso ? new Date(iso).toLocaleString() : "\u2014";
|
|
@@ -68,18 +99,21 @@ function actorIsId(e) {
|
|
|
68
99
|
function actorIsPerson(e) {
|
|
69
100
|
return !!(e.actor_name || e.actor_email);
|
|
70
101
|
}
|
|
102
|
+
function withHeld(known, of) {
|
|
103
|
+
return [...known, ...[...new Set(entries.value.map(of))].filter((v) => v && !known.includes(v)).sort()];
|
|
104
|
+
}
|
|
71
105
|
const filterTabs = computed(() => [
|
|
72
106
|
{
|
|
73
107
|
key: "resource",
|
|
74
108
|
label: t("integration.audit.table.column.resource"),
|
|
75
109
|
getter: (e) => e.resource_type,
|
|
76
|
-
options: resourceTypes.map((rt) => ({ value: rt, label: prettyType(rt) }))
|
|
110
|
+
options: withHeld(resourceTypes, (e) => e.resource_type).map((rt) => ({ value: rt, label: prettyType(rt) }))
|
|
77
111
|
},
|
|
78
112
|
{
|
|
79
113
|
key: "action",
|
|
80
114
|
label: t("integration.audit.table.column.action"),
|
|
81
115
|
getter: (e) => e.action,
|
|
82
|
-
options: actions.map((a) => ({ value: a, label: prettyAction(a) }))
|
|
116
|
+
options: withHeld(actions, (e) => e.action).map((a) => ({ value: a, label: prettyAction(a) }))
|
|
83
117
|
}
|
|
84
118
|
]);
|
|
85
119
|
const columns = computed(() => [
|
|
@@ -37,6 +37,7 @@ import { hasRevenexxSource } from "../../utils/revenexxCredentialSource";
|
|
|
37
37
|
import { resolveLocalized } from "../../utils/localize";
|
|
38
38
|
import { fromModelValue, toItemValue, toModelValue } from "../../utils/selectOptions";
|
|
39
39
|
import { keepOpenBeside } from "../../utils/dialog";
|
|
40
|
+
import { testOutcomeView } from "../../utils/credentialTest";
|
|
40
41
|
const t = useIntegrationsT();
|
|
41
42
|
useHead({ title: () => t("integration.credentials.list.head.title") });
|
|
42
43
|
const { credentials, loading, error, load, create, update, remove, testInline, test, oauthAuthorizeUrl } = useIntegrationsCredentials();
|
|
@@ -462,12 +463,12 @@ async function confirmDelete() {
|
|
|
462
463
|
<div
|
|
463
464
|
v-if="testResult"
|
|
464
465
|
class="rounded-md px-3 py-2 text-sm"
|
|
465
|
-
:class="testResult.
|
|
466
|
+
:class="testOutcomeView(testResult, t).toneClass"
|
|
466
467
|
>
|
|
467
468
|
<!-- What the credential type said, whichever way the test went. The fixed
|
|
468
469
|
sentences are the fallback for a type that reports nothing, not the
|
|
469
470
|
first choice (specs/credential-test.md). -->
|
|
470
|
-
{{ testResult
|
|
471
|
+
{{ testOutcomeView(testResult, t).text }}
|
|
471
472
|
</div>
|
|
472
473
|
|
|
473
474
|
</div>
|
|
@@ -41,7 +41,9 @@ function onReorder(order) {
|
|
|
41
41
|
void setQuery(order);
|
|
42
42
|
}
|
|
43
43
|
let searchTimer;
|
|
44
|
-
|
|
44
|
+
const SEARCH_MAX = 200;
|
|
45
|
+
function onSearch(typed) {
|
|
46
|
+
const text = [...typed].slice(0, SEARCH_MAX).join("");
|
|
45
47
|
clearTimeout(searchTimer);
|
|
46
48
|
if (text === query.value.search) return;
|
|
47
49
|
searchTimer = setTimeout(() => {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "@revenexx/studio";
|
|
27
27
|
import { errorReason } from "../../utils/apiError";
|
|
28
28
|
import { keepOpenBeside } from "../../utils/dialog";
|
|
29
|
+
import { SECRET_KEY_MAX } from "../../composables/useIntegrationsSecrets";
|
|
29
30
|
const t = useIntegrationsT();
|
|
30
31
|
useHead({ title: () => t("integration.secrets.head.title") });
|
|
31
32
|
const { secrets, loading, error, isValidKey, upsert, remove, load } = useIntegrationsSecrets();
|
|
@@ -121,9 +122,10 @@ const columns = computed(() => [
|
|
|
121
122
|
placeholder="STRIPE_API_KEY"
|
|
122
123
|
class="font-mono bg-white"
|
|
123
124
|
autocomplete="off"
|
|
125
|
+
:maxlength="SECRET_KEY_MAX"
|
|
124
126
|
/>
|
|
125
127
|
<p class="text-sm text-muted-foreground">
|
|
126
|
-
{{ t("integration.secrets.form.key.patternLabel") }} <code class="font-mono">^[A-
|
|
128
|
+
{{ t("integration.secrets.form.key.patternLabel") }} <code class="font-mono">^[A-Za-z0-9_-]+$</code>
|
|
127
129
|
</p>
|
|
128
130
|
</div>
|
|
129
131
|
<div class="space-y-1.5">
|
|
@@ -22,7 +22,7 @@ import { keepOpenBeside } from "../../../utils/dialog";
|
|
|
22
22
|
const t = useIntegrationsT();
|
|
23
23
|
const route = useRoute();
|
|
24
24
|
const router = useRouter();
|
|
25
|
-
const { get, load, loaded, fetchDetail,
|
|
25
|
+
const { get, load, loaded, fetchDetail, checkRequirements, uncheckedReason } = useIntegrationsTemplates();
|
|
26
26
|
const { load: loadNodeCatalog, findManifest, resolveLocalized } = useIntegrationsNodes();
|
|
27
27
|
const { keys: secretKeys, load: loadSecrets } = useIntegrationsSecrets();
|
|
28
28
|
const { ofType, load: loadCredentials } = useIntegrationsCredentials();
|
|
@@ -30,10 +30,15 @@ const slug = computed(() => String(route.params.slug));
|
|
|
30
30
|
const tpl = computed(() => get(slug.value));
|
|
31
31
|
useHead({ title: () => t("integration.templates.detail.head.title", { name: tpl.value?.name ?? t("integration.templates.detail.head.fallbackName") }) });
|
|
32
32
|
const reqs = ref(null);
|
|
33
|
+
const reqsError = ref(null);
|
|
33
34
|
onMounted(async () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
try {
|
|
36
|
+
await load();
|
|
37
|
+
await Promise.all([fetchDetail(slug.value), loadNodeCatalog(false, { summary: true }), loadCredentials(), loadSecrets()]);
|
|
38
|
+
reqs.value = await checkRequirements(slug.value);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
reqsError.value = uncheckedReason(e);
|
|
41
|
+
}
|
|
37
42
|
});
|
|
38
43
|
const descriptionHtml = computed(() => renderTemplateDescription(tpl.value?.descriptionMd));
|
|
39
44
|
const steps = computed(() => {
|
|
@@ -86,7 +91,7 @@ const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((trig)
|
|
|
86
91
|
const when = describeCron(String(trig.config.cron)) ?? String(trig.config.cron);
|
|
87
92
|
label = `${t("integration.templates.detail.trigger.schedule")} \xB7 ${when}${trig.config.timezone ? ` (${trig.config.timezone})` : ""}`;
|
|
88
93
|
}
|
|
89
|
-
if (trig.type === "webhook" && trig.config?.
|
|
94
|
+
if (trig.type === "webhook" && trig.config?.method) label = `${t("integration.templates.trigger.webhook")} \xB7 ${String(trig.config.method).toUpperCase()}`;
|
|
90
95
|
if (trig.type === "manual") label = t("integration.templates.trigger.manual");
|
|
91
96
|
return { key: trig.id, icon: glyphForTrigger(trig.type), label };
|
|
92
97
|
}));
|
|
@@ -104,24 +109,14 @@ const variableColumns = computed(() => [
|
|
|
104
109
|
]);
|
|
105
110
|
const credentialReqs = computed(() => reqs.value?.credentialTypes ?? []);
|
|
106
111
|
const secretReqs = computed(() => reqs.value?.secrets ?? []);
|
|
107
|
-
const secretChecks = computed(() => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
label: s.key ?? t("integration.templates.detail.secrets.chosenAtInstall"),
|
|
113
|
-
present: s.exists || (s.key ? secretKeys.value.includes(s.key) : false)
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
return (tpl.value?.requiredSecrets ?? []).map((key) => ({
|
|
117
|
-
key,
|
|
118
|
-
label: key,
|
|
119
|
-
present: secretKeys.value.includes(key)
|
|
120
|
-
}));
|
|
121
|
-
});
|
|
112
|
+
const secretChecks = computed(() => secretReqs.value.map((s) => ({
|
|
113
|
+
key: s.key,
|
|
114
|
+
label: s.key ?? t("integration.templates.detail.secrets.chosenAtInstall"),
|
|
115
|
+
present: s.exists || (s.key ? secretKeys.value.includes(s.key) : false)
|
|
116
|
+
})));
|
|
122
117
|
const missingSecrets = computed(() => secretChecks.value.filter((s) => !s.present).length);
|
|
123
118
|
const missingCredentials = computed(() => credentialReqs.value.filter((c) => !ofType(c.slug).length).length);
|
|
124
|
-
const setupReady = computed(() => missingSecrets.value === 0 && missingCredentials.value === 0);
|
|
119
|
+
const setupReady = computed(() => reqs.value !== null && missingSecrets.value === 0 && missingCredentials.value === 0);
|
|
125
120
|
const packageLabel = computed(() => tpl.value ? templatePackageLabel(tpl.value) : null);
|
|
126
121
|
const categoryLabel = computed(() => templateCategoryLabel(tpl.value?.category ?? ""));
|
|
127
122
|
const installOpen = ref(false);
|
|
@@ -298,11 +293,15 @@ function levelDot(level) {
|
|
|
298
293
|
point `aria-labelledby` at. Copy this pattern only for a panel
|
|
299
294
|
that genuinely has none. -->
|
|
300
295
|
<div role="group" :aria-label="t('integration.templates.detail.setup.ariaLabel')" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2.5">
|
|
301
|
-
<div v-if="
|
|
296
|
+
<div v-if="reqsError" class="flex items-start gap-1.5 text-sm text-amber-700" data-testid="template-setup-unchecked">
|
|
297
|
+
<ShieldAlert :size="14" class="mt-0.5 shrink-0" />
|
|
298
|
+
{{ t("integration.templates.detail.setup.unchecked", { reason: reqsError }) }}
|
|
299
|
+
</div>
|
|
300
|
+
<div v-else-if="setupReady" class="flex items-center gap-1.5 text-sm text-emerald-700">
|
|
302
301
|
<CheckCircle2 :size="14" />
|
|
303
302
|
{{ t("integration.templates.detail.setup.ready") }}
|
|
304
303
|
</div>
|
|
305
|
-
<div v-else class="text-sm text-amber-700 space-y-0.5">
|
|
304
|
+
<div v-else-if="reqs" class="text-sm text-amber-700 space-y-0.5">
|
|
306
305
|
<div v-if="missingCredentials" class="flex items-center gap-1.5">
|
|
307
306
|
<Key :size="12" />
|
|
308
307
|
{{ t("integration.templates.detail.setup.missingCredentials", missingCredentials) }}
|
|
@@ -472,7 +472,7 @@ export interface CredentialTypeField {
|
|
|
472
472
|
label: LocalizedString;
|
|
473
473
|
required?: boolean;
|
|
474
474
|
options?: Array<{
|
|
475
|
-
label:
|
|
475
|
+
label: LocalizedString;
|
|
476
476
|
value: string | number | boolean;
|
|
477
477
|
}>;
|
|
478
478
|
validation?: {
|
|
@@ -495,8 +495,10 @@ export interface CredentialType {
|
|
|
495
495
|
slug: string;
|
|
496
496
|
version: string;
|
|
497
497
|
name: LocalizedString;
|
|
498
|
-
|
|
499
|
-
|
|
498
|
+
/** Always sent; null where the descriptor has none. */
|
|
499
|
+
description?: LocalizedString | null;
|
|
500
|
+
/** Always sent; null where the descriptor has none. */
|
|
501
|
+
icon?: string | null;
|
|
500
502
|
/** Signed logo/brand image URLs from the API (prefer `category: 'logo'`). */
|
|
501
503
|
images?: CredentialTypeImage[];
|
|
502
504
|
auth_kind: AuthKind;
|
|
@@ -629,7 +631,8 @@ export interface WorkflowTemplate {
|
|
|
629
631
|
* under `sync` is a name invented by the reader's side of the wire.
|
|
630
632
|
*/
|
|
631
633
|
category: TemplateCategory | '';
|
|
632
|
-
|
|
634
|
+
/** Free-form registry tags; `any` is the one the studio words itself. */
|
|
635
|
+
industries: string[];
|
|
633
636
|
/** Vendor slugs this template wires together (used to compute prerequisites). */
|
|
634
637
|
vendors: string[];
|
|
635
638
|
/** Required secret keys; UI shows which are missing. */
|
|
@@ -695,6 +698,17 @@ export interface WorkflowRunNodeStep {
|
|
|
695
698
|
attempt: number;
|
|
696
699
|
durationMs: number | null;
|
|
697
700
|
}
|
|
701
|
+
export interface RunStepFrame {
|
|
702
|
+
file: string;
|
|
703
|
+
line: number;
|
|
704
|
+
column: number;
|
|
705
|
+
name?: string | null;
|
|
706
|
+
}
|
|
707
|
+
export interface RunStepError {
|
|
708
|
+
message: string;
|
|
709
|
+
type?: string;
|
|
710
|
+
frames?: RunStepFrame[];
|
|
711
|
+
}
|
|
698
712
|
export interface RunStepRow {
|
|
699
713
|
node_id: string;
|
|
700
714
|
node_slug: string;
|
|
@@ -713,7 +727,11 @@ export interface RunStepRow {
|
|
|
713
727
|
port: string;
|
|
714
728
|
} | null;
|
|
715
729
|
branch?: string | null;
|
|
716
|
-
|
|
730
|
+
/**
|
|
731
|
+
* Why the step failed. The platform sends the object; a string is what older
|
|
732
|
+
* rows and older mocks carry. Read it through `utils/stepError.ts`, never print it.
|
|
733
|
+
*/
|
|
734
|
+
error?: RunStepError | string | null;
|
|
717
735
|
input?: unknown;
|
|
718
736
|
output?: unknown;
|
|
719
737
|
/** Resolved node config for this step (present with include_payloads). */
|
|
@@ -725,10 +743,10 @@ export interface RunStepRow {
|
|
|
725
743
|
export interface DeadLetterRow {
|
|
726
744
|
id: number;
|
|
727
745
|
workflow_id: number;
|
|
728
|
-
trigger_id:
|
|
746
|
+
trigger_id: number | null;
|
|
729
747
|
source: string | null;
|
|
730
748
|
trigger_handle: string | null;
|
|
731
|
-
reason: string
|
|
749
|
+
reason: string;
|
|
732
750
|
status: 'pending' | 'replayed' | 'discarded' | string;
|
|
733
751
|
created_at: string | null;
|
|
734
752
|
updated_at: string | null;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One record,
|
|
2
|
+
* One record, out of the `{ data: record }` a wrapping endpoint sends — the
|
|
3
|
+
* template endpoints and `GET /credential-types/{slug}` (see the table above).
|
|
3
4
|
*
|
|
4
|
-
* Throws when the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Throws when the body carries no `data`, a null one, or no body at all — the
|
|
6
|
+
* case the old expression turned into an object with none of the caller's
|
|
7
|
+
* fields, which every page downstream drew as empty.
|
|
7
8
|
*
|
|
8
9
|
* **The thrown message is deliberately empty.** `errorReason(err, fallback)` in
|
|
9
10
|
* `apiError.ts` falls back to the caller's own translated sentence when the
|
|
@@ -11,7 +12,16 @@
|
|
|
11
12
|
* not load this" copy rather than as an untranslated technical string in the UI.
|
|
12
13
|
* The response that caused it rides along in `body`.
|
|
13
14
|
*/
|
|
14
|
-
export declare function unwrapRecord<T>(res: unknown
|
|
15
|
+
export declare function unwrapRecord<T>(res: unknown): T;
|
|
16
|
+
/**
|
|
17
|
+
* One record that IS the body — every single-record endpoint outside templates.
|
|
18
|
+
*
|
|
19
|
+
* Throws, the way `unwrapRecord` does, for no body at all and for a body shaped
|
|
20
|
+
* like an envelope: a `data` key and none of `ownFields`. No version of these
|
|
21
|
+
* endpoints sent one. `ownFields` is for a record that may carry a `data` field of
|
|
22
|
+
* its own — a run (`RUN_OWN_FIELDS`) — so that one is not mistaken for a wrapper.
|
|
23
|
+
*/
|
|
24
|
+
export declare function bareRecord<T>(res: unknown, ownFields?: readonly string[]): T;
|
|
15
25
|
/**
|
|
16
26
|
* A list, from `{ data: [ … ] }` or from a bare array.
|
|
17
27
|
*
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { IntegrationsApiError } from "./apiError.js";
|
|
2
|
-
export function unwrapRecord(res
|
|
3
|
-
|
|
2
|
+
export function unwrapRecord(res) {
|
|
3
|
+
const data = res?.data;
|
|
4
|
+
if (data === null || data === void 0 || typeof data !== "object" || Array.isArray(data)) {
|
|
4
5
|
throw new IntegrationsApiError(200, res, "");
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
export function bareRecord(res, ownFields = []) {
|
|
10
|
+
if (res === null || res === void 0 || typeof res !== "object" || Array.isArray(res)) {
|
|
11
|
+
throw new IntegrationsApiError(200, res, "");
|
|
12
|
+
}
|
|
13
|
+
if ("data" in res && !ownFields.some((f) => f in res)) {
|
|
14
|
+
throw new IntegrationsApiError(200, res, "");
|
|
12
15
|
}
|
|
13
16
|
return res;
|
|
14
17
|
}
|