feat(editor): F4.5a the device sheet for PCs, notebooks, switches and modems
Measured first: the new oracle SheetRun types 77 texts into FILIUS' own settings panels and records what was marked red, what the device took and what the field showed afterwards. Java's two judgements disagree (red text taken, black text refused, any int as a modem port, a refused text dropped without a word); here one rule decides both (packages/doc fields.ts), a refused text stays marked in the field, and only what Java's model accepts reaches the document. - doc: settings.ts (name, label policy, addresses, IP forwarding, DHCP stopping the own DHCP server, LAN/WLAN with the wireless connection, switch SSID dropping its wireless hosts, retention time, cloud, modem port as its pair), sheet.ts (what the sheet shows) - fls: modemPair/modemPort in one place - editor: DeviceSheet beside the surface or below it on a phone, opened with the selection; every change one undo step - not adopted from Java: the name overwritten by the MAC address, the access point search depending on unrelated devices Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import {
|
||||
DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
||||
defaultSwitchConfiguration,
|
||||
firewallConfiguration,
|
||||
interfaceKey,
|
||||
@@ -26,7 +27,7 @@ import {
|
||||
type NodeKind,
|
||||
type TopologyNode,
|
||||
} from '@mdg-filius/core';
|
||||
import type { ImportedProject } from '@mdg-filius/fls';
|
||||
import { DEFAULT_MODEM_PORT, modemPair, type ImportedProject } from '@mdg-filius/fls';
|
||||
import { emptyProject, inOrder, nodeEntry, ROOTS } from './projectDoc.js';
|
||||
|
||||
/** The kinds of the palette, in Java's order (GUIDesignSidebar.addComponentItemsToSidebar; the cable comes first, a tool). */
|
||||
@@ -101,7 +102,7 @@ export function newDeviceProject(device: NewDevice): ImportedProject {
|
||||
project.firewallConfigurationsByNodeId.set(device.id, firewallConfiguration());
|
||||
break;
|
||||
case 'remoteLink':
|
||||
project.remoteLinkConfigurations.set(device.id, { pairIdentifier: 'java-modem-port-12345', latencyMilliseconds: 20, isEnabled: true });
|
||||
project.remoteLinkConfigurations.set(device.id, { pairIdentifier: modemPair(DEFAULT_MODEM_PORT), latencyMilliseconds: DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS, isEnabled: true });
|
||||
break;
|
||||
}
|
||||
return project;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// The device sheet's rules for what may be typed into a field: Java's
|
||||
// EingabenUeberpruefung as FILIUS' settings panels use it
|
||||
// (JHostKonfiguration, JSwitchKonfiguration, JModemKonfiguration), measured
|
||||
// with tools/java-oracle's SheetRun (test/sheet.java.json).
|
||||
//
|
||||
// Java judges a text twice: while it is typed, the panel colours it red when
|
||||
// the pattern does not match; when the field loses the focus, the model's
|
||||
// setter takes it or keeps the old value — and the two disagree. Java takes
|
||||
// red text ("10..0.0.1", "+10.0.0.1", "-0.0.0.1", Arabic-Indic digits, a
|
||||
// retention time of "-5"), refuses black text (".0.0.1", a retention time
|
||||
// past Long's range), and a modem takes any int as its port (0, 65536, -1),
|
||||
// unchecked. Here one rule decides both: a text is taken exactly when it is
|
||||
// not marked. What is not taken is not thrown away either (Java puts the old
|
||||
// value back into the field without a word): the sheet keeps it, marked,
|
||||
// until it is mended (plan I6).
|
||||
//
|
||||
// Spaces around an address or a number are left out (Java refuses
|
||||
// " 10.0.0.1"; a phone's keyboard puts a space after a word). A name and an
|
||||
// SSID are taken as typed, spaces included, as Java takes them.
|
||||
|
||||
import { IP_ADDRESS_PATTERN, javaIpCheck } from '@mdg-filius/core';
|
||||
|
||||
export type FieldRule = 'name' | 'ipAddress' | 'ipAddressOrEmpty' | 'subnetMask' | 'ssid' | 'seconds' | 'port';
|
||||
|
||||
/** What a field's text comes to: the value the device takes, or that it takes none. */
|
||||
export type CheckedField = { readonly value: string } | { readonly invalid: true };
|
||||
|
||||
/** EingabenUeberpruefung.musterSubNetz. */
|
||||
const SUBNET_MASK_PATTERN = /^(0*([1-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.((0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.){2}(0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))$/;
|
||||
/** EingabenUeberpruefung.musterServiceSetIdentifier: printable ASCII, at least one character. */
|
||||
const SSID_PATTERN = /^[ -~]+$/;
|
||||
/** EingabenUeberpruefung.musterPort: 1 to 65535, no leading zero. */
|
||||
const PORT_PATTERN = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{1,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
|
||||
/** EingabenUeberpruefung.musterNurZahlen, at least one digit (Java's Long.parseLong refuses none). */
|
||||
const DIGITS_PATTERN = /^[0-9]+$/;
|
||||
|
||||
const INVALID: CheckedField = { invalid: true };
|
||||
|
||||
/**
|
||||
* An address as the panel's pattern and the model's IP.ipCheck both accept
|
||||
* it: neither alone (the pattern lets ".0.0.1" through, ipCheck "10..0.0.1"
|
||||
* and "+10.0.0.1"); the device keeps ipCheck's form ("010.0.0.1" is 10.0.0.1).
|
||||
*/
|
||||
function address(text: string, pattern: RegExp): CheckedField {
|
||||
const canonical = javaIpCheck(text);
|
||||
return canonical !== null && pattern.test(text) ? { value: canonical } : INVALID;
|
||||
}
|
||||
|
||||
export function checkField(rule: FieldRule, text: string): CheckedField {
|
||||
switch (rule) {
|
||||
case 'name':
|
||||
return { value: text };
|
||||
case 'ssid':
|
||||
return SSID_PATTERN.test(text) ? { value: text } : INVALID;
|
||||
case 'ipAddress':
|
||||
return address(text.trim(), IP_ADDRESS_PATTERN);
|
||||
case 'ipAddressOrEmpty':
|
||||
return text.trim() === '' ? { value: '' } : address(text.trim(), IP_ADDRESS_PATTERN);
|
||||
case 'subnetMask':
|
||||
return address(text.trim(), SUBNET_MASK_PATTERN);
|
||||
case 'seconds': {
|
||||
const digits = text.trim();
|
||||
// In milliseconds the device's number must stay exact.
|
||||
return DIGITS_PATTERN.test(digits) && Number(digits) * 1000 <= Number.MAX_SAFE_INTEGER ? { value: String(Number(digits)) } : INVALID;
|
||||
}
|
||||
case 'port':
|
||||
return PORT_PATTERN.test(text.trim()) ? { value: text.trim() } : INVALID;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
// Public surface of the shared document.
|
||||
export * from './commands.js';
|
||||
export * from './fields.js';
|
||||
export * from './janitor.js';
|
||||
export * from './projectDoc.js';
|
||||
export * from './settings.js';
|
||||
export * from './sheet.js';
|
||||
export * from './snapshot.js';
|
||||
export * from './values.js';
|
||||
export * from './view.js';
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// What the device sheet does to the document: a device's settings changed as
|
||||
// FILIUS' settings panels change them (JHostKonfiguration,
|
||||
// JSwitchKonfiguration, JModemKonfiguration; measured with SheetRun), each a
|
||||
// transaction with the person's origin, like the design tools' commands.
|
||||
//
|
||||
// A text field's setter takes the text only when the field's rule does
|
||||
// (fields.ts) and says so; the device keeps its value otherwise. A host's
|
||||
// wireless card and a switch's SSID move the wireless connections as Java
|
||||
// moves them: a host joins the first switch whose SSID it names, a switch
|
||||
// that changes its SSID loses its wireless hosts.
|
||||
//
|
||||
// Not adopted from Java: a host that shows its MAC address as its name takes
|
||||
// that address as its name as soon as another field is taken (SheetRun's
|
||||
// flow useMacAsNameThenOff); here the name stays what it was. And Java's
|
||||
// search for the access point (updateWifiConnection) keeps the last switch
|
||||
// with the SSID it passes until a device that is neither comes after the
|
||||
// host — which switch depends on unrelated devices; here it is the first.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import { DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS, defaultSwitchConfiguration, forwardsIPv4ByDefault, type Id, type NodeKind } from '@mdg-filius/core';
|
||||
import { modemPair } from '@mdg-filius/fls';
|
||||
import { connect, type ConnectError } from './commands.js';
|
||||
import { checkField, type CheckedField } from './fields.js';
|
||||
import { inOrder, ROOTS } from './projectDoc.js';
|
||||
import { toY } from './values.js';
|
||||
|
||||
const nodes = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
||||
const links = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.links);
|
||||
const settingsOf = (node: Y.Map<unknown>) => node.get('settings') as Y.Map<unknown>;
|
||||
const kindOf = (node: Y.Map<unknown>) => node.get('kind') as NodeKind;
|
||||
|
||||
/** A setting of a device as a map of its fields, made with these fields when the device has none. */
|
||||
function settingMap(node: Y.Map<unknown>, key: string, fields: Readonly<Record<string, string | number | boolean>>): Y.Map<unknown> {
|
||||
const settings = settingsOf(node);
|
||||
let map = settings.get(key) as Y.Map<unknown> | undefined;
|
||||
if (map === undefined) {
|
||||
map = toY(fields) as Y.Map<unknown>;
|
||||
settings.set(key, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Changes a device in one transaction when the text passes the rule; says what it came to. */
|
||||
function setChecked(doc: Y.Doc, id: Id, checked: CheckedField, change: (node: Y.Map<unknown>, value: string) => void, origin: unknown): CheckedField {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined || 'invalid' in checked) return checked;
|
||||
doc.transact(() => change(node, checked.value), origin);
|
||||
return checked;
|
||||
}
|
||||
|
||||
export function renameDevice(doc: Y.Doc, id: Id, name: string, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node !== undefined) doc.transact(() => node.set('name', name), origin);
|
||||
}
|
||||
|
||||
/** What a PC or notebook shows as its name (Host.useIPAsName, useMACAsName): its address, its MAC address, both or its name. */
|
||||
export function setHostLabels(doc: Y.Doc, id: Id, labels: { readonly ipAddress: boolean; readonly macAddress: boolean }, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return;
|
||||
const policy = labels.ipAddress && labels.macAddress ? 'ipAndMAC' : labels.ipAddress ? 'ipAddress' : labels.macAddress ? 'macAddress' : 'manual';
|
||||
doc.transact(() => node.set('labelPolicy', policy), origin);
|
||||
}
|
||||
|
||||
export type HostAddressField = 'ipAddress' | 'subnetMask' | 'defaultGateway' | 'dnsServer';
|
||||
|
||||
const ADDRESS_RULES = { ipAddress: 'ipAddress', subnetMask: 'subnetMask', defaultGateway: 'ipAddressOrEmpty', dnsServer: 'ipAddressOrEmpty' } as const;
|
||||
|
||||
/** A PC's or notebook's address, mask, gateway or DNS server (Betriebssystem.setzeIPAdresse …). */
|
||||
export function setHostAddress(doc: Y.Doc, id: Id, field: HostAddressField, text: string, origin: unknown): CheckedField {
|
||||
return setChecked(
|
||||
doc,
|
||||
id,
|
||||
checkField(ADDRESS_RULES[field], text),
|
||||
(node, value) => settingMap(node, 'device', { ipAddress: '192.168.0.10', subnetMask: '255.255.255.0', defaultGateway: '', dnsServer: '' }).set(field, value),
|
||||
origin,
|
||||
);
|
||||
}
|
||||
|
||||
/** IP forwarding, kept only where it differs from the kind's (as the project keeps it). */
|
||||
export function setIpForwarding(doc: Y.Doc, id: Id, on: boolean, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return;
|
||||
doc.transact(() => {
|
||||
if (on === forwardsIPv4ByDefault(kindOf(node))) settingsOf(node).delete('ipForwarding');
|
||||
else settingsOf(node).set('ipForwarding', on);
|
||||
}, origin);
|
||||
}
|
||||
|
||||
/** DHCP for the configuration; switched on, it stops the device's own DHCP server (JHostKonfiguration.aenderungenAnnehmen). */
|
||||
export function setDhcpClient(doc: Y.Doc, id: Id, on: boolean, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return;
|
||||
doc.transact(() => {
|
||||
if (!on) {
|
||||
settingsOf(node).delete('dhcpClient');
|
||||
return;
|
||||
}
|
||||
settingsOf(node).set('dhcpClient', toY({ isEnabled: true }));
|
||||
(settingsOf(node).get('dhcpServer') as Y.Map<unknown> | undefined)?.set('isActive', false);
|
||||
}, origin);
|
||||
}
|
||||
|
||||
const wirelessOf = (node: Y.Map<unknown>) => (settingsOf(node).get('wireless') as Y.Map<unknown> | undefined)?.get('isEnabled') === true;
|
||||
|
||||
/** The cables of a device to a switch: a wireless host's wireless connections. */
|
||||
function linksToSwitches(doc: Y.Doc, id: Id): string[] {
|
||||
return [...links(doc).entries()]
|
||||
.filter(([, link]) => {
|
||||
const ends = [link.get('sourceNode') as Id, link.get('targetNode') as Id];
|
||||
const other = ends[0] === id ? ends[1]! : ends[1] === id ? ends[0]! : undefined;
|
||||
const node = other === undefined ? undefined : nodes(doc).get(other);
|
||||
return node !== undefined && kindOf(node) === 'networkSwitch';
|
||||
})
|
||||
.map(([linkId]) => linkId);
|
||||
}
|
||||
|
||||
/** Why a host's card cannot become wireless (Java disables the choice): its cable is plugged. */
|
||||
export type WirelessError = 'cablePlugged' | ConnectError;
|
||||
|
||||
/**
|
||||
* A PC's or notebook's card by cable or wireless, and the SSID of the network
|
||||
* it joins ('' for none). Java (JHostKonfiguration.updateWifiConnection):
|
||||
* when either changes, the host leaves its wireless network, then joins the
|
||||
* first switch with that SSID, when it is wireless and names one. A card
|
||||
* whose cable is plugged cannot become wireless. The SSID stays when the
|
||||
* card goes back to the cable, as Java keeps it.
|
||||
*/
|
||||
export function setHostWireless(doc: Y.Doc, id: Id, wireless: boolean, ssid: string, linkId: Id, origin: unknown): { readonly link?: Id } | { readonly error: WirelessError } {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return { error: 'unknownDevice' };
|
||||
const before = settingsOf(node).get('wireless') as Y.Map<unknown> | undefined;
|
||||
const wasWireless = wirelessOf(node);
|
||||
const plugged = [...links(doc).values()].some((link) => link.get('sourceNode') === id || link.get('targetNode') === id);
|
||||
if (wireless && !wasWireless && plugged) return { error: 'cablePlugged' };
|
||||
if (wasWireless === wireless && ((before?.get('ssid') as string | undefined) ?? '') === ssid) return {};
|
||||
let result: { readonly link?: Id } | { readonly error: WirelessError } = {};
|
||||
doc.transact(() => {
|
||||
if (wasWireless) for (const each of linksToSwitches(doc, id)) links(doc).delete(each);
|
||||
if (!wireless && ssid === '') settingsOf(node).delete('wireless');
|
||||
else settingsOf(node).set('wireless', toY({ isEnabled: wireless, ssid }));
|
||||
if (!wireless || ssid === '') return;
|
||||
const accessPoint = inOrder(nodes(doc)).find(([, candidate]) => kindOf(candidate) === 'networkSwitch' && (settingsOf(candidate).get('switch') as Y.Map<unknown> | undefined)?.get('ssid') === ssid);
|
||||
if (accessPoint !== undefined) result = connect(doc, { node: id }, { node: accessPoint[0] }, linkId, origin);
|
||||
}, origin);
|
||||
return result;
|
||||
}
|
||||
|
||||
const SWITCH_DEFAULTS = (id: Id) => ({ ...defaultSwitchConfiguration(id) });
|
||||
|
||||
/** A switch's SSID; a new one drops its wireless hosts (JSwitchKonfiguration.aenderungenAnnehmen). */
|
||||
export function setSwitchSsid(doc: Y.Doc, id: Id, text: string, origin: unknown): CheckedField {
|
||||
return setChecked(
|
||||
doc,
|
||||
id,
|
||||
checkField('ssid', text),
|
||||
(node, value) => {
|
||||
const setting = settingMap(node, 'switch', SWITCH_DEFAULTS(id));
|
||||
if (setting.get('ssid') === value) return;
|
||||
setting.set('ssid', value);
|
||||
for (const linkId of [...links(doc).keys()]) {
|
||||
const link = links(doc).get(linkId)!;
|
||||
const ends = [link.get('sourceNode') as Id, link.get('targetNode') as Id];
|
||||
const host = ends[0] === id ? ends[1]! : ends[1] === id ? ends[0]! : undefined;
|
||||
const other = host === undefined ? undefined : nodes(doc).get(host);
|
||||
if (other !== undefined && (kindOf(other) === 'pc' || kindOf(other) === 'notebook') && wirelessOf(other)) links(doc).delete(linkId);
|
||||
}
|
||||
},
|
||||
origin,
|
||||
);
|
||||
}
|
||||
|
||||
/** How long a switch keeps a learned MAC address, in seconds as the panel asks for it. */
|
||||
export function setSwitchRetention(doc: Y.Doc, id: Id, text: string, origin: unknown): CheckedField {
|
||||
return setChecked(doc, id, checkField('seconds', text), (node, value) => settingMap(node, 'switch', SWITCH_DEFAULTS(id)).set('retentionTimeMilliseconds', Number(value) * 1000), origin);
|
||||
}
|
||||
|
||||
/** A switch drawn as a cloud (Switch.cloud), kept only when true. */
|
||||
export function setSwitchCloud(doc: Y.Doc, id: Id, cloud: boolean, origin: unknown): void {
|
||||
const node = nodes(doc).get(id);
|
||||
if (node === undefined) return;
|
||||
doc.transact(() => {
|
||||
const setting = settingMap(node, 'switch', SWITCH_DEFAULTS(id));
|
||||
if (cloud) setting.set('showsAsCloud', true);
|
||||
else setting.delete('showsAsCloud');
|
||||
}, origin);
|
||||
}
|
||||
|
||||
/** A modem's port: the pair it belongs to (two modems on one port are a pair, as the export writes them). */
|
||||
export function setModemPort(doc: Y.Doc, id: Id, text: string, origin: unknown): CheckedField {
|
||||
return setChecked(
|
||||
doc,
|
||||
id,
|
||||
checkField('port', text),
|
||||
(node, value) =>
|
||||
settingMap(node, 'remoteLink', { pairIdentifier: '', latencyMilliseconds: DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS, isEnabled: true }).set('pairIdentifier', modemPair(Number(value))),
|
||||
origin,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// What the device sheet shows of a device, read straight from the document:
|
||||
// the fields of FILIUS' settings panel of its kind (JHostKonfiguration,
|
||||
// JSwitchKonfiguration, JModemKonfiguration). A router's and a home router's
|
||||
// sheet show their name only, until their own panels follow.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import { defaultSwitchConfiguration, DEFAULT_SWITCH_RETENTION_MILLISECONDS, forwardsIPv4ByDefault, stableGeneratedMacAddress, type HostLabelPolicy, type NodeKind } from '@mdg-filius/core';
|
||||
import { modemPort } from '@mdg-filius/fls';
|
||||
import { inOrder, ROOTS } from './projectDoc.js';
|
||||
|
||||
export interface HostSheet {
|
||||
readonly kind: 'pc' | 'notebook';
|
||||
readonly name: string;
|
||||
/** Host.useIPAsName and useMACAsName. */
|
||||
readonly labels: { readonly ipAddress: boolean; readonly macAddress: boolean };
|
||||
/** The card's MAC address (the model keeps them in upper case, as Java shows them). */
|
||||
readonly macAddress: string;
|
||||
readonly ipAddress: string;
|
||||
readonly subnetMask: string;
|
||||
readonly defaultGateway: string;
|
||||
readonly dnsServer: string;
|
||||
readonly dhcpClient: boolean;
|
||||
readonly ipForwarding: boolean;
|
||||
readonly wireless: boolean;
|
||||
/** The SSID of the network the card joins, '' for none. */
|
||||
readonly ssid: string;
|
||||
/** Java offers the wireless card only while the cable's port is free, or the card is wireless already. */
|
||||
readonly wirelessPossible: boolean;
|
||||
/** The switches' SSIDs, in the order of the devices, each once: the networks to choose from. */
|
||||
readonly networks: readonly string[];
|
||||
}
|
||||
|
||||
export interface SwitchSheet {
|
||||
readonly kind: 'networkSwitch';
|
||||
readonly name: string;
|
||||
readonly ssid: string;
|
||||
readonly retentionSeconds: number;
|
||||
readonly cloud: boolean;
|
||||
}
|
||||
|
||||
export interface ModemSheet {
|
||||
readonly kind: 'remoteLink';
|
||||
readonly name: string;
|
||||
readonly port: number;
|
||||
}
|
||||
|
||||
export interface NameSheet {
|
||||
readonly kind: Exclude<NodeKind, 'pc' | 'notebook' | 'networkSwitch' | 'remoteLink'>;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export type DeviceSheet = HostSheet | SwitchSheet | ModemSheet | NameSheet;
|
||||
|
||||
const settingsOf = (node: Y.Map<unknown>) => node.get('settings') as Y.Map<unknown>;
|
||||
const setting = (node: Y.Map<unknown>, key: string) => settingsOf(node).get(key) as Y.Map<unknown> | undefined;
|
||||
|
||||
/** The sheet of a device, or undefined when the document has no such device. */
|
||||
export function deviceSheet(doc: Y.Doc, id: string): DeviceSheet | undefined {
|
||||
const all = doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
||||
const node = all.get(id);
|
||||
if (node === undefined) return undefined;
|
||||
const kind = node.get('kind') as NodeKind;
|
||||
const name = node.get('name') as string;
|
||||
switch (kind) {
|
||||
case 'pc':
|
||||
case 'notebook': {
|
||||
const device = setting(node, 'device');
|
||||
const wireless = setting(node, 'wireless');
|
||||
const port = (node.get('ports') as Y.Array<Y.Map<unknown>>).get(0);
|
||||
const portId = port?.get('id') as string | undefined;
|
||||
const plugged = [...doc.getMap<Y.Map<unknown>>(ROOTS.links).values()].some((link) => link.get('sourceNode') === id || link.get('targetNode') === id);
|
||||
const policy = node.get('labelPolicy') as HostLabelPolicy;
|
||||
const isWireless = wireless?.get('isEnabled') === true;
|
||||
const networks = new Set<string>();
|
||||
for (const [other, candidate] of inOrder(all)) {
|
||||
if (candidate.get('kind') !== 'networkSwitch') continue;
|
||||
networks.add((setting(candidate, 'switch')?.get('ssid') as string | undefined) ?? defaultSwitchConfiguration(other).ssid);
|
||||
}
|
||||
return {
|
||||
kind,
|
||||
name,
|
||||
labels: { ipAddress: policy === 'ipAddress' || policy === 'ipAndMAC', macAddress: policy === 'macAddress' || policy === 'ipAndMAC' },
|
||||
macAddress: (port?.get('mac') as string | undefined) ?? (portId === undefined ? '' : stableGeneratedMacAddress(portId)),
|
||||
ipAddress: (device?.get('ipAddress') as string | undefined) ?? '',
|
||||
subnetMask: (device?.get('subnetMask') as string | undefined) ?? '',
|
||||
defaultGateway: (device?.get('defaultGateway') as string | undefined) ?? '',
|
||||
dnsServer: (device?.get('dnsServer') as string | undefined) ?? '',
|
||||
dhcpClient: setting(node, 'dhcpClient')?.get('isEnabled') === true,
|
||||
ipForwarding: (settingsOf(node).get('ipForwarding') as boolean | undefined) ?? forwardsIPv4ByDefault(kind),
|
||||
wireless: isWireless,
|
||||
ssid: (wireless?.get('ssid') as string | undefined) ?? '',
|
||||
wirelessPossible: isWireless || !plugged,
|
||||
networks: [...networks],
|
||||
};
|
||||
}
|
||||
case 'networkSwitch': {
|
||||
const config = setting(node, 'switch');
|
||||
return {
|
||||
kind,
|
||||
name,
|
||||
ssid: (config?.get('ssid') as string | undefined) ?? defaultSwitchConfiguration(id).ssid,
|
||||
// Whole seconds, as Java's panel shows them (retentionTime / 1000 in long arithmetic).
|
||||
retentionSeconds: Math.trunc(((config?.get('retentionTimeMilliseconds') as number | undefined) ?? DEFAULT_SWITCH_RETENTION_MILLISECONDS) / 1000),
|
||||
cloud: config?.get('showsAsCloud') === true,
|
||||
};
|
||||
}
|
||||
case 'remoteLink':
|
||||
return { kind, name, port: modemPort(setting(node, 'remoteLink')?.get('pairIdentifier') as string | undefined) };
|
||||
default:
|
||||
return { kind, name };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user