import SlColorPicker from '@shoelace-style/shoelace/dist/components/color-picker/color-picker.component.js'; import SlDrawer from '@shoelace-style/shoelace/dist/components/drawer/drawer.component.js'; import SlInput from '@shoelace-style/shoelace/dist/components/input/input.component.js'; import SlSelect from '@shoelace-style/shoelace/dist/components/select/select.component.js'; import type SlMenuItem from '@shoelace-style/shoelace/dist/components/menu-item/menu-item.component.js'; import type SlMenu from '@shoelace-style/shoelace/dist/components/menu/menu.component.js'; import { SlChangeEvent } from '@shoelace-style/shoelace'; import { html, nothing, TemplateResult } from 'lit'; import { NetworkComponent } from 'src'; import { Ipv4Address } from '../adressing/Ipv4Address'; import { Ipv6Address } from '../adressing/Ipv6Address'; import { MacAddress } from '../adressing/MacAddress'; import { Net } from '../components/logicalNodes/Net'; import { Router } from '../components/physicalNodes/Connector'; import { GraphNodeFactory } from '../event-handlers/component-manipulation'; import { biAlphabet, biDiagram3, biEthernet, biPCICardNetwork, biPencil, biPlusSquare, biRouter, biTrash, biWifi } from '../styles/icons'; import { EdgeController } from '../event-handlers/edge-controller'; import { styleMap } from 'lit/directives/style-map.js'; import { keyed } from 'lit/directives/keyed.js'; import { GraphEdge } from '../components/GraphEdge'; import { AlertHelper } from '../utils/AlertHelper'; import { AddressingHelper } from '../utils/AdressingHelper'; import { msg } from '@lit/localize'; export function hasSimulationTableEntries(node: { data(name: string): unknown }): boolean { return ['arpTableIpMac', 'routingTable', 'macAddressTable'].some((name) => { const table = node.data(name); return table instanceof Map && table.size > 0; }); } export function contextMenuTemplate(this: NetworkComponent): TemplateResult { if (!this.selectedObject) return html``; const type = this.selectedObject?.isNode() ? this.selectedObject.data('cssClass').includes('net-node') ? 'network' : 'node' : 'edge'; if (this.mode === 'simulate' && (type !== 'node' || !hasSimulationTableEntries(this.selectedObject))) return html``; const anchorRect = this.contextMenuAnchor?.getBoundingClientRect(); const canvasRect = this._cy?.getBoundingClientRect(); const viewportHeight = this.ownerDocument.documentElement.clientHeight; const spaceAbove = anchorRect && canvasRect ? anchorRect.top - Math.max(0, canvasRect.top) : 0; const spaceBelow = anchorRect && canvasRect ? Math.min(viewportHeight, canvasRect.bottom) - anchorRect.bottom : 0; const opensUpward = spaceAbove > spaceBelow; return html` ${this.mode === 'edit' ? html` ${type === 'network' ? networkConfigDrawerTemplate.bind(this)() : nothing} ${type === 'node' ? nodeConfigDrawerTemplate.bind(this)() : nothing} ${type === 'edge' ? edgeConfigDrawerTemplate.bind(this)() : nothing} ` : nothing} ${this.contextMenuAnchor ? html`
this.closeContextMenu()} @wheel=${() => this.closeContextMenu()} @contextmenu=${(e: Event) => e.preventDefault()} @keydown=${(e: KeyboardEvent) => { if (e.key !== 'Escape' || e.defaultPrevented) return; e.preventDefault(); e.stopPropagation(); if (this.contextMenuColorOpen) { this.contextMenuColorOpen = false; this.renderRoot.querySelector('#contextMenuColor')?.focus(); } else { this.closeContextMenu(true); } }} > e.stopPropagation()} @wheel=${(e: Event) => e.stopPropagation()} > ${this.mode === 'simulate' ? html`
${nodeRoutingTableTemplate.bind(this)()}
` : html` ${contextMenuItemsTemplate.bind(this)(type)} ${this.contextMenuColorOpen ? colorPanelTemplate.bind(this)() : nothing} `}
` : nothing} `; } function contextMenuItemsTemplate(this: NetworkComponent, type: 'node' | 'network' | 'edge'): TemplateResult { const color = this.selectedObject.data('color') || '#ffffff'; return html` ${type === 'node' ? html` { if (e.key === 'Escape') return; e.stopPropagation(); if (e.key === 'Enter') { e.preventDefault(); (e.currentTarget as HTMLElement).closest('sl-menu-item')?.focus(); } }} @sl-input=${(e: Event) => { this.selectedObject.data('name', (e.target as SlInput).value); }} > ` : nothing} ${type === 'network' ? msg('Define Network') : type === 'node' ? msg('Change Port Config') : msg('Define Interfaces')} ${msg('Change color')} ${gatewayMenuItemTemplate.bind(this)(type)} ${msg('Delete')} `; } function stopMenuControlEvent(e: Event): void { e.stopPropagation(); } function setCurrentMenuControl(e: FocusEvent): void { const item = (e.currentTarget as HTMLElement).closest('sl-menu-item'); if (item) item.closest('sl-menu')?.setCurrentItem(item); } function gatewayMenuItemTemplate(this: NetworkComponent, type: 'node' | 'network' | 'edge'): TemplateResult { const node = this.selectedObject; const isHost = type === 'node' && node.hasClass('host-node') && node.isChild(); const gateways: Map | undefined = type === 'network' ? node.data('gateways') : isHost ? node.parent().data('gateways') : undefined; if (!gateways?.size) return html``; const dataKey = type === 'network' ? 'currentDefaultGateway' : 'defaultGateway'; return html` { if (e.key !== 'Escape') e.stopPropagation(); }} @sl-change=${(e: Event) => { const gateway = (e.target as SlSelect).value as string; node.data(dataKey, [gateway, gateways.get(gateway)]); }} > ${Array.from(gateways.keys()).map((key) => html`${key}`)} `; } async function handleContextMenuSelect(this: NetworkComponent, e: CustomEvent<{ item: SlMenuItem }>): Promise { if (e.target !== e.currentTarget) return; const item = e.detail.item; if (item.value === 'color') { this.contextMenuColorOpen = !this.contextMenuColorOpen; if (this.contextMenuColorOpen) { await this.updateComplete; const picker = this.renderRoot.querySelector('#contextMenuColorPicker'); await picker?.updateComplete; if (this.contextMenuColorOpen) picker?.focus({ preventScroll: true }); } return; } this.contextMenuColorOpen = false; switch (item.value) { case 'rename': item.querySelector('sl-input')?.focus(); break; case 'gateway': item.querySelector('sl-select')?.show(); break; case 'node-config': openConfigDrawer.call(this, '#nodeConfigDrawer'); break; case 'network-config': openConfigDrawer.call(this, '#networkConfigDrawer'); break; case 'edge-config': openConfigDrawer.call(this, '#edgeConfigDrawer'); break; case 'delete': if (this.selectedObject.isNode()) { GraphNodeFactory.removeNode(this.selectedObject, this); } else { EdgeController.removeConnection(this.selectedObject.data(), this._graph); } this.selectedObject.remove(); this.closeContextMenu(true); break; } } function colorPanelTemplate(this: NetworkComponent): TemplateResult { return html` `; } function handleDrawerKeydown(e: KeyboardEvent) { if (e.key !== 'Escape') return; (e.currentTarget as SlDrawer).hide(); } function openConfigDrawer(this: NetworkComponent, selector: string) { this.closeContextMenu(); (this.shadowRoot?.querySelector(selector) as SlDrawer | null)?.show(); } function nodeConfigDrawerTemplate(this: NetworkComponent) { const node: any = this.selectedObject; const ports: Map> = node?.data('portData'); const entries = ports ? Array.from(ports.entries()) : []; return html` this.requestUpdate()} > ${entries.length > 0 ? html`
${entries.map(([index, port], position) => { const connected = isPortConnected.bind(this)(node, index); return html` ${position > 0 ? html`` : ''}
${position + 1} ${port.get('Name') || msg('Port') + ' ' + (position + 1)} e.stopPropagation()} @click=${(e: MouseEvent) => { e.stopPropagation(); removePort.bind(this)(node, index); }} >${biTrash}
${port.has('Name') ? html` { const v = (e.target as SlInput).value; const portData = node.data('portData'); portData.get(index).set('Name', v); node.data('portData', portData); }} >` : ''} ${port.has('Connection Type') ? html` ${port.get('Connection Type') === 'wireless' ? biWifi : biEthernet} ${msg('Ethernet')} ${msg('Wireless')} ` : ''} ${port.has('MAC') ? html`` : ''} ${port.has('IPv4') ? html`` : ''} ${port.has('IPv6') ? html`` : ''}
`; })}
` : html`

