@revenexx/studio-integrations 0.1.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 (106) hide show
  1. package/README.md +51 -0
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +45 -0
  5. package/dist/runtime/components/Integrations/Layout.d.vue.ts +12 -0
  6. package/dist/runtime/components/Integrations/Layout.vue +20 -0
  7. package/dist/runtime/components/Integrations/Layout.vue.d.ts +12 -0
  8. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +16 -0
  9. package/dist/runtime/components/Integrations/NodeInspector.vue +450 -0
  10. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +16 -0
  11. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +2 -0
  12. package/dist/runtime/components/Integrations/NodePalette.vue +140 -0
  13. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +2 -0
  14. package/dist/runtime/components/Integrations/RunDetailDialog.d.vue.ts +11 -0
  15. package/dist/runtime/components/Integrations/RunDetailDialog.vue +169 -0
  16. package/dist/runtime/components/Integrations/RunDetailDialog.vue.d.ts +11 -0
  17. package/dist/runtime/components/Integrations/Sidebar.d.vue.ts +2 -0
  18. package/dist/runtime/components/Integrations/Sidebar.vue +32 -0
  19. package/dist/runtime/components/Integrations/Sidebar.vue.d.ts +2 -0
  20. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +23 -0
  21. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +86 -0
  22. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +23 -0
  23. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +14 -0
  24. package/dist/runtime/components/Integrations/WorkflowEditor.vue +419 -0
  25. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +14 -0
  26. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +11 -0
  27. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +200 -0
  28. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +11 -0
  29. package/dist/runtime/components/Integrations/WorkflowTriggerView.d.vue.ts +8 -0
  30. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +82 -0
  31. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue.d.ts +8 -0
  32. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +6 -0
  33. package/dist/runtime/components/Integrations/WorkflowsTable.vue +268 -0
  34. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +6 -0
  35. package/dist/runtime/components/Integrations/workflowLayout.d.ts +17 -0
  36. package/dist/runtime/components/Integrations/workflowLayout.js +29 -0
  37. package/dist/runtime/composables/integrationsApi.d.ts +13 -0
  38. package/dist/runtime/composables/integrationsApi.js +55 -0
  39. package/dist/runtime/composables/useCredentialTypes.d.ts +19 -0
  40. package/dist/runtime/composables/useCredentialTypes.js +47 -0
  41. package/dist/runtime/composables/useIntegrationsAudit.d.ts +26 -0
  42. package/dist/runtime/composables/useIntegrationsAudit.js +39 -0
  43. package/dist/runtime/composables/useIntegrationsCredentials.d.ts +43 -0
  44. package/dist/runtime/composables/useIntegrationsCredentials.js +98 -0
  45. package/dist/runtime/composables/useIntegrationsFocus.d.ts +16 -0
  46. package/dist/runtime/composables/useIntegrationsFocus.js +17 -0
  47. package/dist/runtime/composables/useIntegrationsNodes.d.ts +6 -0
  48. package/dist/runtime/composables/useIntegrationsNodes.js +32 -0
  49. package/dist/runtime/composables/useIntegrationsRuns.d.ts +31 -0
  50. package/dist/runtime/composables/useIntegrationsRuns.js +109 -0
  51. package/dist/runtime/composables/useIntegrationsSchedules.d.ts +14 -0
  52. package/dist/runtime/composables/useIntegrationsSchedules.js +32 -0
  53. package/dist/runtime/composables/useIntegrationsSecrets.d.ts +27 -0
  54. package/dist/runtime/composables/useIntegrationsSecrets.js +76 -0
  55. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +19 -0
  56. package/dist/runtime/composables/useIntegrationsTemplates.js +141 -0
  57. package/dist/runtime/composables/useIntegrationsTriggers.d.ts +39 -0
  58. package/dist/runtime/composables/useIntegrationsTriggers.js +39 -0
  59. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +28 -0
  60. package/dist/runtime/composables/useIntegrationsWorkflows.js +152 -0
  61. package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
  62. package/dist/runtime/data/integrationNodeCatalog.js +67 -0
  63. package/dist/runtime/data/integrationRuns.d.ts +7 -0
  64. package/dist/runtime/data/integrationRuns.js +109 -0
  65. package/dist/runtime/data/integrationTemplates.d.ts +11 -0
  66. package/dist/runtime/data/integrationTemplates.js +9 -0
  67. package/dist/runtime/data/integrationWorkflowSamples.d.ts +18 -0
  68. package/dist/runtime/data/integrationWorkflowSamples.js +318 -0
  69. package/dist/runtime/pages/integrations/audit.d.vue.ts +2 -0
  70. package/dist/runtime/pages/integrations/audit.vue +156 -0
  71. package/dist/runtime/pages/integrations/audit.vue.d.ts +2 -0
  72. package/dist/runtime/pages/integrations/credentials/oauth-complete.d.vue.ts +2 -0
  73. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +28 -0
  74. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue.d.ts +2 -0
  75. package/dist/runtime/pages/integrations/credentials.d.vue.ts +2 -0
  76. package/dist/runtime/pages/integrations/credentials.vue +403 -0
  77. package/dist/runtime/pages/integrations/credentials.vue.d.ts +2 -0
  78. package/dist/runtime/pages/integrations/index.d.vue.ts +2 -0
  79. package/dist/runtime/pages/integrations/index.vue +226 -0
  80. package/dist/runtime/pages/integrations/index.vue.d.ts +2 -0
  81. package/dist/runtime/pages/integrations/runs.d.vue.ts +2 -0
  82. package/dist/runtime/pages/integrations/runs.vue +100 -0
  83. package/dist/runtime/pages/integrations/runs.vue.d.ts +2 -0
  84. package/dist/runtime/pages/integrations/schedules.d.vue.ts +2 -0
  85. package/dist/runtime/pages/integrations/schedules.vue +111 -0
  86. package/dist/runtime/pages/integrations/schedules.vue.d.ts +2 -0
  87. package/dist/runtime/pages/integrations/secrets.d.vue.ts +2 -0
  88. package/dist/runtime/pages/integrations/secrets.vue +248 -0
  89. package/dist/runtime/pages/integrations/secrets.vue.d.ts +2 -0
  90. package/dist/runtime/pages/integrations/workflows/[id].d.vue.ts +2 -0
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +326 -0
  92. package/dist/runtime/pages/integrations/workflows/[id].vue.d.ts +2 -0
  93. package/dist/runtime/pages/integrations/workflows/index.d.vue.ts +2 -0
  94. package/dist/runtime/pages/integrations/workflows/index.vue +31 -0
  95. package/dist/runtime/pages/integrations/workflows/index.vue.d.ts +2 -0
  96. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +2 -0
  97. package/dist/runtime/pages/integrations/workflows/new.vue +153 -0
  98. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +2 -0
  99. package/dist/runtime/types/integrations.d.ts +427 -0
  100. package/dist/runtime/types/integrations.js +0 -0
  101. package/dist/runtime/utils/localize.d.ts +9 -0
  102. package/dist/runtime/utils/localize.js +21 -0
  103. package/dist/runtime/utils/statusStyle.d.ts +15 -0
  104. package/dist/runtime/utils/statusStyle.js +78 -0
  105. package/dist/types.d.mts +9 -0
  106. package/package.json +46 -0
