@revenexx/studio-integrations 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +29 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +16 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +15 -14
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
- package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
- package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +37 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +6 -4
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +3 -1
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.js +4 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +70 -52
- package/dist/runtime/pages/integrations/index.vue +50 -56
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +77 -68
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +18 -5
- package/dist/runtime/utils/localize.js +32 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,232 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f7ddd90: The run surfaces now update from pushed platform events, not only from their own
|
|
8
|
+
refresh.
|
|
9
|
+
|
|
10
|
+
Where a host has Talkback configured, the Runs list, a workflow's Runs tab, a run's
|
|
11
|
+
record and the replay canvas react to the run and workflow lifecycle the integrations
|
|
12
|
+
service publishes — so a run that finishes is on screen in about a second instead of
|
|
13
|
+
up to three, and a run started from the API, a schedule or an MCP tool appears on an
|
|
14
|
+
idle list that previously had no reason to ask. `workflow.deleted` also takes its runs
|
|
15
|
+
off the list, which no `run.deleted` event exists to do.
|
|
16
|
+
|
|
17
|
+
A run's record follows the same reports, and it now re-reads everything it shows when
|
|
18
|
+
the run it is open on ends — the status it displays comes from a different request
|
|
19
|
+
than the one an ending run used to trigger, so a record left open on a finishing run
|
|
20
|
+
could keep showing the state before it ended. That is fixed for the reported route;
|
|
21
|
+
the three-second poll behind it is unchanged.
|
|
22
|
+
|
|
23
|
+
While events are actually arriving, each surface's own refresh slows to every 30
|
|
24
|
+
seconds and stays as the backstop rather than stopping — the connection signal
|
|
25
|
+
available today cannot tell a quiet socket from a dead one, so a list that stopped
|
|
26
|
+
converging was not a trade worth making. **A connection that reports nothing is
|
|
27
|
+
treated as no connection**: the slower refresh is taken only once something has been
|
|
28
|
+
delivered, so a tenant whose event catalogue does not release these topics to browsers
|
|
29
|
+
keeps the cadence it always had rather than becoming the slowest configuration of the
|
|
30
|
+
three. If the connection drops and comes back having missed more than the platform can
|
|
31
|
+
replay, every surface reads again instead of waiting for a report that no longer
|
|
32
|
+
exists.
|
|
33
|
+
|
|
34
|
+
With no Talkback host configured — the default — nothing is subscribed, neither
|
|
35
|
+
`@revenexx/talkback-js` nor `centrifuge` is loaded, and every surface polls exactly as
|
|
36
|
+
before.
|
|
37
|
+
|
|
38
|
+
**This raises the floor for `@revenexx/studio-shared` to `^0.3.0`**, which is the
|
|
39
|
+
release that carries the realtime seam. A host installing this module gets the newer
|
|
40
|
+
one automatically; a host that pins studio-shared itself has to move with it, or it
|
|
41
|
+
will end up with two copies and no realtime at all.
|
|
42
|
+
|
|
43
|
+
Realtime needs `@revenexx/talkback-js` and `centrifuge` in the host application —
|
|
44
|
+
they are optional peers of studio-shared, so a host that never sets a Talkback host
|
|
45
|
+
installs neither.
|
|
46
|
+
|
|
47
|
+
- 9ef81b8: The studio reads in the language your application chose, both halves of the
|
|
48
|
+
screen at once.
|
|
49
|
+
|
|
50
|
+
Until now every surface was English. Two things change that. The text the node
|
|
51
|
+
packages publish — a node's name, what it is for, the label and example of every
|
|
52
|
+
setting it offers, a credential type's fields, a template's whole description —
|
|
53
|
+
has been shipping in German as well as English all along, and was being discarded
|
|
54
|
+
on the way to the screen; it is now resolved in the reader's language. And the
|
|
55
|
+
studio's own chrome — headings, buttons, tabs, table headers, empty states,
|
|
56
|
+
toasts, confirmations — is read from a message catalogue the module carries
|
|
57
|
+
itself, in English and German.
|
|
58
|
+
|
|
59
|
+
You do not need an i18n library installed, and the module never reads your
|
|
60
|
+
catalogue, a cookie or a browser setting: the language comes from you. Wire one
|
|
61
|
+
of two ways.
|
|
62
|
+
|
|
63
|
+
A language that does not change, in `nuxt.config.ts`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
export default defineNuxtConfig({
|
|
67
|
+
modules: ["@revenexx/studio-integrations"],
|
|
68
|
+
studioIntegrations: { locale: "de" },
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Deployments can override that with `NUXT_PUBLIC_STUDIO_INTEGRATIONS_LOCALE`
|
|
73
|
+
without a rebuild.
|
|
74
|
+
|
|
75
|
+
A language the user switches while the app is running, from a plugin of yours:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const { setLocale } = useIntegrationsLocale();
|
|
79
|
+
watch(hostLocale, (l) => setLocale(l), { immediate: true });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`useIntegrationsLocale()` is auto-imported like every other composable here, so
|
|
83
|
+
you import nothing. A switch reaches surfaces that are already open — no reload,
|
|
84
|
+
no refetch, no reopening a dialog. Both routes may be left alone: with neither
|
|
85
|
+
wired the studio is English, exactly as before, so upgrading changes nothing
|
|
86
|
+
until you ask it to.
|
|
87
|
+
|
|
88
|
+
A tag is BCP-47 and a region falls back to its language, so `de-CH` reads German
|
|
89
|
+
rather than English; anything the catalogue does not carry reads English.
|
|
90
|
+
|
|
91
|
+
**This expects a client-rendered host.** The language is held once per module
|
|
92
|
+
instance, which is once per browser tab on the client and once per process on a
|
|
93
|
+
server. If you render the studio server-side _and_ give each visitor their own
|
|
94
|
+
language, one visitor's choice can reach another's page — a page in the wrong
|
|
95
|
+
language, nothing more, since the value only chooses between the translations of a
|
|
96
|
+
string. A single fixed language is unaffected either way. Both routes above are
|
|
97
|
+
safe in a client-rendered app, which is what the known consumers are.
|
|
98
|
+
|
|
99
|
+
Four English strings changed spelling, to British. One of them is an accessible
|
|
100
|
+
name: the grey swatch in the sticky-note and group-frame colour pickers is
|
|
101
|
+
`Grey`, not `Gray` — update it if a test of yours addresses it by name. The other
|
|
102
|
+
three are wording only: two tooltips on the sample-payload button say `catalogue`,
|
|
103
|
+
and the workflow settings dialog says `Execution behaviour`.
|
|
104
|
+
|
|
105
|
+
- 7cb9f1c: The studio's lists narrow for real.
|
|
106
|
+
|
|
107
|
+
Credentials, runs, workflows and schedules each carried a filter button whose menu
|
|
108
|
+
read "No filters defined." — it could never filter anything. Each now narrows by the
|
|
109
|
+
dimensions its own rows already show: a credential's state and system, a run's
|
|
110
|
+
outcome and what started it, a workflow's position, build and dispatch mode, and
|
|
111
|
+
whether a schedule is live plus the timezone it fires in. Two choices narrow
|
|
112
|
+
together, each has a way back to everything, and the words on the menu are the words
|
|
113
|
+
on the rows.
|
|
114
|
+
|
|
115
|
+
Secrets loses the button instead: a key and two dates have nothing categorical to
|
|
116
|
+
narrow by, so the honest outcome there is not offering it.
|
|
117
|
+
|
|
118
|
+
A narrowing applies to the rows the page has loaded. On the lists collected per
|
|
119
|
+
workflow that is as far back as the page has read.
|
|
120
|
+
|
|
121
|
+
- 8f332af: Deleting is drawn with one bin everywhere, duplicating with one pair of sheets, and a
|
|
122
|
+
trigger's delete button says what it deletes.
|
|
123
|
+
|
|
124
|
+
The studio drew the delete action with three different bin glyphs, two of them one
|
|
125
|
+
click apart on the same node: a node's hover toolbar used Lucide's bin while that
|
|
126
|
+
node's own inspector used Solar's, and the credential surfaces used a third, fuller
|
|
127
|
+
Solar bin. Nothing was broken — what it cost is recognition, on the one action that
|
|
128
|
+
cannot be undone. Every delete and remove in the studio now draws Solar's
|
|
129
|
+
`TrashBinMinimalistic`.
|
|
130
|
+
|
|
131
|
+
The copy sheets sat one button along and are folded in: a node's hover toolbar drew
|
|
132
|
+
Lucide's glyph for **Duplicate** while that same node's inspector drew Solar's for
|
|
133
|
+
**Copy**. Both are Solar's now.
|
|
134
|
+
|
|
135
|
+
Two things a host or a test may notice:
|
|
136
|
+
|
|
137
|
+
- **The trigger card's delete button has an accessible name**, `Delete trigger`,
|
|
138
|
+
where it previously fell back to its `Delete` tooltip. That is what the node card's
|
|
139
|
+
`Delete node` already did, so the two can now be addressed and heard the same way.
|
|
140
|
+
- **No delete icon is imported under another library's name any more.** The alias
|
|
141
|
+
`TrashBinMinimalistic as Trash2` is what hid the divergence — two components read
|
|
142
|
+
identically in the markup and differed only in an import line. The bin travels as
|
|
143
|
+
`TrashBin`.
|
|
144
|
+
|
|
145
|
+
Why Solar, what `lucide-vue-next` is still for, and why the glyph itself is not
|
|
146
|
+
promised in a spec: `docs/icons.md`. It also names what is left — five shapes the
|
|
147
|
+
studio still draws from both libraries — and the ticket that carries them.
|
|
148
|
+
|
|
149
|
+
- fa0ef1a: The studio navigation stays when a workflow is opened, and the canvas gets its
|
|
150
|
+
width only when the operator asks.
|
|
151
|
+
|
|
152
|
+
Until now, opening a workflow — or one of its runs — folded the navigation away on
|
|
153
|
+
mount, and nothing anywhere brought it back: the way on was the breadcrumb or the
|
|
154
|
+
browser's back button. That fold is gone. The rail now stands on every surface,
|
|
155
|
+
including a workflow's canvas, and a control at the head of the workflow page,
|
|
156
|
+
reading **Hide navigation**, hands its width to the canvas when the operator wants
|
|
157
|
+
it. The same control then reads **Show navigation** and gives it back, without
|
|
158
|
+
leaving the surface. The choice is remembered per browser, so it is stated once
|
|
159
|
+
rather than on every visit, and it applies to the canvas alone — a run's record,
|
|
160
|
+
the run lists and the earlier versions keep the navigation either way.
|
|
161
|
+
|
|
162
|
+
Two things to know if you host this module:
|
|
163
|
+
|
|
164
|
+
- **A folded rail is now `inert` and `aria-hidden`.** It used to be clipped to zero
|
|
165
|
+
width with its links still focusable, so a keyboard walked into a navigation
|
|
166
|
+
nobody could see. If a test of yours reaches into the collapsed sidebar, it will
|
|
167
|
+
no longer find it in the accessibility tree — which is the point.
|
|
168
|
+
- **The fold is still driven by `useIntegrationsFocus()`**, unchanged, so a host
|
|
169
|
+
shell that reads `focused` keeps working as before. What changed is who turns it
|
|
170
|
+
on: nothing does it by itself any more. If your own shell collapses the sidebar
|
|
171
|
+
on that state, make sure the surface doing so also offers the way back — the
|
|
172
|
+
control belongs to the page, because a host shell can be replaced and the module's
|
|
173
|
+
cannot reach into it.
|
|
174
|
+
|
|
175
|
+
Two new accessible names, in English and German: `Hide navigation` /
|
|
176
|
+
`Show navigation`.
|
|
177
|
+
|
|
178
|
+
### Patch Changes
|
|
179
|
+
|
|
180
|
+
- 30f5bda: A group frame now tells the truth about what it holds as soon as a member is deleted.
|
|
181
|
+
|
|
182
|
+
Deleting a node out of a group left the frame printing the count it had before —
|
|
183
|
+
`Invoice sync · 3 nodes` over two remaining nodes — until the page was reloaded. The
|
|
184
|
+
saved workflow was correct throughout; it was the caption that lagged. The larger
|
|
185
|
+
half of the same defect: deleting a frame's last member left the frame drawn on the
|
|
186
|
+
canvas over a workflow that no longer contained it, so the editor showed a group
|
|
187
|
+
that had already ceased to exist.
|
|
188
|
+
|
|
189
|
+
Both follow the deletion at once now. An empty frame you drew yourself still stays —
|
|
190
|
+
it counts down to `0 nodes` and waits to be filled.
|
|
191
|
+
|
|
192
|
+
The cause was timing rather than arithmetic: the canvas rebuild ran inside Vue Flow's
|
|
193
|
+
own change pass, which left the rendered graph on the array from before the deletion.
|
|
194
|
+
It is deferred by one tick. Nothing about the API, the saved blob or the keyboard
|
|
195
|
+
changes.
|
|
196
|
+
|
|
197
|
+
- d690841: A note on a read-only canvas no longer offers controls that do nothing.
|
|
198
|
+
|
|
199
|
+
On the run replay and the revision preview, a sticky note used to be drawn with
|
|
200
|
+
the toolbar it has in the editor — a bin, seven colours, a corner handle — and
|
|
201
|
+
could still be double-clicked into a text field. None of it had any effect: the
|
|
202
|
+
note survived its own Delete, typing into it changed nothing, and the workflow
|
|
203
|
+
never became dirty. A reader could not tell "not allowed here" from "broken".
|
|
204
|
+
|
|
205
|
+
The note is now read-only in the way a node card and a group frame already were:
|
|
206
|
+
no toolbar, no resize handle, and a double-click opens nothing. An empty note is
|
|
207
|
+
quiet too: the _Double-click to edit_ hint belongs to the editor, and on these
|
|
208
|
+
canvases it named the one gesture the note refuses. Everything the note says stays
|
|
209
|
+
exactly as readable, formatting and all — this removes affordances, not content.
|
|
210
|
+
|
|
211
|
+
A double-click on a read-only note now reaches the canvas instead of being
|
|
212
|
+
swallowed, so it zooms there like a double-click on a node card or on empty space.
|
|
213
|
+
A group frame's label was swallowing it the same way, and no longer does — the note
|
|
214
|
+
and that label were the two places on those canvases where the gesture did nothing
|
|
215
|
+
at all, and a dead spot reads as broken. The zoom itself is how these canvases
|
|
216
|
+
behave rather than something promised: what is promised is that nothing opens to be
|
|
217
|
+
edited.
|
|
218
|
+
|
|
219
|
+
If you render `StickyNoteView` yourself: the read-only state _is_ the absence of
|
|
220
|
+
its callbacks. `onUpdate` and `onRemove` in `StickyNodeData` are now optional, and
|
|
221
|
+
a note handed neither draws no tools — the pattern `GroupFrameView` already used
|
|
222
|
+
for its own toolbar. Passing no-ops, which is what the two read-only panels did,
|
|
223
|
+
now means "editable, and nothing happens".
|
|
224
|
+
|
|
225
|
+
A host addressing the note's `Delete note` button or its colour swatches on a
|
|
226
|
+
replay or preview canvas will no longer find them. That is the fix, which is why
|
|
227
|
+
this is a patch: nothing here is additive, and what a host can observe is the
|
|
228
|
+
defect being gone.
|
|
229
|
+
|
|
3
230
|
## 0.6.0
|
|
4
231
|
|
|
5
232
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -30,6 +30,33 @@ supply a token/tenant through the platform provider (override
|
|
|
30
30
|
`usePlatformAuth` / `usePlatformTenant`, or the standalone defaults read
|
|
31
31
|
`NUXT_PUBLIC_DEV_TOKEN` / `NUXT_PUBLIC_DEV_TENANT`).
|
|
32
32
|
|
|
33
|
+
### Language
|
|
34
|
+
|
|
35
|
+
The studio speaks English and German. That covers both halves of a screen: the
|
|
36
|
+
text node packages publish (names, descriptions, settings) **and** the studio's
|
|
37
|
+
own chrome — headings, buttons, tabs, empty states, toasts. Both fall back to
|
|
38
|
+
English. The studio has no switcher of its own, so hand the choice over one of
|
|
39
|
+
two ways:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// nuxt.config.ts — a language that does not change
|
|
43
|
+
export default defineNuxtConfig({
|
|
44
|
+
studioIntegrations: { locale: 'de' }, // or NUXT_PUBLIC_STUDIO_INTEGRATIONS_LOCALE
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// a host plugin — a language the user switches while the app is running
|
|
50
|
+
const { setLocale } = useIntegrationsLocale()
|
|
51
|
+
watch(hostLocale, l => setLocale(l), { immediate: true })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A switch reaches surfaces that are already open; nothing is refetched. With
|
|
55
|
+
neither wired, the studio is English. The module carries its own message
|
|
56
|
+
catalogue (`runtime/i18n/messages/*.json`) — it never reads the host's, and needs
|
|
57
|
+
no i18n library installed in the host. See
|
|
58
|
+
[`docs/interface-language.md`](docs/interface-language.md).
|
|
59
|
+
|
|
33
60
|
## ⚠️ Tailwind: you MUST scan this package
|
|
34
61
|
|
|
35
62
|
The module ships its runtime as source-ish `.vue`; Tailwind generates the
|
|
@@ -63,6 +90,10 @@ pnpm build # dist/
|
|
|
63
90
|
pnpm changeset # record what your change does to the version
|
|
64
91
|
```
|
|
65
92
|
|
|
93
|
+
Branch off `main` as `feature/<desc>`, `fix/<desc>` or `chore/<desc>` — GitHub refuses
|
|
94
|
+
the first push of any other name, and `main` itself takes a reviewed pull request with
|
|
95
|
+
CI green: [`docs/branch-protection.md`](docs/branch-protection.md).
|
|
96
|
+
|
|
66
97
|
Hosts to develop against:
|
|
67
98
|
|
|
68
99
|
* **Playground** (`playground/`, in this repo): thin standalone Nuxt host that
|
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
export * from '../dist/runtime/types/integrations.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface ModuleOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The language the studio renders in, as a BCP-47 tag (`en`, `de`, `de-CH`).
|
|
7
|
+
*
|
|
8
|
+
* This is the *initial* language and the whole of the contract for a host
|
|
9
|
+
* whose language never changes. A host with a switcher leaves it alone and
|
|
10
|
+
* calls `useIntegrationsLocale().setLocale()` instead — its plugins run after
|
|
11
|
+
* this module's, so the two do not fight over the value.
|
|
12
|
+
*
|
|
13
|
+
* It reaches the runtime through `runtimeConfig.public.studioIntegrations`,
|
|
14
|
+
* so a deployment can override it with
|
|
15
|
+
* `NUXT_PUBLIC_STUDIO_INTEGRATIONS_LOCALE` without a rebuild.
|
|
16
|
+
*/
|
|
17
|
+
locale?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
5
20
|
|
|
6
21
|
export { _default as default };
|
|
22
|
+
export type { ModuleOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { defineNuxtModule, createResolver, hasNuxtModule, installModule, addComponentsDir, addImportsDir, extendPages } from '@nuxt/kit';
|
|
3
|
+
import { defineNuxtModule, createResolver, addPlugin, hasNuxtModule, installModule, addComponentsDir, addImportsDir, extendPages } from '@nuxt/kit';
|
|
4
4
|
|
|
5
5
|
function toSegment(name) {
|
|
6
6
|
if (name.startsWith("[...")) return `:${name.slice(4, -1)}(.*)*`;
|
|
@@ -27,9 +27,16 @@ const module$1 = defineNuxtModule({
|
|
|
27
27
|
name: "@revenexx/studio-integrations",
|
|
28
28
|
configKey: "studioIntegrations"
|
|
29
29
|
},
|
|
30
|
-
|
|
30
|
+
defaults: {
|
|
31
|
+
locale: "en"
|
|
32
|
+
},
|
|
33
|
+
async setup(options, nuxt) {
|
|
31
34
|
const { resolve } = createResolver(import.meta.url);
|
|
32
35
|
const runtime = resolve("./runtime");
|
|
36
|
+
const publicConfig = nuxt.options.runtimeConfig.public;
|
|
37
|
+
const configured = publicConfig.studioIntegrations ?? {};
|
|
38
|
+
publicConfig.studioIntegrations = { ...configured, locale: configured.locale ?? options.locale };
|
|
39
|
+
addPlugin(resolve("./runtime/plugins/locale"));
|
|
33
40
|
if (!hasNuxtModule("@solar-icons/nuxt", nuxt)) {
|
|
34
41
|
await installModule("@solar-icons/nuxt", {
|
|
35
42
|
namePrefix: "Solar",
|
|
@@ -52,6 +59,7 @@ const module$1 = defineNuxtModule({
|
|
|
52
59
|
nuxt.options.build.transpile.push(runtime);
|
|
53
60
|
addComponentsDir({ path: join(runtime, "components"), pathPrefix: true });
|
|
54
61
|
addImportsDir(join(runtime, "composables"));
|
|
62
|
+
addPlugin(join(runtime, "plugins", "realtime.client"));
|
|
55
63
|
extendPages((pages) => {
|
|
56
64
|
const pagesDir = join(runtime, "pages", "integrations");
|
|
57
65
|
if (!existsSync(pagesDir)) return;
|
|
@@ -25,29 +25,30 @@ const props = defineProps({
|
|
|
25
25
|
credentialType: { type: [String, Array], required: false }
|
|
26
26
|
});
|
|
27
27
|
const emit = defineEmits(["update:open", "created"]);
|
|
28
|
+
const t = useIntegrationsT();
|
|
28
29
|
const { create, testInline, load, oauthAuthorizeUrl } = useIntegrationsCredentials();
|
|
29
30
|
const { load: loadTypes, bySlug } = useCredentialTypes();
|
|
30
31
|
function authKindLabel(k) {
|
|
31
32
|
switch (k) {
|
|
32
33
|
case "api-key":
|
|
33
|
-
return "
|
|
34
|
+
return t("integration.credentials.shared.authKind.apiKey");
|
|
34
35
|
case "oauth2-authcode":
|
|
35
36
|
case "oauth2-client-credentials":
|
|
36
|
-
return "
|
|
37
|
+
return t("integration.credentials.shared.authKind.oauth2");
|
|
37
38
|
case "basic-auth":
|
|
38
|
-
return "
|
|
39
|
+
return t("integration.credentials.shared.authKind.basicAuth");
|
|
39
40
|
case "static":
|
|
40
|
-
return "
|
|
41
|
+
return t("integration.credentials.shared.authKind.static");
|
|
41
42
|
case "secret":
|
|
42
|
-
return "
|
|
43
|
+
return t("integration.credentials.shared.authKind.secret");
|
|
43
44
|
default:
|
|
44
45
|
return k ?? "";
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
const allowedSlugs = computed(() => {
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
return (Array.isArray(
|
|
49
|
+
const t2 = props.credentialType;
|
|
50
|
+
if (!t2) return [];
|
|
51
|
+
return (Array.isArray(t2) ? t2 : [t2]).map((s) => s.trim()).filter(Boolean);
|
|
51
52
|
});
|
|
52
53
|
const allowedTypes = computed(() => allowedSlugs.value.map((s) => bySlug(s)).filter((x) => !!x));
|
|
53
54
|
const pickedType = ref(null);
|
|
@@ -56,11 +57,11 @@ const sourceActive = ref(false);
|
|
|
56
57
|
const saving = ref(false);
|
|
57
58
|
const testing = ref(false);
|
|
58
59
|
const testResult = ref(null);
|
|
59
|
-
function fieldsOf(
|
|
60
|
-
return
|
|
60
|
+
function fieldsOf(t2) {
|
|
61
|
+
return t2?.fields ?? [];
|
|
61
62
|
}
|
|
62
|
-
function isOAuthAuthcode(
|
|
63
|
-
return
|
|
63
|
+
function isOAuthAuthcode(t2) {
|
|
64
|
+
return t2?.auth_kind === "oauth2-authcode";
|
|
64
65
|
}
|
|
65
66
|
watch(() => props.open, async (o) => {
|
|
66
67
|
if (!o) return;
|
|
@@ -76,12 +77,12 @@ watch(() => props.open, async (o) => {
|
|
|
76
77
|
form.name = "";
|
|
77
78
|
}
|
|
78
79
|
}, { immediate: true });
|
|
79
|
-
function pickType(
|
|
80
|
-
pickedType.value =
|
|
80
|
+
function pickType(t2) {
|
|
81
|
+
pickedType.value = t2;
|
|
81
82
|
form.config = {};
|
|
82
83
|
testResult.value = null;
|
|
83
84
|
sourceActive.value = false;
|
|
84
|
-
if (!form.name) form.name = resolveLocalized(
|
|
85
|
+
if (!form.name) form.name = resolveLocalized(t2.name);
|
|
85
86
|
}
|
|
86
87
|
function buildConfigPayload() {
|
|
87
88
|
const out = {};
|
|
@@ -130,14 +131,14 @@ async function submit() {
|
|
|
130
131
|
config: buildConfigPayload()
|
|
131
132
|
});
|
|
132
133
|
await load(true);
|
|
133
|
-
toast.success("
|
|
134
|
+
toast.success(t("integration.credentials.toast.created"));
|
|
134
135
|
if (isOAuthAuthcode(pickedType.value)) {
|
|
135
136
|
void connect(created.id);
|
|
136
137
|
}
|
|
137
138
|
emit("created", created.id);
|
|
138
139
|
emit("update:open", false);
|
|
139
140
|
} catch (e) {
|
|
140
|
-
toast.error(e?.message ?? "
|
|
141
|
+
toast.error(e?.message ?? t("integration.credentials.createDialog.error.create"));
|
|
141
142
|
} finally {
|
|
142
143
|
saving.value = false;
|
|
143
144
|
}
|
|
@@ -148,16 +149,16 @@ async function submit() {
|
|
|
148
149
|
<Dialog :open="open" @update:open="(v) => emit('update:open', v)">
|
|
149
150
|
<DialogContent class="sm:max-w-lg">
|
|
150
151
|
<DialogHeader>
|
|
151
|
-
<DialogTitle>
|
|
152
|
+
<DialogTitle>{{ t("integration.credentials.shared.dialog.title.create") }}</DialogTitle>
|
|
152
153
|
<DialogDescription class="text-sm">
|
|
153
|
-
|
|
154
|
+
{{ t("integration.credentials.createDialog.description") }}
|
|
154
155
|
</DialogDescription>
|
|
155
156
|
</DialogHeader>
|
|
156
157
|
|
|
157
158
|
<!-- Choose the type when the field accepts more than one -->
|
|
158
159
|
<div v-if="!pickedType" class="space-y-2 py-1">
|
|
159
160
|
<p v-if="!allowedTypes.length" class="text-sm text-muted-foreground">
|
|
160
|
-
|
|
161
|
+
{{ t("integration.credentials.createDialog.noTypeAvailable") }}
|
|
161
162
|
</p>
|
|
162
163
|
<button
|
|
163
164
|
v-for="t in allowedTypes"
|
|
@@ -185,13 +186,13 @@ async function submit() {
|
|
|
185
186
|
class="ml-auto text-sm text-muted-foreground hover:text-foreground"
|
|
186
187
|
@click="pickedType = null"
|
|
187
188
|
>
|
|
188
|
-
|
|
189
|
+
{{ t("integration.credentials.createDialog.action.changeType") }}
|
|
189
190
|
</button>
|
|
190
191
|
</div>
|
|
191
192
|
|
|
192
193
|
<div class="space-y-1.5">
|
|
193
|
-
<Label for="cred-name">
|
|
194
|
-
<Input id="cred-name" v-model="form.name" class="bg-white" placeholder="
|
|
194
|
+
<Label for="cred-name">{{ t("integration.credentials.shared.field.name.label") }}</Label>
|
|
195
|
+
<Input id="cred-name" v-model="form.name" class="bg-white" :placeholder="t('integration.credentials.createDialog.field.name.placeholder')" />
|
|
195
196
|
</div>
|
|
196
197
|
|
|
197
198
|
<!-- "Use revenexx …" source picker (SFTP / Mail / API key) -->
|
|
@@ -232,7 +233,7 @@ async function submit() {
|
|
|
232
233
|
@update:model-value="(v) => form.config[f.key] = v"
|
|
233
234
|
>
|
|
234
235
|
<SelectTrigger :id="`create-field-${f.key}`" class="bg-white">
|
|
235
|
-
<SelectValue placeholder="
|
|
236
|
+
<SelectValue :placeholder="t('integration.credentials.shared.field.select.placeholder')" />
|
|
236
237
|
</SelectTrigger>
|
|
237
238
|
<SelectContent>
|
|
238
239
|
<SelectItem v-for="opt in f.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
|
|
@@ -250,7 +251,7 @@ async function submit() {
|
|
|
250
251
|
</div>
|
|
251
252
|
|
|
252
253
|
<p v-if="isOAuthAuthcode(pickedType)" class="text-sm text-muted-foreground">
|
|
253
|
-
|
|
254
|
+
{{ t("integration.credentials.shared.oauthHint") }}
|
|
254
255
|
</p>
|
|
255
256
|
|
|
256
257
|
<div
|
|
@@ -258,7 +259,7 @@ async function submit() {
|
|
|
258
259
|
class="rounded-md px-3 py-2 text-sm"
|
|
259
260
|
:class="testResult.ok ? 'bg-emerald-500/10 text-emerald-700' : 'bg-rose-500/10 text-rose-700'"
|
|
260
261
|
>
|
|
261
|
-
{{ testResult.ok ? "
|
|
262
|
+
{{ testResult.ok ? t("integration.credentials.shared.test.ok") : testResult.error ?? t("integration.credentials.shared.test.failed") }}
|
|
262
263
|
</div>
|
|
263
264
|
</div>
|
|
264
265
|
|
|
@@ -267,11 +268,11 @@ async function submit() {
|
|
|
267
268
|
<Button variant="outline" :disabled="saving || testing" @click="runTest">
|
|
268
269
|
<Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
|
|
269
270
|
<TestIcon v-else :size="14" class="mr-1" />
|
|
270
|
-
|
|
271
|
+
{{ t("integration.credentials.shared.action.testConnection") }}
|
|
271
272
|
</Button>
|
|
272
273
|
<Button :disabled="saving || !form.name.trim()" @click="submit">
|
|
273
274
|
<Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
|
|
274
|
-
|
|
275
|
+
{{ t("integration.common.action.save") }}
|
|
275
276
|
</Button>
|
|
276
277
|
</div>
|
|
277
278
|
</DialogFooter>
|