@k-4u/resource-mapper-core 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/coverage/junit.xml +17 -17
  2. package/coverage/test-results.json +1 -1
  3. package/data/services/api/api-gateway.yaml +18 -18
  4. package/data/services/api/group-info.yaml +7 -7
  5. package/data/services/api/lambda-orders.yaml +21 -21
  6. package/data/services/api/lambda-products.yaml +15 -15
  7. package/data/services/api/lambda-users.yaml +15 -15
  8. package/data/services/compute/alb.yaml +15 -15
  9. package/data/services/compute/ecs-inventory.yaml +16 -16
  10. package/data/services/compute/ecs-notification.yaml +15 -15
  11. package/data/services/compute/group-info.yaml +7 -7
  12. package/data/services/data/dynamodb-notifications.yaml +12 -12
  13. package/data/services/data/dynamodb-orders.yaml +9 -9
  14. package/data/services/data/dynamodb-products.yaml +9 -9
  15. package/data/services/data/dynamodb-users.yaml +9 -9
  16. package/data/services/data/group-info.yaml +7 -7
  17. package/data/services/data/rds-postgres.yaml +9 -9
  18. package/data/services/data/redis.yaml +10 -10
  19. package/data/services/frontend/cloudfront.yaml +12 -12
  20. package/data/services/frontend/group-info.yaml +7 -7
  21. package/data/services/frontend/route53.yaml +15 -15
  22. package/data/services/frontend/s3-website.yaml +9 -9
  23. package/data/teams/cloud-shepherds.yaml +15 -15
  24. package/data/teams/data-wizards.yaml +15 -15
  25. package/data/teams/interface-architects.yaml +18 -18
  26. package/e2e/demo.test.ts +54 -54
  27. package/e2e/header-toolbar.spec.ts +53 -53
  28. package/e2e/layout.spec.ts +30 -30
  29. package/package.json +74 -69
  30. package/playwright.config.ts +10 -10
  31. package/plugins/mapper-data-plugin.ts +32 -32
  32. package/project.json +22 -22
  33. package/src/app.css +125 -125
  34. package/src/app.d.ts +31 -31
  35. package/src/app.html +11 -11
  36. package/src/lib/assets/favicon.svg +18 -18
  37. package/src/lib/components/EmptyState.svelte +37 -37
  38. package/src/lib/components/ErrorDisplay.svelte +82 -82
  39. package/src/lib/components/FlowCanvas.svelte +223 -223
  40. package/src/lib/components/GenericSidebarCard.svelte +43 -43
  41. package/src/lib/components/GroupDetailSidebar.svelte +31 -31
  42. package/src/lib/components/Header.svelte +57 -57
  43. package/src/lib/components/Legend.svelte +25 -25
  44. package/src/lib/components/LoadingOverlay.svelte +42 -42
  45. package/src/lib/components/LoadingSpinner.svelte +10 -10
  46. package/src/lib/components/ServiceDetailSidebar.svelte +89 -89
  47. package/src/lib/components/TeamContactCard.svelte +166 -166
  48. package/src/lib/components/flow/ExternalNode.svelte +45 -45
  49. package/src/lib/components/flow/MainGroupNode.svelte +24 -24
  50. package/src/lib/components/flow/ServiceGroupNode.svelte +17 -17
  51. package/src/lib/components/flow/ServiceNode.svelte +40 -40
  52. package/src/lib/components/flow/SnakeEdge.svelte +206 -206
  53. package/src/lib/components/flow/index.ts +5 -5
  54. package/src/lib/components/index.ts +12 -12
  55. package/src/lib/data/connections.ts +26 -26
  56. package/src/lib/data/groups.ts +11 -11
  57. package/src/lib/data/services.ts +12 -12
  58. package/src/lib/data/teams.ts +11 -11
  59. package/src/lib/index.ts +1 -1
  60. package/src/lib/state/theme.svelte.ts +21 -21
  61. package/src/lib/stores/diagram.ts +6 -6
  62. package/src/lib/stores/routingStore.test.ts +133 -133
  63. package/src/lib/stores/routingStore.ts +232 -232
  64. package/src/lib/utils/awsIcons.ts +117 -117
  65. package/src/lib/utils/flow/groupOverviewGraph.ts +73 -73
  66. package/src/lib/utils/flow/helpers.ts +14 -14
  67. package/src/lib/utils/flow/layout.test.ts +271 -271
  68. package/src/lib/utils/flow/layout.ts +240 -240
  69. package/src/lib/utils/flow/serviceIds.ts +4 -4
  70. package/src/lib/utils/flow/servicesGraph.test.ts +119 -119
  71. package/src/lib/utils/flow/servicesGraph.ts +258 -258
  72. package/src/routes/+error.svelte +36 -36
  73. package/src/routes/+layout.svelte +27 -27
  74. package/src/routes/+page.svelte +81 -81
  75. package/src/routes/+page.ts +31 -31
  76. package/src/routes/group/[groupId]/+page.svelte +102 -102
  77. package/src/routes/group/[groupId]/+page.ts +48 -48
  78. package/static/static/robots.txt +3 -3
  79. package/svelte.config.js +27 -27
  80. package/tailwind.config.js +12 -12
  81. package/tsconfig.json +22 -22
  82. package/vite.config.ts +80 -80
  83. package/.aws-icons-last-updated +0 -1
  84. package/.svelte-kit/ambient.d.ts +0 -263
  85. package/.svelte-kit/generated/client/app.js +0 -31
  86. package/.svelte-kit/generated/client/matchers.js +0 -1
  87. package/.svelte-kit/generated/client/nodes/0.js +0 -1
  88. package/.svelte-kit/generated/client/nodes/1.js +0 -1
  89. package/.svelte-kit/generated/client/nodes/2.js +0 -3
  90. package/.svelte-kit/generated/client/nodes/3.js +0 -3
  91. package/.svelte-kit/generated/client-optimized/app.js +0 -31
  92. package/.svelte-kit/generated/client-optimized/matchers.js +0 -1
  93. package/.svelte-kit/generated/client-optimized/nodes/0.js +0 -1
  94. package/.svelte-kit/generated/client-optimized/nodes/1.js +0 -1
  95. package/.svelte-kit/generated/client-optimized/nodes/2.js +0 -3
  96. package/.svelte-kit/generated/client-optimized/nodes/3.js +0 -3
  97. package/.svelte-kit/generated/root.js +0 -3
  98. package/.svelte-kit/generated/root.svelte +0 -68
  99. package/.svelte-kit/generated/server/internal.js +0 -53
  100. package/.svelte-kit/non-ambient.d.ts +0 -43
  101. package/.svelte-kit/output/client/.vite/manifest.json +0 -175
  102. package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +0 -1
  103. package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
  104. package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
  105. package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  106. package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +0 -46
  107. package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +0 -1
  108. package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +0 -1
  109. package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +0 -2
  110. package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +0 -1
  111. package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +0 -2
  112. package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +0 -1
  113. package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +0 -1
  114. package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +0 -2
  115. package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +0 -1
  116. package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
  117. package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
  118. package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +0 -1
  119. package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +0 -1
  120. package/.svelte-kit/output/client/_app/version.json +0 -1
  121. package/.svelte-kit/output/client/libavoid.wasm +0 -0
  122. package/.svelte-kit/output/client/static/robots.txt +0 -3
  123. package/.svelte-kit/output/prerendered/dependencies/_app/env.js +0 -1
  124. package/.svelte-kit/output/server/.vite/manifest.json +0 -224
  125. package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +0 -1
  126. package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +0 -1
  127. package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +0 -1
  128. package/.svelte-kit/output/server/chunks/ErrorDisplay.js +0 -59
  129. package/.svelte-kit/output/server/chunks/LoadingOverlay.js +0 -12
  130. package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +0 -1671
  131. package/.svelte-kit/output/server/chunks/connections.js +0 -33
  132. package/.svelte-kit/output/server/chunks/diagram.js +0 -7
  133. package/.svelte-kit/output/server/chunks/environment.js +0 -34
  134. package/.svelte-kit/output/server/chunks/equality.js +0 -57
  135. package/.svelte-kit/output/server/chunks/exports.js +0 -174
  136. package/.svelte-kit/output/server/chunks/false.js +0 -4
  137. package/.svelte-kit/output/server/chunks/index.js +0 -59
  138. package/.svelte-kit/output/server/chunks/index2.js +0 -2939
  139. package/.svelte-kit/output/server/chunks/index3.js +0 -20
  140. package/.svelte-kit/output/server/chunks/internal.js +0 -1017
  141. package/.svelte-kit/output/server/chunks/shared.js +0 -770
  142. package/.svelte-kit/output/server/chunks/utils.js +0 -43
  143. package/.svelte-kit/output/server/entries/pages/_error.svelte.js +0 -64
  144. package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +0 -65
  145. package/.svelte-kit/output/server/entries/pages/_page.svelte.js +0 -3991
  146. package/.svelte-kit/output/server/entries/pages/_page.ts.js +0 -30
  147. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +0 -67
  148. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +0 -47
  149. package/.svelte-kit/output/server/index.js +0 -3747
  150. package/.svelte-kit/output/server/internal.js +0 -13
  151. package/.svelte-kit/output/server/manifest-full.js +0 -47
  152. package/.svelte-kit/output/server/manifest.js +0 -47
  153. package/.svelte-kit/output/server/nodes/0.js +0 -8
  154. package/.svelte-kit/output/server/nodes/1.js +0 -8
  155. package/.svelte-kit/output/server/nodes/2.js +0 -10
  156. package/.svelte-kit/output/server/nodes/3.js +0 -10
  157. package/.svelte-kit/output/server/remote-entry.js +0 -557
  158. package/.svelte-kit/tsconfig.json +0 -61
  159. package/.svelte-kit/types/route_meta_data.json +0 -8
  160. package/.svelte-kit/types/src/routes/$types.d.ts +0 -26
  161. package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +0 -21
  162. package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +0 -49
  163. package/.svelte-kit/types/src/routes/proxy+page.ts +0 -33
  164. package/build/_app/env.js +0 -1
  165. package/build/_app/env.js.br +0 -1
  166. package/build/_app/env.js.gz +0 -0
  167. package/build/_app/immutable/assets/0.Czt_67iE.css +0 -1
  168. package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
  169. package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
  170. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
  171. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
  172. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
  173. package/build/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
  174. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
  175. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
  176. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  177. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
  178. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
  179. package/build/_app/immutable/chunks/BlLuv0eP.js +0 -46
  180. package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
  181. package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
  182. package/build/_app/immutable/chunks/CSBHmwYv.js +0 -1
  183. package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
  184. package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
  185. package/build/_app/immutable/chunks/CTCi5ueQ.js +0 -1
  186. package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
  187. package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
  188. package/build/_app/immutable/chunks/CfOzjaik.js +0 -2
  189. package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
  190. package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
  191. package/build/_app/immutable/chunks/D4PdvFNs.js +0 -1
  192. package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
  193. package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
  194. package/build/_app/immutable/chunks/DXgP-QUS.js +0 -2
  195. package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
  196. package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
  197. package/build/_app/immutable/chunks/DlbDC5An.js +0 -1
  198. package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
  199. package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
  200. package/build/_app/immutable/chunks/wRWe7aK9.js +0 -1
  201. package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
  202. package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
  203. package/build/_app/immutable/entry/app.ConrMuHl.js +0 -2
  204. package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
  205. package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
  206. package/build/_app/immutable/entry/start.Bm6FyGme.js +0 -1
  207. package/build/_app/immutable/entry/start.Bm6FyGme.js.br +0 -2
  208. package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
  209. package/build/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
  210. package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
  211. package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
  212. package/build/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
  213. package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
  214. package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
  215. package/build/_app/immutable/nodes/2.CLD-8chl.js +0 -1
  216. package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
  217. package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
  218. package/build/_app/immutable/nodes/3.DXYeBoel.js +0 -1
  219. package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
  220. package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
  221. package/build/_app/version.json +0 -1
  222. package/build/_app/version.json.br +0 -0
  223. package/build/_app/version.json.gz +0 -0
  224. package/build/index.html +0 -34
  225. package/build/index.html.br +0 -0
  226. package/build/index.html.gz +0 -0
  227. package/build/libavoid.wasm +0 -0
  228. package/build/libavoid.wasm.br +0 -0
  229. package/build/libavoid.wasm.gz +0 -0
  230. package/build/static/robots.txt +0 -3
  231. package/coverage/coverage-final.json +0 -6
  232. package/coverage/coverage-summary.json +0 -7
  233. package/coverage/lcov-report/base.css +0 -224
  234. package/coverage/lcov-report/block-navigation.js +0 -87
  235. package/coverage/lcov-report/favicon.png +0 -0
  236. package/coverage/lcov-report/index.html +0 -131
  237. package/coverage/lcov-report/prettify.css +0 -1
  238. package/coverage/lcov-report/prettify.js +0 -2
  239. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  240. package/coverage/lcov-report/sorter.js +0 -210
  241. package/coverage/lcov-report/stores/index.html +0 -116
  242. package/coverage/lcov-report/stores/routingStore.ts.html +0 -781
  243. package/coverage/lcov-report/utils/flow/helpers.ts.html +0 -127
  244. package/coverage/lcov-report/utils/flow/index.html +0 -161
  245. package/coverage/lcov-report/utils/flow/layout.ts.html +0 -805
  246. package/coverage/lcov-report/utils/flow/serviceIds.ts.html +0 -97
  247. package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +0 -859
  248. package/coverage/lcov.info +0 -646
