@revenexx/studio-integrations 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  3. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  6. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  9. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  12. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  15. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  17. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  18. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  20. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  21. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  22. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  23. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  24. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  26. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  27. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  29. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  30. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  32. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  33. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  39. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  42. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  45. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  46. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  48. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  49. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  51. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  52. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  53. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  54. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  55. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  56. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  57. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  60. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  61. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  63. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  66. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  69. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  70. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  71. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  72. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  74. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  75. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  77. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  78. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  80. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  81. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  84. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  87. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  90. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  93. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  94. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  96. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  99. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  105. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  108. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  111. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  114. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  117. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  118. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  121. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  122. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  123. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  124. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  125. package/dist/runtime/composables/integrationsApi.d.ts +6 -2
  126. package/dist/runtime/composables/integrationsApi.js +5 -4
  127. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  128. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  129. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  130. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  131. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  132. package/dist/runtime/composables/useIntegrationsNodes.js +6 -3
  133. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  134. package/dist/runtime/composables/useIntegrationsRevisions.js +51 -0
  135. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  136. package/dist/runtime/composables/useIntegrationsRuns.js +92 -4
  137. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  138. package/dist/runtime/composables/useIntegrationsTemplates.js +32 -4
  139. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  140. package/dist/runtime/composables/useIntegrationsWorkflows.js +57 -1
  141. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  142. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  143. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  144. package/dist/runtime/composables/useNodeConfigResolve.js +123 -0
  145. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  146. package/dist/runtime/composables/useNodeDrag.js +10 -0
  147. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  148. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  149. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  150. package/dist/runtime/data/expressionEditor.js +40 -0
  151. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  152. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  153. package/dist/runtime/data/nodeInputData.js +62 -0
  154. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  155. package/dist/runtime/data/nodeRunData.js +91 -0
  156. package/dist/runtime/data/payloadForm.d.ts +44 -0
  157. package/dist/runtime/data/payloadForm.js +126 -0
  158. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  159. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  160. package/dist/runtime/pages/integrations/audit.vue +25 -35
  161. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  162. package/dist/runtime/pages/integrations/index.vue +133 -36
  163. package/dist/runtime/pages/integrations/runs.vue +125 -75
  164. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  165. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  166. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  167. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  168. package/dist/runtime/types/integrations.d.ts +190 -4
  169. package/dist/runtime/utils/credentialState.d.ts +28 -0
  170. package/dist/runtime/utils/credentialState.js +29 -0
  171. package/dist/runtime/utils/localize.d.ts +3 -1
  172. package/dist/runtime/utils/localize.js +1 -2
  173. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  174. package/dist/runtime/utils/statusStyle.js +15 -0
  175. package/package.json +11 -1
  176. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  177. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  178. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  179. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -1,7 +1,6 @@
1
1
  <script setup>
