@k-4u/resource-mapper-core 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 (250) hide show
  1. package/.aws-icons-last-updated +1 -0
  2. package/.svelte-kit/ambient.d.ts +263 -0
  3. package/.svelte-kit/generated/client/app.js +31 -0
  4. package/.svelte-kit/generated/client/matchers.js +1 -0
  5. package/.svelte-kit/generated/client/nodes/0.js +1 -0
  6. package/.svelte-kit/generated/client/nodes/1.js +1 -0
  7. package/.svelte-kit/generated/client/nodes/2.js +3 -0
  8. package/.svelte-kit/generated/client/nodes/3.js +3 -0
  9. package/.svelte-kit/generated/client-optimized/app.js +31 -0
  10. package/.svelte-kit/generated/client-optimized/matchers.js +1 -0
  11. package/.svelte-kit/generated/client-optimized/nodes/0.js +1 -0
  12. package/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
  13. package/.svelte-kit/generated/client-optimized/nodes/2.js +3 -0
  14. package/.svelte-kit/generated/client-optimized/nodes/3.js +3 -0
  15. package/.svelte-kit/generated/root.js +3 -0
  16. package/.svelte-kit/generated/root.svelte +68 -0
  17. package/.svelte-kit/generated/server/internal.js +53 -0
  18. package/.svelte-kit/non-ambient.d.ts +43 -0
  19. package/.svelte-kit/output/client/.vite/manifest.json +175 -0
  20. package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +1 -0
  21. package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
  22. package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
  23. package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  24. package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +46 -0
  25. package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +1 -0
  26. package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +1 -0
  27. package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +2 -0
  28. package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +1 -0
  29. package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +2 -0
  30. package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +1 -0
  31. package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +1 -0
  32. package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +2 -0
  33. package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +1 -0
  34. package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
  35. package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
  36. package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +1 -0
  37. package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +1 -0
  38. package/.svelte-kit/output/client/_app/version.json +1 -0
  39. package/.svelte-kit/output/client/libavoid.wasm +0 -0
  40. package/.svelte-kit/output/client/static/robots.txt +3 -0
  41. package/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
  42. package/.svelte-kit/output/server/.vite/manifest.json +224 -0
  43. package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +1 -0
  44. package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +1 -0
  45. package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +1 -0
  46. package/.svelte-kit/output/server/chunks/ErrorDisplay.js +59 -0
  47. package/.svelte-kit/output/server/chunks/LoadingOverlay.js +12 -0
  48. package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +1671 -0
  49. package/.svelte-kit/output/server/chunks/connections.js +33 -0
  50. package/.svelte-kit/output/server/chunks/diagram.js +7 -0
  51. package/.svelte-kit/output/server/chunks/environment.js +34 -0
  52. package/.svelte-kit/output/server/chunks/equality.js +57 -0
  53. package/.svelte-kit/output/server/chunks/exports.js +174 -0
  54. package/.svelte-kit/output/server/chunks/false.js +4 -0
  55. package/.svelte-kit/output/server/chunks/index.js +59 -0
  56. package/.svelte-kit/output/server/chunks/index2.js +2939 -0
  57. package/.svelte-kit/output/server/chunks/index3.js +20 -0
  58. package/.svelte-kit/output/server/chunks/internal.js +1017 -0
  59. package/.svelte-kit/output/server/chunks/shared.js +770 -0
  60. package/.svelte-kit/output/server/chunks/utils.js +43 -0
  61. package/.svelte-kit/output/server/entries/pages/_error.svelte.js +64 -0
  62. package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +65 -0
  63. package/.svelte-kit/output/server/entries/pages/_page.svelte.js +3991 -0
  64. package/.svelte-kit/output/server/entries/pages/_page.ts.js +30 -0
  65. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +67 -0
  66. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +47 -0
  67. package/.svelte-kit/output/server/index.js +3747 -0
  68. package/.svelte-kit/output/server/internal.js +13 -0
  69. package/.svelte-kit/output/server/manifest-full.js +47 -0
  70. package/.svelte-kit/output/server/manifest.js +47 -0
  71. package/.svelte-kit/output/server/nodes/0.js +8 -0
  72. package/.svelte-kit/output/server/nodes/1.js +8 -0
  73. package/.svelte-kit/output/server/nodes/2.js +10 -0
  74. package/.svelte-kit/output/server/nodes/3.js +10 -0
  75. package/.svelte-kit/output/server/remote-entry.js +557 -0
  76. package/.svelte-kit/tsconfig.json +61 -0
  77. package/.svelte-kit/types/route_meta_data.json +8 -0
  78. package/.svelte-kit/types/src/routes/$types.d.ts +26 -0
  79. package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +21 -0
  80. package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +49 -0
  81. package/.svelte-kit/types/src/routes/proxy+page.ts +33 -0
  82. package/build/_app/env.js +1 -0
  83. package/build/_app/env.js.br +1 -0
  84. package/build/_app/env.js.gz +0 -0
  85. package/build/_app/immutable/assets/0.Czt_67iE.css +1 -0
  86. package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
  87. package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
  88. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
  89. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
  90. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
  91. package/build/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
  92. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
  93. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
  94. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  95. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
  96. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
  97. package/build/_app/immutable/chunks/BlLuv0eP.js +46 -0
  98. package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
  99. package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
  100. package/build/_app/immutable/chunks/CSBHmwYv.js +1 -0
  101. package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
  102. package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
  103. package/build/_app/immutable/chunks/CTCi5ueQ.js +1 -0
  104. package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
  105. package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
  106. package/build/_app/immutable/chunks/CfOzjaik.js +2 -0
  107. package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
  108. package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
  109. package/build/_app/immutable/chunks/D4PdvFNs.js +1 -0
  110. package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
  111. package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
  112. package/build/_app/immutable/chunks/DXgP-QUS.js +2 -0
  113. package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
  114. package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
  115. package/build/_app/immutable/chunks/DlbDC5An.js +1 -0
  116. package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
  117. package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
  118. package/build/_app/immutable/chunks/wRWe7aK9.js +1 -0
  119. package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
  120. package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
  121. package/build/_app/immutable/entry/app.ConrMuHl.js +2 -0
  122. package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
  123. package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
  124. package/build/_app/immutable/entry/start.Bm6FyGme.js +1 -0
  125. package/build/_app/immutable/entry/start.Bm6FyGme.js.br +2 -0
  126. package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
  127. package/build/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
  128. package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
  129. package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
  130. package/build/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
  131. package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
  132. package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
  133. package/build/_app/immutable/nodes/2.CLD-8chl.js +1 -0
  134. package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
  135. package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
  136. package/build/_app/immutable/nodes/3.DXYeBoel.js +1 -0
  137. package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
  138. package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
  139. package/build/_app/version.json +1 -0
  140. package/build/_app/version.json.br +0 -0
  141. package/build/_app/version.json.gz +0 -0
  142. package/build/index.html +34 -0
  143. package/build/index.html.br +0 -0
  144. package/build/index.html.gz +0 -0
  145. package/build/libavoid.wasm +0 -0
  146. package/build/libavoid.wasm.br +0 -0
  147. package/build/libavoid.wasm.gz +0 -0
  148. package/build/static/robots.txt +3 -0
  149. package/coverage/coverage-final.json +6 -0
  150. package/coverage/coverage-summary.json +7 -0
  151. package/coverage/junit.xml +57 -0
  152. package/coverage/lcov-report/base.css +224 -0
  153. package/coverage/lcov-report/block-navigation.js +87 -0
  154. package/coverage/lcov-report/favicon.png +0 -0
  155. package/coverage/lcov-report/index.html +131 -0
  156. package/coverage/lcov-report/prettify.css +1 -0
  157. package/coverage/lcov-report/prettify.js +2 -0
  158. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  159. package/coverage/lcov-report/sorter.js +210 -0
  160. package/coverage/lcov-report/stores/index.html +116 -0
  161. package/coverage/lcov-report/stores/routingStore.ts.html +781 -0
  162. package/coverage/lcov-report/utils/flow/helpers.ts.html +127 -0
  163. package/coverage/lcov-report/utils/flow/index.html +161 -0
  164. package/coverage/lcov-report/utils/flow/layout.ts.html +805 -0
  165. package/coverage/lcov-report/utils/flow/serviceIds.ts.html +97 -0
  166. package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +859 -0
  167. package/coverage/lcov.info +646 -0
  168. package/coverage/test-results.json +1 -0
  169. package/data/services/api/api-gateway.yaml +18 -0
  170. package/data/services/api/group-info.yaml +7 -0
  171. package/data/services/api/lambda-orders.yaml +21 -0
  172. package/data/services/api/lambda-products.yaml +15 -0
  173. package/data/services/api/lambda-users.yaml +15 -0
  174. package/data/services/compute/alb.yaml +15 -0
  175. package/data/services/compute/ecs-inventory.yaml +16 -0
  176. package/data/services/compute/ecs-notification.yaml +15 -0
  177. package/data/services/compute/group-info.yaml +7 -0
  178. package/data/services/data/dynamodb-notifications.yaml +12 -0
  179. package/data/services/data/dynamodb-orders.yaml +9 -0
  180. package/data/services/data/dynamodb-products.yaml +9 -0
  181. package/data/services/data/dynamodb-users.yaml +9 -0
  182. package/data/services/data/group-info.yaml +7 -0
  183. package/data/services/data/rds-postgres.yaml +9 -0
  184. package/data/services/data/redis.yaml +10 -0
  185. package/data/services/frontend/cloudfront.yaml +12 -0
  186. package/data/services/frontend/group-info.yaml +7 -0
  187. package/data/services/frontend/route53.yaml +15 -0
  188. package/data/services/frontend/s3-website.yaml +9 -0
  189. package/data/teams/cloud-shepherds.yaml +15 -0
  190. package/data/teams/data-wizards.yaml +15 -0
  191. package/data/teams/interface-architects.yaml +19 -0
  192. package/e2e/demo.test.ts +54 -0
  193. package/e2e/header-toolbar.simple.spec.ts +0 -0
  194. package/e2e/header-toolbar.spec.ts +53 -0
  195. package/e2e/layout.spec.ts +30 -0
  196. package/package.json +69 -0
  197. package/playwright.config.ts +10 -0
  198. package/plugins/mapper-data-plugin.ts +32 -0
  199. package/project.json +23 -0
  200. package/src/app.css +125 -0
  201. package/src/app.d.ts +31 -0
  202. package/src/app.html +11 -0
  203. package/src/lib/assets/favicon.svg +19 -0
  204. package/src/lib/components/EmptyState.svelte +37 -0
  205. package/src/lib/components/ErrorDisplay.svelte +82 -0
  206. package/src/lib/components/FlowCanvas.svelte +223 -0
  207. package/src/lib/components/GenericSidebarCard.svelte +44 -0
  208. package/src/lib/components/GroupDetailSidebar.svelte +31 -0
  209. package/src/lib/components/Header.svelte +57 -0
  210. package/src/lib/components/Legend.svelte +25 -0
  211. package/src/lib/components/LoadingOverlay.svelte +42 -0
  212. package/src/lib/components/LoadingSpinner.svelte +10 -0
  213. package/src/lib/components/ServiceDetailSidebar.svelte +90 -0
  214. package/src/lib/components/TeamContactCard.svelte +166 -0
  215. package/src/lib/components/flow/ExternalNode.svelte +45 -0
  216. package/src/lib/components/flow/MainGroupNode.svelte +24 -0
  217. package/src/lib/components/flow/ServiceGroupNode.svelte +17 -0
  218. package/src/lib/components/flow/ServiceNode.svelte +40 -0
  219. package/src/lib/components/flow/SnakeEdge.svelte +206 -0
  220. package/src/lib/components/flow/index.ts +6 -0
  221. package/src/lib/components/index.ts +12 -0
  222. package/src/lib/data/connections.ts +26 -0
  223. package/src/lib/data/groups.ts +11 -0
  224. package/src/lib/data/services.ts +12 -0
  225. package/src/lib/data/teams.ts +11 -0
  226. package/src/lib/index.ts +1 -0
  227. package/src/lib/state/theme.svelte.ts +21 -0
  228. package/src/lib/stores/diagram.ts +6 -0
  229. package/src/lib/stores/routingStore.test.ts +133 -0
  230. package/src/lib/stores/routingStore.ts +232 -0
  231. package/src/lib/utils/awsIcons.ts +117 -0
  232. package/src/lib/utils/flow/groupOverviewGraph.ts +73 -0
  233. package/src/lib/utils/flow/helpers.ts +14 -0
  234. package/src/lib/utils/flow/layout.test.ts +271 -0
  235. package/src/lib/utils/flow/layout.ts +240 -0
  236. package/src/lib/utils/flow/serviceIds.ts +5 -0
  237. package/src/lib/utils/flow/servicesGraph.test.ts +119 -0
  238. package/src/lib/utils/flow/servicesGraph.ts +258 -0
  239. package/src/routes/+error.svelte +36 -0
  240. package/src/routes/+layout.svelte +27 -0
  241. package/src/routes/+page.svelte +81 -0
  242. package/src/routes/+page.ts +31 -0
  243. package/src/routes/group/[groupId]/+page.svelte +102 -0
  244. package/src/routes/group/[groupId]/+page.ts +48 -0
  245. package/src/routes/layout.css +0 -0
  246. package/static/static/robots.txt +3 -0
  247. package/svelte.config.js +28 -0
  248. package/tailwind.config.js +12 -0
  249. package/tsconfig.json +22 -0
  250. package/vite.config.ts +81 -0
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ import TeamContactCard from '$lib/components/TeamContactCard.svelte'
3
+ import {useNodes, useOnSelectionChange} from "@xyflow/svelte";
4
+ import type {GroupInfo, ServiceDefinition} from "$shared/types";
5
+ import {getAwsIconPath} from "$lib/utils/awsIcons";
6
+ import {GenericSidebarCard} from "$lib/components";
7
+ import {getGroup} from "$lib/data/groups";
8
+
9
+ const nodes = useNodes();
10
+ let selectedNode = $derived(nodes.current.find((node) => node.selected));
11
+
12
+ useOnSelectionChange(({nodes: n, edges}) => {
13
+ selectedNodes = n.map((node) => node.id);
14
+ selectedEdges = edges.map((edge) => edge.id);
15
+ });
16
+
17
+ const {group, serviceNodeLookup, externalServiceLookup} = $props<{
18
+ group: GroupInfo | null;
19
+ serviceNodeLookup: Record<string, ServiceDefinition>;
20
+ externalServiceLookup: Record<string, { service: ServiceDefinition; group: GroupInfo }>;
21
+ }>();
22
+
23
+ let isExternal = $derived(!!selectedNode && selectedNode.id in externalServiceLookup);
24
+
25
+ let serviceInfo = $derived.by<ServiceDefinition | null>(() => {
26
+ if (!selectedNode) return null;
27
+
28
+ // Use the boolean to pick the source
29
+ return isExternal
30
+ ? externalServiceLookup[selectedNode.id]?.service
31
+ : serviceNodeLookup[selectedNode.id];
32
+ });
33
+
34
+ let activeGroup = $state<GroupInfo | null>(group);
35
+
36
+ $effect(() => {
37
+ // If it's external and we have a groupId, go fetch it
38
+ if (isExternal && serviceInfo?.groupId) {
39
+ getGroup(serviceInfo.groupId).then(res => {
40
+ activeGroup = res;
41
+ });
42
+ } else {
43
+ // Otherwise, reset back to the group passed in via props
44
+ activeGroup = group;
45
+ }
46
+ });
47
+
48
+ let displayGroup = $derived(activeGroup);
49
+ let displayTeamId = $derived(displayGroup?.teamId);
50
+
51
+ let iconPath = $derived(serviceInfo ? getAwsIconPath(serviceInfo.serviceType) : null);
52
+ let initials = $derived(
53
+ serviceInfo?.friendlyName
54
+ ? serviceInfo.friendlyName.split(' ').map(n => n[0]).join('').slice(0,3).toUpperCase()
55
+ : 'Svc'
56
+ );
57
+ </script>
58
+
59
+ <aside class="flex h-full w-96 flex-col border-l border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-slate-800 text-gray-900 dark:text-gray-100">
60
+ <div class="flex-1 overflow-y-auto p-4 space-y-4">
61
+
62
+ {#if !displayGroup}
63
+ <div class="rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-slate-700 p-4 text-sm text-gray-500 dark:text-gray-300">
64
+ Select a group to view details.
65
+ </div>
66
+ {:else}
67
+ <GenericSidebarCard
68
+ title={isExternal ? "Group" : "Current Group"}
69
+ subtitle={displayGroup.name}
70
+ description={displayGroup.description}
71
+ classes={isExternal ? "external" : ""}
72
+ />
73
+
74
+ {#if displayTeamId}
75
+ <TeamContactCard teamId={displayTeamId} classes={isExternal ? "external" : ""} />
76
+ {/if}
77
+
78
+ {#if serviceInfo}
79
+ <GenericSidebarCard
80
+ title="Service"
81
+ subtitle={serviceInfo.friendlyName}
82
+ description={serviceInfo.description}
83
+ iconPath={iconPath ?? undefined}
84
+ iconAlt={initials}
85
+ classes={isExternal ? "external" : ""}
86
+ />
87
+ {/if}
88
+ {/if}
89
+ </div>
90
+ </aside>
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import type {Team} from '$shared/types'
3
+ import Icon from '@iconify/svelte'
4
+ import {getTeam} from "$lib/data/teams";
5
+ import {LoadingSpinner, GenericSidebarCard} from "$lib/components";
6
+ import { slide } from 'svelte/transition';
7
+ import { quintOut } from 'svelte/easing';
8
+
9
+ const {teamId, classes} = $props<{ teamId: string, classes?: string }>();
10
+
11
+ const iconMap: Record<string, string> = {
12
+ email: 'mailto:',
13
+ phone: 'tel:',
14
+ sms: 'sms:',
15
+ web: '',
16
+ slack: '',
17
+ teams: '',
18
+ pigeon: '',
19
+ pagerduty: ''
20
+ }
21
+
22
+ const channelIcons: Record<string, string> = {
23
+ email: 'mdi:email-outline',
24
+ phone: 'mdi:phone-outline',
25
+ sms: 'mdi:message-text-outline',
26
+ web: 'mdi:web',
27
+ slack: 'mdi:slack',
28
+ teams: 'mdi:microsoft-teams',
29
+ pigeon: 'mdi:owl',
30
+ pagerduty: 'mdi:bell-ring-outline'
31
+ }
32
+
33
+ let team = $state<Team | null>(null);
34
+ let isLoading = $state(false);
35
+
36
+ $effect(() => {
37
+ if (!teamId) {
38
+ team = null
39
+ return
40
+ }
41
+ isLoading = true
42
+ getTeam(teamId)
43
+ .then((data) => {
44
+ team = data
45
+ })
46
+ .catch((error) => {
47
+ console.error('Error loading team data:', error)
48
+ team = null
49
+ })
50
+ .finally(() => {
51
+ isLoading = false
52
+ })
53
+ })
54
+
55
+
56
+ const defaultChannelIcon = 'mdi:link-variant'
57
+
58
+ function getChannelIcon(channel: string): string {
59
+ return channelIcons[channel.toLowerCase()] ?? defaultChannelIcon
60
+ }
61
+
62
+ function formatChannel(channel: string) {
63
+ return channel.replace(/[-_]/g, ' ').replace(/^\w|\s\w/g, (c) => c.toUpperCase())
64
+ }
65
+
66
+ function buildLink(channel: string, detail: string) {
67
+ const prefix = iconMap[channel.toLowerCase()]
68
+ if (prefix === undefined) {
69
+ return null
70
+ }
71
+ if (!prefix) {
72
+ return detail.startsWith('http') ? detail : detail.startsWith('//') ? detail : `https://${detail}`
73
+ }
74
+ return `${prefix}${detail}`
75
+ }
76
+
77
+ let activeChannel = $state<string | null>(null);
78
+ let copied = $state(false);
79
+
80
+ function toggleChannel(channelId: string) {
81
+ activeChannel = activeChannel === channelId ? null : channelId;
82
+ copied = false; // Reset feedback when switching
83
+ }
84
+
85
+ async function copyToClipboard(text: string) {
86
+ try {
87
+ await navigator.clipboard.writeText(text);
88
+ copied = true;
89
+ setTimeout(() => (copied = false), 2000);
90
+ } catch (err) {
91
+ console.error('Failed to copy: ', err);
92
+ }
93
+ }
94
+ </script>
95
+
96
+ {#if isLoading}
97
+ <LoadingSpinner message="Loading team information..."/>
98
+ {/if}
99
+
100
+ {#if team}
101
+ <GenericSidebarCard title="Team" subtitle={team.name} description={team.description} classes={classes}>
102
+ <div class="mt-4 w-full">
103
+ {#if team.reachability?.length}
104
+ <div class="flex flex-col overflow-hidden rounded-xl border border-gray-300 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-800/50">
105
+
106
+ <div class="flex items-center divide-x divide-gray-200 dark:divide-slate-700">
107
+ {#each team.reachability as entry}
108
+ {@const isExpanded = activeChannel === entry.channel}
109
+ <button
110
+ onclick={() => toggleChannel(entry.channel)}
111
+ class="flex h-12 flex-1 items-center justify-center transition-all
112
+ {isExpanded ? 'bg-blue-50 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400' : 'text-gray-500 hover:bg-gray-50 dark:text-gray-400 dark:hover:bg-slate-700/50'}"
113
+ >
114
+ <Icon icon={getChannelIcon(entry.channel)} class="h-5 w-5" />
115
+ </button>
116
+ {/each}
117
+ </div>
118
+
119
+ {#each team.reachability as entry}
120
+ {#if activeChannel === entry.channel}
121
+ <div
122
+ transition:slide={{ duration: 300, easing: quintOut }}
123
+ class="relative border-t border-gray-200 bg-gray-50/50 px-4 py-3 dark:border-slate-700 dark:bg-slate-900/30"
124
+ >
125
+ <button
126
+ onclick={() => copyToClipboard(entry.detail)}
127
+ class="absolute right-2 top-2 rounded-md p-1.5 transition-colors hover:bg-gray-200 dark:hover:bg-slate-700"
128
+ aria-label="Copy to clipboard"
129
+ >
130
+ {#if copied}
131
+ <Icon icon="mdi:check-bold" class="h-4 w-4 text-green-500" />
132
+ {:else}
133
+ <Icon icon="mdi:content-copy" class="h-4 w-4 text-gray-400 hover:text-gray-600 dark:text-slate-500 dark:hover:text-slate-300" />
134
+ {/if}
135
+ </button>
136
+
137
+ <div class="flex flex-col gap-1 pr-8">
138
+ <span class="text-[10px] font-black uppercase tracking-widest text-gray-400 dark:text-slate-500">
139
+ {formatChannel(entry.channel)}
140
+ </span>
141
+
142
+ {#if buildLink(entry.channel, entry.detail)}
143
+ <a
144
+ href={buildLink(entry.channel, entry.detail)}
145
+ target={entry.channel === 'web' ? '_blank' : undefined}
146
+ rel="noreferrer"
147
+ class="text-sm font-semibold text-blue-600 hover:underline dark:text-blue-400 break-all"
148
+ >
149
+ {entry.detail}
150
+ </a>
151
+ {:else}
152
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-200 leading-relaxed">
153
+ {entry.detail}
154
+ </span>
155
+ {/if}
156
+ </div>
157
+ </div>
158
+ {/if}
159
+ {/each}
160
+ </div>
161
+ {:else}
162
+ <p class="text-sm italic text-gray-500 dark:text-gray-400">No contact methods provided.</p>
163
+ {/if}
164
+ </div>
165
+ </GenericSidebarCard>
166
+ {/if}
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import type {FlowNodeData} from '$shared/flow-types'
3
+ import {Handle, type NodeProps, Position, type Node} from '@xyflow/svelte'
4
+ import {getAwsIconPath} from '$lib/utils/awsIcons'
5
+ import {goto} from '$app/navigation'
6
+
7
+ let {
8
+ data,
9
+ }: NodeProps<Node<FlowNodeData>> = $props()
10
+
11
+ let initials = $derived(data.label
12
+ ? data.label
13
+ .split(' ')
14
+ .map(part => part[0])
15
+ .join('')
16
+ .slice(0, 3)
17
+ .toUpperCase()
18
+ : 'Svc')
19
+
20
+ //Todo: Figure out if we just want to hard-link the data format to the nodes
21
+ //@ts-ignore
22
+ let iconPath = $derived(getAwsIconPath(data.serviceType))
23
+
24
+ function navigateToExternalGroupPage() {
25
+ goto(`/group/${data.groupId}`);
26
+ }
27
+
28
+ </script>
29
+
30
+ <Handle type="target" position={Position.Left} id="input" />
31
+ <Handle type="source" position={Position.Right} id="output"/>
32
+
33
+ <div class="flex items-center gap-3" ondblclick={() => navigateToExternalGroupPage()} role="navigation">
34
+ {#if iconPath}
35
+ <img src={iconPath} alt={data.label} class="h-10 w-10 rounded-lg bg-white/5 object-contain"
36
+ loading="lazy"/>
37
+ {:else}
38
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-800 text-xs font-semibold">
39
+ {initials}
40
+ </div>
41
+ {/if}
42
+ <div class="flex-1">
43
+ <div class="text-sm font-semibold">{data.label}</div>
44
+ </div>
45
+ </div>
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type {FlowNodeData} from '$shared/flow-types'
3
+ import {Handle, type Node, type NodeProps, Position} from '@xyflow/svelte'
4
+ import {goto} from '$app/navigation'
5
+
6
+ let {
7
+ data,
8
+ selected = false,
9
+ }: NodeProps<Node<FlowNodeData>> = $props()
10
+
11
+ //On double click, navigate to the group page.
12
+ const navigateToGroupPage = () => {
13
+ goto(`/group/${data.groupId}`);
14
+ }
15
+
16
+ </script>
17
+
18
+
19
+ <Handle type="target" position={Position.Left} id="input" />
20
+ <Handle type="source" position={Position.Right} id="output" />
21
+ <div class="relative" ondblclick={() => navigateToGroupPage()} aria-label="Group node" aria-roledescription="Group node">
22
+ {data.label}
23
+ </div>
24
+
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+
3
+ import type {FlowNodeData} from '$shared/flow-types'
4
+ import {type Node, type NodeProps} from '@xyflow/svelte'
5
+
6
+ let {
7
+ data,
8
+ width,
9
+ height,
10
+ selected = false,
11
+ }: NodeProps<Node<FlowNodeData>> = $props()
12
+ </script>
13
+
14
+ <!--Todo: Add custom styling depending on what kind of group it is -->
15
+ <div class="custom-group-node" style:width="{width}px" style:height="{height}px">
16
+ <div class="big-title">{data.label}</div>
17
+ </div>
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import type {FlowNodeData} from '$shared/flow-types'
3
+ import {Handle, type NodeProps, Position, type Node} from '@xyflow/svelte'
4
+ import {getAwsIconPath} from '$lib/utils/awsIcons'
5
+
6
+
7
+ let {
8
+ data,
9
+ }: NodeProps<Node<FlowNodeData>> = $props()
10
+
11
+ let initials = $derived(data.label
12
+ ? data.label
13
+ .split(' ')
14
+ .map(part => part[0])
15
+ .join('')
16
+ .slice(0, 3)
17
+ .toUpperCase()
18
+ : 'Svc')
19
+
20
+ //Todo: Figure out if we just want to hard-link the data format to the nodes
21
+ //@ts-ignore
22
+ let iconPath = $derived(getAwsIconPath(data.serviceType))
23
+ </script>
24
+
25
+ <Handle type="target" position={Position.Left} id="input" />
26
+ <Handle type="source" position={Position.Right} id="output"/>
27
+
28
+ <div class="flex items-center gap-3">
29
+ {#if iconPath}
30
+ <img src={iconPath} alt={data.label} class="h-10 w-10 rounded-lg bg-white/5 object-contain"
31
+ loading="lazy"/>
32
+ {:else}
33
+ <div class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-800 text-xs font-semibold">
34
+ {initials}
35
+ </div>
36
+ {/if}
37
+ <div class="flex-1">
38
+ <div class="text-sm font-semibold">{data.label}</div>
39
+ </div>
40
+ </div>
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import {BaseEdge, EdgeLabel, type EdgeProps, Position, useEdges, useNodes} from '@xyflow/svelte';
3
+ import {calculateEdgeOffset, getAbsolutePosition, getNodeDimensions} from '$lib/utils/flow/layout';
4
+ import {page} from '$app/state';
5
+ import {routingStore} from '$lib/stores/routingStore';
6
+ import {onDestroy} from 'svelte';
7
+
8
+ let {
9
+ id,
10
+ data,
11
+ source,
12
+ target,
13
+ sourceHandleId,
14
+ targetHandleId,
15
+ sourceX,
16
+ sourceY,
17
+ sourcePosition,
18
+ targetX,
19
+ targetY,
20
+ targetPosition,
21
+ label,
22
+ style,
23
+ selected
24
+ }: EdgeProps = $props();
25
+
26
+ const nodes = useNodes();
27
+ const edges = useEdges();
28
+
29
+ let isConnectedNodeSelected = $derived.by(() => {
30
+ const sourceNode = nodes.current.find((n) => n.id === source);
31
+ const targetNode = nodes.current.find((n) => n.id === target);
32
+ return sourceNode?.selected || targetNode?.selected;
33
+ });
34
+
35
+ let isEffectivelySelected = $derived(selected || isConnectedNodeSelected);
36
+
37
+ let sourceOffset = $derived(calculateEdgeOffset(id, nodes.current, edges.current, true));
38
+ let targetOffset = $derived(calculateEdgeOffset(id, nodes.current, edges.current, false));
39
+
40
+ // Register nodes and edges with the routing store
41
+ $effect(() => {
42
+ const sourceNode = nodes.current.find(n => n.id === source);
43
+ const targetNode = nodes.current.find(n => n.id === target);
44
+
45
+ if (sourceNode) {
46
+ const absPos = getAbsolutePosition(sourceNode.id, nodes.current);
47
+ const { w, h } = getNodeDimensions(sourceNode);
48
+ routingStore.registerNode({
49
+ nodeId: sourceNode.id,
50
+ absPos,
51
+ width: w,
52
+ height: h,
53
+ isGroup: sourceNode.type === 'serviceGroup'
54
+ });
55
+ }
56
+
57
+ if (targetNode) {
58
+ const absPos = getAbsolutePosition(targetNode.id, nodes.current);
59
+ const { w, h } = getNodeDimensions(targetNode);
60
+ routingStore.registerNode({
61
+ nodeId: targetNode.id,
62
+ absPos,
63
+ width: w,
64
+ height: h,
65
+ isGroup: targetNode.type === 'serviceGroup'
66
+ });
67
+ }
68
+ });
69
+
70
+ $effect(() => {
71
+ // We rely on XYFlow props for positions, but we apply our calculated offsets
72
+ let sX = sourceX;
73
+ let sY = sourceY;
74
+ let tX = targetX;
75
+ let tY = targetY;
76
+
77
+ if (sourcePosition === Position.Left || sourcePosition === Position.Right) {
78
+ sY += sourceOffset;
79
+ } else {
80
+ sX += sourceOffset;
81
+ }
82
+
83
+ if (targetPosition === Position.Left || targetPosition === Position.Right) {
84
+ tY += targetOffset;
85
+ } else {
86
+ tX += targetOffset;
87
+ }
88
+
89
+ routingStore.registerEdge({
90
+ edgeId: id,
91
+ sourceNodeId: source,
92
+ targetNodeId: target,
93
+ sourcePos: { x: sX, y: sY },
94
+ sourceSide: sourcePosition ?? 'right',
95
+ targetPos: { x: tX, y: tY },
96
+ targetSide: targetPosition ?? 'left'
97
+ });
98
+ });
99
+
100
+ onDestroy(() => {
101
+ routingStore.unregisterEdge(id);
102
+ });
103
+
104
+ // 1. Derive the path and label position
105
+ let pts = $derived($routingStore[id] || []);
106
+
107
+ let edgePathData = $derived.by(() => {
108
+ if (!pts || pts.length === 0) {
109
+ // Fallback if not routed yet
110
+ return { path: `M ${sourceX} ${sourceY} L ${targetX} ${targetY}`, labelX: (sourceX + targetX) / 2, labelY: (sourceY + targetY) / 2 };
111
+ }
112
+
113
+ // Generate rounded SVG path from points
114
+ let d = `M ${pts[0].x} ${pts[0].y}`;
115
+ const radius = 8;
116
+
117
+ for (let i = 0; i < pts.length - 1; i++) {
118
+ const p = pts[i];
119
+ const next = pts[i + 1];
120
+ const prev = i > 0 ? pts[i - 1] : null;
121
+
122
+ if (prev && next) {
123
+ // Round this corner
124
+ const dPrev = { x: p.x - prev.x, y: p.y - prev.y };
125
+ const dNext = { x: next.x - p.x, y: next.y - p.y };
126
+ const lenPrev = Math.hypot(dPrev.x, dPrev.y);
127
+ const lenNext = Math.hypot(dNext.x, dNext.y);
128
+ const actualRadius = Math.min(radius, lenPrev / 2, lenNext / 2);
129
+
130
+ const startPoint = {
131
+ x: p.x - (dPrev.x / lenPrev) * actualRadius,
132
+ y: p.y - (dPrev.y / lenPrev) * actualRadius
133
+ };
134
+ const endPoint = {
135
+ x: p.x + (dNext.x / lenNext) * actualRadius,
136
+ y: p.y + (dNext.y / lenNext) * actualRadius
137
+ };
138
+
139
+ d += ` L ${startPoint.x} ${startPoint.y} Q ${p.x} ${p.y} ${endPoint.x} ${endPoint.y}`;
140
+ } else if (i > 0) {
141
+ d += ` L ${p.x} ${p.y}`;
142
+ }
143
+ }
144
+ d += ` L ${pts[pts.length - 1].x} ${pts[pts.length - 1].y}`;
145
+
146
+ // Label position: midpoint of the longest segment
147
+ let longestLen = -1;
148
+ let labelPos = { x: (sourceX + targetX) / 2, y: (sourceY + targetY) / 2 };
149
+ for (let i = 0; i < pts.length - 1; i++) {
150
+ const dx = pts[i + 1].x - pts[i].x;
151
+ const dy = pts[i + 1].y - pts[i].y;
152
+ const len = Math.hypot(dx, dy);
153
+ if (len > longestLen) {
154
+ longestLen = len;
155
+ labelPos = { x: pts[i].x + dx / 2, y: pts[i].y + dy / 2 };
156
+ }
157
+ }
158
+ return { path: d, labelX: labelPos.x, labelY: labelPos.y };
159
+ });
160
+
161
+ let path = $derived(edgePathData.path);
162
+ let labelPos = $derived({ x: edgePathData.labelX, y: edgePathData.labelY });
163
+
164
+ let incomingOrOutgoingOrInternal = $derived.by(() => {
165
+ const urlGroupId = page.params.groupId;
166
+
167
+ if (!urlGroupId) {
168
+ return null;
169
+ }
170
+
171
+ // Determine if the edge is incoming, outgoing, or internal based on the handle ids, and if the group is the same.
172
+ //Assume that a node id is in the format of "svc::<group>::<service>".
173
+ const sourceGroup = source.split('::')[1];
174
+ const targetGroup = target.split('::')[1];
175
+
176
+ if (sourceGroup === targetGroup) {
177
+ return 'internal';
178
+ } else if (sourceHandleId === 'output' && sourceGroup === urlGroupId) {
179
+ return 'outgoing';
180
+ } else if (targetHandleId === 'input' && targetGroup === urlGroupId) {
181
+ return 'incoming';
182
+ } else {
183
+ return 'internal';
184
+ }
185
+ });
186
+
187
+ </script>
188
+
189
+ <BaseEdge
190
+ class={`${isEffectivelySelected ? 'snake-edge-path selected' : 'snake-edge-path'} ${incomingOrOutgoingOrInternal ?? ''}`}
191
+ {id}
192
+ {path}
193
+ {style}
194
+ />
195
+
196
+ {#if label}
197
+ <EdgeLabel>
198
+ <div
199
+ style:transform="translate(-50%, -50%) translate({labelPos.x}px, {labelPos.y}px)"
200
+ class="edge-label"
201
+ class:selected={isEffectivelySelected}
202
+ >
203
+ {label}
204
+ </div>
205
+ </EdgeLabel>
206
+ {/if}
@@ -0,0 +1,6 @@
1
+ //Export all
2
+ export {default as ExternalNode} from './ExternalNode.svelte';
3
+ export {default as MainGroupNode} from './MainGroupNode.svelte';
4
+ export {default as ServiceNode} from './ServiceNode.svelte';
5
+ export {default as ServiceGroupNode} from './ServiceGroupNode.svelte';
6
+ export {default as SnakeEdge} from './SnakeEdge.svelte';
@@ -0,0 +1,12 @@
1
+ export * from './flow';
2
+ export {default as EmptyState} from './EmptyState.svelte';
3
+ export {default as ErrorDisplay} from './ErrorDisplay.svelte';
4
+ export {default as FlowCanvas} from './FlowCanvas.svelte';
5
+ export {default as GenericSidebarCard} from './GenericSidebarCard.svelte';
6
+ export {default as GroupDetailSidebar} from './GroupDetailSidebar.svelte';
7
+ export {default as Header} from './Header.svelte';
8
+ export {default as Legend} from './Legend.svelte';
9
+ export {default as LoadingOverlay} from './LoadingOverlay.svelte';
10
+ export {default as LoadingSpinner} from './LoadingSpinner.svelte';
11
+ export {default as ServiceDetailSidebar} from './ServiceDetailSidebar.svelte';
12
+ export {default as TeamContactCard} from './TeamContactCard.svelte';
@@ -0,0 +1,26 @@
1
+ import type { GroupConnection } from '$shared/types'
2
+ import bakedData from 'virtual:mapper-data'
3
+
4
+ export async function getConnectionsFromGroup(groupId: string | null | undefined): Promise<GroupConnection[]> {
5
+ if (!groupId) return []
6
+ // Not pre-baked, so filter from all groupConnections
7
+ return (bakedData.groupConnections as GroupConnection[]).filter(c => c.sourceGroup === groupId)
8
+ }
9
+
10
+ export async function getConnectionsToGroup(groupId: string | null | undefined): Promise<GroupConnection[]> {
11
+ if (!groupId) return []
12
+ // Not pre-baked, so filter from all groupConnections
13
+ return (bakedData.groupConnections as GroupConnection[]).filter(c => c.targetGroup === groupId)
14
+ }
15
+
16
+ export async function getAllGroupConnections(): Promise<GroupConnection[]> {
17
+ // Returns all group connections except self connections
18
+ return (bakedData.groupConnections as GroupConnection[]).filter(
19
+ c => c.sourceGroup !== c.targetGroup
20
+ )
21
+ }
22
+
23
+ export async function getAllGroupConnectionsWithSelf(): Promise<GroupConnection[]> {
24
+ // Returns all group connections including self connections
25
+ return bakedData.groupConnections as GroupConnection[]
26
+ }
@@ -0,0 +1,11 @@
1
+ import type { GroupInfo } from '$shared/types'
2
+ import bakedData from 'virtual:mapper-data'
3
+
4
+ export async function getAllGroups(): Promise<Record<string, GroupInfo>> {
5
+ return bakedData.groups as Record<string, GroupInfo>;
6
+ }
7
+
8
+ export async function getGroup(groupId: string | null | undefined): Promise<GroupInfo | null> {
9
+ if (!groupId) return null;
10
+ return ((bakedData.groups as Record<string, GroupInfo>)[groupId]) ?? null;
11
+ }
@@ -0,0 +1,12 @@
1
+ import type { ExternalGroupServices, ServiceDefinition } from '$shared/types'
2
+ import bakedData from 'virtual:mapper-data'
3
+
4
+ export async function getServicesByGroup(groupId: string | null | undefined): Promise<ServiceDefinition[]> {
5
+ if (!groupId) return []
6
+ return (bakedData.servicesByGroup[groupId] as ServiceDefinition[]) || []
7
+ }
8
+
9
+ export async function getExternalServicesForGroup(groupId: string | null | undefined): Promise<ExternalGroupServices[]> {
10
+ if (!groupId) return []
11
+ return (bakedData.externalServices[groupId] as ExternalGroupServices[]) || []
12
+ }
@@ -0,0 +1,11 @@
1
+ import type { Team } from '$shared/types'
2
+ import bakedData from 'virtual:mapper-data'
3
+
4
+ export async function getAllTeams(): Promise<Record<string, Team>> {
5
+ return bakedData.teams as Record<string, Team>;
6
+ }
7
+
8
+ export async function getTeam(teamId: string | null | undefined): Promise<Team | null> {
9
+ if (!teamId) return null;
10
+ return (bakedData.teams[teamId] as Team) ?? null;
11
+ }