feat: F4.8d the documentation mode's report as a PDF, as Java's
ReportRun reads Java's report back with iText: every piece of text with its place, font, size and colour. The editor lays the report out by iText's rules (A4, leading 1.5, loose chunks in the current line, tables 80 % wide with padding 2, fixed traffic columns, page breaks) and draws it with pdf-lib in the PDF's own Helvetica, widths without kerning as iText measures. All device pages of Internet_Komplett, the first page and the data exchange of einfaches_rechnernetz_komplett match Java piece by piece; Java's iText reads our PDF with the same text in the same places. The sidebar gains Java's "Report"; the frames come from the room by a short subscription. Not adopted: true/false for DHCP (Java's own Ja/Nein), hash table orders, an image running off the page, the report writing back into the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ export * from './fields.js';
|
||||
export * from './files.js';
|
||||
export * from './janitor.js';
|
||||
export * from './projectDoc.js';
|
||||
export * from './report.js';
|
||||
export * from './settings.js';
|
||||
export * from './simulation.js';
|
||||
export * from './sheet.js';
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// What FILIUS' report (ReportGenerator.addComponentConfigSection) tells of
|
||||
// each device that has cards (InternetKnoten: computers, notebooks, routers,
|
||||
// home routers; not switches or modems), in the order of the devices: its
|
||||
// label, gateway and DNS server, whether DHCP sets it up (computers only),
|
||||
// its cards, its programs (computers only, in the order they were
|
||||
// installed), its DNS records (with a DNS server), its forwarding table (the
|
||||
// rows whose destination is an address, as Java's holeTabelle and its
|
||||
// filter give them) and a home router's port forwarding (the rows whose
|
||||
// protocol Java knows: TCP, UDP, 6 or 17, written TCP or UDP). And the cards
|
||||
// whose data exchange the report lists, with their device's label and
|
||||
// address.
|
||||
//
|
||||
// Better than Java: a protocol written in small letters is written TCP or
|
||||
// UDP too (Java's switch only knows the capitals and the numbers); the
|
||||
// report changes nothing (Java writes the protocols back into the home
|
||||
// router's table).
|
||||
|
||||
import type * as Y from 'yjs';
|
||||
import { DNS_HOSTS_PATH, isValidJavaIPAddress, readDnsHosts, routeTableRows, type InstallableProgram, type NodeKind, type RouteRow } from '@mdg-filius/core';
|
||||
import { nodeFiles } from './files.js';
|
||||
import { inOrder, ROOTS } from './projectDoc.js';
|
||||
import { deviceSheet } from './sheet.js';
|
||||
import { captureCards, nodeLabel } from './view.js';
|
||||
|
||||
export interface ReportCard {
|
||||
readonly macAddress: string;
|
||||
readonly ipAddress: string;
|
||||
readonly subnetMask: string;
|
||||
}
|
||||
|
||||
export interface ReportDevice {
|
||||
readonly id: string;
|
||||
readonly kind: Extract<NodeKind, 'pc' | 'notebook' | 'router' | 'gateway'>;
|
||||
readonly label: string;
|
||||
readonly gateway: string;
|
||||
readonly dnsServer: string;
|
||||
/** Computers only. */
|
||||
readonly dhcpClient?: boolean;
|
||||
readonly cards: readonly ReportCard[];
|
||||
/** Computers only: the programs with a window (not the DHCP server), in the order they were installed. */
|
||||
readonly programs?: readonly InstallableProgram[];
|
||||
/** With a DNS server: its records. */
|
||||
readonly dnsRecords?: readonly { readonly domainName: string; readonly type: string; readonly rdata: string }[];
|
||||
readonly routes: readonly RouteRow[];
|
||||
/** Home routers only: the rows of its port forwarding Java reports. */
|
||||
readonly portForwarding?: readonly (readonly [protocol: string, publicPort: string, lanIPAddress: string, lanPort: string])[];
|
||||
}
|
||||
|
||||
const PROTOCOLS: Readonly<Record<string, string>> = { tcp: 'TCP', '6': 'TCP', udp: 'UDP', '17': 'UDP' };
|
||||
|
||||
/** The rows Java reports: those whose destination is an address. */
|
||||
const reported = (rows: readonly RouteRow[]) => rows.filter((row) => isValidJavaIPAddress(row.destinationNetwork));
|
||||
|
||||
/** The DNS server's file of records; without it none, as Java reads none. */
|
||||
function hostsText(doc: Y.Doc, id: string): string {
|
||||
try {
|
||||
return nodeFiles(doc, id)?.textFile(DNS_HOSTS_PATH) ?? '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function reportDevices(doc: Y.Doc): ReportDevice[] {
|
||||
const devices: ReportDevice[] = [];
|
||||
for (const [id, node] of inOrder(doc.getMap<Y.Map<unknown>>(ROOTS.nodes))) {
|
||||
const sheet = deviceSheet(doc, id)!;
|
||||
const label = nodeLabel(node);
|
||||
const device = (node.get('settings') as Y.Map<unknown>).get('device') as Y.Map<unknown> | undefined;
|
||||
const dnsServer = (device?.get('dnsServer') as string | undefined) ?? '';
|
||||
if (sheet.kind === 'pc' || sheet.kind === 'notebook') {
|
||||
const programs = [...(node.get('programs') as Y.Map<unknown>).keys()] as InstallableProgram[];
|
||||
const hosts = programs.includes('dnsServer') ? hostsText(doc, id) : undefined;
|
||||
const card = { ipAddress: sheet.ipAddress, subnetMask: sheet.subnetMask };
|
||||
devices.push({
|
||||
id,
|
||||
kind: sheet.kind,
|
||||
label,
|
||||
gateway: sheet.defaultGateway,
|
||||
dnsServer: sheet.dnsServer,
|
||||
dhcpClient: sheet.dhcpClient,
|
||||
cards: [{ macAddress: sheet.macAddress, ...card }],
|
||||
programs: programs.filter((program) => program !== 'dhcpServer'),
|
||||
...(hosts === undefined ? {} : { dnsRecords: readDnsHosts(hosts).map(({ domainName, type, rdata }) => ({ domainName, type, rdata })) }),
|
||||
routes: reported(routeTableRows([card], [], sheet.defaultGateway)),
|
||||
});
|
||||
} else if (sheet.kind === 'router') {
|
||||
devices.push({
|
||||
id,
|
||||
kind: sheet.kind,
|
||||
label,
|
||||
gateway: sheet.gateway,
|
||||
dnsServer,
|
||||
cards: sheet.interfaces.map(({ macAddress, ipAddress, subnetMask }) => ({ macAddress, ipAddress, subnetMask })),
|
||||
routes: reported(sheet.table),
|
||||
});
|
||||
} else if (sheet.kind === 'gateway') {
|
||||
const cards = [sheet.wan, sheet.lan].map(({ macAddress, ipAddress, subnetMask }) => ({ macAddress, ipAddress, subnetMask }));
|
||||
devices.push({
|
||||
id,
|
||||
kind: sheet.kind,
|
||||
label,
|
||||
gateway: sheet.gateway,
|
||||
dnsServer,
|
||||
cards,
|
||||
routes: reported(routeTableRows(cards, [], sheet.gateway)),
|
||||
portForwarding: sheet.portForwarding.flatMap((row) => {
|
||||
const protocol = PROTOCOLS[row.protocolValue.toLowerCase()];
|
||||
return protocol === undefined ? [] : [[protocol, row.publicPortValue, row.lanIPAddress, row.lanPortValue] as const];
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
/** Every card of the reported devices, in their order: its port, its device's label and its address. */
|
||||
export function reportCards(doc: Y.Doc): { readonly portId: string; readonly label: string; readonly ipAddress: string }[] {
|
||||
// Only computers, routers and home routers have cards with an address.
|
||||
return inOrder(doc.getMap<Y.Map<unknown>>(ROOTS.nodes)).flatMap(([id, node]) => captureCards(doc, id).map((card) => ({ portId: card.portId, label: nodeLabel(node), ipAddress: card.ipAddress })));
|
||||
}
|
||||
Reference in New Issue
Block a user