The lab raises a mesh, draws it, and now places tier 0 inside it #1
+35
-14
@@ -29,7 +29,10 @@
|
||||
import type { Diagram, DiagramMachine } from "./model.ts";
|
||||
|
||||
const LANE_MIN_HEIGHT = 170;
|
||||
const LANE_GAP = 80;
|
||||
const LANE_EMPTY_HEIGHT = 62;
|
||||
// Wide enough that a gateway placed on a boundary sits BETWEEN the lanes rather than on
|
||||
// top of one — an earlier value let a router's box cover the lane's own name and ranges.
|
||||
const LANE_GAP = 110;
|
||||
const LANE_X = 40;
|
||||
const LANE_WIDTH = 980;
|
||||
const NODE_WIDTH = 150;
|
||||
@@ -163,11 +166,19 @@ export function toDrawio(diagram: Diagram): string {
|
||||
// wrap onto a second row drew that row outside the box it was supposed to be inside.
|
||||
const occupants = new Map<string, number>();
|
||||
for (const machine of diagram.machines) {
|
||||
const first = machine.attachments[0]?.segment;
|
||||
if (first) occupants.set(first, (occupants.get(first) ?? 0) + 1);
|
||||
// Only a machine that sits INSIDE a lane occupies it. A gateway is counted against the
|
||||
// segment it faces but drawn in the gap, so counting it here left a segment holding no
|
||||
// machines at full height with nothing in it.
|
||||
if (machine.attachments.length !== 1) continue;
|
||||
const only = machine.attachments[0]?.segment;
|
||||
if (only) occupants.set(only, (occupants.get(only) ?? 0) + 1);
|
||||
}
|
||||
const laneHeight = (name: string): number => {
|
||||
const rows = Math.max(1, Math.ceil((occupants.get(name) ?? 0) / SLOTS_PER_ROW));
|
||||
const rows = Math.ceil((occupants.get(name) ?? 0) / SLOTS_PER_ROW);
|
||||
// A segment whose only occupants are the gateways in the gaps beside it holds nothing
|
||||
// itself, so it collapses to its own name and ranges. Left at full height it padded a
|
||||
// layered scenario with empty boxes and pushed the interesting rows apart.
|
||||
if (rows === 0) return LANE_EMPTY_HEIGHT;
|
||||
return Math.max(LANE_MIN_HEIGHT, 45 + rows * (NODE_HEIGHT + 45));
|
||||
};
|
||||
|
||||
@@ -233,13 +244,22 @@ export function toDrawio(diagram: Diagram): string {
|
||||
const row = Math.floor(slot / SLOTS_PER_ROW);
|
||||
x = LANE_X + 40 + column * SLOT_WIDTH;
|
||||
if (spans) {
|
||||
// A gateway straddles, so it sits in the gap below the highest lane it joins —
|
||||
// which is what makes at-a-glance reading of "this is the way in" work.
|
||||
const top = machine.attachments
|
||||
.map((a) => a.segment)
|
||||
.filter((name) => laneY.has(name))
|
||||
.reduce((best, name) => ((laneY.get(name) ?? 0) < (laneY.get(best) ?? 0) ? name : best));
|
||||
y = (laneY.get(top) ?? 80) + (laneH.get(top) ?? LANE_MIN_HEIGHT) + LANE_GAP / 2 - NODE_HEIGHT / 2;
|
||||
// A gateway straddles a boundary, and WHICH boundary matters. Its first attachment
|
||||
// is the segment it faces outward on; the rest are the ones it serves. Placing it
|
||||
// below the outward lane put a gateway serving `home` and `devices` above an
|
||||
// unrelated `cafe`, with its connection crossing a network it has nothing to do
|
||||
// with. It belongs immediately above the topmost lane it actually serves.
|
||||
const drawn = machine.attachments.map((a) => a.segment).filter((name) => laneY.has(name));
|
||||
const served = drawn.slice(1);
|
||||
const highest = (names: string[]) =>
|
||||
names.reduce((best, name) => ((laneY.get(name) ?? 0) < (laneY.get(best) ?? 0) ? name : best));
|
||||
if (served.length > 0) {
|
||||
y = (laneY.get(highest(served)) ?? 80) - LANE_GAP / 2 - NODE_HEIGHT / 2;
|
||||
} else {
|
||||
// Transit faces every lane and serves none, so it sits below the topmost.
|
||||
const top = highest(drawn);
|
||||
y = (laneY.get(top) ?? 80) + (laneH.get(top) ?? LANE_MIN_HEIGHT) + LANE_GAP / 2 - NODE_HEIGHT / 2;
|
||||
}
|
||||
} else {
|
||||
y = (laneY.get(first.segment) ?? 80) + 45 + row * (NODE_HEIGHT + 45);
|
||||
}
|
||||
@@ -266,14 +286,15 @@ export function toDrawio(diagram: Diagram): string {
|
||||
.join(" · "),
|
||||
});
|
||||
|
||||
// Badges sit along the top edge, right to left, so the first fact stated is nearest the
|
||||
// resource's own corner and the row grows away from the label underneath it.
|
||||
// Badges sit along the top edge and read left to right in the order the facts are
|
||||
// stated, which is the order the declaration states them in.
|
||||
const badgeRun = badges.length * BADGE + Math.max(0, badges.length - 1) * 3;
|
||||
badges.forEach((badge, index) => {
|
||||
cells.push({
|
||||
id: `${id}-b${index}`,
|
||||
value: badge.code,
|
||||
style: `${STYLE.badge};fillColor=${badge.fill};strokeColor=${badge.stroke}`,
|
||||
x: x + NODE_WIDTH - BADGE - index * (BADGE + 3),
|
||||
x: x + NODE_WIDTH - badgeRun + index * (BADGE + 3),
|
||||
y: y - BADGE / 2,
|
||||
w: BADGE,
|
||||
h: BADGE,
|
||||
|
||||
@@ -87,7 +87,13 @@ export async function diagramFromLive(instanceId: string): Promise<Diagram> {
|
||||
const addresses = (mac ? heldByMac.get(mac) : undefined) ?? heldByName.get(device) ?? [];
|
||||
attachments.push({ segment, addresses });
|
||||
}
|
||||
attachments.sort((a, b) => a.segment.localeCompare(b.segment));
|
||||
// Outside first, then the segments behind it — the same order the declared picture uses,
|
||||
// and what lets the layout place a gateway above the lanes it SERVES rather than below
|
||||
// the one it faces. Sorting alphabetically threw that away.
|
||||
const outside = config["user.mesh-lab.outside"];
|
||||
attachments.sort((a, b) =>
|
||||
a.segment === outside ? -1 : b.segment === outside ? 1 : a.segment.localeCompare(b.segment),
|
||||
);
|
||||
|
||||
const notes: string[] = [];
|
||||
notes.push(item.type === "container" ? "container" : "virtual machine");
|
||||
|
||||
Reference in New Issue
Block a user