@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
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
|
-
import {
|
|
1
|
+
import { readdirSync, statSync, existsSync } 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)}(.*)*`;
|
|
@@ -22,14 +22,22 @@ function scanRoutes(dir, baseUrl, baseName) {
|
|
|
22
22
|
}
|
|
23
23
|
return routes;
|
|
24
24
|
}
|
|
25
|
+
|
|
25
26
|
const module$1 = defineNuxtModule({
|
|
26
27
|
meta: {
|
|
27
28
|
name: "@revenexx/studio-integrations",
|
|
28
29
|
configKey: "studioIntegrations"
|
|
29
30
|
},
|
|
30
|
-
|
|
31
|
+
defaults: {
|
|
32
|
+
locale: "en"
|
|
33
|
+
},
|
|
34
|
+
async setup(options, nuxt) {
|
|
31
35
|
const { resolve } = createResolver(import.meta.url);
|
|
32
36
|
const runtime = resolve("./runtime");
|
|
37
|
+
const publicConfig = nuxt.options.runtimeConfig.public;
|
|
38
|
+
const configured = publicConfig.studioIntegrations ?? {};
|
|
39
|
+
publicConfig.studioIntegrations = { ...configured, locale: configured.locale ?? options.locale };
|
|
40
|
+
addPlugin(resolve("./runtime/plugins/locale"));
|
|
33
41
|
if (!hasNuxtModule("@solar-icons/nuxt", nuxt)) {
|
|
34
42
|
await installModule("@solar-icons/nuxt", {
|
|
35
43
|
namePrefix: "Solar",
|
|
@@ -52,6 +60,7 @@ const module$1 = defineNuxtModule({
|
|
|
52
60
|
nuxt.options.build.transpile.push(runtime);
|
|
53
61
|
addComponentsDir({ path: join(runtime, "components"), pathPrefix: true });
|
|
54
62
|
addImportsDir(join(runtime, "composables"));
|
|
63
|
+
addPlugin(join(runtime, "plugins", "realtime.client"));
|
|
55
64
|
extendPages((pages) => {
|
|
56
65
|
const pagesDir = join(runtime, "pages", "integrations");
|
|
57
66
|
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,10 @@ 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
|
-
|
|
262
|
+
<!-- The type's own sentence, either way. This is the surface where it is
|
|
263
|
+
worth the most: nothing is stored yet, so the operator is deciding
|
|
264
|
+
whether these access data reach what they meant (specs/credential-test.md). -->
|
|
265
|
+
{{ testResult.ok ? testResult.message ?? t("integration.credentials.shared.test.ok") : testResult.error ?? testResult.message ?? t("integration.credentials.shared.test.failed") }}
|
|
262
266
|
</div>
|
|
263
267
|
</div>
|
|
264
268
|
|
|
@@ -267,11 +271,11 @@ async function submit() {
|
|
|
267
271
|
<Button variant="outline" :disabled="saving || testing" @click="runTest">
|
|
268
272
|
<Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
|
|
269
273
|
<TestIcon v-else :size="14" class="mr-1" />
|
|
270
|
-
|
|
274
|
+
{{ t("integration.credentials.shared.action.testConnection") }}
|
|
271
275
|
</Button>
|
|
272
276
|
<Button :disabled="saving || !form.name.trim()" @click="submit">
|
|
273
277
|
<Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
|
|
274
|
-
|
|
278
|
+
{{ t("integration.common.action.save") }}
|
|
275
279
|
</Button>
|
|
276
280
|
</div>
|
|
277
281
|
</DialogFooter>
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CloseCircle as XCircle,
|
|
6
6
|
RefreshCircle as Loader2,
|
|
7
7
|
TestTube as TestIcon,
|
|
8
|
-
|
|
8
|
+
TrashBinMinimalistic as TrashBin
|
|
9
9
|
} from "#solar-icons";
|
|
10
10
|
import {
|
|
11
11
|
AlertDialog,
|
|
@@ -40,12 +40,13 @@ const props = defineProps({
|
|
|
40
40
|
type: { type: [Object, null], required: true }
|
|
41
41
|
});
|
|
42
42
|
const emit = defineEmits(["update:open", "changed"]);
|
|
43
|
+
const t = useIntegrationsT();
|
|
43
44
|
const { update, remove, test, oauthAuthorizeUrl, load } = useIntegrationsCredentials();
|
|
44
45
|
const tab = ref("credentials");
|
|
45
|
-
const tabs = [
|
|
46
|
-
{ key: "credentials", label: "
|
|
47
|
-
{ key: "details", label: "
|
|
48
|
-
];
|
|
46
|
+
const tabs = computed(() => [
|
|
47
|
+
{ key: "credentials", label: t("integration.credentials.detail.tab.credentials") },
|
|
48
|
+
{ key: "details", label: t("integration.credentials.detail.tab.details") }
|
|
49
|
+
]);
|
|
49
50
|
const form = reactive({ name: "", config: {} });
|
|
50
51
|
const sourceActive = ref(false);
|
|
51
52
|
const saving = ref(false);
|
|
@@ -65,8 +66,8 @@ const connecting = ref(false);
|
|
|
65
66
|
const typeName = computed(() => resolveLocalized(props.type?.name) || props.credential?.credential_type_slug || "");
|
|
66
67
|
const isOAuthAuthcode = computed(() => props.type?.auth_kind === "oauth2-authcode");
|
|
67
68
|
const state = computed(() => props.credential ? credentialState(props.credential) : null);
|
|
68
|
-
function fieldsOf(
|
|
69
|
-
return
|
|
69
|
+
function fieldsOf(t2) {
|
|
70
|
+
return t2?.fields ?? [];
|
|
70
71
|
}
|
|
71
72
|
function formatDateTime(iso) {
|
|
72
73
|
if (!iso) return "\u2014";
|
|
@@ -118,10 +119,10 @@ async function save() {
|
|
|
118
119
|
await update(props.credential.id, { name: form.name.trim(), config: buildConfigPayload() });
|
|
119
120
|
emit("changed");
|
|
120
121
|
emit("update:open", false);
|
|
121
|
-
toast.success("
|
|
122
|
+
toast.success(t("integration.credentials.toast.updated"));
|
|
122
123
|
} catch (e) {
|
|
123
124
|
fieldErrors.value = apiFieldErrors(e);
|
|
124
|
-
saveError.value = refusalReason(e, "
|
|
125
|
+
saveError.value = refusalReason(e, t("integration.credentials.error.save"));
|
|
125
126
|
toast.error(saveError.value);
|
|
126
127
|
} finally {
|
|
127
128
|
saving.value = false;
|
|
@@ -134,11 +135,11 @@ async function destroy() {
|
|
|
134
135
|
deleting.value = true;
|
|
135
136
|
try {
|
|
136
137
|
await remove(props.credential.id);
|
|
137
|
-
toast.success("
|
|
138
|
+
toast.success(t("integration.credentials.toast.deleted"));
|
|
138
139
|
emit("changed");
|
|
139
140
|
emit("update:open", false);
|
|
140
141
|
} catch (e) {
|
|
141
|
-
toast.error(e?.message ?? "
|
|
142
|
+
toast.error(e?.message ?? t("integration.credentials.error.delete"));
|
|
142
143
|
} finally {
|
|
143
144
|
deleting.value = false;
|
|
144
145
|
}
|
|
@@ -154,7 +155,7 @@ async function connect() {
|
|
|
154
155
|
window.removeEventListener("message", onMessage);
|
|
155
156
|
await load(true);
|
|
156
157
|
emit("changed");
|
|
157
|
-
toast.success("
|
|
158
|
+
toast.success(t("integration.credentials.toast.connected"));
|
|
158
159
|
}
|
|
159
160
|
};
|
|
160
161
|
window.addEventListener("message", onMessage);
|
|
@@ -167,7 +168,7 @@ async function connect() {
|
|
|
167
168
|
}
|
|
168
169
|
}, 1e3);
|
|
169
170
|
} catch (e) {
|
|
170
|
-
toast.error(e?.message ?? "
|
|
171
|
+
toast.error(e?.message ?? t("integration.credentials.error.oauthConnect"));
|
|
171
172
|
} finally {
|
|
172
173
|
connecting.value = false;
|
|
173
174
|
}
|
|
@@ -210,21 +211,27 @@ async function connect() {
|
|
|
210
211
|
@click="connect"
|
|
211
212
|
>
|
|
212
213
|
<Loader2 v-if="connecting" :size="13" class="mr-1 animate-spin" />
|
|
213
|
-
|
|
214
|
+
{{ t("integration.credentials.detail.action.reconnect") }}
|
|
214
215
|
</Button>
|
|
215
216
|
</div>
|
|
216
|
-
|
|
217
|
+
<!-- The stored sentence, and only while nothing fresher is on screen. The
|
|
218
|
+
live box below shows the result of a test just run, and the reload that
|
|
219
|
+
follows it puts the very same sentence here — so both would print it
|
|
220
|
+
twice, and past the length the platform truncates at, twice differently.
|
|
221
|
+
The freshly returned one wins, being whole (specs/credential-test.md
|
|
222
|
+
AC-1). -->
|
|
223
|
+
<p v-if="state.message && !liveTest" class="mt-1 text-sm break-words opacity-90">
|
|
217
224
|
{{ state.message }}
|
|
218
225
|
</p>
|
|
219
226
|
<p v-else-if="state.key === 'untested'" class="mt-1 text-sm opacity-80">
|
|
220
|
-
|
|
227
|
+
{{ t("integration.credentials.detail.state.untestedHint") }}
|
|
221
228
|
</p>
|
|
222
229
|
</div>
|
|
223
230
|
|
|
224
231
|
<!-- Editable fields -->
|
|
225
232
|
<div class="space-y-3">
|
|
226
233
|
<div class="space-y-1.5">
|
|
227
|
-
<Label for="detail-cred-name">
|
|
234
|
+
<Label for="detail-cred-name">{{ t("integration.credentials.shared.field.name.label") }}</Label>
|
|
228
235
|
<Input
|
|
229
236
|
id="detail-cred-name"
|
|
230
237
|
v-model="form.name"
|
|
@@ -262,7 +269,7 @@ async function connect() {
|
|
|
262
269
|
:model-value="form.config[f.key] ?? ''"
|
|
263
270
|
type="password"
|
|
264
271
|
autocomplete="new-password"
|
|
265
|
-
placeholder="
|
|
272
|
+
:placeholder="t('integration.credentials.shared.field.secret.placeholder')"
|
|
266
273
|
class="bg-white"
|
|
267
274
|
v-bind="configFieldAria(f.key)"
|
|
268
275
|
@update:model-value="(v) => form.config[f.key] = v"
|
|
@@ -285,7 +292,7 @@ async function connect() {
|
|
|
285
292
|
@update:model-value="(v) => form.config[f.key] = v"
|
|
286
293
|
>
|
|
287
294
|
<SelectTrigger :id="`detail-field-${f.key}`" class="bg-white" v-bind="configFieldAria(f.key)">
|
|
288
|
-
<SelectValue placeholder="
|
|
295
|
+
<SelectValue :placeholder="t('integration.credentials.shared.field.select.placeholder')" />
|
|
289
296
|
</SelectTrigger>
|
|
290
297
|
<SelectContent>
|
|
291
298
|
<SelectItem v-for="opt in f.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
|
|
@@ -313,7 +320,7 @@ async function connect() {
|
|
|
313
320
|
class="rounded-md px-3 py-2 text-sm"
|
|
314
321
|
:class="liveTest.ok ? 'bg-emerald-500/10 text-emerald-700' : 'bg-rose-500/10 text-rose-700'"
|
|
315
322
|
>
|
|
316
|
-
{{ liveTest.ok ? "
|
|
323
|
+
{{ liveTest.ok ? liveTest.message ?? t("integration.credentials.shared.test.ok") : liveTest.error ?? liveTest.message ?? t("integration.credentials.shared.test.failed") }}
|
|
317
324
|
</div>
|
|
318
325
|
|
|
319
326
|
<!-- The save was refused. Same rule as the create dialog: stated here, and at
|
|
@@ -327,11 +334,11 @@ async function connect() {
|
|
|
327
334
|
<Button variant="outline" :disabled="testing || saving" @click="runTest">
|
|
328
335
|
<Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
|
|
329
336
|
<TestIcon v-else :size="14" class="mr-1" />
|
|
330
|
-
|
|
337
|
+
{{ t("integration.credentials.shared.action.testConnection") }}
|
|
331
338
|
</Button>
|
|
332
339
|
<Button :disabled="saving || !form.name.trim()" @click="save">
|
|
333
340
|
<Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
|
|
334
|
-
|
|
341
|
+
{{ t("integration.common.action.save") }}
|
|
335
342
|
</Button>
|
|
336
343
|
</div>
|
|
337
344
|
</div>
|
|
@@ -341,37 +348,37 @@ async function connect() {
|
|
|
341
348
|
<!-- Tab 2: Details -->
|
|
342
349
|
<div v-else class="space-y-5 max-h-[62vh] overflow-y-auto pr-1 pt-4">
|
|
343
350
|
<div class="grid grid-cols-[9rem_1fr] gap-x-4 gap-y-2 text-sm">
|
|
344
|
-
<span class="text-muted-foreground">
|
|
351
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.type") }}</span>
|
|
345
352
|
<span>{{ typeName }}</span>
|
|
346
353
|
|
|
347
|
-
<span class="text-muted-foreground">
|
|
354
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.typeSlug") }}</span>
|
|
348
355
|
<span class="font-mono text-xs">{{ credential.credential_type_slug }}</span>
|
|
349
356
|
|
|
350
|
-
<span class="text-muted-foreground">
|
|
357
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.version") }}</span>
|
|
351
358
|
<span class="font-mono text-xs">{{ type?.version ?? "\u2014" }}</span>
|
|
352
359
|
|
|
353
|
-
<span class="text-muted-foreground">
|
|
360
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.authKind") }}</span>
|
|
354
361
|
<span class="font-mono text-xs">{{ type?.auth_kind ?? "\u2014" }}</span>
|
|
355
362
|
|
|
356
|
-
<span class="text-muted-foreground">
|
|
363
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.lastTested") }}</span>
|
|
357
364
|
<span class="tabular-nums">{{ formatDateTime(credential.last_test_at) }}</span>
|
|
358
365
|
|
|
359
|
-
<span class="text-muted-foreground">
|
|
366
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.id") }}</span>
|
|
360
367
|
<span class="font-mono text-xs break-all">{{ credential.id }}</span>
|
|
361
368
|
|
|
362
|
-
<span class="text-muted-foreground">
|
|
369
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.created") }}</span>
|
|
363
370
|
<span class="tabular-nums">{{ formatDateTime(credential.created_at) }}</span>
|
|
364
371
|
|
|
365
|
-
<span class="text-muted-foreground">
|
|
372
|
+
<span class="text-muted-foreground">{{ t("integration.credentials.detail.field.updated") }}</span>
|
|
366
373
|
<span class="tabular-nums">{{ formatDateTime(credential.updated_at) }}</span>
|
|
367
374
|
</div>
|
|
368
375
|
|
|
369
376
|
<!-- Danger zone -->
|
|
370
377
|
<div class="rounded-lg border border-rose-500/30 bg-rose-500/[0.03] p-3">
|
|
371
|
-
<div class="text-sm font-semibold text-rose-700">
|
|
378
|
+
<div class="text-sm font-semibold text-rose-700">{{ t("integration.credentials.detail.dangerZone.title") }}</div>
|
|
372
379
|
<div class="mt-1.5 flex items-center justify-between gap-3">
|
|
373
380
|
<p class="text-sm text-muted-foreground">
|
|
374
|
-
|
|
381
|
+
{{ t("integration.credentials.detail.dangerZone.description") }}
|
|
375
382
|
</p>
|
|
376
383
|
<Button
|
|
377
384
|
variant="outline"
|
|
@@ -381,8 +388,8 @@ async function connect() {
|
|
|
381
388
|
@click="pendingDelete = true"
|
|
382
389
|
>
|
|
383
390
|
<Loader2 v-if="deleting" :size="14" class="mr-1 animate-spin" />
|
|
384
|
-
<
|
|
385
|
-
|
|
391
|
+
<TrashBin v-else :size="14" class="mr-1" />
|
|
392
|
+
{{ t("integration.credentials.detail.action.deleteCredential") }}
|
|
386
393
|
</Button>
|
|
387
394
|
</div>
|
|
388
395
|
</div>
|
|
@@ -392,19 +399,19 @@ async function connect() {
|
|
|
392
399
|
<AlertDialog :open="pendingDelete" @update:open="(v) => pendingDelete = v">
|
|
393
400
|
<AlertDialogContent>
|
|
394
401
|
<AlertDialogHeader>
|
|
395
|
-
<AlertDialogTitle>
|
|
402
|
+
<AlertDialogTitle>{{ t("integration.credentials.shared.confirm.delete.title") }}</AlertDialogTitle>
|
|
396
403
|
<AlertDialogDescription>
|
|
397
404
|
<span class="font-medium text-foreground">{{ credential?.name }}</span>
|
|
398
|
-
|
|
405
|
+
{{ t("integration.credentials.shared.confirm.delete.body") }}
|
|
399
406
|
</AlertDialogDescription>
|
|
400
407
|
</AlertDialogHeader>
|
|
401
408
|
<AlertDialogFooter>
|
|
402
409
|
<Button variant="outline" :disabled="deleting" @click="pendingDelete = false">
|
|
403
|
-
|
|
410
|
+
{{ t("integration.common.action.cancel") }}
|
|
404
411
|
</Button>
|
|
405
412
|
<Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="destroy">
|
|
406
413
|
<Loader2 v-if="deleting" :size="14" class="animate-spin" />
|
|
407
|
-
{{ deleting ? "
|
|
414
|
+
{{ deleting ? t("integration.common.state.deleting") : t("integration.common.action.delete") }}
|
|
408
415
|
</Button>
|
|
409
416
|
</AlertDialogFooter>
|
|
410
417
|
</AlertDialogContent>
|