172 lines
5.9 KiB
JavaScript
172 lines
5.9 KiB
JavaScript
|
|
const DEVICE_TO_NODE = {
|
||
|
|
server: 'hw-dell-pe',
|
||
|
|
switch: 'net-catalyst',
|
||
|
|
storage: 'storage-netapp',
|
||
|
|
firewall: 'net-fortinet',
|
||
|
|
router: 'net-junper-srx',
|
||
|
|
loadbalancer: 'net-f5',
|
||
|
|
san: 'storage-pure',
|
||
|
|
pdu: 'infra-pdu',
|
||
|
|
ups: 'infra-apc-ups',
|
||
|
|
cooling: 'infra-cooling',
|
||
|
|
patch_panel: 'infra-patch-panel',
|
||
|
|
tape: 'storage-netapp',
|
||
|
|
kvm: 'infra-rack',
|
||
|
|
other: 'infra-rack'
|
||
|
|
};
|
||
|
|
|
||
|
|
const MANUFACTURER_NODE = {
|
||
|
|
hpe: 'hw-hpe-dl',
|
||
|
|
dell: 'hw-dell-pe',
|
||
|
|
lenovo: 'hw-lenovo-ts',
|
||
|
|
cisco: 'net-catalyst',
|
||
|
|
juniper: 'net-junper-ex',
|
||
|
|
arista: 'net-arista',
|
||
|
|
netapp: 'storage-netapp',
|
||
|
|
pure: 'storage-pure',
|
||
|
|
fortinet: 'net-fortinet',
|
||
|
|
f5: 'net-f5',
|
||
|
|
palo: 'net-paloalto',
|
||
|
|
nvidia: 'ai-nvidia',
|
||
|
|
apc: 'infra-apc-ups'
|
||
|
|
};
|
||
|
|
|
||
|
|
const DIAGRAM_TYPE_LABELS = {
|
||
|
|
data_architecture: 'Data Architectuur',
|
||
|
|
network_topology: 'Netwerk Topologie',
|
||
|
|
system_architecture: 'Systeem Architectuur',
|
||
|
|
application_flow: 'Applicatie Flow',
|
||
|
|
application_landscape: 'Applicatie Landschap'
|
||
|
|
};
|
||
|
|
|
||
|
|
function resolveNodeType(device) {
|
||
|
|
const m = (device.manufacturer || '').toLowerCase();
|
||
|
|
for (const [key, type] of Object.entries(MANUFACTURER_NODE)) {
|
||
|
|
if (m.includes(key)) return type;
|
||
|
|
}
|
||
|
|
return DEVICE_TO_NODE[device.device_type] || 'hw-dell-pe';
|
||
|
|
}
|
||
|
|
|
||
|
|
function rackDevicesToNodes(devices, startX = 40, startY = 40) {
|
||
|
|
return devices.map((d, i) => ({
|
||
|
|
id: 'rd-' + d.id,
|
||
|
|
type: resolveNodeType(d),
|
||
|
|
x: startX + (i % 4) * 180,
|
||
|
|
y: startY + Math.floor(i / 4) * 90,
|
||
|
|
label: d.name,
|
||
|
|
sub: (d.model || d.device_type || '').toString(),
|
||
|
|
css: 'node-hw',
|
||
|
|
w: 160,
|
||
|
|
h: 48,
|
||
|
|
ip: d.mgmt_ip || '',
|
||
|
|
vendor: d.manufacturer || '',
|
||
|
|
model: d.model || '',
|
||
|
|
specs: d.specs || '',
|
||
|
|
notes: d.notes || '',
|
||
|
|
rack_device_id: d.id,
|
||
|
|
layer: 'infra'
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseDiagramJson(diagram) {
|
||
|
|
let nodes = [];
|
||
|
|
let edges = [];
|
||
|
|
try {
|
||
|
|
nodes = JSON.parse(diagram.nodes || '[]');
|
||
|
|
edges = JSON.parse(diagram.edges || '[]');
|
||
|
|
} catch (e) { /* ignore */ }
|
||
|
|
if (!Array.isArray(nodes) && nodes && nodes.nodes) {
|
||
|
|
edges = nodes.edges || [];
|
||
|
|
nodes = nodes.nodes || [];
|
||
|
|
}
|
||
|
|
if (!Array.isArray(edges) && edges && edges.edges) {
|
||
|
|
nodes = edges.nodes || nodes;
|
||
|
|
edges = edges.edges || [];
|
||
|
|
}
|
||
|
|
if (!Array.isArray(nodes)) nodes = [];
|
||
|
|
if (!Array.isArray(edges)) edges = [];
|
||
|
|
return { nodes, edges };
|
||
|
|
}
|
||
|
|
|
||
|
|
function summarizeDiagram(diagram) {
|
||
|
|
const { nodes, edges } = parseDiagramJson(diagram);
|
||
|
|
const layers = {};
|
||
|
|
nodes.forEach(n => {
|
||
|
|
const layer = n.layer || (n.type || '').split('-')[0] || 'other';
|
||
|
|
layers[layer] = (layers[layer] || 0) + 1;
|
||
|
|
});
|
||
|
|
const protocols = {};
|
||
|
|
edges.forEach(e => {
|
||
|
|
const p = e.protocol || 'unspecified';
|
||
|
|
protocols[p] = (protocols[p] || 0) + 1;
|
||
|
|
});
|
||
|
|
return { nodes, edges, layers, protocols, nodeCount: nodes.length, edgeCount: edges.length };
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildStudioDemo(rack, devices) {
|
||
|
|
const infraNodes = rackDevicesToNodes(devices, 40, 80);
|
||
|
|
const appNodes = [
|
||
|
|
{ id: 'demo-api', type: 'api-gateway', x: 480, y: 90, label: 'API Gateway', sub: 'Ingress / Routing', css: 'node-ingest', w: 150, h: 44, layer: 'integration' },
|
||
|
|
{ id: 'demo-erp', type: 'app-java', x: 480, y: 190, label: 'ERP Core', sub: 'Java / Spring', css: 'node-compute', w: 140, h: 44, layer: 'app' },
|
||
|
|
{ id: 'demo-dw', type: 'storage-warehouse', x: 480, y: 290, label: 'Data Warehouse', sub: 'Analytics', css: 'node-storage', w: 155, h: 44, layer: 'data' },
|
||
|
|
{ id: 'demo-kafka', type: 'queue-kafka', x: 680, y: 140, label: 'Event Bus', sub: 'Kafka', css: 'node-queue', w: 140, h: 44, layer: 'integration' },
|
||
|
|
{ id: 'demo-portal', type: 'app-react', x: 680, y: 240, label: 'Web Portal', sub: 'React Frontend', css: 'node-compute', w: 145, h: 44, layer: 'app' }
|
||
|
|
];
|
||
|
|
const nodes = [...infraNodes, ...appNodes];
|
||
|
|
const edges = [];
|
||
|
|
if (infraNodes[0]) {
|
||
|
|
edges.push({ from: infraNodes[0].id, to: 'demo-api', flow: 'both', protocol: 'sync' });
|
||
|
|
edges.push({ from: 'demo-api', to: 'demo-erp', flow: 'flow', protocol: 'sync' });
|
||
|
|
edges.push({ from: 'demo-erp', to: 'demo-dw', flow: 'flow', protocol: 'batch' });
|
||
|
|
edges.push({ from: 'demo-api', to: 'demo-kafka', flow: 'pulse', protocol: 'event' });
|
||
|
|
edges.push({ from: 'demo-kafka', to: 'demo-portal', flow: 'both', protocol: 'async' });
|
||
|
|
if (infraNodes[1]) edges.push({ from: infraNodes[1].id, to: 'demo-dw', flow: 'flow', protocol: 'batch' });
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
flow: {
|
||
|
|
name: 'Applicatie Flow — ' + rack.name,
|
||
|
|
diagram_type: 'application_flow',
|
||
|
|
nodes: JSON.stringify(nodes),
|
||
|
|
edges: JSON.stringify(edges)
|
||
|
|
},
|
||
|
|
landscape: {
|
||
|
|
name: 'Applicatie Landschap — ' + rack.name,
|
||
|
|
diagram_type: 'application_landscape',
|
||
|
|
nodes: JSON.stringify(appNodes.map(n => ({ ...n, x: n.x - 200, y: n.y }))),
|
||
|
|
edges: JSON.stringify([
|
||
|
|
{ from: 'demo-portal', to: 'demo-api', flow: 'off', protocol: 'sync' },
|
||
|
|
{ from: 'demo-api', to: 'demo-erp', flow: 'off', protocol: 'sync' },
|
||
|
|
{ from: 'demo-erp', to: 'demo-dw', flow: 'off', protocol: 'batch' }
|
||
|
|
])
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function getWorkspaceTab(diagramType) {
|
||
|
|
if (diagramType === 'application_flow') return 'flow';
|
||
|
|
if (diagramType === 'application_landscape') return 'landscape';
|
||
|
|
return 'flow';
|
||
|
|
}
|
||
|
|
|
||
|
|
function getLayerForCategory(catLabel) {
|
||
|
|
const c = (catLabel || '').toLowerCase();
|
||
|
|
if (c.includes('data bron') || c.includes('ingest') || c.includes('opslag') || c.includes('database')) return 'data';
|
||
|
|
if (c.includes('microservice') || c.includes('apps')) return 'app';
|
||
|
|
if (c.includes('api') || c.includes('messaging') || c.includes('queue')) return 'integration';
|
||
|
|
if (c.includes('netwerk') || c.includes('compute / server') || c.includes('infrastructure') || c.includes('security') || c.includes('observability') || c.includes('ci/cd')) return 'infra';
|
||
|
|
if (c.includes('ai')) return 'app';
|
||
|
|
return 'other';
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
DEVICE_TO_NODE,
|
||
|
|
DIAGRAM_TYPE_LABELS,
|
||
|
|
resolveNodeType,
|
||
|
|
rackDevicesToNodes,
|
||
|
|
parseDiagramJson,
|
||
|
|
summarizeDiagram,
|
||
|
|
getLayerForCategory,
|
||
|
|
buildStudioDemo,
|
||
|
|
getWorkspaceTab
|
||
|
|
};
|