@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.
Files changed (137) hide show
  1. package/CHANGELOG.md +643 -0
  2. package/README.md +31 -0
  3. package/dist/module.d.mts +17 -1
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +12 -3
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +32 -28
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +45 -38
  8. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
  9. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
  10. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
  11. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
  12. package/dist/runtime/components/Integrations/ExpressionInput.vue +21 -15
  13. package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
  14. package/dist/runtime/components/Integrations/Layout.vue +28 -3
  15. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
  16. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +30 -16
  17. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
  18. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -8
  19. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
  20. package/dist/runtime/components/Integrations/NodeConfigField.vue +45 -42
  21. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
  22. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
  23. package/dist/runtime/components/Integrations/NodeInspector.vue +315 -221
  24. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
  25. package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
  26. package/dist/runtime/components/Integrations/NodeOutputSchema.vue +37 -10
  27. package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
  28. package/dist/runtime/components/Integrations/NodePalette.vue +40 -35
  29. package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
  30. package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
  31. package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
  32. package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
  33. package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
  34. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
  35. package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
  36. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
  37. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
  38. package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
  39. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
  40. package/dist/runtime/components/Integrations/StickyNoteView.vue +82 -34
  41. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
  42. package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
  43. package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
  44. package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
  45. package/dist/runtime/components/Integrations/TemplateCard.vue +18 -8
  46. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +22 -12
  47. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
  48. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +41 -39
  49. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
  50. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
  51. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
  52. package/dist/runtime/components/Integrations/WorkflowEditor.vue +131 -87
  53. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
  54. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
  55. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +27 -24
  56. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
  57. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
  58. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +76 -46
  59. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +55 -43
  60. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
  61. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +55 -43
  62. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
  63. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
  64. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -13
  65. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +79 -39
  66. package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
  67. package/dist/runtime/composables/integrationsApi.js +4 -2
  68. package/dist/runtime/composables/useCredentialTypes.js +4 -2
  69. package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
  70. package/dist/runtime/composables/useIntegrationsCredentials.js +8 -6
  71. package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
  72. package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
  73. package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
  74. package/dist/runtime/composables/useIntegrationsNodes.js +14 -2
  75. package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
  76. package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
  77. package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
  78. package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
  79. package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
  80. package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
  81. package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
  82. package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
  83. package/dist/runtime/composables/useIntegrationsT.js +9 -0
  84. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
  85. package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
  86. package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
  87. package/dist/runtime/composables/useIntegrationsTriggers.js +21 -8
  88. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
  89. package/dist/runtime/composables/useIntegrationsWorkflows.js +30 -7
  90. package/dist/runtime/composables/useNodeConfigResolve.js +4 -2
  91. package/dist/runtime/data/integrationNodeCatalog.d.ts +63 -1
  92. package/dist/runtime/data/integrationNodeCatalog.js +22 -4
  93. package/dist/runtime/data/integrationTemplates.d.ts +9 -0
  94. package/dist/runtime/data/integrationTemplates.js +7 -1
  95. package/dist/runtime/data/nodeInputData.d.ts +58 -1
  96. package/dist/runtime/data/nodeInputData.js +30 -5
  97. package/dist/runtime/data/payloadForm.js +2 -1
  98. package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
  99. package/dist/runtime/data/triggerGlyphs.js +11 -0
  100. package/dist/runtime/data/workflowIconCatalog.js +2 -1
  101. package/dist/runtime/i18n/messages/de.json +1224 -0
  102. package/dist/runtime/i18n/messages/en.json +1224 -0
  103. package/dist/runtime/i18n/translate.d.ts +83 -0
  104. package/dist/runtime/i18n/translate.js +41 -0
  105. package/dist/runtime/pages/integrations/audit.vue +52 -34
  106. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
  107. package/dist/runtime/pages/integrations/credentials.vue +73 -52
  108. package/dist/runtime/pages/integrations/index.vue +66 -73
  109. package/dist/runtime/pages/integrations/runs.vue +33 -21
  110. package/dist/runtime/pages/integrations/schedules.vue +49 -28
  111. package/dist/runtime/pages/integrations/secrets.vue +34 -34
  112. package/dist/runtime/pages/integrations/templates/[slug].vue +89 -72
  113. package/dist/runtime/pages/integrations/templates/index.vue +35 -27
  114. package/dist/runtime/pages/integrations/workflows/[id].vue +351 -197
  115. package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
  116. package/dist/runtime/plugins/locale.d.ts +10 -0
  117. package/dist/runtime/plugins/locale.js +6 -0
  118. package/dist/runtime/plugins/realtime.client.d.ts +32 -0
  119. package/dist/runtime/plugins/realtime.client.js +4 -0
  120. package/dist/runtime/types/integrations.d.ts +25 -1
  121. package/dist/runtime/utils/cockpitSources.d.ts +22 -3
  122. package/dist/runtime/utils/credentialState.d.ts +30 -2
  123. package/dist/runtime/utils/credentialState.js +26 -11
  124. package/dist/runtime/utils/localize.d.ts +52 -5
  125. package/dist/runtime/utils/localize.js +47 -9
  126. package/dist/runtime/utils/pollBudget.d.ts +72 -0
  127. package/dist/runtime/utils/pollBudget.js +17 -0
  128. package/dist/runtime/utils/schedule.d.ts +0 -25
  129. package/dist/runtime/utils/schedule.js +37 -25
  130. package/dist/runtime/utils/servedPaths.d.ts +22 -0
  131. package/dist/runtime/utils/servedPaths.js +3 -0
  132. package/dist/runtime/utils/statusStyle.d.ts +26 -2
  133. package/dist/runtime/utils/statusStyle.js +9 -1
  134. package/dist/runtime/utils/validationFindings.d.ts +2 -2
  135. package/dist/runtime/utils/validationFindings.js +4 -3
  136. package/dist/types.d.mts +2 -6
  137. 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
