@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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The studio's own interface strings, and the function that reads one.
|
|
3
|
+
*
|
|
4
|
+
* **Why the module carries a catalogue at all.** `@revenexx/studio-integrations`
|
|
5
|
+
* is published. It renders inside the cockpit, which has vue-i18n and a
|
|
6
|
+
* catalogue of its own, and it renders inside the playground host and any other
|
|
7
|
+
* consumer, which have neither. Its chrome — every heading, button, empty state
|
|
8
|
+
* and toast in `runtime/` — is the module's text, not the host's, so the module
|
|
9
|
+
* ships it. A host cannot be asked to carry `integration.*` keys it has never
|
|
10
|
+
* seen, and the module cannot reach into a catalogue it does not own.
|
|
11
|
+
*
|
|
12
|
+
* **Why not vue-i18n.** Adding it here would either duplicate the host's
|
|
13
|
+
* instance or force every consumer to install it. The resolution rules a flat
|
|
14
|
+
* catalogue actually needs are small — a fallback chain, `{named}`
|
|
15
|
+
* interpolation and the `singular | plural` pipe — and they are below, tested,
|
|
16
|
+
* in eighty lines. The message format is deliberately vue-i18n's, so the
|
|
17
|
+
* catalogue can be handed to the cockpit's tooling (or to a translator working
|
|
18
|
+
* from it) without a conversion step.
|
|
19
|
+
*
|
|
20
|
+
* **Where the language comes from.** {@link studioLocale}, which is the seam
|
|
21
|
+
* PO-229 built for the API's bilingual text — the host sets it once with
|
|
22
|
+
* `useIntegrationsLocale().setLocale()` or `studioIntegrations.locale`, and both
|
|
23
|
+
* halves of a screen follow the same value. Reading it here is also what makes
|
|
24
|
+
* the chrome reactive: every call site is inside a template or a `computed()`,
|
|
25
|
+
* so each becomes a subscriber to the language without a line changing.
|
|
26
|
+
*/
|
|
27
|
+
/** The languages the module ships. Anything else falls back to English. */
|
|
28
|
+
export declare const MESSAGES: Readonly<Record<string, Record<string, string>>>;
|
|
29
|
+
/** The language every key is guaranteed to exist in. */
|
|
30
|
+
export declare const FALLBACK_LOCALE = "en";
|
|
31
|
+
/** Interpolation values for a message with `{placeholders}`. */
|
|
32
|
+
export type TranslateNamed = Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* The call shapes a caller may use — vue-i18n's, so that moving a key between
|
|
35
|
+
* this catalogue and the cockpit's is a copy and not a rewrite.
|
|
36
|
+
*/
|
|
37
|
+
export interface Translate {
|
|
38
|
+
(key: string): string;
|
|
39
|
+
(key: string, named: TranslateNamed): string;
|
|
40
|
+
(key: string, plural: number): string;
|
|
41
|
+
(key: string, plural: number, named: TranslateNamed): string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Which catalogue answers for a BCP-47 tag: the exact tag, then the language
|
|
45
|
+
* without its region (so `de-CH` reads `de` rather than English), then English.
|
|
46
|
+
*/
|
|
47
|
+
export declare function messagesFor(locale: string): Record<string, string>;
|
|
48
|
+
/**
|
|
49
|
+
* The raw message for a key, or `undefined`.
|
|
50
|
+
*
|
|
51
|
+
* An empty value counts as missing — a translator clearing a cell must not
|
|
52
|
+
* blank the UI, it must fall through to English.
|
|
53
|
+
*/
|
|
54
|
+
export declare function lookup(key: string, locale: string): string | undefined;
|
|
55
|
+
/** Replace `{name}` with the value given for it. An unknown name is left alone. */
|
|
56
|
+
export declare function interpolate(message: string, values: TranslateNamed): string;
|
|
57
|
+
/**
|
|
58
|
+
* Pick a branch out of `singular | plural`.
|
|
59
|
+
*
|
|
60
|
+
* vue-i18n's default rule, which is what the catalogue is written against: two
|
|
61
|
+
* branches select on `count === 1`, three add a zero branch. Both languages the
|
|
62
|
+
* module ships have two plural forms; the third branch exists so a catalogue
|
|
63
|
+
* that wants "no runs / one run / n runs" can say so without new machinery.
|
|
64
|
+
*
|
|
65
|
+
* A message without a pipe is returned whole — passing a count to a key that
|
|
66
|
+
* has no plural form is harmless, the reverse is not.
|
|
67
|
+
*/
|
|
68
|
+
export declare function selectPlural(message: string, count: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a key in a named language. `translate` is this, in the studio's.
|
|
71
|
+
*
|
|
72
|
+
* A key with no message anywhere is returned unchanged, which is the same
|
|
73
|
+
* promise the host bridge in `@revenexx/studio-shared` makes: a missing key is
|
|
74
|
+
* a visible defect on screen and never a blank, and never a crash.
|
|
75
|
+
*/
|
|
76
|
+
export declare function translateIn(locale: string, key: string, ...args: unknown[]): string;
|
|
77
|
+
/**
|
|
78
|
+
* Translate a key in the language the host set.
|
|
79
|
+
*
|
|
80
|
+
* Reads {@link studioLocale} on every call rather than closing over it, so a
|
|
81
|
+
* `computed()` that translated once re-runs on a language switch.
|
|
82
|
+
*/
|
|
83
|
+
export declare const translate: Translate;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { studioLocale } from "../utils/localize.js";
|
|
2
|
+
import de from "./messages/de.json" with { type: "json" };
|
|
3
|
+
import en from "./messages/en.json" with { type: "json" };
|
|
4
|
+
export const MESSAGES = Object.freeze({
|
|
5
|
+
en,
|
|
6
|
+
de
|
|
7
|
+
});
|
|
8
|
+
export const FALLBACK_LOCALE = "en";
|
|
9
|
+
export function messagesFor(locale) {
|
|
10
|
+
const wanted = (locale || "").toLowerCase();
|
|
11
|
+
const base = wanted.split("-")[0];
|
|
12
|
+
return MESSAGES[wanted] ?? MESSAGES[base] ?? MESSAGES[FALLBACK_LOCALE];
|
|
13
|
+
}
|
|
14
|
+
export function lookup(key, locale) {
|
|
15
|
+
const primary = messagesFor(locale)[key];
|
|
16
|
+
if (primary) return primary;
|
|
17
|
+
const fallback = MESSAGES[FALLBACK_LOCALE][key];
|
|
18
|
+
return fallback || void 0;
|
|
19
|
+
}
|
|
20
|
+
export function interpolate(message, values) {
|
|
21
|
+
if (!message.includes("{")) return message;
|
|
22
|
+
return message.replace(/\{(\w+)\}/g, (whole, name) => name in values ? String(values[name]) : whole);
|
|
23
|
+
}
|
|
24
|
+
export function selectPlural(message, count) {
|
|
25
|
+
if (!message.includes("|")) return message;
|
|
26
|
+
const branches = message.split("|").map((part) => part.trim());
|
|
27
|
+
if (branches.length === 2) return branches[count === 1 ? 0 : 1];
|
|
28
|
+
if (branches.length >= 3) return branches[count === 0 ? 0 : count === 1 ? 1 : 2];
|
|
29
|
+
return branches[0];
|
|
30
|
+
}
|
|
31
|
+
export function translateIn(locale, key, ...args) {
|
|
32
|
+
const message = lookup(key, locale);
|
|
33
|
+
if (message === void 0) return key;
|
|
34
|
+
const [first, second] = args;
|
|
35
|
+
const count = typeof first === "number" ? first : void 0;
|
|
36
|
+
const named = typeof first === "object" && first !== null ? first : second;
|
|
37
|
+
const branch = count === void 0 ? message : selectPlural(message, count);
|
|
38
|
+
const values = count === void 0 ? named ?? {} : { n: count, count, ...named ?? {} };
|
|
39
|
+
return interpolate(branch, values);
|
|
40
|
+
}
|
|
41
|
+
export const translate = ((key, ...args) => translateIn(studioLocale(), key, ...args));
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
} from "#solar-icons";
|
|
12
12
|
import { Badge, Button } from "@revenexx/studio";
|
|
13
13
|
import { toast } from "vue-sonner";
|
|
14
|
-
|
|
14
|
+
const t = useIntegrationsT();
|
|
15
|
+
useHead({ title: () => t("integration.audit.head.title") });
|
|
15
16
|
const { entries, loading, error, hasMore, load, loadMore } = useIntegrationsAudit();
|
|
16
17
|
const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run"];
|
|
17
18
|
const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed"];
|
|
@@ -25,11 +26,28 @@ function actionTone(a) {
|
|
|
25
26
|
if (a === "delete" || a === "run.failed") return "bg-rose-500/10 text-rose-700 border-rose-500/20";
|
|
26
27
|
return "bg-violet-500/10 text-violet-700 border-violet-500/20";
|
|
27
28
|
}
|
|
29
|
+
const resourceTypeKeys = {
|
|
30
|
+
credential: "integration.audit.resourceType.credential",
|
|
31
|
+
secret: "integration.audit.resourceType.secret",
|
|
32
|
+
workflow: "integration.audit.resourceType.workflow",
|
|
33
|
+
trigger: "integration.audit.resourceType.trigger",
|
|
34
|
+
workflow_run: "integration.audit.resourceType.workflowRun"
|
|
35
|
+
};
|
|
36
|
+
const actionKeys = {
|
|
37
|
+
"create": "integration.audit.action.create",
|
|
38
|
+
"update": "integration.audit.action.update",
|
|
39
|
+
"delete": "integration.audit.action.delete",
|
|
40
|
+
"run.started": "integration.audit.action.runStarted",
|
|
41
|
+
"run.finished": "integration.audit.action.runFinished",
|
|
42
|
+
"run.failed": "integration.audit.action.runFailed"
|
|
43
|
+
};
|
|
28
44
|
function prettyAction(a) {
|
|
29
|
-
|
|
45
|
+
const key = actionKeys[a];
|
|
46
|
+
return key ? t(key) : a.replace(".", " ");
|
|
30
47
|
}
|
|
31
|
-
function prettyType(
|
|
32
|
-
|
|
48
|
+
function prettyType(rt) {
|
|
49
|
+
const key = resourceTypeKeys[rt];
|
|
50
|
+
return key ? t(key) : rt.replace("_", " ");
|
|
33
51
|
}
|
|
34
52
|
function resourceName(e) {
|
|
35
53
|
return e.metadata?.name || e.resource_id || "\u2014";
|
|
@@ -37,31 +55,31 @@ function resourceName(e) {
|
|
|
37
55
|
function fmtDate(iso) {
|
|
38
56
|
return iso ? new Date(iso).toLocaleString() : "\u2014";
|
|
39
57
|
}
|
|
40
|
-
const filterTabs = [
|
|
58
|
+
const filterTabs = computed(() => [
|
|
41
59
|
{
|
|
42
60
|
key: "resource",
|
|
43
|
-
label: "
|
|
61
|
+
label: t("integration.audit.table.column.resource"),
|
|
44
62
|
getter: (e) => e.resource_type,
|
|
45
|
-
options: resourceTypes.map((
|
|
63
|
+
options: resourceTypes.map((rt) => ({ value: rt, label: prettyType(rt) }))
|
|
46
64
|
},
|
|
47
65
|
{
|
|
48
66
|
key: "action",
|
|
49
|
-
label: "
|
|
67
|
+
label: t("integration.audit.table.column.action"),
|
|
50
68
|
getter: (e) => e.action,
|
|
51
69
|
options: actions.map((a) => ({ value: a, label: prettyAction(a) }))
|
|
52
70
|
}
|
|
53
|
-
];
|
|
54
|
-
const columns = [
|
|
55
|
-
{ key: "action", label: "
|
|
56
|
-
{ key: "resource", label: "
|
|
57
|
-
{ key: "actor", label: "
|
|
58
|
-
{ key: "when", label: "
|
|
59
|
-
];
|
|
71
|
+
]);
|
|
72
|
+
const columns = computed(() => [
|
|
73
|
+
{ key: "action", label: t("integration.audit.table.column.action"), width: "130px", sortable: true, sortGetter: (e) => e.action },
|
|
74
|
+
{ key: "resource", label: t("integration.audit.table.column.resource"), sortable: true, sortGetter: (e) => `${e.resource_type} ${resourceName(e)}`, searchGetter: (e) => `${e.resource_type} ${resourceName(e)} ${e.action} ${e.actor_sub ?? ""}` },
|
|
75
|
+
{ key: "actor", label: t("integration.audit.table.column.actor"), width: "170px", sortable: true, sortGetter: (e) => e.actor_sub ?? "" },
|
|
76
|
+
{ key: "when", label: t("integration.audit.table.column.when"), width: "180px", sortable: true, sortGetter: (e) => e.created_at }
|
|
77
|
+
]);
|
|
60
78
|
const selected = ref(null);
|
|
61
|
-
function copy(
|
|
62
|
-
if (
|
|
63
|
-
navigator.clipboard.writeText(
|
|
64
|
-
toast.success("
|
|
79
|
+
function copy(t2) {
|
|
80
|
+
if (t2 && navigator.clipboard) {
|
|
81
|
+
navigator.clipboard.writeText(t2);
|
|
82
|
+
toast.success(t("integration.audit.toast.copied"));
|
|
65
83
|
}
|
|
66
84
|
}
|
|
67
85
|
const panelEl = ref(null);
|
|
@@ -115,7 +133,7 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
115
133
|
|
|
116
134
|
<template>
|
|
117
135
|
<IntegrationsLayout>
|
|
118
|
-
<PageHeader title="
|
|
136
|
+
<PageHeader :title="t('integration.audit.header.title')" :description="t('integration.audit.header.description')" />
|
|
119
137
|
|
|
120
138
|
<div class="p-4 space-y-3">
|
|
121
139
|
<p v-if="error" class="text-sm text-rose-700">{{ error }}</p>
|
|
@@ -132,10 +150,10 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
132
150
|
:row-key="(e) => e.id"
|
|
133
151
|
:default-sort="{ column: 'when', direction: 'desc' }"
|
|
134
152
|
:pagination="false"
|
|
135
|
-
:search="{ placeholder: '
|
|
153
|
+
:search="{ placeholder: t('integration.audit.search.placeholder') }"
|
|
136
154
|
:filter-tabs="filterTabs"
|
|
137
155
|
:loading="loading"
|
|
138
|
-
empty-text="
|
|
156
|
+
:empty-text="t('integration.audit.state.empty')"
|
|
139
157
|
@row-click="(e) => selected = e"
|
|
140
158
|
>
|
|
141
159
|
<template #cell-action="{ row }">
|
|
@@ -151,7 +169,7 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
151
169
|
</div>
|
|
152
170
|
</template>
|
|
153
171
|
<template #cell-actor="{ row }">
|
|
154
|
-
<span class="text-sm font-mono text-muted-foreground truncate">{{ row.actor_sub ?? "system" }}</span>
|
|
172
|
+
<span class="text-sm font-mono text-muted-foreground truncate">{{ row.actor_sub ?? t("integration.audit.actor.system") }}</span>
|
|
155
173
|
</template>
|
|
156
174
|
<template #cell-when="{ row }">
|
|
157
175
|
<span class="text-sm text-muted-foreground">{{ fmtDate(row.created_at) }}</span>
|
|
@@ -159,7 +177,7 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
159
177
|
</DataTable>
|
|
160
178
|
|
|
161
179
|
<div v-if="hasMore" class="flex justify-center pt-1">
|
|
162
|
-
<Button variant="outline" size="sm" :disabled="loading" @click="loadMore()">{{ loading ? "
|
|
180
|
+
<Button variant="outline" size="sm" :disabled="loading" @click="loadMore()">{{ loading ? t("integration.common.state.loading") : t("integration.audit.action.loadMore") }}</Button>
|
|
163
181
|
</div>
|
|
164
182
|
</div>
|
|
165
183
|
|
|
@@ -178,7 +196,7 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
178
196
|
<button
|
|
179
197
|
type="button"
|
|
180
198
|
tabindex="-1"
|
|
181
|
-
aria-label="
|
|
199
|
+
:aria-label="t('integration.audit.panel.dismiss.aria')"
|
|
182
200
|
class="flex-1 bg-black/20 backdrop-blur-[1px]"
|
|
183
201
|
@click="selected = null"
|
|
184
202
|
/>
|
|
@@ -195,27 +213,27 @@ onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
|
195
213
|
ref="panelEl"
|
|
196
214
|
role="dialog"
|
|
197
215
|
aria-modal="true"
|
|
198
|
-
aria-label="
|
|
216
|
+
:aria-label="t('integration.audit.panel.title.aria')"
|
|
199
217
|
tabindex="-1"
|
|
200
218
|
class="drawer-panel w-full max-w-md overflow-y-auto border-l border-black/[0.06] bg-white shadow-xl outline-none"
|
|
201
219
|
>
|
|
202
220
|
<div class="sticky top-0 z-10 flex items-center gap-2 border-b border-black/[0.06] bg-white/90 backdrop-blur px-4 py-3">
|
|
203
221
|
<Badge variant="outline" :class="['border text-xs font-medium uppercase', actionTone(selected.action)]">{{ prettyAction(selected.action) }}</Badge>
|
|
204
222
|
<span class="text-sm font-semibold text-[#0B0522] capitalize">{{ prettyType(selected.resource_type) }}</span>
|
|
205
|
-
<Button variant="ghost" size="sm" aria-label="
|
|
223
|
+
<Button variant="ghost" size="sm" :aria-label="t('integration.audit.panel.close.aria')" class="ml-auto h-7 w-7 p-0" @click="selected = null"><Close :size="15" /></Button>
|
|
206
224
|
</div>
|
|
207
225
|
<div class="p-4 space-y-4 text-sm">
|
|
208
226
|
<div class="space-y-1.5">
|
|
209
|
-
<div class="flex justify-between gap-2"><span class="text-muted-foreground">
|
|
210
|
-
<div v-if="selected.resource_id" class="flex justify-between gap-2"><span class="text-muted-foreground">
|
|
211
|
-
<div class="flex justify-between gap-2"><span class="text-muted-foreground">
|
|
212
|
-
<div class="flex justify-between gap-2"><span class="text-muted-foreground">
|
|
213
|
-
<div v-if="selected.tenant_id" class="flex justify-between gap-2"><span class="text-muted-foreground">
|
|
227
|
+
<div class="flex justify-between gap-2"><span class="text-muted-foreground">{{ t("integration.audit.table.column.resource") }}</span><span class="font-medium text-right">{{ resourceName(selected) }}</span></div>
|
|
228
|
+
<div v-if="selected.resource_id" class="flex justify-between gap-2"><span class="text-muted-foreground">{{ t("integration.audit.detail.id") }}</span><code class="font-mono text-xs">{{ selected.resource_id }}</code></div>
|
|
229
|
+
<div class="flex justify-between gap-2"><span class="text-muted-foreground">{{ t("integration.audit.table.column.actor") }}</span><code class="font-mono text-xs">{{ selected.actor_sub ?? t("integration.audit.actor.system") }}</code></div>
|
|
230
|
+
<div class="flex justify-between gap-2"><span class="text-muted-foreground">{{ t("integration.audit.table.column.when") }}</span><span>{{ fmtDate(selected.created_at) }}</span></div>
|
|
231
|
+
<div v-if="selected.tenant_id" class="flex justify-between gap-2"><span class="text-muted-foreground">{{ t("integration.audit.detail.tenant") }}</span><span>{{ selected.tenant_id }}</span></div>
|
|
214
232
|
</div>
|
|
215
233
|
<div v-if="selected.metadata && Object.keys(selected.metadata).length">
|
|
216
234
|
<div class="mb-1 flex items-center justify-between">
|
|
217
|
-
<span class="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
218
|
-
<button class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" @click="copy(JSON.stringify(selected.metadata, null, 2))"><CopyIcon :size="12" /> copy</button>
|
|
235
|
+
<span class="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">{{ t("integration.audit.detail.detailsHeading") }}</span>
|
|
236
|
+
<button class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" @click="copy(JSON.stringify(selected.metadata, null, 2))"><CopyIcon :size="12" /> {{ t("integration.audit.action.copy") }}</button>
|
|
219
237
|
</div>
|
|
220
238
|
<pre class="overflow-x-auto rounded-md bg-[#0B0522] p-3 text-xs leading-relaxed text-[#E6E1F5]"><code>{{ JSON.stringify(selected.metadata, null, 2) }}</code></pre>
|
|
221
239
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { onMounted, ref } from "vue";
|
|
3
3
|
definePageMeta({ layout: false });
|
|
4
|
+
const t = useIntegrationsT();
|
|
4
5
|
const closing = ref(true);
|
|
5
6
|
onMounted(() => {
|
|
6
7
|
try {
|
|
@@ -20,10 +21,10 @@ onMounted(() => {
|
|
|
20
21
|
<main class="flex min-h-screen items-center justify-center bg-white text-center">
|
|
21
22
|
<div class="space-y-2">
|
|
22
23
|
<h1 class="text-sm font-medium text-foreground">
|
|
23
|
-
|
|
24
|
+
{{ t("integration.credentials.oauthComplete.title") }}
|
|
24
25
|
</h1>
|
|
25
26
|
<p class="text-sm text-muted-foreground">
|
|
26
|
-
{{ closing ? "
|
|
27
|
+
{{ closing ? t("integration.credentials.oauthComplete.hint.open") : t("integration.credentials.oauthComplete.hint.closing") }}
|
|
27
28
|
</p>
|
|
28
29
|
</div>
|
|
29
30
|
</main>
|