@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,289 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import type { GroupPort } from '../../composables/useGraphConnect'
4
+
5
+ type SchemaTypeChoice = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any'
6
+
7
+ const SCHEMA_TYPE_CHOICES: SchemaTypeChoice[] = [
8
+ 'string',
9
+ 'number',
10
+ 'boolean',
11
+ 'object',
12
+ 'array',
13
+ 'any',
14
+ ]
15
+
16
+ const props = defineProps<{
17
+ ports: GroupPort[]
18
+ }>()
19
+
20
+ const emit = defineEmits<{
21
+ (e: 'ports:update', next: GroupPort[]): void
22
+ }>()
23
+
24
+ const activeTab = ref<'data' | 'trigger'>('data')
25
+
26
+ const tabItems = [
27
+ { label: 'Data ports', value: 'data', slot: 'data', icon: 'i-ph-plugs' },
28
+ { label: 'Trigger ports', value: 'trigger', slot: 'trigger', icon: 'i-ph-lightning' },
29
+ ]
30
+
31
+ interface Section {
32
+ kind: 'data' | 'trigger'
33
+ direction: 'in' | 'out'
34
+ }
35
+
36
+ function portsFor(section: Section): Array<{ port: GroupPort; index: number }> {
37
+ const out: Array<{ port: GroupPort; index: number }> = []
38
+ props.ports.forEach((p, i) => {
39
+ if (p.kind === section.kind && p.direction === section.direction) {
40
+ out.push({ port: p, index: i })
41
+ }
42
+ })
43
+ return out
44
+ }
45
+
46
+ // Surface a per-section duplicate-name error. Returns the first duplicated
47
+ // name found within the (kind, direction) tuple, or `null` if all names are
48
+ // unique. Used to both disable the Add button and display an inline alert.
49
+ function duplicateNameIn(section: Section): string | null {
50
+ const seen = new Set<string>()
51
+ for (const p of props.ports) {
52
+ if (p.kind !== section.kind || p.direction !== section.direction) continue
53
+ const trimmed = p.name.trim()
54
+ if (trimmed === '') continue
55
+ if (seen.has(trimmed)) return trimmed
56
+ seen.add(trimmed)
57
+ }
58
+ return null
59
+ }
60
+
61
+ function nextDefaultName(section: Section): string {
62
+ const taken = new Set(
63
+ props.ports
64
+ .filter((p) => p.kind === section.kind && p.direction === section.direction)
65
+ .map((p) => p.name.trim()),
66
+ )
67
+ const prefix = section.direction === 'in' ? 'input' : 'output'
68
+ for (let i = 1; i < 999; i++) {
69
+ const candidate = `${prefix}${i}`
70
+ if (!taken.has(candidate)) return candidate
71
+ }
72
+ return `${prefix}_${Date.now()}`
73
+ }
74
+
75
+ function emitPorts(next: GroupPort[]) {
76
+ emit('ports:update', next)
77
+ }
78
+
79
+ function addPort(section: Section) {
80
+ if (duplicateNameIn(section)) return
81
+ const newPort: GroupPort =
82
+ section.kind === 'data'
83
+ ? {
84
+ name: nextDefaultName(section),
85
+ kind: 'data',
86
+ direction: section.direction,
87
+ schema: { type: 'any' },
88
+ }
89
+ : {
90
+ name: nextDefaultName(section),
91
+ kind: 'trigger',
92
+ direction: section.direction,
93
+ }
94
+ emitPorts([...props.ports, newPort])
95
+ }
96
+
97
+ function removePort(index: number) {
98
+ const next = props.ports.slice()
99
+ next.splice(index, 1)
100
+ emitPorts(next)
101
+ }
102
+
103
+ function updateName(index: number, name: string) {
104
+ const next = props.ports.map((p, i) => (i === index ? { ...p, name } : p))
105
+ emitPorts(next)
106
+ }
107
+
108
+ function schemaTypeOf(port: GroupPort): SchemaTypeChoice {
109
+ const t = port.schema?.type
110
+ if (typeof t === 'string' && (SCHEMA_TYPE_CHOICES as string[]).includes(t)) {
111
+ return t as SchemaTypeChoice
112
+ }
113
+ return 'any'
114
+ }
115
+
116
+ function updateSchemaType(index: number, type: SchemaTypeChoice) {
117
+ const next = props.ports.map((p, i) => {
118
+ if (i !== index) return p
119
+ if (p.kind !== 'data') return p
120
+ if (type === 'any') {
121
+ // Represent "any" as a schema with no type constraint. Keeping a
122
+ // `schema` object (rather than dropping it) preserves the invariant
123
+ // that data ports always carry a schema, even if it's permissive.
124
+ return { ...p, schema: { type: 'any' } }
125
+ }
126
+ return { ...p, schema: { ...(p.schema ?? {}), type } }
127
+ })
128
+ emitPorts(next)
129
+ }
130
+
131
+ const sections = computed(() => ({
132
+ data: {
133
+ in: { kind: 'data' as const, direction: 'in' as const },
134
+ out: { kind: 'data' as const, direction: 'out' as const },
135
+ },
136
+ trigger: {
137
+ in: { kind: 'trigger' as const, direction: 'in' as const },
138
+ out: { kind: 'trigger' as const, direction: 'out' as const },
139
+ },
140
+ }))
141
+ </script>
142
+
143
+ <template>
144
+ <UCard>
145
+ <template #header>
146
+ <div class="flex items-center gap-2">
147
+ <UIcon name="i-ph-plugs-connected" class="size-4 text-primary" />
148
+ <span class="text-sm font-semibold text-highlighted">
149
+ {{ $t('dms_automation.editor.groupPorts.title') }}
150
+ </span>
151
+ </div>
152
+ </template>
153
+
154
+ <UTabs v-model="activeTab" :items="tabItems" size="sm" variant="link">
155
+ <template #data>
156
+ <div class="flex flex-col gap-4 pt-2">
157
+ <div
158
+ v-for="section in [sections.data.in, sections.data.out]"
159
+ :key="`${section.kind}-${section.direction}`"
160
+ class="flex flex-col gap-2"
161
+ >
162
+ <div class="text-default text-xs font-medium uppercase tracking-wide">
163
+ {{ section.direction === 'in' ? 'Inputs' : 'Outputs' }}
164
+ </div>
165
+
166
+ <div
167
+ v-for="entry in portsFor(section)"
168
+ :key="entry.index"
169
+ class="flex items-center gap-2"
170
+ >
171
+ <UInput
172
+ class="flex-1"
173
+ placeholder="Port name"
174
+ :model-value="entry.port.name"
175
+ @update:model-value="(v: string | number) => updateName(entry.index, String(v))"
176
+ />
177
+ <USelect
178
+ class="w-28"
179
+ :items="[...SCHEMA_TYPE_CHOICES]"
180
+ :model-value="schemaTypeOf(entry.port)"
181
+ @update:model-value="(v: string) => updateSchemaType(entry.index, v as SchemaTypeChoice)"
182
+ />
183
+ <UButton
184
+ size="xs"
185
+ variant="ghost"
186
+ color="error"
187
+ icon="i-ph-trash"
188
+ :aria-label="$t('dms_automation.editor.groupPorts.remove')"
189
+ @click="removePort(entry.index)"
190
+ />
191
+ </div>
192
+
193
+ <div v-if="portsFor(section).length === 0" class="text-dimmed text-xs">
194
+ No {{ section.direction === 'in' ? 'inputs' : 'outputs' }} defined.
195
+ </div>
196
+
197
+ <UAlert
198
+ v-if="duplicateNameIn(section)"
199
+ color="error"
200
+ variant="subtle"
201
+ icon="i-ph-warning"
202
+ :title="$t('dms_automation.editor.groupPorts.duplicate', { name: duplicateNameIn(section) })"
203
+ :description="$t('dms_automation.editor.groupPorts.duplicateHelp')"
204
+ />
205
+
206
+ <div>
207
+ <UButton
208
+ size="xs"
209
+ variant="soft"
210
+ color="primary"
211
+ icon="i-ph-plus"
212
+ :disabled="!!duplicateNameIn(section)"
213
+ @click="addPort(section)"
214
+ >
215
+ Add {{ section.direction === 'in' ? 'input' : 'output' }}
216
+ </UButton>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </template>
221
+
222
+ <template #trigger>
223
+ <div class="flex flex-col gap-4 pt-2">
224
+ <div class="text-dimmed text-xs">
225
+ Name a port <code class="text-default">main</code> to place its
226
+ handle on the group's title bar (like an ordinary action).
227
+ Any other name renders in the body, one row per port.
228
+ </div>
229
+ <div
230
+ v-for="section in [sections.trigger.in, sections.trigger.out]"
231
+ :key="`${section.kind}-${section.direction}`"
232
+ class="flex flex-col gap-2"
233
+ >
234
+ <div class="text-default text-xs font-medium uppercase tracking-wide">
235
+ {{ section.direction === 'in' ? 'Inputs' : 'Outputs' }}
236
+ </div>
237
+
238
+ <div
239
+ v-for="entry in portsFor(section)"
240
+ :key="entry.index"
241
+ class="flex items-center gap-2"
242
+ >
243
+ <UInput
244
+ class="flex-1"
245
+ placeholder="Port name"
246
+ :model-value="entry.port.name"
247
+ @update:model-value="(v: string | number) => updateName(entry.index, String(v))"
248
+ />
249
+ <UButton
250
+ size="xs"
251
+ variant="ghost"
252
+ color="error"
253
+ icon="i-ph-trash"
254
+ :aria-label="$t('dms_automation.editor.groupPorts.remove')"
255
+ @click="removePort(entry.index)"
256
+ />
257
+ </div>
258
+
259
+ <div v-if="portsFor(section).length === 0" class="text-dimmed text-xs">
260
+ No {{ section.direction === 'in' ? 'inputs' : 'outputs' }} defined.
261
+ </div>
262
+
263
+ <UAlert
264
+ v-if="duplicateNameIn(section)"
265
+ color="error"
266
+ variant="subtle"
267
+ icon="i-ph-warning"
268
+ :title="$t('dms_automation.editor.groupPorts.duplicate', { name: duplicateNameIn(section) })"
269
+ :description="$t('dms_automation.editor.groupPorts.duplicateHelp')"
270
+ />
271
+
272
+ <div>
273
+ <UButton
274
+ size="xs"
275
+ variant="soft"
276
+ color="primary"
277
+ icon="i-ph-plus"
278
+ :disabled="!!duplicateNameIn(section)"
279
+ @click="addPort(section)"
280
+ >
281
+ Add {{ section.direction === 'in' ? 'input' : 'output' }}
282
+ </UButton>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </template>
287
+ </UTabs>
288
+ </UCard>
289
+ </template>
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+
4
+ // Textarea for any-typed config values. Keeps local string state so the user
5
+ // can type freely (e.g. a partial JSON literal) without the parent re-render
6
+ // clobbering it. Only commits the parsed value upward on blur. If the text
7
+ // fails to JSON.parse on blur, the raw string is committed as-is.
8
+
9
+ const props = defineProps<{
10
+ modelValue: unknown
11
+ }>()
12
+
13
+ const emit = defineEmits<{
14
+ (e: 'update:model-value', v: unknown): void
15
+ }>()
16
+
17
+ function serialize(v: unknown): string {
18
+ if (v === undefined) return ''
19
+ if (typeof v === 'string') return v
20
+ try {
21
+ return JSON.stringify(v, null, 2)
22
+ } catch {
23
+ return ''
24
+ }
25
+ }
26
+
27
+ function parse(s: string): unknown {
28
+ if (s.trim() === '') return undefined
29
+ try {
30
+ return JSON.parse(s)
31
+ } catch {
32
+ return s
33
+ }
34
+ }
35
+
36
+ const text = ref<string>(serialize(props.modelValue))
37
+
38
+ watch(
39
+ () => props.modelValue,
40
+ (v) => {
41
+ // Only sync from outside if the parsed text doesn't already match —
42
+ // avoids overwriting the user's in-progress text on unrelated parent
43
+ // re-renders.
44
+ const parsed = parse(text.value)
45
+ if (JSON.stringify(parsed) === JSON.stringify(v)) return
46
+ text.value = serialize(v)
47
+ },
48
+ )
49
+
50
+ function onBlur() {
51
+ emit('update:model-value', parse(text.value))
52
+ }
53
+ </script>
54
+
55
+ <template>
56
+ <UTextarea
57
+ v-model="text"
58
+ :rows="4"
59
+ placeholder="String, number, true/false, null, or JSON"
60
+ class="font-mono"
61
+ @blur="onBlur"
62
+ />
63
+ </template>
@@ -0,0 +1,356 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import JsonField from './JsonField.vue'
4
+ import type { NodeKindEntry } from '../../composables/useAutomationNodeKinds'
5
+ import type {
6
+ ActionType,
7
+ DataEdge,
8
+ DataNodeType,
9
+ GraphNode,
10
+ GroupPort,
11
+ JsonSchema,
12
+ JsonSchemaProperty,
13
+ TriggerType,
14
+ } from '../../composables/useGraphConnect'
15
+ import { isGroupNode } from '../../composables/useGraphConnect'
16
+
17
+ const props = defineProps<{
18
+ node: GraphNode | null
19
+ dataEdges: DataEdge[]
20
+ triggerType: TriggerType | null
21
+ actionType: ActionType | null
22
+ triggerTypes: TriggerType[]
23
+ actionTypes: ActionType[]
24
+ dataNodeTypes: DataNodeType[]
25
+ // Per-NodeKind metadata served by `/api/automation/node-kinds`. The
26
+ // inspector reads each kind's optional `configSchema` to render its
27
+ // editable config form — no per-kind branches here.
28
+ nodeKinds: NodeKindEntry[]
29
+ // Read-only graphs (template-instance view) hide the delete button.
30
+ readOnly?: boolean
31
+ }>()
32
+
33
+ const emit = defineEmits<{
34
+ (e: 'update:node', n: GraphNode): void
35
+ (e: 'unwire', payload: { field: string }): void
36
+ (e: 'ports:update', payload: { nodeId: string; ports: GroupPort[] }): void
37
+ // Fires when the user clicks the inspector's "Enter subgraph" button on a
38
+ // group node. Editor.vue translates this into a subgraph-path push (same
39
+ // path as double-clicking the group on the canvas).
40
+ (e: 'group:enter', payload: { id: string }): void
41
+ // Fires when the user clicks the inspector's "Delete node" button. Editor.vue
42
+ // removes the node and its edges from the current graph — the same outcome as
43
+ // pressing Delete/Backspace on the canvas, surfaced for discoverability.
44
+ (e: 'delete:node', payload: { id: string }): void
45
+ }>()
46
+
47
+ // Sentinel nodes (the group's input/output handles) are auto-managed and
48
+ // never user-deletable, mirroring the canvas keyboard-delete guard.
49
+ const SENTINEL_KINDS: ReadonlySet<string> = new Set(['groupInput', 'groupOutput'])
50
+
51
+ const canDelete = computed<boolean>(() => {
52
+ const n = props.node
53
+ if (!n || props.readOnly) return false
54
+ return !SENTINEL_KINDS.has(n.kind)
55
+ })
56
+
57
+ function emitDelete() {
58
+ if (!props.node || !canDelete.value) return
59
+ emit('delete:node', { id: props.node.id })
60
+ }
61
+
62
+ // True when the selected node is a group with a usable subgraph. Template
63
+ // instances without an inline subgraph (templateId set, no `subgraph`) can't
64
+ // be entered yet — the runtime resolves their subgraph via the template
65
+ // cache, but the editor doesn't wire that into navigation.
66
+ const canEnterGroup = computed<boolean>(() => {
67
+ const n = props.node
68
+ if (!n || n.kind !== 'group') return false
69
+ const g = n as GraphNode & { templateId?: string; subgraph?: unknown }
70
+ if (g.templateId && !g.subgraph) return false
71
+ return true
72
+ })
73
+
74
+ function emitEnterGroup() {
75
+ if (!props.node) return
76
+ emit('group:enter', { id: props.node.id })
77
+ }
78
+
79
+ // Group ports — surfaced via GroupPortEditor when a group node is selected.
80
+ // The editor emits ports:update with the full GroupPort[] array; we bubble
81
+ // it up to Editor.vue with the owning node id so the parent can locate the
82
+ // group in whichever graph it lives in and run sentinel sync.
83
+ const groupPorts = computed<GroupPort[]>(() => {
84
+ const n = props.node
85
+ if (!n || !isGroupNode(n)) return []
86
+ return Array.isArray(n.ports) ? n.ports : []
87
+ })
88
+
89
+ function onGroupPortsUpdate(next: GroupPort[]) {
90
+ if (!props.node || props.node.kind !== 'group') return
91
+ emit('ports:update', { nodeId: props.node.id, ports: next })
92
+ }
93
+
94
+ const dataNodeType = computed<DataNodeType | null>(() => {
95
+ const n = props.node
96
+ if (!n || n.kind !== 'data') return null
97
+ return props.dataNodeTypes.find((d) => d.id === n.typeId) ?? null
98
+ })
99
+
100
+ const schema = computed<JsonSchema | null>(() => {
101
+ const n = props.node
102
+ if (!n) return null
103
+ // Type-registry-backed kinds — their config form comes from the
104
+ // selected type's schema, not the kind's own configSchema.
105
+ if (n.kind === 'trigger') return props.triggerType?.configSchema ?? null
106
+ if (n.kind === 'action') return props.actionType?.inputSchema ?? null
107
+ // Data nodes: merge inputSchema (wired-port fields) with configSchema
108
+ // (in-place config fields, e.g. a constant's value). configSchema wins on
109
+ // key collisions; nodes declaring only one schema are unaffected.
110
+ if (n.kind === 'data') {
111
+ const t = dataNodeType.value
112
+ if (!t) return null
113
+ if (!t.configSchema) return t.inputSchema ?? null
114
+ return {
115
+ type: 'object',
116
+ properties: {
117
+ ...(t.inputSchema?.properties ?? {}),
118
+ ...(t.configSchema.properties ?? {}),
119
+ },
120
+ required: [
121
+ ...(t.inputSchema?.required ?? []),
122
+ ...(t.configSchema.required ?? []),
123
+ ],
124
+ }
125
+ }
126
+ return (
127
+ (props.nodeKinds.find((k) => k.kind === n.kind)?.meta.ui?.configSchema as
128
+ | JsonSchema
129
+ | undefined) ?? null
130
+ )
131
+ })
132
+
133
+ const fields = computed<
134
+ Array<{ name: string; prop: JsonSchemaProperty }>
135
+ >(() => {
136
+ const s = schema.value
137
+ if (!s?.properties) return []
138
+ return Object.entries(s.properties).map(([name, prop]) => ({
139
+ name,
140
+ prop: prop ?? {},
141
+ }))
142
+ })
143
+
144
+ const nodeTypeLabel = computed<string>(() => {
145
+ const n = props.node
146
+ if (!n) return ''
147
+ if (n.kind === 'trigger') return props.triggerType?.name ?? n.typeId ?? 'Trigger'
148
+ if (n.kind === 'action') return props.actionType?.name ?? n.typeId ?? 'Action'
149
+ if (n.kind === 'data') return dataNodeType.value?.name ?? n.typeId ?? 'Data'
150
+ return n.kind
151
+ })
152
+
153
+ function findIncoming(field: string): DataEdge | undefined {
154
+ if (!props.node) return undefined
155
+ const id = props.node.id
156
+ return props.dataEdges.find((e) => e.to.node === id && e.to.field === field)
157
+ }
158
+
159
+ function emitUpdated(field: string, value: unknown) {
160
+ if (!props.node) return
161
+ const next: GraphNode = {
162
+ ...props.node,
163
+ config: { ...props.node.config, [field]: value },
164
+ }
165
+ emit('update:node', next)
166
+ }
167
+
168
+ function getValue(field: string): unknown {
169
+ return props.node?.config?.[field]
170
+ }
171
+
172
+ function asString(v: unknown): string {
173
+ if (v === undefined || v === null) return ''
174
+ if (typeof v === 'string') return v
175
+ return String(v)
176
+ }
177
+
178
+ function asNumber(v: unknown): number | undefined {
179
+ if (v === undefined || v === null || v === '') return undefined
180
+ const n = Number(v)
181
+ return Number.isNaN(n) ? undefined : n
182
+ }
183
+
184
+ function asBool(v: unknown): boolean {
185
+ return !!v
186
+ }
187
+
188
+ // Returns true if `prop.type` is `"string"` OR an array of types that includes
189
+ // `"string"`. Used to render a plain string input instead of a JSON textarea
190
+ // for multi-type fields like compare-node ports.
191
+ function isStringish(prop: JsonSchemaProperty): boolean {
192
+ const t = prop.type
193
+ if (t === 'string') return true
194
+ if (Array.isArray(t) && t.includes('string')) return true
195
+ return false
196
+ }
197
+
198
+ function isNumberish(prop: JsonSchemaProperty): boolean {
199
+ const t = prop.type
200
+ if (t === 'number' || t === 'integer') return true
201
+ if (Array.isArray(t) && (t.includes('number') || t.includes('integer')) && !t.includes('string')) return true
202
+ return false
203
+ }
204
+
205
+ function isBoolish(prop: JsonSchemaProperty): boolean {
206
+ const t = prop.type
207
+ if (t === 'boolean') return true
208
+ if (Array.isArray(t) && t.includes('boolean') && !t.includes('string') && !t.includes('number')) return true
209
+ return false
210
+ }
211
+
212
+ function arrayToText(v: unknown): string {
213
+ if (Array.isArray(v)) return v.map((x) => String(x)).join('\n')
214
+ if (typeof v === 'string') return v
215
+ return ''
216
+ }
217
+
218
+ function textToArray(s: string): string[] {
219
+ return s.split('\n').map((x) => x.trim()).filter((x) => x.length > 0)
220
+ }
221
+
222
+
223
+ function onUnwire(field: string) {
224
+ emit('unwire', { field })
225
+ }
226
+ </script>
227
+
228
+ <template>
229
+ <UCard>
230
+ <template #header>
231
+ <div class="flex items-center gap-2">
232
+ <UIcon name="i-ph-faders" class="size-4 text-primary" />
233
+ <span class="text-sm font-semibold text-highlighted">
234
+ {{ $t('dms_automation.editor.inspector.title') }}
235
+ </span>
236
+ </div>
237
+ </template>
238
+
239
+ <div v-if="!node" class="p-2 text-sm text-dimmed">
240
+ {{ $t('dms_automation.editor.inspector.empty') }}
241
+ </div>
242
+
243
+ <div v-else class="flex flex-col gap-3">
244
+ <UButton
245
+ v-if="canEnterGroup"
246
+ block
247
+ color="primary"
248
+ icon="i-ph-arrow-square-in"
249
+ variant="outline"
250
+ @click="emitEnterGroup"
251
+ >
252
+ {{ $t('dms_automation.editor.inspector.enterSubgraph') }}
253
+ </UButton>
254
+ <div>
255
+ <div class="text-xs text-dimmed">
256
+ {{ $t('dms_automation.editor.inspector.type') }}
257
+ </div>
258
+ <div class="text-sm text-highlighted">{{ nodeTypeLabel }}</div>
259
+ </div>
260
+ <div>
261
+ <div class="text-xs text-dimmed">
262
+ {{ $t('dms_automation.editor.inspector.nodeId') }}
263
+ </div>
264
+ <div class="truncate font-mono text-xs text-toned">{{ node.id }}</div>
265
+ </div>
266
+
267
+ <div
268
+ v-if="fields.length === 0 && node.kind !== 'group'"
269
+ class="text-xs text-dimmed"
270
+ >
271
+ {{ $t('dms_automation.editor.inspector.noFields') }}
272
+ </div>
273
+
274
+ <!-- Group port editor — declared ports drive the sentinel handles
275
+ inside the subgraph (see syncSentinels in Editor.vue). -->
276
+ <DmsAutomationGroupPortEditor
277
+ v-if="node.kind === 'group'"
278
+ :ports="groupPorts"
279
+ @ports:update="onGroupPortsUpdate"
280
+ />
281
+
282
+ <div
283
+ v-for="f in fields"
284
+ :key="f.name"
285
+ class="flex flex-col gap-1"
286
+ >
287
+ <label class="text-xs font-medium text-highlighted">{{ f.prop.title || f.name }}</label>
288
+
289
+ <template v-if="findIncoming(f.name)">
290
+ <DmsAutomationEditorWiredField
291
+ :field="f.name"
292
+ :source="{
293
+ node: findIncoming(f.name)!.from.node,
294
+ port: findIncoming(f.name)!.from.port,
295
+ }"
296
+ :source-label="findIncoming(f.name)!.from.node"
297
+ @unwire="onUnwire(f.name)"
298
+ />
299
+ </template>
300
+
301
+ <template v-else>
302
+ <USelect
303
+ v-if="Array.isArray(f.prop.enum) && f.prop.enum.length > 0"
304
+ :items="(f.prop.enum as unknown[]).map((v) => String(v))"
305
+ :model-value="asString(getValue(f.name) ?? f.prop.default)"
306
+ @update:model-value="(v: string) => emitUpdated(f.name, v)"
307
+ />
308
+ <UInput
309
+ v-else-if="isStringish(f.prop)"
310
+ :model-value="asString(getValue(f.name))"
311
+ @update:model-value="(v: string | number) => emitUpdated(f.name, String(v))"
312
+ />
313
+ <UInput
314
+ v-else-if="isNumberish(f.prop)"
315
+ type="number"
316
+ :model-value="asNumber(getValue(f.name))"
317
+ @update:model-value="(v: string | number) => emitUpdated(f.name, asNumber(v))"
318
+ />
319
+ <USwitch
320
+ v-else-if="isBoolish(f.prop)"
321
+ :model-value="asBool(getValue(f.name))"
322
+ @update:model-value="(v: boolean) => emitUpdated(f.name, v)"
323
+ />
324
+ <UTextarea
325
+ v-else-if="f.prop.type === 'array'"
326
+ :rows="3"
327
+ :placeholder="$t('dms_automation.editor.inspector.arrayPlaceholder')"
328
+ :model-value="arrayToText(getValue(f.name))"
329
+ @update:model-value="(v: string | number) => emitUpdated(f.name, textToArray(String(v)))"
330
+ />
331
+ <JsonField
332
+ v-else
333
+ :model-value="getValue(f.name)"
334
+ @update:model-value="(v: unknown) => emitUpdated(f.name, v)"
335
+ />
336
+ </template>
337
+
338
+ <p v-if="f.prop.description" class="text-xs text-dimmed">
339
+ {{ f.prop.description }}
340
+ </p>
341
+ </div>
342
+
343
+ <UButton
344
+ v-if="canDelete"
345
+ block
346
+ color="error"
347
+ icon="i-ph-trash"
348
+ variant="outline"
349
+ class="mt-1"
350
+ @click="emitDelete"
351
+ >
352
+ {{ $t('dms_automation.editor.inspector.deleteNode') }}
353
+ </UButton>
354
+ </div>
355
+ </UCard>
356
+ </template>