@antelopejs/dms-automation 0.0.1

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 (305) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +95 -0
  3. package/dist/cluster/bus.d.ts +17 -0
  4. package/dist/cluster/bus.js +82 -0
  5. package/dist/cluster/bus.js.map +1 -0
  6. package/dist/cluster/instance.d.ts +1 -0
  7. package/dist/cluster/instance.js +6 -0
  8. package/dist/cluster/instance.js.map +1 -0
  9. package/dist/cluster/leader.d.ts +7 -0
  10. package/dist/cluster/leader.js +123 -0
  11. package/dist/cluster/leader.js.map +1 -0
  12. package/dist/cluster/mode.d.ts +3 -0
  13. package/dist/cluster/mode.js +25 -0
  14. package/dist/cluster/mode.js.map +1 -0
  15. package/dist/data/procedures-table.d.ts +66 -0
  16. package/dist/data/procedures-table.js +271 -0
  17. package/dist/data/procedures-table.js.map +1 -0
  18. package/dist/data/runs-table.d.ts +23 -0
  19. package/dist/data/runs-table.js +125 -0
  20. package/dist/data/runs-table.js.map +1 -0
  21. package/dist/db/index.d.ts +7 -0
  22. package/dist/db/index.js +24 -0
  23. package/dist/db/index.js.map +1 -0
  24. package/dist/db/models/automation_template.model.d.ts +58 -0
  25. package/dist/db/models/automation_template.model.js +134 -0
  26. package/dist/db/models/automation_template.model.js.map +1 -0
  27. package/dist/db/models/index.d.ts +3 -0
  28. package/dist/db/models/index.js +20 -0
  29. package/dist/db/models/index.js.map +1 -0
  30. package/dist/db/models/procedure.model.d.ts +25 -0
  31. package/dist/db/models/procedure.model.js +28 -0
  32. package/dist/db/models/procedure.model.js.map +1 -0
  33. package/dist/db/models/procedure_run.model.d.ts +34 -0
  34. package/dist/db/models/procedure_run.model.js +39 -0
  35. package/dist/db/models/procedure_run.model.js.map +1 -0
  36. package/dist/db/models/stats.model.d.ts +73 -0
  37. package/dist/db/models/stats.model.js +267 -0
  38. package/dist/db/models/stats.model.js.map +1 -0
  39. package/dist/db/models/type_config.model.d.ts +47 -0
  40. package/dist/db/models/type_config.model.js +51 -0
  41. package/dist/db/models/type_config.model.js.map +1 -0
  42. package/dist/db/tables/action_type_config.table.d.ts +7 -0
  43. package/dist/db/tables/action_type_config.table.js +35 -0
  44. package/dist/db/tables/action_type_config.table.js.map +1 -0
  45. package/dist/db/tables/automation_template.table.d.ts +11 -0
  46. package/dist/db/tables/automation_template.table.js +52 -0
  47. package/dist/db/tables/automation_template.table.js.map +1 -0
  48. package/dist/db/tables/index.d.ts +5 -0
  49. package/dist/db/tables/index.js +22 -0
  50. package/dist/db/tables/index.js.map +1 -0
  51. package/dist/db/tables/procedure.table.d.ts +10 -0
  52. package/dist/db/tables/procedure.table.js +48 -0
  53. package/dist/db/tables/procedure.table.js.map +1 -0
  54. package/dist/db/tables/procedure_run.table.d.ts +13 -0
  55. package/dist/db/tables/procedure_run.table.js +57 -0
  56. package/dist/db/tables/procedure_run.table.js.map +1 -0
  57. package/dist/db/tables/trigger_type_config.table.d.ts +7 -0
  58. package/dist/db/tables/trigger_type_config.table.js +35 -0
  59. package/dist/db/tables/trigger_type_config.table.js.map +1 -0
  60. package/dist/implementations/dms-automation/index.d.ts +19 -0
  61. package/dist/implementations/dms-automation/index.js +50 -0
  62. package/dist/implementations/dms-automation/index.js.map +1 -0
  63. package/dist/index.d.ts +14 -0
  64. package/dist/index.js +121 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/pages/builder.d.ts +10 -0
  67. package/dist/pages/builder.js +28 -0
  68. package/dist/pages/builder.js.map +1 -0
  69. package/dist/pages/category.d.ts +1 -0
  70. package/dist/pages/category.js +10 -0
  71. package/dist/pages/category.js.map +1 -0
  72. package/dist/pages/index.d.ts +6 -0
  73. package/dist/pages/index.js +9 -0
  74. package/dist/pages/index.js.map +1 -0
  75. package/dist/pages/library.d.ts +10 -0
  76. package/dist/pages/library.js +28 -0
  77. package/dist/pages/library.js.map +1 -0
  78. package/dist/pages/overview.d.ts +10 -0
  79. package/dist/pages/overview.js +28 -0
  80. package/dist/pages/overview.js.map +1 -0
  81. package/dist/pages/procedures.d.ts +10 -0
  82. package/dist/pages/procedures.js +72 -0
  83. package/dist/pages/procedures.js.map +1 -0
  84. package/dist/pages/runs.d.ts +11 -0
  85. package/dist/pages/runs.js +61 -0
  86. package/dist/pages/runs.js.map +1 -0
  87. package/dist/routes/action-types.d.ts +23 -0
  88. package/dist/routes/action-types.js +80 -0
  89. package/dist/routes/action-types.js.map +1 -0
  90. package/dist/routes/data-node-types.d.ts +6 -0
  91. package/dist/routes/data-node-types.js +36 -0
  92. package/dist/routes/data-node-types.js.map +1 -0
  93. package/dist/routes/graphSchema.d.ts +92 -0
  94. package/dist/routes/graphSchema.js +60 -0
  95. package/dist/routes/graphSchema.js.map +1 -0
  96. package/dist/routes/index.d.ts +8 -0
  97. package/dist/routes/index.js +11 -0
  98. package/dist/routes/index.js.map +1 -0
  99. package/dist/routes/node-kinds.d.ts +9 -0
  100. package/dist/routes/node-kinds.js +35 -0
  101. package/dist/routes/node-kinds.js.map +1 -0
  102. package/dist/routes/procedures.d.ts +23 -0
  103. package/dist/routes/procedures.js +181 -0
  104. package/dist/routes/procedures.js.map +1 -0
  105. package/dist/routes/runs.d.ts +7 -0
  106. package/dist/routes/runs.js +81 -0
  107. package/dist/routes/runs.js.map +1 -0
  108. package/dist/routes/stats.d.ts +6 -0
  109. package/dist/routes/stats.js +35 -0
  110. package/dist/routes/stats.js.map +1 -0
  111. package/dist/routes/templates.d.ts +20 -0
  112. package/dist/routes/templates.js +243 -0
  113. package/dist/routes/templates.js.map +1 -0
  114. package/dist/routes/trigger-types.d.ts +24 -0
  115. package/dist/routes/trigger-types.js +80 -0
  116. package/dist/routes/trigger-types.js.map +1 -0
  117. package/dist/routes/typeConfigShared.d.ts +22 -0
  118. package/dist/routes/typeConfigShared.js +37 -0
  119. package/dist/routes/typeConfigShared.js.map +1 -0
  120. package/dist/runtime/builtins/actions/http.d.ts +13 -0
  121. package/dist/runtime/builtins/actions/http.js +47 -0
  122. package/dist/runtime/builtins/actions/http.js.map +1 -0
  123. package/dist/runtime/builtins/actions/index.d.ts +2 -0
  124. package/dist/runtime/builtins/actions/index.js +7 -0
  125. package/dist/runtime/builtins/actions/index.js.map +1 -0
  126. package/dist/runtime/builtins/actions/log.d.ts +13 -0
  127. package/dist/runtime/builtins/actions/log.js +49 -0
  128. package/dist/runtime/builtins/actions/log.js.map +1 -0
  129. package/dist/runtime/builtins/data/boolean.d.ts +2 -0
  130. package/dist/runtime/builtins/data/boolean.js +39 -0
  131. package/dist/runtime/builtins/data/boolean.js.map +1 -0
  132. package/dist/runtime/builtins/data/compare.d.ts +2 -0
  133. package/dist/runtime/builtins/data/compare.js +29 -0
  134. package/dist/runtime/builtins/data/compare.js.map +1 -0
  135. package/dist/runtime/builtins/data/constant.d.ts +2 -0
  136. package/dist/runtime/builtins/data/constant.js +29 -0
  137. package/dist/runtime/builtins/data/constant.js.map +1 -0
  138. package/dist/runtime/builtins/data/index.d.ts +2 -0
  139. package/dist/runtime/builtins/data/index.js +20 -0
  140. package/dist/runtime/builtins/data/index.js.map +1 -0
  141. package/dist/runtime/builtins/data/json.d.ts +2 -0
  142. package/dist/runtime/builtins/data/json.js +46 -0
  143. package/dist/runtime/builtins/data/json.js.map +1 -0
  144. package/dist/runtime/builtins/data/math.d.ts +2 -0
  145. package/dist/runtime/builtins/data/math.js +53 -0
  146. package/dist/runtime/builtins/data/math.js.map +1 -0
  147. package/dist/runtime/builtins/data/object.d.ts +2 -0
  148. package/dist/runtime/builtins/data/object.js +121 -0
  149. package/dist/runtime/builtins/data/object.js.map +1 -0
  150. package/dist/runtime/builtins/data/string.d.ts +2 -0
  151. package/dist/runtime/builtins/data/string.js +143 -0
  152. package/dist/runtime/builtins/data/string.js.map +1 -0
  153. package/dist/runtime/builtins/index.d.ts +17 -0
  154. package/dist/runtime/builtins/index.js +24 -0
  155. package/dist/runtime/builtins/index.js.map +1 -0
  156. package/dist/runtime/builtins/nodes/action.d.ts +1 -0
  157. package/dist/runtime/builtins/nodes/action.js +36 -0
  158. package/dist/runtime/builtins/nodes/action.js.map +1 -0
  159. package/dist/runtime/builtins/nodes/data.d.ts +1 -0
  160. package/dist/runtime/builtins/nodes/data.js +28 -0
  161. package/dist/runtime/builtins/nodes/data.js.map +1 -0
  162. package/dist/runtime/builtins/nodes/delay.d.ts +1 -0
  163. package/dist/runtime/builtins/nodes/delay.js +41 -0
  164. package/dist/runtime/builtins/nodes/delay.js.map +1 -0
  165. package/dist/runtime/builtins/nodes/forRange.d.ts +1 -0
  166. package/dist/runtime/builtins/nodes/forRange.js +52 -0
  167. package/dist/runtime/builtins/nodes/forRange.js.map +1 -0
  168. package/dist/runtime/builtins/nodes/foreach.d.ts +1 -0
  169. package/dist/runtime/builtins/nodes/foreach.js +31 -0
  170. package/dist/runtime/builtins/nodes/foreach.js.map +1 -0
  171. package/dist/runtime/builtins/nodes/groupInput.d.ts +1 -0
  172. package/dist/runtime/builtins/nodes/groupInput.js +11 -0
  173. package/dist/runtime/builtins/nodes/groupInput.js.map +1 -0
  174. package/dist/runtime/builtins/nodes/groupOutput.d.ts +1 -0
  175. package/dist/runtime/builtins/nodes/groupOutput.js +11 -0
  176. package/dist/runtime/builtins/nodes/groupOutput.js.map +1 -0
  177. package/dist/runtime/builtins/nodes/if.d.ts +1 -0
  178. package/dist/runtime/builtins/nodes/if.js +23 -0
  179. package/dist/runtime/builtins/nodes/if.js.map +1 -0
  180. package/dist/runtime/builtins/nodes/parallel.d.ts +1 -0
  181. package/dist/runtime/builtins/nodes/parallel.js +34 -0
  182. package/dist/runtime/builtins/nodes/parallel.js.map +1 -0
  183. package/dist/runtime/builtins/nodes/retry.d.ts +1 -0
  184. package/dist/runtime/builtins/nodes/retry.js +46 -0
  185. package/dist/runtime/builtins/nodes/retry.js.map +1 -0
  186. package/dist/runtime/builtins/nodes/setVariable.d.ts +1 -0
  187. package/dist/runtime/builtins/nodes/setVariable.js +31 -0
  188. package/dist/runtime/builtins/nodes/setVariable.js.map +1 -0
  189. package/dist/runtime/builtins/nodes/switch.d.ts +1 -0
  190. package/dist/runtime/builtins/nodes/switch.js +33 -0
  191. package/dist/runtime/builtins/nodes/switch.js.map +1 -0
  192. package/dist/runtime/builtins/nodes/trigger.d.ts +1 -0
  193. package/dist/runtime/builtins/nodes/trigger.js +25 -0
  194. package/dist/runtime/builtins/nodes/trigger.js.map +1 -0
  195. package/dist/runtime/builtins/nodes/tryCatch.d.ts +1 -0
  196. package/dist/runtime/builtins/nodes/tryCatch.js +35 -0
  197. package/dist/runtime/builtins/nodes/tryCatch.js.map +1 -0
  198. package/dist/runtime/builtins/triggers/cron.d.ts +9 -0
  199. package/dist/runtime/builtins/triggers/cron.js +52 -0
  200. package/dist/runtime/builtins/triggers/cron.js.map +1 -0
  201. package/dist/runtime/builtins/triggers/index.d.ts +2 -0
  202. package/dist/runtime/builtins/triggers/index.js +12 -0
  203. package/dist/runtime/builtins/triggers/index.js.map +1 -0
  204. package/dist/runtime/builtins/triggers/manual.d.ts +3 -0
  205. package/dist/runtime/builtins/triggers/manual.js +20 -0
  206. package/dist/runtime/builtins/triggers/manual.js.map +1 -0
  207. package/dist/runtime/builtins/triggers/webhook.d.ts +14 -0
  208. package/dist/runtime/builtins/triggers/webhook.js +90 -0
  209. package/dist/runtime/builtins/triggers/webhook.js.map +1 -0
  210. package/dist/runtime/executionResult.d.ts +6 -0
  211. package/dist/runtime/executionResult.js +3 -0
  212. package/dist/runtime/executionResult.js.map +1 -0
  213. package/dist/runtime/executor.d.ts +9 -0
  214. package/dist/runtime/executor.js +300 -0
  215. package/dist/runtime/executor.js.map +1 -0
  216. package/dist/runtime/flatten.d.ts +3 -0
  217. package/dist/runtime/flatten.js +188 -0
  218. package/dist/runtime/flatten.js.map +1 -0
  219. package/dist/runtime/nodeKinds.d.ts +64 -0
  220. package/dist/runtime/nodeKinds.js +43 -0
  221. package/dist/runtime/nodeKinds.js.map +1 -0
  222. package/dist/runtime/reconcileDiff.d.ts +9 -0
  223. package/dist/runtime/reconcileDiff.js +26 -0
  224. package/dist/runtime/reconcileDiff.js.map +1 -0
  225. package/dist/runtime/registry.d.ts +19 -0
  226. package/dist/runtime/registry.js +47 -0
  227. package/dist/runtime/registry.js.map +1 -0
  228. package/dist/runtime/resolveInputs.d.ts +14 -0
  229. package/dist/runtime/resolveInputs.js +64 -0
  230. package/dist/runtime/resolveInputs.js.map +1 -0
  231. package/dist/runtime/resolveSubgraph.d.ts +7 -0
  232. package/dist/runtime/resolveSubgraph.js +15 -0
  233. package/dist/runtime/resolveSubgraph.js.map +1 -0
  234. package/dist/runtime/subscriptions.d.ts +16 -0
  235. package/dist/runtime/subscriptions.js +419 -0
  236. package/dist/runtime/subscriptions.js.map +1 -0
  237. package/dist/runtime/subwalk.d.ts +17 -0
  238. package/dist/runtime/subwalk.js +74 -0
  239. package/dist/runtime/subwalk.js.map +1 -0
  240. package/dist/runtime/topo.d.ts +3 -0
  241. package/dist/runtime/topo.js +75 -0
  242. package/dist/runtime/topo.js.map +1 -0
  243. package/dist/runtime/typeOrigin.d.ts +7 -0
  244. package/dist/runtime/typeOrigin.js +24 -0
  245. package/dist/runtime/typeOrigin.js.map +1 -0
  246. package/dist/runtime/validate.d.ts +10 -0
  247. package/dist/runtime/validate.js +185 -0
  248. package/dist/runtime/validate.js.map +1 -0
  249. package/dist/runtime/validateTemplate.d.ts +3 -0
  250. package/dist/runtime/validateTemplate.js +54 -0
  251. package/dist/runtime/validateTemplate.js.map +1 -0
  252. package/dist/types/constants.d.ts +6 -0
  253. package/dist/types/constants.js +10 -0
  254. package/dist/types/constants.js.map +1 -0
  255. package/dist/types/graph.d.ts +51 -0
  256. package/dist/types/graph.js +23 -0
  257. package/dist/types/graph.js.map +1 -0
  258. package/dist/types/pagination.d.ts +8 -0
  259. package/dist/types/pagination.js +3 -0
  260. package/dist/types/pagination.js.map +1 -0
  261. package/dist/types/runLog.d.ts +26 -0
  262. package/dist/types/runLog.js +3 -0
  263. package/dist/types/runLog.js.map +1 -0
  264. package/frontend-vue/.prettierignore +4 -0
  265. package/frontend-vue/app/components/Actions.vue +141 -0
  266. package/frontend-vue/app/components/Catalog/TypeConfigForm.vue +166 -0
  267. package/frontend-vue/app/components/Catalog/TypeList.vue +198 -0
  268. package/frontend-vue/app/components/DataNodes.vue +259 -0
  269. package/frontend-vue/app/components/Editor/BuilderPalette.vue +234 -0
  270. package/frontend-vue/app/components/Editor/GraphCanvas.vue +994 -0
  271. package/frontend-vue/app/components/Editor/GroupPortEditor.vue +289 -0
  272. package/frontend-vue/app/components/Editor/JsonField.vue +63 -0
  273. package/frontend-vue/app/components/Editor/NodeInspector.vue +356 -0
  274. package/frontend-vue/app/components/Editor/ProcedureList.vue +156 -0
  275. package/frontend-vue/app/components/Editor/RunsDrawer.vue +312 -0
  276. package/frontend-vue/app/components/Editor/WiredField.vue +42 -0
  277. package/frontend-vue/app/components/Editor/nodes/GenericNode.vue +161 -0
  278. package/frontend-vue/app/components/Editor/nodes/GroupInputNode.vue +19 -0
  279. package/frontend-vue/app/components/Editor/nodes/GroupNode.vue +200 -0
  280. package/frontend-vue/app/components/Editor/nodes/GroupOutputNode.vue +19 -0
  281. package/frontend-vue/app/components/Editor/nodes/GroupSentinel.vue +161 -0
  282. package/frontend-vue/app/components/Editor/nodes/styles.ts +53 -0
  283. package/frontend-vue/app/components/Editor.vue +2504 -0
  284. package/frontend-vue/app/components/Library.vue +67 -0
  285. package/frontend-vue/app/components/Overview.vue +503 -0
  286. package/frontend-vue/app/components/RunDetailDrawer.vue +165 -0
  287. package/frontend-vue/app/components/RunFireTree.vue +170 -0
  288. package/frontend-vue/app/components/RunTraceDrawer.vue +59 -0
  289. package/frontend-vue/app/components/RunsStats.vue +126 -0
  290. package/frontend-vue/app/components/RunsTimelineDisplay.vue +287 -0
  291. package/frontend-vue/app/components/Templates.vue +663 -0
  292. package/frontend-vue/app/components/Triggers.vue +137 -0
  293. package/frontend-vue/app/composables/useAutomationNodeKinds.ts +48 -0
  294. package/frontend-vue/app/composables/useAutomationRuns.ts +75 -0
  295. package/frontend-vue/app/composables/useGraphConnect.ts +542 -0
  296. package/frontend-vue/app/plugins/runs-timeline-display.client.ts +16 -0
  297. package/frontend-vue/app/utils/automation.ts +51 -0
  298. package/frontend-vue/dms.frontend.ts +26 -0
  299. package/frontend-vue/eslint.config.mjs +24 -0
  300. package/frontend-vue/i18n/locales/dms-automation-en-GB.json +369 -0
  301. package/frontend-vue/i18n/locales/dms-automation-fr-FR.json +369 -0
  302. package/frontend-vue/package.json +33 -0
  303. package/frontend-vue/pnpm-lock.yaml +6483 -0
  304. package/frontend-vue/pnpm-workspace.yaml +2 -0
  305. package/package.json +81 -0
