@webwriter/network
Version:
Visualization of network topologies. Can represent different kinds of networks.
675 lines (627 loc) • 28.9 kB
text/typescript
import SlDetails from '@shoelace-style/shoelace/dist/components/details/details.component.js';
import SlDialog from '@shoelace-style/shoelace/dist/components/dialog/dialog.component.js';
import { html } from 'lit';
import { ComputerNetwork } from '../..';
import { Ipv4Address } from '../adressing/Ipv4Address';
import { Ipv6Address } from '../adressing/Ipv6Address';
import { MacAddress } from '../adressing/MacAddress';
import { RoutableDecorator } from '../components/dataDecorators/Routable';
import { SwitchableDecorator } from '../components/dataDecorators/Switchable';
import { GraphEdge } from '../components/GraphEdge';
import { Packet } from '../components/logicalNodes/DataNode';
import { Net } from '../components/logicalNodes/Net';
import { AccessPoint, Bridge, Hub, Repeater, Router, Switch } from '../components/physicalNodes/Connector';
import { Host } from '../components/physicalNodes/Host';
import { ConnectionType, PhysicalNode } from '../components/physicalNodes/PhysicalNode';
import { PacketSimulator } from '../event-handlers/packet-simulator';
import { initNetwork } from '../network-config';
import { RoutingData } from '../utils/routingData';
import { TableHelper } from '../utils/TableHelper';
class ExampleDescription {
id: number;
previewPath: string;
filePath: string;
helpText: string;
constructor(id: number, previewPath: string, filePath: string, helpText: string) {
this.id = id;
this.previewPath = previewPath;
this.filePath = filePath;
this.helpText = helpText;
}
}
export class ImportExportController {
static reader: FileReader = new FileReader();
static exportFile(network: ComputerNetwork): void {
if (!network.networkAvailable) return;
let data = {};
let physicalNodes = [];
let logicalNodes = [];
let edges = [];
network._graph.nodes('.physical-node').forEach((e) => {
let i = {};
i['dataExport'] = e.data();
i['dataExport']['cssClass'] = e.classes() as string[];
let portData = [];
let portLink = [];
let portNet = [];
let nets = [];
(e.data() as PhysicalNode).portData.forEach((values, port) => {
let newData = {};
newData['Name'] = values.get('Name');
newData['Connection Type'] = values.get('Connection Type');
newData['MAC'] = values.get('MAC');
newData['IPv4'] = values.get('IPv4');
newData['IPv6'] = values.get('IPv6');
newData['index'] = port;
portData.push(newData);
});
(e.data() as PhysicalNode).portLinkMapping.forEach((data, port) => {
let newData = {};
newData['index'] = port;
newData['linkId'] = data;
portLink.push(newData);
});
if (e.hasClass('gateway-node')) {
let gateway: Router = e.data();
if (gateway.portNetMapping.size != 0) {
gateway.portNetMapping.forEach((net, port) => {
let newData = {};
newData['index'] = port;
newData['netId'] = net.id;
portNet.push(newData);
});
}
if (gateway.nets.length != 0) {
gateway.nets.forEach((net) => {
let newData = {};
newData['netId'] = net.id;
nets.push(newData);
});
}
}
if (e.hasClass('decorated-node')) {
let newCss: string[] = (e.classes() as string[]).filter((css) => !css.includes('decorated'));
i['dataExport']['cssClass'] = newCss;
}
i['portData'] = portData;
i['portLink'] = portLink;
i['position'] = e.position();
i['portNet'] = portNet;
i['nets'] = nets;
physicalNodes.push(i);
});
network._graph.nodes('.net-node').forEach((e) => {
let i = {};
let gateways = [];
i['dataExport'] = e.data();
i['dataExport']['cssClass'] = e.classes() as string[];
i['position'] = e.position();
e.data('gateways').forEach((port, gatewayNodeId) => {
let e = {};
e['port'] = port;
e['gatewayNodeId'] = gatewayNodeId;
gateways.push(e);
});
i['gateways'] = gateways;
logicalNodes.push(i);
});
network._graph.edges().forEach((e) => {
let i = {};
i['dataExport'] = e.data();
i['dataExport']['cssClass'] = e.classes() as string[];
edges.push(i);
});
if (network.packetSimulator.inited) {
data['inited'] = true;
let switchableTables = [];
network._graph.nodes('.switchable-decorated').forEach((e) => {
let i = {};
let switchable: SwitchableDecorator = e.data();
i['id'] = switchable.id;
let table = [];
if (switchable.macAddressTable.size > 0) {
switchable.macAddressTable.forEach((port, mac) => {
let row = {};
row['port'] = port;
row['mac'] = mac;
table.push(row);
});
}
i['table'] = table;
switchableTables.push(i);
});
let routableTables = [];
network._graph.nodes('.routable-decorated').forEach((e) => {
let i = {};
let routable: RoutableDecorator = e.data();
i['id'] = routable.id;
let arpTable = [];
if (routable.arpTableIpMac.size > 0) {
routable.arpTableIpMac.forEach((mac, ip) => {
let row = {};
row['ip'] = ip;
row['mac'] = mac;
arpTable.push(row);
});
}
i['arpTable'] = arpTable;
let routingTable = [];
if (routable.routingTable.size > 0) {
routable.routingTable.forEach((routingData) => {
let row = {};
row['destination'] = routingData.destination;
row['gateway'] = routingData.gateway;
row['interfaceName'] = routingData.interfaceName;
row['bitmask'] = routingData.bitmask;
row['netmask'] = routingData.netmask;
row['port'] = routingData.port;
routingTable.push(row);
});
}
i['routingTable'] = routingTable;
routableTables.push(i);
});
data['switchable'] = switchableTables;
data['routable'] = routableTables;
}
data['physical-nodes'] = physicalNodes;
data['logical-nodes'] = logicalNodes;
data['edges'] = edges;
let myblob = new Blob([JSON.stringify(data)], {
type: 'application/json',
});
let url = URL.createObjectURL(myblob);
ImportExportController.download(url, 'network-graph-' + Date.now() + '.json');
}
static download(path, filename): void {
// Create a new link
const anchor = document.createElement('a');
anchor.href = path;
anchor.download = filename;
// Append to the DOM
document.body.appendChild(anchor);
// Trigger `click` event
anchor.click();
// Remove element from DOM
document.body.removeChild(anchor);
}
static importFile(network: ComputerNetwork): void {
const fileInput = network.renderRoot.querySelector('#import-file') as HTMLInputElement;
const selectedFile = fileInput.files[0];
ImportExportController.importSpecificFile(selectedFile, network);
}
static importSpecificFile(selectedFile: File, network: ComputerNetwork): void {
ImportExportController.reader.onloadend = async () => {
initNetwork(network);
network.ipv4Database = new Map();
network.macDatabase = new Map();
network.ipv6Database = new Map();
let json;
if (typeof ImportExportController.reader.result === 'string') {
json = JSON.parse(ImportExportController.reader.result);
}
json['logical-nodes'].forEach((net) => {
let ad: string = net['dataExport'].hasOwnProperty('networkAddress')
? net['dataExport']['networkAddress']['address']
: null;
let data: Net = new Net(
net['dataExport']['color'],
ad,
net['dataExport']['netmask'],
net['dataExport']['bitmask'],
network.ipv4Database,
net['dataExport']['id']
);
data.cssClass = net['dataExport']['cssClass'];
if (net['gateways'].length != 0) {
net['gateways'].forEach((p) => {
data.gateways.set(p['gatewayNodeId'], p['port']);
});
}
if (net['dataExport'].hasOwnProperty('parent')) {
data.parent = net['dataExport']['parent'];
network._graph.add({
group: 'nodes',
data: data,
classes: data.cssClass,
position: net['position'],
parent: network._graph.$('#' + net['dataExport']['parent']),
});
} else {
network._graph.add({
group: 'nodes',
data: data,
classes: data.cssClass,
position: net['position'],
});
}
});
json['physical-nodes'].forEach((element) => {
let data: PhysicalNode;
let cssClasses: string[] = element['dataExport']['cssClass'];
let nameMap: Map<number, string> = new Map();
let connectionMap: Map<number, ConnectionType> = new Map();
let macMap: Map<number, MacAddress> = new Map();
let ipv4Map: Map<number, Ipv4Address> = new Map();
let ipv6Map: Map<number, Ipv6Address> = new Map();
element['portData'].forEach((p) => {
nameMap.set(p['index'], p['Name']);
connectionMap.set(p['index'], p['Connection Type']);
if (p.hasOwnProperty('MAC')) {
let mac: MacAddress = MacAddress.validateAddress(p['MAC']['address'], network.macDatabase);
macMap.set(p['index'], mac);
MacAddress.addAddressToDatabase(mac, network.macDatabase, element['dataExport']['id']);
}
if (p.hasOwnProperty('IPv4')) {
let ip4: Ipv4Address = Ipv4Address.validateAddress(p['IPv4']['address'], network.ipv4Database);
ipv4Map.set(p['index'], ip4);
Ipv4Address.addAddressToDatabase(ip4, network.ipv4Database, element['dataExport']['id']);
}
if (p.hasOwnProperty('IPv6')) {
let ip6: Ipv6Address = Ipv6Address.validateAddress(p['IPv6']['address'], network.ipv6Database);
ipv6Map.set(p['index'], ip6);
Ipv6Address.addAddressToDatabase(ip6, network.ipv6Database, element['dataExport']['id']);
}
});
if (cssClasses.includes('router-node')) {
data = new Router(
element['dataExport']['color'],
element['dataExport']['numberOfInterfacesOrPorts'],
nameMap,
connectionMap,
macMap,
ipv4Map,
ipv6Map,
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('repeater-node')) {
data = new Repeater(
element['dataExport']['color'],
connectionMap,
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('hub-node')) {
data = new Hub(
element['dataExport']['color'],
element['dataExport']['numberOfInterfacesOrPorts'],
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('switch-node')) {
data = new Switch(
element['dataExport']['color'],
element['dataExport']['numberOfInterfacesOrPorts'],
macMap,
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('bridge-node')) {
data = new Bridge(
element['dataExport']['color'],
connectionMap,
macMap,
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('access-point-node')) {
data = new AccessPoint(
element['dataExport']['color'],
element['dataExport']['numberOfInterfacesOrPorts'],
macMap,
element['dataExport']['name'],
element['dataExport']['id']
);
} else if (cssClasses.includes('host-node')) {
data = new Host(
element['dataExport']['color'],
element['dataExport']['backgroundPath'].split('/')[7].split('.')[0],
element['dataExport']['numberOfInterfacesOrPorts'],
nameMap,
connectionMap,
macMap,
ipv4Map,
ipv6Map,
element['dataExport']['name'],
element['dataExport']['id']
);
}
element['portLink'].forEach((p) => {
data.portLinkMapping.set(p['index'], p['linkId']);
});
data.cssClass = cssClasses;
data.defaultGateway = element['dataExport']['defaultGateway'];
if (data instanceof Router && cssClasses.includes('gateway-node')) {
element['portNet'].forEach((p) => {
(data as Router).portNetMapping.set(
p['index'],
network._graph.$('#' + p['netId']).data() as Net
);
});
element['nets'].forEach((p) => {
(data as Router).nets.push(network._graph.$('#' + p['netId']).data() as Net);
});
}
if (element['dataExport'].hasOwnProperty('parent')) {
data.parent = element['dataExport']['parent'];
network._graph.add({
group: 'nodes',
data: data,
classes: data.cssClass,
position: element['position'],
parent: network._graph.$('#' + element['dataExport']['parent']),
});
} else {
network._graph.add({
group: 'nodes',
data: data,
classes: element['dataExport']['cssClass'],
position: element['position'],
});
}
});
json['edges'].forEach((edge) => {
let graphEdge: GraphEdge = new GraphEdge(
edge['dataExport']['color'],
network._graph.$('#' + edge['dataExport']['source']).data() as PhysicalNode,
network._graph.$('#' + edge['dataExport']['target']).data() as PhysicalNode
);
GraphEdge.addPorts(graphEdge, edge['dataExport']['inPort'], edge['dataExport']['outPort']);
graphEdge.cssClass = edge['dataExport']['cssClass'];
network._graph.add({
group: 'edges',
data: graphEdge,
classes: graphEdge.cssClass,
});
});
if (json.hasOwnProperty('inited')) {
network.packetSimulator.inited = false;
network.packetSimulator.initSession(network);
} else {
(network.renderRoot.querySelector('#tables-for-packet-simulator') as SlDetails).innerHTML = '';
}
if (json.hasOwnProperty('switchable')) {
json['switchable'].forEach((element) => {
let rows: any[] = element['table'];
let map: Map<string, number> = (network._graph.$('#' + element['id']).data() as SwitchableDecorator)
.macAddressTable;
rows.forEach((row) => {
map.set(row['mac'], +row['port']);
TableHelper.addRow('mac-address-table-' + element['id'], 'MacAddressTable', network, [
row['port'],
row['mac'],
]);
});
});
}
if (json.hasOwnProperty('routable')) {
json['routable'].forEach((element) => {
let routingRows: any[] = element['routingTable'];
let routingMap: Map<string, RoutingData> = (
network._graph.$('#' + element['id']).data() as RoutableDecorator
).routingTable;
routingRows.forEach((row) => {
routingMap.set(
row['destination'],
new RoutingData(
row['destination'],
row['gateway'],
+row['bitmask'],
row['interfaceName'],
+row['port']
)
);
TableHelper.addRow('routing-table-' + element['id'], 'RoutingTable', network, [
row['destination'],
row['gateway'],
+row['bitmask'],
+row['port'],
]);
});
let arpRows: any[] = element['arpTable'];
let arpMapIpMac: Map<string, string> = (
network._graph.$('#' + element['id']).data() as RoutableDecorator
).arpTableIpMac;
let arpMapMacIp: Map<string, string> = (
network._graph.$('#' + element['id']).data() as RoutableDecorator
).arpTableMacIp;
arpRows.forEach((row) => {
arpMapIpMac.set(row['ip'], row['mac']);
arpMapMacIp.set(row['mac'], row['ip']);
TableHelper.addRow('arp-table-' + element['id'], 'ArpTable', network, [row['ip'], row['mac']]);
});
});
}
};
ImportExportController.reader.readAsText(selectedFile, 'UTF-8');
}
static cidrs: Map<string, ExampleDescription> = new Map<string, ExampleDescription>([
[
'Example 1',
new ExampleDescription(
1,
'resources/preview-examples/CIDR-and-classful-drag&drop--then-check.png',
'resources/examples/CIDR-and-classful-drag&drop--then-check.json',
'An exercise for CIDR from classful networks: drag and drop then check.'
),
],
[
'Example 2',
new ExampleDescription(
2,
'resources/preview-examples/CIDR-fill-in-the-blank.png',
'resources/examples/CIDR-fill-in-the-blank.json',
'An exercise for CIDR from classful networks: configure the classful networks then check.'
),
],
[
'Example 3',
new ExampleDescription(
3,
'resources/preview-examples/subnetting-fill-in-the-blank.png',
'resources/examples/subnetting-fill-in-the-blank.json',
'An exercise for subnetting from a classful network: configure the subnets then check.'
),
],
[
'Example 4',
new ExampleDescription(
4,
'resources/preview-examples/cidr-subnetting-drag&drop.png',
'resources/examples/cidr-subnetting-drag&drop.json',
'A mixed exercise of CIDR and subnetting: drag and drop then check.'
),
],
]); //id, example-info
static simulations: Map<string, ExampleDescription> = new Map<string, ExampleDescription>([
[
'Example 1',
new ExampleDescription(
1,
'resources/preview-examples/repeater-simulation.png',
'resources/examples/repeater-simulation.json',
'Ethernet with a repeater and 2 hosts.'
),
],
[
'Example 2',
new ExampleDescription(
2,
'resources/preview-examples/hub-simulation.png',
'resources/examples/hub-simulation.json',
'Ethernet with a hub and 3 hosts.'
),
],
[
'Example 3',
new ExampleDescription(
3,
'resources/preview-examples/bridge-simulation.png',
'resources/examples/bridge-simulation.json',
'Ethernet with a bridge and 2 hosts.'
),
],
[
'Example 4',
new ExampleDescription(
4,
'resources/preview-examples/switch-simulation.png',
'resources/examples/switch-simulation.json',
'Ethernet with a switch and 3 hosts.'
),
],
[
'Example 5',
new ExampleDescription(
5,
'resources/preview-examples/accesspoint-simulation.png',
'resources/examples/accesspoint-simulation.json',
'Wifi network with 1 access point, 3 hosts.'
),
],
[
'Example 6',
new ExampleDescription(
6,
'resources/preview-examples/gateway-2ethernet.png',
'resources/examples/gateway-2ethernet.json',
'2 Ethernets with a gateway.'
),
],
[
'Example 7',
new ExampleDescription(
7,
'resources/preview-examples/gateway-2net-wireless-ethernet.png',
'resources/examples/gateway-2net-wireless-ethernet.json',
'An ethernet, a wireless network and a gateway.'
),
],
[
'Example 8',
new ExampleDescription(
8,
'resources/preview-examples/gateway-3ethernet.png',
'resources/examples/gateway-3ethernet.json',
'3 Ethernets and a gateway.'
),
],
[
'Example 9',
new ExampleDescription(
9,
'resources/preview-examples/3gateways - next gateway routes.png',
'resources/examples/3gateways - next gateway routes.json',
'2 Ethernets and 3 routers.'
),
],
[
'Example 10',
new ExampleDescription(
10,
'resources/preview-examples/4 routers-2 nets.png',
'resources/examples/4 routers-2 nets.json',
'2 Ethernets and 4 routers.'
),
],
]); //id, example-info
static openExample(filePath: string, network: ComputerNetwork) {
console.log('open Example');
fetch(filePath).then((response) => {
response.blob().then((blob) => {
const file = new File([blob], 'temporal.json', { type: blob.type });
console.log(file);
ImportExportController.importSpecificFile(file, network);
});
});
(network.renderRoot.querySelector('#example-graphs') as SlDialog).hide();
}
static exampleTemplate(network: ComputerNetwork) {
let cidrExamples = [];
let simulationExamples = [];
ImportExportController.cidrs.forEach((value, name) => {
cidrExamples.push(html`
<sl-card class="card-overview">
<img slot="image" src=${value.previewPath} alt=${name} />
${value.helpText}
<div slot="footer">
<sl-button pill @click="${() => ImportExportController.openExample(value.filePath, network)}"
>Open</sl-button
>
</div>
</sl-card>
`);
});
ImportExportController.simulations.forEach((value, name) => {
simulationExamples.push(html`
<sl-card class="card-overview">
<img slot="image" src=${value.previewPath} alt=${name} />
${value.helpText}
<div slot="footer">
<sl-button pill @click="${() => ImportExportController.openExample(value.filePath, network)}"
>Open</sl-button
>
</div>
</sl-card>
`);
});
return html`
<sl-tab-group>
<sl-tab slot="nav" panel="cidr">Example exercises for CIDR/ Subnetting</sl-tab>
<sl-tab slot="nav" panel="simulation">Example networks for simulations</sl-tab>
<sl-tab-panel name="cidr"
><b>Tip</b>: Use the <b>CIDR/Subnetting controller</b> to <b>drag-and-drop</b> then <b>check</b>.
<b>Right-click</b> on each network, then select <b>Edit details</b> to change its configuration.
<br /><br />
${cidrExamples}</sl-tab-panel
>
<sl-tab-panel name="simulation"
><b>Tip</b>: Set <b>source</b> and <b>destination</b> then click on <b>play</b> button in
<b>Packet sending controller</b> to start simulation <br /><br />
${simulationExamples}</sl-tab-panel
>
</sl-tab-group>
`;
}
}