@revenexx/studio-integrations 0.6.0 → 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 +227 -0
- package/README.md +31 -0
- 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 +29 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
- 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 +16 -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.vue +15 -14
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
- package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
- package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
- 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 +37 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
- 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.js +6 -4
- 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 +3 -1
- 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/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.js +4 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- 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 +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +70 -52
- package/dist/runtime/pages/integrations/index.vue +50 -56
- 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 +77 -68
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
- 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/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +18 -5
- package/dist/runtime/utils/localize.js +32 -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/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
|
@@ -3,7 +3,7 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4). It
|
|
7
7
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
8
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
9
|
* inlining it would bury the tab's own markup.
|
|
@@ -4,6 +4,7 @@ defineProps({
|
|
|
4
4
|
ports: { type: Array, required: true },
|
|
5
5
|
valueAt: { type: Function, required: true }
|
|
6
6
|
});
|
|
7
|
+
const t = useIntegrationsT();
|
|
7
8
|
const TYPE_GLYPHS = {
|
|
8
9
|
string: { char: "A", cls: "text-sky-600" },
|
|
9
10
|
number: { char: "#", cls: "text-amber-600" },
|
|
@@ -32,11 +33,11 @@ function typeGlyph(dataType) {
|
|
|
32
33
|
</div>
|
|
33
34
|
</div>
|
|
34
35
|
<p v-else class="text-[11px] leading-snug text-muted-foreground">
|
|
35
|
-
|
|
36
|
+
{{ t("integration.nodeInspector.output.schema.noFields") }}<template v-if="valueAt(p.name) !== null">{{ t("integration.nodeInspector.output.schema.sampledSuffix") }} <span class="font-mono">{{ valueAt(p.name) }}</span></template>.
|
|
36
37
|
</p>
|
|
37
38
|
</div>
|
|
38
39
|
<p v-if="!ports.length" class="text-xs leading-snug text-muted-foreground">
|
|
39
|
-
|
|
40
|
+
{{ t("integration.nodeInspector.output.schema.noOutputs") }}
|
|
40
41
|
</p>
|
|
41
42
|
</div>
|
|
42
43
|
</template>
|
|
@@ -3,7 +3,7 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4). It
|
|
7
7
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
8
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
9
|
* inlining it would bury the tab's own markup.
|
|
@@ -8,6 +8,7 @@ const { load: loadNodes } = useIntegrationsNodes();
|
|
|
8
8
|
onMounted(() => {
|
|
9
9
|
void loadNodes();
|
|
10
10
|
});
|
|
11
|
+
const t = useIntegrationsT();
|
|
11
12
|
const emit = defineEmits(["insert"]);
|
|
12
13
|
const search = ref("");
|
|
13
14
|
const searchInput = ref(null);
|
|
@@ -160,8 +161,8 @@ const currentGroup = computed(() => {
|
|
|
160
161
|
return cursor ?? null;
|
|
161
162
|
});
|
|
162
163
|
const levelTitle = computed(() => {
|
|
163
|
-
if (triggersLevel.value) return "
|
|
164
|
-
return currentGroup.value?.label ?? "
|
|
164
|
+
if (triggersLevel.value) return t("integration.canvas.palette.group.triggers");
|
|
165
|
+
return currentGroup.value?.label ?? t("integration.canvas.palette.title.addNode");
|
|
165
166
|
});
|
|
166
167
|
function pushPath(path) {
|
|
167
168
|
direction.value = "push";
|
|
@@ -194,7 +195,7 @@ const searchHits = computed(() => {
|
|
|
194
195
|
const matchedTriggers = computed(() => {
|
|
195
196
|
const q = search.value.trim().toLowerCase();
|
|
196
197
|
if (!q || currentGroup.value) return [];
|
|
197
|
-
const matches = (
|
|
198
|
+
const matches = (tpl) => resolveLocalized(tpl.name).toLowerCase().includes(q) || tpl.type.includes(q) || resolveLocalized(tpl.description).toLowerCase().includes(q);
|
|
198
199
|
return TRIGGER_TEMPLATES.filter(matches);
|
|
199
200
|
});
|
|
200
201
|
const nodeDrag = useNodeDrag();
|
|
@@ -278,7 +279,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
278
279
|
<button
|
|
279
280
|
type="button"
|
|
280
281
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
281
|
-
title="
|
|
282
|
+
:title="t('integration.canvas.palette.action.back')"
|
|
282
283
|
@click="popLevel"
|
|
283
284
|
>
|
|
284
285
|
<Lucide.ArrowLeft :size="14" />
|
|
@@ -293,8 +294,8 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
293
294
|
ref="searchInput"
|
|
294
295
|
v-model="search"
|
|
295
296
|
type="text"
|
|
296
|
-
:aria-label="stack.length ?
|
|
297
|
-
:placeholder="stack.length ?
|
|
297
|
+
:aria-label="stack.length ? t('integration.canvas.palette.search.ariaGroup', { group: levelTitle }) : t('integration.canvas.palette.search.ariaAll')"
|
|
298
|
+
:placeholder="stack.length ? t('integration.canvas.palette.search.placeholderGroup', { group: levelTitle }) : t('integration.canvas.palette.search.placeholderAll')"
|
|
298
299
|
class="h-7 w-full rounded-md border border-black/[0.08] bg-white pl-7 pr-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/40"
|
|
299
300
|
@keydown.esc="search ? search = '' : stack.length && popLevel()"
|
|
300
301
|
>
|
|
@@ -321,28 +322,28 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
321
322
|
<!-- ── Search results (flat, path as subtitle) ─────────────────── -->
|
|
322
323
|
<template v-if="searching">
|
|
323
324
|
<div v-if="matchedTriggers.length" class="mb-1">
|
|
324
|
-
<div class="px-2 py-1 text-mono text-muted-foreground">
|
|
325
|
+
<div class="px-2 py-1 text-mono text-muted-foreground">{{ t("integration.canvas.palette.group.triggers") }}</div>
|
|
325
326
|
<div
|
|
326
|
-
v-for="
|
|
327
|
-
:key="
|
|
327
|
+
v-for="tpl in matchedTriggers"
|
|
328
|
+
:key="tpl.id"
|
|
328
329
|
class="group flex cursor-grab items-center gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
|
|
329
330
|
draggable="true"
|
|
330
|
-
@dragstart="onTriggerDragStart(
|
|
331
|
+
@dragstart="onTriggerDragStart(tpl, $event)"
|
|
331
332
|
@dragend="onDragEnd"
|
|
332
|
-
@click="insertTrigger(
|
|
333
|
+
@click="insertTrigger(tpl)"
|
|
333
334
|
>
|
|
334
335
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
335
|
-
<component :is="iconFor(
|
|
336
|
+
<component :is="iconFor(tpl.icon)" :size="13" />
|
|
336
337
|
</span>
|
|
337
338
|
<div class="min-w-0">
|
|
338
|
-
<div class="truncate text-sm font-medium">{{ resolveLocalized(
|
|
339
|
-
<div class="text-xs leading-snug text-muted-foreground">
|
|
339
|
+
<div class="truncate text-sm font-medium">{{ resolveLocalized(tpl.name) }}</div>
|
|
340
|
+
<div class="text-xs leading-snug text-muted-foreground">{{ t("integration.canvas.badge.trigger") }}</div>
|
|
340
341
|
</div>
|
|
341
342
|
</div>
|
|
342
343
|
</div>
|
|
343
344
|
|
|
344
345
|
<p v-if="!searchHits.length && !matchedTriggers.length" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
345
|
-
|
|
346
|
+
{{ t("integration.canvas.palette.search.empty", { query: search }) }}
|
|
346
347
|
</p>
|
|
347
348
|
<div
|
|
348
349
|
v-for="hit in searchHits"
|
|
@@ -364,7 +365,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
364
365
|
v-if="versionsOf(hit.manifest.slug).length > 1"
|
|
365
366
|
:value="effVersion(hit.manifest)"
|
|
366
367
|
class="shrink-0 rounded border border-black/10 bg-white px-1 py-px text-xs tabular-nums text-muted-foreground"
|
|
367
|
-
:aria-label="
|
|
368
|
+
:aria-label="t('integration.canvas.palette.version.selectAria', { name: resolveLocalized(hit.manifest.name) })"
|
|
368
369
|
@click.stop
|
|
369
370
|
@mousedown.stop
|
|
370
371
|
@change.stop="setSelectedVersion(hit.manifest.slug, $event.target.value)"
|
|
@@ -386,21 +387,21 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
386
387
|
<!-- ── Triggers level ──────────────────────────────────────────── -->
|
|
387
388
|
<template v-else-if="triggersLevel">
|
|
388
389
|
<div
|
|
389
|
-
v-for="
|
|
390
|
-
:key="
|
|
390
|
+
v-for="tpl in TRIGGER_TEMPLATES"
|
|
391
|
+
:key="tpl.id"
|
|
391
392
|
class="group flex cursor-grab items-center gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
|
|
392
393
|
draggable="true"
|
|
393
|
-
:title="resolveLocalized(
|
|
394
|
-
@dragstart="onTriggerDragStart(
|
|
394
|
+
:title="resolveLocalized(tpl.name)"
|
|
395
|
+
@dragstart="onTriggerDragStart(tpl, $event)"
|
|
395
396
|
@dragend="onDragEnd"
|
|
396
|
-
@click="insertTrigger(
|
|
397
|
+
@click="insertTrigger(tpl)"
|
|
397
398
|
>
|
|
398
399
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
399
|
-
<component :is="iconFor(
|
|
400
|
+
<component :is="iconFor(tpl.icon)" :size="13" />
|
|
400
401
|
</span>
|
|
401
402
|
<div class="min-w-0">
|
|
402
|
-
<div class="truncate text-sm font-medium">{{ resolveLocalized(
|
|
403
|
-
<div class="text-xs leading-snug text-muted-foreground">{{ resolveLocalized(
|
|
403
|
+
<div class="truncate text-sm font-medium">{{ resolveLocalized(tpl.name) }}</div>
|
|
404
|
+
<div class="text-xs leading-snug text-muted-foreground">{{ resolveLocalized(tpl.description) }}</div>
|
|
404
405
|
</div>
|
|
405
406
|
</div>
|
|
406
407
|
</template>
|
|
@@ -409,7 +410,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
409
410
|
<template v-else-if="!currentGroup">
|
|
410
411
|
<!-- Triggers group on top -->
|
|
411
412
|
<div v-if="tree.core.children.size">
|
|
412
|
-
<div class="px-2 py-1 text-mono text-muted-foreground">
|
|
413
|
+
<div class="px-2 py-1 text-mono text-muted-foreground">{{ t("integration.canvas.palette.group.core") }}</div>
|
|
413
414
|
<button
|
|
414
415
|
type="button"
|
|
415
416
|
class="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition hover:border-black/[0.08] hover:bg-muted/40"
|
|
@@ -418,7 +419,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
418
419
|
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-emerald-600/80">
|
|
419
420
|
<Lucide.Zap :size="13" />
|
|
420
421
|
</span>
|
|
421
|
-
<span class="min-w-0 flex-1 truncate text-sm font-medium">
|
|
422
|
+
<span class="min-w-0 flex-1 truncate text-sm font-medium">{{ t("integration.canvas.palette.group.triggers") }}</span>
|
|
422
423
|
<span class="text-sm tabular-nums text-muted-foreground">{{ TRIGGER_TEMPLATES.length }}</span>
|
|
423
424
|
<Lucide.ChevronRight :size="13" class="text-muted-foreground" />
|
|
424
425
|
</button>
|
|
@@ -439,7 +440,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
439
440
|
</div>
|
|
440
441
|
|
|
441
442
|
<div v-if="tree.apps.children.size" class="mt-2">
|
|
442
|
-
<div class="px-2 py-1 text-mono text-muted-foreground">
|
|
443
|
+
<div class="px-2 py-1 text-mono text-muted-foreground">{{ t("integration.canvas.palette.group.integrations") }}</div>
|
|
443
444
|
<button
|
|
444
445
|
v-for="[label, group] in appRows"
|
|
445
446
|
:key="`app-${label}`"
|
|
@@ -499,7 +500,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
499
500
|
v-if="versionsOf(m.slug).length > 1"
|
|
500
501
|
:value="effVersion(m)"
|
|
501
502
|
class="shrink-0 rounded border border-black/10 bg-white px-1 py-px text-xs tabular-nums text-muted-foreground"
|
|
502
|
-
:aria-label="
|
|
503
|
+
:aria-label="t('integration.canvas.palette.version.selectAria', { name: resolveLocalized(m.name) })"
|
|
503
504
|
@click.stop
|
|
504
505
|
@mousedown.stop
|
|
505
506
|
@change.stop="setSelectedVersion(m.slug, $event.target.value)"
|
|
@@ -521,7 +522,7 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
521
522
|
</div>
|
|
522
523
|
|
|
523
524
|
<div class="border-t border-black/[0.06] px-3 py-2 text-sm text-muted-foreground">
|
|
524
|
-
|
|
525
|
+
{{ t("integration.canvas.palette.footer.hint") }}
|
|
525
526
|
</div>
|
|
526
527
|
</aside>
|
|
527
528
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
-
import { AddCircle as Plus, TrashBinMinimalistic as
|
|
3
|
+
import { AddCircle as Plus, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Input,
|
|
@@ -15,27 +15,28 @@ const props = defineProps({
|
|
|
15
15
|
modelValue: { type: null, required: true }
|
|
16
16
|
});
|
|
17
17
|
const emit = defineEmits(["update:modelValue"]);
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
{ value: "
|
|
21
|
-
{ value: "
|
|
22
|
-
{ value: "
|
|
23
|
-
{ value: "
|
|
24
|
-
{ value: "
|
|
25
|
-
{ value: "
|
|
26
|
-
{ value: "
|
|
27
|
-
{ value: "
|
|
28
|
-
{ value: "
|
|
29
|
-
{ value: "
|
|
30
|
-
{ value: "
|
|
31
|
-
{ value: "
|
|
32
|
-
{ value: "
|
|
33
|
-
{ value: "
|
|
34
|
-
{ value: "
|
|
35
|
-
{ value: "
|
|
36
|
-
|
|
18
|
+
const t = useIntegrationsT();
|
|
19
|
+
const METHODS = computed(() => [
|
|
20
|
+
{ value: "equal", label: t("integration.nodeConfig.queryBuilder.method.equal"), attribute: true, values: "list" },
|
|
21
|
+
{ value: "notEqual", label: t("integration.nodeConfig.queryBuilder.method.notEqual"), attribute: true, values: "list" },
|
|
22
|
+
{ value: "contains", label: t("integration.nodeConfig.queryBuilder.method.contains"), attribute: true, values: "list" },
|
|
23
|
+
{ value: "search", label: t("integration.nodeConfig.queryBuilder.method.search"), attribute: true, values: "single" },
|
|
24
|
+
{ value: "startsWith", label: t("integration.nodeConfig.queryBuilder.method.startsWith"), attribute: true, values: "single" },
|
|
25
|
+
{ value: "endsWith", label: t("integration.nodeConfig.queryBuilder.method.endsWith"), attribute: true, values: "single" },
|
|
26
|
+
{ value: "lessThan", label: t("integration.nodeConfig.queryBuilder.method.lessThan"), attribute: true, values: "single" },
|
|
27
|
+
{ value: "lessThanEqual", label: t("integration.nodeConfig.queryBuilder.method.lessThanEqual"), attribute: true, values: "single" },
|
|
28
|
+
{ value: "greaterThan", label: t("integration.nodeConfig.queryBuilder.method.greaterThan"), attribute: true, values: "single" },
|
|
29
|
+
{ value: "greaterThanEqual", label: t("integration.nodeConfig.queryBuilder.method.greaterThanEqual"), attribute: true, values: "single" },
|
|
30
|
+
{ value: "between", label: t("integration.nodeConfig.queryBuilder.method.between"), attribute: true, values: "list" },
|
|
31
|
+
{ value: "isNull", label: t("integration.nodeConfig.queryBuilder.method.isNull"), attribute: true, values: "none" },
|
|
32
|
+
{ value: "isNotNull", label: t("integration.nodeConfig.queryBuilder.method.isNotNull"), attribute: true, values: "none" },
|
|
33
|
+
{ value: "orderAsc", label: t("integration.nodeConfig.queryBuilder.method.orderAsc"), attribute: true, values: "none" },
|
|
34
|
+
{ value: "orderDesc", label: t("integration.nodeConfig.queryBuilder.method.orderDesc"), attribute: true, values: "none" },
|
|
35
|
+
{ value: "limit", label: t("integration.nodeConfig.queryBuilder.method.limit"), attribute: false, values: "single" },
|
|
36
|
+
{ value: "offset", label: t("integration.nodeConfig.queryBuilder.method.offset"), attribute: false, values: "single" }
|
|
37
|
+
]);
|
|
37
38
|
function methodMeta(method) {
|
|
38
|
-
return METHODS.find((m) => m.value === method) ?? null;
|
|
39
|
+
return METHODS.value.find((m) => m.value === method) ?? null;
|
|
39
40
|
}
|
|
40
41
|
function formatValue(v) {
|
|
41
42
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
@@ -57,16 +58,16 @@ function parseRow(str) {
|
|
|
57
58
|
return { kind: "raw", text: str };
|
|
58
59
|
}
|
|
59
60
|
function coerceValue(part) {
|
|
60
|
-
const
|
|
61
|
-
if (/^-?\d+(\.\d+)?$/.test(
|
|
62
|
-
if (
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
61
|
+
const t2 = part.trim();
|
|
62
|
+
if (/^-?\d+(\.\d+)?$/.test(t2)) return Number(t2);
|
|
63
|
+
if (t2 === "true") return true;
|
|
64
|
+
if (t2 === "false") return false;
|
|
65
|
+
return t2;
|
|
65
66
|
}
|
|
66
67
|
function rowToString(row) {
|
|
67
68
|
if (row.kind === "raw") {
|
|
68
|
-
const
|
|
69
|
-
return
|
|
69
|
+
const t2 = row.text.trim();
|
|
70
|
+
return t2 === "" ? null : t2;
|
|
70
71
|
}
|
|
71
72
|
const meta = methodMeta(row.method);
|
|
72
73
|
if (!meta) return null;
|
|
@@ -131,13 +132,13 @@ function writeJson(text) {
|
|
|
131
132
|
try {
|
|
132
133
|
const parsed = JSON.parse(text);
|
|
133
134
|
if (!Array.isArray(parsed)) {
|
|
134
|
-
jsonError.value = "
|
|
135
|
+
jsonError.value = t("integration.nodeConfig.queryBuilder.json.notArray");
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
jsonError.value = null;
|
|
138
139
|
emit("update:modelValue", parsed.map(String));
|
|
139
140
|
} catch {
|
|
140
|
-
jsonError.value = "
|
|
141
|
+
jsonError.value = t("integration.nodeConfig.json.invalid");
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? methodMeta(r.method) : null));
|
|
@@ -153,7 +154,7 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
153
154
|
:class="mode === 'builder' ? 'bg-slate-100 text-foreground' : 'text-muted-foreground hover:text-foreground'"
|
|
154
155
|
@click="mode = 'builder'"
|
|
155
156
|
>
|
|
156
|
-
|
|
157
|
+
{{ t("integration.nodeConfig.queryBuilder.mode.builder") }}
|
|
157
158
|
</button>
|
|
158
159
|
<button
|
|
159
160
|
type="button"
|
|
@@ -161,7 +162,7 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
161
162
|
:class="mode === 'json' ? 'bg-slate-100 text-foreground' : 'text-muted-foreground hover:text-foreground'"
|
|
162
163
|
@click="mode = 'json'"
|
|
163
164
|
>
|
|
164
|
-
|
|
165
|
+
{{ t("integration.nodeConfig.object.toggle.json") }}
|
|
165
166
|
</button>
|
|
166
167
|
</div>
|
|
167
168
|
</div>
|
|
@@ -175,7 +176,7 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
175
176
|
>
|
|
176
177
|
<!-- A repeating row has no room for visible labels, so each
|
|
177
178
|
control names itself and says which row it is. -->
|
|
178
|
-
<SelectTrigger :aria-label="
|
|
179
|
+
<SelectTrigger :aria-label="t('integration.nodeConfig.queryBuilder.row.comparisonAria', { n: idx + 1 })" class="h-7 w-40 shrink-0 bg-white text-sm">
|
|
179
180
|
<SelectValue />
|
|
180
181
|
</SelectTrigger>
|
|
181
182
|
<SelectContent>
|
|
@@ -186,17 +187,17 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
186
187
|
</Select>
|
|
187
188
|
<Input
|
|
188
189
|
v-if="rowMeta[idx]?.attribute"
|
|
189
|
-
:aria-label="
|
|
190
|
+
:aria-label="t('integration.nodeConfig.queryBuilder.row.fieldAria', { n: idx + 1 })"
|
|
190
191
|
:model-value="row.attribute"
|
|
191
|
-
placeholder="
|
|
192
|
+
:placeholder="t('integration.nodeConfig.queryBuilder.row.fieldPlaceholder')"
|
|
192
193
|
class="h-7 w-36 shrink-0 bg-white font-mono text-sm"
|
|
193
194
|
@update:model-value="(v) => setRow(idx, { attribute: String(v) })"
|
|
194
195
|
/>
|
|
195
196
|
<Input
|
|
196
197
|
v-if="rowMeta[idx]?.values !== 'none'"
|
|
197
|
-
:aria-label="
|
|
198
|
+
:aria-label="t('integration.nodeConfig.queryBuilder.row.valueAria', { n: idx + 1 })"
|
|
198
199
|
:model-value="row.values"
|
|
199
|
-
:placeholder="rowMeta[idx]?.values === 'list' ? '
|
|
200
|
+
:placeholder="rowMeta[idx]?.values === 'list' ? t('integration.nodeConfig.queryBuilder.row.valueListPlaceholder') : t('integration.nodeConfig.queryBuilder.row.valuePlaceholder')"
|
|
200
201
|
class="h-7 min-w-0 flex-1 bg-white text-sm"
|
|
201
202
|
@update:model-value="(v) => setRow(idx, { values: String(v) })"
|
|
202
203
|
/>
|
|
@@ -204,19 +205,19 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
204
205
|
</template>
|
|
205
206
|
<Input
|
|
206
207
|
v-else
|
|
207
|
-
:aria-label="
|
|
208
|
+
:aria-label="t('integration.nodeConfig.queryBuilder.row.rawTextAria', { n: idx + 1 })"
|
|
208
209
|
:model-value="row.text"
|
|
209
210
|
class="h-7 min-w-0 flex-1 bg-white font-mono text-sm"
|
|
210
|
-
title="
|
|
211
|
+
:title="t('integration.nodeConfig.queryBuilder.row.rawTextTitle')"
|
|
211
212
|
@update:model-value="(v) => setRow(idx, { text: String(v) })"
|
|
212
213
|
/>
|
|
213
214
|
<button
|
|
214
215
|
type="button"
|
|
215
|
-
:aria-label="
|
|
216
|
+
:aria-label="t('integration.nodeConfig.queryBuilder.row.removeAria', { n: idx + 1 })"
|
|
216
217
|
class="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:text-rose-600"
|
|
217
218
|
@click="removeRow(idx)"
|
|
218
219
|
>
|
|
219
|
-
<
|
|
220
|
+
<TrashBin :size="11" />
|
|
220
221
|
</button>
|
|
221
222
|
</div>
|
|
222
223
|
<Button
|
|
@@ -227,13 +228,13 @@ const rowMeta = computed(() => rows.value.map((r) => r.kind === "query" ? method
|
|
|
227
228
|
@click="addRow"
|
|
228
229
|
>
|
|
229
230
|
<Plus :size="11" />
|
|
230
|
-
|
|
231
|
+
{{ t("integration.nodeConfig.queryBuilder.action.addFilter") }}
|
|
231
232
|
</Button>
|
|
232
233
|
</template>
|
|
233
234
|
|
|
234
235
|
<template v-else>
|
|
235
236
|
<Textarea
|
|
236
|
-
aria-label="
|
|
237
|
+
:aria-label="t('integration.nodeConfig.queryBuilder.jsonAria')"
|
|
237
238
|
:model-value="jsonDraft"
|
|
238
239
|
:rows="6"
|
|
239
240
|
spellcheck="false"
|
|
@@ -14,6 +14,7 @@ const props = defineProps({
|
|
|
14
14
|
run: { type: Object, required: true }
|
|
15
15
|
});
|
|
16
16
|
const emit = defineEmits(["changed"]);
|
|
17
|
+
const t = useIntegrationsT();
|
|
17
18
|
const router = useRouter();
|
|
18
19
|
const { cancelRun, terminateRun, retryRun, resumeRun } = useIntegrationsRuns();
|
|
19
20
|
function viewDetails() {
|
|
@@ -26,8 +27,31 @@ const isActive = computed(() => props.run.status === "running" || props.run.stat
|
|
|
26
27
|
const isTerminal = computed(() => ["succeeded", "failed", "cancelled"].includes(props.run.status));
|
|
27
28
|
const canResume = computed(() => props.run.status === "failed");
|
|
28
29
|
const hasAny = computed(() => isActive.value || isTerminal.value);
|
|
29
|
-
const LABEL = { cancel: "Cancel", terminate: "Terminate", retry: "Retry", resume: "Resume" };
|
|
30
30
|
const busy = ref(false);
|
|
31
|
+
function actionLabel(a) {
|
|
32
|
+
switch (a) {
|
|
33
|
+
case "cancel":
|
|
34
|
+
return t("integration.runs.menu.cancel");
|
|
35
|
+
case "terminate":
|
|
36
|
+
return t("integration.runs.menu.terminate");
|
|
37
|
+
case "retry":
|
|
38
|
+
return t("integration.runs.menu.retry");
|
|
39
|
+
case "resume":
|
|
40
|
+
return t("integration.runs.menu.resume");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function actionErrorFallback(a) {
|
|
44
|
+
switch (a) {
|
|
45
|
+
case "cancel":
|
|
46
|
+
return t("integration.runs.error.cancel");
|
|
47
|
+
case "terminate":
|
|
48
|
+
return t("integration.runs.error.terminate");
|
|
49
|
+
case "retry":
|
|
50
|
+
return t("integration.runs.error.retry");
|
|
51
|
+
case "resume":
|
|
52
|
+
return t("integration.runs.error.resume");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
31
55
|
async function runAction(a) {
|
|
32
56
|
if (busy.value) return;
|
|
33
57
|
busy.value = true;
|
|
@@ -49,18 +73,18 @@ async function runAction(a) {
|
|
|
49
73
|
}
|
|
50
74
|
const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
|
|
51
75
|
toast.success(
|
|
52
|
-
(a === "retry" || a === "resume") && newId && newId !== String(props.run.id) ?
|
|
76
|
+
(a === "retry" || a === "resume") && newId && newId !== String(props.run.id) ? t("integration.runs.toast.action.started", { id: newId }) : t("integration.runs.toast.action.requested", { action: actionLabel(a) })
|
|
53
77
|
);
|
|
54
78
|
emit("changed");
|
|
55
79
|
} catch (e) {
|
|
56
|
-
toast.error(e?.message ??
|
|
80
|
+
toast.error(e?.message ?? actionErrorFallback(a));
|
|
57
81
|
} finally {
|
|
58
82
|
busy.value = false;
|
|
59
83
|
}
|
|
60
84
|
}
|
|
61
85
|
function confirmTerminate() {
|
|
62
|
-
toast(
|
|
63
|
-
action: { label: "
|
|
86
|
+
toast(t("integration.runs.toast.terminate.confirm", { id: props.run.id }), {
|
|
87
|
+
action: { label: actionLabel("terminate"), onClick: () => void runAction("terminate") }
|
|
64
88
|
});
|
|
65
89
|
}
|
|
66
90
|
</script>
|
|
@@ -70,19 +94,19 @@ function confirmTerminate() {
|
|
|
70
94
|
<DropdownMenuTrigger as-child>
|
|
71
95
|
<!-- Icon-only, so it needs a name of its own — the run id is the only
|
|
72
96
|
thing that tells two rows' menus apart. -->
|
|
73
|
-
<Button variant="ghost" size="icon" class="size-7" :disabled="busy" :aria-label="
|
|
97
|
+
<Button variant="ghost" size="icon" class="size-7" :disabled="busy" :aria-label="t('integration.runs.menu.aria.actions', { id: run.id })" @click.stop>
|
|
74
98
|
<MoreHorizontal :size="16" />
|
|
75
99
|
</Button>
|
|
76
100
|
</DropdownMenuTrigger>
|
|
77
101
|
<DropdownMenuContent align="end" class="w-40" @click.stop>
|
|
78
|
-
<DropdownMenuItem @select="viewDetails">
|
|
79
|
-
<DropdownMenuItem @select="viewReplay">
|
|
102
|
+
<DropdownMenuItem @select="viewDetails">{{ t("integration.runs.menu.viewDetails") }}</DropdownMenuItem>
|
|
103
|
+
<DropdownMenuItem @select="viewReplay">{{ t("integration.runs.menu.replay") }}</DropdownMenuItem>
|
|
80
104
|
<DropdownMenuSeparator v-if="hasAny" />
|
|
81
|
-
<DropdownMenuItem v-if="isActive" @select="runAction('cancel')">
|
|
82
|
-
<DropdownMenuItem v-if="isActive" class="text-rose-600" @select="confirmTerminate">
|
|
105
|
+
<DropdownMenuItem v-if="isActive" @select="runAction('cancel')">{{ actionLabel("cancel") }}</DropdownMenuItem>
|
|
106
|
+
<DropdownMenuItem v-if="isActive" class="text-rose-600" @select="confirmTerminate">{{ actionLabel("terminate") }}</DropdownMenuItem>
|
|
83
107
|
<DropdownMenuSeparator v-if="isActive && (canResume || isTerminal)" />
|
|
84
|
-
<DropdownMenuItem v-if="canResume" @select="runAction('resume')">
|
|
85
|
-
<DropdownMenuItem v-if="isTerminal" @select="runAction('retry')">
|
|
108
|
+
<DropdownMenuItem v-if="canResume" @select="runAction('resume')">{{ actionLabel("resume") }}</DropdownMenuItem>
|
|
109
|
+
<DropdownMenuItem v-if="isTerminal" @select="runAction('retry')">{{ actionLabel("retry") }}</DropdownMenuItem>
|
|
86
110
|
</DropdownMenuContent>
|
|
87
111
|
</DropdownMenu>
|
|
88
112
|
</template>
|