${msg('No port available.')}

`} ${biPlusSquare} ${msg('Add Port')}
`; } function edgeConfigDrawerTemplate(this: NetworkComponent) { const edge: any = this.selectedObject; if (!edge) return html``; const source: any = edge.source(); const target: any = edge.target(); const availableSourcePorts: number[] = []; const availableTargetPorts: number[] = []; source.data('portLinkMapping').forEach((link: any, port: any) => { if (link == null || link == undefined || link == '' || link === edge.data('id')) { availableSourcePorts.push(port); } }); target.data('portLinkMapping').forEach((link: any, port: any) => { if (link == null || link == undefined || link == '' || link === edge.data('id')) { availableTargetPorts.push(port); } }); const endpointTemplate = ( node: any, side: 'source' | 'target', availablePorts: number[], selected: number | undefined, role: string ) => { const other = side === 'source' ? 'target' : 'source'; return html`
${role} ${node.data('name')}
{ const port = parseInt((e.target as SlSelect).value as string); if (Number.isNaN(port)) { this.selectedPorts[side].connectionType = null; this.selectedPorts[side].port = null; } else { const cv = node.data('portData').get(port).get('Connection Type'); this.selectedPorts[side].connectionType = cv; this.selectedPorts[side].port = port; } if (this.selectedPorts['source'].port && this.selectedPorts['target'].port) updatePortLink.bind(this)(); this.requestUpdate(); }} > ${biPCICardNetwork} ${availablePorts.map((port) => { const portData = node.data('portData').get(port); return html` ${portData.get('Connection Type') === 'ethernet' ? biEthernet : biWifi} ${`MAC: ${portData.get('MAC').address}`} ${portData.get('Name') || port} `; })} ${availablePorts.length === 0 ? html`

${msg('No port available.')}

` : ''}
`; }; return html` this.requestUpdate()} >
${endpointTemplate(source, 'source', availableSourcePorts, edge.data('inPort'), msg('Source'))} ${endpointTemplate(target, 'target', availableTargetPorts, edge.data('outPort'), msg('Target'))}
`; } function networkConfigDrawerTemplate(this: NetworkComponent) { const network: any = this.selectedObject; return html` this.requestUpdate()} >
{ const value = (e.target as SlInput).value; const subnet: Net = this.selectedObject.data(); if (Ipv4Address.validateAddress(value, this.ipv4Database) == null) { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('IPv4 address is invalid.')); return; } else { (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } const success = subnet.handleChangesOnNewNetInfo(value, subnet.netmask, subnet.bitmask, this); if (success) { this.selectedObject.toggleClass('unconfigured-net', false); const name = this.selectedObject.data('name'); this.selectedObject.data('name', name); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('Invalid net id.')); } }} > { const value = (e.target as SlInput).value; const subnet: Net = this.selectedObject.data(); if (Ipv4Address.validateAddress(value, this.ipv4Database) == null) { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('IPv4 address is invalid.')); return; } else { (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } const success = subnet.handleChangesOnNewNetInfo( subnet.networkAddress.address, value, subnet.bitmask, this ); if (success) { this.selectedObject.toggleClass('unconfigured-net', false); const name = this.selectedObject.data('name'); this.selectedObject.data('name', name); const bitmask = ( AddressingHelper.decimalStringWithDotToBinary(value).match(new RegExp('1', 'g')) || [] ).length; this.shadowRoot?.querySelector('#bitmask')?.setAttribute('value', bitmask.toString()); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('Invalid netmask.')); } }} > { const value = parseInt((e.target as SlInput).value); const subnet: Net = this.selectedObject.data(); const netmask = AddressingHelper.binaryToDecimalOctets(''.padStart(value, '1').padEnd(32, '0')); const success = subnet.handleChangesOnNewNetInfo( subnet.networkAddress.address, netmask.join('.'), value, this ); this.shadowRoot?.querySelector('#netmask')?.setAttribute('value', netmask.join('.')); if (success) { this.selectedObject.toggleClass('unconfigured-net', false); const name = this.selectedObject.data('name'); this.selectedObject.data('name', name); (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('Invalid bitmask.')); } }} >
`; } function handleMacAddressChangeGenerator(this: NetworkComponent, index: string) { return function handleMacAddressChange(this: NetworkComponent, e: SlChangeEvent) { const v = (e.target as SlInput).value; const mac = MacAddress.validateAddress(v, this.macDatabase); if (mac != null) { MacAddress.removeAddressFromDatabase( this.selectedObject.data('portData').get(index).get('MAC'), this.macDatabase ); const portData = this.selectedObject.data('portData'); portData.get(index).set('MAC', mac); this.selectedObject.data('portData', portData); MacAddress.addAddressToDatabase(mac, this.macDatabase, this.selectedObject.id); (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('MAC address is invalid.')); } }; } function handleIPv4AddressChangeGenerator(this: NetworkComponent, index: string) { const subnet = this.selectedObject.isChild() ? this.selectedObject.parent().data() : null; const gateway: boolean = this.selectedObject.hasClass('gateway-node'); return function handleIPv4AddressChange(this: NetworkComponent, e: SlChangeEvent) { const v = (e.target as SlInput).value; const ipv4 = Ipv4Address.validateAddress(v, this.ipv4Database); if (ipv4 != null) { if (subnet != null && this.subnettingMode == 'HOST_BASED') { Net.calculateCIDRGivenNewHost(subnet, ipv4, this.ipv4Database, this); this.selectedObject.parent().classes(subnet.cssClass); } if (subnet != null && this.subnettingMode == 'NET_BASED' && !ipv4.matchesNetworkCidr(subnet)) { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg("Inserted IPv4 doesn't match the subnet mask.")); return; } if (gateway) { // console.log('gateway', this.selectedObject.data(), index); const affectedNetwork: Net | undefined = (this.selectedObject.data() as Router).portNetMapping.get( parseInt(index) ); if (this.subnettingMode == 'HOST_BASED' && affectedNetwork) { Net.calculateCIDRGivenNewHost(affectedNetwork, ipv4, this.ipv4Database, this); this._graph.$('#' + affectedNetwork.id).classes(affectedNetwork.cssClass); } if (this.subnettingMode == 'NET_BASED' && affectedNetwork && !ipv4.matchesNetworkCidr(affectedNetwork)) { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute( 'help-text', msg("Inserted IPv4 for gateway doesn't match the subnet mask or the network is not configured.") ); return; } } Ipv4Address.removeAddressFromDatabase( this.selectedObject.data('portData').get(index).get('IPv4'), this.ipv4Database ); const portData = this.selectedObject.data('portData'); portData.get(index).set('IPv4', ipv4); this.selectedObject.data('portData', portData); Ipv4Address.addAddressToDatabase(ipv4, this.ipv4Database, this.selectedObject.data('id')); (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('IPv4 address is invalid.')); } }; } function handleIPv6AddressChangeGenerator(this: NetworkComponent, index: string) { return function handleIPv6AddressChange(this: NetworkComponent, e: SlChangeEvent) { const v = (e.target as SlInput).value; const ipv6 = Ipv6Address.validateAddress(v, this.ipv6Database); if (ipv6 != null) { Ipv6Address.removeAddressFromDatabase( this.selectedObject.data('portData').get(index).get('IPv6'), this.ipv6Database ); const portData = this.selectedObject.data('portData'); portData.get(index).set('IPv6', ipv6); this.selectedObject.data('portData', portData); Ipv6Address.addAddressToDatabase(ipv6, this.ipv6Database, this.selectedObject.data('id')); (e.target as SlInput).classList.remove('danger'); (e.target as SlInput).classList.add('success'); (e.target as SlInput).setAttribute('help-text', ''); } else { (e.target as SlInput).classList.add('danger'); (e.target as SlInput).classList.remove('success'); (e.target as SlInput).setAttribute('help-text', msg('IPv6 address is invalid.')); } }; } function handlePortTypeChangeGenerator(this: NetworkComponent, index: string) { return function handlePortTypeChange(this: NetworkComponent, e: SlChangeEvent) { const v = (e.target as SlSelect).value; const portData = this.selectedObject.data('portData'); portData.get(index).set('Connection Type', v); this.selectedObject.data('portData', portData); }; } function addPort(this: NetworkComponent, node: any) { const portInfo = new Map(); if ( node.data('cssClass').includes('access-point-node') || node.data('cssClass').includes('switch-node') || node.data('cssClass').includes('bridge-node') ) { const mac = MacAddress.generateRandomAddress(this.macDatabase); portInfo.set('MAC', mac); } if (node.data('cssClass').includes('bridge-node') || node.data('cssClass').includes('repeater-node')) { portInfo.set('Connection Type', 'ethernet'); } if (node.data('cssClass').includes('router-node') || node.data('cssClass').includes('host-node')) { const mac = MacAddress.generateRandomAddress(this.macDatabase); const ipv4 = Ipv4Address.getLoopBackAddress(); const ipv6 = Ipv6Address.getLoopBackAddress(); const takenNames = new Set([...node.data('portData').values()].map((port: any) => port.get('Name'))); let nameNumber = node.data('portData').size + 1; while (takenNames.has('port-' + nameNumber)) nameNumber++; const name = 'port-' + nameNumber; const connectionType = 'ethernet'; portInfo.set('MAC', mac); portInfo.set('IPv4', ipv4); portInfo.set('IPv6', ipv6); portInfo.set('Name', name); portInfo.set('Connection Type', connectionType); } node.data('portData').set(node.data('portData').size + 1, portInfo); node.data('portLinkMapping').set(node.data('portLinkMapping').size + 1, null); node.data('numberOfInterfacesOrPorts', node.data('numberOfInterfacesOrPorts') + 1); this.requestUpdate(); } function removePort(this: NetworkComponent, node: any, index: string) { if (isPortConnected.bind(this)(node, index)) { AlertHelper.toastAlert('warning', 'exclamation-triangle', '', msg('Disconnect the port to remove it.')); return; } const ipv4 = node.data('portData').get(index).get('IPv4'); if (ipv4 != null) Ipv4Address.removeAddressFromDatabase(ipv4, this.ipv4Database); const ipv6 = node.data('portData').get(index).get('IPv6'); if (ipv6 != null) Ipv6Address.removeAddressFromDatabase(ipv6, this.ipv6Database); const mac = node.data('portData').get(index).get('MAC'); if (mac != null) MacAddress.removeAddressFromDatabase(mac, this.macDatabase); node.data('portData').delete(index); node.data('portLinkMapping').delete(index); node.data('portNetMapping')?.delete(index); compactPortNumbers.bind(this)(node); node.data('numberOfInterfacesOrPorts', node.data('portData').size); this.requestUpdate(); } function compactPortNumbers(this: NetworkComponent, node: any): void { const renumber = new Map(); let next = 1; for (const port of node.data('portData').keys()) renumber.set(Number(port), next++); if ([...renumber].every(([from, to]) => from === to)) return; const shifted = (port: any): number | undefined => { const to = renumber.get(Number(port)); return to !== undefined && to !== Number(port) ? to : undefined; }; const rekey = (map: Map | undefined) => { if (!map) return; const entries = [...map.entries()]; map.clear(); entries.forEach(([port, value]) => { const to = renumber.get(Number(port)); if (to !== undefined) map.set(to, value); }); }; //the port maps first: edge labels read them as soon as an edge's port number changes rekey(node.data('portData')); rekey(node.data('portLinkMapping')); rekey(node.data('portNetMapping')); //connections, through data() so the saved connections follow node.connectedEdges().forEach((edge: any) => { if (edge.source().same(node)) { const to = shifted(edge.data('inPort')); if (to !== undefined) edge.data('inPort', to); } if (edge.target().same(node)) { const to = shifted(edge.data('outPort')); if (to !== undefined) edge.data('outPort', to); } }); //gateway assignments referring to one of this router's ports const id = node.id(); this._graph.nodes('.net-node').forEach((net: any) => { const gateways: Map | undefined = net.data('gateways'); const gatewayTo = gateways?.has(id) ? shifted(gateways.get(id)) : undefined; if (gatewayTo !== undefined) gateways!.set(id, gatewayTo); const current = net.data('currentDefaultGateway'); const currentTo = current?.[0] === id ? shifted(current[1]) : undefined; if (currentTo !== undefined) net.data('currentDefaultGateway', [id, currentTo]); }); this._graph.nodes('.physical-node').forEach((other: any) => { const gateway = other.data('defaultGateway'); const to = gateway?.[0] === id ? shifted(gateway[1]) : undefined; if (to !== undefined) other.data('defaultGateway', [id, to]); }); } function updatePortLink(this: NetworkComponent) { const edge = this.selectedObject; const inPort = this.selectedPorts['source'].port || 0; const outPort = this.selectedPorts['target'].port || 0; const sourceNode = edge.source().data(); const targetNode = edge.target().data(); const newData = configurePorts.bind(this)(edge.data(), inPort, outPort); console.log(newData); if (newData != null) { edge.removeClass('unconfigured-edge'); edge.addClass(newData.cssClass); } //set new format-display for this connection if no error appears this.subnettingController.setUpGateway( this._graph.$('#' + sourceNode.id), this._graph.$('#' + targetNode.id), inPort, this.ipv4Database ); this.subnettingController.setUpGateway( this._graph.$('#' + targetNode.id), this._graph.$('#' + sourceNode.id), outPort, this.ipv4Database ); } function configurePorts(this: NetworkComponent, edge: GraphEdge, inPort: number, outPort: number): GraphEdge | null { let inPortData: Map = edge.from.portData.get(inPort)!; let outPortData: Map = edge.to.portData.get(outPort)!; if (inPortData.get('Connection Type') == 'wireless' && outPortData.get('Connection Type') == 'wireless') { edge.cssClass.push('wireless-edge'); } else if ( (inPortData.get('Connection Type') == 'wireless' && outPortData.get('Connection Type') == 'ethernet') || (inPortData.get('Connection Type') == 'ethernet' && outPortData.get('Connection Type') == 'wireless') ) { AlertHelper.toastAlert( 'danger', 'exclamation-triangle', msg('The connection type of assigned ports are not compatible!'), msg('Please re-assign your ports or dismiss this connection.') ); return null; } else { edge.cssClass.push('wired-edge'); } edge.cssClass.push('labelled-edge'); let index; if ((index = edge.cssClass.indexOf('unconfigured-edge')) > -1) edge.cssClass.splice(index, 1); console.log(edge); const nodeFrom = this._graph.getElementById(edge.source); const nodeTo = this._graph.getElementById(edge.target); const fromMap = new Map(nodeFrom.data('portLinkMapping')); const toMap = new Map(nodeTo.data('portLinkMapping')); fromMap.set(inPort, edge.id); toMap.set(outPort, edge.id); nodeFrom.data('portLinkMapping', fromMap); nodeTo.data('portLinkMapping', toMap); const netEdge = this._graph.getElementById(edge.id); netEdge.data('inPort', inPort); netEdge.data('outPort', outPort); return edge; //check if one node belongs to a net, if yes --> other node must be a router } function isPortConnected(this: NetworkComponent, node: any, port: string): boolean { //ask the edges: portLinkMapping holds '' for ports that were never linked return node.connectedEdges().some( (edge: any) => (edge.source().same(node) && Number(edge.data('inPort')) === Number(port)) || (edge.target().same(node) && Number(edge.data('outPort')) === Number(port)) ); } function nodeRoutingTableTemplate(this: NetworkComponent): TemplateResult { const node = this.selectedObject; const arpTable: Map | undefined = node.data('arpTableIpMac'); const routingTable: Map | undefined = node.data('routingTable'); const macAddressTable: Map | undefined = node.data('macAddressTable'); const tables: { name: string; label: string; columns: string[]; rows: (string | number)[][] }[] = []; if (arpTable?.size) { tables.push({ name: 'arp', label: msg('ARP Table'), columns: ['IP', 'MAC'], rows: Array.from(arpTable.entries()), }); } if (routingTable?.size) { tables.push({ name: 'routing', label: msg('Routing Table'), columns: [msg('Net'), msg('Gateway')], rows: Array.from(routingTable, ([destination, route]) => [destination, route.gateway]), }); } if (macAddressTable?.size) { tables.push({ name: 'mac', label: msg('Mac Address Table'), columns: ['MAC', msg('Port')], rows: Array.from(macAddressTable.entries()), }); } // A different node can expose different tabs. Recreate the group so its first tab is selected. return html`${keyed(node.id(), html` ${tables.map((table) => html`${table.label}`)} ${tables.map((table) => html` ${table.columns.map((column) => html``)} ${table.rows.map((row) => html`${row.map((value) => html``)}`)}
${column}
${value}
`)}
`)}`; }