/** The picture of what a scenario asks for. Needs nothing running. */ import type { Scenario } from "../declaration/types.ts"; import { planRouters, routerMachineName, ttlSeconds } from "../lifecycle/router.ts"; import { depthOf, type Diagram, type DiagramMachine, type DiagramSegment } from "./model.ts"; export function diagramFromDeclaration(scenario: Scenario): Diagram { const parentOf = (name: string) => scenario.segments[name]?.gateway?.to; const segments: DiagramSegment[] = Object.entries(scenario.segments).map(([name, segment]) => ({ name, kind: segment.kind, cidr: segment.cidr, mtu: segment.mtu, behind: segment.gateway?.to, depth: depthOf(name, parentOf), })); const machines: DiagramMachine[] = Object.entries(scenario.machines).map(([name, spec]) => { const notes: string[] = []; if (spec.inbound === "deny") notes.push("refuses inbound"); for (const publication of spec.published ?? []) { notes.push(`published :${publication.port} via ${publication.on}`); } return { name, kind: "machine" as const, notes, attachments: spec.at === "detached" ? [] : spec.at.map((a) => ({ segment: a.segment, addresses: a.address })), }; }); // Routers are implicit in a declaration — a scenario says a segment sits behind one and // never names it. The diagram has to show them anyway, or the picture omits the machines // that carry every interesting property. for (const plan of planRouters(scenario, "diagram")) { const notes: string[] = []; notes.push(plan.nat.length ? `NAT ${plan.nat.join("+")}` : "routed, no NAT"); notes.push(plan.forwardable ? "forwardable" : "NOT forwardable"); const ttl = ttlSeconds(plan.mappingTtl); if (ttl !== undefined) notes.push(`mappings expire ${ttl}s`); machines.push({ name: routerMachineName(plan), kind: "router", notes, attachments: [ { segment: plan.outside, addresses: plan.outsideAddresses }, ...plan.inside.map((segment) => ({ segment, addresses: [] })), ], }); } const publicSegments = segments.filter((s) => s.kind === "public"); if (publicSegments.length > 1) { machines.push({ name: "transit", kind: "transit", notes: ["routed, never bridged"], attachments: publicSegments.map((s) => ({ segment: s.name, addresses: [] })), }); } return { title: scenario.scenario, source: "declared", segments, machines }; }