feat(editor): F4.5c the DHCP server and the port forwarding in the device sheet
Measured first: SheetRun now types into FILIUS' DHCP dialog and keeps rows
in its port forwarding dialog. Java's DHCP server keeps its bounds as typed
(".0.0.1" too), never takes an empty gateway or DNS server, hands out the
device's gateway and DNS server unless its own (a home router always its
LAN address, where "manual settings" do nothing), drops a second static
assignment of a MAC address silently and lets "_" through its MAC pattern;
the port forwarding dialog keeps any row, its firmware forwards only the
valid ones. Here a value passes its rule or is not taken, with a word why.
- doc: DHCP server addresses and flags, static assignments (a MAC address
once, ignoring case), port forwarding rows (TCP or UDP, musterPort, the
LAN address); the sheet shows what the server hands out and which rows
Java's firmware forwards (the core's parsePortForwardingRow, exported)
- editor: Java's dialogs as pages of the sheet with a back button, the
three row tables on one RowsEditor (the forwarding table rebuilt on it);
a sheet per device, so an open page closes when another device is chosen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import { ICON_URLS } from '../canvas/icons.js';
|
||||
import { useTranslate } from '../i18n/i18n.js';
|
||||
import { NameField, type Change } from './NameField.js';
|
||||
import { HomeRouterFields, RouterFields } from './RouterSheets.js';
|
||||
import { DhcpServerPanel, PortForwardingPanel } from './ServicePanels.js';
|
||||
import { SheetCheck, SheetField } from './SheetField.js';
|
||||
import './sheet.css';
|
||||
|
||||
@@ -59,10 +60,16 @@ export interface DeviceSheetProps {
|
||||
readonly onHighlight: (link: string | null) => void;
|
||||
}
|
||||
|
||||
/** A page of the sheet that Java opens as a dialog. */
|
||||
export type SheetPage = 'dhcp' | 'forwarding';
|
||||
|
||||
export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose, onHighlight }: DeviceSheetProps) {
|
||||
const t = useTranslate();
|
||||
const sheet = useDeviceSheet(doc, id);
|
||||
// The page open, if any; the editor makes a sheet per device, so another device starts at its fields.
|
||||
const [open, setOpen] = useState<SheetPage | null>(null);
|
||||
if (sheet === undefined) return null;
|
||||
const onOpen = (each: SheetPage) => setOpen(each);
|
||||
const change: Change = {
|
||||
doc,
|
||||
id,
|
||||
@@ -77,16 +84,22 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
|
||||
return (
|
||||
<aside className="device-sheet" aria-label={t('sheet.title')} data-device={id}>
|
||||
<header>
|
||||
<img src={ICON_URLS[iconOf(sheet.kind, sheet.kind === 'networkSwitch' && sheet.cloud)]} alt="" />
|
||||
<h2>{title}</h2>
|
||||
{open === null ? (
|
||||
<img src={ICON_URLS[iconOf(sheet.kind, sheet.kind === 'networkSwitch' && sheet.cloud)]} alt="" />
|
||||
) : (
|
||||
<button type="button" className="sheet-close" onClick={() => setOpen(null)} aria-label={t('sheet.back')} title={t('sheet.back')}>
|
||||
‹
|
||||
</button>
|
||||
)}
|
||||
<h2>{open === 'dhcp' ? t('sheet.dhcpServer') : open === 'forwarding' ? t('sheet.portForwarding') : title}</h2>
|
||||
<button type="button" className="sheet-close" onClick={onClose} aria-label={t('sheet.close')} title={t('sheet.close')}>
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
{/* A field per device: a text typed for one device is not offered to the next. */}
|
||||
<div key={id}>
|
||||
{/* A field per device: a text typed for one device is not offered to the next. Kept while a page is open, so that its tab stays as Java's panel does behind a dialog. */}
|
||||
<div key={id} hidden={open !== null}>
|
||||
{sheet.kind === 'pc' || sheet.kind === 'notebook' ? (
|
||||
<HostFields sheet={sheet} label={label} change={change} onMessage={onMessage} />
|
||||
<HostFields sheet={sheet} label={label} change={change} onMessage={onMessage} onOpen={onOpen} />
|
||||
) : sheet.kind === 'networkSwitch' ? (
|
||||
<SwitchFields sheet={sheet} change={change} />
|
||||
) : sheet.kind === 'remoteLink' ? (
|
||||
@@ -94,16 +107,37 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
|
||||
) : sheet.kind === 'router' ? (
|
||||
<RouterFields sheet={sheet} change={change} onHighlight={onHighlight} />
|
||||
) : sheet.kind === 'gateway' ? (
|
||||
<HomeRouterFields sheet={sheet} change={change} onHighlight={onHighlight} />
|
||||
<HomeRouterFields sheet={sheet} change={change} onHighlight={onHighlight} onOpen={onOpen} />
|
||||
) : (
|
||||
<NameField name={sheet.name} change={change} />
|
||||
)}
|
||||
</div>
|
||||
{open === 'dhcp' && (sheet.kind === 'pc' || sheet.kind === 'notebook' || sheet.kind === 'gateway') ? (
|
||||
<div key={`${id}-dhcp`}>
|
||||
<DhcpServerPanel server={sheet.dhcpServer} change={change} />
|
||||
</div>
|
||||
) : open === 'forwarding' && sheet.kind === 'gateway' ? (
|
||||
<div key={`${id}-forwarding`}>
|
||||
<PortForwardingPanel sheet={sheet} change={change} />
|
||||
</div>
|
||||
) : null}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function HostFields({ sheet, label, change, onMessage }: { readonly sheet: HostSheet; readonly label: string; readonly change: Change; readonly onMessage: (message: string) => void }) {
|
||||
function HostFields({
|
||||
sheet,
|
||||
label,
|
||||
change,
|
||||
onMessage,
|
||||
onOpen,
|
||||
}: {
|
||||
readonly sheet: HostSheet;
|
||||
readonly label: string;
|
||||
readonly change: Change;
|
||||
readonly onMessage: (message: string) => void;
|
||||
readonly onOpen: (page: SheetPage) => void;
|
||||
}) {
|
||||
const t = useTranslate();
|
||||
const address =
|
||||
(field: 'ipAddress' | 'subnetMask' | 'defaultGateway' | 'dnsServer') =>
|
||||
@@ -168,6 +202,12 @@ function HostFields({ sheet, label, change, onMessage }: { readonly sheet: HostS
|
||||
/>
|
||||
<SheetCheck name="ipForwarding" label={t('sheet.ipForwarding')} checked={sheet.ipForwarding} onChange={(on) => change.run(() => setIpForwarding(change.doc, change.id, on, change.origin))} />
|
||||
<SheetCheck name="dhcpClient" label={t('sheet.dhcpClient')} checked={sheet.dhcpClient} onChange={(on) => change.run(() => setDhcpClient(change.doc, change.id, on, change.origin))} />
|
||||
{/* Java closes the server's dialog while DHCP configures the card. */}
|
||||
<div className="sheet-buttons">
|
||||
<button type="button" disabled={sheet.dhcpClient} onClick={() => onOpen('dhcp')}>
|
||||
{t('sheet.dhcpServer')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user