@webwriter/network
Version:
Visualization of network topologies. Can represent different kinds of networks.
865 lines (803 loc) • 42 kB
text/typescript
import { SlInput, SlDialog, SlButton, SlTabGroup, SlTabPanel, SlSelect } from '@shoelace-style/shoelace';
import { html, TemplateResult } from 'lit';
import { ComputerNetwork } from '../..';
import { Address } from '../adressing/Address';
import { Ipv4Address } from '../adressing/Ipv4Address';
import { Ipv6Address } from '../adressing/Ipv6Address';
import { MacAddress } from '../adressing/MacAddress';
import { GraphEdge } from '../components/GraphEdge';
import { Data, Packet, Frame } from '../components/logicalNodes/DataNode';
import { Net } from '../components/logicalNodes/Net';
import { Router } from '../components/physicalNodes/Connector';
import { PhysicalNode } from '../components/physicalNodes/PhysicalNode';
import { AlertHelper } from '../utils/AlertHelper';
import { SubnettingController } from './subnetting-controller';
import { msg } from '@lit/localize';
export class DialogFactory {
static generateInputsDetailsForNode(network: ComputerNetwork): void {
let currentComponentToAdd = network.currentComponentToAdd;
if (currentComponentToAdd == '') {
return;
}
let numberOfPortsOrInterfaces: number = (network.renderRoot.querySelector('#ports') as SlInput).valueAsNumber
? (network.renderRoot.querySelector('#ports') as SlInput).valueAsNumber
: currentComponentToAdd == 'computer' || currentComponentToAdd == 'mobile'
? 1
: 2;
let layer: number = 0;
let portNum: number = 0;
switch (currentComponentToAdd) {
case 'computer':
case 'mobile':
case 'router':
layer = 3; //add IPv4, IPv6
break;
case 'access-point':
case 'bridge':
case 'switch':
layer = 2; //add MAC
break;
case 'hub':
case 'repeater':
layer = 1; //add connection type
break;
default:
break;
}
if (
currentComponentToAdd == 'repeater' ||
currentComponentToAdd == 'bridge' ||
numberOfPortsOrInterfaces == null ||
numberOfPortsOrInterfaces == undefined
) {
portNum = 2;
} else {
portNum = numberOfPortsOrInterfaces;
}
let dialog = new SlDialog();
dialog.label = 'Add details for each port of your ' + currentComponentToAdd;
let table = `<table>`;
//add the columns
table += `<tr>`;
table += `<td>${msg('Port number')}</td>`;
table += layer > 2 ? `<td>${msg('Interface name')}</td>` : ``;
table += `<td>${msg('Connection type')}</td>`;
table += layer > 1 ? `<td>${msg('MAC Address')}</td>` : '';
table += layer > 2 ? `<td>Ipv4</td><td>Ipv6</td>` : '';
table += `</tr>`;
//add row for each port
for (let i = 1; i <= portNum; i++) {
table += `<tr>`;
table += `<td>` + i + `</td>`;
table +=
layer > 2
? `<td><sl-input id="interface-name-` +
i +
`" placeholder="Interface name" clearable></sl-input></td>`
: ``;
switch (currentComponentToAdd) {
case 'hub':
case 'switch':
table += `<td>${msg('Ethernet')}</td>`;
break;
case 'access-point':
table += i == 1 ? `<td>${msg('Ethernet')}</td>` : `<td>${msg('Wireless')}</td>`;
break;
default:
table +=
`<td><sl-select id="connection-type-` +
i +
`"><sl-menu-item value="ethernet" style="overflow: hidden;">${msg('Ethernet')}</sl-menu-item>
<sl-menu-item value="wireless" style="overflow: hidden;">${msg('Wireless')}</sl-menu-item></sl-select></td>`;
break;
}
table +=
layer > 1
? `<td><sl-input id="mac-` + i + `" placeholder="FF:FF:FF:FF:FF:FF" clearable></sl-input></td>`
: '';
table +=
layer > 2 ? `<td><sl-input id="ip4-` + i + `" placeholder="0:0:0:0" clearable></sl-input></td>` : '';
table +=
layer > 2
? `<td><sl-input id="ip6-` +
i +
`"placeholder="FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF" clearable></sl-input></td>`
: '';
table += `</tr>`;
}
table += `</table>`;
dialog.innerHTML = table;
//TODO: add event listener vào cái nút add node
const saveButton = new SlButton();
saveButton.slot = 'footer';
saveButton.variant = 'primary';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => dialog.hide());
dialog.appendChild(saveButton);
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static generateInputsDetailsForEdge(
network: ComputerNetwork,
edge: any,
sourceNode: PhysicalNode,
targetNode: PhysicalNode
): void {
//filter available ports
let availableSourcePorts: number[] = [];
let availableTargetPorts: number[] = [];
sourceNode.portLinkMapping.forEach((link, port) => {
if (link == null || link == undefined || link == '') {
availableSourcePorts.push(port);
}
});
targetNode.portLinkMapping.forEach((link, port) => {
if (link == null || link == undefined || link == '') {
availableTargetPorts.push(port);
}
});
let dialog = new SlDialog();
dialog.label = msg('Assigning ports for this connection');
let tabGroup: SlTabGroup = new SlTabGroup();
tabGroup.innerHTML +=
`<sl-tab slot="nav" panel="chooseSourcePort">` +
sourceNode.name +
`</sl-tab><sl-tab slot="nav" panel="chooseTargetPort">` +
targetNode.name +
`</sl-tab>`;
//init panel with data of ports of source + select port for source
let sourcePanel = new SlTabPanel();
sourcePanel.name = 'chooseSourcePort';
let sourceTable: string = `<table cellspacing="10"><tr>`;
sourceTable += `<td>${msg('Port number')}</td>`;
sourceNode.portData
.entries()
.next()
.value[1].forEach((_, columnName) => (sourceTable += `<td>` + columnName + `</td>`));
sourceTable += `</tr>`;
sourceNode.portData.forEach((data, port) => {
sourceTable += `<tr>`;
sourceTable += `<td>` + port + `</td>`; //add index
data.forEach((value) => {
if (value instanceof Address) {
sourceTable += `<td>` + value.address + `</td>`;
} else {
sourceTable += `<td>` + value + `</td>`;
}
});
sourceTable += `</tr>`;
});
sourceTable += `</table>`;
sourcePanel.innerHTML += sourceTable;
let selectedSourcePort = new SlSelect();
availableSourcePorts.forEach(
(port) => (selectedSourcePort.innerHTML += `<sl-menu-item value="` + port + `">` + port + `</sl-menu-item>`)
);
sourcePanel.innerHTML += msg('Select one from available ports:');
sourcePanel.appendChild(selectedSourcePort);
tabGroup.append(sourcePanel);
//init panel with data of ports of target + select port for target
let targetPanel = new SlTabPanel();
targetPanel.name = 'chooseTargetPort';
let targetTable: string = `<table cellspacing="10"><tr>`;
targetTable += `<td>${msg('Port number')}</td>`;
targetNode.portData
.entries()
.next()
.value[1].forEach((_, columnName) => (targetTable += `<td>` + columnName + `</td>`));
targetTable += `</tr>`;
targetNode.portData.forEach((data, index) => {
targetTable += `<tr>`;
targetTable += `<td>` + index + `</td>`; //add port/interface name
data.forEach((value) => {
if (value instanceof Address) {
targetTable += `<td>` + value.address + `</td>`;
} else {
targetTable += `<td>` + value + `</td>`;
}
});
targetTable += `</tr>`;
});
targetTable += `</table>`;
targetPanel.innerHTML += targetTable;
let selectedTargetPort = new SlSelect();
availableTargetPorts.forEach(
(port) => (selectedTargetPort.innerHTML += `<sl-menu-item value="` + port + `">` + port + `</sl-menu-item>`)
);
targetPanel.innerHTML += msg('Select one from available ports:');
targetPanel.appendChild(selectedTargetPort);
tabGroup.append(targetPanel);
dialog.appendChild(tabGroup);
const saveButton = new SlButton();
saveButton.slot = 'footer';
saveButton.variant = 'primary';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
let inPort: number = +(selectedSourcePort.value as string);
let outPort: number = +(selectedTargetPort.value as string);
if (Number.isNaN(inPort) || inPort == undefined || inPort == null) {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
'',
msg('Please choose port/interface for') + ' ' + sourceNode.name
);
return;
}
if (Number.isNaN(outPort) || outPort == undefined || outPort == null) {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
'',
msg('Please choose port/interface for') + ' ' + sourceNode.name
);
return;
}
let newData = GraphEdge.addPorts(edge.data(), inPort, outPort); //add port-link mapping for source+target
if (newData != null) {
edge.removeClass('unconfigured-edge');
edge.addClass(newData.cssClass);
dialog.hide();
} //set new format-display for this connection if no error appears
SubnettingController.setUpGateway(
network._graph.$('#' + sourceNode.id),
network._graph.$('#' + targetNode.id),
inPort,
network.ipv4Database
);
SubnettingController.setUpGateway(
network._graph.$('#' + targetNode.id),
network._graph.$('#' + sourceNode.id),
outPort,
network.ipv4Database
);
});
dialog.appendChild(saveButton);
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static handleChangesInDialogForPhysicalNode(
id: string,
node: any,
network: ComputerNetwork,
isGateway: boolean,
subnet?: Net
) {
let physicalNode: PhysicalNode = node.data();
let dialog: SlDialog = new SlDialog();
dialog.label = msg('Details about component') + ' ' + physicalNode.name;
dialog.innerHTML +=
`<sl-input style="color: #43628A;"label="Name" id="` +
id +
`-name` +
`" value="` +
physicalNode.name +
`" clearable type="string">`;
let table: string = `<div style="margin-top: 10px; color: #43628A;">${msg('Details of the ports')}</div><table cellspacing="10"><tr>`;
table += `<td>Index</td>`;
physicalNode.portData
.entries()
.next()
.value[1].forEach((_, columnName) => (table += `<td>` + columnName + `</td>`));
table += `</tr>`;
physicalNode.portData.forEach((data, index) => {
table += `<tr>`;
table += `<td>` + index + `</td>`; //add index
data.forEach((value, key) => {
if (value instanceof Address) {
table +=
`<td><sl-input id="` +
id +
'-' +
index +
'-' +
key +
`" placeholder="` +
value.address +
`" clearable type="string"></td>`;
} else if (key == 'Connection Type') {
//show only
table += `<td>` + value + `</td>`;
} else {
table +=
`<td><sl-input id="` +
id +
'-' +
index +
'-' +
key +
`" placeholder="` +
value +
`" clearable type="string"></td>`;
}
});
table += `</tr>`;
});
table += `</table>`;
dialog.innerHTML += table;
const saveButton = new SlButton();
saveButton.slot = 'footer';
saveButton.variant = 'primary';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
let changed: boolean = false;
let newNodeName: string = (network.renderRoot.querySelector('#' + id + '-name') as SlInput).value.trim();
if (newNodeName != physicalNode.name) physicalNode.name = newNodeName;
//for each interface-index
for (let index = 1; index <= physicalNode.portData.size; index++) {
let nameInput = network.renderRoot.querySelector('#' + id + '-' + index + '-' + 'Name') as SlInput;
let newName = nameInput.value.trim() != '' ? nameInput.value.trim() : nameInput.placeholder;
if (newName != '') {
physicalNode.portData.get(index).set('Name', newName);
changed = true;
}
if (physicalNode.layer >= 2) {
let macInput = network.renderRoot.querySelector('#' + id + '-' + index + '-' + 'MAC') as SlInput;
let newMac = macInput.value.trim() != '' ? macInput.value.trim() : '';
let validatedMac = newMac != '' ? MacAddress.validateAddress(newMac, network.macDatabase) : null;
if (validatedMac != null) {
MacAddress.removeAddressFromDatabase(
physicalNode.portData.get(index).get('MAC'),
network.macDatabase
);
physicalNode.portData.get(index).set('MAC', validatedMac);
MacAddress.addAddressToDatabase(validatedMac, network.macDatabase, physicalNode.id);
changed = true;
} else if (newMac != '') {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
'',
newMac + ' ' + msg('is not a valid MAC Address.')
);
}
}
if (physicalNode.layer >= 3) {
let ip4Input = network.renderRoot.querySelector('#' + id + '-' + index + '-' + 'IPv4') as SlInput;
let ip6Input = network.renderRoot.querySelector('#' + id + '-' + index + '-' + 'IPv6') as SlInput;
let newIpv4 = ip4Input.value.trim() != '' ? ip4Input.value.trim() : '';
let newIpv6 = ip6Input.value.trim() != '' ? ip6Input.value.trim() : '';
let validatedIpv4 =
newIpv4 != '' ? Ipv4Address.validateAddress(newIpv4, network.ipv4Database) : null;
if (validatedIpv4 != null) {
let keepOldIp: boolean = false;
//if this physical node is in a network
if (subnet != null && subnet != undefined) {
switch (Net.mode) {
case 'HOST_BASED':
if (validatedIpv4 != null && validatedIpv4 != undefined)
Net.calculateCIDRGivenNewHost(subnet, validatedIpv4, network.ipv4Database);
node.parent().classes(subnet.cssClass);
break;
case 'NET_BASED':
if (validatedIpv4 != null && !validatedIpv4.matchesNetworkCidr(subnet)) {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
msg('Subnet-based mode on:'),
msg("Inserted IPv4 doesn't match the subnet mask.")
);
keepOldIp = true;
}
break;
default:
break;
}
}
//if this physical node is a gateway of some networks
if (isGateway) {
let affectedNetwork: Net = (physicalNode as Router).portNetMapping.get(index);
switch (Net.mode) {
case 'HOST_BASED':
if (validatedIpv4 != null && validatedIpv4 != undefined)
Net.calculateCIDRGivenNewHost(
affectedNetwork,
validatedIpv4,
network.ipv4Database
);
network._graph.$('#' + affectedNetwork.id).classes(affectedNetwork.cssClass);
break;
case 'NET_BASED':
if (
affectedNetwork != null &&
validatedIpv4 != null &&
!validatedIpv4.matchesNetworkCidr(affectedNetwork)
) {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
msg('Subnet-based mode on:'),
msg("Inserted IPv4 for gateway doesn't match the subnet mask or the network is not configured.")
);
keepOldIp = true;
}
break;
default:
break;
}
}
if (!keepOldIp) {
Ipv4Address.removeAddressFromDatabase(
physicalNode.portData.get(index).get('IPv4'),
network.ipv4Database
);
physicalNode.portData.get(index).set('IPv4', validatedIpv4);
Ipv4Address.addAddressToDatabase(validatedIpv4, network.ipv4Database, physicalNode.id);
changed = true;
}
} else if (newIpv4 != '') {
AlertHelper.toastAlert('warning', 'exclamation-triangle', '', newIpv4 + ' ' + msg('is not valid.'));
}
let validatedIpv6 =
newIpv6 != '' ? Ipv6Address.validateAddress(newIpv6, network.ipv6Database) : null;
if (validatedIpv6 != null) {
Ipv6Address.removeAddressFromDatabase(
physicalNode.portData.get(index).get('IPv6'),
network.ipv6Database
);
physicalNode.portData.get(index).set('IPv6', validatedIpv6);
Ipv6Address.addAddressToDatabase(validatedIpv6, network.ipv6Database, physicalNode.id);
changed = true;
} else if (newIpv6 != '') {
AlertHelper.toastAlert(
'warning',
'exclamation-triangle',
'',
newIpv6 + ' ' + msg('is not a valid IPv6 Address.')
);
}
}
}
if (changed) {
AlertHelper.toastAlert('success', 'check2-circle', msg('Your changes have been saved.'), '');
}
dialog.hide();
});
dialog.appendChild(saveButton);
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static handleChangesInDialogForNet(id: string, node: any, network: ComputerNetwork) {
let dialog: SlDialog = new SlDialog();
dialog.label = 'Details of this network:';
let subnet: Net = node.data();
dialog.innerHTML +=
`<sl-input id="change-id-` +
id +
`" label="${msg('Network Address')}" placeholder="` +
(subnet.networkAddress != undefined && subnet.networkAddress != null ? subnet.networkAddress.address : '') +
`" clearable type="string">`;
dialog.innerHTML +=
`<sl-input id="change-bitmask-` +
id +
`" label="Bitmask" placeholder="` +
(subnet.bitmask != undefined && subnet.bitmask != null ? subnet.bitmask : '') +
`" clearable type="number" min=0>`;
dialog.innerHTML +=
`<sl-input id="change-mask-` +
id +
`" label="Subnet Mask" placeholder="` +
(subnet.netmask != undefined && subnet.netmask != null ? subnet.netmask : '') +
`" clearable type="string">`;
//table for gateways
let gateways: Map<string, number> = subnet.gateways;
if (gateways.size != 0) {
let table: string = `<table cellspacing="10"><tr><td>${msg('Gateway')}</td><td>${msg('Interface')}</td><td>${msg('Connection Type')}</td><td>MAC</td><td>IPv4</td><td>IPv6</td></tr>`;
//TODO: add id for changes?
gateways.forEach((port, gatewayId) => {
if (port != null) {
let router: Router = network._graph.$('#' + gatewayId).data();
let data = router.portData.get(port);
table += `<tr>`;
table += `<td>` + router.name + `</td>`;
table += `<td>` + data.get('Name') + `</td>`;
table += `<td>` + data.get('Connection Type') + `</td>`;
table += `<td>` + data.get('MAC').address + `</td>`;
table += `<td>` + data.get('IPv4').address + `</td>`;
table += `<td>` + data.get('IPv6').address + `</td>`;
table += `</tr>`;
}
});
table += `</table>`;
dialog.innerHTML += table;
}
//TODO: add event listener vào cái nút add node
const saveButton = new SlButton();
saveButton.slot = 'footer';
saveButton.variant = 'primary';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
let idInput = network.renderRoot.querySelector('#change-id-' + id) as SlInput;
let bitmaskInput = network.renderRoot.querySelector('#change-bitmask-' + id) as SlInput;
let netmaskInput = network.renderRoot.querySelector('#change-mask-' + id) as SlInput;
let newId = idInput.value.trim() != '' ? idInput.value.trim() : idInput.placeholder;
let newBitmask = bitmaskInput.value.trim() != '' ? bitmaskInput.value.trim() : bitmaskInput.placeholder;
let newnetmask = netmaskInput.value.trim() != '' ? netmaskInput.value.trim() : netmaskInput.placeholder;
if (
subnet.handleChangesOnNewNetInfo(
newId != '' ? newId : null,
newnetmask != '' ? newnetmask : null,
newBitmask != '' ? +newBitmask : null,
network
)
) {
node.toggleClass('unconfigured-net', false);
}
dialog.hide();
});
dialog.appendChild(saveButton);
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static handleChangeDefaultGateway(subnet: Net, id: string, node: any, network: ComputerNetwork) {
let dialog: SlDialog = new SlDialog();
dialog.label = 'Details of available gateways';
let gateways: Map<string, number> = subnet.gateways; //gateway-node-id, port
let select = `<sl-select id="new-gateway-` + id + `">`;
if (gateways.size != 0) {
let table: string = `<table cellspacing="10"><tr><td>${msg('Gateway')}</td><td>${msg('Port number')}</td><td>${msg('Interface')}</td><td>${msg('Connection Type')}</td><td>MAC</td><td>IPv4</td><td>IPv6</td></tr>`;
gateways.forEach((port, gatewayId) => {
if (port != null) {
let router: Router = network._graph.$('#' + gatewayId).data();
let data = router.portData.get(port);
table += `<tr>`;
table += `<td>` + router.name + `</td>`;
table += `<td>` + port + `</td>`;
table += `<td>` + data.get('Name') + `</td>`;
table += `<td>` + data.get('Connection Type') + `</td>`;
table += `<td>` + data.get('MAC').address + `</td>`;
table += `<td>` + data.get('IPv4').address + `</td>`;
table += `<td>` + data.get('IPv6').address + `</td>`;
table += `</tr>`;
select +=
`<sl-menu-item value="` +
gatewayId +
'/' +
port +
`" style="overflow: hidden;">` +
router.name +
`</sl-menu-item>`;
}
});
table += `</table>`;
dialog.innerHTML += table;
select += `</sl-select>`;
dialog.innerHTML += select;
} else {
dialog.innerHTML += msg('This network has no gateway.');
}
if (!node.hasClass('default-gateway-not-found')) {
let current: [string, number] = node.data('defaultGateway');
dialog.innerHTML += 'Current default gateway is: ' + current[0] + ', port: ' + current[1];
}
const saveButton = new SlButton();
saveButton.slot = 'footer';
saveButton.variant = 'primary';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
let newGateway: string = (
network.renderRoot.querySelector('#' + 'new-gateway-' + id) as SlInput
).value.trim();
if (newGateway != '') {
node.data('defaultGateway', newGateway.split('/'));
let cssClass = node.data('cssClass');
while (cssClass.includes('default-gateway-not-found')) {
cssClass.splice(cssClass.indexOf('default-gateway-not-found'), 1);
}
cssClass.push('gateway-changeable');
node.toggleClass('default-gateway-not-found', false);
node.toggleClass('gateway-changeable', true);
if (!node.data('cssClass').includes('gateway-changeable'))
node.data('cssClass').push('gateway-changeable');
}
dialog.hide();
});
dialog.appendChild(saveButton);
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static showDataHeaders(data: Data, network: ComputerNetwork): void {
let dialog = new SlDialog();
dialog.label = data.id;
if (data instanceof Packet) {
dialog.innerHTML += msg('Mac Address of Sender:') + data.layer2header.macSender + '<br/>';
dialog.innerHTML += msg('IP Address of Sender:') + data.layer3header.ipSender + '<br/>';
dialog.innerHTML += msg('Mac Address of Receiver:') + data.layer2header.macReceiver + '<br/>';
dialog.innerHTML += msg('IP Address of Receiver:') + data.layer3header.ipReceiver;
} else if (data instanceof Frame) {
dialog.innerHTML += msg('Mac Address of Sender:') + data.layer2header.macSender + '<br/>';
dialog.innerHTML += msg('IP Address of Sender:') + data.layer2header.ipSender + '<br/>';
dialog.innerHTML += msg('Mac Address of Receiver:') + data.layer2header.macReceiver + '<br/>';
dialog.innerHTML += msg('IP Address of Receiver:') + data.layer2header.ipReceiver;
}
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).innerHTML = '';
(network.renderRoot.querySelector('#inputDialog') as HTMLElement).append(dialog);
dialog.show();
}
static showHelpText(network: ComputerNetwork): TemplateResult {
return html`
<sl-tab-group>
<sl-tab slot="nav" panel="node">${msg('Add/ Configure graph components')}</sl-tab>
<sl-tab slot="nav" panel="cidr">${msg('CIDR/ Subnetting')}</sl-tab>
<sl-tab slot="nav" panel="simulation">${msg('Packet travelling simulation')}</sl-tab>
<sl-tab-panel name="node">
<sl-details summary="${msg('How to add a physical node (host, router, switch,...)?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Choose your component')}</div>
<img src="/resources/help-instructions/toolbar-select-physical.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('More details with "Add details for ports"')}</div>
<img src="/resources/help-instructions/toolbar-physical-node.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 3:</b> ${msg('Pick a color also if you want to:')}</div>
<img src="/resources/help-instructions/pick-color.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 4:</b> ${msg('Click add')}</div>
<img src="/resources/help-instructions/add-button.png"/>
</sl-card>
</sl-details>
<sl-details summary="${msg('How to add a logical node (network node)?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Choose your component')}</div>
<img src="/resources/help-instructions/toolbar-select-logical.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('Configure your network')}</div>
<img src="/resources/help-instructions/toolbar-logical-node.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 3:</b> ${msg('Pick a color also if you want to:')}</div>
<img src="/resources/help-instructions/pick-color.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 4:</b> ${msg('Click add')}</div>
<img src="/resources/help-instructions/add-button.png"/>
</sl-card>
</sl-details>
<sl-details summary="${msg('How to add a link between two physical nodes?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Choose edge component')}</div>
<img src="/resources/help-instructions/toolbar-select-edge.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('Pick a color also if you want to:')}</div>
<img src="/resources/help-instructions/pick-color.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 3:</b> ${msg('Toggle draw mode')}</div>
<img src="/resources/help-instructions/draw-edge-button.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 4:</b> ${msg('Draw then configure on right click')}</div>
<img src="/resources/help-instructions/configure-edge.png"/>
</sl-card>
</sl-details>
</sl-tab-panel>
<sl-tab-panel name="cidr">
<sl-details summary="How to assign a gateway?">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Activate the "assign gateway" mode:')}</div>
<img src="/resources/help-instructions/drag-to-assign-gateway.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('Drag the gateway on the edge of a network:')}</div>
<img src="/resources/help-instructions/drag-on-the-edge.png"/>
</sl-card>
</sl-details>
<sl-details summary="${msg('How to drag a component into a network?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Activate the "drag-and-drop" mode:')}</div>
<img height="175px" src="/resources/help-instructions/drag-and-drop.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('Drag the component inside the network:')}</div>
<img src="/resources/help-instructions/drag-inside.png"/>
</sl-card>
</sl-details>
<sl-details summary="${msg('How does the Net-based mode work?')}">
<sl-card class="card-image">
<div slot="header">${msg('Before dragging a node into network')} <b>1.1.1.0 /24</b></div>
<img src="/resources/help-instructions/before-netmode.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header">${msg('After dragging the node into network')} <b>1.1.1.0 /24</b></div>
<img src="/resources/help-instructions/after-netmode.png"/>
<div slot="footer">${msg('New IPv4 will be assigned for conflicting addresses.')}<br/>
${msg('During this mode, when users edit addresses of:')}
<ul>
<li><b>networks</b>: ${msg('conflicting addresses of all related hosts/gateways will be reassigned.')}</li>
<li><b>hosts/gateways/subnetworks</b>: ${msg('new addresses will be validated against the network addresses and will only be accepted when they are valid.')}</li>
</ul>
</div>
</sl-card>
</sl-details>
<sl-details summary="${msg('How does the Host-based mode work?')}">
<sl-card class="card-image">
<div slot="header">${msg('Before dragging host0 into network')} <b>1.1.1.128 /25</b></div>
<img height="210px" src="/resources/help-instructions/before-hostmode.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header">${msg('After dragging host0 into network')} <b>1.1.1.128 /25</b></div>
<img height="210px" src="/resources/help-instructions/after-hostmode.png"/>
<div slot="footer">${msg('The existing network will expand to contain the new host.')}<br/>
${msg('During this mode, when users edit addresses of:')}
<ul>
<li><b>${msg('networks')}</b>: ${msg('new network address range will be checked if they expands the old ones.')}</li>
<li><b>${msg('hosts/gateways/subnetworks')}</b>: ${msg('the existing network will expand to contain the new addresses.')}</li>
</ul>
</div>
</sl-card>
</sl-details>
</sl-tab-panel>
<sl-tab-panel name="simulation">
<sl-details summary="${msg('How to manipulate a simulation session?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('if you don\'t use a graph from the examples, Init a new simulation session')}</div>
<img height="175px" src="/resources/help-instructions/init-session.png"/>
<div slot="footer">${msg('In this step, for the:')}
<ul>
<li>${msg('host/router: ARP table and routing table are created')}</li>
<li>${msg('switch/bridge/access point: MAC address table is created')}</li>
<ul>
</div>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2:</b> ${msg('assign a sender/receiver for the packet')}</div>
<img height="175px" src="/resources/help-instructions/choose-receiver.png"/>
<div slot="footer">
<ul>
<li>${msg('Click on Choose sender or Choose receiver')}</li>
<li>${msg('Click on a host on the canvas')}</li>
<li>${msg('Choose an IP')}</li>
<ul>
</div>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 3:</b> ${msg('start sending a packet with the assigned sender and receiver in step 2')}</div>
<img height="175px" src="/resources/help-instructions/send-packet.png"/>
<div slot="footer">${msg('Repeat steps 2 and 3 as much as desired.<br/> You can also use pause/resume or change focus/speed during or before starting to send a packet.')}</div>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Last step')}</b></div>
<img height="175px" src="/resources/help-instructions/stop-session.png"/>
<div slot="footer">
<b>${msg('Stop session')}</b> ${msg('resets all tables and ends the current simulation session.')}<br/>
${msg('Start from step 1 again for a new simulation session.')}
</div>
</sl-card>
</sl-details>
<sl-details summary="${msg('How to configure the tables?')}">
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 1:</b> ${msg('Init. Ignore this step if you use an example graph, or there is a session running')}.</div>
<img src="/resources/help-instructions/init-table.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2.a:</b> ${msg('Click on Add to generate a new row in the table')}</div>
<img src="/resources/help-instructions/add-table.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 2.b:</b> ${msg('Fill your table according to its type. Except for the tables of the routers, tables of other nodes can be filled automatically.')}</div>
<img src="/resources/help-instructions/example-table.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 3:</b> ${msg('Remove button removes all checked rows')}</div>
<img src="/resources/help-instructions/remove-table.png"/>
</sl-card>
<sl-card class="card-image">
<div slot="header"><b>${msg('Step')} 4:</b> ${msg('Save button saves the current table on the UI to the database')}</div>
<img src="/resources/help-instructions/save-table.png"/>
</sl-card>
</sl-details>
</sl-tab-panel>
</sl-tab-group>
`;
}
}