import { SlButton } from '@shoelace-style/shoelace'; import { NetworkComponent } from '..'; import { Ipv4Address } from '../adressing/Ipv4Address'; import { Ipv6Address } from '../adressing/Ipv6Address'; import { MacAddress } from '../adressing/MacAddress'; import { Net } from '../components/logicalNodes/Net'; import { Repeater, Hub, Switch, Bridge, AccessPoint, Router } from '../components/physicalNodes/Connector'; import { Host } from '../components/physicalNodes/Host'; import { ConnectionType, PhysicalNode } from '../components/physicalNodes/PhysicalNode'; import { initNetwork } from '../network-config'; import { AddressingHelper } from '../utils/AdressingHelper'; import { biPcDisplayHorizontal, biPhone, iconToDataURI } from '../styles/icons'; interface ComponentData { componentType: string; color?: string; } interface PhysicalNodeData extends ComponentData { interfaces: Array; } interface NetNodeData extends ComponentData { net: NetData; } interface InterfaceData { name?: string; connectionType?: ConnectionType; mac?: string; ipv4?: string; ipv6?: string; } interface NetData { netid: string; netmask: string; bitmask: number; } export class GraphNodeFactory { static addNode(network: NetworkComponent, data?: PhysicalNodeData | NetNodeData): void { if (data == null || data.componentType == '' || data.componentType == null) { return; } if (!network.networkAvailable) { initNetwork(network); } switch (data.componentType) { case 'edge': //edge is not added with the "plus-button" break; case 'net': const netData = data as NetNodeData; this.addNetNodeData(network, netData); // this.addNetNode(network); break; default: //default is adding physical node const nodeData = data as PhysicalNodeData; if (!nodeData.interfaces || nodeData.interfaces.length == 0) { nodeData.interfaces = []; } this.addPhysicalNodeData(network, nodeData); break; } network._graph.center(); } private static addNetNodeData(network: NetworkComponent, data: NetNodeData): void { let netid: string = data.net?.netid || ''; let netmask: string = data.net?.netmask || ''; let bitmask: number = data.net?.bitmask || 0; let color: string = data.color || '#70e6af'; let newNet = Net.createNet(color, netid, netmask, bitmask, network.ipv4Database, network); if (newNet != null) { network._graph.add({ group: 'nodes', data: newNet, classes: newNet.cssClass, }); } } private static addPhysicalNodeData(network: NetworkComponent, data: PhysicalNodeData): void { let numberOfPorts: number = data.interfaces.length; let color: string = data.color || '#70e6af'; let names: Map = new Map(); let portConnectionTypes: Map = new Map(); let portMacs: Map = new Map(); let portIpv4s: Map = new Map(); let portIpv6s: Map = new Map(); for (let index = 1; index <= numberOfPorts; index++) { let name: string = data.interfaces[index - 1].name || ''; if (name != '') names.set(index, name); let connectionType: ConnectionType = data.interfaces[index - 1].connectionType || 'ethernet'; portConnectionTypes.set(index, connectionType); let macAddress: MacAddress = MacAddress.validateAddress(data.interfaces[index - 1].mac ?? '', network.macDatabase) || MacAddress.generateRandomAddress(network.macDatabase); portMacs.set(index, macAddress); switch (data.componentType) { case 'router': case 'computer': case 'mobile': let ipv4: Ipv4Address = Ipv4Address.validateAddress(data.interfaces[index - 1].ipv4 ?? '', network.ipv4Database) || Ipv4Address.getLoopBackAddress(); let ipv6: Ipv6Address = Ipv6Address.validateAddress(data.interfaces[index - 1].ipv6 ?? '', network.ipv6Database) || Ipv6Address.getLoopBackAddress(); portIpv4s.set(index, ipv4); portIpv6s.set(index, ipv6); break; } } let component: PhysicalNode; switch (data.componentType) { //connectors //layer 1 case 'repeater': component = new Repeater(color, portConnectionTypes, ''); break; case 'hub': component = new Hub(color, numberOfPorts, ''); break; //layer 2 case 'switch': component = new Switch(color, numberOfPorts, portMacs, ''); break; case 'bridge': component = new Bridge(color, portConnectionTypes, portMacs, ''); break; case 'access-point': component = new AccessPoint(color, numberOfPorts, portMacs, ''); break; //layer 3 case 'router': component = new Router( color, numberOfPorts, names, portConnectionTypes, portMacs, portIpv4s, portIpv6s, '' ); break; //host case 'computer': component = new Host( color, iconToDataURI(biPcDisplayHorizontal), numberOfPorts, names, portConnectionTypes, portMacs, portIpv4s, portIpv6s, '' ); break; case 'mobile': component = new Host( color, iconToDataURI(biPhone), numberOfPorts, names, portConnectionTypes, portMacs, portIpv4s, portIpv6s, '' ); break; default: return; } if (component.layer >= 2) { portMacs.forEach((mac) => { MacAddress.addAddressToDatabase(mac, network.macDatabase, component.id); }); } if (component.layer >= 3) { portIpv4s.forEach((ip4) => { Ipv4Address.addAddressToDatabase(ip4, network.ipv4Database, component.id); }); portIpv6s.forEach((ip6) => { Ipv6Address.addAddressToDatabase(ip6, network.ipv6Database, component.id); }); } network._graph.add({ group: 'nodes', data: component, position: { x: 10, y: 10 }, classes: component.cssClass, }); } static removeNode(node: any, network: NetworkComponent): void { if (node.hasClass('net-node')) { this.removeNet(node, network); } else if (node.hasClass('gateway-node')) { this.removeGateway(node, network); } else { let physicalNode: PhysicalNode = node.data(); if (physicalNode.layer > 2) { physicalNode.portData.forEach((data) => { network.ipv4Database.delete(data.get('IPv4').address); }); } } } static removeGateway(node: any, network: NetworkComponent): void { let gateway: Router = node.data(); gateway.portNetMapping.forEach((net, port) => { net.gateways.delete(gateway.id); if ( net.currentDefaultGateway != undefined && net.currentDefaultGateway != null && net.currentDefaultGateway[0] == gateway.id && net.currentDefaultGateway[1] == port ) { if (net.gateways.size > 0) { let iter = net.gateways.entries(); let temp = iter.next(); while (temp.value != undefined && temp.value[1] == null) { temp = iter.next(); } if (temp.value != undefined && temp.value[1] != null) { net.currentDefaultGateway = temp.value; } else { net.currentDefaultGateway = null!; } } else { net.currentDefaultGateway = null!; } } network._graph .$('#' + net.id) .children() .forEach((child: any) => { let oldGateway = child.data('defaultGateway'); if ( oldGateway != null && oldGateway != undefined && oldGateway[0] == node.id() && oldGateway[1] == port ) { if (net.currentDefaultGateway != null && net.currentDefaultGateway != undefined) { child.data('defaultGateway', net.currentDefaultGateway); } else { child.data('defaultGateway', null); child.toggleClass('default-gateway-not-found', true); if (!child.data('cssClass').includes('default-gateway-not-found')) child.data('cssClass').push('default-gateway-not-found'); } } }); }); gateway.portData.forEach((data) => { network.ipv4Database.delete(data.get('IPv4').address); }); } static removeNet(node: any, network: NetworkComponent): void { //free addresses of all children node.children().forEach((child: any) => { this.removeNode(child, network); }); let net: Net = node.data() as Net; if (net.networkAddress != null && net.networkAddress != undefined) { network.ipv4Database.delete(AddressingHelper.getBroadcastAddress(net.networkAddress.address, net.bitmask)); //remove the broadcast address network.ipv4Database.delete(net.networkAddress.address); //free ID of network } //free the port of the gateways net.gateways.forEach((port, gatewayId) => { let gateway: Router = network._graph.$('#' + gatewayId).data(); if (port != null && port != undefined && !Number.isNaN(port)) { gateway.portNetMapping.set(port, null!); gateway.portLinkMapping.set(port, ''); } gateway.nets.forEach((nw, index, array) => { if (nw.id == net.id) { array.splice(index, 1); } }); if (gateway.nets.length == 0) network._graph.$('#' + gatewayId).toggleClass('gateway-node', false); //if the gateway has no networks --> back to router-node }); } static toggleResetColor(network: NetworkComponent): void { let changeColorHandler = function (event: any) { let node = event.target; node.data('color', network.currentColor); }; if (!network.resetColorModeOn) { network._graph.on('tap', changeColorHandler); (network.renderRoot.querySelector('#resetColorBtn') as SlButton).name = 'pause'; (network.renderRoot.querySelector('#resetColorBtn') as HTMLElement).style.backgroundColor = '#0291DB'; (network.renderRoot.querySelector('#drawBtn') as HTMLButtonElement).disabled = true; } else { // just remove handler network._graph.removeListener('tap'); (network.renderRoot.querySelector('#resetColorBtn') as SlButton).name = 'eyedropper'; (network.renderRoot.querySelector('#resetColorBtn') as HTMLElement).style.backgroundColor = '#8BA8CC'; (network.renderRoot.querySelector('#drawBtn') as HTMLButtonElement).disabled = false; } network.resetColorModeOn = !network.resetColorModeOn; } }