@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.
- package/.aws-icons-last-updated +1 -0
- package/.svelte-kit/ambient.d.ts +263 -0
- package/.svelte-kit/generated/client/app.js +31 -0
- package/.svelte-kit/generated/client/matchers.js +1 -0
- package/.svelte-kit/generated/client/nodes/0.js +1 -0
- package/.svelte-kit/generated/client/nodes/1.js +1 -0
- package/.svelte-kit/generated/client/nodes/2.js +3 -0
- package/.svelte-kit/generated/client/nodes/3.js +3 -0
- package/.svelte-kit/generated/client-optimized/app.js +31 -0
- package/.svelte-kit/generated/client-optimized/matchers.js +1 -0
- package/.svelte-kit/generated/client-optimized/nodes/0.js +1 -0
- package/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
- package/.svelte-kit/generated/client-optimized/nodes/2.js +3 -0
- package/.svelte-kit/generated/client-optimized/nodes/3.js +3 -0
- package/.svelte-kit/generated/root.js +3 -0
- package/.svelte-kit/generated/root.svelte +68 -0
- package/.svelte-kit/generated/server/internal.js +53 -0
- package/.svelte-kit/non-ambient.d.ts +43 -0
- package/.svelte-kit/output/client/.vite/manifest.json +175 -0
- package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +1 -0
- package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
- package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
- package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +46 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +2 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +2 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +2 -0
- package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +1 -0
- package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +1 -0
- package/.svelte-kit/output/client/_app/version.json +1 -0
- package/.svelte-kit/output/client/libavoid.wasm +0 -0
- package/.svelte-kit/output/client/static/robots.txt +3 -0
- package/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
- package/.svelte-kit/output/server/.vite/manifest.json +224 -0
- package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +1 -0
- package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +1 -0
- package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +1 -0
- package/.svelte-kit/output/server/chunks/ErrorDisplay.js +59 -0
- package/.svelte-kit/output/server/chunks/LoadingOverlay.js +12 -0
- package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +1671 -0
- package/.svelte-kit/output/server/chunks/connections.js +33 -0
- package/.svelte-kit/output/server/chunks/diagram.js +7 -0
- package/.svelte-kit/output/server/chunks/environment.js +34 -0
- package/.svelte-kit/output/server/chunks/equality.js +57 -0
- package/.svelte-kit/output/server/chunks/exports.js +174 -0
- package/.svelte-kit/output/server/chunks/false.js +4 -0
- package/.svelte-kit/output/server/chunks/index.js +59 -0
- package/.svelte-kit/output/server/chunks/index2.js +2939 -0
- package/.svelte-kit/output/server/chunks/index3.js +20 -0
- package/.svelte-kit/output/server/chunks/internal.js +1017 -0
- package/.svelte-kit/output/server/chunks/shared.js +770 -0
- package/.svelte-kit/output/server/chunks/utils.js +43 -0
- package/.svelte-kit/output/server/entries/pages/_error.svelte.js +64 -0
- package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +65 -0
- package/.svelte-kit/output/server/entries/pages/_page.svelte.js +3991 -0
- package/.svelte-kit/output/server/entries/pages/_page.ts.js +30 -0
- package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +67 -0
- package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +47 -0
- package/.svelte-kit/output/server/index.js +3747 -0
- package/.svelte-kit/output/server/internal.js +13 -0
- package/.svelte-kit/output/server/manifest-full.js +47 -0
- package/.svelte-kit/output/server/manifest.js +47 -0
- package/.svelte-kit/output/server/nodes/0.js +8 -0
- package/.svelte-kit/output/server/nodes/1.js +8 -0
- package/.svelte-kit/output/server/nodes/2.js +10 -0
- package/.svelte-kit/output/server/nodes/3.js +10 -0
- package/.svelte-kit/output/server/remote-entry.js +557 -0
- package/.svelte-kit/tsconfig.json +61 -0
- package/.svelte-kit/types/route_meta_data.json +8 -0
- package/.svelte-kit/types/src/routes/$types.d.ts +26 -0
- package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +21 -0
- package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +49 -0
- package/.svelte-kit/types/src/routes/proxy+page.ts +33 -0
- package/build/_app/env.js +1 -0
- package/build/_app/env.js.br +1 -0
- package/build/_app/env.js.gz +0 -0
- package/build/_app/immutable/assets/0.Czt_67iE.css +1 -0
- package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
- package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
- package/build/_app/immutable/chunks/BlLuv0eP.js +46 -0
- package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
- package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
- package/build/_app/immutable/chunks/CSBHmwYv.js +1 -0
- package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
- package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
- package/build/_app/immutable/chunks/CTCi5ueQ.js +1 -0
- package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
- package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
- package/build/_app/immutable/chunks/CfOzjaik.js +2 -0
- package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
- package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
- package/build/_app/immutable/chunks/D4PdvFNs.js +1 -0
- package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
- package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
- package/build/_app/immutable/chunks/DXgP-QUS.js +2 -0
- package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
- package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
- package/build/_app/immutable/chunks/DlbDC5An.js +1 -0
- package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
- package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
- package/build/_app/immutable/chunks/wRWe7aK9.js +1 -0
- package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
- package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
- package/build/_app/immutable/entry/app.ConrMuHl.js +2 -0
- package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
- package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
- package/build/_app/immutable/entry/start.Bm6FyGme.js +1 -0
- package/build/_app/immutable/entry/start.Bm6FyGme.js.br +2 -0
- package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
- package/build/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
- package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
- package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
- package/build/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
- package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
- package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
- package/build/_app/immutable/nodes/2.CLD-8chl.js +1 -0
- package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
- package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
- package/build/_app/immutable/nodes/3.DXYeBoel.js +1 -0
- package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
- package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
- package/build/_app/version.json +1 -0
- package/build/_app/version.json.br +0 -0
- package/build/_app/version.json.gz +0 -0
- package/build/index.html +34 -0
- package/build/index.html.br +0 -0
- package/build/index.html.gz +0 -0
- package/build/libavoid.wasm +0 -0
- package/build/libavoid.wasm.br +0 -0
- package/build/libavoid.wasm.gz +0 -0
- package/build/static/robots.txt +3 -0
- package/coverage/coverage-final.json +6 -0
- package/coverage/coverage-summary.json +7 -0
- package/coverage/junit.xml +57 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/stores/index.html +116 -0
- package/coverage/lcov-report/stores/routingStore.ts.html +781 -0
- package/coverage/lcov-report/utils/flow/helpers.ts.html +127 -0
- package/coverage/lcov-report/utils/flow/index.html +161 -0
- package/coverage/lcov-report/utils/flow/layout.ts.html +805 -0
- package/coverage/lcov-report/utils/flow/serviceIds.ts.html +97 -0
- package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +859 -0
- package/coverage/lcov.info +646 -0
- package/coverage/test-results.json +1 -0
- package/data/services/api/api-gateway.yaml +18 -0
- package/data/services/api/group-info.yaml +7 -0
- package/data/services/api/lambda-orders.yaml +21 -0
- package/data/services/api/lambda-products.yaml +15 -0
- package/data/services/api/lambda-users.yaml +15 -0
- package/data/services/compute/alb.yaml +15 -0
- package/data/services/compute/ecs-inventory.yaml +16 -0
- package/data/services/compute/ecs-notification.yaml +15 -0
- package/data/services/compute/group-info.yaml +7 -0
- package/data/services/data/dynamodb-notifications.yaml +12 -0
- package/data/services/data/dynamodb-orders.yaml +9 -0
- package/data/services/data/dynamodb-products.yaml +9 -0
- package/data/services/data/dynamodb-users.yaml +9 -0
- package/data/services/data/group-info.yaml +7 -0
- package/data/services/data/rds-postgres.yaml +9 -0
- package/data/services/data/redis.yaml +10 -0
- package/data/services/frontend/cloudfront.yaml +12 -0
- package/data/services/frontend/group-info.yaml +7 -0
- package/data/services/frontend/route53.yaml +15 -0
- package/data/services/frontend/s3-website.yaml +9 -0
- package/data/teams/cloud-shepherds.yaml +15 -0
- package/data/teams/data-wizards.yaml +15 -0
- package/data/teams/interface-architects.yaml +19 -0
- package/e2e/demo.test.ts +54 -0
- package/e2e/header-toolbar.simple.spec.ts +0 -0
- package/e2e/header-toolbar.spec.ts +53 -0
- package/e2e/layout.spec.ts +30 -0
- package/package.json +69 -0
- package/playwright.config.ts +10 -0
- package/plugins/mapper-data-plugin.ts +32 -0
- package/project.json +23 -0
- package/src/app.css +125 -0
- package/src/app.d.ts +31 -0
- package/src/app.html +11 -0
- package/src/lib/assets/favicon.svg +19 -0
- package/src/lib/components/EmptyState.svelte +37 -0
- package/src/lib/components/ErrorDisplay.svelte +82 -0
- package/src/lib/components/FlowCanvas.svelte +223 -0
- package/src/lib/components/GenericSidebarCard.svelte +44 -0
- package/src/lib/components/GroupDetailSidebar.svelte +31 -0
- package/src/lib/components/Header.svelte +57 -0
- package/src/lib/components/Legend.svelte +25 -0
- package/src/lib/components/LoadingOverlay.svelte +42 -0
- package/src/lib/components/LoadingSpinner.svelte +10 -0
- package/src/lib/components/ServiceDetailSidebar.svelte +90 -0
- package/src/lib/components/TeamContactCard.svelte +166 -0
- package/src/lib/components/flow/ExternalNode.svelte +45 -0
- package/src/lib/components/flow/MainGroupNode.svelte +24 -0
- package/src/lib/components/flow/ServiceGroupNode.svelte +17 -0
- package/src/lib/components/flow/ServiceNode.svelte +40 -0
- package/src/lib/components/flow/SnakeEdge.svelte +206 -0
- package/src/lib/components/flow/index.ts +6 -0
- package/src/lib/components/index.ts +12 -0
- package/src/lib/data/connections.ts +26 -0
- package/src/lib/data/groups.ts +11 -0
- package/src/lib/data/services.ts +12 -0
- package/src/lib/data/teams.ts +11 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/state/theme.svelte.ts +21 -0
- package/src/lib/stores/diagram.ts +6 -0
- package/src/lib/stores/routingStore.test.ts +133 -0
- package/src/lib/stores/routingStore.ts +232 -0
- package/src/lib/utils/awsIcons.ts +117 -0
- package/src/lib/utils/flow/groupOverviewGraph.ts +73 -0
- package/src/lib/utils/flow/helpers.ts +14 -0
- package/src/lib/utils/flow/layout.test.ts +271 -0
- package/src/lib/utils/flow/layout.ts +240 -0
- package/src/lib/utils/flow/serviceIds.ts +5 -0
- package/src/lib/utils/flow/servicesGraph.test.ts +119 -0
- package/src/lib/utils/flow/servicesGraph.ts +258 -0
- package/src/routes/+error.svelte +36 -0
- package/src/routes/+layout.svelte +27 -0
- package/src/routes/+page.svelte +81 -0
- package/src/routes/+page.ts +31 -0
- package/src/routes/group/[groupId]/+page.svelte +102 -0
- package/src/routes/group/[groupId]/+page.ts +48 -0
- package/src/routes/layout.css +0 -0
- package/static/static/robots.txt +3 -0
- package/svelte.config.js +28 -0
- package/tailwind.config.js +12 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +81 -0
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// place files you want to import through the `$lib` alias in this folder.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class ThemeState {
|
|
2
|
+
isDark = $state(false);
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
if (typeof window !== 'undefined') {
|
|
6
|
+
const stored = localStorage.getItem('preferredTheme');
|
|
7
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
8
|
+
this.isDark = stored ? stored === 'dark' : prefersDark;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
toggle() {
|
|
13
|
+
this.isDark = !this.isDark;
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
localStorage.setItem('preferredTheme', this.isDark ? 'dark' : 'light');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const theme = new ThemeState();
|
|
21
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { writable, type Writable } from 'svelte/store';
|
|
2
|
+
import type {GroupInfo} from "$shared/types";
|
|
3
|
+
|
|
4
|
+
export const selectedGroup: Writable<GroupInfo | null> = writable(null);
|
|
5
|
+
export const showLegend: Writable<boolean> = writable(true);
|
|
6
|
+
export const logDiagramAction = writable(0);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { routingStore } from './routingStore';
|
|
3
|
+
import type { XYPosition } from '@xyflow/svelte';
|
|
4
|
+
|
|
5
|
+
// Mock setTimeout to control the 16ms debounce
|
|
6
|
+
vi.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
describe('routingStore', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Reset the store/router if possible?
|
|
11
|
+
// Since it's a singleton, we might need a reset method or just clear the maps.
|
|
12
|
+
// For now let's just assume we can test it incrementally.
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('registers nodes and routes an edge between them', async () => {
|
|
16
|
+
const nodeA = {
|
|
17
|
+
nodeId: 'a',
|
|
18
|
+
absPos: { x: 0, y: 0 },
|
|
19
|
+
width: 100,
|
|
20
|
+
height: 50,
|
|
21
|
+
isGroup: false
|
|
22
|
+
};
|
|
23
|
+
const nodeB = {
|
|
24
|
+
nodeId: 'b',
|
|
25
|
+
absPos: { x: 200, y: 200 },
|
|
26
|
+
width: 100,
|
|
27
|
+
height: 50,
|
|
28
|
+
isGroup: false
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
await routingStore.registerNode(nodeA);
|
|
32
|
+
await routingStore.registerNode(nodeB);
|
|
33
|
+
|
|
34
|
+
await routingStore.registerEdge({
|
|
35
|
+
edgeId: 'e1',
|
|
36
|
+
sourceNodeId: 'a',
|
|
37
|
+
targetNodeId: 'b',
|
|
38
|
+
sourcePos: { x: 100, y: 25 },
|
|
39
|
+
sourceSide: 'right',
|
|
40
|
+
targetPos: { x: 200, y: 225 },
|
|
41
|
+
targetSide: 'left'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Trigger debounce
|
|
45
|
+
vi.advanceTimersByTime(20);
|
|
46
|
+
|
|
47
|
+
// Check if edge e1 has points
|
|
48
|
+
let results: Record<string, XYPosition[]> = {};
|
|
49
|
+
const unsubscribe = routingStore.subscribe(val => {
|
|
50
|
+
results = val;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(results['e1']).toBeDefined();
|
|
54
|
+
// Since we use shoulders (checkpoints), libavoid might still return 3 points if it's very direct,
|
|
55
|
+
// but typically it's 4+ for orthogonal snaking.
|
|
56
|
+
expect(results['e1'].length).toBeGreaterThanOrEqual(3);
|
|
57
|
+
|
|
58
|
+
unsubscribe();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('nudges parallel edges apart', async () => {
|
|
62
|
+
// Vertically stacked nodes
|
|
63
|
+
const nodes = [
|
|
64
|
+
{ nodeId: 's', absPos: { x: 0, y: 100 }, width: 100, height: 50, isGroup: false },
|
|
65
|
+
{ nodeId: 't1', absPos: { x: 300, y: 0 }, width: 100, height: 50, isGroup: false },
|
|
66
|
+
{ nodeId: 't2', absPos: { x: 300, y: 200 }, width: 100, height: 50, isGroup: false }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const n of nodes) await routingStore.registerNode(n);
|
|
70
|
+
|
|
71
|
+
// Two edges from same source to different targets
|
|
72
|
+
// Their paths might want to share the same "lane" if they were routed individually
|
|
73
|
+
await routingStore.registerEdge({
|
|
74
|
+
edgeId: 'e1', sourceNodeId: 's', targetNodeId: 't1',
|
|
75
|
+
sourcePos: { x: 100, y: 125 }, sourceSide: 'right',
|
|
76
|
+
targetPos: { x: 300, y: 25 }, targetSide: 'left'
|
|
77
|
+
});
|
|
78
|
+
await routingStore.registerEdge({
|
|
79
|
+
edgeId: 'e2', sourceNodeId: 's', targetNodeId: 't2',
|
|
80
|
+
sourcePos: { x: 100, y: 125 }, sourceSide: 'right',
|
|
81
|
+
targetPos: { x: 300, y: 225 }, targetSide: 'left'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
vi.advanceTimersByTime(20);
|
|
85
|
+
|
|
86
|
+
let results: Record<string, XYPosition[]> = {};
|
|
87
|
+
routingStore.subscribe(val => results = val)();
|
|
88
|
+
|
|
89
|
+
const p1 = results['e1'];
|
|
90
|
+
const p2 = results['e2'];
|
|
91
|
+
|
|
92
|
+
expect(p1).toBeDefined();
|
|
93
|
+
expect(p2).toBeDefined();
|
|
94
|
+
|
|
95
|
+
// Check vertical segments (trunks)
|
|
96
|
+
const getTrunkX = (pts: XYPosition[]) => {
|
|
97
|
+
const v = pts.find((p, i) => i > 0 && p.x === pts[i-1].x && Math.abs(p.y - pts[i-1].y) > 10);
|
|
98
|
+
return v ? v.x : null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const x1 = getTrunkX(p1);
|
|
102
|
+
const x2 = getTrunkX(p2);
|
|
103
|
+
|
|
104
|
+
if (x1 !== null && x2 !== null) {
|
|
105
|
+
expect(Math.abs(x1 - x2)).toBeGreaterThanOrEqual(10); // Nudging working
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('unregisters an edge and stops returning its points', async () => {
|
|
110
|
+
await routingStore.registerEdge({
|
|
111
|
+
edgeId: 'e_remove',
|
|
112
|
+
sourceNodeId: 'a',
|
|
113
|
+
targetNodeId: 'b',
|
|
114
|
+
sourcePos: { x: 100, y: 25 },
|
|
115
|
+
sourceSide: 'right',
|
|
116
|
+
targetPos: { x: 200, y: 225 },
|
|
117
|
+
targetSide: 'left'
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
vi.advanceTimersByTime(20);
|
|
121
|
+
|
|
122
|
+
let results: Record<string, XYPosition[]> = {};
|
|
123
|
+
routingStore.subscribe(val => results = val)();
|
|
124
|
+
expect(results['e_remove']).toBeDefined();
|
|
125
|
+
|
|
126
|
+
routingStore.unregisterEdge('e_remove');
|
|
127
|
+
|
|
128
|
+
// Unregistering deletes from state immediately but also processes transaction
|
|
129
|
+
results = {};
|
|
130
|
+
routingStore.subscribe(val => results = val)();
|
|
131
|
+
expect(results['e_remove']).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {writable} from 'svelte/store';
|
|
2
|
+
import type {XYPosition} from '@xyflow/svelte';
|
|
3
|
+
import {type Avoid, AvoidLib, type ConnRef, type Router, type ShapeRef} from 'libavoid-js';
|
|
4
|
+
import {OFFSET_STEP} from "$lib/utils/flow/layout";
|
|
5
|
+
|
|
6
|
+
// --- Types ---
|
|
7
|
+
|
|
8
|
+
export interface EdgeRoutingUpdate {
|
|
9
|
+
edgeId: string;
|
|
10
|
+
sourceNodeId: string;
|
|
11
|
+
targetNodeId: string;
|
|
12
|
+
sourcePos: XYPosition;
|
|
13
|
+
sourceSide: string;
|
|
14
|
+
targetPos: XYPosition;
|
|
15
|
+
targetSide: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NodeRoutingUpdate {
|
|
19
|
+
nodeId: string;
|
|
20
|
+
absPos: XYPosition;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
isGroup: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// --- Library Loading ---
|
|
27
|
+
|
|
28
|
+
let avoidLibPromise: Promise<void> | null = null;
|
|
29
|
+
let avoidLibInstance: Avoid | null = null;
|
|
30
|
+
|
|
31
|
+
async function ensureAvoidLibLoaded() {
|
|
32
|
+
if (avoidLibPromise) return avoidLibPromise;
|
|
33
|
+
if (typeof window === 'undefined') {
|
|
34
|
+
//We're in test mode in node, load the library directly from the file system
|
|
35
|
+
avoidLibPromise = AvoidLib.load();
|
|
36
|
+
} else {
|
|
37
|
+
avoidLibPromise = AvoidLib.load("/libavoid.wasm");
|
|
38
|
+
}
|
|
39
|
+
return avoidLibPromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function getAvoidLib() {
|
|
43
|
+
await ensureAvoidLibLoaded();
|
|
44
|
+
avoidLibInstance ??= AvoidLib.getInstance();
|
|
45
|
+
return avoidLibInstance;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Helpers ---
|
|
49
|
+
|
|
50
|
+
const getDirFlag = (pos: string) => {
|
|
51
|
+
const p = pos.toLowerCase();
|
|
52
|
+
if (p === 'right') return 8;
|
|
53
|
+
if (p === 'left') return 4;
|
|
54
|
+
if (p === 'top') return 1;
|
|
55
|
+
if (p === 'bottom') return 2;
|
|
56
|
+
return 15;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getShoulderPoint = (avoid: Avoid, x: number, y: number, pos: string, dist: number) => {
|
|
60
|
+
switch (pos.toLowerCase()) {
|
|
61
|
+
case 'right': return new avoid.Point(x + dist, y);
|
|
62
|
+
case 'left': return new avoid.Point(x - dist, y);
|
|
63
|
+
case 'top': return new avoid.Point(x, y - dist);
|
|
64
|
+
case 'bottom': return new avoid.Point(x, y + dist);
|
|
65
|
+
default: return new avoid.Point(x, y);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// --- Store Implementation ---
|
|
70
|
+
|
|
71
|
+
function createRoutingStore() {
|
|
72
|
+
const { subscribe, set } = writable<Record<string, XYPosition[]>>({});
|
|
73
|
+
|
|
74
|
+
let router: Router | null = null;
|
|
75
|
+
let avoid: Avoid | null = null;
|
|
76
|
+
|
|
77
|
+
const nodes = new Map<string, ShapeRef>();
|
|
78
|
+
const nodesMetadata = new Map<string, XYPosition>();
|
|
79
|
+
const edges = new Map<string, ConnRef>();
|
|
80
|
+
|
|
81
|
+
let pendingTransaction = false;
|
|
82
|
+
|
|
83
|
+
async function init() {
|
|
84
|
+
if (router) return;
|
|
85
|
+
avoid = await getAvoidLib();
|
|
86
|
+
router = new avoid.Router(avoid.RouterFlag.OrthogonalRouting.value);
|
|
87
|
+
|
|
88
|
+
router.setRoutingParameter(avoid.RoutingParameter.reverseDirectionPenalty.valueOf(), 1000);
|
|
89
|
+
router.setRoutingParameter(avoid.RoutingParameter.portDirectionPenalty.valueOf(), 500);
|
|
90
|
+
router.setRoutingParameter(avoid.RoutingParameter.idealNudgingDistance.valueOf(), OFFSET_STEP);
|
|
91
|
+
router.setRoutingParameter(avoid.RoutingParameter.segmentPenalty.valueOf(), 50);
|
|
92
|
+
|
|
93
|
+
// Options for nudging
|
|
94
|
+
router.setRoutingOption(avoid.RoutingOption.nudgeOrthogonalSegmentsConnectedToShapes.valueOf(), true);
|
|
95
|
+
router.setRoutingOption(avoid.RoutingOption.nudgeOrthogonalTouchingColinearSegments.valueOf(), true);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function processTransaction() {
|
|
99
|
+
if (!router || pendingTransaction) return;
|
|
100
|
+
|
|
101
|
+
pendingTransaction = true;
|
|
102
|
+
// Batch to next frame (16ms)
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
if (!router) {
|
|
105
|
+
pendingTransaction = false;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
router.processTransaction();
|
|
110
|
+
|
|
111
|
+
const results: Record<string, XYPosition[]> = {};
|
|
112
|
+
edges.forEach((conn, id) => {
|
|
113
|
+
const route = conn.displayRoute();
|
|
114
|
+
const pts: XYPosition[] = [];
|
|
115
|
+
for (let i = 0; i < route.size(); i++) {
|
|
116
|
+
const p = route.at(i);
|
|
117
|
+
pts.push({ x: p.x, y: p.y });
|
|
118
|
+
}
|
|
119
|
+
results[id] = pts;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
set(results);
|
|
123
|
+
pendingTransaction = false;
|
|
124
|
+
}, 16);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
subscribe,
|
|
129
|
+
|
|
130
|
+
registerNode: async (data: NodeRoutingUpdate) => {
|
|
131
|
+
await init();
|
|
132
|
+
if (!router || !avoid) return;
|
|
133
|
+
|
|
134
|
+
if (data.isGroup) {
|
|
135
|
+
if (nodes.has(data.nodeId)) {
|
|
136
|
+
const shape = nodes.get(data.nodeId)!;
|
|
137
|
+
router.deleteShape(shape);
|
|
138
|
+
nodes.delete(data.nodeId);
|
|
139
|
+
nodesMetadata.delete(data.nodeId);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
nodesMetadata.set(data.nodeId, data.absPos);
|
|
145
|
+
|
|
146
|
+
const poly = new avoid.Polygon(4);
|
|
147
|
+
poly.setPoint(0, new avoid.Point(data.absPos.x, data.absPos.y));
|
|
148
|
+
poly.setPoint(1, new avoid.Point(data.absPos.x + data.width, data.absPos.y));
|
|
149
|
+
poly.setPoint(2, new avoid.Point(data.absPos.x + data.width, data.absPos.y + data.height));
|
|
150
|
+
poly.setPoint(3, new avoid.Point(data.absPos.x, data.absPos.y + data.height));
|
|
151
|
+
|
|
152
|
+
if (nodes.has(data.nodeId)) {
|
|
153
|
+
const shape = nodes.get(data.nodeId)!;
|
|
154
|
+
//@ts-ignore - libavoid typings are wrong, moveShape_poly actually needs three args
|
|
155
|
+
router.moveShape_poly(shape, poly, false);
|
|
156
|
+
} else {
|
|
157
|
+
const shape = new avoid.ShapeRef(router, poly);
|
|
158
|
+
nodes.set(data.nodeId, shape);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
processTransaction();
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
registerEdge: async (data: EdgeRoutingUpdate) => {
|
|
165
|
+
await init();
|
|
166
|
+
if (!router || !avoid) return;
|
|
167
|
+
|
|
168
|
+
const sShape = nodes.get(data.sourceNodeId);
|
|
169
|
+
const tShape = nodes.get(data.targetNodeId);
|
|
170
|
+
|
|
171
|
+
if (!sShape || !tShape) return;
|
|
172
|
+
|
|
173
|
+
const createConnEnd = (nodeId: string, x: number, y: number, side: string, shape: ShapeRef) => {
|
|
174
|
+
if (!avoid) throw new Error('Avoid not loaded');
|
|
175
|
+
const pinClass = Array.from(data.edgeId).reduce((a, b) => a + (b.codePointAt(0) ?? 0), 0);
|
|
176
|
+
|
|
177
|
+
// Convert absolute coords to relative coords for the pin (libavoid expects relative to shape's top-left)
|
|
178
|
+
const abs = nodesMetadata.get(nodeId);
|
|
179
|
+
const xOff = x - (abs?.x ?? 0);
|
|
180
|
+
const yOff = y - (abs?.y ?? 0);
|
|
181
|
+
|
|
182
|
+
const pin = new avoid.ShapeConnectionPin(shape, pinClass, xOff, yOff, false, -40, getDirFlag(side));
|
|
183
|
+
pin.setExclusive(true);
|
|
184
|
+
return new avoid.ConnEnd(shape, pinClass);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const srcEnd = createConnEnd(data.sourceNodeId, data.sourcePos.x, data.sourcePos.y, data.sourceSide, sShape);
|
|
188
|
+
const dstEnd = createConnEnd(data.targetNodeId, data.targetPos.x, data.targetPos.y, data.targetSide, tShape);
|
|
189
|
+
|
|
190
|
+
let conn: ConnRef;
|
|
191
|
+
if (edges.has(data.edgeId)) {
|
|
192
|
+
conn = edges.get(data.edgeId)!;
|
|
193
|
+
conn.setSourceEndpoint(srcEnd);
|
|
194
|
+
conn.setDestEndpoint(dstEnd);
|
|
195
|
+
} else {
|
|
196
|
+
conn = new avoid.ConnRef(router, srcEnd, dstEnd);
|
|
197
|
+
edges.set(data.edgeId, conn);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Checkpoints for shoulders
|
|
201
|
+
const checkpoints = new avoid.CheckpointVector();
|
|
202
|
+
checkpoints.push_back(new avoid.Checkpoint(getShoulderPoint(avoid, data.sourcePos.x, data.sourcePos.y, data.sourceSide, 10)));
|
|
203
|
+
checkpoints.push_back(new avoid.Checkpoint(getShoulderPoint(avoid, data.targetPos.x, data.targetPos.y, data.targetSide, 10)));
|
|
204
|
+
conn.setRoutingCheckpoints(checkpoints);
|
|
205
|
+
|
|
206
|
+
processTransaction();
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
unregisterEdge: (id: string) => {
|
|
210
|
+
if (!router || !edges.has(id)) return;
|
|
211
|
+
const conn = edges.get(id)!;
|
|
212
|
+
router.deleteConnector(conn);
|
|
213
|
+
edges.delete(id);
|
|
214
|
+
|
|
215
|
+
set(Object.fromEntries(
|
|
216
|
+
Array.from(edges.entries()).map(([id, conn]) => {
|
|
217
|
+
const route = conn.displayRoute();
|
|
218
|
+
const pts: XYPosition[] = [];
|
|
219
|
+
for (let i = 0; i < route.size(); i++) {
|
|
220
|
+
const p = route.at(i);
|
|
221
|
+
pts.push({ x: p.x, y: p.y });
|
|
222
|
+
}
|
|
223
|
+
return [id, pts];
|
|
224
|
+
})
|
|
225
|
+
));
|
|
226
|
+
|
|
227
|
+
processTransaction();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const routingStore = createRoutingStore();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ServiceType } from '@k-4u/resource-mapper-shared'
|
|
2
|
+
|
|
3
|
+
type IconLookup = Record<ServiceType, string>
|
|
4
|
+
|
|
5
|
+
//@ts-ignore We ignore a bunch of missing options here for now. This should probably be generated from the service definitions at some point, but for now we can just hardcode the most common ones.
|
|
6
|
+
const SERVICE_TYPE_TO_ICON: IconLookup = {
|
|
7
|
+
[ServiceType.EC2]: 'Compute/EC2.svg',
|
|
8
|
+
[ServiceType.ECS]: 'Containers/Elastic-Container-Service.svg',
|
|
9
|
+
[ServiceType.EKS]: 'Containers/Elastic-Kubernetes-Service.svg',
|
|
10
|
+
[ServiceType.LAMBDA]: 'Compute/Lambda.svg',
|
|
11
|
+
[ServiceType.FARGATE]: 'Compute/Fargate.svg',
|
|
12
|
+
[ServiceType.BATCH]: 'Compute/Batch.svg',
|
|
13
|
+
[ServiceType.ELASTIC_BEANSTALK]: 'Compute/Elastic-Beanstalk.svg',
|
|
14
|
+
[ServiceType.APP_RUNNER]: 'Containers/App-Runner.svg',
|
|
15
|
+
[ServiceType.ECR]: 'Containers/Elastic-Container-Registry.svg',
|
|
16
|
+
[ServiceType.ECS_ANYWHERE]: 'Containers/ECS-Anywhere.svg',
|
|
17
|
+
[ServiceType.EKS_ANYWHERE]: 'Containers/EKS-Anywhere.svg',
|
|
18
|
+
[ServiceType.S3]: 'Storage/Simple-Storage-Service.svg',
|
|
19
|
+
[ServiceType.EFS]: 'Storage/EFS.svg',
|
|
20
|
+
[ServiceType.EBS]: 'Storage/Elastic-Block-Store.svg',
|
|
21
|
+
[ServiceType.FSX]: 'Storage/FSx.svg',
|
|
22
|
+
[ServiceType.STORAGE_GATEWAY]: 'Storage/Storage-Gateway.svg',
|
|
23
|
+
[ServiceType.BACKUP]: 'Storage/Backup.svg',
|
|
24
|
+
[ServiceType.S3_GLACIER]: 'Storage/Simple-Storage-Service-Glacier.svg',
|
|
25
|
+
[ServiceType.RDS]: 'Database/RDS.svg',
|
|
26
|
+
[ServiceType.DYNAMODB]: 'Database/DynamoDB.svg',
|
|
27
|
+
[ServiceType.ELASTICACHE]: 'Database/ElastiCache.svg',
|
|
28
|
+
[ServiceType.VALKEY]: 'Database/ElastiCache.svg',
|
|
29
|
+
[ServiceType.AURORA]: 'Database/Aurora.svg',
|
|
30
|
+
[ServiceType.NEPTUNE]: 'Database/Neptune.svg',
|
|
31
|
+
[ServiceType.DOCUMENTDB]: 'Database/DocumentDB.svg',
|
|
32
|
+
[ServiceType.KEYSPACES]: 'Database/Keyspaces.svg',
|
|
33
|
+
[ServiceType.TIMESTREAM]: 'Database/Timestream.svg',
|
|
34
|
+
[ServiceType.MEMORYDB]: 'Database/MemoryDB-for-Redis.svg',
|
|
35
|
+
[ServiceType.QLDB]: 'Database/Quantum-Ledger-Database.svg',
|
|
36
|
+
[ServiceType.VPC]: 'Networking-Content-Delivery/Virtual-Private-Cloud.svg',
|
|
37
|
+
[ServiceType.CLOUDFRONT]: 'Networking-Content-Delivery/CloudFront.svg',
|
|
38
|
+
[ServiceType.ROUTE53]: 'Networking-Content-Delivery/Route-53.svg',
|
|
39
|
+
[ServiceType.API_GATEWAY]: 'App-Integration/API-Gateway.svg',
|
|
40
|
+
[ServiceType.DIRECT_CONNECT]: 'Networking-Content-Delivery/Direct-Connect.svg',
|
|
41
|
+
[ServiceType.APP_MESH]: 'Networking-Content-Delivery/App-Mesh.svg',
|
|
42
|
+
[ServiceType.CLOUD_MAP]: 'Networking-Content-Delivery/Cloud-Map.svg',
|
|
43
|
+
[ServiceType.GLOBAL_ACCELERATOR]: 'Networking-Content-Delivery/Global-Accelerator.svg',
|
|
44
|
+
[ServiceType.ALB]: 'Networking-Content-Delivery/Elastic-Load-Balancing.svg',
|
|
45
|
+
[ServiceType.NLB]: 'Networking-Content-Delivery/Elastic-Load-Balancing.svg',
|
|
46
|
+
[ServiceType.ELB]: 'Networking-Content-Delivery/Elastic-Load-Balancing.svg',
|
|
47
|
+
[ServiceType.ELASTIC_LOAD_BALANCING]: 'Networking-Content-Delivery/Elastic-Load-Balancing.svg',
|
|
48
|
+
[ServiceType.SQS]: 'App-Integration/Simple-Queue-Service.svg',
|
|
49
|
+
[ServiceType.SNS]: 'App-Integration/Simple-Notification-Service.svg',
|
|
50
|
+
[ServiceType.EVENTBRIDGE]: 'App-Integration/EventBridge.svg',
|
|
51
|
+
[ServiceType.EVENTBRIDGE_RULE]: 'App-Integration/EventBridge.svg',
|
|
52
|
+
[ServiceType.STEP_FUNCTIONS]: 'App-Integration/Step-Functions.svg',
|
|
53
|
+
[ServiceType.SWF]: 'App-Integration/Step-Functions.svg',
|
|
54
|
+
[ServiceType.MQ]: 'App-Integration/MQ.svg',
|
|
55
|
+
[ServiceType.APPSYNC]: 'Front-End-Web-Mobile/AppSync.svg',
|
|
56
|
+
[ServiceType.KINESIS]: 'Analytics/Kinesis.svg',
|
|
57
|
+
[ServiceType.KINESIS_FIREHOSE]: 'Analytics/Kinesis-Firehose.svg',
|
|
58
|
+
[ServiceType.KINESIS_ANALYTICS]: 'Analytics/Kinesis-Data-Analytics.svg',
|
|
59
|
+
[ServiceType.ATHENA]: 'Analytics/Athena.svg',
|
|
60
|
+
[ServiceType.EMR]: 'Analytics/EMR.svg',
|
|
61
|
+
[ServiceType.GLUE]: 'Analytics/Glue.svg',
|
|
62
|
+
[ServiceType.REDSHIFT]: 'Analytics/Redshift.svg',
|
|
63
|
+
[ServiceType.QUICKSIGHT]: 'Analytics/QuickSight.svg',
|
|
64
|
+
[ServiceType.OPENSEARCH]: 'Analytics/OpenSearch-Service.svg',
|
|
65
|
+
[ServiceType.MSK]: 'Analytics/Managed-Streaming-for-Apache-Kafka.svg',
|
|
66
|
+
[ServiceType.IAM]: 'Security-Identity-Compliance/Identity-and-Access-Management.svg',
|
|
67
|
+
[ServiceType.COGNITO]: 'Security-Identity-Compliance/Cognito.svg',
|
|
68
|
+
[ServiceType.SECRETS_MANAGER]: 'Security-Identity-Compliance/Secrets-Manager.svg',
|
|
69
|
+
[ServiceType.KMS]: 'Security-Identity-Compliance/Key-Management-Service.svg',
|
|
70
|
+
[ServiceType.WAF]: 'Security-Identity-Compliance/WAF.svg',
|
|
71
|
+
[ServiceType.SHIELD]: 'Security-Identity-Compliance/Shield.svg',
|
|
72
|
+
[ServiceType.GUARDDUTY]: 'Security-Identity-Compliance/GuardDuty.svg',
|
|
73
|
+
[ServiceType.INSPECTOR]: 'Security-Identity-Compliance/Inspector.svg',
|
|
74
|
+
[ServiceType.MACIE]: 'Security-Identity-Compliance/Macie.svg',
|
|
75
|
+
[ServiceType.SECURITY_HUB]: 'Security-Identity-Compliance/Security-Hub.svg',
|
|
76
|
+
[ServiceType.CERTIFICATE_MANAGER]: 'Security-Identity-Compliance/Certificate-Manager.svg',
|
|
77
|
+
[ServiceType.FIREWALL_MANAGER]: 'Security-Identity-Compliance/Firewall-Manager.svg',
|
|
78
|
+
[ServiceType.NETWORK_FIREWALL]: 'Security-Identity-Compliance/Network-Firewall.svg',
|
|
79
|
+
[ServiceType.CLOUDWATCH]: 'Management-Governance/CloudWatch.svg',
|
|
80
|
+
[ServiceType.CLOUDTRAIL]: 'Management-Governance/CloudTrail.svg',
|
|
81
|
+
[ServiceType.CONFIG]: 'Management-Governance/Config.svg',
|
|
82
|
+
[ServiceType.SYSTEMS_MANAGER]: 'Management-Governance/Systems-Manager.svg',
|
|
83
|
+
[ServiceType.CLOUDFORMATION]: 'Management-Governance/CloudFormation.svg',
|
|
84
|
+
[ServiceType.ORGANIZATIONS]: 'Management-Governance/Organizations.svg',
|
|
85
|
+
[ServiceType.CONTROL_TOWER]: 'Management-Governance/Control-Tower.svg',
|
|
86
|
+
[ServiceType.TRUSTED_ADVISOR]: 'Management-Governance/Trusted-Advisor.svg',
|
|
87
|
+
[ServiceType.CODECOMMIT]: 'Developer-Tools/CodeCommit.svg',
|
|
88
|
+
[ServiceType.CODEBUILD]: 'Developer-Tools/CodeBuild.svg',
|
|
89
|
+
[ServiceType.CODEDEPLOY]: 'Developer-Tools/CodeDeploy.svg',
|
|
90
|
+
[ServiceType.CODEPIPELINE]: 'Developer-Tools/CodePipeline.svg',
|
|
91
|
+
[ServiceType.CODEARTIFACT]: 'Developer-Tools/CodeArtifact.svg',
|
|
92
|
+
[ServiceType.CLOUD9]: 'Developer-Tools/Cloud9.svg',
|
|
93
|
+
[ServiceType.X_RAY]: 'Developer-Tools/X-Ray.svg',
|
|
94
|
+
[ServiceType.SAGEMAKER]: 'Machine-Learning/SageMaker.svg',
|
|
95
|
+
[ServiceType.COMPREHEND]: 'Machine-Learning/Comprehend.svg',
|
|
96
|
+
[ServiceType.REKOGNITION]: 'Machine-Learning/Rekognition.svg',
|
|
97
|
+
[ServiceType.TEXTRACT]: 'Machine-Learning/Textract.svg',
|
|
98
|
+
[ServiceType.TRANSLATE]: 'Machine-Learning/Translate.svg',
|
|
99
|
+
[ServiceType.TRANSCRIBE]: 'Machine-Learning/Transcribe.svg',
|
|
100
|
+
[ServiceType.POLLY]: 'Machine-Learning/Polly.svg',
|
|
101
|
+
[ServiceType.LEX]: 'Machine-Learning/Lex.svg',
|
|
102
|
+
[ServiceType.DMS]: 'Migration-Transfer/Database-Migration-Service.svg',
|
|
103
|
+
[ServiceType.TRANSFER_FAMILY]: 'Migration-Transfer/Transfer-Family.svg',
|
|
104
|
+
[ServiceType.MIGRATION_HUB]: 'Migration-Transfer/Migration-Hub.svg',
|
|
105
|
+
[ServiceType.DATASYNC]: 'Migration-Transfer/DataSync.svg',
|
|
106
|
+
[ServiceType.SNOWBALL]: 'Migration-Transfer/Snowball.svg',
|
|
107
|
+
[ServiceType.AMPLIFY]: 'Front-End-Web-Mobile/Amplify.svg'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getAwsIconPath(serviceType?: ServiceType | null): string | null {
|
|
111
|
+
if (!serviceType) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
const file = SERVICE_TYPE_TO_ICON[serviceType]
|
|
115
|
+
return file ? `/icons/aws/${file}` : null
|
|
116
|
+
}
|
|
117
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {GroupConnection, GroupInfo} from '$shared/types'
|
|
2
|
+
import type {FlowEdgeData, FlowGraphInput, FlowNodeData} from '$shared/flow-types'
|
|
3
|
+
import {createGraphSignature, formatConnectionLabel, sanitizeNodeId} from '$lib/utils/flow/helpers'
|
|
4
|
+
import type {Edge, Node} from "@xyflow/svelte"
|
|
5
|
+
|
|
6
|
+
export interface GroupOverviewGraphResult {
|
|
7
|
+
graph: FlowGraphInput | null
|
|
8
|
+
nodeToGroupMap: Record<string, string>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildGroupOverviewGraph(
|
|
12
|
+
groups: Record<string, GroupInfo>,
|
|
13
|
+
connections: GroupConnection[] = []
|
|
14
|
+
): GroupOverviewGraphResult {
|
|
15
|
+
if (!groups || Object.keys(groups).length === 0) {
|
|
16
|
+
return { graph: null, nodeToGroupMap: {} }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Convert each group into a diagram node
|
|
20
|
+
const nodes: Node<FlowNodeData>[] = Object.entries(groups).map(([groupId, groupInfo]) => ({
|
|
21
|
+
id: sanitizeNodeId(groupId, 'mainGroup'),
|
|
22
|
+
type: 'mainGroup',
|
|
23
|
+
data: {
|
|
24
|
+
label: groupInfo.name,
|
|
25
|
+
subLabel: groupInfo.description ?? groupId,
|
|
26
|
+
groupId,
|
|
27
|
+
kind: 'mainGroup'
|
|
28
|
+
},
|
|
29
|
+
width: 120,
|
|
30
|
+
height: 80,
|
|
31
|
+
position: { x: 0, y: 0 },
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
// Convert each connection into a diagram edge
|
|
35
|
+
const edges: Edge<FlowEdgeData>[] = connections.map((connection, index) => {
|
|
36
|
+
const sourceId = sanitizeNodeId(connection.sourceGroup, 'mainGroup')
|
|
37
|
+
const targetId = sanitizeNodeId(connection.targetGroup, 'mainGroup')
|
|
38
|
+
return {
|
|
39
|
+
id: `edge_${sourceId}_${targetId}_${index}`,
|
|
40
|
+
source: sourceId,
|
|
41
|
+
sourceHandle: 'output',
|
|
42
|
+
target: targetId,
|
|
43
|
+
targetHandle: 'input',
|
|
44
|
+
type: 'snake',
|
|
45
|
+
label: formatConnectionLabel(connection.connectionCount),
|
|
46
|
+
data: {
|
|
47
|
+
label: formatConnectionLabel(connection.connectionCount),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Create a unique signature for the graph for caching/change detection
|
|
53
|
+
const signature = createGraphSignature('group-overview', {
|
|
54
|
+
groups: nodes.map(node => node.data.groupId).sort(),
|
|
55
|
+
connections: connections.map(connection => ({
|
|
56
|
+
sourceGroup: connection.sourceGroup,
|
|
57
|
+
targetGroup: connection.targetGroup,
|
|
58
|
+
connectionCount: connection.connectionCount
|
|
59
|
+
}))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const graph: FlowGraphInput = { groupNodes: [], serviceNodes: nodes, edges, signature }
|
|
63
|
+
// Map diagram node IDs back to group IDs for UI interaction
|
|
64
|
+
const nodeToGroupMap = nodes.reduce<Record<string, string>>((lookup, node) => {
|
|
65
|
+
if (node.data.groupId) {
|
|
66
|
+
lookup[node.id] = node.data.groupId
|
|
67
|
+
}
|
|
68
|
+
return lookup
|
|
69
|
+
}, {})
|
|
70
|
+
|
|
71
|
+
console.debug(graph)
|
|
72
|
+
return { graph, nodeToGroupMap }
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type {GroupConnection} from '$shared/types'
|
|
2
|
+
|
|
3
|
+
const sanitizeRegEx = /[^a-zA-Z0-9_]/g
|
|
4
|
+
|
|
5
|
+
export const sanitizeNodeId = (value: string, prefix = 'node') => {
|
|
6
|
+
const safe = (value || prefix).replace(sanitizeRegEx, '_')
|
|
7
|
+
return `${prefix}::${safe}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const formatConnectionLabel = (connectionCount: number) =>
|
|
11
|
+
connectionCount === 1 ? '1 connection' : `${connectionCount} connections`
|
|
12
|
+
|
|
13
|
+
export const createGraphSignature = (namespace: string, payload: unknown) =>
|
|
14
|
+
`${namespace}:${JSON.stringify(payload)}`
|