@revenexx/studio-integrations 0.6.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +643 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +32 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +45 -38
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +21 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +30 -16
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -8
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +45 -42
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +315 -221
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +37 -10
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +40 -35
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +82 -34
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +18 -8
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +22 -12
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +41 -39
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +131 -87
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +27 -24
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +76 -46
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -13
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +79 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +14 -2
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +21 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +30 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +4 -2
- package/dist/runtime/data/integrationNodeCatalog.d.ts +63 -1
- package/dist/runtime/data/integrationNodeCatalog.js +22 -4
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1224 -0
- package/dist/runtime/i18n/messages/en.json +1224 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +73 -52
- package/dist/runtime/pages/integrations/index.vue +66 -73
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +89 -72
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +351 -197
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +25 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +30 -2
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +52 -5
- package/dist/runtime/utils/localize.js +47 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* The Studio's single Talkback subscriber.
|
|
4
|
+
*
|
|
5
|
+
* The integrations service publishes its run, workflow and bundle-build lifecycle
|
|
6
|
+
* onto the platform event bus (`App\Enums\OutboxTopic` → `event_outbox` → the relay
|
|
7
|
+
* → NATS → the Talkback bridge → Centrifugo), so a browser can learn that a run
|
|
8
|
+
* finished — or that a build is done — instead of asking every three seconds. This
|
|
9
|
+
* is the browser end of that chain, and everything it hands out is a **refetch
|
|
10
|
+
* signal**: the envelope carries no run payload — the bus has a 1 MB cap — so "the
|
|
11
|
+
* data arrived over the socket" was never on offer, and every surface still reloads
|
|
12
|
+
* through its own loader.
|
|
13
|
+
*
|
|
14
|
+
* ONE SUBSCRIBER FOR THE WHOLE APPLICATION, and this is the part that is not a
|
|
15
|
+
* style preference. `talkback-js` deduplicates on `envelope.id` **across the whole
|
|
16
|
+
* client**, before it dispatches to a channel's listeners (`src/core/talkback.ts`,
|
|
17
|
+
* `seen.admit`). The bridge publishes every event twice — once to the action
|
|
18
|
+
* channel, once to the resource channel, which is contractual (ADR-0094) — so if
|
|
19
|
+
* the runs grid took the action channel and an open detail panel took the resource
|
|
20
|
+
* channel, only whichever arrived first would fire and the other would silently
|
|
21
|
+
* never refetch. Two components cannot each own a subscription to the same event.
|
|
22
|
+
* So the channels are owned here, once, and the surfaces watch the signals below.
|
|
23
|
+
*
|
|
24
|
+
* ONLY ACTION CHANNELS, for the same reason: subscribing to just one of the two
|
|
25
|
+
* channels an event lands on makes each event exactly one delivery, with nothing
|
|
26
|
+
* to lose the race against. A resource channel would buy a narrower filter that
|
|
27
|
+
* `lastRunEvent.topicId` already provides.
|
|
28
|
+
*
|
|
29
|
+
* ONE ACTION IS ONE CHANNEL. The action is the fourth topic segment and Centrifugo
|
|
30
|
+
* has no wildcards, so watching the five run actions costs five subscriptions, and
|
|
31
|
+
* the eleven topics the service emits cost eleven for the whole application. The
|
|
32
|
+
* three-segment form (`revenexx.integrations.run`) is the resource KIND channel,
|
|
33
|
+
* which the bridge never publishes to.
|
|
34
|
+
*
|
|
35
|
+
* SAFE WHEN REALTIME IS OFF, which is the production state in the cockpit until
|
|
36
|
+
* the edge is shown to pass a WebSocket upgrade (PE-382 T0.2). With no
|
|
37
|
+
* `talkbackHost` configured nothing is subscribed, `delivering` stays false, and
|
|
38
|
+
* every caller keeps the polling it had — as it does for a tenant that is
|
|
39
|
+
* configured but whose channels never carry anything.
|
|
40
|
+
*/
|
|
41
|
+
/** Mirrors `App\Enums\OutboxTopic` in `revenexx/integrations`. */
|
|
42
|
+
export declare const RUN_TOPIC = "revenexx.integrations.run";
|
|
43
|
+
export declare const WORKFLOW_TOPIC = "revenexx.integrations.workflow";
|
|
44
|
+
/**
|
|
45
|
+
* The bundle build. Its own entity rather than `workflow.build_started`, because
|
|
46
|
+
* the service's envelope allows no underscore in a topic segment and Console's
|
|
47
|
+
* manifest schema allows no hyphen — `build.started` is the only form that passes
|
|
48
|
+
* both. `topic_id` is still the WORKFLOW's id: a build has no row of its own.
|
|
49
|
+
*/
|
|
50
|
+
export declare const BUILD_TOPIC = "revenexx.integrations.build";
|
|
51
|
+
/** `run.cancelled` also carries a `terminated` status — the two collapse into one topic. */
|
|
52
|
+
export declare const RUN_ACTIONS: readonly ["created", "started", "finished", "failed", "cancelled"];
|
|
53
|
+
/**
|
|
54
|
+
* There is no `run.deleted`. Runs cascade with their workflow inside Postgres
|
|
55
|
+
* without firing model events, which is why `workflow.deleted` carries
|
|
56
|
+
* `cascades_runs: true` instead.
|
|
57
|
+
*/
|
|
58
|
+
export declare const WORKFLOW_ACTIONS: readonly ["created", "updated", "deleted"];
|
|
59
|
+
/**
|
|
60
|
+
* `started` is emitted per queue ATTEMPT, and the job has three: a failing build
|
|
61
|
+
* sends started/failed up to three times, with `data.attempt` (1-based) as the
|
|
62
|
+
* only way to tell a final failure from one that is about to be retried.
|
|
63
|
+
*/
|
|
64
|
+
export declare const BUILD_ACTIONS: readonly ["started", "succeeded", "failed"];
|
|
65
|
+
/** What a surface needs off an envelope. A structural subset of talkback-js's `Envelope`. */
|
|
66
|
+
export interface RealtimeEvent {
|
|
67
|
+
/** `evt_<ulid>`, the client's deduplication key. Carried for logging. */
|
|
68
|
+
id: string;
|
|
69
|
+
/** The fourth topic segment: `finished`, `deleted`, … */
|
|
70
|
+
action: string;
|
|
71
|
+
/** The run's or workflow's primary key, as a string. Never a number — see OutboxEnvelope. */
|
|
72
|
+
topicId: string | null;
|
|
73
|
+
/** The event's `data` map, e.g. `cascades_runs` on `workflow.deleted`. */
|
|
74
|
+
data: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface IntegrationsRealtime {
|
|
77
|
+
/**
|
|
78
|
+
* Whether this connection has actually reported something, and therefore
|
|
79
|
+
* whether a poll may take the long budget (`pollBudget.ts`).
|
|
80
|
+
*
|
|
81
|
+
* IT COUNTS DELIVERIES, NOT CONFIGURATION, and that is the whole reason it is
|
|
82
|
+
* not called `live`. Being subscribed is no evidence of anything: a channel
|
|
83
|
+
* resolves from the LOCAL subscription, and a tenant whose Console event
|
|
84
|
+
* catalogue does not mark these topics `channel: true` holds eleven channels
|
|
85
|
+
* that will never carry a byte (PE-375). Spending that state as a delivery
|
|
86
|
+
* signal would thin every run surface's refresh by ten with nothing arriving to
|
|
87
|
+
* make up for it — ten times worse than before any of this existed, silently.
|
|
88
|
+
* So the budget is only thinned once something has really been delivered.
|
|
89
|
+
*
|
|
90
|
+
* What it still cannot say is whether delivery is *continuing*: a socket that
|
|
91
|
+
* dies quietly after one event keeps reading true, because the per-channel
|
|
92
|
+
* confirmation that would settle it is not passed through the packages between
|
|
93
|
+
* Centrifugo and here (`docs/realtime.md`). That is why the poll backs off to a
|
|
94
|
+
* slow safety interval rather than stopping.
|
|
95
|
+
*/
|
|
96
|
+
delivering: ComputedRef<boolean>;
|
|
97
|
+
/** Bumped on every run event. Watch it to refetch a run list. */
|
|
98
|
+
runTick: Readonly<Ref<number>>;
|
|
99
|
+
/** Bumped on every workflow event. */
|
|
100
|
+
workflowTick: Readonly<Ref<number>>;
|
|
101
|
+
/**
|
|
102
|
+
* Bumped on every bundle-build event.
|
|
103
|
+
*
|
|
104
|
+
* Kept apart from `workflowTick` rather than folded into it: a build is the one
|
|
105
|
+
* workflow change that happens WITHOUT anybody editing the workflow, and it can
|
|
106
|
+
* fire six times for a single save (three attempts × started/failed). The runs
|
|
107
|
+
* grid watches `workflowTick` to catch a cascade-delete and has no business
|
|
108
|
+
* reloading for build progress.
|
|
109
|
+
*/
|
|
110
|
+
buildTick: Readonly<Ref<number>>;
|
|
111
|
+
/** The last run event, for a surface that only cares about one run's id. */
|
|
112
|
+
lastRunEvent: Readonly<Ref<RealtimeEvent | null>>;
|
|
113
|
+
lastWorkflowEvent: Readonly<Ref<RealtimeEvent | null>>;
|
|
114
|
+
/** The last build event. `topicId` is the workflow it belongs to. */
|
|
115
|
+
lastBuildEvent: Readonly<Ref<RealtimeEvent | null>>;
|
|
116
|
+
/** The subscribed channel names, for diagnostics. */
|
|
117
|
+
channels: ComputedRef<string[]>;
|
|
118
|
+
}
|
|
119
|
+
/** The shape a handler receives. Talkback's `Envelope` is assignable to it. */
|
|
120
|
+
export interface EnvelopeLike {
|
|
121
|
+
id?: unknown;
|
|
122
|
+
topic?: unknown;
|
|
123
|
+
topic_id?: unknown;
|
|
124
|
+
data?: unknown;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Narrows a publication to the fields a surface acts on.
|
|
128
|
+
*
|
|
129
|
+
* Everything is read defensively rather than cast. The envelope crosses four
|
|
130
|
+
* services to get here (`OutboxEmitter` → the relay → NATS → the bridge), and a
|
|
131
|
+
* surface that threw on an unexpected shape would take the page down over an event
|
|
132
|
+
* it could simply have ignored.
|
|
133
|
+
*
|
|
134
|
+
* `topic_id` is stringified on purpose: it is a string on the wire — an 18-digit id
|
|
135
|
+
* loses precision as a float64 above 2^53 — and `OutboxEnvelope` is careful about
|
|
136
|
+
* that, so nothing here should be the place that turns it back into a number.
|
|
137
|
+
*/
|
|
138
|
+
export declare function toRealtimeEvent(envelope: EnvelopeLike): RealtimeEvent;
|
|
139
|
+
/**
|
|
140
|
+
* The workflow a run or build event belongs to, or null.
|
|
141
|
+
*
|
|
142
|
+
* `WorkflowRunEventObserver`'s payload carries `workflow_id` beside `run_id`, which
|
|
143
|
+
* is what lets a workflow-scoped surface ignore every other workflow's runs — in a
|
|
144
|
+
* busy tenant that is the difference between one refetch and a refetch per run in
|
|
145
|
+
* the account. Null means "cannot tell", and a caller should refetch rather than
|
|
146
|
+
* assume the event was not for it.
|
|
147
|
+
*/
|
|
148
|
+
export declare function workflowIdOf(event: RealtimeEvent | null): string | null;
|
|
149
|
+
export declare function useIntegrationsRealtime(): IntegrationsRealtime;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { computed, effectScope, ref, shallowRef } from "vue";
|
|
2
|
+
export const RUN_TOPIC = "revenexx.integrations.run";
|
|
3
|
+
export const WORKFLOW_TOPIC = "revenexx.integrations.workflow";
|
|
4
|
+
export const BUILD_TOPIC = "revenexx.integrations.build";
|
|
5
|
+
export const RUN_ACTIONS = ["created", "started", "finished", "failed", "cancelled"];
|
|
6
|
+
export const WORKFLOW_ACTIONS = ["created", "updated", "deleted"];
|
|
7
|
+
export const BUILD_ACTIONS = ["started", "succeeded", "failed"];
|
|
8
|
+
export function toRealtimeEvent(envelope) {
|
|
9
|
+
const topic = typeof envelope.topic === "string" ? envelope.topic : "";
|
|
10
|
+
const topicId = envelope.topic_id;
|
|
11
|
+
const data = envelope.data;
|
|
12
|
+
return {
|
|
13
|
+
id: typeof envelope.id === "string" ? envelope.id : "",
|
|
14
|
+
action: topic.split(".")[3] ?? "",
|
|
15
|
+
topicId: topicId === null || topicId === void 0 ? null : String(topicId),
|
|
16
|
+
data: data !== null && typeof data === "object" ? data : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function workflowIdOf(event) {
|
|
20
|
+
const id = event?.data.workflow_id;
|
|
21
|
+
return typeof id === "string" && id ? id : null;
|
|
22
|
+
}
|
|
23
|
+
const runTick = ref(0);
|
|
24
|
+
const workflowTick = ref(0);
|
|
25
|
+
const buildTick = ref(0);
|
|
26
|
+
const lastRunEvent = shallowRef(null);
|
|
27
|
+
const lastWorkflowEvent = shallowRef(null);
|
|
28
|
+
const lastBuildEvent = shallowRef(null);
|
|
29
|
+
const deliveries = computed(() => runTick.value + workflowTick.value + buildTick.value);
|
|
30
|
+
let scope = null;
|
|
31
|
+
let openChannels = null;
|
|
32
|
+
function onRun(envelope) {
|
|
33
|
+
lastRunEvent.value = toRealtimeEvent(envelope);
|
|
34
|
+
runTick.value++;
|
|
35
|
+
}
|
|
36
|
+
function onWorkflow(envelope) {
|
|
37
|
+
lastWorkflowEvent.value = toRealtimeEvent(envelope);
|
|
38
|
+
workflowTick.value++;
|
|
39
|
+
}
|
|
40
|
+
function onBuild(envelope) {
|
|
41
|
+
lastBuildEvent.value = toRealtimeEvent(envelope);
|
|
42
|
+
buildTick.value++;
|
|
43
|
+
}
|
|
44
|
+
function onRunGap() {
|
|
45
|
+
lastRunEvent.value = null;
|
|
46
|
+
runTick.value++;
|
|
47
|
+
}
|
|
48
|
+
function onWorkflowGap() {
|
|
49
|
+
lastWorkflowEvent.value = null;
|
|
50
|
+
workflowTick.value++;
|
|
51
|
+
}
|
|
52
|
+
function onBuildGap() {
|
|
53
|
+
lastBuildEvent.value = null;
|
|
54
|
+
buildTick.value++;
|
|
55
|
+
}
|
|
56
|
+
export function useIntegrationsRealtime() {
|
|
57
|
+
const { talkback, enabled } = usePlatformTalkback();
|
|
58
|
+
if (enabled && import.meta.client && !scope) {
|
|
59
|
+
scope = effectScope(true);
|
|
60
|
+
scope.run(() => {
|
|
61
|
+
const subs = [
|
|
62
|
+
...RUN_ACTIONS.map((action) => useTalkbackTopic(`${RUN_TOPIC}.${action}`, { handler: onRun, onResync: onRunGap })),
|
|
63
|
+
...WORKFLOW_ACTIONS.map((action) => useTalkbackTopic(`${WORKFLOW_TOPIC}.${action}`, { handler: onWorkflow, onResync: onWorkflowGap })),
|
|
64
|
+
...BUILD_ACTIONS.map((action) => useTalkbackTopic(`${BUILD_TOPIC}.${action}`, { handler: onBuild, onResync: onBuildGap }))
|
|
65
|
+
];
|
|
66
|
+
openChannels = computed(() => subs.map((s) => s.channel.value).filter((c) => !!c));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const channels = computed(() => openChannels?.value ?? []);
|
|
70
|
+
return {
|
|
71
|
+
delivering: computed(() => deliveries.value > 0 && enabled && !!talkback.value && channels.value.length > 0),
|
|
72
|
+
runTick,
|
|
73
|
+
workflowTick,
|
|
74
|
+
buildTick,
|
|
75
|
+
lastRunEvent,
|
|
76
|
+
lastWorkflowEvent,
|
|
77
|
+
lastBuildEvent,
|
|
78
|
+
channels
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
5
|
export function useIntegrationsRevisions() {
|
|
5
6
|
const api = useIntegrationsApi();
|
|
7
|
+
const t = useIntegrationsT();
|
|
6
8
|
const loading = ref(false);
|
|
7
9
|
const error = ref(null);
|
|
8
10
|
function captureError(e, fallback) {
|
|
@@ -16,7 +18,7 @@ export function useIntegrationsRevisions() {
|
|
|
16
18
|
const res = await api.get(`/workflows/${workflowId}/revisions?page=${page}&per_page=${perPage}`);
|
|
17
19
|
return { revisions: res.data ?? [], meta: res.meta };
|
|
18
20
|
} catch (e) {
|
|
19
|
-
error.value = captureError(e, "
|
|
21
|
+
error.value = captureError(e, t("integration.revisions.error.loadFailed"));
|
|
20
22
|
throw e;
|
|
21
23
|
} finally {
|
|
22
24
|
loading.value = false;
|
|
@@ -25,12 +27,12 @@ export function useIntegrationsRevisions() {
|
|
|
25
27
|
async function get(workflowId, revision) {
|
|
26
28
|
const res = await api.get(`/workflows/${workflowId}/revisions/${revision}`);
|
|
27
29
|
const apiBlob = res.blob ?? {};
|
|
28
|
-
const triggers = (res.triggers ?? []).map((
|
|
29
|
-
id:
|
|
30
|
-
type:
|
|
31
|
-
name: typeof
|
|
32
|
-
active:
|
|
33
|
-
config:
|
|
30
|
+
const triggers = (res.triggers ?? []).map((t2) => ({
|
|
31
|
+
id: t2.handle ?? String(t2.id ?? ""),
|
|
32
|
+
type: t2.type,
|
|
33
|
+
name: typeof t2.name === "string" ? t2.name : t2.name ?? void 0,
|
|
34
|
+
active: t2.active,
|
|
35
|
+
config: t2.config ?? {}
|
|
34
36
|
}));
|
|
35
37
|
return {
|
|
36
38
|
...res,
|
|
@@ -24,7 +24,7 @@ export declare function useIntegrationsRuns(): {
|
|
|
24
24
|
}) => WorkflowRun[];
|
|
25
25
|
get: (id: string) => WorkflowRun | null;
|
|
26
26
|
fetchRun: (workflowId: number | string, runId: number | string, workflowName: string) => Promise<WorkflowRun>;
|
|
27
|
-
trigger: (workflowId: number | string, payload?: Record<string, unknown>, mode?: "
|
|
27
|
+
trigger: (workflowId: number | string, payload?: Record<string, unknown>, mode?: "async" | "sync") => Promise<unknown>;
|
|
28
28
|
cancelRun: (workflowId: number | string, runId: number | string, reason?: string) => Promise<unknown>;
|
|
29
29
|
terminateRun: (workflowId: number | string, runId: number | string, reason?: string) => Promise<unknown>;
|
|
30
30
|
retryRun: (workflowId: number | string, runId: number | string) => Promise<unknown>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
6
|
function mapStatus(status) {
|
|
@@ -33,6 +34,7 @@ export function useIntegrationsRuns() {
|
|
|
33
34
|
const gate = useLoadGate("integrations.runs", { loading, loaded });
|
|
34
35
|
const SYNC_LIMIT = 5;
|
|
35
36
|
const api = useIntegrationsApi();
|
|
37
|
+
const t = useIntegrationsT();
|
|
36
38
|
const { workflows, load: loadWorkflows, loaded: workflowsLoaded } = useIntegrationsWorkflows();
|
|
37
39
|
function mapRun(r, workflowName) {
|
|
38
40
|
const startedMs = r.started_at ? new Date(r.started_at).getTime() : null;
|
|
@@ -99,7 +101,7 @@ export function useIntegrationsRuns() {
|
|
|
99
101
|
}
|
|
100
102
|
loaded.value = workflowsLoaded.value && perWorkflow.every((p) => p.status === "fulfilled");
|
|
101
103
|
} catch (e) {
|
|
102
|
-
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? "
|
|
104
|
+
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? t("integration.runs.error.loadFailed");
|
|
103
105
|
} finally {
|
|
104
106
|
gate.release();
|
|
105
107
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ScheduleEntry } from '../types/integrations.js';
|
|
2
2
|
/**
|
|
3
|
-
* Derived view — every trigger of type `schedule` across all workflows,
|
|
4
|
-
*
|
|
3
|
+
* Derived view — every trigger of type `schedule` across all workflows, as
|
|
4
|
+
* `useIntegrationsTriggers` holds them: read out of the workflow list, which carries
|
|
5
|
+
* each workflow's triggers, rather than fetched per workflow (PO-236).
|
|
5
6
|
* Next/last-run timestamps are mocked deterministically (no telemetry
|
|
6
7
|
* endpoint yet).
|
|
7
8
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
6
|
const KEY_PATTERN = /^[A-Z0-9_]+$/;
|
|
@@ -10,6 +11,7 @@ export function useIntegrationsSecrets() {
|
|
|
10
11
|
const loaded = useState("integrations.secrets.loaded", () => false);
|
|
11
12
|
const gate = useLoadGate("integrations.secrets", { loading, loaded });
|
|
12
13
|
const api = useIntegrationsApi();
|
|
14
|
+
const t = useIntegrationsT();
|
|
13
15
|
function isValidKey(key) {
|
|
14
16
|
return KEY_PATTERN.test(key);
|
|
15
17
|
}
|
|
@@ -35,7 +37,7 @@ export function useIntegrationsSecrets() {
|
|
|
35
37
|
secrets.value = normalize(await api.get("/secrets"));
|
|
36
38
|
loaded.value = true;
|
|
37
39
|
} catch (e) {
|
|
38
|
-
error.value = captureError(e, "
|
|
40
|
+
error.value = captureError(e, t("integration.secrets.error.loadFailed"));
|
|
39
41
|
} finally {
|
|
40
42
|
gate.release();
|
|
41
43
|
}
|
|
@@ -47,7 +49,7 @@ export function useIntegrationsSecrets() {
|
|
|
47
49
|
}
|
|
48
50
|
async function upsert(key, value) {
|
|
49
51
|
if (!isValidKey(key)) {
|
|
50
|
-
throw new Error("
|
|
52
|
+
throw new Error(t("integration.secrets.error.invalidKey"));
|
|
51
53
|
}
|
|
52
54
|
const created = !secrets.value.some((s) => s.key === key);
|
|
53
55
|
await api.post("/secrets", { key, value });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { Translate } from '../i18n/translate.js';
|
|
3
|
+
/**
|
|
4
|
+
* `useIntegrationsT()` — the studio's own interface strings.
|
|
5
|
+
*
|
|
6
|
+
* const t = useIntegrationsT()
|
|
7
|
+
* t('integration.dashboard.runs.title')
|
|
8
|
+
* t('integration.dashboard.attention.missedRuns', missed.length)
|
|
9
|
+
* t('integration.credentials.toast.deleted', { name: credential.name })
|
|
10
|
+
*
|
|
11
|
+
* Returns `t` directly rather than `{ t }`, matching `useT()` in
|
|
12
|
+
* `@revenexx/studio-shared`. It is **not** that function: `useT()` asks the
|
|
13
|
+
* host's catalogue, which has no `integration.*` keys in it, so this module
|
|
14
|
+
* reads its own (`runtime/i18n/messages/*.json`) instead. The names differ on
|
|
15
|
+
* purpose — studio-shared registers a global auto-import called `useT`, and two
|
|
16
|
+
* modules claiming one name is a silent, one-wins collision.
|
|
17
|
+
*
|
|
18
|
+
* Not a composable in the reactive-state sense: no component instance is
|
|
19
|
+
* needed, so it may be called from a plain helper or outside `setup()`.
|
|
20
|
+
*
|
|
21
|
+
* **Reactivity is on the caller.** `t()` reads the language at call time, so
|
|
22
|
+
* anything evaluated once — a `const TABS = [...]` at module or setup scope —
|
|
23
|
+
* keeps whatever language was current when the file loaded. Put it in a
|
|
24
|
+
* `computed()`. That is the one way to ship this looking finished and have it
|
|
25
|
+
* not switch.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useIntegrationsT(): Translate;
|
|
28
|
+
/**
|
|
29
|
+
* The language the studio's chrome is rendering in, as a `ComputedRef`.
|
|
30
|
+
*
|
|
31
|
+
* For the rare case that needs the tag itself — an `Intl` format, a `lang`
|
|
32
|
+
* attribute — rather than a translated string. To *set* it, that is
|
|
33
|
+
* `useIntegrationsLocale()`, which is the host's seam.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useIntegrationsUiLocale(): ComputedRef<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { studioLocale } from "../utils/localize.js";
|
|
3
|
+
import { translate } from "../i18n/translate.js";
|
|
4
|
+
export function useIntegrationsT() {
|
|
5
|
+
return translate;
|
|
6
|
+
}
|
|
7
|
+
export function useIntegrationsUiLocale() {
|
|
8
|
+
return computed(() => studioLocale());
|
|
9
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TemplateRequirements, WorkflowTemplate } from '../types/integrations.js';
|
|
2
2
|
export declare function useIntegrationsTemplates(): {
|
|
3
|
-
templates:
|
|
3
|
+
templates: import("vue").ComputedRef<WorkflowTemplate[]>;
|
|
4
4
|
loaded: any;
|
|
5
5
|
loading: any;
|
|
6
6
|
error: any;
|
|
7
|
-
total: import("vue").ComputedRef<
|
|
8
|
-
startingPoints: import("vue").ComputedRef<
|
|
7
|
+
total: import("vue").ComputedRef<number>;
|
|
8
|
+
startingPoints: import("vue").ComputedRef<WorkflowTemplate[]>;
|
|
9
9
|
load: (force?: boolean) => Promise<WorkflowTemplate[]>;
|
|
10
10
|
list: () => WorkflowTemplate[];
|
|
11
11
|
get: (slug: string) => WorkflowTemplate | null;
|
|
@@ -84,7 +84,14 @@ function applyDetail(base, api) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
export function useIntegrationsTemplates() {
|
|
87
|
-
const
|
|
87
|
+
const t = useIntegrationsT();
|
|
88
|
+
const rawList = useState("integrations.templates.raw", () => []);
|
|
89
|
+
const rawDetails = useState("integrations.templates.rawDetails", () => ({}));
|
|
90
|
+
const templates = computed(() => rawList.value.map((item) => {
|
|
91
|
+
const base = mapListItem(item);
|
|
92
|
+
const detail = rawDetails.value[item.slug];
|
|
93
|
+
return detail ? applyDetail(base, detail) : base;
|
|
94
|
+
}));
|
|
88
95
|
const loaded = useState("integrations.templates.loaded", () => false);
|
|
89
96
|
const loading = useState("integrations.templates.loading", () => false);
|
|
90
97
|
const error = useState("integrations.templates.error", () => null);
|
|
@@ -101,11 +108,13 @@ export function useIntegrationsTemplates() {
|
|
|
101
108
|
error.value = null;
|
|
102
109
|
try {
|
|
103
110
|
const list2 = unwrap(await api.get("/templates")) ?? [];
|
|
104
|
-
|
|
111
|
+
rawList.value = list2;
|
|
112
|
+
rawDetails.value = {};
|
|
105
113
|
loaded.value = true;
|
|
106
114
|
} catch (e) {
|
|
107
|
-
error.value = captureError(e, "
|
|
108
|
-
|
|
115
|
+
error.value = captureError(e, t("integration.templates.state.error.fallback"));
|
|
116
|
+
rawList.value = [];
|
|
117
|
+
rawDetails.value = {};
|
|
109
118
|
loaded.value = true;
|
|
110
119
|
} finally {
|
|
111
120
|
gate.release();
|
|
@@ -116,16 +125,15 @@ export function useIntegrationsTemplates() {
|
|
|
116
125
|
return templates.value;
|
|
117
126
|
}
|
|
118
127
|
function get(slug) {
|
|
119
|
-
return templates.value.find((
|
|
128
|
+
return templates.value.find((t2) => t2.slug === slug) ?? null;
|
|
120
129
|
}
|
|
121
130
|
async function fetchDetail(slug) {
|
|
122
131
|
const base = get(slug);
|
|
123
132
|
if (!base?.fromApi) return base;
|
|
124
133
|
try {
|
|
125
134
|
const detail = unwrap(await api.get(`/templates/${slug}`));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return merged;
|
|
135
|
+
rawDetails.value = { ...rawDetails.value, [slug]: detail };
|
|
136
|
+
return get(slug);
|
|
129
137
|
} catch {
|
|
130
138
|
return base;
|
|
131
139
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { TriggerType } from '../types/integrations.js';
|
|
2
|
+
import type { ApiTriggerLite } from './useIntegrationsWorkflows.js';
|
|
2
3
|
/**
|
|
3
4
|
* Workflow triggers — a per-workflow sub-resource in the Integrations service
|
|
4
|
-
* (`/workflows/{id}/triggers`), NOT part of the workflow blob.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* (`/workflows/{id}/triggers`), NOT part of the workflow blob. `list()`, `create()`,
|
|
6
|
+
* `update()` and `remove()` address that sub-resource, one workflow at a time, which
|
|
7
|
+
* is how the editor's trigger settings work.
|
|
8
|
+
*
|
|
9
|
+
* `loadAll()` does NOT: the workflow list carries each workflow's triggers in full,
|
|
10
|
+
* so the tenant-wide view is derived from that response rather than fetched per
|
|
11
|
+
* workflow (PO-236). What it still costs is the workflow list, once.
|
|
7
12
|
*/
|
|
8
13
|
export interface ApiTrigger {
|
|
9
14
|
id: number;
|
|
@@ -26,6 +31,21 @@ export interface TriggerInput {
|
|
|
26
31
|
config?: Record<string, unknown> | null;
|
|
27
32
|
active?: boolean;
|
|
28
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The tenant's triggers, read off the workflow list rather than fetched per workflow
|
|
36
|
+
* (PO-236). Pure, and exported for that reason: it is the whole of what the tenant-wide
|
|
37
|
+
* view now does, and the composable around it needs a Nuxt app that the unit setup has
|
|
38
|
+
* no environment for.
|
|
39
|
+
*
|
|
40
|
+
* `byWorkflow` holds the triggers as the API sent them (`useIntegrationsWorkflows`
|
|
41
|
+
* keeps them beside the cockpit model, whose `id` is the handle). Two fields are not
|
|
42
|
+
* in there and are filled from the workflow the triggers arrived with: `workflow_id`,
|
|
43
|
+
* which every row id and route is built from, and `workflowName`, which the views show.
|
|
44
|
+
*/
|
|
45
|
+
export declare function tenantTriggers(workflows: {
|
|
46
|
+
id: number;
|
|
47
|
+
name: string;
|
|
48
|
+
}[], byWorkflow: Record<number, ApiTriggerLite[]>): TriggerWithWorkflow[];
|
|
29
49
|
export declare function useIntegrationsTriggers(): {
|
|
30
50
|
all: any;
|
|
31
51
|
loading: any;
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
2
3
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
3
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
|
+
export function tenantTriggers(workflows, byWorkflow) {
|
|
6
|
+
return workflows.flatMap((wf) => (byWorkflow[wf.id] ?? []).flatMap((t) => {
|
|
7
|
+
if (typeof t.id !== "number") return [];
|
|
8
|
+
return [{
|
|
9
|
+
...t,
|
|
10
|
+
id: t.id,
|
|
11
|
+
workflow_id: wf.id,
|
|
12
|
+
workflowName: wf.name,
|
|
13
|
+
handle: t.handle ?? "",
|
|
14
|
+
active: t.active ?? true
|
|
15
|
+
}];
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
4
18
|
export function useIntegrationsTriggers() {
|
|
5
19
|
const all = useState("integrations.triggers.all", () => []);
|
|
6
20
|
const loading = useState("integrations.triggers.loading", () => false);
|
|
7
21
|
const error = useState("integrations.triggers.error", () => null);
|
|
8
22
|
const loaded = useState("integrations.triggers.loaded", () => false);
|
|
9
23
|
const api = useIntegrationsApi();
|
|
10
|
-
const
|
|
24
|
+
const t = useIntegrationsT();
|
|
25
|
+
const { workflows, apiTriggers, load: loadWorkflows, loaded: workflowsLoaded, error: workflowsError } = useIntegrationsWorkflows();
|
|
11
26
|
const gate = useLoadGate("integrations.triggers", { loading, loaded });
|
|
12
27
|
function unwrapList(res) {
|
|
13
28
|
if (Array.isArray(res)) return res;
|
|
@@ -20,14 +35,12 @@ export function useIntegrationsTriggers() {
|
|
|
20
35
|
if (!await gate.claim(force)) return all.value;
|
|
21
36
|
error.value = null;
|
|
22
37
|
try {
|
|
23
|
-
await loadWorkflows();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
all.value = settled.flatMap((p) => p.status === "fulfilled" ? p.value : []);
|
|
28
|
-
loaded.value = workflowsLoaded.value && settled.every((p) => p.status === "fulfilled");
|
|
38
|
+
await loadWorkflows(force);
|
|
39
|
+
all.value = tenantTriggers(workflows.value, apiTriggers.value);
|
|
40
|
+
error.value = workflowsError.value;
|
|
41
|
+
loaded.value = workflowsLoaded.value && !workflowsError.value;
|
|
29
42
|
} catch (e) {
|
|
30
|
-
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? "
|
|
43
|
+
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? t("integration.triggers.error.loadFailed");
|
|
31
44
|
} finally {
|
|
32
45
|
gate.release();
|
|
33
46
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import type { Workflow, WorkflowBlob, WorkflowExecutionMode } from '../types/integrations.js';
|
|
1
|
+
import type { TriggerType, Workflow, WorkflowBlob, WorkflowExecutionMode } from '../types/integrations.js';
|
|
2
|
+
export interface ApiTriggerLite {
|
|
3
|
+
id?: number;
|
|
4
|
+
handle?: string;
|
|
5
|
+
type: TriggerType;
|
|
6
|
+
name?: string | Record<string, string> | null;
|
|
7
|
+
config?: Record<string, unknown> | null;
|
|
8
|
+
active?: boolean;
|
|
9
|
+
}
|
|
2
10
|
export declare function useIntegrationsWorkflows(): {
|
|
3
11
|
workflows: any;
|
|
4
12
|
loading: any;
|
|
5
13
|
error: any;
|
|
6
14
|
loaded: any;
|
|
15
|
+
apiTriggers: any;
|
|
7
16
|
total: import("vue").ComputedRef<any>;
|
|
8
17
|
activeCount: import("vue").ComputedRef<any>;
|
|
9
18
|
load: (force?: boolean) => Promise<Workflow[]>;
|