- useHead({ title: "Audit Log \xB7 Integration Studio" });
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
- return a.replace(".", " ");
45
+ const key = actionKeys[a];
46
+ return key ? t(key) : a.replace(".", " ");
30
47
  }
31
- function prettyType(t) {
32
- return t.replace("_", " ");
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: "Resource",
61
+ label: t("integration.audit.table.column.resource"),
44
62
  getter: (e) => e.resource_type,
45
- options: resourceTypes.map((t) => ({ value: t, label: prettyType(t) }))
63
+ options: resourceTypes.map((rt) => ({ value: rt, label: prettyType(rt) }))
46
64
  },
47
65
  {
48
66
  key: "action",
49
- label: "Action",
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: "Action", width: "130px", sortable: true, sortGetter: (e) => e.action },
56
- { key: "resource", label: "Resource", sortable: true, sortGetter: (e) => `${e.resource_type} ${resourceName(e)}`, searchGetter: (e) => `${e.resource_type} ${resourceName(e)} ${e.action} ${e.actor_sub ?? ""}` },
57
- { key: "actor", label: "Actor", width: "170px", sortable: true, sortGetter: (e) => e.actor_sub ?? "" },
58
- { key: "when", label: "When", width: "180px", sortable: true, sortGetter: (e) => e.created_at }
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(t) {
62
- if (t && navigator.clipboard) {
63
- navigator.clipboard.writeText(t);
64
- toast.success("Copied");
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="Audit Log" description="Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers and runs." />
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: 'Search action, resource, actor\u2026' }"
153
+ :search="{ placeholder: t('integration.audit.search.placeholder') }"
136
154
  :filter-tabs="filterTabs"
137
155
  :loading="loading"
138
- empty-text="No audit entries match."
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 ? "Loading\u2026" : "Load more" }}</Button>
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="Dismiss entry details"
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="Audit entry details"
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="Close entry details" class="ml-auto h-7 w-7 p-0" @click="selected = null"><Close :size="15" /></Button>
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">Resource</span><span class="font-medium text-right">{{ resourceName(selected) }}</span></div>
210
- <div v-if="selected.resource_id" class="flex justify-between gap-2"><span class="text-muted-foreground">ID</span><code class="font-mono text-xs">{{ selected.resource_id }}</code></div>
211
- <div class="flex justify-between gap-2"><span class="text-muted-foreground">Actor</span><code class="font-mono text-xs">{{ selected.actor_sub ?? "system" }}</code></div>
212
- <div class="flex justify-between gap-2"><span class="text-muted-foreground">When</span><span>{{ fmtDate(selected.created_at) }}</span></div>
213
- <div v-if="selected.tenant_id" class="flex justify-between gap-2"><span class="text-muted-foreground">Tenant</span><span>{{ selected.tenant_id }}</span></div>
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">Details</span>
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
- Connection complete
24
+ {{ t("integration.credentials.oauthComplete.title") }}
24
25
  </h1>
25
26
  <p class="text-sm text-muted-foreground">
26
- {{ closing ? "You can close this window." : "Done \u2014 closing\u2026" }}
27
+ {{ closing ? t("integration.credentials.oauthComplete.hint.open") : t("integration.credentials.oauthComplete.hint.closing") }}
27
28
  </p>
28
29
  </div>
29
30
  </main>