The lab raises a mesh, draws it, and now places tier 0 inside it #1
+34
-13
@@ -29,7 +29,10 @@
|
|||||||
import type { Diagram, DiagramMachine } from "./model.ts";
|
import type { Diagram, DiagramMachine } from "./model.ts";
|
||||||
|
|
||||||
const LANE_MIN_HEIGHT = 170;
|
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_X = 40;
|
||||||
const LANE_WIDTH = 980;
|
const LANE_WIDTH = 980;
|
||||||
const NODE_WIDTH = 150;
|
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.
|
// wrap onto a second row drew that row outside the box it was supposed to be inside.
|
||||||
const occupants = new Map<string, number>();
|
const occupants = new Map<string, number>();
|
||||||
for (const machine of diagram.machines) {
|
for (const machine of diagram.machines) {
|
||||||
const first = machine.attachments[0]?.segment;
|
// Only a machine that sits INSIDE a lane occupies it. A gateway is counted against the
|
||||||
if (first) occupants.set(first, (occupants.get(first) ?? 0) + 1);
|
// 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 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));
|
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);
|
const row = Math.floor(slot / SLOTS_PER_ROW);
|
||||||
x = LANE_X + 40 + column * SLOT_WIDTH;
|
x = LANE_X + 40 + column * SLOT_WIDTH;
|
||||||
if (spans) {
|
if (spans) {
|
||||||
// A gateway straddles, so it sits in the gap below the highest lane it joins —
|
// A gateway straddles a boundary, and WHICH boundary matters. Its first attachment
|
||||||
// which is what makes at-a-glance reading of "this is the way in" work.
|
// is the segment it faces outward on; the rest are the ones it serves. Placing it
|
||||||
const top = machine.attachments
|
// below the outward lane put a gateway serving `home` and `devices` above an
|
||||||
.map((a) => a.segment)
|
// unrelated `cafe`, with its connection crossing a network it has nothing to do
|
||||||
.filter((name) => laneY.has(name))
|
// with. It belongs immediately above the topmost lane it actually serves.
|
||||||
.reduce((best, name) => ((laneY.get(name) ?? 0) < (laneY.get(best) ?? 0) ? name : best));
|
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;
|
y = (laneY.get(top) ?? 80) + (laneH.get(top) ?? LANE_MIN_HEIGHT) + LANE_GAP / 2 - NODE_HEIGHT / 2;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
y = (laneY.get(first.segment) ?? 80) + 45 + row * (NODE_HEIGHT + 45);
|
y = (laneY.get(first.segment) ?? 80) + 45 + row * (NODE_HEIGHT + 45);
|
||||||
}
|
}
|
||||||
@@ -266,14 +286,15 @@ export function toDrawio(diagram: Diagram): string {
|
|||||||
.join(" · "),
|
.join(" · "),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Badges sit along the top edge, right to left, so the first fact stated is nearest the
|
// Badges sit along the top edge and read left to right in the order the facts are
|
||||||
// resource's own corner and the row grows away from the label underneath it.
|
// 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) => {
|
badges.forEach((badge, index) => {
|
||||||
cells.push({
|
cells.push({
|
||||||
id: `${id}-b${index}`,
|
id: `${id}-b${index}`,
|
||||||
value: badge.code,
|
value: badge.code,
|
||||||
style: `${STYLE.badge};fillColor=${badge.fill};strokeColor=${badge.stroke}`,
|
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,
|
y: y - BADGE / 2,
|
||||||
w: BADGE,
|
w: BADGE,
|
||||||
h: 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) ?? [];
|
const addresses = (mac ? heldByMac.get(mac) : undefined) ?? heldByName.get(device) ?? [];
|
||||||
attachments.push({ segment, addresses });
|
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[] = [];
|
const notes: string[] = [];
|
||||||
notes.push(item.type === "container" ? "container" : "virtual machine");
|
notes.push(item.type === "container" ? "container" : "virtual machine");
|
||||||
|
|||||||
Reference in New Issue
Block a user