@revenexx/studio-integrations 0.5.1 → 0.7.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 +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -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 +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -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 +7 -5
- 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 +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- 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 +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -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 +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -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 +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -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 +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -20,9 +20,11 @@ import {
|
|
|
20
20
|
SelectValue
|
|
21
21
|
} from "@revenexx/studio";
|
|
22
22
|
import TemplateFlowPreview from "../../../components/Integrations/TemplateFlowPreview.vue";
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
23
|
+
import NodeMark from "../../../components/Integrations/NodeMark.vue";
|
|
24
|
+
import { templateCategoryLabel, templatePackageLabel } from "../../../data/integrationTemplates";
|
|
25
|
+
import { describeCron } from "../../../utils/schedule";
|
|
25
26
|
const SELECT_LATER = "__later__";
|
|
27
|
+
const t = useIntegrationsT();
|
|
26
28
|
const route = useRoute();
|
|
27
29
|
const router = useRouter();
|
|
28
30
|
const { get, load, loaded, fetchDetail, requirements, instantiate } = useIntegrationsTemplates();
|
|
@@ -31,7 +33,7 @@ const { keys: secretKeys, load: loadSecrets } = useIntegrationsSecrets();
|
|
|
31
33
|
const { ofType, load: loadCredentials } = useIntegrationsCredentials();
|
|
32
34
|
const slug = computed(() => String(route.params.slug));
|
|
33
35
|
const tpl = computed(() => get(slug.value));
|
|
34
|
-
useHead({ title: () =>
|
|
36
|
+
useHead({ title: () => t("integration.templates.detail.head.title", { name: tpl.value?.name ?? t("integration.templates.detail.head.fallbackName") }) });
|
|
35
37
|
const reqs = ref(null);
|
|
36
38
|
onMounted(async () => {
|
|
37
39
|
await load();
|
|
@@ -49,7 +51,7 @@ const steps = computed(() => {
|
|
|
49
51
|
if (!blob?.nodes.length) return [];
|
|
50
52
|
const nodeById = new Map(blob.nodes.map((n) => [n.id, n]));
|
|
51
53
|
const happyEdges = (blob.edges ?? []).filter((e) => e.from.output !== "error");
|
|
52
|
-
const triggerHandles = new Set((blob.triggers ?? []).map((
|
|
54
|
+
const triggerHandles = new Set((blob.triggers ?? []).map((t2) => t2.id));
|
|
53
55
|
const incoming = /* @__PURE__ */ new Map();
|
|
54
56
|
for (const e of happyEdges) {
|
|
55
57
|
if (!triggerHandles.has(e.from.nodeId)) incoming.set(e.to.nodeId, (incoming.get(e.to.nodeId) ?? 0) + 1);
|
|
@@ -89,23 +91,28 @@ function firstSentence(text) {
|
|
|
89
91
|
return idx === -1 ? text : text.slice(0, idx + 1);
|
|
90
92
|
}
|
|
91
93
|
const TRIGGER_ICON = { schedule: Clock, webhook: Webhook, manual: Play, event: Zap };
|
|
92
|
-
const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((
|
|
93
|
-
let label =
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((trig) => {
|
|
95
|
+
let label = trig.type.charAt(0).toUpperCase() + trig.type.slice(1);
|
|
96
|
+
if (trig.type === "schedule" && trig.config?.cron) {
|
|
97
|
+
const when = describeCron(String(trig.config.cron)) ?? String(trig.config.cron);
|
|
98
|
+
label = `${t("integration.templates.detail.trigger.schedule")} \xB7 ${when}${trig.config.timezone ? ` (${trig.config.timezone})` : ""}`;
|
|
99
|
+
}
|
|
100
|
+
if (trig.type === "webhook" && trig.config?.path) label = `${t("integration.templates.trigger.webhook")} \xB7 ${trig.config.path}`;
|
|
101
|
+
if (trig.type === "manual") label = t("integration.templates.trigger.manual");
|
|
102
|
+
return { key: trig.id, icon: TRIGGER_ICON[trig.type] ?? Zap, label };
|
|
98
103
|
}));
|
|
99
104
|
const variables = computed(() => Object.entries(tpl.value?.blob.variables ?? {}).map(([key, v]) => ({
|
|
100
105
|
key,
|
|
101
106
|
default: String(v?.default ?? ""),
|
|
102
107
|
description: resolveLocalized(v?.description ?? "")
|
|
103
108
|
})));
|
|
104
|
-
const variableColumns = [
|
|
105
|
-
{ key: "key", label: "
|
|
106
|
-
{ key: "description", label: "
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
const variableColumns = computed(() => [
|
|
110
|
+
{ key: "key", label: t("integration.templates.detail.variables.column.key"), width: "minmax(0,180px)", sortable: true },
|
|
111
|
+
{ key: "description", label: t("integration.templates.detail.variables.column.description") },
|
|
112
|
+
// Stored as `default` in the blob, but it *is* the value the workflow runs
|
|
113
|
+
// with — see `specs/workflow-variables.md`.
|
|
114
|
+
{ key: "default", label: t("integration.templates.detail.variables.column.value"), width: "minmax(0,200px)" }
|
|
115
|
+
]);
|
|
109
116
|
const credentialReqs = computed(() => reqs.value?.credentialTypes ?? []);
|
|
110
117
|
const secretReqs = computed(() => reqs.value?.secrets ?? []);
|
|
111
118
|
const secretChecks = computed(() => {
|
|
@@ -113,7 +120,7 @@ const secretChecks = computed(() => {
|
|
|
113
120
|
if (fromApi.length) {
|
|
114
121
|
return fromApi.map((s) => ({
|
|
115
122
|
key: s.key,
|
|
116
|
-
label: s.key ?? "
|
|
123
|
+
label: s.key ?? t("integration.templates.detail.secrets.chosenAtInstall"),
|
|
117
124
|
present: s.exists || (s.key ? secretKeys.value.includes(s.key) : false)
|
|
118
125
|
}));
|
|
119
126
|
}
|
|
@@ -126,17 +133,8 @@ const secretChecks = computed(() => {
|
|
|
126
133
|
const missingSecrets = computed(() => secretChecks.value.filter((s) => !s.present).length);
|
|
127
134
|
const missingCredentials = computed(() => credentialReqs.value.filter((c) => !ofType(c.slug).length).length);
|
|
128
135
|
const setupReady = computed(() => missingSecrets.value === 0 && missingCredentials.value === 0);
|
|
129
|
-
const packageLabel = computed(() =>
|
|
130
|
-
|
|
131
|
-
if (!pkg) return null;
|
|
132
|
-
const tail = (pkg.name.split("/").pop() ?? pkg.name).replace(/^integrations-nodes-/, "");
|
|
133
|
-
if (tail === "core") return null;
|
|
134
|
-
return pkg.label ?? tail.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
|
|
135
|
-
});
|
|
136
|
-
const categoryLabel = computed(() => {
|
|
137
|
-
const cat = tpl.value?.category;
|
|
138
|
-
return TEMPLATE_CATEGORIES.find((c) => c.key === cat)?.label ?? (cat ? cat.charAt(0).toUpperCase() + cat.slice(1) : "");
|
|
139
|
-
});
|
|
136
|
+
const packageLabel = computed(() => tpl.value ? templatePackageLabel(tpl.value) : null);
|
|
137
|
+
const categoryLabel = computed(() => templateCategoryLabel(tpl.value?.category ?? ""));
|
|
140
138
|
const installOpen = ref(false);
|
|
141
139
|
const installing = ref(false);
|
|
142
140
|
const installError = ref(null);
|
|
@@ -167,12 +165,20 @@ async function confirmInstall() {
|
|
|
167
165
|
});
|
|
168
166
|
if (id) await router.push(`/integrations/workflows/${id}`);
|
|
169
167
|
} catch (e) {
|
|
170
|
-
installError.value = e.message || "
|
|
168
|
+
installError.value = e.message || t("integration.templates.detail.install.error.fallback");
|
|
171
169
|
} finally {
|
|
172
170
|
installing.value = false;
|
|
173
171
|
}
|
|
174
172
|
}
|
|
175
|
-
const
|
|
173
|
+
const PUBLISHER_KIND_LABEL_KEY = {
|
|
174
|
+
revenexx: "integration.templates.detail.publisher.kind.revenexx",
|
|
175
|
+
partner: "integration.templates.detail.publisher.kind.partner",
|
|
176
|
+
community: "integration.templates.detail.publisher.kind.community"
|
|
177
|
+
};
|
|
178
|
+
function publisherKindLabel(kind) {
|
|
179
|
+
const key = PUBLISHER_KIND_LABEL_KEY[kind];
|
|
180
|
+
return key ? t(key) : kind;
|
|
181
|
+
}
|
|
176
182
|
function publisherBadgeClass(kind) {
|
|
177
183
|
return kind === "revenexx" ? "bg-violet-500/10 text-violet-700 border-violet-500/20" : kind === "partner" ? "bg-amber-500/10 text-amber-700 border-amber-500/20" : "bg-slate-500/10 text-slate-700 border-slate-500/20";
|
|
178
184
|
}
|
|
@@ -187,14 +193,14 @@ function levelDot(level) {
|
|
|
187
193
|
<PageHeader :title="tpl.name">
|
|
188
194
|
<template #title>
|
|
189
195
|
<span class="inline-flex items-center gap-1.5">
|
|
190
|
-
<NuxtLink to="/integrations/templates" class="font-normal text-muted-foreground/80 hover:text-primary transition-colors">
|
|
196
|
+
<NuxtLink to="/integrations/templates" class="font-normal text-muted-foreground/80 hover:text-primary transition-colors">{{ t("integration.templates.detail.breadcrumb.templates") }}</NuxtLink>
|
|
191
197
|
<Chevron :size="15" weight="Outline" class="text-muted-foreground/50" />
|
|
192
198
|
<span class="truncate">{{ tpl.name }}</span>
|
|
193
199
|
</span>
|
|
194
200
|
</template>
|
|
195
201
|
<template #actions>
|
|
196
202
|
<Button class="h-7 text-sm" @click="openInstall">
|
|
197
|
-
|
|
203
|
+
{{ t("integration.templates.detail.action.useTemplate") }}
|
|
198
204
|
</Button>
|
|
199
205
|
</template>
|
|
200
206
|
</PageHeader>
|
|
@@ -207,7 +213,7 @@ function levelDot(level) {
|
|
|
207
213
|
<Loader2 :size="18" class="animate-spin text-muted-foreground/50" />
|
|
208
214
|
</div>
|
|
209
215
|
<div class="absolute left-3 top-3 flex items-center gap-1.5">
|
|
210
|
-
<span class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
216
|
+
<span v-if="categoryLabel" class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
211
217
|
{{ categoryLabel }}
|
|
212
218
|
</span>
|
|
213
219
|
<span class="inline-flex items-center gap-1 rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
@@ -233,8 +239,13 @@ function levelDot(level) {
|
|
|
233
239
|
<div class="grid gap-4 lg:grid-cols-3">
|
|
234
240
|
<div class="lg:col-span-2 space-y-4">
|
|
235
241
|
<!-- About: the registry description, rendered -->
|
|
236
|
-
<div
|
|
237
|
-
|
|
242
|
+
<div
|
|
243
|
+
v-if="descriptionHtml"
|
|
244
|
+
role="region"
|
|
245
|
+
aria-labelledby="tpl-about-heading"
|
|
246
|
+
class="rounded-lg border border-black/[0.06] bg-white p-4"
|
|
247
|
+
>
|
|
248
|
+
<h3 id="tpl-about-heading" class="text-sm font-semibold mb-2">{{ t("integration.templates.detail.about.title") }}</h3>
|
|
238
249
|
<div
|
|
239
250
|
class="text-sm leading-relaxed text-foreground/90 space-y-2 [&_h1]:text-sm [&_h1]:font-semibold [&_h2]:text-sm [&_h2]:font-semibold [&_h3]:text-sm [&_h3]:font-semibold [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:list-disc [&_ul]:pl-5 [&_li]:mt-1 [&_code]:rounded [&_code]:bg-slate-100 [&_code]:px-1 [&_code]:py-px [&_code]:font-mono [&_code]:text-[13px] [&_a]:text-primary [&_a]:underline [&_strong]:font-semibold"
|
|
240
251
|
v-html="descriptionHtml"
|
|
@@ -242,8 +253,12 @@ function levelDot(level) {
|
|
|
242
253
|
</div>
|
|
243
254
|
|
|
244
255
|
<!-- Step by step, humanized from the actual nodes -->
|
|
245
|
-
<div
|
|
246
|
-
|
|
256
|
+
<div
|
|
257
|
+
role="region"
|
|
258
|
+
aria-labelledby="tpl-steps-heading"
|
|
259
|
+
class="rounded-lg border border-black/[0.06] bg-white p-4"
|
|
260
|
+
>
|
|
261
|
+
<h3 id="tpl-steps-heading" class="text-sm font-semibold mb-3">{{ t("integration.templates.detail.steps.title") }}</h3>
|
|
247
262
|
<ol v-if="steps.length" class="space-y-2.5">
|
|
248
263
|
<li
|
|
249
264
|
v-for="(step, i) in steps"
|
|
@@ -254,7 +269,7 @@ function levelDot(level) {
|
|
|
254
269
|
{{ i + 1 }}
|
|
255
270
|
</span>
|
|
256
271
|
<span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-white ring-1 ring-black/[0.08]">
|
|
257
|
-
<
|
|
272
|
+
<NodeMark :manifest="step.manifest" :size="15" :badge="false" />
|
|
258
273
|
</span>
|
|
259
274
|
<span class="min-w-0 pt-0.5">
|
|
260
275
|
<span class="block text-sm font-medium text-foreground">{{ step.label }}</span>
|
|
@@ -263,14 +278,23 @@ function levelDot(level) {
|
|
|
263
278
|
</li>
|
|
264
279
|
</ol>
|
|
265
280
|
<p v-else class="text-sm text-muted-foreground italic">
|
|
266
|
-
|
|
281
|
+
{{ t("integration.templates.detail.steps.empty") }}
|
|
267
282
|
</p>
|
|
268
283
|
</div>
|
|
269
284
|
|
|
270
285
|
<!-- Variables the operator can tune after install -->
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
286
|
+
<!-- Named by its own heading rather than by a copy of it: two strings
|
|
287
|
+
saying the same thing drift, and the test only holds one of them.
|
|
288
|
+
`role` stays because this is a div — a bare <section> would be a
|
|
289
|
+
region on its own, this one has to say so. -->
|
|
290
|
+
<div
|
|
291
|
+
v-if="variables.length"
|
|
292
|
+
role="region"
|
|
293
|
+
aria-labelledby="tpl-variables-heading"
|
|
294
|
+
class="rounded-lg border border-black/[0.06] bg-white p-4"
|
|
295
|
+
>
|
|
296
|
+
<h3 id="tpl-variables-heading" class="text-sm font-semibold mb-1">{{ t("integration.templates.detail.variables.title") }}</h3>
|
|
297
|
+
<p class="text-sm text-muted-foreground mb-3">{{ t("integration.templates.detail.variables.description") }}</p>
|
|
274
298
|
<DataTable
|
|
275
299
|
:data="variables"
|
|
276
300
|
:columns="variableColumns"
|
|
@@ -296,51 +320,55 @@ function levelDot(level) {
|
|
|
296
320
|
<!-- Setup rail -->
|
|
297
321
|
<div class="space-y-3">
|
|
298
322
|
<!-- Readiness + CTA -->
|
|
299
|
-
|
|
323
|
+
<!-- `aria-label` deliberately, unlike the two panels below: this one
|
|
324
|
+
shows no heading of its own, so there is no visible string to
|
|
325
|
+
point `aria-labelledby` at. Copy this pattern only for a panel
|
|
326
|
+
that genuinely has none. -->
|
|
327
|
+
<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">
|
|
300
328
|
<div v-if="setupReady" class="flex items-center gap-1.5 text-sm text-emerald-700">
|
|
301
329
|
<CheckCircle2 :size="14" />
|
|
302
|
-
|
|
330
|
+
{{ t("integration.templates.detail.setup.ready") }}
|
|
303
331
|
</div>
|
|
304
332
|
<div v-else class="text-sm text-amber-700 space-y-0.5">
|
|
305
333
|
<div v-if="missingCredentials" class="flex items-center gap-1.5">
|
|
306
334
|
<Key :size="12" />
|
|
307
|
-
{{ missingCredentials
|
|
335
|
+
{{ t("integration.templates.detail.setup.missingCredentials", missingCredentials) }}
|
|
308
336
|
</div>
|
|
309
337
|
<div v-if="missingSecrets" class="flex items-center gap-1.5">
|
|
310
338
|
<ShieldAlert :size="12" />
|
|
311
|
-
{{ missingSecrets
|
|
339
|
+
{{ t("integration.templates.detail.setup.missingSecrets", missingSecrets) }}
|
|
312
340
|
</div>
|
|
313
341
|
</div>
|
|
314
342
|
<Button class="w-full h-8 text-sm" @click="openInstall">
|
|
315
|
-
|
|
343
|
+
{{ t("integration.templates.detail.action.useTemplate") }}
|
|
316
344
|
</Button>
|
|
317
345
|
<p class="text-xs text-muted-foreground">
|
|
318
|
-
|
|
346
|
+
{{ t("integration.templates.detail.setup.hint") }}
|
|
319
347
|
</p>
|
|
320
348
|
</div>
|
|
321
349
|
|
|
322
350
|
<div class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
323
|
-
<div class="text-mono text-muted-foreground">
|
|
351
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.templates.detail.publisher.title") }}</div>
|
|
324
352
|
<div class="flex items-center gap-2">
|
|
325
353
|
<span class="flex size-6 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
|
326
354
|
<User :size="12" />
|
|
327
355
|
</span>
|
|
328
356
|
<div class="text-sm font-medium truncate">{{ tpl.publisher.name }}</div>
|
|
329
357
|
<Badge variant="outline" :class="['text-sm border', publisherBadgeClass(tpl.publisher.kind)]">
|
|
330
|
-
{{
|
|
358
|
+
{{ publisherKindLabel(tpl.publisher.kind) }}
|
|
331
359
|
</Badge>
|
|
332
360
|
</div>
|
|
333
361
|
<div v-if="packageLabel" class="border-t border-black/[0.06] pt-2 text-sm">
|
|
334
|
-
<div class="text-muted-foreground">
|
|
362
|
+
<div class="text-muted-foreground">{{ t("integration.templates.detail.publisher.shipsWith") }}</div>
|
|
335
363
|
<div class="font-medium">{{ packageLabel }}</div>
|
|
336
364
|
</div>
|
|
337
365
|
<div class="border-t border-black/[0.06] pt-2 grid grid-cols-2 gap-2 text-sm">
|
|
338
366
|
<div>
|
|
339
|
-
<div class="text-muted-foreground">
|
|
367
|
+
<div class="text-muted-foreground">{{ t("integration.templates.detail.rail.stepsLabel") }}</div>
|
|
340
368
|
<div class="font-medium tabular-nums">{{ steps.length || tpl.nodeCount }}</div>
|
|
341
369
|
</div>
|
|
342
370
|
<div>
|
|
343
|
-
<div class="text-muted-foreground">
|
|
371
|
+
<div class="text-muted-foreground">{{ t("integration.templates.detail.rail.levelLabel") }}</div>
|
|
344
372
|
<span class="mt-0.5 inline-flex items-center gap-1 rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
345
373
|
<span class="size-1.5 rounded-full" :class="levelDot(tpl.level)" />
|
|
346
374
|
{{ tpl.level }}
|
|
@@ -349,9 +377,12 @@ function levelDot(level) {
|
|
|
349
377
|
</div>
|
|
350
378
|
</div>
|
|
351
379
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
380
|
+
<!-- Named by the label it shows, like the variables panel above: an
|
|
381
|
+
invented name is a second string saying the same thing, and it is
|
|
382
|
+
what a screen reader hears instead of what is written. -->
|
|
383
|
+
<div v-if="credentialReqs.length" role="group" aria-labelledby="tpl-credentials-label" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
384
|
+
<div id="tpl-credentials-label" class="flex items-center gap-1.5 text-mono text-muted-foreground">
|
|
385
|
+
<Key :size="11" /> {{ t("integration.templates.detail.credentials.title") }}
|
|
355
386
|
</div>
|
|
356
387
|
<ul class="space-y-1">
|
|
357
388
|
<li
|
|
@@ -368,22 +399,22 @@ function levelDot(level) {
|
|
|
368
399
|
class="shrink-0 bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-sm"
|
|
369
400
|
>
|
|
370
401
|
<CheckCircle2 :size="10" class="mr-0.5" />
|
|
371
|
-
{{ ofType(c.slug).length }}
|
|
402
|
+
{{ t("integration.templates.detail.credentials.readyCount", ofType(c.slug).length) }}
|
|
372
403
|
</Badge>
|
|
373
404
|
<Badge v-else variant="outline" class="shrink-0 bg-amber-500/10 border-amber-500/20 text-amber-700 text-sm">
|
|
374
|
-
|
|
405
|
+
{{ t("integration.templates.detail.credentials.connectFirst") }}
|
|
375
406
|
</Badge>
|
|
376
407
|
</li>
|
|
377
408
|
</ul>
|
|
378
|
-
<NuxtLink to="/integrations/credentials" class="block text-sm text-primary hover:underline">
|
|
409
|
+
<NuxtLink to="/integrations/credentials" class="block text-sm text-primary hover:underline">{{ t("integration.templates.detail.credentials.manageLink") }} →</NuxtLink>
|
|
379
410
|
</div>
|
|
380
411
|
|
|
381
|
-
<div v-if="secretChecks.length" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
412
|
+
<div v-if="secretChecks.length" role="group" aria-labelledby="tpl-secrets-label" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
382
413
|
<div class="flex items-center gap-2">
|
|
383
|
-
<span class="text-mono text-muted-foreground">
|
|
414
|
+
<span id="tpl-secrets-label" class="text-mono text-muted-foreground">{{ t("integration.templates.detail.secrets.title") }}</span>
|
|
384
415
|
<span v-if="missingSecrets > 0" class="flex items-center gap-1 text-sm text-amber-700">
|
|
385
416
|
<ShieldAlert :size="11" />
|
|
386
|
-
{{ missingSecrets }}
|
|
417
|
+
{{ t("integration.templates.detail.secrets.missingBadge", missingSecrets) }}
|
|
387
418
|
</span>
|
|
388
419
|
</div>
|
|
389
420
|
<ul class="space-y-1">
|
|
@@ -399,20 +430,20 @@ function levelDot(level) {
|
|
|
399
430
|
class="bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-sm"
|
|
400
431
|
>
|
|
401
432
|
<CheckCircle2 :size="10" class="mr-0.5" />
|
|
402
|
-
|
|
433
|
+
{{ t("integration.templates.detail.secrets.set") }}
|
|
403
434
|
</Badge>
|
|
404
435
|
<Badge v-else variant="outline" class="bg-amber-500/10 border-amber-500/20 text-amber-700 text-sm">
|
|
405
|
-
|
|
436
|
+
{{ t("integration.templates.detail.secrets.missing") }}
|
|
406
437
|
</Badge>
|
|
407
438
|
</li>
|
|
408
439
|
</ul>
|
|
409
440
|
</div>
|
|
410
441
|
|
|
411
442
|
<div v-if="tpl.industries.length || tpl.vendors.length" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
412
|
-
<div class="text-mono text-muted-foreground">
|
|
443
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.templates.detail.fit.title") }}</div>
|
|
413
444
|
<div class="flex flex-wrap gap-1">
|
|
414
445
|
<span v-for="ind in tpl.industries" :key="`i-${ind}`" class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
415
|
-
{{ ind === "any" ? "
|
|
446
|
+
{{ ind === "any" ? t("integration.templates.detail.fit.anyIndustry") : ind }}
|
|
416
447
|
</span>
|
|
417
448
|
<span v-for="v in tpl.vendors" :key="`v-${v}`" class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 uppercase text-xs font-medium text-slate-700">
|
|
418
449
|
{{ v }}
|
|
@@ -425,65 +456,65 @@ function levelDot(level) {
|
|
|
425
456
|
</template>
|
|
426
457
|
<div v-else-if="!loaded" class="p-12 flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
|
427
458
|
<Loader2 :size="14" class="animate-spin" />
|
|
428
|
-
|
|
459
|
+
{{ t("integration.templates.detail.state.loading") }}
|
|
429
460
|
</div>
|
|
430
461
|
<div v-else class="p-12 text-center text-sm text-muted-foreground">
|
|
431
|
-
|
|
462
|
+
{{ t("integration.templates.detail.state.notFound") }}
|
|
432
463
|
</div>
|
|
433
464
|
|
|
434
465
|
<!-- Install: name + per-node credential binding ------------------------- -->
|
|
435
466
|
<Dialog v-model:open="installOpen">
|
|
436
467
|
<DialogContent class="sm:max-w-lg">
|
|
437
468
|
<DialogHeader>
|
|
438
|
-
<DialogTitle>
|
|
469
|
+
<DialogTitle>{{ t("integration.templates.detail.action.useTemplate") }}</DialogTitle>
|
|
439
470
|
<DialogDescription class="text-sm">
|
|
440
|
-
|
|
471
|
+
{{ t("integration.templates.detail.install.description") }}
|
|
441
472
|
</DialogDescription>
|
|
442
473
|
</DialogHeader>
|
|
443
474
|
|
|
444
475
|
<div class="space-y-4 py-1 max-h-[28rem] overflow-y-auto pr-1">
|
|
445
476
|
<div class="space-y-1.5">
|
|
446
|
-
<Label for="wf-name">
|
|
447
|
-
<Input id="wf-name" v-model="name" class="bg-white" placeholder="
|
|
477
|
+
<Label for="wf-name">{{ t("integration.templates.detail.install.nameLabel") }}</Label>
|
|
478
|
+
<Input id="wf-name" v-model="name" class="bg-white" :placeholder="t('integration.templates.detail.install.namePlaceholder')" />
|
|
448
479
|
</div>
|
|
449
480
|
|
|
450
481
|
<!-- Credentials -->
|
|
451
482
|
<div class="space-y-2">
|
|
452
483
|
<div class="text-mono text-muted-foreground border-b border-black/[0.06] pb-1.5">
|
|
453
|
-
|
|
484
|
+
{{ t("integration.templates.detail.credentials.title") }}
|
|
454
485
|
</div>
|
|
455
486
|
<p v-if="!credentialReqs.length" class="text-sm text-muted-foreground italic">
|
|
456
|
-
|
|
487
|
+
{{ t("integration.templates.detail.install.noCredentials") }}
|
|
457
488
|
</p>
|
|
458
489
|
<div v-for="req in credentialReqs" :key="req.slug" class="space-y-2">
|
|
459
490
|
<div class="flex items-center gap-2">
|
|
460
491
|
<span class="text-sm font-medium">{{ req.name ?? req.slug }}</span>
|
|
461
492
|
<Badge v-if="!req.registered" variant="outline" class="bg-rose-500/10 border-rose-500/20 text-rose-700 text-sm">
|
|
462
|
-
|
|
493
|
+
{{ t("integration.templates.detail.install.notRegistered") }}
|
|
463
494
|
</Badge>
|
|
464
495
|
<Badge v-else-if="!ofType(req.slug).length" variant="outline" class="bg-amber-500/10 border-amber-500/20 text-amber-700 text-sm">
|
|
465
|
-
|
|
496
|
+
{{ t("integration.templates.detail.install.noneYet") }}
|
|
466
497
|
</Badge>
|
|
467
498
|
</div>
|
|
468
499
|
|
|
469
500
|
<p v-if="!ofType(req.slug).length" class="rounded-md border-l-2 border-black/[0.08] bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground">
|
|
470
|
-
|
|
471
|
-
<NuxtLink to="/integrations/credentials" class="text-primary hover:underline">
|
|
472
|
-
|
|
501
|
+
{{ t("integration.templates.detail.install.noCredentialsOfType") }}
|
|
502
|
+
<NuxtLink to="/integrations/credentials" class="text-primary hover:underline">{{ t("integration.templates.detail.install.createOneLink") }}</NuxtLink>
|
|
503
|
+
{{ t("integration.templates.detail.install.orInstallLater") }}
|
|
473
504
|
</p>
|
|
474
505
|
<div v-for="nodeId in nodesFor(req.usedBy)" v-else :key="nodeId" class="space-y-1">
|
|
475
|
-
<Label class="text-xs text-muted-foreground font-mono">
|
|
506
|
+
<Label :for="`tpl-cred-${nodeId}`" class="text-xs text-muted-foreground font-mono">{{ t("integration.templates.detail.install.nodeLabel", { id: nodeId }) }}</Label>
|
|
476
507
|
<Select
|
|
477
508
|
:model-value="bindings[nodeId] ?? SELECT_LATER"
|
|
478
509
|
@update:model-value="(v2) => {
|
|
479
510
|
bindings[nodeId] = v2 ?? SELECT_LATER;
|
|
480
511
|
}"
|
|
481
512
|
>
|
|
482
|
-
<SelectTrigger class="bg-white">
|
|
483
|
-
<SelectValue placeholder="
|
|
513
|
+
<SelectTrigger :id="`tpl-cred-${nodeId}`" class="bg-white">
|
|
514
|
+
<SelectValue :placeholder="t('integration.templates.detail.install.selectLater')" />
|
|
484
515
|
</SelectTrigger>
|
|
485
516
|
<SelectContent>
|
|
486
|
-
<SelectItem :value="SELECT_LATER">
|
|
517
|
+
<SelectItem :value="SELECT_LATER">{{ t("integration.templates.detail.install.selectLater") }}</SelectItem>
|
|
487
518
|
<SelectItem v-for="c in ofType(req.slug)" :key="c.id" :value="c.id">
|
|
488
519
|
{{ c.name }}
|
|
489
520
|
</SelectItem>
|
|
@@ -496,11 +527,11 @@ function levelDot(level) {
|
|
|
496
527
|
<!-- Secrets -->
|
|
497
528
|
<div v-if="secretChecks.length" class="space-y-2">
|
|
498
529
|
<div class="text-mono text-muted-foreground border-b border-black/[0.06] pb-1.5">
|
|
499
|
-
|
|
530
|
+
{{ t("integration.templates.detail.install.secretsHeader") }}
|
|
500
531
|
</div>
|
|
501
532
|
<p class="text-sm text-muted-foreground">
|
|
502
|
-
|
|
503
|
-
<NuxtLink to="/integrations/secrets" class="text-primary hover:underline">
|
|
533
|
+
{{ t("integration.templates.detail.install.secretsDescription") }}
|
|
534
|
+
<NuxtLink to="/integrations/secrets" class="text-primary hover:underline">{{ t("integration.nav.item.secrets") }}</NuxtLink> {{ t("integration.templates.detail.install.secretsDescriptionSuffix") }}
|
|
504
535
|
</p>
|
|
505
536
|
<ul class="space-y-1">
|
|
506
537
|
<li
|
|
@@ -510,7 +541,7 @@ function levelDot(level) {
|
|
|
510
541
|
>
|
|
511
542
|
<code class="font-mono">{{ s.label }}</code>
|
|
512
543
|
<Badge variant="outline" :class="s.present ? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-sm' : 'bg-amber-500/10 border-amber-500/20 text-amber-700 text-sm'">
|
|
513
|
-
{{ s.present ? "
|
|
544
|
+
{{ s.present ? t("integration.templates.detail.install.secretStored") : t("integration.templates.detail.secrets.missing") }}
|
|
514
545
|
</Badge>
|
|
515
546
|
</li>
|
|
516
547
|
</ul>
|
|
@@ -523,11 +554,11 @@ function levelDot(level) {
|
|
|
523
554
|
|
|
524
555
|
<DialogFooter>
|
|
525
556
|
<Button variant="outline" :disabled="installing" @click="installOpen = false">
|
|
526
|
-
|
|
557
|
+
{{ t("integration.common.action.cancel") }}
|
|
527
558
|
</Button>
|
|
528
559
|
<Button :disabled="installing || !name.trim()" @click="confirmInstall">
|
|
529
560
|
<Loader2 v-if="installing" :size="14" class="mr-1 animate-spin" />
|
|
530
|
-
{{ installing ? "
|
|
561
|
+
{{ installing ? t("integration.templates.detail.install.submitting") : t("integration.templates.detail.install.submit") }}
|
|
531
562
|
</Button>
|
|
532
563
|
</DialogFooter>
|
|
533
564
|
</DialogContent>
|