@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
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
-
import
|
|
3
|
+
import TemplateCard from "../../components/Integrations/TemplateCard.vue";
|
|
4
4
|
import WorkflowCreateDialog from "../../components/Integrations/WorkflowCreateDialog.vue";
|
|
5
5
|
import { Pulse as Activity, DangerTriangle as AlertTriangle, ArrowRight, Box as Boxes, ClockCircle as Clock, Database, Layers, AddCircle as Plus, Radio, MagicStick as Sparkles, LinkRound as Webhook, Programming as Workflow } from "#solar-icons";
|
|
6
6
|
import { Badge, Button } from "@revenexx/studio";
|
|
7
|
-
|
|
7
|
+
import { runStatusLabel, statusClass } from "../../utils/statusStyle";
|
|
8
|
+
const t = useIntegrationsT();
|
|
9
|
+
useHead({ title: () => t("integration.dashboard.head.title") });
|
|
8
10
|
const { workflows, activeCount: activeWorkflows, loading: workflowsLoading, load: loadWorkflows } = useIntegrationsWorkflows();
|
|
9
11
|
const { runs, runningCount, failedCount, loading: runsLoading, load: loadRuns } = useIntegrationsRuns();
|
|
10
|
-
const {
|
|
11
|
-
watch(() =>
|
|
12
|
-
if (
|
|
12
|
+
const { startingPoints, loading: templatesLoading, load: loadTemplates, ensureDetails } = useIntegrationsTemplates();
|
|
13
|
+
watch(() => startingPoints.value.map((t2) => t2.slug).join(","), () => {
|
|
14
|
+
if (startingPoints.value.length) void ensureDetails(startingPoints.value.map((t2) => t2.slug));
|
|
13
15
|
}, { immediate: true });
|
|
14
16
|
const { activeCount: schedulesActive, load: loadSchedules } = useIntegrationsSchedules();
|
|
15
17
|
const { pendingCount: missedRuns, globalLoading: missedLoading, loadGlobal: loadMissedRuns } = useIntegrationsDeadLetters();
|
|
@@ -24,107 +26,104 @@ onMounted(() => {
|
|
|
24
26
|
void loadSchedules();
|
|
25
27
|
void loadMissedRuns();
|
|
26
28
|
});
|
|
27
|
-
const
|
|
29
|
+
const latestByWorkflow = computed(() => {
|
|
28
30
|
const seen = /* @__PURE__ */ new Set();
|
|
29
31
|
const out = [];
|
|
30
32
|
for (const r of runs.value) {
|
|
31
33
|
if (seen.has(r.workflowId)) continue;
|
|
32
34
|
seen.add(r.workflowId);
|
|
33
35
|
out.push(r);
|
|
34
|
-
if (out.length >= 10) break;
|
|
35
36
|
}
|
|
36
37
|
return out;
|
|
37
38
|
});
|
|
38
|
-
const
|
|
39
|
+
const latestPerWorkflow = computed(() => latestByWorkflow.value.slice(0, 10));
|
|
40
|
+
const failedRecent = computed(() => latestByWorkflow.value.filter((r) => r.status === "failed").slice(0, 3));
|
|
39
41
|
function formatRelative(iso) {
|
|
40
42
|
if (!iso) return "\u2014";
|
|
41
43
|
const ms = Date.now() - new Date(iso).getTime();
|
|
42
|
-
if (ms < 6e4) return "
|
|
43
|
-
if (ms < 36e5) return
|
|
44
|
-
if (ms < 864e5) return
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
function statusClass(status) {
|
|
48
|
-
switch (status) {
|
|
49
|
-
case "succeeded":
|
|
50
|
-
return "bg-emerald-500/15 text-emerald-700 border-emerald-500/20";
|
|
51
|
-
case "running":
|
|
52
|
-
return "bg-sky-500/15 text-sky-700 border-sky-500/20";
|
|
53
|
-
case "failed":
|
|
54
|
-
return "bg-rose-500/15 text-rose-700 border-rose-500/20";
|
|
55
|
-
case "queued":
|
|
56
|
-
return "bg-amber-500/15 text-amber-700 border-amber-500/20";
|
|
57
|
-
case "cancelled":
|
|
58
|
-
return "bg-slate-500/15 text-slate-700 border-slate-500/20";
|
|
59
|
-
}
|
|
44
|
+
if (ms < 6e4) return t("integration.common.time.justNow");
|
|
45
|
+
if (ms < 36e5) return t("integration.common.time.minutesAgo", Math.floor(ms / 6e4));
|
|
46
|
+
if (ms < 864e5) return t("integration.common.time.hoursAgo", Math.floor(ms / 36e5));
|
|
47
|
+
return t("integration.common.time.daysAgo", Math.floor(ms / 864e5));
|
|
60
48
|
}
|
|
61
49
|
const kpis = computed(() => [
|
|
62
50
|
{
|
|
63
|
-
|
|
51
|
+
key: "activeWorkflows",
|
|
52
|
+
label: t("integration.dashboard.kpi.activeWorkflows.label"),
|
|
64
53
|
value: activeWorkflows.value,
|
|
65
|
-
|
|
54
|
+
// A plural key, so the count is the second argument. Called without one it
|
|
55
|
+
// would put the raw "a | b" on screen.
|
|
56
|
+
sub: t("integration.dashboard.kpi.activeWorkflows.sub", workflows.value.length - activeWorkflows.value),
|
|
66
57
|
icon: Workflow,
|
|
67
58
|
tile: "bg-emerald-500 text-white"
|
|
68
59
|
},
|
|
69
60
|
{
|
|
70
|
-
|
|
61
|
+
key: "runningNow",
|
|
62
|
+
label: t("integration.dashboard.kpi.runningNow.label"),
|
|
71
63
|
value: runningCount.value,
|
|
72
|
-
sub: "
|
|
64
|
+
sub: t("integration.dashboard.kpi.runningNow.sub"),
|
|
73
65
|
icon: Activity,
|
|
74
66
|
tile: "bg-sky-500 text-white"
|
|
75
67
|
},
|
|
76
68
|
{
|
|
77
|
-
|
|
69
|
+
key: "scheduled",
|
|
70
|
+
label: t("integration.dashboard.kpi.scheduled.label"),
|
|
78
71
|
value: schedulesActive.value,
|
|
79
|
-
sub: "
|
|
72
|
+
sub: t("integration.dashboard.kpi.scheduled.sub"),
|
|
80
73
|
icon: Clock,
|
|
81
74
|
tile: "bg-violet-500 text-white"
|
|
82
75
|
},
|
|
83
76
|
{
|
|
84
|
-
|
|
77
|
+
key: "missedRuns",
|
|
78
|
+
label: t("integration.dashboard.kpi.missedRuns.label"),
|
|
85
79
|
value: missedRuns.value,
|
|
86
|
-
sub: "
|
|
80
|
+
sub: t("integration.dashboard.kpi.missedRuns.sub"),
|
|
87
81
|
icon: AlertTriangle,
|
|
88
82
|
tile: missedRuns.value ? "bg-amber-500 text-white" : "bg-slate-300 text-white",
|
|
89
83
|
to: "/integrations/runs?tab=missed"
|
|
90
84
|
}
|
|
91
85
|
]);
|
|
92
|
-
const surfaces = [
|
|
93
|
-
{ label: "
|
|
94
|
-
{ label: "
|
|
95
|
-
{ label: "
|
|
96
|
-
{ label: "
|
|
97
|
-
];
|
|
86
|
+
const surfaces = computed(() => [
|
|
87
|
+
{ label: t("integration.dashboard.surface.io.label"), to: "/integrations/io", icon: Database, hint: t("integration.dashboard.surface.io.hint") },
|
|
88
|
+
{ label: t("integration.dashboard.surface.sftp.label"), to: "/integrations/sftp", icon: Boxes, hint: t("integration.dashboard.surface.sftp.hint") },
|
|
89
|
+
{ label: t("integration.dashboard.surface.events.label"), to: "/integrations/events", icon: Radio, hint: t("integration.dashboard.surface.events.hint") },
|
|
90
|
+
{ label: t("integration.dashboard.surface.webhooks.label"), to: "/integrations/webhooks", icon: Webhook, hint: t("integration.dashboard.surface.webhooks.hint") }
|
|
91
|
+
]);
|
|
98
92
|
</script>
|
|
99
93
|
|
|
100
94
|
<template>
|
|
101
95
|
<IntegrationsLayout>
|
|
102
96
|
<PageHeader
|
|
103
|
-
title="
|
|
104
|
-
description="
|
|
97
|
+
:title="t('integration.dashboard.header.title')"
|
|
98
|
+
:description="t('integration.dashboard.header.description')"
|
|
105
99
|
>
|
|
106
100
|
<template #actions>
|
|
107
101
|
<NuxtLink to="/integrations/templates">
|
|
108
102
|
<Button variant="outline" class="h-7 text-sm">
|
|
109
103
|
<Layers :size="12" />
|
|
110
|
-
|
|
104
|
+
{{ t("integration.dashboard.action.browseTemplates") }}
|
|
111
105
|
</Button>
|
|
112
106
|
</NuxtLink>
|
|
113
107
|
<Button class="h-7 text-sm" @click="createOpen = true">
|
|
114
108
|
<Plus :size="14" />
|
|
115
|
-
|
|
109
|
+
{{ t("integration.dashboard.action.newWorkflow") }}
|
|
116
110
|
</Button>
|
|
117
111
|
</template>
|
|
118
112
|
</PageHeader>
|
|
119
113
|
|
|
120
114
|
<div class="p-4 space-y-4">
|
|
121
|
-
<!-- KPI strip
|
|
122
|
-
|
|
115
|
+
<!-- KPI strip. Each tile carries its figure in its own name: the Missed-runs
|
|
116
|
+
tile is a link, and a link takes its name from its label instead of its
|
|
117
|
+
contents — named "Missed runs" alone it would announce less than the
|
|
118
|
+
three tiles beside it. -->
|
|
119
|
+
<div role="group" :aria-label="t('integration.dashboard.kpi.aria.group')" class="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
|
|
123
120
|
<component
|
|
124
121
|
:is="k.to ? NuxtLinkComp : 'div'"
|
|
125
122
|
v-for="k in kpis"
|
|
126
|
-
:key="k.
|
|
123
|
+
:key="k.key"
|
|
127
124
|
:to="k.to"
|
|
125
|
+
:role="k.to ? void 0 : 'group'"
|
|
126
|
+
:aria-label="`${k.label}: ${k.value}`"
|
|
128
127
|
class="block rounded-lg border border-black/[0.06] bg-white p-3"
|
|
129
128
|
:class="k.to ? 'transition hover:border-black/20' : ''"
|
|
130
129
|
>
|
|
@@ -148,30 +147,34 @@ const surfaces = [
|
|
|
148
147
|
</component>
|
|
149
148
|
</div>
|
|
150
149
|
|
|
151
|
-
<!-- Two-column: recent runs + needs attention +
|
|
150
|
+
<!-- Two-column: recent runs + needs attention + templates to start from -->
|
|
152
151
|
<div class="grid gap-4 lg:grid-cols-3">
|
|
153
152
|
<!-- Recent runs ─ takes 2 cols -->
|
|
154
|
-
<div class="lg:col-span-2 rounded-lg border border-black/[0.06] bg-white">
|
|
153
|
+
<div role="group" :aria-label="t('integration.dashboard.runs.title')" class="lg:col-span-2 rounded-lg border border-black/[0.06] bg-white">
|
|
155
154
|
<div class="flex items-center justify-between border-b border-black/[0.06] px-3 py-2">
|
|
156
155
|
<div class="flex items-center gap-2">
|
|
157
156
|
<span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
158
157
|
<Activity :size="12" />
|
|
159
158
|
</span>
|
|
160
|
-
<h3 class="text-sm font-semibold">
|
|
159
|
+
<h3 class="text-sm font-semibold">{{ t("integration.dashboard.runs.title") }}</h3>
|
|
161
160
|
</div>
|
|
162
161
|
<NuxtLink to="/integrations/runs" class="text-sm text-muted-foreground hover:text-foreground inline-flex items-center gap-1">
|
|
163
|
-
|
|
162
|
+
{{ t("integration.common.action.viewAll") }} <ArrowRight :size="11" />
|
|
164
163
|
</NuxtLink>
|
|
165
164
|
</div>
|
|
166
165
|
<div>
|
|
167
|
-
|
|
166
|
+
<!-- An entry is a link, not a div that answers a click: that is what
|
|
167
|
+
makes it reachable by keyboard, and it is what gives it a name —
|
|
168
|
+
the values it shows. A name written onto a plain row would be
|
|
169
|
+
heard by nobody, because ARIA does not name a generic element. -->
|
|
170
|
+
<NuxtLink
|
|
168
171
|
v-for="r in latestPerWorkflow"
|
|
169
172
|
:key="r.id"
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
:to="{ path: `/integrations/workflows/${r.workflowId}`, query: { run: r.id } }"
|
|
174
|
+
class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 hover:bg-muted/30"
|
|
172
175
|
>
|
|
173
176
|
<Badge variant="outline" :class="['border text-xs font-medium uppercase', statusClass(r.status)]">
|
|
174
|
-
{{ r.status }}
|
|
177
|
+
{{ runStatusLabel(r.status) }}
|
|
175
178
|
</Badge>
|
|
176
179
|
<div class="truncate">
|
|
177
180
|
<div class="font-medium text-foreground truncate">{{ r.workflowName }}</div>
|
|
@@ -183,7 +186,7 @@ const surfaces = [
|
|
|
183
186
|
<div class="text-sm text-muted-foreground tabular-nums text-right">
|
|
184
187
|
{{ r.durationMs == null ? "\u2026" : `${(r.durationMs / 1e3).toFixed(2)}s` }}
|
|
185
188
|
</div>
|
|
186
|
-
</
|
|
189
|
+
</NuxtLink>
|
|
187
190
|
<template v-if="latestLoading && !latestPerWorkflow.length">
|
|
188
191
|
<div
|
|
189
192
|
v-for="n in 6"
|
|
@@ -200,19 +203,19 @@ const surfaces = [
|
|
|
200
203
|
</div>
|
|
201
204
|
</template>
|
|
202
205
|
<div v-else-if="!latestPerWorkflow.length" class="px-3 py-8 text-center text-sm text-muted-foreground">
|
|
203
|
-
|
|
206
|
+
{{ t("integration.dashboard.runs.empty") }}
|
|
204
207
|
</div>
|
|
205
208
|
</div>
|
|
206
209
|
</div>
|
|
207
210
|
|
|
208
211
|
<!-- Needs attention -->
|
|
209
|
-
<div class="rounded-lg border border-black/[0.06] bg-white">
|
|
212
|
+
<div role="group" :aria-label="t('integration.dashboard.attention.title')" class="rounded-lg border border-black/[0.06] bg-white">
|
|
210
213
|
<div class="flex items-center justify-between border-b border-black/[0.06] px-3 py-2">
|
|
211
214
|
<div class="flex items-center gap-2">
|
|
212
215
|
<span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
213
216
|
<AlertTriangle :size="12" />
|
|
214
217
|
</span>
|
|
215
|
-
<h3 class="text-sm font-semibold">
|
|
218
|
+
<h3 class="text-sm font-semibold">{{ t("integration.dashboard.attention.title") }}</h3>
|
|
216
219
|
</div>
|
|
217
220
|
</div>
|
|
218
221
|
<div v-if="(missedLoading || latestLoading) && !missedRuns && !failedRecent.length" class="space-y-2 p-3">
|
|
@@ -225,28 +228,32 @@ const surfaces = [
|
|
|
225
228
|
to="/integrations/runs?tab=missed"
|
|
226
229
|
class="block space-y-1 border-b border-black/[0.04] px-3 py-2 text-sm hover:bg-muted/30"
|
|
227
230
|
>
|
|
228
|
-
<div class="font-medium">{{ missedRuns
|
|
231
|
+
<div class="font-medium">{{ t("integration.dashboard.attention.missedRuns", missedRuns) }}</div>
|
|
229
232
|
<div class="text-sm text-amber-700">
|
|
230
|
-
|
|
233
|
+
{{ t("integration.dashboard.attention.missedRuns.hint") }}
|
|
231
234
|
</div>
|
|
232
235
|
</NuxtLink>
|
|
233
|
-
|
|
236
|
+
<!-- A link, for the same reason as the entries next door — and like
|
|
237
|
+
the waiting-firings entry above it, which was one all along. -->
|
|
238
|
+
<NuxtLink
|
|
234
239
|
v-for="r in failedRecent"
|
|
235
240
|
:key="r.id"
|
|
236
|
-
|
|
237
|
-
|
|
241
|
+
:to="`/integrations/workflows/${r.workflowId}`"
|
|
242
|
+
class="block space-y-1 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 hover:bg-muted/30"
|
|
238
243
|
>
|
|
239
244
|
<div class="font-medium truncate">{{ r.workflowName }}</div>
|
|
240
245
|
<div class="text-sm text-rose-700 truncate" :title="r.errorMessage">
|
|
241
|
-
{{ r.errorMessage ?? "
|
|
246
|
+
{{ r.errorMessage ?? t("integration.dashboard.attention.failed") }}
|
|
242
247
|
</div>
|
|
248
|
+
<!-- When it failed is when it ended, not when it set off: a run
|
|
249
|
+
that broke after an hour would otherwise be dated an hour early. -->
|
|
243
250
|
<div class="text-sm text-muted-foreground tabular-nums">
|
|
244
|
-
{{ formatRelative(r.startedAt) }}
|
|
251
|
+
{{ formatRelative(r.finishedAt ?? r.startedAt) }}
|
|
245
252
|
</div>
|
|
246
|
-
</
|
|
253
|
+
</NuxtLink>
|
|
247
254
|
</div>
|
|
248
255
|
<div v-else class="px-3 py-6 text-center text-sm text-muted-foreground">
|
|
249
|
-
|
|
256
|
+
{{ t("integration.dashboard.attention.healthy") }}
|
|
250
257
|
</div>
|
|
251
258
|
</div>
|
|
252
259
|
</div>
|
|
@@ -272,49 +279,41 @@ const surfaces = [
|
|
|
272
279
|
</NuxtLink>
|
|
273
280
|
</div>
|
|
274
281
|
|
|
275
|
-
<!--
|
|
276
|
-
|
|
282
|
+
<!-- Named for the offer it makes, not for a ranking nothing measures
|
|
283
|
+
(PO-238): every template's install count was nought, so the sort that
|
|
284
|
+
produced this list compared nothing at all. The wording deliberately
|
|
285
|
+
echoes the create-workflow dialog's "Start from template" tab — the same
|
|
286
|
+
offer, so the same words, rather than a second name for it. -->
|
|
287
|
+
<div role="group" :aria-label="t('integration.dashboard.templates.title')" class="rounded-lg border border-black/[0.06] bg-white">
|
|
277
288
|
<div class="flex items-center justify-between border-b border-black/[0.06] px-3 py-2">
|
|
278
289
|
<div class="flex items-center gap-2">
|
|
279
290
|
<span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
280
291
|
<Sparkles :size="12" />
|
|
281
292
|
</span>
|
|
282
|
-
<h3 class="text-sm font-semibold">
|
|
293
|
+
<h3 class="text-sm font-semibold">{{ t("integration.dashboard.templates.title") }}</h3>
|
|
283
294
|
</div>
|
|
284
295
|
<NuxtLink to="/integrations/templates" class="text-sm text-muted-foreground hover:text-foreground inline-flex items-center gap-1">
|
|
285
|
-
|
|
296
|
+
{{ t("integration.common.action.seeAll") }} <ArrowRight :size="11" />
|
|
286
297
|
</NuxtLink>
|
|
287
298
|
</div>
|
|
288
|
-
<div v-if="templatesLoading && !
|
|
299
|
+
<div v-if="templatesLoading && !startingPoints.length" class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
289
300
|
<div v-for="n in 4" :key="`sk-tpl-${n}`" class="rounded-md border border-black/[0.06] bg-white p-3 space-y-2">
|
|
290
301
|
<div class="h-5 w-20 rounded bg-black/[0.07] animate-pulse" />
|
|
291
302
|
<div class="h-3 w-3/4 rounded bg-black/[0.07] animate-pulse" />
|
|
292
303
|
<div class="h-3 w-1/2 rounded bg-black/[0.05] animate-pulse" />
|
|
293
304
|
</div>
|
|
294
305
|
</div>
|
|
295
|
-
<div v-else-if="!
|
|
296
|
-
|
|
306
|
+
<div v-else-if="!startingPoints.length" class="px-3 py-6 text-center text-sm text-muted-foreground">
|
|
307
|
+
{{ t("integration.dashboard.templates.empty") }}
|
|
297
308
|
</div>
|
|
298
309
|
<div v-else class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
299
|
-
<
|
|
300
|
-
v-for="tpl in
|
|
310
|
+
<TemplateCard
|
|
311
|
+
v-for="tpl in startingPoints"
|
|
301
312
|
:key="tpl.slug"
|
|
313
|
+
:template="tpl"
|
|
314
|
+
density="compact"
|
|
302
315
|
:to="`/integrations/templates/${tpl.slug}`"
|
|
303
|
-
|
|
304
|
-
>
|
|
305
|
-
<div class="relative h-20 shrink-0 border-b border-black/[0.06] bg-slate-50/70 [background-image:radial-gradient(circle,rgba(15,23,42,0.07)_1px,transparent_1px)] [background-size:12px_12px]">
|
|
306
|
-
<TemplateFlowPreview v-if="tpl.detailLoaded" :blob="tpl.blob" :height="80" class="px-1" />
|
|
307
|
-
<span class="absolute left-1.5 top-1.5 inline-flex items-center whitespace-nowrap rounded-full bg-slate-100 px-1.5 py-px text-[11px] font-medium uppercase text-slate-700">
|
|
308
|
-
{{ tpl.category }}
|
|
309
|
-
</span>
|
|
310
|
-
</div>
|
|
311
|
-
<div class="flex min-h-0 flex-1 flex-col p-3">
|
|
312
|
-
<div class="text-sm font-medium truncate">{{ tpl.name }}</div>
|
|
313
|
-
<p class="mt-1 text-sm text-muted-foreground line-clamp-2">
|
|
314
|
-
{{ tpl.description }}
|
|
315
|
-
</p>
|
|
316
|
-
</div>
|
|
317
|
-
</NuxtLink>
|
|
316
|
+
/>
|
|
318
317
|
</div>
|
|
319
318
|
</div>
|
|
320
319
|
</div>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as
|
|
2
|
+
import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
3
3
|
import { Badge, Button } from "@revenexx/studio";
|
|
4
4
|
import { toast } from "vue-sonner";
|
|
5
5
|
import RunsTable from "../../components/Integrations/RunsTable.vue";
|
|
6
|
+
import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
6
7
|
import { statusClass } from "../../utils/statusStyle";
|
|
7
|
-
|
|
8
|
+
const t = useIntegrationsT();
|
|
9
|
+
useHead({ title: () => t("integration.runs.list.head.title") });
|
|
8
10
|
const route = useRoute();
|
|
9
11
|
const { runs, loading, load } = useIntegrationsRuns();
|
|
10
12
|
const {
|
|
@@ -17,18 +19,28 @@ const {
|
|
|
17
19
|
} = useIntegrationsDeadLetters();
|
|
18
20
|
const tab = ref(route.query.tab === "missed" ? "missed" : "runs");
|
|
19
21
|
const tabs = computed(() => [
|
|
20
|
-
{ key: "runs", label: "
|
|
22
|
+
{ key: "runs", label: t("integration.runs.tabs.runs.label") },
|
|
21
23
|
// "Missed runs": trigger firings that never became a run (dead letters).
|
|
22
|
-
{ key: "missed", label: "
|
|
24
|
+
{ key: "missed", label: t("integration.deadLetters.tabs.missed.label"), badge: pendingCount.value || null }
|
|
23
25
|
]);
|
|
26
|
+
const POLL_MS = 3e3;
|
|
24
27
|
const hasActiveRuns = computed(() => runs.value.some((r) => r.status === "running" || r.status === "queued"));
|
|
28
|
+
const { delivering, runTick, workflowTick } = useIntegrationsRealtime();
|
|
29
|
+
const budget = createPollBudget({ intervalMs: POLL_MS, liveIntervalMs: SAFETY_POLL_MS });
|
|
30
|
+
function reload() {
|
|
31
|
+
budget.spend();
|
|
32
|
+
void load(true);
|
|
33
|
+
}
|
|
34
|
+
watch([runTick, workflowTick], () => {
|
|
35
|
+
reload();
|
|
36
|
+
});
|
|
25
37
|
let pollTimer = null;
|
|
26
38
|
onMounted(() => {
|
|
27
39
|
void load(true);
|
|
28
40
|
void loadGlobal(true);
|
|
29
41
|
pollTimer = setInterval(() => {
|
|
30
|
-
if (hasActiveRuns.value) void load(true);
|
|
31
|
-
},
|
|
42
|
+
if (hasActiveRuns.value && !loading.value && budget.due(delivering.value)) void load(true);
|
|
43
|
+
}, POLL_MS);
|
|
32
44
|
});
|
|
33
45
|
onUnmounted(() => {
|
|
34
46
|
if (pollTimer) {
|
|
@@ -42,11 +54,11 @@ async function onReplay(dl) {
|
|
|
42
54
|
try {
|
|
43
55
|
const res = await replay(dl.id);
|
|
44
56
|
const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
|
|
45
|
-
toast.success(newId ?
|
|
57
|
+
toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
|
|
46
58
|
await loadGlobal(true);
|
|
47
|
-
|
|
59
|
+
reload();
|
|
48
60
|
} catch (e) {
|
|
49
|
-
toast.error(e?.message ?? "
|
|
61
|
+
toast.error(e?.message ?? t("integration.deadLetters.toast.replay.error"));
|
|
50
62
|
} finally {
|
|
51
63
|
busyId.value = null;
|
|
52
64
|
}
|
|
@@ -55,10 +67,10 @@ async function onDiscard(dl) {
|
|
|
55
67
|
busyId.value = dl.id;
|
|
56
68
|
try {
|
|
57
69
|
await discard(dl.id);
|
|
58
|
-
toast.success("
|
|
70
|
+
toast.success(t("integration.deadLetters.toast.discard.done"));
|
|
59
71
|
await loadGlobal(true);
|
|
60
72
|
} catch (e) {
|
|
61
|
-
toast.error(e?.message ?? "
|
|
73
|
+
toast.error(e?.message ?? t("integration.deadLetters.toast.discard.error"));
|
|
62
74
|
} finally {
|
|
63
75
|
busyId.value = null;
|
|
64
76
|
}
|
|
@@ -76,25 +88,25 @@ function fmt(iso) {
|
|
|
76
88
|
<template>
|
|
77
89
|
<IntegrationsLayout>
|
|
78
90
|
<PageHeader
|
|
79
|
-
title="
|
|
80
|
-
description="
|
|
91
|
+
:title="t('integration.runs.list.header.title')"
|
|
92
|
+
:description="t('integration.runs.list.header.description')"
|
|
81
93
|
/>
|
|
82
94
|
|
|
83
95
|
<TabBar v-model="tab" :tabs="tabs" class="px-4" />
|
|
84
96
|
|
|
85
97
|
<div class="p-4 space-y-4">
|
|
86
|
-
<RunsTable v-if="tab === 'runs'" :runs="runs" :loading="loading" @changed="
|
|
98
|
+
<RunsTable v-if="tab === 'runs'" :runs="runs" :loading="loading" @changed="reload" />
|
|
87
99
|
|
|
88
100
|
<!-- Missed runs — trigger firings that never produced a run (the
|
|
89
101
|
workflow wasn't runnable when its schedule/event fired). -->
|
|
90
102
|
<template v-else>
|
|
91
103
|
<div class="flex items-center justify-between">
|
|
92
104
|
<p class="text-sm text-muted-foreground">
|
|
93
|
-
|
|
105
|
+
{{ t("integration.deadLetters.list.description") }}
|
|
94
106
|
</p>
|
|
95
107
|
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() => loadGlobal(true)">
|
|
96
108
|
<Loader2 v-if="missedLoading" :size="12" class="animate-spin" />
|
|
97
|
-
|
|
109
|
+
{{ t("integration.common.action.refresh") }}
|
|
98
110
|
</Button>
|
|
99
111
|
</div>
|
|
100
112
|
|
|
@@ -109,7 +121,7 @@ function fmt(iso) {
|
|
|
109
121
|
</div>
|
|
110
122
|
</div>
|
|
111
123
|
<div v-else-if="!missed.length" class="rounded-lg border border-black/[0.06] bg-white p-6 text-center text-sm text-muted-foreground">
|
|
112
|
-
|
|
124
|
+
{{ t("integration.deadLetters.state.empty") }}
|
|
113
125
|
</div>
|
|
114
126
|
|
|
115
127
|
<div v-else class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
|
|
@@ -123,7 +135,7 @@ function fmt(iso) {
|
|
|
123
135
|
<NuxtLink :to="`/integrations/workflows/${dl.workflow_id}`" class="text-sm font-medium truncate transition-colors hover:text-primary">
|
|
124
136
|
{{ dl.workflowName }}
|
|
125
137
|
</NuxtLink>
|
|
126
|
-
<div class="text-sm break-words">{{ dl.reason || "
|
|
138
|
+
<div class="text-sm break-words">{{ dl.reason || t("integration.deadLetters.reason.fallback") }}</div>
|
|
127
139
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
|
|
128
140
|
<span>#{{ dl.id }}</span>
|
|
129
141
|
<span v-if="dl.source">{{ dl.source }}</span>
|
|
@@ -135,11 +147,11 @@ function fmt(iso) {
|
|
|
135
147
|
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="busyId === dl.id" @click="onReplay(dl)">
|
|
136
148
|
<Loader2 v-if="busyId === dl.id" :size="12" class="animate-spin" />
|
|
137
149
|
<ReplayIcon v-else :size="12" />
|
|
138
|
-
|
|
150
|
+
{{ t("integration.deadLetters.action.replay") }}
|
|
139
151
|
</Button>
|
|
140
152
|
<Button variant="ghost" size="sm" class="h-7 text-sm text-rose-600 hover:bg-rose-500/10" :disabled="busyId === dl.id" @click="onDiscard(dl)">
|
|
141
|
-
<
|
|
142
|
-
|
|
153
|
+
<TrashBin :size="12" />
|
|
154
|
+
{{ t("integration.deadLetters.action.discard") }}
|
|
143
155
|
</Button>
|
|
144
156
|
</div>
|
|
145
157
|
</div>
|