import { css, html, LitElement, PropertyValueMap, TemplateResult } from 'lit'; import { LitElementWw } from '@webwriter/lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { networkStyles } from './styles/network'; import { toolboxStyles } from './styles/toolbox'; import { contextMenuStyles } from './styles/contextmenu'; import { simulationMenuStyles } from './styles/simulationmenu'; import { GraphNodeFactory } from './event-handlers/component-manipulation'; import { EdgeController } from './event-handlers/edge-controller'; import { DialogFactory } from './event-handlers/dialog-content'; import { SubnettingController } from './event-handlers/subnetting-controller'; import { Net, SubnettingMode } from './components/logicalNodes/Net'; import { PacketSimulator } from './event-handlers/packet-simulator'; import { ImportExportController } from './exporting/importExportController'; import { biBoxes, biBroadcastPin, biCloudArrowUp, biCloudCheck, biCloudPlus, biDiagram3, biHdd, biPcDisplayHorizontal, biPencil, biPerson, biPhone, biRouter, biShare, biTrash, faPlus, iBridge, iHub, iSwitch, } from './styles/icons'; import 'cytoscape-context-menus/cytoscape-context-menus.css'; import { initNetwork } from './network-config'; import { EventObject } from 'cytoscape'; import { contextMenuTemplate } from './ui/ContextMenu'; import '@shoelace-style/shoelace/dist/themes/light.css'; import SlButton from '@shoelace-style/shoelace/dist/components/button/button.component.js'; import SlDetails from '@shoelace-style/shoelace/dist/components/details/details.component.js'; import SlInput from '@shoelace-style/shoelace/dist/components/input/input.component.js'; import SlCheckbox from '@shoelace-style/shoelace/dist/components/checkbox/checkbox.component.js'; import SlTooltip from '@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js'; import SlButtonGroup from '@shoelace-style/shoelace/dist/components/button-group/button-group.component.js'; import SlAlert from '@shoelace-style/shoelace/dist/components/alert/alert.component.js'; import SlSelect from '@shoelace-style/shoelace/dist/components/select/select.component.js'; import SlOption from '@shoelace-style/shoelace/dist/components/option/option.component.js'; import SlDialog from '@shoelace-style/shoelace/dist/components/dialog/dialog.component.js'; import SlColorPicker from '@shoelace-style/shoelace/dist/components/color-picker/color-picker.component.js'; import SlPopup from '@shoelace-style/shoelace/dist/components/popup/popup.component.js'; import SlTabGroup from '@shoelace-style/shoelace/dist/components/tab-group/tab-group.component.js'; import SlTab from '@shoelace-style/shoelace/dist/components/tab/tab.component.js'; import SlTabPanel from '@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.component.js'; import { MacAddress } from './adressing/MacAddress'; import { simulationMenuTemplate } from './ui/SimulationMenu'; import { Component, Connection, load, Network, setupListeners } from './utils/setup'; import { SlChangeEvent } from '@shoelace-style/shoelace'; @customElement('ww-network') export class NetworkComponent extends LitElementWw { @query('#cy') accessor _cy: any; _graph: any; currentComponentToAdd: string = ''; currentColor: string = 'white'; colors = [ 'Plum', '#BAADD1', '#9CB6D6', '#9DCBD1', 'LightSeaGreen', '#5FCCAB', '#ADE07A', '#E2E379', 'Tomato', '#FFA6B4', '#FF938B', '#FFA07A', '#8A8A8A', '#A6A6A6', '#D4B6A0', '#C29C8D', ]; networkAvailable: Boolean = false; _edgeHandles: any; //controller for edgehandles extension drawModeOn: boolean = false; _menu: any; //controller for menu extension _cdnd: any; //controller for drag-and-drop compound nodes extension resetColorModeOn: boolean = false; ipv4Database: Map = new Map(); //(address, nodeId) macDatabase: Map = new Map(); ipv6Database: Map = new Map(); @state() accessor packetSimulator: PacketSimulator = new PacketSimulator(this); @state() accessor subnettingController: SubnettingController = new SubnettingController(this); @property({ type: Boolean, reflect: true }) accessor automate: boolean = false; @property({ type: String, reflect: true }) accessor screen: 'small' | 'medium' = 'medium'; //small/medium @property({ type: Object }) accessor selectedObject: any; @query('#toolboxButtons') accessor toolboxButtons!: HTMLElement; @query('#contextMenu') accessor contextMenu!: HTMLElement; @state() accessor selectedPorts: { source: { connectionType: 'ethernet' | 'wireless' | null; port: number | null; }; target: { connectionType: 'ethernet' | 'wireless' | null; port: number | null; }; } = { source: { connectionType: null, port: 0 }, target: { connectionType: null, port: 0 }, }; @state() accessor mutexDragAndDrop: string | null = null; @property({ type: Array, reflect: true, attribute: true }) accessor componets: Array = []; @property({ type: Array, reflect: true, attribute: true }) accessor connections: Array = []; @property({ type: Array, reflect: true, attribute: true }) accessor networks: Array = []; @state() accessor mode: 'edit' | 'simulate' = 'edit'; @state() accessor subnettingMode: SubnettingMode = 'MANUAL'; /* Previously Static Variables */ net_mode: SubnettingMode = 'MANUAL'; public static get styles() { return [networkStyles, toolboxStyles, contextMenuStyles, simulationMenuStyles]; } static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }; public static get scopedElements() { return { 'sl-button': SlButton, 'sl-button-group': SlButtonGroup, 'sl-details': SlDetails, 'sl-input': SlInput, 'sl-checkbox': SlCheckbox, 'sl-tooltip': SlTooltip, 'sl-alert': SlAlert, 'sl-select': SlSelect, 'sl-option': SlOption, 'sl-dialog': SlDialog, 'sl-color-picker': SlColorPicker, 'sl-popup': SlPopup, 'sl-tab-group': SlTabGroup, 'sl-tab': SlTab, 'sl-tab-panel': SlTabPanel, }; } public isEditable(): boolean { return this.contentEditable === 'true' || this.contentEditable === ''; } protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); initNetwork(this); this._graph.on('cxttap', (event: EventObject) => { event.preventDefault(); if (event.target === this._graph) { this.contextMenu.style.display = 'none'; return; } this.selectedObject = event.target; if (!this.selectedObject.isNode()) { const edge = this.selectedObject.data(); this.selectedPorts = { source: { connectionType: null, port: 0 }, target: { connectionType: null, port: 0 }, }; if (edge.inPort != undefined && edge.inPort != null && !Number.isNaN(edge.inPort)) { this.selectedPorts.source.port = edge.inPort; this.selectedPorts.source.connectionType = edge.from.portData .get(edge.inPort) .get('Connection Type'); } if (edge.outPort != undefined && edge.outPort != null && !Number.isNaN(edge.outPort)) { this.selectedPorts.target.port = edge.outPort; this.selectedPorts.target.connectionType = edge.to.portData .get(edge.outPort) .get('Connection Type'); } } console.log(this.selectedObject.data()); this.contextMenu.style.display = 'block'; this.contextMenu.style.left = event.renderedPosition.x + 'px'; this.contextMenu.style.top = event.renderedPosition.y + 'px'; }); this._graph.on('tap', (event: EventObject) => { const t = this.selectedObject; this.selectedObject = null; this.selectedObject = t; this.contextMenu.style.display = 'none'; }); this._graph.on('drag', (event: EventObject) => { const t = this.selectedObject; this.selectedObject = null; this.selectedObject = t; this.contextMenu.style.display = 'none'; }); load.bind(this)(); setupListeners.bind(this)(); } public render(): TemplateResult { return html` ${this.isEditable() ? this.asideTemplate() : null}
{ const mode = (event.target as HTMLSelectElement).value as 'edit' | 'simulate'; if (mode === 'edit') { const components = [...this.componets]; const connections = [...this.connections]; const networks = [...this.networks]; this.ipv4Database = new Map(); //(address, nodeId) this.macDatabase = new Map(); this.ipv6Database = new Map(); console.log(components, connections, networks); this._graph.elements().remove(); this.componets = components; this.connections = connections; this.networks = networks; load.bind(this)(); } else { this._graph.$('node').lock(); this.packetSimulator.initSession(this); } this.mode = mode; }} size="small" > ${this.mode === 'edit' ? biPencil : biBoxes} Edit Simulate
${this.toolboxTemplate()} ${contextMenuTemplate.bind(this)()} ${simulationMenuTemplate.bind(this)()}
`; } private toolboxTemplate(): TemplateResult { return html`
this.openToolbox()}> ${faPlus}
${biPerson}
${biPcDisplayHorizontal} ${biPhone}
${biHdd}
${biRouter} ${biBroadcastPin} ${biHdd} ${iHub} ${iBridge} ${iSwitch}
${biShare}
${biDiagram3}
${biCloudPlus} ${biCloudArrowUp} ${biCloudCheck}
`; } private openToolbox(): void { this.toolboxButtons.classList.toggle('closed'); } private addHost() { return { computer: () => { GraphNodeFactory.addNode(this, { componentType: 'computer', interfaces: [ { name: 'eth0', connectionType: 'ethernet', mac: MacAddress.generateRandomAddress(this.macDatabase).address, ipv4: '192.168.20.1', ipv6: '0:0:0:0:0:0:0:1', }, ], }); }, mobile: () => { GraphNodeFactory.addNode(this, { componentType: 'mobile', interfaces: [ { name: 'eth0', connectionType: 'wireless', mac: MacAddress.generateRandomAddress(this.macDatabase).address, ipv4: '192.168.20.1', ipv6: '0:0:0:0:0:0:0:1', }, ], }); }, }; } private addNetworkDevice() { return { router: () => { GraphNodeFactory.addNode(this, { componentType: 'router', interfaces: [], }); }, accessPoint: () => { GraphNodeFactory.addNode(this, { componentType: 'access-point', interfaces: [ { mac: MacAddress.generateRandomAddress(this.macDatabase).address }, { mac: MacAddress.generateRandomAddress(this.macDatabase).address }, ], }); }, repeater: () => { GraphNodeFactory.addNode(this, { componentType: 'repeater', interfaces: [{ connectionType: 'ethernet' }, { connectionType: 'ethernet' }], }); }, hub: () => { GraphNodeFactory.addNode(this, { componentType: 'hub', interfaces: [], }); }, bridge: () => { GraphNodeFactory.addNode(this, { componentType: 'bridge', interfaces: [ { connectionType: 'ethernet', mac: MacAddress.generateRandomAddress(this.macDatabase).address }, { connectionType: 'ethernet', mac: MacAddress.generateRandomAddress(this.macDatabase).address }, ], }); }, switch: () => { GraphNodeFactory.addNode(this, { componentType: 'switch', interfaces: [ { mac: MacAddress.generateRandomAddress(this.macDatabase).address }, { mac: MacAddress.generateRandomAddress(this.macDatabase).address }, ], }); }, }; } private addEdge() {} private addNetwork() { GraphNodeFactory.addNode(this, { componentType: 'net', net: { netid: '1.1.1.0', netmask: '255.255.255.0', bitmask: 24, }, }); } private asideTemplate(): TemplateResult { return html` `; } private clickOnComponentButton(e: Event): void { this.currentComponentToAdd = (e.target as HTMLElement).getAttribute('id'); let nodeToHighLight: string = ''; let panelToActive: string = ''; switch (this.currentComponentToAdd) { case 'computer': case 'mobile': nodeToHighLight = 'host'; panelToActive = 'physical'; break; case 'router': case 'access-point': case 'hub': case 'repeater': case 'bridge': case 'switch': nodeToHighLight = 'connector'; panelToActive = 'physical'; break; case 'net': nodeToHighLight = 'net'; panelToActive = 'logical'; default: nodeToHighLight = this.currentComponentToAdd; break; } this.renderRoot.querySelectorAll('.btn').forEach((e) => { if (e.id == nodeToHighLight) { //highlight the chosen component (e as HTMLElement).style.border = 'solid 2px #404040'; } else { //un-highlight other components (e as HTMLElement).style.border = 'solid 1px transparent'; } }); if (panelToActive != '') { (this.renderRoot.querySelector('#physical-logical-group') as SlTabGroup).show(panelToActive); } } updated(changedProperties: Map) { if (changedProperties.has('contentEditable')) { // new value is const newValue = this.isEditable(); if (newValue) { if (this.networkAvailable) this._graph.elements().toggleClass('deletable', true); ['host', 'connector', 'edge', 'net', 'addCompBtn', 'drawBtn'].forEach((buttonId) => { if (this.renderRoot.querySelector('#' + buttonId)) (this.renderRoot.querySelector('#' + buttonId) as HTMLButtonElement).disabled = false; }); } else { if (this.networkAvailable) this._graph.elements().toggleClass('deletable', false); ['host', 'connector', 'edge', 'net', 'addCompBtn', 'drawBtn'].forEach((buttonId) => { if (this.renderRoot.querySelector('#' + buttonId)) (this.renderRoot.querySelector('#' + buttonId) as HTMLButtonElement).disabled = true; }); } } if (changedProperties.has('automate')) { // new value is const newValue = this.automate; if (newValue) { (this.renderRoot.querySelector('#current-subnet-mode') as SlSelect).disabled = false; } else { (this.renderRoot.querySelector('#current-subnet-mode') as SlSelect).value = 'MANUAL'; (this.renderRoot.querySelector('#current-subnet-mode') as SlSelect).disabled = true; Net.setMode('MANUAL', this); } } } }