@revenexx/studio-integrations 2.0.0 → 2.2.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 +141 -0
- package/README.md +1 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -6
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +134 -131
- package/dist/runtime/components/Integrations/CredentialLogo.vue +1 -1
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +1 -1
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +6 -6
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +90 -11
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +121 -121
- package/dist/runtime/components/Integrations/NodeConfigField.vue +28 -9
- package/dist/runtime/components/Integrations/NodeInspector.vue +44 -41
- package/dist/runtime/components/Integrations/NodePalette.vue +52 -37
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +6 -5
- package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +7 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +14 -2
- package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +7 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +7 -26
- package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +10 -6
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +224 -225
- package/dist/runtime/components/Integrations/WorkflowExportDialog.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowExportDialog.vue +71 -0
- package/dist/runtime/components/Integrations/WorkflowExportDialog.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.d.vue.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue +95 -0
- package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue.d.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +0 -7
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +2 -2
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +2 -3
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +14 -8
- package/dist/runtime/composables/useIntegrationsNodes.js +4 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +3 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -22
- package/dist/runtime/composables/useNow.d.ts +17 -0
- package/dist/runtime/composables/useNow.js +16 -0
- package/dist/runtime/composables/useWorkflowFile.d.ts +21 -0
- package/dist/runtime/composables/useWorkflowFile.js +63 -0
- package/dist/runtime/data/expressionEditor.d.ts +11 -1
- package/dist/runtime/data/expressionEditor.js +2 -1
- package/dist/runtime/data/nodeIcons.d.ts +14 -0
- package/dist/runtime/data/nodeIcons.js +57 -14
- package/dist/runtime/data/nodeRunData.js +1 -1
- package/dist/runtime/data/payloadForm.js +1 -1
- package/dist/runtime/i18n/messages/de.json +42 -0
- package/dist/runtime/i18n/messages/en.json +42 -0
- package/dist/runtime/pages/integrations/audit.vue +3 -3
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +2 -2
- package/dist/runtime/pages/integrations/index.vue +5 -10
- package/dist/runtime/pages/integrations/schedules.vue +13 -1
- package/dist/runtime/pages/integrations/templates/index.vue +40 -40
- package/dist/runtime/pages/integrations/workflows/[id].vue +324 -244
- package/dist/runtime/types/integrations.d.ts +13 -0
- package/dist/runtime/utils/credentialState.d.ts +0 -7
- package/dist/runtime/utils/credentialState.js +0 -12
- package/dist/runtime/utils/localize.d.ts +12 -0
- package/dist/runtime/utils/localize.js +1 -1
- package/dist/runtime/utils/paletteOrder.d.ts +29 -0
- package/dist/runtime/utils/paletteOrder.js +46 -0
- package/dist/runtime/utils/relativeTime.d.ts +29 -0
- package/dist/runtime/utils/relativeTime.js +23 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +12 -6
- package/dist/runtime/utils/runFeedQuery.js +3 -18
- package/dist/runtime/utils/settingValue.d.ts +33 -0
- package/dist/runtime/utils/settingValue.js +8 -0
- package/dist/runtime/utils/statusStyle.d.ts +8 -8
- package/dist/runtime/utils/statusStyle.js +4 -1
- package/dist/runtime/utils/workflowFile.d.ts +119 -0
- package/dist/runtime/utils/workflowFile.js +192 -0
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,146 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 2.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- deb3bf6: A setting takes a value mapped from an earlier step only where its node says it may (PO-548). Every choice, number and switch used to offer the `{{ }}` switch whatever the node declared — so XML's parse-or-build, a decision somebody has to make, could be mapped; now only a setting that declares `expressionAllowed` offers it. And one that does keeps its own control — the list, the number box, the switch — with the `{{ }}` switch beside it, instead of being drawn as the mapping box from the start. A value mapped into a setting before this stays shown, with the switch back to the regular control.
|
|
8
|
+
|
|
9
|
+
## 2.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 6ff6833: A workflow can be exported to a file and imported again, into the same environment or
|
|
14
|
+
another: Export as file in a workflow's row actions and in the editor's actions menu, Import
|
|
15
|
+
from file in the New workflow dialog, and Import from file… in the editor to replace a
|
|
16
|
+
workflow's content. The file never carries credentials, secret values or a webhook's
|
|
17
|
+
address; an import creates a paused workflow, or replaces the editor's content as
|
|
18
|
+
unsaved changes that keep each webhook's address and leave an active workflow running
|
|
19
|
+
until a valid version is saved (PO-525).
|
|
20
|
+
- 9721bc2: PO-537: inside a palette folder, the nodes stand in the order their package gives
|
|
21
|
+
them rather than in the alphabetical order of their identifiers.
|
|
22
|
+
|
|
23
|
+
A node that states its place (`paletteOrder` in its manifest) comes first; the rest
|
|
24
|
+
follow in the order the package lists them, which the Integrations API now reports
|
|
25
|
+
as `package_position`; a node for which neither is known still falls back to its
|
|
26
|
+
identifier. So "Send Email" leads Brevo's Email folder instead of "Cancel Scheduled
|
|
27
|
+
Email".
|
|
28
|
+
|
|
29
|
+
Where a folder holds nodes of several packages, each package orders only its own:
|
|
30
|
+
no node takes a place another package's node held. Against an API that does not
|
|
31
|
+
report `package_position` yet, folders keep today's order.
|
|
32
|
+
|
|
33
|
+
- 1820289: A run's record now shows what the run returned per step: one block for each step, under the name it carries on the canvas, in the order of the steps in the workflow — the way the dialog after a run already shows it. Before, the record printed the result as one object keyed by internal node ids, in an order the run never took. A result the workflow's steps cannot account for, because the workflow has changed since the run, say, is still shown whole.
|
|
34
|
+
- 6cc8b71: A node's optional settings fold only after its last required setting. Before, every optional setting from the fourth on was folded wherever it stood, so a setting between two required ones vanished from the middle of the form, and one "Show n optional settings" control opened settings in two separate places. Everything up to the last required setting is now always shown, and the fold is one tail at the end — Brevo's Double Opt-in, for one, now shows "Never on lists" and folds only "Attributes".
|
|
35
|
+
- 664725b: A mapped value in a node's setting can now be changed where it stands. Click it
|
|
36
|
+
to select it, then click it again or press Enter, and it opens as the expression
|
|
37
|
+
it stands for;
|
|
38
|
+
once the caret or the focus leaves it, it reads as its source again. Until now it
|
|
39
|
+
could only be deleted and written again, which for a condition meant retyping the
|
|
40
|
+
whole expression to correct one character (PO-544).
|
|
41
|
+
- 7fd02ea: The node palette's folders now stand alphabetically on every level, in the
|
|
42
|
+
language the studio is shown in and regardless of case. Until now a folder stood
|
|
43
|
+
where its first node's internal identifier fell in the catalogue, so Test Nodes
|
|
44
|
+
appeared between PostBin and Shipcloud. SFTP is no longer pinned directly after
|
|
45
|
+
FTP; the nodes inside a folder keep the order their package gives them (PO-545).
|
|
46
|
+
- 7525758: A Core folder in the node palette is one folder in every language (PO-546). The studio now names the Core folders it knows — Flow, Data, Text & Formats, Files, Web, Messaging, FTP / SFTP — from its own translations, by their English name, so a node that names no folder, or one an older release left registered with an out-of-date name, no longer opens a second folder (_Data_ beside _Daten_, _Flow_ beside _Ablauf_). Every one of them has its own icon. A path of two levels no longer files a node under Integrations — only an app package or a logo does — so Core can nest a folder; a subfolder without a logo shows the first image its nodes carry.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- ddcbdbd: The Schedules page says why a schedule will not fire, in the Next run column: its
|
|
51
|
+
workflow is inactive or not built, its schedule cannot be read, its timezone is
|
|
52
|
+
unknown, or its rhythm never comes round. Until now every one of these read as a
|
|
53
|
+
bare dash beside a live badge. A paused schedule and one with no schedule set keep
|
|
54
|
+
the dash, because the row already says so (PO-261).
|
|
55
|
+
- ebbc148: Nothing a user sees changes. The module now declares `@solar-icons/vue`, which its
|
|
56
|
+
components already import through `@solar-icons/nuxt`. A host that typechecks and
|
|
57
|
+
sees every `#solar-icons` import reported as a missing export has to declare
|
|
58
|
+
`@solar-icons/vue` itself: the type declaration is generated into the host's own
|
|
59
|
+
`.nuxt`, so it is resolved from the host's dependencies, not this module's (PO-281).
|
|
60
|
+
- fd5a78e: Nothing a user sees changes. The module is linted now, and the first run's fixes ship
|
|
61
|
+
in it: unused imports and dead helpers removed, the pattern that finds `${{ … }}` in a
|
|
62
|
+
setting no longer slows down on long unclosed input, and the OAuth popup renders one
|
|
63
|
+
root element (PO-305).
|
|
64
|
+
- d9f4742: Cancelling a run from its row in either run list now asks first, the way terminating
|
|
65
|
+
already did and the way the run's record asks before both. A cancelled run cannot be
|
|
66
|
+
resumed, only retried from the start, so a slip in the row's menu was as final as a
|
|
67
|
+
terminate (PO-336).
|
|
68
|
+
- 271de84: A run the platform reports as `canceled` — Temporal's spelling — or in another letter case (`COMPLETED`) now reads the same in the Runs list as at its own record. The list used to draw such a run as _failed_ while the record called it _cancelled_ or _succeeded_; both now read the platform's status through one table.
|
|
69
|
+
- 90d9ae3: "How long ago" reads the same everywhere it is shown: the dashboard's Latest executions and
|
|
70
|
+
Needs attention panels, the runs list's Started column and a credential's last
|
|
71
|
+
check. Under ten seconds is "just now", from there to a minute it counts seconds
|
|
72
|
+
("40s ago") — the dashboard used to say "just now" for the whole minute — and on
|
|
73
|
+
the dashboard and the runs list that count keeps moving between realtime events.
|
|
74
|
+
From thirty days on it is a date in the studio's language instead of a count like
|
|
75
|
+
"412d ago". A timestamp that cannot be read shows "—"; the dashboard used to print
|
|
76
|
+
"NaNd ago" (PO-393).
|
|
77
|
+
- 60ef22a: The Shipcloud package's node icons draw as icons rather than as grey boxes.
|
|
78
|
+
|
|
79
|
+
A node declares its icon as an `mdi:` name and the studio renders Lucide, so the
|
|
80
|
+
two vocabularies are bridged by a table. A name in neither the table nor Lucide's
|
|
81
|
+
own spelling silently becomes a generic box — which is what the Shipcloud package
|
|
82
|
+
got for its nodes. Eight names now map: `truck-delivery`,
|
|
83
|
+
`package-variant-closed-plus`, `map-marker-path`, `package-variant-remove`,
|
|
84
|
+
`truck-check-outline`, `file-document-outline`, `file-plus-outline` and
|
|
85
|
+
`map-marker-radius-outline`.
|
|
86
|
+
|
|
87
|
+
The table also has a test now, including one that fails on an entry pointing at a
|
|
88
|
+
Lucide component that does not exist: a wrong entry looks exactly like a missing
|
|
89
|
+
one, and neither says anything.
|
|
90
|
+
|
|
91
|
+
- 78e99d6: A number typed into a setting's mapped-value mode is kept as a number, not as text
|
|
92
|
+
(PO-521).
|
|
93
|
+
|
|
94
|
+
**A numeric setting has two boxes and only one of them kept the number.** The plain
|
|
95
|
+
one wrote what was typed as a number; the box beside it — the one that also takes a
|
|
96
|
+
value mapped from elsewhere in the workflow — wrote the raw text through. Brightness
|
|
97
|
+
50 was stored as `"50"`, the workflow saved without a word, and the run stopped with
|
|
98
|
+
`Config field 'brightness' must be of type number.` Nothing in that field could fix
|
|
99
|
+
it, because the field itself is what produced the text. Reported from the Philips Hue
|
|
100
|
+
**Set Light** node, where every value the author touched was text and the one they
|
|
101
|
+
left at its default was a number.
|
|
102
|
+
|
|
103
|
+
The rule now lives in one place: a numeric setting keeps the number, an emptied box
|
|
104
|
+
is no value rather than `''`, a mapped value is left exactly as it was typed, and
|
|
105
|
+
text denoting no number is kept as typed so the operator sees what they wrote rather
|
|
106
|
+
than finding the box silently emptied. Text in one of the three base notations —
|
|
107
|
+
`0x10`, `0b101`, `0o17` — counts as no number here, since the platform refuses it on
|
|
108
|
+
the way in and storing 16, 5 or 15 would put a value in the workflow nobody typed.
|
|
109
|
+
|
|
110
|
+
**When the conversion happens differs by box, and has to.** The plain one converts as
|
|
111
|
+
it is typed. The mapped-value box shows a document rather than a line of text, and it
|
|
112
|
+
re-projects that document whenever it disagrees with the value it is given — so it
|
|
113
|
+
stores what was typed and converts when it is left. Converting there on every
|
|
114
|
+
keystroke would rebuild the document under the operator's cursor: a number typed over
|
|
115
|
+
what the box already held would keep only its last digit, and `0.4` could never be
|
|
116
|
+
typed at all, because half-way through it is `0.` — a number worth `0`, whose text has
|
|
117
|
+
no dot in it. The range a
|
|
118
|
+
setting allows is read through the same rule, so a workflow **saved before this
|
|
119
|
+
change** — its number still stored as text — is marked in the dialog instead of
|
|
120
|
+
reading as sound until the run refuses it.
|
|
121
|
+
|
|
122
|
+
A yes/no setting has the same shape of defect and is deliberately untouched; it is
|
|
123
|
+
recorded in the Gaps of `specs/node-configuration.md`, with why.
|
|
124
|
+
|
|
125
|
+
The platform half of PO-521 ships separately: cross-validation now reads a numeric
|
|
126
|
+
setting's value as the number it denotes, which is what lets the workflows already
|
|
127
|
+
saved this way run again without anything rewriting them.
|
|
128
|
+
|
|
129
|
+
- 2632bae: A credential type's logo sits in a smaller box: 8 px larger than the symbol instead of
|
|
130
|
+
16, so 24 px on the Credentials list instead of 32, with the type picker and the
|
|
131
|
+
credential dialogs shrinking in step. The generic key shown for a type without a logo
|
|
132
|
+
shrinks with it (PO-527).
|
|
133
|
+
- 1159568: PO-542: nodes whose icon the studio could not translate no longer draw as an
|
|
134
|
+
empty box.
|
|
135
|
+
|
|
136
|
+
Node packages name their icons in one vocabulary and the studio draws another, so
|
|
137
|
+
each name goes through a table; a name missing from it fell back to the generic box
|
|
138
|
+
without a word. The table now covers the names the packages ship today — all of
|
|
139
|
+
Philips Hue and PostBin, sixteen Brevo nodes, the notifications nodes, and one each
|
|
140
|
+
in core and DeepL. The four chat brands (Discord, Microsoft Teams, Telegram,
|
|
141
|
+
WhatsApp) have no counterpart in the drawing set and get a neutral messaging
|
|
142
|
+
symbol; their vendor logo still marks them.
|
|
143
|
+
|
|
3
144
|
## 2.0.0
|
|
4
145
|
|
|
5
146
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ cockpit monorepo under `packages/studio-integrations`).
|
|
|
86
86
|
```bash
|
|
87
87
|
pnpm install # runs the module `prepare` (nuxt-module-build)
|
|
88
88
|
pnpm test # vitest
|
|
89
|
+
pnpm lint # eslint — `pnpm lint --fix` fixes what has a fix, layout above all
|
|
89
90
|
pnpm build # dist/
|
|
90
91
|
pnpm changeset # record what your change does to the version
|
|
91
92
|
```
|
package/dist/module.json
CHANGED
|
@@ -149,16 +149,16 @@ async function submit() {
|
|
|
149
149
|
{{ t("integration.credentials.createDialog.noTypeAvailable") }}
|
|
150
150
|
</p>
|
|
151
151
|
<button
|
|
152
|
-
v-for="
|
|
153
|
-
:key="
|
|
152
|
+
v-for="type in allowedTypes"
|
|
153
|
+
:key="type.slug"
|
|
154
154
|
type="button"
|
|
155
155
|
class="flex w-full items-center gap-2 rounded-lg border border-black/[0.08] p-3 text-left transition hover:border-black/20 hover:bg-muted/30"
|
|
156
|
-
@click="pickType(
|
|
156
|
+
@click="pickType(type)"
|
|
157
157
|
>
|
|
158
|
-
<IntegrationsCredentialLogo :type="
|
|
158
|
+
<IntegrationsCredentialLogo :type="type" :size="18" />
|
|
159
159
|
<div class="min-w-0 truncate text-sm">
|
|
160
|
-
<span class="font-semibold">{{ resolveLocalized(
|
|
161
|
-
<span class="ml-1.5 text-muted-foreground">({{ authKindLabel(
|
|
160
|
+
<span class="font-semibold">{{ resolveLocalized(type.name) }}</span>
|
|
161
|
+
<span class="ml-1.5 text-muted-foreground">({{ authKindLabel(type.auth_kind) }})</span>
|
|
162
162
|
</div>
|
|
163
163
|
</button>
|
|
164
164
|
</div>
|
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
} from "@revenexx/studio";
|
|
32
32
|
import { toast } from "../../utils/notify";
|
|
33
33
|
import { apiRefusal, errorReason } from "../../utils/apiError";
|
|
34
|
-
import { credentialState
|
|
34
|
+
import { credentialState } from "../../utils/credentialState";
|
|
35
|
+
import { relativeTime } from "../../utils/relativeTime";
|
|
36
|
+
import { useNow } from "../../composables/useNow";
|
|
35
37
|
import { hasRevenexxSource } from "../../utils/revenexxCredentialSource";
|
|
36
38
|
import { resolveLocalized } from "../../utils/localize";
|
|
37
39
|
const props = defineProps({
|
|
@@ -41,6 +43,7 @@ const props = defineProps({
|
|
|
41
43
|
});
|
|
42
44
|
const emit = defineEmits(["update:open", "changed"]);
|
|
43
45
|
const t = useIntegrationsT();
|
|
46
|
+
const now = useNow();
|
|
44
47
|
const { update, remove, test, oauthAuthorizeUrl, load } = useIntegrationsCredentials();
|
|
45
48
|
const tab = ref("credentials");
|
|
46
49
|
const tabs = computed(() => [
|
|
@@ -204,167 +207,167 @@ async function connect() {
|
|
|
204
207
|
scrolling pane and the block below it are siblings, as they were when the
|
|
205
208
|
pane was the whole tab, so nothing about the spacing around them moves. -->
|
|
206
209
|
<template v-if="tab === 'credentials'">
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
210
|
+
<div class="space-y-5 max-h-[62vh] overflow-y-auto pr-1 pt-4">
|
|
211
|
+
<!-- Connection state — the headline answer: are these credentials correct? -->
|
|
212
|
+
<div class="rounded-lg border p-3" :class="state.toneClass">
|
|
213
|
+
<div class="flex items-center gap-2">
|
|
214
|
+
<CheckCircle2 v-if="state.key === 'connected'" :size="16" class="shrink-0" />
|
|
215
|
+
<XCircle v-else-if="state.key === 'failed'" :size="16" class="shrink-0" />
|
|
216
|
+
<span class="font-medium">{{ state.label }}</span>
|
|
217
|
+
<span v-if="state.at" class="text-sm opacity-70">· verified {{ relativeTime(state.at, now) }}</span>
|
|
218
|
+
<div class="flex-1" />
|
|
219
|
+
<Button
|
|
220
|
+
v-if="isOAuthAuthcode"
|
|
221
|
+
size="sm"
|
|
222
|
+
variant="outline"
|
|
223
|
+
class="bg-white/60"
|
|
224
|
+
:disabled="connecting"
|
|
225
|
+
@click="connect"
|
|
226
|
+
>
|
|
227
|
+
<Loader2 v-if="connecting" :size="13" class="mr-1 animate-spin" />
|
|
228
|
+
{{ t("integration.credentials.detail.action.reconnect") }}
|
|
229
|
+
</Button>
|
|
230
|
+
</div>
|
|
231
|
+
<!-- The stored sentence, and only while nothing fresher is on screen. The
|
|
229
232
|
live box below shows the result of a test just run, and the reload that
|
|
230
233
|
follows it puts the very same sentence here — so both would print it
|
|
231
234
|
twice, and past the length the platform truncates at, twice differently.
|
|
232
235
|
The freshly returned one wins, being whole (specs/credential-test.md
|
|
233
236
|
AC-1). -->
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
<p v-if="state.message && !liveTest" class="mt-1 text-sm break-words opacity-90">
|
|
238
|
+
{{ state.message }}
|
|
239
|
+
</p>
|
|
240
|
+
<p v-else-if="state.key === 'untested'" class="mt-1 text-sm opacity-80">
|
|
241
|
+
{{ t("integration.credentials.detail.state.untestedHint") }}
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
245
|
+
<!-- Editable fields -->
|
|
246
|
+
<div class="space-y-3">
|
|
247
|
+
<div class="space-y-1.5">
|
|
248
|
+
<Label for="detail-cred-name">{{ t("integration.credentials.shared.field.name.label") }}</Label>
|
|
249
|
+
<Input
|
|
250
|
+
id="detail-cred-name"
|
|
251
|
+
v-model="form.name"
|
|
252
|
+
class="bg-white"
|
|
253
|
+
:aria-invalid="fieldErrors.name ? 'true' : void 0"
|
|
254
|
+
:aria-describedby="fieldErrors.name ? 'detail-cred-name-error' : void 0"
|
|
255
|
+
/>
|
|
256
|
+
<p v-if="fieldErrors.name" id="detail-cred-name-error" class="text-sm text-rose-700">
|
|
257
|
+
{{ fieldErrors.name }}
|
|
258
|
+
</p>
|
|
259
|
+
</div>
|
|
257
260
|
|
|
258
|
-
|
|
261
|
+
<!-- "Use revenexx …" source picker (SFTP / Mail / API key) — mounted
|
|
259
262
|
only for the types it serves, which is tidiness rather than
|
|
260
263
|
protection: it renders nothing for any other type anyway. It used
|
|
261
264
|
to be protection, because the component resolved cockpit-only
|
|
262
265
|
sources at setup and mounting it elsewhere took this form down;
|
|
263
266
|
since PO-222 it asks instead (`utils/cockpitSources.ts`). -->
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
<IntegrationsCredentialRevenexxSource
|
|
268
|
+
v-if="hasRevenexxSource(type?.slug)"
|
|
269
|
+
:type="type"
|
|
270
|
+
@update:active="(v) => {
|
|
268
271
|
sourceActive = v;
|
|
269
272
|
clearRefusal();
|
|
270
273
|
}"
|
|
271
|
-
|
|
272
|
-
|
|
274
|
+
@apply="(c) => Object.assign(form.config, c)"
|
|
275
|
+
/>
|
|
273
276
|
|
|
274
|
-
|
|
277
|
+
<!-- Each label names its own control (`for`/`id`): without it a
|
|
275
278
|
screen reader reads the field as unlabelled, and the label text
|
|
276
279
|
cannot be used to reach it at all. -->
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
<div v-for="f in fieldsOf(type)" v-show="!sourceActive" :key="f.key" class="space-y-1.5">
|
|
281
|
+
<Label :for="`detail-field-${f.key}`">{{ resolveLocalized(f.label) }}{{ f.required ? " *" : "" }}</Label>
|
|
279
282
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
283
|
+
<Input
|
|
284
|
+
v-if="f.type === 'secret'"
|
|
285
|
+
:id="`detail-field-${f.key}`"
|
|
286
|
+
:model-value="form.config[f.key] ?? ''"
|
|
287
|
+
type="password"
|
|
288
|
+
autocomplete="new-password"
|
|
289
|
+
:placeholder="t('integration.credentials.shared.field.secret.placeholder')"
|
|
290
|
+
class="bg-white"
|
|
291
|
+
v-bind="configFieldAria(f.key)"
|
|
292
|
+
@update:model-value="(v) => form.config[f.key] = v"
|
|
293
|
+
/>
|
|
294
|
+
<Input
|
|
295
|
+
v-else-if="f.type === 'number'"
|
|
296
|
+
:id="`detail-field-${f.key}`"
|
|
297
|
+
type="number"
|
|
298
|
+
:model-value="form.config[f.key] ?? ''"
|
|
299
|
+
class="bg-white"
|
|
300
|
+
v-bind="configFieldAria(f.key)"
|
|
301
|
+
@update:model-value="(v) => form.config[f.key] = v === '' ? void 0 : Number(v)"
|
|
302
|
+
/>
|
|
303
|
+
<div v-else-if="f.type === 'boolean'" class="flex items-center gap-2 pt-1">
|
|
304
|
+
<Switch :id="`detail-field-${f.key}`" :model-value="Boolean(form.config[f.key])" v-bind="configFieldAria(f.key)" @update:model-value="(v) => form.config[f.key] = v" />
|
|
305
|
+
</div>
|
|
306
|
+
<Select
|
|
307
|
+
v-else-if="f.type === 'select'"
|
|
308
|
+
:model-value="form.config[f.key] ?? ''"
|
|
309
|
+
@update:model-value="(v) => form.config[f.key] = v"
|
|
310
|
+
>
|
|
311
|
+
<SelectTrigger :id="`detail-field-${f.key}`" class="bg-white" v-bind="configFieldAria(f.key)">
|
|
312
|
+
<SelectValue :placeholder="t('integration.credentials.shared.field.select.placeholder')" />
|
|
313
|
+
</SelectTrigger>
|
|
314
|
+
<SelectContent>
|
|
315
|
+
<SelectItem v-for="opt in f.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
|
|
316
|
+
{{ resolveLocalized(opt.label) }}
|
|
317
|
+
</SelectItem>
|
|
318
|
+
</SelectContent>
|
|
319
|
+
</Select>
|
|
320
|
+
<Input
|
|
321
|
+
v-else
|
|
322
|
+
:id="`detail-field-${f.key}`"
|
|
323
|
+
:model-value="form.config[f.key] ?? ''"
|
|
324
|
+
class="bg-white"
|
|
325
|
+
v-bind="configFieldAria(f.key)"
|
|
326
|
+
@update:model-value="(v) => form.config[f.key] = v"
|
|
327
|
+
/>
|
|
325
328
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
<p v-if="fieldErrors[`config.${f.key}`]" :id="`detail-field-${f.key}-error`" class="text-sm text-rose-700">
|
|
330
|
+
{{ fieldErrors[`config.${f.key}`] }}
|
|
331
|
+
</p>
|
|
332
|
+
</div>
|
|
330
333
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
334
|
+
<!-- Live test feedback (from the test the operator just ran) -->
|
|
335
|
+
<div
|
|
336
|
+
v-if="liveTest"
|
|
337
|
+
class="rounded-md px-3 py-2 text-sm"
|
|
338
|
+
:class="liveTest.ok ? 'bg-emerald-500/10 text-emerald-700' : 'bg-rose-500/10 text-rose-700'"
|
|
339
|
+
>
|
|
340
|
+
{{ liveTest.ok ? liveTest.message ?? t("integration.credentials.shared.test.ok") : liveTest.error ?? liveTest.message ?? t("integration.credentials.shared.test.failed") }}
|
|
341
|
+
</div>
|
|
339
342
|
|
|
340
|
-
|
|
343
|
+
</div>
|
|
341
344
|
|
|
342
|
-
|
|
345
|
+
</div>
|
|
343
346
|
|
|
344
|
-
|
|
347
|
+
<!-- Outside the scrolling pane on purpose, the way the create dialog has it:
|
|
345
348
|
a refusal puts the operator in the first marked field, which scrolls that
|
|
346
349
|
pane back to the top — and a summary rendered at the end of the form would
|
|
347
350
|
be carried out of sight by the very focus meant to make the refusal
|
|
348
351
|
visible. What is marked at a field is not repeated here; what has no field
|
|
349
352
|
to be marked at would otherwise be silent. -->
|
|
350
|
-
|
|
351
|
-
|
|
353
|
+
<div v-if="summaryReasons.length" role="alert" class="mt-3 space-y-1 rounded-md bg-rose-500/10 px-3 py-2 text-sm text-rose-700">
|
|
354
|
+
<!-- Keyed by position: two undrawn keys can be refused with the same
|
|
352
355
|
sentence, and a duplicate key is how one of them goes unrendered. -->
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
<p v-for="(reason, i) in summaryReasons" :key="i">{{ reason }}</p>
|
|
357
|
+
</div>
|
|
355
358
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
359
|
+
<div class="flex items-center gap-2 pt-3">
|
|
360
|
+
<div class="flex-1" />
|
|
361
|
+
<Button variant="outline" :disabled="testing || saving" @click="runTest">
|
|
362
|
+
<Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
|
|
363
|
+
<TestIcon v-else :size="14" class="mr-1" />
|
|
364
|
+
{{ t("integration.credentials.shared.action.testConnection") }}
|
|
365
|
+
</Button>
|
|
366
|
+
<Button :disabled="saving || !form.name.trim()" @click="save">
|
|
367
|
+
<Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
|
|
368
|
+
{{ t("integration.common.action.save") }}
|
|
369
|
+
</Button>
|
|
370
|
+
</div>
|
|
368
371
|
</template>
|
|
369
372
|
|
|
370
373
|
<!-- Tab 2: Details -->
|
|
@@ -5,7 +5,7 @@ const props = defineProps({
|
|
|
5
5
|
type: { type: [Object, null], required: false },
|
|
6
6
|
size: { type: Number, required: false }
|
|
7
7
|
});
|
|
8
|
-
const box = computed(() => (props.size ?? 16) +
|
|
8
|
+
const box = computed(() => (props.size ?? 16) + 8);
|
|
9
9
|
const glyph = computed(() => props.size ?? 16);
|
|
10
10
|
const logoSrc = computed(() => {
|
|
11
11
|
const imgs = props.type?.images;
|
|
@@ -51,16 +51,16 @@ const filtered = computed(() => {
|
|
|
51
51
|
{{ t("integration.credentials.typePicker.empty", { query }) }}
|
|
52
52
|
</p>
|
|
53
53
|
<button
|
|
54
|
-
v-for="
|
|
55
|
-
:key="
|
|
54
|
+
v-for="type in filtered"
|
|
55
|
+
:key="type.slug"
|
|
56
56
|
type="button"
|
|
57
57
|
class="flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left transition hover:bg-muted/50"
|
|
58
|
-
@click="emit('pick',
|
|
58
|
+
@click="emit('pick', type)"
|
|
59
59
|
>
|
|
60
|
-
<IntegrationsCredentialLogo :type="
|
|
60
|
+
<IntegrationsCredentialLogo :type="type" :size="16" />
|
|
61
61
|
<div class="min-w-0 flex-1 truncate text-sm">
|
|
62
|
-
<span class="font-medium">{{ resolveLocalized(
|
|
63
|
-
<span class="ml-1.5 text-muted-foreground">({{ authKindLabel(
|
|
62
|
+
<span class="font-medium">{{ resolveLocalized(type.name) || type.slug }}</span>
|
|
63
|
+
<span class="ml-1.5 text-muted-foreground">({{ authKindLabel(type.auth_kind) }})</span>
|
|
64
64
|
</div>
|
|
65
65
|
</button>
|
|
66
66
|
</div>
|
|
@@ -21,7 +21,9 @@ type __VLS_Props = {
|
|
|
21
21
|
};
|
|
22
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
23
|
"update:modelValue": (value: string) => any;
|
|
24
|
+
blur: () => any;
|
|
24
25
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
26
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
27
|
+
onBlur?: (() => any) | undefined;
|
|
26
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
27
29
|
export default _default;
|