@@ -0,0 +1,100 @@
1
+ <script setup>
2
+ import { ClockCircle as Clock, Play, LinkRound as Webhook } from "#solar-icons";
3
+ import { Badge } from "@revenexx/studio";
4
+ import RunDetailDialog from "../../components/Integrations/RunDetailDialog.vue";
5
+ import { statusClass } from "../../utils/statusStyle";
6
+ useHead({ title: "Runs \xB7 Integration Studio" });
7
+ const { runs, load } = useIntegrationsRuns();
8
+ onMounted(() => {
9
+ void load();
10
+ });
11
+ const detailOpen = ref(false);
12
+ const selectedRun = ref(null);
13
+ function openRun(r) {
14
+ selectedRun.value = r;
15
+ detailOpen.value = true;
16
+ }
17
+ function triggerIcon(type) {
18
+ return type === "schedule" ? Clock : type === "webhook" ? Webhook : Play;
19
+ }
20
+ function formatRelative(iso) {
21
+ if (!iso) return "\u2014";
22
+ const ms = Date.now() - new Date(iso).getTime();
23
+ if (ms < 6e4) return `${Math.max(1, Math.floor(ms / 1e3))}s ago`;
24
+ if (ms < 36e5) return `${Math.floor(ms / 6e4)}m ago`;
25
+ if (ms < 864e5) return `${Math.floor(ms / 36e5)}h ago`;
26
+ return `${Math.floor(ms / 864e5)}d ago`;
27
+ }
28
+ function formatDuration(ms) {
29
+ if (ms == null) return "\u2014";
30
+ if (ms < 1e3) return `${ms}ms`;
31
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
32
+ return `${(ms / 6e4).toFixed(1)}m`;
33
+ }
34
+ const columns = [
35
+ { key: "status", label: "Status", width: "100px", sortable: true, sortGetter: (r) => r.status },
36
+ { key: "workflow", label: "Workflow", sortable: true, sortGetter: (r) => r.workflowName.toLowerCase(), searchGetter: (r) => `${r.workflowName} ${r.id}` },
37
+ { key: "trigger", label: "Trigger", width: "120px", sortable: true, sortGetter: (r) => r.triggerType },
38
+ { key: "started", label: "Started", width: "120px", sortable: true, sortGetter: (r) => r.startedAt },
39
+ { key: "duration", label: "Duration", width: "100px", sortable: true, sortGetter: (r) => r.durationMs ?? -1 },
40
+ { key: "steps", label: "Steps", width: "80px", sortable: true, sortGetter: (r) => r.steps.length }
41
+ ];
42
+ </script>
43
+
44
+ <template>
45
+ <IntegrationsLayout>
46
+ <PageHeader
47
+ title="Runs"
48
+ description="Execution history across every workflow — sourced from Temporal."
49
+ />
50
+
51
+ <div class="p-4">
52
+ <DataTable
53
+ :data="runs"
54
+ :columns="columns"
55
+ :row-key="(r) => r.id"
56
+ :default-sort="{ column: 'started', direction: 'desc' }"
57
+ :search="{ placeholder: 'Search runs\u2026' }"
58
+ :filter="true"
59
+ empty-text="No runs yet — kick off a workflow to populate this view."
60
+ @row-click="(r) => openRun(r)"
61
+ >
62
+ <template #cell-status="{ row: run }">
63
+ <Badge variant="outline" :class="['text-sm border', statusClass(run.status)]">
64
+ {{ run.status }}
65
+ </Badge>
66
+ </template>
67
+
68
+ <template #cell-workflow="{ row: run }">
69
+ <div class="min-w-0">
70
+ <div class="font-medium text-sm truncate">{{ run.workflowName }}</div>
71
+ <div class="font-mono text-sm text-muted-foreground truncate">{{ run.id }}</div>
72
+ </div>
73
+ </template>
74
+
75
+ <template #cell-trigger="{ row: run }">
76
+ <div class="flex items-center gap-1 text-sm text-muted-foreground">
77
+ <component :is="triggerIcon(run.triggerType)" :size="11" />
78
+ {{ run.triggerType }}
79
+ </div>
80
+ </template>
81
+
82
+ <template #cell-started="{ row: run }">
83
+ <span class="text-sm text-muted-foreground tabular-nums">{{ formatRelative(run.startedAt) }}</span>
84
+ </template>
85
+
86
+ <template #cell-duration="{ row: run }">
87
+ <span class="text-sm tabular-nums">{{ formatDuration(run.durationMs) }}</span>
88
+ </template>
89
+
90
+ <template #cell-steps="{ row: run }">
91
+ <span class="text-sm text-muted-foreground tabular-nums">
92
+ {{ run.steps.filter((s) => s.status === "succeeded").length }} / {{ run.steps.length }}
93
+ </span>
94
+ </template>
95
+ </DataTable>
96
+ </div>
97
+
98
+ <RunDetailDialog v-model:open="detailOpen" :run="selectedRun" />
99
+ </IntegrationsLayout>
100
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,111 @@
1
+ <script setup>
2
+ import { ClockCircle as Clock, Globus as Globe } from "#solar-icons";
3
+ import { Badge } from "@revenexx/studio";
4
+ useHead({ title: "Schedules \xB7 Integration Studio" });
5
+ const router = useRouter();
6
+ const { entries, activeCount, load } = useIntegrationsSchedules();
7
+ onMounted(() => {
8
+ void load();
9
+ });
10
+ function describeCron(cron) {
11
+ const parts = cron.split(/\s+/);
12
+ if (parts.length !== 5) return cron;
13
+ const [m, h, dom, mon, dow] = parts;
14
+ if (cron === "0 * * * *") return "every hour";
15
+ if (m === "0" && /^\d+$/.test(h ?? "") && dom === "*" && mon === "*" && dow === "*") return `daily at ${h?.padStart(2, "0")}:00`;
16
+ if (m === "0" && h === "0" && dom === "*" && mon === "*" && dow === "*") return "daily at midnight";
17
+ if (dom !== "*" && mon === "*") return `on day ${dom} of each month`;
18
+ if (dow !== "*" && dow !== "?") return `weekly on ${dow}`;
19
+ return cron;
20
+ }
21
+ function formatRelative(iso) {
22
+ if (!iso) return "\u2014";
23
+ const ms = new Date(iso).getTime() - Date.now();
24
+ if (Math.abs(ms) < 6e4) return ms > 0 ? "in <1m" : "just now";
25
+ if (Math.abs(ms) < 36e5) {
26
+ const mins = Math.round(Math.abs(ms) / 6e4);
27
+ return ms > 0 ? `in ${mins}m` : `${mins}m ago`;
28
+ }
29
+ if (Math.abs(ms) < 864e5) {
30
+ const hrs = Math.round(Math.abs(ms) / 36e5);
31
+ return ms > 0 ? `in ${hrs}h` : `${hrs}h ago`;
32
+ }
33
+ return new Date(iso).toLocaleString(void 0, { month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" });
34
+ }
35
+ const columns = [
36
+ { key: "workflow", label: "Workflow", sortable: true, sortGetter: (e) => e.workflowName.toLowerCase(), searchGetter: (e) => `${e.workflowName} ${e.cron} ${e.timezone}` },
37
+ { key: "cron", label: "Cron", width: "180px", sortable: true, sortGetter: (e) => e.cron },
38
+ { key: "timezone", label: "Timezone", width: "140px", sortable: true, sortGetter: (e) => e.timezone },
39
+ { key: "nextRun", label: "Next run", width: "140px", sortable: true, sortGetter: (e) => e.nextRunAt ?? "" },
40
+ { key: "lastRun", label: "Last run", width: "140px", sortable: true, sortGetter: (e) => e.lastRunAt ?? "" },
41
+ { key: "status", label: "Status", width: "80px", sortable: true, sortGetter: (e) => e.active ? "live" : "paused" }
42
+ ];
43
+ </script>
44
+
45
+ <template>
46
+ <IntegrationsLayout>
47
+ <PageHeader
48
+ title="Schedules"
49
+ description="Every cron-driven trigger across your workflows. Click a row to open the workflow."
50
+ >
51
+ <template #actions>
52
+ <span class="text-sm text-muted-foreground">
53
+ <strong class="text-foreground">{{ activeCount }}</strong> active
54
+ </span>
55
+ </template>
56
+ </PageHeader>
57
+
58
+ <div class="p-4">
59
+ <DataTable
60
+ :data="entries"
61
+ :columns="columns"
62
+ :row-key="(e2) => e2.id"
63
+ :default-sort="{ column: 'nextRun', direction: 'asc' }"
64
+ :search="{ placeholder: 'Search by workflow, cron, timezone\u2026' }"
65
+ :filter="true"
66
+ empty-text="No schedules yet. Add a schedule trigger to any workflow."
67
+ @row-click="(e2) => router.push(`/integrations/workflows/${e2.workflowId}`)"
68
+ >
69
+ <template #cell-workflow="{ row: e }">
70
+ <div class="flex items-center gap-2 min-w-0">
71
+ <div class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
72
+ <Clock :size="14" />
73
+ </div>
74
+ <div class="min-w-0">
75
+ <div class="font-medium text-sm truncate">{{ e.workflowName }}</div>
76
+ <div class="text-sm text-muted-foreground truncate">{{ describeCron(e.cron) }}</div>
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <template #cell-cron="{ row: e }">
82
+ <code class="font-mono text-sm truncate">{{ e.cron }}</code>
83
+ </template>
84
+
85
+ <template #cell-timezone="{ row: e }">
86
+ <div class="flex items-center gap-1 text-sm text-muted-foreground">
87
+ <Globe :size="11" />
88
+ {{ e.timezone }}
89
+ </div>
90
+ </template>
91
+
92
+ <template #cell-nextRun="{ row: e }">
93
+ <span class="text-sm tabular-nums">{{ formatRelative(e.nextRunAt) }}</span>
94
+ </template>
95
+
96
+ <template #cell-lastRun="{ row: e }">
97
+ <span class="text-sm tabular-nums text-muted-foreground">{{ formatRelative(e.lastRunAt) }}</span>
98
+ </template>
99
+
100
+ <template #cell-status="{ row: e }">
101
+ <Badge v-if="e.active" variant="outline" class="bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-sm">
102
+ live
103
+ </Badge>
104
+ <Badge v-else variant="outline" class="text-sm">
105
+ paused
106
+ </Badge>
107
+ </template>
108
+ </DataTable>
109
+ </div>
110
+ </IntegrationsLayout>
111
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,248 @@
1
+ <script setup>
2
+ import { Eye, EyeClosed as EyeOff, Key as KeyRound, RefreshCircle as Loader2, MenuDots as MoreHorizontal, AddCircle as Plus, TrashBinMinimalistic as Trash2 } from "#solar-icons";
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogFooter,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ Button,
11
+ Dialog,
12
+ DialogContent,
13
+ DialogDescription,
14
+ DialogFooter,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ DialogTrigger,
18
+ DropdownMenu,
19
+ DropdownMenuContent,
20
+ DropdownMenuItem,
21
+ DropdownMenuLabel,
22
+ DropdownMenuSeparator,
23
+ DropdownMenuTrigger,
24
+ Input,
25
+ Label
26
+ } from "@revenexx/studio";
27
+ useHead({ title: "Secrets \xB7 Revenexx Cockpit" });
28
+ const { secrets, loading, error, isValidKey, upsert, remove, load } = useIntegrationsSecrets();
29
+ onMounted(() => {
30
+ void load();
31
+ });
32
+ const createOpen = ref(false);
33
+ const newKey = ref("");
34
+ const newValue = ref("");
35
+ const showValue = ref(false);
36
+ const saving = ref(false);
37
+ const formError = ref(null);
38
+ function openCreate() {
39
+ newKey.value = "";
40
+ newValue.value = "";
41
+ showValue.value = false;
42
+ formError.value = null;
43
+ createOpen.value = true;
44
+ }
45
+ async function submitCreate() {
46
+ const key = newKey.value.trim();
47
+ if (!isValidKey(key)) {
48
+ formError.value = "Key must match ^[A-Z0-9_]+$ (uppercase letters, digits, underscores).";
49
+ return;
50
+ }
51
+ if (!newValue.value) {
52
+ formError.value = "Value is required.";
53
+ return;
54
+ }
55
+ saving.value = true;
56
+ try {
57
+ await upsert(key, newValue.value);
58
+ createOpen.value = false;
59
+ } catch (e) {
60
+ formError.value = e.message;
61
+ } finally {
62
+ saving.value = false;
63
+ }
64
+ }
65
+ const pendingDelete = ref(null);
66
+ const deleting = ref(false);
67
+ async function confirmDelete() {
68
+ if (!pendingDelete.value) return;
69
+ deleting.value = true;
70
+ await remove(pendingDelete.value.key);
71
+ deleting.value = false;
72
+ pendingDelete.value = null;
73
+ }
74
+ function formatDateTime(iso) {
75
+ if (!iso) return "\u2014";
76
+ try {
77
+ return new Date(iso).toLocaleString(void 0, { month: "short", day: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
78
+ } catch {
79
+ return iso;
80
+ }
81
+ }
82
+ const columns = [
83
+ { key: "key", label: "Key", sortable: true, sortGetter: (s) => s.key.toLowerCase() },
84
+ { key: "created", label: "Created", width: "160px", sortable: true, sortGetter: (s) => s.created_at },
85
+ { key: "updated", label: "Updated", width: "160px", sortable: true, sortGetter: (s) => s.updated_at },
86
+ { key: "actions", label: "", width: "40px", toggleable: false }
87
+ ];
88
+ </script>
89
+
90
+ <template>
91
+ <IntegrationsLayout>
92
+ <PageHeader
93
+ title="Secrets"
94
+ description="Per-tenant encrypted key/value store. Values are encrypted at rest and never returned by the API."
95
+ >
96
+ <template #actions>
97
+ <Dialog v-model:open="createOpen">
98
+ <DialogTrigger as-child>
99
+ <Button class="h-7 text-sm" @click="openCreate">
100
+ <Plus :size="14" />
101
+ New secret
102
+ </Button>
103
+ </DialogTrigger>
104
+ <DialogContent>
105
+ <DialogHeader>
106
+ <DialogTitle>Create a secret</DialogTitle>
107
+ <DialogDescription class="text-sm">
108
+ Once stored, the value is encrypted with AES-256-GCM and never echoed back. You can overwrite it later, but to read it you have to know it.
109
+ </DialogDescription>
110
+ </DialogHeader>
111
+
112
+ <div class="space-y-4 py-2">
113
+ <div class="space-y-1.5">
114
+ <Label for="sec-key">Key</Label>
115
+ <Input
116
+ id="sec-key"
117
+ v-model="newKey"
118
+ placeholder="STRIPE_API_KEY"
119
+ class="font-mono bg-white"
120
+ autocomplete="off"
121
+ />
122
+ <p class="text-sm text-muted-foreground">
123
+ Pattern: <code class="font-mono">^[A-Z0-9_]+$</code>
124
+ </p>
125
+ </div>
126
+ <div class="space-y-1.5">
127
+ <Label for="sec-value">Value</Label>
128
+ <div class="relative">
129
+ <Input
130
+ id="sec-value"
131
+ v-model="newValue"
132
+ :type="showValue ? 'text' : 'password'"
133
+ class="font-mono bg-white pr-9"
134
+ autocomplete="off"
135
+ />
136
+ <button
137
+ type="button"
138
+ class="absolute right-1 top-1/2 -translate-y-1/2 flex size-7 items-center justify-center rounded text-muted-foreground hover:text-foreground"
139
+ @click="showValue = !showValue"
140
+ >
141
+ <component :is="showValue ? EyeOff : Eye" :size="14" />
142
+ </button>
143
+ </div>
144
+ </div>
145
+
146
+ <div v-if="formError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
147
+ {{ formError }}
148
+ </div>
149
+ </div>
150
+
151
+ <DialogFooter>
152
+ <Button variant="outline" :disabled="saving" @click="createOpen = false">
153
+ Cancel
154
+ </Button>
155
+ <Button :disabled="saving" @click="submitCreate">
156
+ <Loader2 v-if="saving" :size="14" class="animate-spin" />
157
+ {{ saving ? "Saving\u2026" : "Save" }}
158
+ </Button>
159
+ </DialogFooter>
160
+ </DialogContent>
161
+ </Dialog>
162
+ </template>
163
+ </PageHeader>
164
+
165
+ <div class="p-4">
166
+ <div v-if="error" class="mb-3 rounded-lg border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
167
+ {{ error }}
168
+ </div>
169
+ <div v-if="loading && !secrets.length" class="flex items-center gap-2 px-1 py-3 text-sm text-muted-foreground">
170
+ <Loader2 :size="14" class="animate-spin" /> Loading secrets…
171
+ </div>
172
+ <DataTable
173
+ :data="secrets"
174
+ :columns="columns"
175
+ :row-key="(s2) => s2.key"
176
+ :default-sort="{ column: 'key', direction: 'asc' }"
177
+ :search="{ placeholder: 'Search keys\u2026' }"
178
+ :filter="true"
179
+ empty-text="No secrets yet."
180
+ >
181
+ <template #cell-key="{ row: s }">
182
+ <div class="flex items-center gap-2 min-w-0">
183
+ <div class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
184
+ <KeyRound :size="14" />
185
+ </div>
186
+ <div class="min-w-0">
187
+ <div class="font-mono text-sm truncate">{{ s.key }}</div>
188
+ <div class="text-sm text-muted-foreground">Value masked — write-only</div>
189
+ </div>
190
+ </div>
191
+ </template>
192
+
193
+ <template #cell-created="{ row: s }">
194
+ <span class="text-sm tabular-nums text-muted-foreground">{{ formatDateTime(s.created_at) }}</span>
195
+ </template>
196
+
197
+ <template #cell-updated="{ row: s }">
198
+ <span class="text-sm tabular-nums text-muted-foreground">{{ formatDateTime(s.updated_at) }}</span>
199
+ </template>
200
+
201
+ <template #cell-actions="{ row: s }">
202
+ <div @click.stop>
203
+ <DropdownMenu>
204
+ <DropdownMenuTrigger as-child>
205
+ <Button variant="ghost" size="icon" class="size-7">
206
+ <MoreHorizontal :size="14" />
207
+ </Button>
208
+ </DropdownMenuTrigger>
209
+ <DropdownMenuContent align="end">
210
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
211
+ <DropdownMenuSeparator />
212
+ <DropdownMenuItem class="text-destructive focus:text-destructive" @select="pendingDelete = s">
213
+ <Trash2 :size="14" />
214
+ Delete
215
+ </DropdownMenuItem>
216
+ </DropdownMenuContent>
217
+ </DropdownMenu>
218
+ </div>
219
+ </template>
220
+ </DataTable>
221
+ </div>
222
+
223
+ <AlertDialog :open="!!pendingDelete" @update:open="(v) => {
224
+ if (!v) pendingDelete = null;
225
+ }">
226
+ <AlertDialogContent>
227
+ <AlertDialogHeader>
228
+ <AlertDialogTitle>Delete secret?</AlertDialogTitle>
229
+ <AlertDialogDescription>
230
+ <span v-if="pendingDelete">
231
+ <span class="font-mono font-medium text-foreground">{{ pendingDelete.key }}</span>
232
+ will be permanently removed. Workflows that reference it will break the next time they run.
233
+ </span>
234
+ </AlertDialogDescription>
235
+ </AlertDialogHeader>
236
+ <AlertDialogFooter>
237
+ <Button variant="outline" :disabled="deleting" @click="pendingDelete = null">
238
+ Cancel
239
+ </Button>
240
+ <Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="confirmDelete">
241
+ <Loader2 v-if="deleting" :size="14" class="animate-spin" />
242
+ {{ deleting ? "Deleting\u2026" : "Delete" }}
243
+ </Button>
244
+ </AlertDialogFooter>
245
+ </AlertDialogContent>
246
+ </AlertDialog>
247
+ </IntegrationsLayout>
248
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;