@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.
- package/LICENSE +190 -0
- package/README.md +95 -0
- package/dist/cluster/bus.d.ts +17 -0
- package/dist/cluster/bus.js +82 -0
- package/dist/cluster/bus.js.map +1 -0
- package/dist/cluster/instance.d.ts +1 -0
- package/dist/cluster/instance.js +6 -0
- package/dist/cluster/instance.js.map +1 -0
- package/dist/cluster/leader.d.ts +7 -0
- package/dist/cluster/leader.js +123 -0
- package/dist/cluster/leader.js.map +1 -0
- package/dist/cluster/mode.d.ts +3 -0
- package/dist/cluster/mode.js +25 -0
- package/dist/cluster/mode.js.map +1 -0
- package/dist/data/procedures-table.d.ts +66 -0
- package/dist/data/procedures-table.js +271 -0
- package/dist/data/procedures-table.js.map +1 -0
- package/dist/data/runs-table.d.ts +23 -0
- package/dist/data/runs-table.js +125 -0
- package/dist/data/runs-table.js.map +1 -0
- package/dist/db/index.d.ts +7 -0
- package/dist/db/index.js +24 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/models/automation_template.model.d.ts +58 -0
- package/dist/db/models/automation_template.model.js +134 -0
- package/dist/db/models/automation_template.model.js.map +1 -0
- package/dist/db/models/index.d.ts +3 -0
- package/dist/db/models/index.js +20 -0
- package/dist/db/models/index.js.map +1 -0
- package/dist/db/models/procedure.model.d.ts +25 -0
- package/dist/db/models/procedure.model.js +28 -0
- package/dist/db/models/procedure.model.js.map +1 -0
- package/dist/db/models/procedure_run.model.d.ts +34 -0
- package/dist/db/models/procedure_run.model.js +39 -0
- package/dist/db/models/procedure_run.model.js.map +1 -0
- package/dist/db/models/stats.model.d.ts +73 -0
- package/dist/db/models/stats.model.js +267 -0
- package/dist/db/models/stats.model.js.map +1 -0
- package/dist/db/models/type_config.model.d.ts +47 -0
- package/dist/db/models/type_config.model.js +51 -0
- package/dist/db/models/type_config.model.js.map +1 -0
- package/dist/db/tables/action_type_config.table.d.ts +7 -0
- package/dist/db/tables/action_type_config.table.js +35 -0
- package/dist/db/tables/action_type_config.table.js.map +1 -0
- package/dist/db/tables/automation_template.table.d.ts +11 -0
- package/dist/db/tables/automation_template.table.js +52 -0
- package/dist/db/tables/automation_template.table.js.map +1 -0
- package/dist/db/tables/index.d.ts +5 -0
- package/dist/db/tables/index.js +22 -0
- package/dist/db/tables/index.js.map +1 -0
- package/dist/db/tables/procedure.table.d.ts +10 -0
- package/dist/db/tables/procedure.table.js +48 -0
- package/dist/db/tables/procedure.table.js.map +1 -0
- package/dist/db/tables/procedure_run.table.d.ts +13 -0
- package/dist/db/tables/procedure_run.table.js +57 -0
- package/dist/db/tables/procedure_run.table.js.map +1 -0
- package/dist/db/tables/trigger_type_config.table.d.ts +7 -0
- package/dist/db/tables/trigger_type_config.table.js +35 -0
- package/dist/db/tables/trigger_type_config.table.js.map +1 -0
- package/dist/implementations/dms-automation/index.d.ts +19 -0
- package/dist/implementations/dms-automation/index.js +50 -0
- package/dist/implementations/dms-automation/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/builder.d.ts +10 -0
- package/dist/pages/builder.js +28 -0
- package/dist/pages/builder.js.map +1 -0
- package/dist/pages/category.d.ts +1 -0
- package/dist/pages/category.js +10 -0
- package/dist/pages/category.js.map +1 -0
- package/dist/pages/index.d.ts +6 -0
- package/dist/pages/index.js +9 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/library.d.ts +10 -0
- package/dist/pages/library.js +28 -0
- package/dist/pages/library.js.map +1 -0
- package/dist/pages/overview.d.ts +10 -0
- package/dist/pages/overview.js +28 -0
- package/dist/pages/overview.js.map +1 -0
- package/dist/pages/procedures.d.ts +10 -0
- package/dist/pages/procedures.js +72 -0
- package/dist/pages/procedures.js.map +1 -0
- package/dist/pages/runs.d.ts +11 -0
- package/dist/pages/runs.js +61 -0
- package/dist/pages/runs.js.map +1 -0
- package/dist/routes/action-types.d.ts +23 -0
- package/dist/routes/action-types.js +80 -0
- package/dist/routes/action-types.js.map +1 -0
- package/dist/routes/data-node-types.d.ts +6 -0
- package/dist/routes/data-node-types.js +36 -0
- package/dist/routes/data-node-types.js.map +1 -0
- package/dist/routes/graphSchema.d.ts +92 -0
- package/dist/routes/graphSchema.js +60 -0
- package/dist/routes/graphSchema.js.map +1 -0
- package/dist/routes/index.d.ts +8 -0
- package/dist/routes/index.js +11 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/node-kinds.d.ts +9 -0
- package/dist/routes/node-kinds.js +35 -0
- package/dist/routes/node-kinds.js.map +1 -0
- package/dist/routes/procedures.d.ts +23 -0
- package/dist/routes/procedures.js +181 -0
- package/dist/routes/procedures.js.map +1 -0
- package/dist/routes/runs.d.ts +7 -0
- package/dist/routes/runs.js +81 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/stats.d.ts +6 -0
- package/dist/routes/stats.js +35 -0
- package/dist/routes/stats.js.map +1 -0
- package/dist/routes/templates.d.ts +20 -0
- package/dist/routes/templates.js +243 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/trigger-types.d.ts +24 -0
- package/dist/routes/trigger-types.js +80 -0
- package/dist/routes/trigger-types.js.map +1 -0
- package/dist/routes/typeConfigShared.d.ts +22 -0
- package/dist/routes/typeConfigShared.js +37 -0
- package/dist/routes/typeConfigShared.js.map +1 -0
- package/dist/runtime/builtins/actions/http.d.ts +13 -0
- package/dist/runtime/builtins/actions/http.js +47 -0
- package/dist/runtime/builtins/actions/http.js.map +1 -0
- package/dist/runtime/builtins/actions/index.d.ts +2 -0
- package/dist/runtime/builtins/actions/index.js +7 -0
- package/dist/runtime/builtins/actions/index.js.map +1 -0
- package/dist/runtime/builtins/actions/log.d.ts +13 -0
- package/dist/runtime/builtins/actions/log.js +49 -0
- package/dist/runtime/builtins/actions/log.js.map +1 -0
- package/dist/runtime/builtins/data/boolean.d.ts +2 -0
- package/dist/runtime/builtins/data/boolean.js +39 -0
- package/dist/runtime/builtins/data/boolean.js.map +1 -0
- package/dist/runtime/builtins/data/compare.d.ts +2 -0
- package/dist/runtime/builtins/data/compare.js +29 -0
- package/dist/runtime/builtins/data/compare.js.map +1 -0
- package/dist/runtime/builtins/data/constant.d.ts +2 -0
- package/dist/runtime/builtins/data/constant.js +29 -0
- package/dist/runtime/builtins/data/constant.js.map +1 -0
- package/dist/runtime/builtins/data/index.d.ts +2 -0
- package/dist/runtime/builtins/data/index.js +20 -0
- package/dist/runtime/builtins/data/index.js.map +1 -0
- package/dist/runtime/builtins/data/json.d.ts +2 -0
- package/dist/runtime/builtins/data/json.js +46 -0
- package/dist/runtime/builtins/data/json.js.map +1 -0
- package/dist/runtime/builtins/data/math.d.ts +2 -0
- package/dist/runtime/builtins/data/math.js +53 -0
- package/dist/runtime/builtins/data/math.js.map +1 -0
- package/dist/runtime/builtins/data/object.d.ts +2 -0
- package/dist/runtime/builtins/data/object.js +121 -0
- package/dist/runtime/builtins/data/object.js.map +1 -0
- package/dist/runtime/builtins/data/string.d.ts +2 -0
- package/dist/runtime/builtins/data/string.js +143 -0
- package/dist/runtime/builtins/data/string.js.map +1 -0
- package/dist/runtime/builtins/index.d.ts +17 -0
- package/dist/runtime/builtins/index.js +24 -0
- package/dist/runtime/builtins/index.js.map +1 -0
- package/dist/runtime/builtins/nodes/action.d.ts +1 -0
- package/dist/runtime/builtins/nodes/action.js +36 -0
- package/dist/runtime/builtins/nodes/action.js.map +1 -0
- package/dist/runtime/builtins/nodes/data.d.ts +1 -0
- package/dist/runtime/builtins/nodes/data.js +28 -0
- package/dist/runtime/builtins/nodes/data.js.map +1 -0
- package/dist/runtime/builtins/nodes/delay.d.ts +1 -0
- package/dist/runtime/builtins/nodes/delay.js +41 -0
- package/dist/runtime/builtins/nodes/delay.js.map +1 -0
- package/dist/runtime/builtins/nodes/forRange.d.ts +1 -0
- package/dist/runtime/builtins/nodes/forRange.js +52 -0
- package/dist/runtime/builtins/nodes/forRange.js.map +1 -0
- package/dist/runtime/builtins/nodes/foreach.d.ts +1 -0
- package/dist/runtime/builtins/nodes/foreach.js +31 -0
- package/dist/runtime/builtins/nodes/foreach.js.map +1 -0
- package/dist/runtime/builtins/nodes/groupInput.d.ts +1 -0
- package/dist/runtime/builtins/nodes/groupInput.js +11 -0
- package/dist/runtime/builtins/nodes/groupInput.js.map +1 -0
- package/dist/runtime/builtins/nodes/groupOutput.d.ts +1 -0
- package/dist/runtime/builtins/nodes/groupOutput.js +11 -0
- package/dist/runtime/builtins/nodes/groupOutput.js.map +1 -0
- package/dist/runtime/builtins/nodes/if.d.ts +1 -0
- package/dist/runtime/builtins/nodes/if.js +23 -0
- package/dist/runtime/builtins/nodes/if.js.map +1 -0
- package/dist/runtime/builtins/nodes/parallel.d.ts +1 -0
- package/dist/runtime/builtins/nodes/parallel.js +34 -0
- package/dist/runtime/builtins/nodes/parallel.js.map +1 -0
- package/dist/runtime/builtins/nodes/retry.d.ts +1 -0
- package/dist/runtime/builtins/nodes/retry.js +46 -0
- package/dist/runtime/builtins/nodes/retry.js.map +1 -0
- package/dist/runtime/builtins/nodes/setVariable.d.ts +1 -0
- package/dist/runtime/builtins/nodes/setVariable.js +31 -0
- package/dist/runtime/builtins/nodes/setVariable.js.map +1 -0
- package/dist/runtime/builtins/nodes/switch.d.ts +1 -0
- package/dist/runtime/builtins/nodes/switch.js +33 -0
- package/dist/runtime/builtins/nodes/switch.js.map +1 -0
- package/dist/runtime/builtins/nodes/trigger.d.ts +1 -0
- package/dist/runtime/builtins/nodes/trigger.js +25 -0
- package/dist/runtime/builtins/nodes/trigger.js.map +1 -0
- package/dist/runtime/builtins/nodes/tryCatch.d.ts +1 -0
- package/dist/runtime/builtins/nodes/tryCatch.js +35 -0
- package/dist/runtime/builtins/nodes/tryCatch.js.map +1 -0
- package/dist/runtime/builtins/triggers/cron.d.ts +9 -0
- package/dist/runtime/builtins/triggers/cron.js +52 -0
- package/dist/runtime/builtins/triggers/cron.js.map +1 -0
- package/dist/runtime/builtins/triggers/index.d.ts +2 -0
- package/dist/runtime/builtins/triggers/index.js +12 -0
- package/dist/runtime/builtins/triggers/index.js.map +1 -0
- package/dist/runtime/builtins/triggers/manual.d.ts +3 -0
- package/dist/runtime/builtins/triggers/manual.js +20 -0
- package/dist/runtime/builtins/triggers/manual.js.map +1 -0
- package/dist/runtime/builtins/triggers/webhook.d.ts +14 -0
- package/dist/runtime/builtins/triggers/webhook.js +90 -0
- package/dist/runtime/builtins/triggers/webhook.js.map +1 -0
- package/dist/runtime/executionResult.d.ts +6 -0
- package/dist/runtime/executionResult.js +3 -0
- package/dist/runtime/executionResult.js.map +1 -0
- package/dist/runtime/executor.d.ts +9 -0
- package/dist/runtime/executor.js +300 -0
- package/dist/runtime/executor.js.map +1 -0
- package/dist/runtime/flatten.d.ts +3 -0
- package/dist/runtime/flatten.js +188 -0
- package/dist/runtime/flatten.js.map +1 -0
- package/dist/runtime/nodeKinds.d.ts +64 -0
- package/dist/runtime/nodeKinds.js +43 -0
- package/dist/runtime/nodeKinds.js.map +1 -0
- package/dist/runtime/reconcileDiff.d.ts +9 -0
- package/dist/runtime/reconcileDiff.js +26 -0
- package/dist/runtime/reconcileDiff.js.map +1 -0
- package/dist/runtime/registry.d.ts +19 -0
- package/dist/runtime/registry.js +47 -0
- package/dist/runtime/registry.js.map +1 -0
- package/dist/runtime/resolveInputs.d.ts +14 -0
- package/dist/runtime/resolveInputs.js +64 -0
- package/dist/runtime/resolveInputs.js.map +1 -0
- package/dist/runtime/resolveSubgraph.d.ts +7 -0
- package/dist/runtime/resolveSubgraph.js +15 -0
- package/dist/runtime/resolveSubgraph.js.map +1 -0
- package/dist/runtime/subscriptions.d.ts +16 -0
- package/dist/runtime/subscriptions.js +419 -0
- package/dist/runtime/subscriptions.js.map +1 -0
- package/dist/runtime/subwalk.d.ts +17 -0
- package/dist/runtime/subwalk.js +74 -0
- package/dist/runtime/subwalk.js.map +1 -0
- package/dist/runtime/topo.d.ts +3 -0
- package/dist/runtime/topo.js +75 -0
- package/dist/runtime/topo.js.map +1 -0
- package/dist/runtime/typeOrigin.d.ts +7 -0
- package/dist/runtime/typeOrigin.js +24 -0
- package/dist/runtime/typeOrigin.js.map +1 -0
- package/dist/runtime/validate.d.ts +10 -0
- package/dist/runtime/validate.js +185 -0
- package/dist/runtime/validate.js.map +1 -0
- package/dist/runtime/validateTemplate.d.ts +3 -0
- package/dist/runtime/validateTemplate.js +54 -0
- package/dist/runtime/validateTemplate.js.map +1 -0
- package/dist/types/constants.d.ts +6 -0
- package/dist/types/constants.js +10 -0
- package/dist/types/constants.js.map +1 -0
- package/dist/types/graph.d.ts +51 -0
- package/dist/types/graph.js +23 -0
- package/dist/types/graph.js.map +1 -0
- package/dist/types/pagination.d.ts +8 -0
- package/dist/types/pagination.js +3 -0
- package/dist/types/pagination.js.map +1 -0
- package/dist/types/runLog.d.ts +26 -0
- package/dist/types/runLog.js +3 -0
- package/dist/types/runLog.js.map +1 -0
- package/frontend-vue/.prettierignore +4 -0
- package/frontend-vue/app/components/Actions.vue +141 -0
- package/frontend-vue/app/components/Catalog/TypeConfigForm.vue +166 -0
- package/frontend-vue/app/components/Catalog/TypeList.vue +198 -0
- package/frontend-vue/app/components/DataNodes.vue +259 -0
- package/frontend-vue/app/components/Editor/BuilderPalette.vue +234 -0
- package/frontend-vue/app/components/Editor/GraphCanvas.vue +994 -0
- package/frontend-vue/app/components/Editor/GroupPortEditor.vue +289 -0
- package/frontend-vue/app/components/Editor/JsonField.vue +63 -0
- package/frontend-vue/app/components/Editor/NodeInspector.vue +356 -0
- package/frontend-vue/app/components/Editor/ProcedureList.vue +156 -0
- package/frontend-vue/app/components/Editor/RunsDrawer.vue +312 -0
- package/frontend-vue/app/components/Editor/WiredField.vue +42 -0
- package/frontend-vue/app/components/Editor/nodes/GenericNode.vue +161 -0
- package/frontend-vue/app/components/Editor/nodes/GroupInputNode.vue +19 -0
- package/frontend-vue/app/components/Editor/nodes/GroupNode.vue +200 -0
- package/frontend-vue/app/components/Editor/nodes/GroupOutputNode.vue +19 -0
- package/frontend-vue/app/components/Editor/nodes/GroupSentinel.vue +161 -0
- package/frontend-vue/app/components/Editor/nodes/styles.ts +53 -0
- package/frontend-vue/app/components/Editor.vue +2504 -0
- package/frontend-vue/app/components/Library.vue +67 -0
- package/frontend-vue/app/components/Overview.vue +503 -0
- package/frontend-vue/app/components/RunDetailDrawer.vue +165 -0
- package/frontend-vue/app/components/RunFireTree.vue +170 -0
- package/frontend-vue/app/components/RunTraceDrawer.vue +59 -0
- package/frontend-vue/app/components/RunsStats.vue +126 -0
- package/frontend-vue/app/components/RunsTimelineDisplay.vue +287 -0
- package/frontend-vue/app/components/Templates.vue +663 -0
- package/frontend-vue/app/components/Triggers.vue +137 -0
- package/frontend-vue/app/composables/useAutomationNodeKinds.ts +48 -0
- package/frontend-vue/app/composables/useAutomationRuns.ts +75 -0
- package/frontend-vue/app/composables/useGraphConnect.ts +542 -0
- package/frontend-vue/app/plugins/runs-timeline-display.client.ts +16 -0
- package/frontend-vue/app/utils/automation.ts +51 -0
- package/frontend-vue/dms.frontend.ts +26 -0
- package/frontend-vue/eslint.config.mjs +24 -0
- package/frontend-vue/i18n/locales/dms-automation-en-GB.json +369 -0
- package/frontend-vue/i18n/locales/dms-automation-fr-FR.json +369 -0
- package/frontend-vue/package.json +33 -0
- package/frontend-vue/pnpm-lock.yaml +6483 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- 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>
|