@@ -1,258 +1,258 @@
1
- import {
2
- type ConnectionType,
3
- type ExternalConnectionDirection,
4
- type ExternalGroupServices,
5
- type GroupConnection,
6
- type GroupInfo,
7
- type ServiceDefinition,
8
- type ServiceIncomingLink,
9
- type ServiceLink
10
- } from '$shared/types'
11
- import type {FlowEdgeData, FlowNodeData, ServicesGraphResult,} from '$shared/flow-types'
12
- import {createGraphSignature} from '$lib/utils/flow/helpers'
13
- import {getServiceNodeIdFromDefinition} from '$lib/utils/flow/serviceIds'
14
- import type {Edge, Node} from "@xyflow/svelte";
15
-
16
- const position = {x: 0, y: 0};
17
-
18
- function createServiceNode(nodeId: string, service: ServiceDefinition): Node<FlowNodeData> {
19
- return {
20
- id: nodeId,
21
- type: 'service',
22
- width: 220,
23
- height: 120,
24
- data: {
25
- label: service.friendlyName,
26
- subLabel: service.description ?? undefined,
27
- groupId: service.groupId,
28
- serviceId: service.identifier,
29
- serviceType: service.serviceType,
30
- kind: 'service'
31
- },
32
- parentId: `group_${service.groupId}`,
33
- position
34
- };
35
- }
36
-
37
-
38
- export function buildGroupServicesGraph(
39
- currentGroup: GroupInfo,
40
- currentServices: ServiceDefinition[],
41
- allGroups: Map<string, GroupInfo>,
42
- groupConnections: GroupConnection[],
43
- externalGroups: ExternalGroupServices[] = []
44
- ): ServicesGraphResult {
45
- const nodes: Node<FlowNodeData>[] = []
46
- const edges: Edge<FlowEdgeData>[] = []
47
- const serviceNodeLookup = new Map<string, string>()
48
- const externalNodeMap = new Map<string, Node<FlowNodeData>>()
49
- const serviceNodeRecord: Record<string, ServiceDefinition> = {}
50
- const externalNodeRecord: Record<string, { service: ServiceDefinition; group: GroupInfo }> = {}
51
-
52
- if (!currentServices.length) {
53
- nodes.push({
54
- id: `placeholder_${currentGroup.groupName}`,
55
- data: {
56
- label: 'No services defined',
57
- subLabel: 'Add services to see relationships',
58
- kind: 'service',
59
- groupId: currentGroup.groupName,
60
- isPlaceholder: true
61
- },
62
- position
63
- })
64
- return {
65
- graph: {
66
- groupNodes: [],
67
- serviceNodes: nodes,
68
- edges,
69
- signature: createGraphSignature('group-services', {group: currentGroup.groupName, placeholder: true})
70
- },
71
- serviceNodes: serviceNodeRecord,
72
- externalNodes: externalNodeRecord
73
- }
74
- }
75
-
76
- // 2. Add Connections
77
- const externalLookup = createDirectionLookup(externalGroups)
78
-
79
- const addConnections = (
80
- serviceNodeId: string,
81
- connections: (ServiceLink | ServiceIncomingLink)[] | undefined
82
- ) => {
83
- connections?.forEach(conn => {
84
- const isOutgoing = 'targetIdentifier' in conn
85
- const remoteIdentifier = isOutgoing ? conn.targetIdentifier : conn.sourceIdentifier
86
- if (remoteIdentifier.groupId === currentGroup.groupName) {
87
- const remoteNodeId = remoteIdentifier.serviceId ? serviceNodeLookup.get(remoteIdentifier.serviceId) : undefined
88
- if (remoteNodeId) {
89
- const sourceId = isOutgoing ? serviceNodeId : remoteNodeId
90
- const targetId = isOutgoing ? remoteNodeId : serviceNodeId
91
- edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'internal'))
92
- }
93
- } else if (remoteIdentifier.groupId && remoteIdentifier.serviceId) {
94
- const extNodeId = ensureExternalNode(remoteIdentifier.groupId, remoteIdentifier.serviceId, externalLookup, externalNodeMap, externalNodeRecord)
95
- if (extNodeId) {
96
- const sourceId = isOutgoing ? serviceNodeId : extNodeId
97
- const targetId = isOutgoing ? extNodeId : serviceNodeId
98
- edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'external'))
99
- }
100
- }
101
- })
102
- }
103
-
104
- currentServices.forEach(service => {
105
- const nodeId = getServiceNodeIdFromDefinition(service)
106
- serviceNodeLookup.set(service.identifier, nodeId)
107
- serviceNodeRecord[nodeId] = service
108
- nodes.push(createServiceNode(nodeId, service))
109
- })
110
-
111
- currentServices.forEach(service => {
112
- const currentServiceNodeId = serviceNodeLookup.get(service.identifier)
113
- if (!currentServiceNodeId) return
114
-
115
- addConnections(currentServiceNodeId, service.outgoingConnections)
116
- addConnections(currentServiceNodeId, service.incomingConnections)
117
- })
118
-
119
- // 4. Create Group Container Nodes
120
- const groupNodes: Node<FlowNodeData>[] = Array.from(allGroups.values()).map(g => ({
121
- id: `group_${g.groupName}`,
122
- type: 'serviceGroup',
123
- data: {
124
- label: g.name,
125
- subLabel: g.description ?? undefined,
126
- groupId: g.groupName,
127
- kind: 'group'
128
- },
129
- position,
130
- class: g.groupName === currentGroup.groupName ? 'current-group' : 'external-group'
131
- }))
132
-
133
- // 5. Add Group-to-Group Edges
134
- groupConnections.forEach(conn => {
135
- edges.push(buildEdge(`group_${conn.sourceGroup}`, `group_${conn.targetGroup}`, 'service-group'))
136
- })
137
-
138
- const signature = createGraphSignature('group-services', {
139
- group: currentGroup.groupName,
140
- services: currentServices.map(service => ({identifier: service.identifier, type: service.serviceType})).sort((a, b) =>
141
- a.identifier.localeCompare(b.identifier)
142
- ),
143
- external: externalGroups.map(entry => ({
144
- direction: entry.direction,
145
- group: entry.group.groupName,
146
- services: entry.services.map(service => service.identifier).sort()
147
- }))
148
- })
149
-
150
- return {
151
- graph: {
152
- groupNodes,
153
- serviceNodes: nodes.concat(Array.from(externalNodeMap.values())),
154
- edges,
155
- signature
156
- },
157
- serviceNodes: serviceNodeRecord,
158
- externalNodes: externalNodeRecord
159
- }
160
- }
161
-
162
- function createDirectionLookup(
163
- entries: ExternalGroupServices[]
164
- ): Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }> {
165
- return entries.reduce((lookup, entry) => {
166
- const key = entry.group?.groupName
167
- if (!key) return lookup
168
- const existing = lookup.get(key) || {}
169
- if (entry.direction === 'incoming') {
170
- existing.incoming = entry
171
- } else if (entry.direction === 'outgoing') {
172
- existing.outgoing = entry
173
- }
174
- lookup.set(key, existing)
175
- return lookup
176
- }, new Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>())
177
- }
178
-
179
- // Registers an external node in the nodeMap and externalNodeRecord.
180
- // It uses a consistent svc::<groupId>::<serviceId> naming scheme.
181
- function ensureExternalNode(
182
- groupId: string,
183
- serviceId: string,
184
- lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>,
185
- nodeMap: Map<string, Node<FlowNodeData>>,
186
- record: Record<string, { service: ServiceDefinition; group: GroupInfo }>
187
- ): string | null {
188
- const key = `svc::${groupId}::${serviceId}`
189
- if (nodeMap.has(key)) {
190
- return key
191
- }
192
-
193
- // Try to find the service in either direction
194
- const externalMeta = findExternalService('outgoing', groupId, serviceId, lookup) ||
195
- findExternalService('incoming', groupId, serviceId, lookup)
196
-
197
- if (!externalMeta) {
198
- return null
199
- }
200
-
201
- nodeMap.set(key, {
202
- id: key,
203
- width: 220,
204
- height: 70,
205
- type: 'external',
206
- data: {
207
- label: externalMeta.service.friendlyName,
208
- serviceType: externalMeta.service.serviceType,
209
- subLabel: externalMeta.group.name,
210
- groupId: groupId,
211
- serviceId: externalMeta.service.identifier,
212
- kind: 'external',
213
- },
214
- extent: "parent",
215
- position: {x: 0, y: 0},
216
- parentId: `group_${groupId}`,
217
- })
218
- record[key] = {service: externalMeta.service, group: externalMeta.group}
219
-
220
- return key
221
- }
222
-
223
- function findExternalService(
224
- direction: ExternalConnectionDirection,
225
- groupId: string,
226
- serviceId: string,
227
- lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>
228
- ) {
229
- const entries = lookup.get(groupId)
230
- if (!entries) return null
231
- const dirEntry = entries[direction]
232
- if (!dirEntry) return null
233
- const service = dirEntry.services.find(item => item.identifier === serviceId)
234
- if (!service) return null
235
- return { group: dirEntry.group, service }
236
- }
237
-
238
- function buildEdge(
239
- sourceId: string,
240
- targetId: string,
241
- connectionType?: ConnectionType | 'service-group',
242
- edgeType: 'external' | 'internal' = 'internal' //TODO: Name me better
243
- ): Edge<FlowEdgeData> {
244
- console.debug(`Creating edge from ${sourceId} to ${targetId} of type ${connectionType} (${edgeType})`)
245
- return {
246
- id: `edge_${sourceId}_${targetId}`,
247
- source: sourceId,
248
- sourceHandle: 'output',
249
- target: targetId,
250
- targetHandle: 'input',
251
- type: 'snake',
252
- // type: 'smoothstep',
253
- markerEnd: 'arrow',
254
- class: `edge-${edgeType}`,
255
- label: connectionType || "",
256
- data: {label: connectionType, connectionType}
257
- }
258
- }
1
+ import {
2
+ type ConnectionType,
3
+ type ExternalConnectionDirection,
4
+ type ExternalGroupServices,
5
+ type GroupConnection,
6
+ type GroupInfo,
7
+ type ServiceDefinition,
8
+ type ServiceIncomingLink,
9
+ type ServiceLink
10
+ } from '$shared/types'
11
+ import type {FlowEdgeData, FlowNodeData, ServicesGraphResult,} from '$shared/flow-types'
12
+ import {createGraphSignature} from '$lib/utils/flow/helpers'
13
+ import {getServiceNodeIdFromDefinition} from '$lib/utils/flow/serviceIds'
14
+ import type {Edge, Node} from "@xyflow/svelte";
15
+
16
+ const position = {x: 0, y: 0};
17
+
18
+ function createServiceNode(nodeId: string, service: ServiceDefinition): Node<FlowNodeData> {
19
+ return {
20
+ id: nodeId,
21
+ type: 'service',
22
+ width: 220,
23
+ height: 120,
24
+ data: {
25
+ label: service.friendlyName,
26
+ subLabel: service.description ?? undefined,
27
+ groupId: service.groupId,
28
+ serviceId: service.identifier,
29
+ serviceType: service.serviceType,
30
+ kind: 'service'
31
+ },
32
+ parentId: `group_${service.groupId}`,
33
+ position
34
+ };
35
+ }
36
+
37
+
38
+ export function buildGroupServicesGraph(
39
+ currentGroup: GroupInfo,
40
+ currentServices: ServiceDefinition[],
41
+ allGroups: Map<string, GroupInfo>,
42
+ groupConnections: GroupConnection[],
43
+ externalGroups: ExternalGroupServices[] = []
44
+ ): ServicesGraphResult {
45
+ const nodes: Node<FlowNodeData>[] = []
46
+ const edges: Edge<FlowEdgeData>[] = []
47
+ const serviceNodeLookup = new Map<string, string>()
48
+ const externalNodeMap = new Map<string, Node<FlowNodeData>>()
49
+ const serviceNodeRecord: Record<string, ServiceDefinition> = {}
50
+ const externalNodeRecord: Record<string, { service: ServiceDefinition; group: GroupInfo }> = {}
51
+
52
+ if (!currentServices.length) {
53
+ nodes.push({
54
+ id: `placeholder_${currentGroup.groupName}`,
55
+ data: {
56
+ label: 'No services defined',
57
+ subLabel: 'Add services to see relationships',
58
+ kind: 'service',
59
+ groupId: currentGroup.groupName,
60
+ isPlaceholder: true
61
+ },
62
+ position
63
+ })
64
+ return {
65
+ graph: {
66
+ groupNodes: [],
67
+ serviceNodes: nodes,
68
+ edges,
69
+ signature: createGraphSignature('group-services', {group: currentGroup.groupName, placeholder: true})
70
+ },
71
+ serviceNodes: serviceNodeRecord,
72
+ externalNodes: externalNodeRecord
73
+ }
74
+ }
75
+
76
+ // 2. Add Connections
77
+ const externalLookup = createDirectionLookup(externalGroups)
78
+
79
+ const addConnections = (
80
+ serviceNodeId: string,
81
+ connections: (ServiceLink | ServiceIncomingLink)[] | undefined
82
+ ) => {
83
+ connections?.forEach(conn => {
84
+ const isOutgoing = 'targetIdentifier' in conn
85
+ const remoteIdentifier = isOutgoing ? conn.targetIdentifier : conn.sourceIdentifier
86
+ if (remoteIdentifier.groupId === currentGroup.groupName) {
87
+ const remoteNodeId = remoteIdentifier.serviceId ? serviceNodeLookup.get(remoteIdentifier.serviceId) : undefined
88
+ if (remoteNodeId) {
89
+ const sourceId = isOutgoing ? serviceNodeId : remoteNodeId
90
+ const targetId = isOutgoing ? remoteNodeId : serviceNodeId
91
+ edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'internal'))
92
+ }
93
+ } else if (remoteIdentifier.groupId && remoteIdentifier.serviceId) {
94
+ const extNodeId = ensureExternalNode(remoteIdentifier.groupId, remoteIdentifier.serviceId, externalLookup, externalNodeMap, externalNodeRecord)
95
+ if (extNodeId) {
96
+ const sourceId = isOutgoing ? serviceNodeId : extNodeId
97
+ const targetId = isOutgoing ? extNodeId : serviceNodeId
98
+ edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'external'))
99
+ }
100
+ }
101
+ })
102
+ }
103
+
104
+ currentServices.forEach(service => {
105
+ const nodeId = getServiceNodeIdFromDefinition(service)
106
+ serviceNodeLookup.set(service.identifier, nodeId)
107
+ serviceNodeRecord[nodeId] = service
108
+ nodes.push(createServiceNode(nodeId, service))
109
+ })
110
+
111
+ currentServices.forEach(service => {
112
+ const currentServiceNodeId = serviceNodeLookup.get(service.identifier)
113
+ if (!currentServiceNodeId) return
114
+
115
+ addConnections(currentServiceNodeId, service.outgoingConnections)
116
+ addConnections(currentServiceNodeId, service.incomingConnections)
117
+ })
118
+
119
+ // 4. Create Group Container Nodes
120
+ const groupNodes: Node<FlowNodeData>[] = Array.from(allGroups.values()).map(g => ({
121
+ id: `group_${g.groupName}`,
122
+ type: 'serviceGroup',
123
+ data: {
124
+ label: g.name,
125
+ subLabel: g.description ?? undefined,
126
+ groupId: g.groupName,
127
+ kind: 'group'
128
+ },
129
+ position,
130
+ class: g.groupName === currentGroup.groupName ? 'current-group' : 'external-group'
131
+ }))
132
+
133
+ // 5. Add Group-to-Group Edges
134
+ groupConnections.forEach(conn => {
135
+ edges.push(buildEdge(`group_${conn.sourceGroup}`, `group_${conn.targetGroup}`, 'service-group'))
136
+ })
137
+
138
+ const signature = createGraphSignature('group-services', {
139
+ group: currentGroup.groupName,
140
+ services: currentServices.map(service => ({identifier: service.identifier, type: service.serviceType})).sort((a, b) =>
141
+ a.identifier.localeCompare(b.identifier)
142
+ ),
143
+ external: externalGroups.map(entry => ({
144
+ direction: entry.direction,
145
+ group: entry.group.groupName,
146
+ services: entry.services.map(service => service.identifier).sort()
147
+ }))
148
+ })
149
+
150
+ return {
151
+ graph: {
152
+ groupNodes,
153
+ serviceNodes: nodes.concat(Array.from(externalNodeMap.values())),
154
+ edges,
155
+ signature
156
+ },
157
+ serviceNodes: serviceNodeRecord,
158
+ externalNodes: externalNodeRecord
159
+ }
160
+ }
161
+
162
+ function createDirectionLookup(
163
+ entries: ExternalGroupServices[]
164
+ ): Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }> {
165
+ return entries.reduce((lookup, entry) => {
166
+ const key = entry.group?.groupName
167
+ if (!key) return lookup
168
+ const existing = lookup.get(key) || {}
169
+ if (entry.direction === 'incoming') {
170
+ existing.incoming = entry
171
+ } else if (entry.direction === 'outgoing') {
172
+ existing.outgoing = entry
173
+ }
174
+ lookup.set(key, existing)
175
+ return lookup
176
+ }, new Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>())
177
+ }
178
+
179
+ // Registers an external node in the nodeMap and externalNodeRecord.
180
+ // It uses a consistent svc::<groupId>::<serviceId> naming scheme.
181
+ function ensureExternalNode(
182
+ groupId: string,
183
+ serviceId: string,
184
+ lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>,
185
+ nodeMap: Map<string, Node<FlowNodeData>>,
186
+ record: Record<string, { service: ServiceDefinition; group: GroupInfo }>
187
+ ): string | null {
188
+ const key = `svc::${groupId}::${serviceId}`
189
+ if (nodeMap.has(key)) {
190
+ return key
191
+ }
192
+
193
+ // Try to find the service in either direction
194
+ const externalMeta = findExternalService('outgoing', groupId, serviceId, lookup) ||
195
+ findExternalService('incoming', groupId, serviceId, lookup)
196
+
197
+ if (!externalMeta) {
198
+ return null
199
+ }
200
+
201
+ nodeMap.set(key, {
202
+ id: key,
203
+ width: 220,
204
+ height: 70,
205
+ type: 'external',
206
+ data: {
207
+ label: externalMeta.service.friendlyName,
208
+ serviceType: externalMeta.service.serviceType,
209
+ subLabel: externalMeta.group.name,
210
+ groupId: groupId,
211
+ serviceId: externalMeta.service.identifier,
212
+ kind: 'external',
213
+ },
214
+ extent: "parent",
215
+ position: {x: 0, y: 0},
216
+ parentId: `group_${groupId}`,
217
+ })
218
+ record[key] = {service: externalMeta.service, group: externalMeta.group}
219
+
220
+ return key
221
+ }
222
+
223
+ function findExternalService(
224
+ direction: ExternalConnectionDirection,
225
+ groupId: string,
226
+ serviceId: string,
227
+ lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>
228
+ ) {
229
+ const entries = lookup.get(groupId)
230
+ if (!entries) return null
231
+ const dirEntry = entries[direction]
232
+ if (!dirEntry) return null
233
+ const service = dirEntry.services.find(item => item.identifier === serviceId)
234
+ if (!service) return null
235
+ return { group: dirEntry.group, service }
236
+ }
237
+
238
+ function buildEdge(
239
+ sourceId: string,
240
+ targetId: string,
241
+ connectionType?: ConnectionType | 'service-group',
242
+ edgeType: 'external' | 'internal' = 'internal' //TODO: Name me better
243
+ ): Edge<FlowEdgeData> {
244
+ console.debug(`Creating edge from ${sourceId} to ${targetId} of type ${connectionType} (${edgeType})`)
245
+ return {
246
+ id: `edge_${sourceId}_${targetId}`,
247
+ source: sourceId,
248
+ sourceHandle: 'output',
249
+ target: targetId,
250
+ targetHandle: 'input',
251
+ type: 'snake',
252
+ // type: 'smoothstep',
253
+ markerEnd: 'arrow',
254
+ class: `edge-${edgeType}`,
255
+ label: connectionType || "",
256
+ data: {label: connectionType, connectionType}
257
+ }
258
+ }
@@ -1,36 +1,36 @@
1
- <script lang="ts">
2
- import {ErrorDisplay} from '$lib/components';
3
- import { page } from '$app/stores';
4
-
5
- let { error, status } = $props<{ error: Error; status: number }>();
6
-
7
- const checkList = [
8
- "Check your internet connection",
9
- "Try refreshing the page",
10
- "Contact support if the issue persists"
11
- ];
12
-
13
- const technicalDetails = $derived({
14
- status,
15
- message: error?.message,
16
- stack: error?.stack,
17
- url: $page.url.pathname
18
- });
19
-
20
- function handleRetry() {
21
- location.reload();
22
- }
23
- function handleBack() {
24
- history.back();
25
- }
26
- </script>
27
-
28
- <ErrorDisplay
29
- title={status ? `Error ${status}` : 'Error'}
30
- message={error?.message ?? 'An unexpected error occurred.'}
31
- checkList={checkList}
32
- technicalDetails={technicalDetails}
33
- onRetry={handleRetry}
34
- onBack={handleBack}
35
- />
36
-
1
+ <script lang="ts">
2
+ import {ErrorDisplay} from '$lib/components';
3
+ import { page } from '$app/stores';
4
+
5
+ let { error, status } = $props<{ error: Error; status: number }>();
6
+
7
+ const checkList = [
8
+ "Check your internet connection",
9
+ "Try refreshing the page",
10
+ "Contact support if the issue persists"
11
+ ];
12
+
13
+ const technicalDetails = $derived({
14
+ status,
15
+ message: error?.message,
16
+ stack: error?.stack,
17
+ url: $page.url.pathname
18
+ });
19
+
20
+ function handleRetry() {
21
+ location.reload();
22
+ }
23
+ function handleBack() {
24
+ history.back();
25
+ }
26
+ </script>
27
+
28
+ <ErrorDisplay
29
+ title={status ? `Error ${status}` : 'Error'}
30
+ message={error?.message ?? 'An unexpected error occurred.'}
31
+ checkList={checkList}
32
+ technicalDetails={technicalDetails}
33
+ onRetry={handleRetry}
34
+ onBack={handleBack}
35
+ />
36
+
@@ -1,27 +1,27 @@
1
- <script lang="ts">
2
- import {Header, LoadingOverlay} from '$lib/components';
3
- import {navigating} from '$app/state';
4
- import './layout.css';
5
- import '../app.css';
6
- import favicon from '$lib/assets/favicon.svg';
7
- import {theme} from '$lib/state/theme.svelte';
8
-
9
- let { children } = $props();
10
-
11
- $effect(() => {
12
- if (typeof document !== 'undefined') {
13
- document.documentElement.classList.toggle('dark', theme.isDark);
14
- }
15
- });
16
- </script>
17
-
18
- <svelte:head><link rel="icon" href={favicon} /></svelte:head>
19
- <Header />
20
-
21
- {#if navigating.to}
22
- <LoadingOverlay message="Loading..." />
23
- {/if}
24
-
25
- <main class="w-full h-full px-6 py-6">
26
- {@render children()}
27
- </main>
1
+ <script lang="ts">
2
+ import {Header, LoadingOverlay} from '$lib/components';
3
+ import {navigating} from '$app/state';
4
+ import './layout.css';
5
+ import '../app.css';
6
+ import favicon from '$lib/assets/favicon.svg';
7
+ import {theme} from '$lib/state/theme.svelte';
8
+
9
+ let { children } = $props();
10
+
11
+ $effect(() => {
12
+ if (typeof document !== 'undefined') {
13
+ document.documentElement.classList.toggle('dark', theme.isDark);
14
+ }
15
+ });
16
+ </script>
17
+
18
+ <svelte:head><link rel="icon" href={favicon} /></svelte:head>
19
+ <Header />
20
+
21
+ {#if navigating.to}
22
+ <LoadingOverlay message="Loading..." />
23
+ {/if}
24
+
25
+ <main class="w-full h-full px-6 py-6">
26
+ {@render children()}
27
+ </main>