@revenexx/studio-integrations 0.6.0 → 1.0.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 +643 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +32 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +45 -38
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +21 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +30 -16
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -8
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +45 -42
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +315 -221
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +37 -10
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +40 -35
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +82 -34
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +18 -8
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +22 -12
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +41 -39
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +131 -87
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +27 -24
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +76 -46
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -13
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +79 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +14 -2
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +21 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +30 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +4 -2
- package/dist/runtime/data/integrationNodeCatalog.d.ts +63 -1
- package/dist/runtime/data/integrationNodeCatalog.js +22 -4
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1224 -0
- package/dist/runtime/i18n/messages/en.json +1224 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +73 -52
- package/dist/runtime/pages/integrations/index.vue +66 -73
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +89 -72
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +351 -197
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +25 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +30 -2
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +52 -5
- package/dist/runtime/utils/localize.js +47 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
|
@@ -4,6 +4,22 @@ import '@vue-flow/minimap/dist/style.css';
|
|
|
4
4
|
import type { Workflow, WorkflowBlob } from '../../types/integrations.js';
|
|
5
5
|
type __VLS_Props = {
|
|
6
6
|
workflow: Workflow;
|
|
7
|
+
/**
|
|
8
|
+
* Unsaved work the page is still holding for this workflow, read ONCE, at
|
|
9
|
+
* mount (behaviour specs/workflow-save.md).
|
|
10
|
+
*
|
|
11
|
+
* The editor is mounted per tab, so a trip to the Runs tab and back seeded a
|
|
12
|
+
* fresh working copy from the SAVED blob while the page still held the draft:
|
|
13
|
+
* the canvas showed one graph, the header said "Unsaved changes" about
|
|
14
|
+
* another, and Save wrote the one nobody could see. Seeding from the draft is
|
|
15
|
+
* what makes a move inside the workflow keep the work.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately not watched. The page's draft is a copy of what this editor
|
|
18
|
+
* last emitted, so following it would re-seed the canvas — and drop the
|
|
19
|
+
* selection, the undo stack and any open dialog with it — on the user's own
|
|
20
|
+
* keystrokes.
|
|
21
|
+
*/
|
|
22
|
+
draft?: WorkflowBlob | null;
|
|
7
23
|
/** Busy flag for the control-pane Play button (a run is being triggered). */
|
|
8
24
|
runBusy?: boolean;
|
|
9
25
|
/** The run is queued behind a bundle build — busy, but worth saying why. */
|
|
@@ -3,6 +3,12 @@ import '@vue-flow/core/dist/theme-default.css';
|
|
|
3
3
|
import type { Workflow } from '../../types/integrations.js';
|
|
4
4
|
type __VLS_Props = {
|
|
5
5
|
workflow: Workflow;
|
|
6
|
+
/**
|
|
7
|
+
* The editor has unsaved work the page is holding. Nothing here shows it — the
|
|
8
|
+
* canvas behind this tab is not mounted — but a restore is what destroys it, so
|
|
9
|
+
* the question this panel asks has to name it (specs/workflow-save.md).
|
|
10
|
+
*/
|
|
11
|
+
draftPending?: boolean;
|
|
6
12
|
};
|
|
7
13
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
14
|
restored: () => any;
|
|
@@ -20,10 +20,12 @@ import NodeDynamicOutputsResolver from "./NodeDynamicOutputsResolver.vue";
|
|
|
20
20
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
21
21
|
import { useIntegrationsRevisions } from "../../composables/useIntegrationsRevisions";
|
|
22
22
|
const props = defineProps({
|
|
23
|
-
workflow: { type: Object, required: true }
|
|
23
|
+
workflow: { type: Object, required: true },
|
|
24
|
+
draftPending: { type: Boolean, required: false }
|
|
24
25
|
});
|
|
25
26
|
const emit = defineEmits(["restored"]);
|
|
26
27
|
const revisionsApi = useIntegrationsRevisions();
|
|
28
|
+
const t = useIntegrationsT();
|
|
27
29
|
const entries = ref([]);
|
|
28
30
|
const page = ref(1);
|
|
29
31
|
const lastPage = ref(1);
|
|
@@ -110,7 +112,7 @@ function buildPreview(blob) {
|
|
|
110
112
|
const nodePos = (id) => {
|
|
111
113
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
112
114
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
113
|
-
const ti = blob.triggers.findIndex((
|
|
115
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
114
116
|
if (ti >= 0) return triggerPos(id, ti);
|
|
115
117
|
return { x: 0, y: 0 };
|
|
116
118
|
};
|
|
@@ -149,16 +151,16 @@ function buildPreview(blob) {
|
|
|
149
151
|
position: { x: s.position.x, y: s.position.y },
|
|
150
152
|
zIndex: -100 + i,
|
|
151
153
|
draggable: false,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
// No callbacks: that absence is what makes the note read-only — it then
|
|
155
|
+
// draws no toolbar, no resize handle and does not open for typing (PO-284).
|
|
156
|
+
data: { sticky: s }
|
|
155
157
|
})),
|
|
156
|
-
...blob.triggers.map((
|
|
157
|
-
id:
|
|
158
|
+
...blob.triggers.map((t2, i) => ({
|
|
159
|
+
id: t2.id,
|
|
158
160
|
type: "trigger",
|
|
159
|
-
position: triggerPos(
|
|
161
|
+
position: triggerPos(t2.id, i),
|
|
160
162
|
draggable: false,
|
|
161
|
-
data:
|
|
163
|
+
data: t2
|
|
162
164
|
})),
|
|
163
165
|
...blob.nodes.map((n) => ({
|
|
164
166
|
id: n.id,
|
|
@@ -218,23 +220,24 @@ async function select(revision) {
|
|
|
218
220
|
buildPreview(detail.blob);
|
|
219
221
|
}
|
|
220
222
|
} catch (e) {
|
|
221
|
-
toast.error(e?.message ?? "
|
|
223
|
+
toast.error(e?.message ?? t("integration.history.toast.loadRevisionFailed"));
|
|
222
224
|
} finally {
|
|
223
225
|
detailLoading.value = false;
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
async function restoreRevision(rev) {
|
|
227
|
-
|
|
229
|
+
const question = props.draftPending ? t("integration.history.confirm.restoreWithDraft", { revision: rev.revision }) : t("integration.history.confirm.restore", { revision: rev.revision });
|
|
230
|
+
if (!confirm(question)) return;
|
|
228
231
|
restoring.value = rev.revision;
|
|
229
232
|
try {
|
|
230
233
|
await revisionsApi.restore(props.workflow.id, rev.revision);
|
|
231
|
-
toast.success(
|
|
234
|
+
toast.success(t("integration.history.toast.restored", { revision: rev.revision }));
|
|
232
235
|
detailCache.clear();
|
|
233
236
|
emit("restored");
|
|
234
237
|
await loadList(true);
|
|
235
238
|
void select(null);
|
|
236
239
|
} catch (e) {
|
|
237
|
-
toast.error(e?.message ?? "
|
|
240
|
+
toast.error(e?.message ?? t("integration.history.toast.restoreFailed"));
|
|
238
241
|
} finally {
|
|
239
242
|
restoring.value = null;
|
|
240
243
|
}
|
|
@@ -259,7 +262,7 @@ const nodeTypes = {
|
|
|
259
262
|
const edgeTypes = {
|
|
260
263
|
flow: markRaw(WorkflowEdgeView)
|
|
261
264
|
};
|
|
262
|
-
const previewTitle = computed(() => selected.value === null ?
|
|
265
|
+
const previewTitle = computed(() => selected.value === null ? t("integration.history.preview.title.current", { revision: props.workflow.revision }) : t("integration.history.preview.title.revision", { revision: selected.value }));
|
|
263
266
|
onMounted(() => {
|
|
264
267
|
void loadList(true);
|
|
265
268
|
void select(null);
|
|
@@ -311,9 +314,9 @@ onMounted(() => {
|
|
|
311
314
|
<!-- Revision list -->
|
|
312
315
|
<aside class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
313
316
|
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
314
|
-
<div class="text-sm font-semibold">
|
|
317
|
+
<div class="text-sm font-semibold">{{ t("integration.history.heading.title") }}</div>
|
|
315
318
|
<p class="text-sm text-muted-foreground">
|
|
316
|
-
|
|
319
|
+
{{ t("integration.history.heading.description") }}
|
|
317
320
|
</p>
|
|
318
321
|
</div>
|
|
319
322
|
|
|
@@ -327,17 +330,17 @@ onMounted(() => {
|
|
|
327
330
|
>
|
|
328
331
|
<div class="flex items-center justify-between gap-2">
|
|
329
332
|
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(workflow.updated_at) }}</span>
|
|
330
|
-
<Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">
|
|
333
|
+
<Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">{{ t("integration.history.badge.current") }}</Badge>
|
|
331
334
|
<Badge
|
|
332
335
|
variant="outline"
|
|
333
336
|
class="text-xs font-medium uppercase"
|
|
334
337
|
:class="workflow.active ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700' : 'border-black/[0.08] text-muted-foreground'"
|
|
335
|
-
:title="workflow.active ? '
|
|
338
|
+
:title="workflow.active ? t('integration.history.badge.activeHint') : t('integration.history.badge.pausedHint')"
|
|
336
339
|
>
|
|
337
|
-
{{ workflow.active ? "
|
|
340
|
+
{{ workflow.active ? t("integration.history.badge.active") : t("integration.history.badge.paused") }}
|
|
338
341
|
</Badge>
|
|
339
342
|
</div>
|
|
340
|
-
<div class="mt-0.5 text-sm text-muted-foreground">
|
|
343
|
+
<div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: workflow.revision }) }}</div>
|
|
341
344
|
</button>
|
|
342
345
|
|
|
343
346
|
<!-- Archived revisions -->
|
|
@@ -351,7 +354,7 @@ onMounted(() => {
|
|
|
351
354
|
<div class="flex items-center justify-between gap-2">
|
|
352
355
|
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(rev.archived_at) }}</span>
|
|
353
356
|
</div>
|
|
354
|
-
<div class="mt-0.5 text-sm text-muted-foreground">
|
|
357
|
+
<div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: rev.revision }) }}</div>
|
|
355
358
|
</button>
|
|
356
359
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 opacity-0 transition group-hover:opacity-100">
|
|
357
360
|
<Button
|
|
@@ -362,13 +365,13 @@ onMounted(() => {
|
|
|
362
365
|
@click.stop="restoreRevision(rev)"
|
|
363
366
|
>
|
|
364
367
|
<Loader2 v-if="restoring === rev.revision" :size="11" class="mr-1 animate-spin" />
|
|
365
|
-
|
|
368
|
+
{{ t("integration.history.action.restore") }}
|
|
366
369
|
</Button>
|
|
367
370
|
</div>
|
|
368
371
|
</div>
|
|
369
372
|
|
|
370
373
|
<p v-if="!entries.length && !listLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
371
|
-
|
|
374
|
+
{{ t("integration.history.state.empty") }}
|
|
372
375
|
</p>
|
|
373
376
|
<p v-if="revisionsApi.error.value" class="px-2 py-2 text-sm text-rose-600">
|
|
374
377
|
{{ revisionsApi.error.value }}
|
|
@@ -393,7 +396,7 @@ onMounted(() => {
|
|
|
393
396
|
class="w-full text-sm"
|
|
394
397
|
@click="loadMore"
|
|
395
398
|
>
|
|
396
|
-
|
|
399
|
+
{{ t("integration.history.action.loadMore") }}
|
|
397
400
|
</Button>
|
|
398
401
|
</div>
|
|
399
402
|
</aside>
|
|
@@ -3,6 +3,12 @@ import '@vue-flow/core/dist/theme-default.css';
|
|
|
3
3
|
import type { Workflow } from '../../types/integrations.js';
|
|
4
4
|
type __VLS_Props = {
|
|
5
5
|
workflow: Workflow;
|
|
6
|
+
/**
|
|
7
|
+
* The editor has unsaved work the page is holding. Nothing here shows it — the
|
|
8
|
+
* canvas behind this tab is not mounted — but a restore is what destroys it, so
|
|
9
|
+
* the question this panel asks has to name it (specs/workflow-save.md).
|
|
10
|
+
*/
|
|
11
|
+
draftPending?: boolean;
|
|
6
12
|
};
|
|
7
13
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
14
|
restored: () => any;
|
|
@@ -8,6 +8,7 @@ const model = defineModel({ type: [Object, null], ...{ default: null } });
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
triggerLabelledby: { type: String, required: false }
|
|
10
10
|
});
|
|
11
|
+
const t = useIntegrationsT();
|
|
11
12
|
const valueId = useId();
|
|
12
13
|
const triggerLabelledby = computed(
|
|
13
14
|
() => props.triggerLabelledby ? `${props.triggerLabelledby} ${valueId}` : void 0
|
|
@@ -26,10 +27,10 @@ function isImage(id) {
|
|
|
26
27
|
return (imageIdFromSrc(model.value.src) ?? model.value.src) === id;
|
|
27
28
|
}
|
|
28
29
|
const selectedLabel = computed(() => {
|
|
29
|
-
if (!model.value) return "
|
|
30
|
-
if (model.value.kind === "solar") return model.value.name ?? "
|
|
30
|
+
if (!model.value) return t("integration.workflows.iconPicker.label.default");
|
|
31
|
+
if (model.value.kind === "solar") return model.value.name ?? t("integration.workflows.iconPicker.label.iconFallback");
|
|
31
32
|
const id = imageIdFromSrc(model.value.src) ?? model.value.src;
|
|
32
|
-
return logos.value.find((l) => l.id === id)?.label ?? "
|
|
33
|
+
return logos.value.find((l) => l.id === id)?.label ?? t("integration.workflows.iconPicker.label.imageFallback");
|
|
33
34
|
});
|
|
34
35
|
</script>
|
|
35
36
|
|
|
@@ -57,8 +58,8 @@ const selectedLabel = computed(() => {
|
|
|
57
58
|
type="button"
|
|
58
59
|
class="flex size-8 items-center justify-center rounded-md border text-sm text-muted-foreground transition hover:bg-muted/50"
|
|
59
60
|
:class="!model ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-transparent'"
|
|
60
|
-
title="
|
|
61
|
-
aria-label="
|
|
61
|
+
:title="t('integration.workflows.iconPicker.action.none')"
|
|
62
|
+
:aria-label="t('integration.workflows.iconPicker.action.none')"
|
|
62
63
|
@click="pick(null)"
|
|
63
64
|
>
|
|
64
65
|
—
|
|
@@ -77,7 +78,7 @@ const selectedLabel = computed(() => {
|
|
|
77
78
|
</div>
|
|
78
79
|
|
|
79
80
|
<template v-if="logos.length">
|
|
80
|
-
<div class="text-mono text-muted-foreground">
|
|
81
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.iconPicker.section.integrations") }}</div>
|
|
81
82
|
<div class="flex flex-wrap gap-1">
|
|
82
83
|
<button
|
|
83
84
|
v-for="l in logos"
|
|
@@ -3,7 +3,8 @@ import { computed, inject, nextTick, ref, watch } from "vue";
|
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
|
-
import {
|
|
6
|
+
import { Copy, DangerTriangle as AlertTriangle, RefreshCircle as Loader2, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
|
+
import { NODE_CATEGORIES, findManifest, isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
7
8
|
import { isTerminalManifest } from "../../data/nodeInputData";
|
|
8
9
|
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
9
10
|
const props = defineProps({
|
|
@@ -13,17 +14,14 @@ const props = defineProps({
|
|
|
13
14
|
});
|
|
14
15
|
defineOptions({ inheritAttrs: false });
|
|
15
16
|
defineEmits(["updateNodeInternals"]);
|
|
17
|
+
const t = useIntegrationsT();
|
|
16
18
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
17
19
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
18
20
|
const title = computed(
|
|
19
21
|
() => resolveDisplayName(props.data.name) || resolveLocalized(manifest.value?.name) || props.data.nodeSlug
|
|
20
22
|
);
|
|
21
|
-
const inputRequired = computed(() => manifest.value?.input?.required !== false);
|
|
22
23
|
const invalidFields = computed(() => props.data.__invalidFields ?? []);
|
|
23
|
-
const invalidLabel = computed(() => {
|
|
24
|
-
const n = invalidFields.value.length;
|
|
25
|
-
return `Config incomplete \u2014 ${n} setting${n === 1 ? "" : "s"} still need${n === 1 ? "s" : ""} fixing: ${invalidFields.value.join(", ")}`;
|
|
26
|
-
});
|
|
24
|
+
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, { fields: invalidFields.value.join(", ") }));
|
|
27
25
|
const notes = computed(() => (props.data.notes ?? "").trim());
|
|
28
26
|
const noteOpen = ref(false);
|
|
29
27
|
const noteReady = ref(false);
|
|
@@ -32,11 +30,15 @@ const notePlacement = ref(Position.Bottom);
|
|
|
32
30
|
const noteMaxHeight = ref(0);
|
|
33
31
|
let noteTimer = null;
|
|
34
32
|
const notePanelId = computed(() => `node-note-${props.id}`);
|
|
33
|
+
function handleTitle(p) {
|
|
34
|
+
return p.hint ? p.label + " \u2014 " + p.hint : p.label;
|
|
35
|
+
}
|
|
35
36
|
function portsFromOutputs(list) {
|
|
36
37
|
const ports = [];
|
|
37
38
|
for (const o of list) {
|
|
38
39
|
if (o.name) {
|
|
39
|
-
|
|
40
|
+
const hint = resolveLocalized(o.description);
|
|
41
|
+
ports.push({ name: o.name, kind: o.kind, label: resolveLocalized(o.label) || o.name, ...hint ? { hint } : {} });
|
|
40
42
|
} else if (o.sourceFromConfig) {
|
|
41
43
|
const raw = props.data.config?.[o.sourceFromConfig];
|
|
42
44
|
const values = Array.isArray(raw) ? raw : [];
|
|
@@ -62,12 +64,12 @@ const outputs = computed(() => {
|
|
|
62
64
|
const resolved = props.data.__resolvedOutputs;
|
|
63
65
|
if (Array.isArray(resolved) && resolved.length) {
|
|
64
66
|
const ports2 = portsFromOutputs(resolved);
|
|
65
|
-
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: "
|
|
67
|
+
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
66
68
|
return ports2;
|
|
67
69
|
}
|
|
68
|
-
if (!m) return [{ name: "default", kind: "default", label: "
|
|
70
|
+
if (!m) return [{ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") }];
|
|
69
71
|
const ports = portsFromOutputs(m.outputs ?? []);
|
|
70
|
-
if (!ports.length) ports.push({ name: "default", kind: "default", label: "
|
|
72
|
+
if (!ports.length) ports.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
71
73
|
return ports;
|
|
72
74
|
});
|
|
73
75
|
function portSemantic(p) {
|
|
@@ -161,6 +163,8 @@ function releaseNote() {
|
|
|
161
163
|
}, 250);
|
|
162
164
|
}
|
|
163
165
|
const nodeActions = inject("workflow-node-actions", null);
|
|
166
|
+
const unbuildableVersion = computed(() => nodeActions !== null && !isVersionExecutable(props.data.nodeSlug, props.data.nodeVersion));
|
|
167
|
+
const unbuildableLabel = computed(() => t("integration.canvas.node.badge.noBuiltCode", { version: props.data.nodeVersion }));
|
|
164
168
|
const canConfigure = computed(() => typeof nodeActions?.configure === "function");
|
|
165
169
|
function configure() {
|
|
166
170
|
nodeActions?.configure?.(props.id);
|
|
@@ -183,21 +187,21 @@ function remove() {
|
|
|
183
187
|
<button
|
|
184
188
|
type="button"
|
|
185
189
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-foreground/80 hover:bg-muted/50"
|
|
186
|
-
title="
|
|
187
|
-
aria-label="
|
|
190
|
+
:title="t('integration.canvas.node.action.duplicate')"
|
|
191
|
+
:aria-label="t('integration.canvas.node.action.duplicateAria')"
|
|
188
192
|
@click="duplicate"
|
|
189
193
|
>
|
|
190
|
-
<
|
|
194
|
+
<Copy :size="11" />
|
|
191
195
|
</button>
|
|
192
196
|
<div class="h-4 w-px bg-black/10" />
|
|
193
197
|
<button
|
|
194
198
|
type="button"
|
|
195
199
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-rose-600 hover:bg-rose-50"
|
|
196
|
-
title="
|
|
197
|
-
aria-label="
|
|
200
|
+
:title="t('integration.common.action.delete')"
|
|
201
|
+
:aria-label="t('integration.canvas.node.action.deleteAria')"
|
|
198
202
|
@click="remove"
|
|
199
203
|
>
|
|
200
|
-
<
|
|
204
|
+
<TrashBin :size="11" />
|
|
201
205
|
</button>
|
|
202
206
|
</NodeToolbar>
|
|
203
207
|
|
|
@@ -219,7 +223,7 @@ function remove() {
|
|
|
219
223
|
>
|
|
220
224
|
<div ref="notePanel" role="tooltip">
|
|
221
225
|
<div class="mb-1 text-[8px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/60">
|
|
222
|
-
|
|
226
|
+
{{ t("integration.canvas.node.note.heading") }}
|
|
223
227
|
</div>
|
|
224
228
|
<p class="whitespace-pre-wrap break-words text-[11px] leading-snug text-foreground/80">{{ notes }}</p>
|
|
225
229
|
</div>
|
|
@@ -227,17 +231,15 @@ function remove() {
|
|
|
227
231
|
|
|
228
232
|
<!-- Input: ONE logical input, attachable from the left or the top — both
|
|
229
233
|
dots feed the same `__in` port. Clearly visible, ring + shadow. -->
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
/>
|
|
240
|
-
</template>
|
|
234
|
+
<Handle
|
|
235
|
+
v-for="side in IN_SIDES"
|
|
236
|
+
:id="`__in__${side}`"
|
|
237
|
+
:key="`in-${side}`"
|
|
238
|
+
type="target"
|
|
239
|
+
:position="sideToPosition(side)"
|
|
240
|
+
:title="t('integration.canvas.node.handle.input')"
|
|
241
|
+
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-slate-700"
|
|
242
|
+
/>
|
|
241
243
|
|
|
242
244
|
<!-- Output handles: right + bottom, distributed per side. Color encodes the
|
|
243
245
|
port semantics (true/false/error/branch/default), matching the edges. -->
|
|
@@ -248,7 +250,7 @@ function remove() {
|
|
|
248
250
|
:key="`${p.name}-${side}`"
|
|
249
251
|
type="source"
|
|
250
252
|
:position="sideToPosition(side)"
|
|
251
|
-
:title="p
|
|
253
|
+
:title="handleTitle(p)"
|
|
252
254
|
:style="distributedStyle(side, i, outputs.length)"
|
|
253
255
|
class="!size-3 !rounded-full !border-2 !border-white !shadow-sm transition-all hover:!size-3.5"
|
|
254
256
|
:class="portTone(p).handle"
|
|
@@ -266,19 +268,43 @@ function remove() {
|
|
|
266
268
|
showPortLabels ? 'pb-3.5' : ''
|
|
267
269
|
]"
|
|
268
270
|
>
|
|
269
|
-
<!--
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
<!-- The card's marks, in one row at the corner the input handles leave free.
|
|
272
|
+
A row rather than two absolutely-placed badges: the amber one's width
|
|
273
|
+
follows its count, so any fixed offset for a second badge overlaps it as
|
|
274
|
+
soon as the count reaches two digits. Hoverable on purpose — a symbol or
|
|
275
|
+
a bare count at a corner is a riddle, so each carries its own title;
|
|
276
|
+
`nodrag` keeps that hover from starting a card drag. -->
|
|
273
277
|
<span
|
|
274
|
-
v-if="invalidFields.length"
|
|
275
|
-
|
|
276
|
-
:aria-label="invalidLabel"
|
|
277
|
-
:title="invalidLabel"
|
|
278
|
-
class="nodrag absolute -left-2 -top-2 z-10 flex cursor-help items-center gap-0.5 rounded-full bg-amber-500 px-1.5 py-0.5 text-[9px] font-bold leading-none text-white shadow-sm"
|
|
278
|
+
v-if="invalidFields.length || unbuildableVersion"
|
|
279
|
+
class="nodrag absolute -left-2 -top-2 z-10 flex items-center gap-1"
|
|
279
280
|
>
|
|
280
|
-
<
|
|
281
|
-
|
|
281
|
+
<span
|
|
282
|
+
v-if="invalidFields.length"
|
|
283
|
+
role="img"
|
|
284
|
+
:aria-label="invalidLabel"
|
|
285
|
+
:title="invalidLabel"
|
|
286
|
+
class="flex cursor-help items-center gap-0.5 rounded-full bg-amber-500 px-1.5 py-0.5 text-[9px] font-bold leading-none text-white shadow-sm"
|
|
287
|
+
>
|
|
288
|
+
<!-- `weight="Bold"` for the same reason the Lucide draw carried
|
|
289
|
+
`stroke-width="2.5"`: 9px of white on amber. Solar's default
|
|
290
|
+
BoldDuotone washes out at that size against the pill. -->
|
|
291
|
+
<AlertTriangle :size="9" weight="Bold" />
|
|
292
|
+
{{ invalidFields.length }}
|
|
293
|
+
</span>
|
|
294
|
+
<!-- The pinned version cannot be run. Rose rather than amber, and no
|
|
295
|
+
count: amber on this card means unfinished work of the user's own —
|
|
296
|
+
settings still to fill in — while this is the platform missing a piece,
|
|
297
|
+
which the user cannot fix by typing. Hence the stronger colour despite
|
|
298
|
+
being the one that does NOT block activation. -->
|
|
299
|
+
<span
|
|
300
|
+
v-if="unbuildableVersion"
|
|
301
|
+
role="img"
|
|
302
|
+
:aria-label="unbuildableLabel"
|
|
303
|
+
:title="unbuildableLabel"
|
|
304
|
+
class="flex cursor-help items-center rounded-full bg-rose-600 px-1.5 py-0.5 text-[9px] font-bold leading-none text-white shadow-sm"
|
|
305
|
+
>
|
|
306
|
+
<AlertTriangle :size="9" weight="Bold" />
|
|
307
|
+
</span>
|
|
282
308
|
</span>
|
|
283
309
|
<!-- Always-visible edit affordance: single click opens the config lightbox.
|
|
284
310
|
(Double-click on the whole card works too.) Hidden in read-only hosts
|
|
@@ -288,8 +314,8 @@ function remove() {
|
|
|
288
314
|
v-if="canConfigure"
|
|
289
315
|
type="button"
|
|
290
316
|
class="nodrag absolute right-1.5 top-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:bg-muted hover:text-foreground"
|
|
291
|
-
title="
|
|
292
|
-
aria-label="
|
|
317
|
+
:title="t('integration.canvas.node.action.configure')"
|
|
318
|
+
:aria-label="t('integration.canvas.node.action.configure')"
|
|
293
319
|
@click.stop="configure"
|
|
294
320
|
@mousedown.stop
|
|
295
321
|
@dblclick.stop
|
|
@@ -305,7 +331,7 @@ function remove() {
|
|
|
305
331
|
type="button"
|
|
306
332
|
class="nodrag absolute right-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/45 transition-colors hover:bg-muted hover:text-foreground"
|
|
307
333
|
:class="showPortLabels ? 'bottom-4' : 'bottom-1.5'"
|
|
308
|
-
aria-label="
|
|
334
|
+
:aria-label="t('integration.canvas.node.note.heading')"
|
|
309
335
|
:aria-describedby="notePanelId"
|
|
310
336
|
@mouseenter="openNote"
|
|
311
337
|
@mouseleave="releaseNote"
|
|
@@ -331,8 +357,12 @@ function remove() {
|
|
|
331
357
|
v-if="resolvePending"
|
|
332
358
|
class="pointer-events-none absolute bottom-1 left-1/2 flex -translate-x-1/2 select-none items-center gap-1 whitespace-nowrap text-[9px] font-semibold uppercase tracking-wide text-muted-foreground"
|
|
333
359
|
>
|
|
334
|
-
|
|
335
|
-
|
|
360
|
+
<!-- `weight="Bold"` for the badge's reason one size down: the default
|
|
361
|
+
BoldDuotone draws a filled disc across the whole viewBox, and a disc
|
|
362
|
+
turning looks like a disc standing still. Bold knocks the arrows out
|
|
363
|
+
of the ring, so 9px of it still reads as spinning. -->
|
|
364
|
+
<Loader2 :size="9" weight="Bold" class="animate-spin" />
|
|
365
|
+
{{ t("integration.canvas.node.port.resolving") }}
|
|
336
366
|
</span>
|
|
337
367
|
<template v-else>
|
|
338
368
|
<span
|
|
@@ -364,7 +394,7 @@ function remove() {
|
|
|
364
394
|
<div v-if="data.nodeVersion" class="mt-0.5 flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
365
395
|
<span
|
|
366
396
|
class="shrink-0 text-[10px] font-medium leading-none tabular-nums text-muted-foreground/55"
|
|
367
|
-
:title="
|
|
397
|
+
:title="t('integration.canvas.node.meta.versionTitle', { version: data.nodeVersion })"
|
|
368
398
|
>v{{ data.nodeVersion }}</span>
|
|
369
399
|
</div>
|
|
370
400
|
</div>
|