2
2
  import {
3
3
  ShieldCheck,
4
- Magnifier as Search,
5
4
  CloseCircle as Close,
6
5
  Copy as CopyIcon,
7
6
  ShieldKeyhole as Credential,
@@ -10,23 +9,15 @@ import {
10
9
  Bolt as Trigger,
11
10
  Play as Run
12
11
  } from "#solar-icons";
13
- import { Badge, Button, Input } from "@revenexx/studio";
12
+ import { Badge, Button } from "@revenexx/studio";
14
13
  import { toast } from "vue-sonner";
15
14
  useHead({ title: "Audit Log \xB7 Integration Studio" });
16
15
  const { entries, loading, error, hasMore, load, loadMore } = useIntegrationsAudit();
17
16
  const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run"];
18
17
  const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed"];
19
- const resourceType = ref(null);
20
- const action = ref(null);
21
- const actor = ref("");
22
- function filters() {
23
- return { resource_type: resourceType.value ?? void 0, action: action.value ?? void 0, sub: actor.value.trim() || void 0 };
24
- }
25
- function reload() {
26
- void load(filters());
27
- }
28
- onMounted(reload);
29
- watch([resourceType, action], reload);
18
+ onMounted(() => {
19
+ void load();
20
+ });
30
21
  const resourceIcon = { credential: Credential, secret: Secret, workflow: Workflow, trigger: Trigger, workflow_run: Run };
31
22
  function actionTone(a) {
32
23
  if (a === "create" || a === "run.finished") return "bg-emerald-500/10 text-emerald-700 border-emerald-500/20";
@@ -46,6 +37,20 @@ function resourceName(e) {
46
37
  function fmtDate(iso) {
47
38
  return iso ? new Date(iso).toLocaleString() : "\u2014";
48
39
  }
40
+ const filterTabs = [
41
+ {
42
+ key: "resource",
43
+ label: "Resource",
44
+ getter: (e) => e.resource_type,
45
+ options: resourceTypes.map((t) => ({ value: t, label: prettyType(t) }))
46
+ },
47
+ {
48
+ key: "action",
49
+ label: "Action",
50
+ getter: (e) => e.action,
51
+ options: actions.map((a) => ({ value: a, label: prettyAction(a) }))
52
+ }
53
+ ];
49
54
  const columns = [
50
55
  { key: "action", label: "Action", width: "130px", sortable: true, sortGetter: (e) => e.action },
51
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 ?? ""}` },
@@ -66,23 +71,6 @@ function copy(t) {
66
71
  <PageHeader title="Audit Log" description="Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers and runs." />
67
72
 
68
73
  <div class="p-4 space-y-3">
69
- <!-- Filters -->
70
- <div class="flex flex-wrap items-center gap-2">
71
- <div class="flex flex-wrap gap-1.5">
72
- <button class="rounded-full border px-3 py-1 text-xs transition-colors" :class="!resourceType ? 'border-primary/40 bg-primary/10 text-primary' : 'border-black/[0.06] text-muted-foreground hover:bg-black/[0.03]'" @click="resourceType = null">All</button>
73
- <button v-for="t in resourceTypes" :key="t" class="rounded-full border px-3 py-1 text-xs capitalize transition-colors" :class="resourceType === t ? 'border-primary/40 bg-primary/10 text-primary' : 'border-black/[0.06] text-muted-foreground hover:bg-black/[0.03]'" @click="resourceType = t">{{ prettyType(t) }}</button>
74
- </div>
75
- <div class="flex-1" />
76
- <select v-model="action" class="h-9 rounded-md border border-black/[0.1] bg-white px-2 text-sm">
77
- <option :value="null">All actions</option>
78
- <option v-for="a in actions" :key="a" :value="a">{{ prettyAction(a) }}</option>
79
- </select>
80
- <div class="relative">
81
- <Search :size="14" class="absolute left-2.5 top-2.5 text-muted-foreground" />
82
- <Input v-model="actor" class="h-9 w-44 pl-8 text-sm" placeholder="Actor (sub)…" @keydown.enter="reload" />
83
- </div>
84
- </div>
85
-
86
74
  <p v-if="error" class="text-sm text-rose-700">{{ error }}</p>
87
75
 
88
76
  <DataTable
@@ -90,12 +78,14 @@ function copy(t) {
90
78
  :columns="columns"
91
79
  :row-key="(e) => e.id"
92
80
  :default-sort="{ column: 'when', direction: 'desc' }"
93
- :search="{ placeholder: 'Search this page\u2026' }"
94
- :empty-text="loading ? 'Loading\u2026' : 'No audit entries match.'"
81
+ :search="{ placeholder: 'Search action, resource, actor\u2026' }"
82
+ :filter-tabs="filterTabs"
83
+ :loading="loading"
84
+ empty-text="No audit entries match."
95
85
  @row-click="(e) => selected = e"
96
86
  >
97
87
  <template #cell-action="{ row }">
98
- <Badge variant="outline" :class="['text-xs border', actionTone(row.action)]">{{ prettyAction(row.action) }}</Badge>
88
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', actionTone(row.action)]">{{ prettyAction(row.action) }}</Badge>
99
89
  </template>
100
90
  <template #cell-resource="{ row }">
101
91
  <div class="flex items-center gap-2 min-w-0">
@@ -115,7 +105,7 @@ function copy(t) {
115
105
  </DataTable>
116
106
 
117
107
  <div v-if="hasMore" class="flex justify-center pt-1">
118
- <Button variant="outline" size="sm" :disabled="loading" @click="loadMore(filters())">{{ loading ? "Loading\u2026" : "Load more" }}</Button>
108
+ <Button variant="outline" size="sm" :disabled="loading" @click="loadMore()">{{ loading ? "Loading\u2026" : "Load more" }}</Button>
119
109
  </div>
120
110
  </div>
121
111
 
@@ -125,7 +115,7 @@ function copy(t) {
125
115
  <div class="flex-1 bg-black/20 backdrop-blur-[1px]" @click="selected = null" />
126
116
  <aside class="w-full max-w-md overflow-y-auto border-l border-black/[0.06] bg-white shadow-xl">
127
117
  <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">
128
- <Badge variant="outline" :class="['text-xs border', actionTone(selected.action)]">{{ prettyAction(selected.action) }}</Badge>
118
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', actionTone(selected.action)]">{{ prettyAction(selected.action) }}</Badge>
129
119
  <span class="text-sm font-semibold text-[#0B0522] capitalize">{{ prettyType(selected.resource_type) }}</span>
130
120
  <Button variant="ghost" size="sm" class="ml-auto h-7 w-7 p-0" @click="selected = null"><Close :size="15" /></Button>
131
121
  </div>
@@ -1,13 +1,10 @@
1
1
  <script setup>
2
2
  import { computed, onMounted, reactive, ref } from "vue";
3
3
  import {
4
- CheckCircle as CheckCircle2,
5
4
  RefreshCircle as Loader2,
6
- Magnifier as Search,
7
5
  AddCircle as Plus,
8
6
  TrashBinTrash as Trash2,
9
- TestTube as TestIcon,
10
- ShieldKeyhole as KeyIcon
7
+ TestTube as TestIcon
11
8
  } from "#solar-icons";
12
9
  import {
13
10
  Badge,
@@ -28,19 +25,13 @@ import {
28
25
  SelectValue
29
26
  } from "@revenexx/studio";
30
27
  import { toast } from "vue-sonner";
28
+ import { credentialState } from "../../utils/credentialState";
31
29
  useHead({ title: "Credentials \xB7 Revenexx Cockpit" });
32
30
  const { credentials, loading, error, load, create, update, remove, testInline, test, oauthAuthorizeUrl } = useIntegrationsCredentials();
33
31
  const { types, load: loadTypes, bySlug } = useCredentialTypes();
34
32
  onMounted(async () => {
35
33
  await Promise.all([load(), loadTypes()]);
36
34
  });
37
- const search = ref("");
38
- const filtered = computed(() => {
39
- const q = search.value.trim().toLowerCase();
40
- const list = [...credentials.value].sort((a, b) => a.name.localeCompare(b.name));
41
- if (!q) return list;
42
- return list.filter((c) => c.name.toLowerCase().includes(q) || c.credential_type_slug.toLowerCase().includes(q));
43
- });
44
35
  function typeFor(slug) {
45
36
  return bySlug(slug);
46
37
  }
@@ -64,6 +55,15 @@ function formatDateTime(iso) {
64
55
  return iso;
65
56
  }
66
57
  }
58
+ const columns = [
59
+ { key: "system", label: "System", width: "minmax(160px,220px)", sortable: true, sortGetter: (c) => typeName(c.credential_type_slug).toLowerCase(), searchGetter: (c) => `${typeName(c.credential_type_slug)} ${c.credential_type_slug}` },
60
+ { key: "name", label: "Name", sortable: true, sortGetter: (c) => c.name.toLowerCase(), searchGetter: (c) => c.name },
61
+ { key: "status", label: "Status", width: "150px", sortable: true, sortGetter: (c) => credentialState(c).key },
62
+ { key: "connected", label: "Connected", width: "150px", sortable: true, sortGetter: (c) => c.last_test_ok ? c.last_test_at ?? "" : "" },
63
+ { key: "created", label: "Created", width: "150px", sortable: true, sortGetter: (c) => c.created_at ?? "" },
64
+ { key: "updated", label: "Updated", width: "150px", sortable: true, sortGetter: (c) => c.updated_at ?? "" },
65
+ { key: "actions", label: "", width: "80px" }
66
+ ];
67
67
  const dialogOpen = ref(false);
68
68
  const editing = ref(null);
69
69
  const pickedType = ref(null);
@@ -71,6 +71,7 @@ const form = reactive({ name: "", config: {} });
71
71
  const saving = ref(false);
72
72
  const testing = ref(false);
73
73
  const testResult = ref(null);
74
+ const sourceActive = ref(false);
74
75
  const isEdit = computed(() => editing.value !== null);
75
76
  function fieldsOf(t) {
76
77
  return t?.fields ?? [];
@@ -81,15 +82,24 @@ function openCreate(t) {
81
82
  form.name = "";
82
83
  form.config = {};
83
84
  testResult.value = null;
85
+ sourceActive.value = false;
84
86
  dialogOpen.value = true;
85
87
  }
88
+ const detailOpen = ref(false);
89
+ const detailCredential = ref(null);
90
+ const detailType = ref(null);
86
91
  function openEdit(c) {
87
- editing.value = c;
88
- pickedType.value = typeFor(c.credential_type_slug);
89
- form.name = c.name;
90
- form.config = { ...c.public_config ?? {} };
91
- testResult.value = null;
92
- dialogOpen.value = true;
92
+ detailCredential.value = c;
93
+ detailType.value = typeFor(c.credential_type_slug);
94
+ detailOpen.value = true;
95
+ }
96
+ async function onDetailChanged() {
97
+ const id = detailCredential.value?.id;
98
+ await load(true);
99
+ if (id) {
100
+ const fresh = credentials.value.find((c) => c.id === id);
101
+ if (fresh) detailCredential.value = fresh;
102
+ }
93
103
  }
94
104
  function buildConfigPayload() {
95
105
  const out = {};
@@ -164,17 +174,6 @@ async function connect(c) {
164
174
  connecting.value = null;
165
175
  }
166
176
  }
167
- const cardTesting = ref(null);
168
- async function testCard(c) {
169
- cardTesting.value = c.id;
170
- try {
171
- const res = await test(c.id);
172
- if (res.ok) toast.success(`${c.name}: connection ok`);
173
- else toast.error(`${c.name}: ${res.error ?? "connection failed"}`);
174
- } finally {
175
- cardTesting.value = null;
176
- }
177
- }
178
177
  async function destroy(c) {
179
178
  if (!confirm(`Delete credential "${c.name}"? Workflows referencing it will fail until re-bound.`)) return;
180
179
  try {
@@ -200,84 +199,76 @@ async function destroy(c) {
200
199
  </template>
201
200
  </PageHeader>
202
201
 
203
- <div class="flex items-center justify-between gap-3 border-b border-black/[0.06] px-4 py-2">
204
- <div class="text-sm text-muted-foreground">
205
- {{ filtered.length }} credential{{ filtered.length === 1 ? "" : "s" }}
206
- </div>
207
- <div class="relative w-64">
208
- <Search :size="12" class="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
209
- <Input v-model="search" placeholder="Search…" class="h-7 pl-7 text-sm bg-white" />
210
- </div>
211
- </div>
212
-
213
202
  <div v-if="error" class="m-4 rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
214
203
  {{ error }}
215
204
  </div>
216
205
 
217
- <div class="p-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
218
- <div
219
- v-for="c in filtered"
220
- :key="c.id"
221
- class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-3 transition hover:shadow-sm"
206
+ <div class="p-4">
207
+ <DataTable
208
+ :data="credentials"
209
+ :columns="columns"
210
+ :row-key="(c2) => c2.id"
211
+ :default-sort="{ column: 'name', direction: 'asc' }"
212
+ :search="{ placeholder: 'Search credentials\u2026' }"
213
+ :filter="true"
214
+ :loading="loading"
215
+ empty-text="No credentials yet — hit “New credential” to connect your first system."
216
+ @row-click="(c2) => openEdit(c2)"
222
217
  >
223
- <div class="flex items-start gap-3">
224
- <span class="flex size-10 items-center justify-center rounded-md bg-slate-100 text-slate-500 shrink-0">
225
- <KeyIcon :size="18" />
226
- </span>
227
- <div class="flex-1 min-w-0">
228
- <div class="text-sm font-semibold truncate">
229
- {{ c.name }}
230
- </div>
231
- <div class="text-sm text-muted-foreground truncate">
232
- {{ typeName(c.credential_type_slug) }}
233
- </div>
218
+ <template #cell-system="{ row: c }">
219
+ <div class="flex items-center gap-2 min-w-0">
220
+ <IntegrationsCredentialLogo :type="typeFor(c.credential_type_slug)" :size="16" />
221
+ <span class="font-medium text-sm truncate">{{ typeName(c.credential_type_slug) }}</span>
234
222
  </div>
223
+ </template>
224
+
225
+ <template #cell-name="{ row: c }">
226
+ <span class="text-sm truncate">{{ c.name }}</span>
227
+ </template>
228
+
229
+ <template #cell-status="{ row: c }">
235
230
  <Badge
236
- v-if="c.status === 'active'"
237
- variant="outline"
238
- class="text-sm border bg-emerald-500/10 border-emerald-500/20 text-emerald-700 gap-1"
239
- >
240
- <CheckCircle2 :size="12" />
241
- Active
242
- </Badge>
243
- <Badge
244
- v-else
245
231
  variant="outline"
246
- class="text-sm border bg-amber-500/10 border-amber-500/20 text-amber-700"
232
+ class="border text-xs font-medium uppercase"
233
+ :class="credentialState(c).toneClass"
234
+ :title="credentialState(c).message ?? void 0"
247
235
  >
248
- Needs reconnect
236
+ {{ credentialState(c).label }}
249
237
  </Badge>
250
- </div>
238
+ </template>
251
239
 
252
- <div class="flex items-center justify-between border-t border-black/[0.04] pt-2 text-sm text-muted-foreground">
253
- <span>Updated {{ formatDateTime(c.updated_at) }}</span>
254
- <div class="flex items-center gap-2">
255
- <button
240
+ <template #cell-connected="{ row: c }">
241
+ <span class="text-sm text-muted-foreground tabular-nums">
242
+ {{ c.last_test_ok ? formatDateTime(c.last_test_at) : "\u2014" }}
243
+ </span>
244
+ </template>
245
+
246
+ <template #cell-created="{ row: c }">
247
+ <span class="text-sm text-muted-foreground tabular-nums">{{ formatDateTime(c.created_at) }}</span>
248
+ </template>
249
+
250
+ <template #cell-updated="{ row: c }">
251
+ <span class="text-sm text-muted-foreground tabular-nums">{{ formatDateTime(c.updated_at) }}</span>
252
+ </template>
253
+
254
+ <template #cell-actions="{ row: c }">
255
+ <div class="flex items-center justify-end gap-2 text-sm" @click.stop>
256
+ <Button
256
257
  v-if="c.status === 'needs_reauth' && isOAuthAuthcode(typeFor(c.credential_type_slug))"
257
- type="button"
258
- class="text-amber-600 hover:underline"
258
+ variant="outline"
259
+ size="sm"
260
+ class="h-7 text-amber-600"
259
261
  :disabled="connecting === c.id"
260
262
  @click="connect(c)"
261
263
  >
262
264
  Reconnect
263
- </button>
264
- <button type="button" class="text-violet-600 hover:underline disabled:opacity-50" :disabled="cardTesting === c.id" @click="testCard(c)">
265
- <Loader2 v-if="cardTesting === c.id" :size="12" class="inline animate-spin" />
266
- <span v-else>Test</span>
267
- </button>
268
- <button type="button" class="text-foreground/50 hover:text-foreground" @click="openEdit(c)">
269
- Edit
270
- </button>
271
- <button type="button" class="text-foreground/40 hover:text-rose-600" @click="destroy(c)">
265
+ </Button>
266
+ <button type="button" class="flex size-7 items-center justify-center rounded text-foreground/40 hover:text-rose-600 hover:bg-rose-500/10" title="Delete" @click="destroy(c)">
272
267
  <Trash2 :size="13" />
273
268
  </button>
274
269
  </div>
275
- </div>
276
- </div>
277
-
278
- <div v-if="!loading && !filtered.length" class="col-span-full px-3 py-12 text-center text-sm text-muted-foreground">
279
- No credentials yet. Hit "New credential" to connect your first system.
280
- </div>
270
+ </template>
271
+ </DataTable>
281
272
  </div>
282
273
 
283
274
  <!-- Create / edit dialog -->
@@ -290,29 +281,15 @@ async function destroy(c) {
290
281
  </DialogDescription>
291
282
  </DialogHeader>
292
283
 
293
- <!-- Step 1: type picker (create only) -->
294
- <div v-if="!pickedType" class="grid grid-cols-2 gap-2 py-2 max-h-80 overflow-y-auto">
295
- <button
296
- v-for="t in types"
297
- :key="t.slug"
298
- type="button"
299
- class="flex items-start gap-2 rounded-lg border border-black/[0.08] p-3 text-left transition hover:border-black/20 hover:bg-muted/30"
300
- @click="pickedType = t;
301
- form.name = loc(t.name)"
302
- >
303
- <span class="flex size-8 items-center justify-center rounded-md bg-slate-100 text-slate-500 shrink-0">
304
- <KeyIcon :size="16" />
305
- </span>
306
- <div class="min-w-0">
307
- <div class="text-sm font-semibold truncate">
308
- {{ loc(t.name) }}
309
- </div>
310
- <div class="text-sm text-muted-foreground truncate">
311
- {{ t.auth_kind }}
312
- </div>
313
- </div>
314
- </button>
315
- </div>
284
+ <!-- Step 1: searchable type picker (create only) -->
285
+ <IntegrationsCredentialTypePicker
286
+ v-if="!pickedType"
287
+ :types="types"
288
+ @pick="(t) => {
289
+ pickedType = t;
290
+ form.name = loc(t.name);
291
+ }"
292
+ />
316
293
 
317
294
  <!-- Step 2: dynamic form -->
318
295
  <div v-else class="space-y-3 py-1 max-h-[28rem] overflow-y-auto pr-1">
@@ -321,7 +298,14 @@ async function destroy(c) {
321
298
  <Input id="cred-name" v-model="form.name" class="bg-white" placeholder="e.g. Production SMTP" />
322
299
  </div>
323
300
 
324
- <div v-for="f in fieldsOf(pickedType)" :key="f.key" class="space-y-1.5">
301
+ <!-- "Use revenexx …" source picker (SFTP / Mail / API key) -->
302
+ <IntegrationsCredentialRevenexxSource
303
+ :type="pickedType"
304
+ @update:active="(v) => sourceActive = v"
305
+ @apply="(c2) => Object.assign(form.config, c2)"
306
+ />
307
+
308
+ <div v-for="f in fieldsOf(pickedType)" v-show="!sourceActive" :key="f.key" class="space-y-1.5">
325
309
  <Label>{{ loc(f.label) }}{{ f.required ? " *" : "" }}</Label>
326
310
 
327
311
  <!-- secret -->
@@ -383,21 +367,32 @@ async function destroy(c) {
383
367
  </div>
384
368
  </div>
385
369
 
386
- <DialogFooter v-if="pickedType">
387
- <Button v-if="!isEdit" variant="outline" :disabled="saving || testing" @click="pickedType = null">
370
+ <DialogFooter v-if="pickedType" class="sm:justify-between">
371
+ <Button v-if="!isEdit" variant="ghost" size="sm" :disabled="saving || testing" @click="pickedType = null">
388
372
  Back
389
373
  </Button>
390
- <Button variant="outline" :disabled="saving || testing" @click="runTest">
391
- <Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
392
- <TestIcon v-else :size="14" class="mr-1" />
393
- Test
394
- </Button>
395
- <Button :disabled="saving || !form.name.trim()" @click="submit">
396
- <Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
397
- {{ isEdit ? "Save" : isOAuthAuthcode(pickedType) ? "Save & connect" : "Create" }}
398
- </Button>
374
+ <span v-else />
375
+ <div class="flex items-center gap-2">
376
+ <Button variant="outline" :disabled="saving || testing" @click="runTest">
377
+ <Loader2 v-if="testing" :size="14" class="mr-1 animate-spin" />
378
+ <TestIcon v-else :size="14" class="mr-1" />
379
+ Connect
380
+ </Button>
381
+ <Button :disabled="saving || !form.name.trim()" @click="submit">
382
+ <Loader2 v-if="saving" :size="14" class="mr-1 animate-spin" />
383
+ Save
384
+ </Button>
385
+ </div>
399
386
  </DialogFooter>
400
387
  </DialogContent>
401
388
  </Dialog>
389
+
390
+ <!-- Detail / edit view (single-page: connection state + fields + metadata) -->
391
+ <IntegrationsCredentialDetailDialog
392
+ v-model:open="detailOpen"
393
+ :credential="detailCredential"
394
+ :type="detailType"
395
+ @changed="onDetailChanged"
396
+ />
402
397
  </IntegrationsLayout>
403
398
  </template>