@revenexx/studio-integrations 0.7.0 → 1.1.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 +633 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +4 -1
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +8 -2
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +2 -6
- package/dist/runtime/components/Integrations/ExpressionInput.vue +5 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +16 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +1 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +2 -0
- 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 +135 -10
- 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 +34 -8
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +12 -8
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +9 -6
- package/dist/runtime/components/Integrations/RunsTable.vue +2 -2
- package/dist/runtime/components/Integrations/StickyNoteView.vue +45 -11
- 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 +3 -3
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +12 -3
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +79 -12
- 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 +13 -3
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +95 -29
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +11 -10
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +37 -26
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -5
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +53 -14
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +9 -4
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +9 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +27 -18
- package/dist/runtime/composables/useIntegrationsNodes.js +11 -1
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +30 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +70 -29
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +7 -14
- package/dist/runtime/composables/useIntegrationsSchedules.js +80 -35
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +30 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +18 -7
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +25 -4
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +22 -1
- package/dist/runtime/data/integrationNodeCatalog.js +8 -4
- package/dist/runtime/data/loopBody.d.ts +129 -0
- package/dist/runtime/data/loopBody.js +76 -0
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/i18n/messages/de.json +62 -33
- package/dist/runtime/i18n/messages/en.json +32 -3
- package/dist/runtime/pages/integrations/credentials.vue +4 -1
- package/dist/runtime/pages/integrations/index.vue +32 -34
- package/dist/runtime/pages/integrations/runs.vue +22 -10
- package/dist/runtime/pages/integrations/schedules.vue +64 -7
- package/dist/runtime/pages/integrations/templates/[slug].vue +13 -5
- package/dist/runtime/pages/integrations/workflows/[id].vue +193 -104
- package/dist/runtime/types/integrations.d.ts +64 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +6 -1
- package/dist/runtime/utils/credentialState.js +1 -1
- package/dist/runtime/utils/localize.d.ts +34 -0
- package/dist/runtime/utils/localize.js +22 -7
- package/dist/runtime/utils/pollBudget.d.ts +1 -1
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +52 -0
- package/dist/runtime/utils/statusStyle.js +26 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { Badge, Button } from "@revenexx/studio";
|
|
|
4
4
|
import { toast } from "vue-sonner";
|
|
5
5
|
import RunsTable from "../../components/Integrations/RunsTable.vue";
|
|
6
6
|
import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
7
|
-
import {
|
|
7
|
+
import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
|
|
8
8
|
const t = useIntegrationsT();
|
|
9
9
|
useHead({ title: () => t("integration.runs.list.head.title") });
|
|
10
10
|
const route = useRoute();
|
|
@@ -37,7 +37,7 @@ watch([runTick, workflowTick], () => {
|
|
|
37
37
|
let pollTimer = null;
|
|
38
38
|
onMounted(() => {
|
|
39
39
|
void load(true);
|
|
40
|
-
void
|
|
40
|
+
void reloadMissedRuns();
|
|
41
41
|
pollTimer = setInterval(() => {
|
|
42
42
|
if (hasActiveRuns.value && !loading.value && budget.due(delivering.value)) void load(true);
|
|
43
43
|
}, POLL_MS);
|
|
@@ -49,31 +49,43 @@ onUnmounted(() => {
|
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
const busyId = ref(null);
|
|
52
|
+
async function reloadMissedRuns() {
|
|
53
|
+
try {
|
|
54
|
+
await loadGlobal(true);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
52
58
|
async function onReplay(dl) {
|
|
53
59
|
busyId.value = dl.id;
|
|
54
60
|
try {
|
|
55
61
|
const res = await replay(dl.id);
|
|
56
62
|
const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
|
|
57
63
|
toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
|
|
58
|
-
await loadGlobal(true);
|
|
59
|
-
reload();
|
|
60
64
|
} catch (e) {
|
|
61
65
|
toast.error(e?.message ?? t("integration.deadLetters.toast.replay.error"));
|
|
62
|
-
} finally {
|
|
63
66
|
busyId.value = null;
|
|
67
|
+
return;
|
|
64
68
|
}
|
|
69
|
+
await reloadMissedRuns();
|
|
70
|
+
reload();
|
|
71
|
+
busyId.value = null;
|
|
65
72
|
}
|
|
66
73
|
async function onDiscard(dl) {
|
|
67
74
|
busyId.value = dl.id;
|
|
68
75
|
try {
|
|
69
76
|
await discard(dl.id);
|
|
70
77
|
toast.success(t("integration.deadLetters.toast.discard.done"));
|
|
71
|
-
await loadGlobal(true);
|
|
72
78
|
} catch (e) {
|
|
73
79
|
toast.error(e?.message ?? t("integration.deadLetters.toast.discard.error"));
|
|
74
|
-
} finally {
|
|
75
80
|
busyId.value = null;
|
|
81
|
+
return;
|
|
76
82
|
}
|
|
83
|
+
await reloadMissedRuns();
|
|
84
|
+
busyId.value = null;
|
|
85
|
+
}
|
|
86
|
+
function workflowNameOf(dl) {
|
|
87
|
+
const name = dl.workflow_name;
|
|
88
|
+
return typeof name === "string" && name.trim() !== "" ? name : t("integration.deadLetters.workflow.fallback", { id: dl.workflow_id });
|
|
77
89
|
}
|
|
78
90
|
function fmt(iso) {
|
|
79
91
|
if (!iso) return "\u2014";
|
|
@@ -104,7 +116,7 @@ function fmt(iso) {
|
|
|
104
116
|
<p class="text-sm text-muted-foreground">
|
|
105
117
|
{{ t("integration.deadLetters.list.description") }}
|
|
106
118
|
</p>
|
|
107
|
-
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() =>
|
|
119
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() => reloadMissedRuns()">
|
|
108
120
|
<Loader2 v-if="missedLoading" :size="12" class="animate-spin" />
|
|
109
121
|
{{ t("integration.common.action.refresh") }}
|
|
110
122
|
</Button>
|
|
@@ -130,10 +142,10 @@ function fmt(iso) {
|
|
|
130
142
|
:key="dl.id"
|
|
131
143
|
class="flex items-start gap-3 px-3 py-2.5"
|
|
132
144
|
>
|
|
133
|
-
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5',
|
|
145
|
+
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5', deadLetterStatusClass(dl.status)]">{{ deadLetterStatusLabel(dl.status) }}</Badge>
|
|
134
146
|
<div class="min-w-0 flex-1">
|
|
135
147
|
<NuxtLink :to="`/integrations/workflows/${dl.workflow_id}`" class="text-sm font-medium truncate transition-colors hover:text-primary">
|
|
136
|
-
{{ dl
|
|
148
|
+
{{ workflowNameOf(dl) }}
|
|
137
149
|
</NuxtLink>
|
|
138
150
|
<div class="text-sm break-words">{{ dl.reason || t("integration.deadLetters.reason.fallback") }}</div>
|
|
139
151
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
|
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
import { ClockCircle as Clock, Globus as Globe } from "#solar-icons";
|
|
3
3
|
import { Badge } from "@revenexx/studio";
|
|
4
4
|
import { cadenceRank, describeCron } from "../../utils/schedule";
|
|
5
|
+
import { runStatusLabel } from "../../utils/statusStyle";
|
|
5
6
|
const t = useIntegrationsT();
|
|
6
7
|
useHead({ title: () => t("integration.schedules.head.title") });
|
|
7
8
|
const router = useRouter();
|
|
8
|
-
const { entries, activeCount, loading, load } = useIntegrationsSchedules();
|
|
9
|
+
const { entries, activeCount, timezonesInUse, loading, error, load } = useIntegrationsSchedules();
|
|
9
10
|
onMounted(() => {
|
|
10
11
|
void load(true);
|
|
11
12
|
});
|
|
12
|
-
function
|
|
13
|
+
function inScheduleZone(iso, timezone) {
|
|
14
|
+
const parts = { month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" };
|
|
15
|
+
try {
|
|
16
|
+
return new Date(iso).toLocaleString(void 0, { ...parts, timeZone: timezone });
|
|
17
|
+
} catch {
|
|
18
|
+
return new Date(iso).toLocaleString(void 0, parts);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function formatRelative(iso, timezone) {
|
|
13
22
|
if (!iso) return "\u2014";
|
|
14
23
|
const ms = new Date(iso).getTime() - Date.now();
|
|
15
24
|
if (Math.abs(ms) < 6e4) return ms > 0 ? t("integration.schedules.time.inLessThanMinute") : t("integration.common.time.justNow");
|
|
@@ -21,7 +30,11 @@ function formatRelative(iso) {
|
|
|
21
30
|
const hrs = Math.round(Math.abs(ms) / 36e5);
|
|
22
31
|
return ms > 0 ? t("integration.schedules.time.inHours", hrs) : t("integration.common.time.hoursAgo", hrs);
|
|
23
32
|
}
|
|
24
|
-
return
|
|
33
|
+
return inScheduleZone(iso, timezone);
|
|
34
|
+
}
|
|
35
|
+
function lastRunText(e) {
|
|
36
|
+
if (e.lastRunAt) return formatRelative(e.lastRunAt, e.timezone);
|
|
37
|
+
return e.lastRunStatus ? runStatusLabel(e.lastRunStatus) : "\u2014";
|
|
25
38
|
}
|
|
26
39
|
const filterTabs = computed(() => [
|
|
27
40
|
{
|
|
@@ -40,7 +53,10 @@ const filterTabs = computed(() => [
|
|
|
40
53
|
key: "timezone",
|
|
41
54
|
label: t("integration.schedules.table.column.timezone"),
|
|
42
55
|
getter: (e) => e.timezone,
|
|
43
|
-
|
|
56
|
+
// The zones the platform reports as in use across the tenant, not the ones
|
|
57
|
+
// visible in the rows: with the list narrowed, deriving them from the rows
|
|
58
|
+
// would shrink the menu to what the previous choice left standing.
|
|
59
|
+
options: timezonesInUse.value.map((tz) => ({ value: tz, label: tz }))
|
|
44
60
|
}
|
|
45
61
|
]);
|
|
46
62
|
const columns = computed(() => [
|
|
@@ -76,7 +92,25 @@ const columns = computed(() => [
|
|
|
76
92
|
</template>
|
|
77
93
|
</PageHeader>
|
|
78
94
|
|
|
95
|
+
<!--
|
|
96
|
+
A refused read says so. Without this the page fell through to the table's
|
|
97
|
+
own empty text, so a 503 and a tenant with no schedules were one screen —
|
|
98
|
+
and the message it needed was already written and translated
|
|
99
|
+
(`specs/schedules.md` AC-13). An alert rather than a bare div, so it is
|
|
100
|
+
announced and not only visible.
|
|
101
|
+
-->
|
|
102
|
+
<div v-if="error" role="alert" class="m-4 rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
103
|
+
{{ error }}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
79
106
|
<div class="p-4">
|
|
107
|
+
<!--
|
|
108
|
+
The empty text answers the error too, so the table cannot go on saying
|
|
109
|
+
"No schedules yet" underneath a banner explaining that there was no
|
|
110
|
+
answer to count. Two renderings of one fact rather than two facts: the
|
|
111
|
+
banner carries the platform's own words, which may be specific, and this
|
|
112
|
+
is why the list is blank.
|
|
113
|
+
-->
|
|
80
114
|
<DataTable
|
|
81
115
|
:loading="loading"
|
|
82
116
|
:data="entries"
|
|
@@ -85,7 +119,7 @@ const columns = computed(() => [
|
|
|
85
119
|
:default-sort="{ column: 'nextRun', direction: 'asc' }"
|
|
86
120
|
:search="{ placeholder: t('integration.schedules.search.placeholder') }"
|
|
87
121
|
:filter-tabs="filterTabs"
|
|
88
|
-
:empty-text="t('integration.schedules.state.empty')"
|
|
122
|
+
:empty-text="error ? t('integration.schedules.error.loadFailed') : t('integration.schedules.state.empty')"
|
|
89
123
|
@row-click="(e2) => router.push(`/integrations/workflows/${e2.workflowId}`)"
|
|
90
124
|
>
|
|
91
125
|
<template #cell-workflow="{ row: e }">
|
|
@@ -143,12 +177,35 @@ const columns = computed(() => [
|
|
|
143
177
|
semantics in the shared table (PO-233, and a gap in
|
|
144
178
|
`specs/accessibility.md`).
|
|
145
179
|
-->
|
|
180
|
+
<!--
|
|
181
|
+
The instant the platform holds, shown as a distance from now, with the
|
|
182
|
+
moment in the schedule's own zone as its title: "in 6d" answers when,
|
|
183
|
+
and "2026-08-24T08:30:00+02:00" answers which side of a clock change it
|
|
184
|
+
falls on. Neither is worked out here — a browser doing summer-time
|
|
185
|
+
arithmetic that a wrong answer makes worse than no answer is exactly
|
|
186
|
+
what this replaced.
|
|
187
|
+
-->
|
|
146
188
|
<template #cell-nextRun="{ row: e }">
|
|
147
|
-
<span
|
|
189
|
+
<span
|
|
190
|
+
:aria-label="t('integration.schedules.table.column.nextRun')"
|
|
191
|
+
:title="e.nextRunLocal ?? void 0"
|
|
192
|
+
class="text-sm tabular-nums"
|
|
193
|
+
>{{ formatRelative(e.nextRunAt, e.timezone) }}</span>
|
|
148
194
|
</template>
|
|
149
195
|
|
|
196
|
+
<!--
|
|
197
|
+
The last run carries a title too, and it is the instant rather than a
|
|
198
|
+
zone reading: the platform reports no local form for a run the way it
|
|
199
|
+
does for a firing, and inventing one here is what this page stopped
|
|
200
|
+
doing. Without it a run further back than a day was readable to the
|
|
201
|
+
minute and to no greater precision than the cell's own text.
|
|
202
|
+
-->
|
|
150
203
|
<template #cell-lastRun="{ row: e }">
|
|
151
|
-
<span
|
|
204
|
+
<span
|
|
205
|
+
:aria-label="t('integration.schedules.table.column.lastRun')"
|
|
206
|
+
:title="e.lastRunAt ?? void 0"
|
|
207
|
+
class="text-sm tabular-nums text-muted-foreground"
|
|
208
|
+
>{{ lastRunText(e) }}</span>
|
|
152
209
|
</template>
|
|
153
210
|
|
|
154
211
|
<template #cell-status="{ row: e }">
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, reactive, ref } from "vue";
|
|
3
3
|
import { marked } from "marked";
|
|
4
|
-
import { AltArrowRight as Chevron,
|
|
4
|
+
import { AltArrowRight as Chevron, CheckCircle as CheckCircle2, Key, RefreshCircle as Loader2, Shield as ShieldAlert, User } from "#solar-icons";
|
|
5
|
+
import { triggerGlyph as glyphForTrigger } from "../../../data/triggerGlyphs";
|
|
5
6
|
import {
|
|
6
7
|
Badge,
|
|
7
8
|
Button,
|
|
@@ -90,7 +91,6 @@ function firstSentence(text) {
|
|
|
90
91
|
const idx = text.indexOf(". ");
|
|
91
92
|
return idx === -1 ? text : text.slice(0, idx + 1);
|
|
92
93
|
}
|
|
93
|
-
const TRIGGER_ICON = { schedule: Clock, webhook: Webhook, manual: Play, event: Zap };
|
|
94
94
|
const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((trig) => {
|
|
95
95
|
let label = trig.type.charAt(0).toUpperCase() + trig.type.slice(1);
|
|
96
96
|
if (trig.type === "schedule" && trig.config?.cron) {
|
|
@@ -99,7 +99,7 @@ const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((trig)
|
|
|
99
99
|
}
|
|
100
100
|
if (trig.type === "webhook" && trig.config?.path) label = `${t("integration.templates.trigger.webhook")} \xB7 ${trig.config.path}`;
|
|
101
101
|
if (trig.type === "manual") label = t("integration.templates.trigger.manual");
|
|
102
|
-
return { key: trig.id, icon:
|
|
102
|
+
return { key: trig.id, icon: glyphForTrigger(trig.type), label };
|
|
103
103
|
}));
|
|
104
104
|
const variables = computed(() => Object.entries(tpl.value?.blob.variables ?? {}).map(([key, v]) => ({
|
|
105
105
|
key,
|
|
@@ -206,8 +206,16 @@ function levelDot(level) {
|
|
|
206
206
|
</PageHeader>
|
|
207
207
|
|
|
208
208
|
<div class="p-4 space-y-4">
|
|
209
|
-
<!-- Hero: the live workflow preview on the editor's dotted canvas
|
|
210
|
-
|
|
209
|
+
<!-- Hero: the live workflow preview on the editor's dotted canvas.
|
|
210
|
+
Named like the two panels below it, with `aria-label` because it
|
|
211
|
+
carries no heading to point at: it was the largest thing on the page
|
|
212
|
+
and the only one with no name at all, which also left it the one
|
|
213
|
+
surface drawing the miniature that nothing could address (PO-247). -->
|
|
214
|
+
<div
|
|
215
|
+
role="region"
|
|
216
|
+
:aria-label="t('integration.templates.detail.flow.ariaLabel')"
|
|
217
|
+
class="relative overflow-hidden rounded-lg border border-black/[0.06] bg-slate-50/70 [background-image:radial-gradient(circle,rgba(15,23,42,0.07)_1px,transparent_1px)] [background-size:14px_14px]"
|
|
218
|
+
>
|
|
211
219
|
<TemplateFlowPreview v-if="tpl.detailLoaded" :blob="tpl.blob" :height="400" class="px-4" />
|
|
212
220
|
<div v-else class="flex h-[400px] items-center justify-center">
|
|
213
221
|
<Loader2 :size="18" class="animate-spin text-muted-foreground/50" />
|