@@ -0,0 +1,663 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import { unwrapList } from '../utils/automation'
4
+
5
+ // `embedded` drops the page-head when this catalog is rendered inside a Library tab.
6
+ defineProps<{ embedded?: boolean }>()
7
+
8
+ // JSON Schema allows `type` to be a single name or a list (`["string", "null"]`).
9
+ interface JsonSchema {
10
+ type?: string | string[]
11
+ [key: string]: unknown
12
+ }
13
+
14
+ interface TemplatePort {
15
+ name: string
16
+ kind: 'data' | 'trigger'
17
+ direction: 'in' | 'out'
18
+ schema?: JsonSchema
19
+ }
20
+
21
+ interface TemplateRow {
22
+ _id: string
23
+ name: string
24
+ description: string
25
+ icon: string
26
+ ports: TemplatePort[]
27
+ subgraph: unknown
28
+ usageCount?: number
29
+ created_at?: string | Date
30
+ updated_at?: string | Date
31
+ }
32
+
33
+ interface TemplateListResponse {
34
+ results: TemplateRow[]
35
+ total: number
36
+ page: number
37
+ limit: number
38
+ }
39
+
40
+ interface TemplateUsage {
41
+ procedureId: string
42
+ nodeId: string
43
+ }
44
+
45
+ interface ProcedureRow {
46
+ _id: string
47
+ name: string
48
+ }
49
+
50
+ interface ProcedureListResponse {
51
+ results: ProcedureRow[]
52
+ }
53
+
54
+ const DEFAULT_ICON = 'i-ph-package'
55
+
56
+ const { $authFetch } = useAuthFetch()
57
+ const router = useDmsRouter()
58
+ const toast = useToast()
59
+
60
+ const items = ref<TemplateRow[]>([])
61
+ const loading = ref(false)
62
+ const loadError = ref<string | null>(null)
63
+ const selectedId = ref<string | null>(null)
64
+
65
+ // Form state for the currently selected template.
66
+ const formName = ref('')
67
+ const formDescription = ref('')
68
+ const formIcon = ref(DEFAULT_ICON)
69
+ const saving = ref(false)
70
+ const saveError = ref<string | null>(null)
71
+
72
+ // Usages panel state.
73
+ const usages = ref<TemplateUsage[]>([])
74
+ const usagesLoading = ref(false)
75
+ const usagesError = ref<string | null>(null)
76
+ const procedureNames = ref<Map<string, string>>(new Map())
77
+
78
+ // Delete-confirm state.
79
+ const deleteOpen = ref(false)
80
+ const deleting = ref(false)
81
+ const deleteError = ref<string | null>(null)
82
+
83
+ async function loadList() {
84
+ loading.value = true
85
+ loadError.value = null
86
+ try {
87
+ const data = await $authFetch<TemplateListResponse | TemplateRow[]>(
88
+ '/api/automation/templates',
89
+ )
90
+ const list = unwrapList<TemplateRow>(data)
91
+ items.value = list
92
+ // Keep current selection if it still exists, otherwise pick the first.
93
+ if (selectedId.value && !list.find((t) => t._id === selectedId.value)) {
94
+ selectedId.value = list[0]?._id ?? null
95
+ } else if (!selectedId.value) {
96
+ selectedId.value = list[0]?._id ?? null
97
+ }
98
+ } catch (e) {
99
+ loadError.value = (e as Error).message ?? 'Failed to load templates'
100
+ } finally {
101
+ loading.value = false
102
+ }
103
+ }
104
+
105
+ await loadList()
106
+
107
+ // Lazily fetch the procedure list once so we can resolve procedureId -> name
108
+ // in the "Used in" panel. If the fetch fails, we silently fall back to showing
109
+ // the raw id (still wrapped in a link to the editor).
110
+ async function ensureProcedureNames() {
111
+ if (procedureNames.value.size > 0) return
112
+ try {
113
+ const data = await $authFetch<ProcedureListResponse | ProcedureRow[]>(
114
+ '/api/automation/procedures?limit=100000',
115
+ )
116
+ const list = unwrapList<ProcedureRow>(data)
117
+ const map = new Map<string, string>()
118
+ for (const p of list) map.set(p._id, p.name)
119
+ procedureNames.value = map
120
+ } catch {
121
+ // Non-fatal: keep the map empty so we fall back to ids.
122
+ }
123
+ }
124
+
125
+ const selected = computed<TemplateRow | null>(
126
+ () => items.value.find((t) => t._id === selectedId.value) ?? null,
127
+ )
128
+
129
+ // Search filter applied to the rail.
130
+ const search = ref('')
131
+ const filteredItems = computed<TemplateRow[]>(() => {
132
+ const q = search.value.trim().toLowerCase()
133
+ if (!q) return items.value
134
+ return items.value.filter(
135
+ (t) =>
136
+ t.name.toLowerCase().includes(q) ||
137
+ (t.description ?? '').toLowerCase().includes(q),
138
+ )
139
+ })
140
+
141
+ // Sync form fields whenever the selection changes.
142
+ watch(
143
+ selected,
144
+ (t) => {
145
+ formName.value = t?.name ?? ''
146
+ formDescription.value = t?.description ?? ''
147
+ formIcon.value = t?.icon || DEFAULT_ICON
148
+ saveError.value = null
149
+ // Reload usages for the new selection.
150
+ void loadUsages()
151
+ },
152
+ { immediate: true },
153
+ )
154
+
155
+ async function loadUsages() {
156
+ const t = selected.value
157
+ if (!t) {
158
+ usages.value = []
159
+ usagesError.value = null
160
+ return
161
+ }
162
+ usagesLoading.value = true
163
+ usagesError.value = null
164
+ try {
165
+ const data = await $authFetch<TemplateUsage[]>(
166
+ `/api/automation/templates/${t._id}/usages`,
167
+ )
168
+ usages.value = unwrapList(data)
169
+ if (usages.value.length > 0) {
170
+ await ensureProcedureNames()
171
+ }
172
+ } catch (e) {
173
+ usagesError.value = (e as Error).message ?? 'Failed to load usages'
174
+ usages.value = []
175
+ } finally {
176
+ usagesLoading.value = false
177
+ }
178
+ }
179
+
180
+ function procedureLabel(id: string): string {
181
+ return procedureNames.value.get(id) ?? id
182
+ }
183
+
184
+ function procedureLink(id: string): string {
185
+ return `/modules/automation/builder?selected=${encodeURIComponent(id)}`
186
+ }
187
+
188
+ async function onSave() {
189
+ const t = selected.value
190
+ if (!t) return
191
+ saving.value = true
192
+ saveError.value = null
193
+ try {
194
+ // PUT is required to send the full template body (including ports +
195
+ // subgraph) since the route revalidates the whole graph. We forward
196
+ // the existing ports/subgraph unchanged — only the metadata changes.
197
+ const body = {
198
+ name: formName.value.trim() || t.name,
199
+ description: formDescription.value,
200
+ icon: formIcon.value || DEFAULT_ICON,
201
+ ports: t.ports ?? [],
202
+ subgraph: t.subgraph ?? {
203
+ nodes: [],
204
+ triggerEdges: [],
205
+ dataEdges: [],
206
+ },
207
+ }
208
+ const updated = await $authFetch<TemplateRow>(
209
+ `/api/automation/templates/${t._id}`,
210
+ { method: 'PUT', body },
211
+ )
212
+ const idx = items.value.findIndex((x) => x._id === t._id)
213
+ if (idx >= 0) {
214
+ items.value[idx] = {
215
+ ...t,
216
+ ...updated,
217
+ usageCount: t.usageCount,
218
+ }
219
+ }
220
+ toast.add({
221
+ title: 'Template saved',
222
+ description: updated.name,
223
+ color: 'success',
224
+ icon: 'i-ph-check-circle',
225
+ })
226
+ } catch (e) {
227
+ saveError.value = (e as Error).message ?? 'Failed to save'
228
+ toast.add({
229
+ title: 'Failed to save template',
230
+ description: (e as Error).message ?? '',
231
+ color: 'error',
232
+ icon: 'i-ph-warning',
233
+ })
234
+ } finally {
235
+ saving.value = false
236
+ }
237
+ }
238
+
239
+ function onEditSubgraph() {
240
+ const t = selected.value
241
+ if (!t) return
242
+ router.push({
243
+ path: '/modules/automation/builder',
244
+ query: { template: t._id },
245
+ })
246
+ }
247
+
248
+ function openDelete() {
249
+ deleteError.value = null
250
+ deleteOpen.value = true
251
+ }
252
+
253
+ async function confirmDelete(force: boolean) {
254
+ const t = selected.value
255
+ if (!t) return
256
+ deleting.value = true
257
+ deleteError.value = null
258
+ try {
259
+ const url = force
260
+ ? `/api/automation/templates/${t._id}?force=1`
261
+ : `/api/automation/templates/${t._id}`
262
+ await $authFetch(url, { method: 'DELETE' })
263
+ toast.add({
264
+ title: 'Template deleted',
265
+ description: t.name,
266
+ color: 'success',
267
+ icon: 'i-ph-check-circle',
268
+ })
269
+ deleteOpen.value = false
270
+ // Drop from list, pick a new selection.
271
+ items.value = items.value.filter((x) => x._id !== t._id)
272
+ selectedId.value = items.value[0]?._id ?? null
273
+ } catch (e) {
274
+ deleteError.value = (e as Error).message ?? 'Failed to delete'
275
+ } finally {
276
+ deleting.value = false
277
+ }
278
+ }
279
+
280
+ function portRowKey(p: TemplatePort, idx: number): string {
281
+ return `${p.direction}-${p.kind}-${p.name}-${idx}`
282
+ }
283
+
284
+ function schemaTypeOf(p: TemplatePort): string {
285
+ if (p.kind !== 'data') return '—'
286
+ const t = p.schema?.type
287
+ if (typeof t === 'string') return t
288
+ if (Array.isArray(t)) return t.join(' | ')
289
+ return 'any'
290
+ }
291
+ </script>
292
+
293
+ <template>
294
+ <div :class="embedded ? 'flex flex-col gap-5' : 'flex flex-col gap-6 p-4 sm:p-6'">
295
+ <section v-if="!embedded" class="flex flex-wrap items-center gap-4">
296
+ <div
297
+ class="grid size-11 shrink-0 place-items-center rounded-xl bg-primary/10 text-primary ring ring-primary/20"
298
+ >
299
+ <UIcon name="i-ph-package" class="size-6" />
300
+ </div>
301
+ <div class="min-w-0 flex-1">
302
+ <h1 class="text-2xl font-semibold tracking-tight text-highlighted">
303
+ {{ $t('dms_automation.templates.title') }}
304
+ </h1>
305
+ <p class="text-sm text-muted">
306
+ {{ $t('dms_automation.templates.description') }}
307
+ </p>
308
+ </div>
309
+ <UButton
310
+ class="ml-auto"
311
+ color="neutral"
312
+ variant="outline"
313
+ icon="i-ph-arrows-clockwise"
314
+ :loading="loading"
315
+ @click="loadList"
316
+ >
317
+ {{ $t('dms_automation.templates.refresh') }}
318
+ </UButton>
319
+ </section>
320
+
321
+ <UAlert
322
+ v-if="loadError"
323
+ color="error"
324
+ variant="subtle"
325
+ icon="i-ph-warning"
326
+ :title="$t('dms_automation.templates.loadError')"
327
+ :description="loadError"
328
+ />
329
+
330
+ <div
331
+ class="grid grid-cols-1 gap-5 lg:grid-cols-[minmax(280px,360px)_1fr] lg:items-start"
332
+ >
333
+ <div class="flex flex-col gap-3">
334
+ <UInput
335
+ v-model="search"
336
+ icon="i-ph-magnifying-glass"
337
+ :placeholder="$t('dms_automation.templates.search')"
338
+ />
339
+ <DmsCard
340
+ :padded="false"
341
+ class="flex flex-col overflow-hidden lg:max-h-[calc(100vh-16rem)]"
342
+ >
343
+ <div
344
+ v-if="items.length === 0"
345
+ class="px-3 py-6 text-center text-sm text-dimmed"
346
+ >
347
+ {{ $t('dms_automation.templates.empty') }}
348
+ </div>
349
+ <ul v-else class="flex flex-col gap-1 overflow-y-auto p-2">
350
+ <li v-for="t in filteredItems" :key="t._id">
351
+ <button
352
+ type="button"
353
+ class="flex w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors"
354
+ :class="
355
+ selectedId === t._id
356
+ ? 'bg-primary/10 text-primary ring-1 ring-primary/20'
357
+ : 'text-toned hover:bg-elevated/60'
358
+ "
359
+ @click="selectedId = t._id"
360
+ >
361
+ <UIcon
362
+ :name="t.icon || DEFAULT_ICON"
363
+ class="mt-0.5 size-4 shrink-0"
364
+ :class="selectedId === t._id ? 'text-primary' : 'text-dimmed'"
365
+ />
366
+ <div class="min-w-0 flex-1">
367
+ <div
368
+ class="truncate text-sm font-medium"
369
+ :class="selectedId === t._id ? 'text-primary' : 'text-highlighted'"
370
+ >
371
+ {{ t.name }}
372
+ </div>
373
+ <div v-if="t.description" class="truncate text-xs text-muted">
374
+ {{ t.description }}
375
+ </div>
376
+ </div>
377
+ <UBadge
378
+ color="neutral"
379
+ variant="subtle"
380
+ size="sm"
381
+ class="shrink-0 tabular-nums"
382
+ >
383
+ {{ t.usageCount ?? 0 }}
384
+ </UBadge>
385
+ </button>
386
+ </li>
387
+ </ul>
388
+ </DmsCard>
389
+ </div>
390
+
391
+ <DmsCard v-if="selected" :padded="false" class="flex flex-col">
392
+ <div class="flex items-start gap-3 border-b border-default px-5 py-4 sm:px-6">
393
+ <div
394
+ class="grid size-10 shrink-0 place-items-center rounded-lg bg-primary/10 text-primary ring ring-primary/20"
395
+ >
396
+ <UIcon :name="formIcon || DEFAULT_ICON" class="size-5" />
397
+ </div>
398
+ <div class="min-w-0 flex-1">
399
+ <h2 class="truncate text-lg font-semibold text-highlighted">
400
+ {{ selected.name }}
401
+ </h2>
402
+ <p class="mt-1 font-mono text-xs text-dimmed">{{ selected._id }}</p>
403
+ </div>
404
+ <UBadge color="neutral" variant="subtle" class="shrink-0">
405
+ {{
406
+ $t('dms_automation.templates.usedInN', {
407
+ n: selected.usageCount ?? 0,
408
+ })
409
+ }}
410
+ </UBadge>
411
+ </div>
412
+
413
+ <div class="flex flex-col gap-6 px-5 py-4 sm:px-6">
414
+ <div class="flex flex-col gap-3">
415
+ <UFormField
416
+ :label="$t('dms_automation.templates.form.name')"
417
+ required
418
+ >
419
+ <UInput v-model="formName" class="w-full" />
420
+ </UFormField>
421
+ <UFormField
422
+ :label="$t('dms_automation.templates.form.description')"
423
+ >
424
+ <UTextarea v-model="formDescription" class="w-full" :rows="3" />
425
+ </UFormField>
426
+ <UFormField
427
+ :label="$t('dms_automation.templates.form.icon')"
428
+ :help="$t('dms_automation.templates.form.iconHelp')"
429
+ >
430
+ <UInput
431
+ v-model="formIcon"
432
+ :placeholder="DEFAULT_ICON"
433
+ class="w-full"
434
+ />
435
+ </UFormField>
436
+ <UAlert
437
+ v-if="saveError"
438
+ color="error"
439
+ variant="subtle"
440
+ icon="i-ph-warning"
441
+ :title="$t('dms_automation.templates.saveError')"
442
+ :description="saveError"
443
+ />
444
+ <div class="flex justify-end gap-2">
445
+ <UButton
446
+ color="primary"
447
+ icon="i-ph-floppy-disk"
448
+ :loading="saving"
449
+ @click="onSave"
450
+ >
451
+ {{ $t('dms_automation.templates.save') }}
452
+ </UButton>
453
+ </div>
454
+ </div>
455
+
456
+ <div class="flex flex-col gap-2">
457
+ <div class="flex items-center justify-between">
458
+ <div class="text-sm font-semibold text-highlighted">
459
+ {{ $t('dms_automation.templates.ports.title') }}
460
+ </div>
461
+ <UButton
462
+ variant="outline"
463
+ color="neutral"
464
+ icon="i-ph-pencil-simple"
465
+ size="sm"
466
+ @click="onEditSubgraph"
467
+ >
468
+ {{ $t('dms_automation.templates.editSubgraph') }}
469
+ </UButton>
470
+ </div>
471
+ <p class="text-xs text-muted">
472
+ {{ $t('dms_automation.templates.ports.editHint') }}
473
+ </p>
474
+ <div
475
+ v-if="(selected.ports ?? []).length === 0"
476
+ class="text-sm text-dimmed"
477
+ >
478
+ {{ $t('dms_automation.templates.ports.empty') }}
479
+ </div>
480
+ <div v-else class="overflow-hidden rounded-lg border border-default">
481
+ <table class="w-full text-sm">
482
+ <thead>
483
+ <tr class="border-b border-default bg-muted">
484
+ <th class="px-4 py-2.5 text-left text-xs font-semibold text-dimmed">
485
+ {{ $t('dms_automation.templates.ports.col.kind') }}
486
+ </th>
487
+ <th class="px-4 py-2.5 text-left text-xs font-semibold text-dimmed">
488
+ {{ $t('dms_automation.templates.ports.col.direction') }}
489
+ </th>
490
+ <th class="px-4 py-2.5 text-left text-xs font-semibold text-dimmed">
491
+ {{ $t('dms_automation.templates.ports.col.name') }}
492
+ </th>
493
+ <th class="px-4 py-2.5 text-left text-xs font-semibold text-dimmed">
494
+ {{ $t('dms_automation.templates.ports.col.type') }}
495
+ </th>
496
+ </tr>
497
+ </thead>
498
+ <tbody>
499
+ <tr
500
+ v-for="(p, idx) in selected.ports"
501
+ :key="portRowKey(p, idx)"
502
+ class="border-b border-default/60 last:border-0"
503
+ >
504
+ <td class="px-4 py-2.5">
505
+ <UBadge
506
+ :color="p.kind === 'trigger' ? 'primary' : 'neutral'"
507
+ variant="subtle"
508
+ >
509
+ {{ p.kind }}
510
+ </UBadge>
511
+ </td>
512
+ <td class="whitespace-nowrap px-4 py-2.5 text-muted">
513
+ {{ p.direction }}
514
+ </td>
515
+ <td class="px-4 py-2.5 font-mono text-highlighted">
516
+ {{ p.name }}
517
+ </td>
518
+ <td class="whitespace-nowrap px-4 py-2.5 font-mono text-primary">
519
+ {{ schemaTypeOf(p) }}
520
+ </td>
521
+ </tr>
522
+ </tbody>
523
+ </table>
524
+ </div>
525
+ </div>
526
+
527
+ <div class="flex flex-col gap-2">
528
+ <div class="text-sm font-semibold text-highlighted">
529
+ {{ $t('dms_automation.templates.usages.title') }}
530
+ </div>
531
+ <UAlert
532
+ v-if="usagesError"
533
+ color="error"
534
+ variant="subtle"
535
+ icon="i-ph-warning"
536
+ :title="$t('dms_automation.templates.usagesError')"
537
+ :description="usagesError"
538
+ />
539
+ <div v-else-if="usagesLoading" class="text-sm text-dimmed">
540
+ Loading…
541
+ </div>
542
+ <div v-else-if="usages.length === 0" class="text-sm text-dimmed">
543
+ {{ $t('dms_automation.templates.usages.empty') }}
544
+ </div>
545
+ <ul v-else class="flex flex-col">
546
+ <li
547
+ v-for="(u, idx) in usages"
548
+ :key="`${u.procedureId}-${u.nodeId}-${idx}`"
549
+ class="flex items-center gap-3 border-b border-default/60 py-2 last:border-0"
550
+ >
551
+ <UIcon name="i-ph-puzzle-piece" class="size-4 shrink-0 text-dimmed" />
552
+ <DmsLink
553
+ :to="procedureLink(u.procedureId)"
554
+ class="truncate text-highlighted hover:text-primary hover:underline"
555
+ >
556
+ {{ procedureLabel(u.procedureId) }}
557
+ </DmsLink>
558
+ <span class="truncate font-mono text-xs text-dimmed">
559
+ {{ u.nodeId }}
560
+ </span>
561
+ </li>
562
+ </ul>
563
+ </div>
564
+
565
+ <div class="flex justify-end border-t border-default pt-4">
566
+ <UButton
567
+ color="error"
568
+ variant="subtle"
569
+ icon="i-ph-trash"
570
+ @click="openDelete"
571
+ >
572
+ {{ $t('dms_automation.templates.delete') }}
573
+ </UButton>
574
+ </div>
575
+ </div>
576
+ </DmsCard>
577
+
578
+ <DmsCard
579
+ v-else
580
+ class="flex min-h-[16rem] items-center justify-center text-center"
581
+ >
582
+ <div class="flex flex-col items-center gap-3 text-dimmed">
583
+ <UIcon name="i-ph-package" class="size-8" />
584
+ <p class="max-w-xs text-sm">
585
+ {{ $t('dms_automation.templates.empty') }}
586
+ </p>
587
+ </div>
588
+ </DmsCard>
589
+ </div>
590
+
591
+ <UModal
592
+ v-model:open="deleteOpen"
593
+ :title="$t('dms_automation.templates.deleteConfirm.title')"
594
+ >
595
+ <template #body>
596
+ <div class="flex flex-col gap-3">
597
+ <p class="text-default text-sm">
598
+ {{
599
+ $t(
600
+ 'dms_automation.templates.deleteConfirm.message',
601
+ { name: selected?.name ?? '' },
602
+ )
603
+ }}
604
+ </p>
605
+ <UAlert
606
+ v-if="(selected?.usageCount ?? 0) > 0"
607
+ color="warning"
608
+ variant="subtle"
609
+ icon="i-ph-warning"
610
+ :title="
611
+ $t(
612
+ 'dms_automation.templates.deleteConfirm.usageWarningTitle',
613
+ )
614
+ "
615
+ :description="
616
+ $t(
617
+ 'dms_automation.templates.deleteConfirm.usageWarning',
618
+ { n: selected?.usageCount ?? 0 },
619
+ )
620
+ "
621
+ />
622
+ <UAlert
623
+ v-if="deleteError"
624
+ color="error"
625
+ variant="subtle"
626
+ icon="i-ph-warning"
627
+ :title="$t('dms_automation.templates.deleteError')"
628
+ :description="deleteError"
629
+ />
630
+ </div>
631
+ </template>
632
+ <template #footer>
633
+ <div class="flex w-full justify-end gap-2">
634
+ <UButton
635
+ :label="$t('dms_automation.templates.deleteConfirm.cancel')"
636
+ variant="outline"
637
+ color="neutral"
638
+ :disabled="deleting"
639
+ @click="deleteOpen = false"
640
+ />
641
+ <UButton
642
+ v-if="(selected?.usageCount ?? 0) > 0"
643
+ color="error"
644
+ icon="i-ph-trash"
645
+ :loading="deleting"
646
+ @click="confirmDelete(true)"
647
+ >
648
+ {{ $t('dms_automation.templates.forceDelete') }}
649
+ </UButton>
650
+ <UButton
651
+ v-else
652
+ color="error"
653
+ icon="i-ph-trash"
654
+ :loading="deleting"
655
+ @click="confirmDelete(false)"
656
+ >
657
+ {{ $t('dms_automation.templates.delete') }}
658
+ </UButton>
659
+ </div>
660
+ </template>
661
+ </UModal>
662
+ </div>
663
+ </template>