mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(dashboard): Network model has ports, machines and cross-machine links
Server boxes and instance rows carry the listening port, player rows their remote ports when the list has them. With servers on more than one machine the graph lists the machines with their totals, a zone on several machines is a box per machine, a folded machine is one box, and the layout stacks each machine's boxes in a framed band; links between machines are marked. One machine lays out as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,15 @@
|
||||
* (single connections, or instances) and `shown` (the ids of those matching the group's filter). Each shown member is
|
||||
* a link end of its own, and the open box itself has none; `graph.members` finds a member by id.
|
||||
*
|
||||
* Servers carry their listening `port` and their machine (`host`, a token) from master's server list. When they run on
|
||||
* more than one machine, `graph.hosts` lists the machines (master's first) with their totals and the boxes on each;
|
||||
* every server box has its `host`, a zone with instances on several machines is a box per machine, a machine the
|
||||
* viewer collapsed (options.collapsedHosts) is one box of kind 'host', and links between two machines are `cross`.
|
||||
* With one machine there are no hosts and nothing changes.
|
||||
*
|
||||
* layout(graph, width, moved, scroll) places the nodes in six columns in a fixed order, grows open boxes by their rows
|
||||
* and gives each link its path; rows in view of an open box's list are anchors (`anchors`).
|
||||
* and gives each link its path; rows in view of an open box's list are anchors (`anchors`). With several machines
|
||||
* each machine's boxes (columns 1 to 4) are a band of their own, one under the other, framed (`frames`).
|
||||
*/
|
||||
(function (root) {
|
||||
'use strict';
|
||||
@@ -22,6 +29,8 @@
|
||||
// Chat has a column of its own between the worlds and master: every world has a link to it
|
||||
// An open group's box: its header, the filter, the rows (at most MAX_ROWS in view, the rest scroll) and a margin
|
||||
var GROUP = { head: 58, filter: 30, row: 30, maxRows: 8, foot: 6 }; // head: a name on up to two lines and one line under it
|
||||
// A machine's frame: room for its label above its boxes, around them, and between two frames
|
||||
var FRAME = { head: 28, side: 8, gap: 6 };
|
||||
|
||||
function num(v) { return typeof v === 'number' && isFinite(v) ? v : 0; }
|
||||
function rates(r) { r = r || {}; return { packets_in: num(r.packets_in), packets_out: num(r.packets_out), bytes_in: num(r.bytes_in), bytes_out: num(r.bytes_out) }; }
|
||||
@@ -55,6 +64,28 @@
|
||||
return ugcOnly ? 'clients' : 'web';
|
||||
}
|
||||
|
||||
// ":1001", or '' without a port
|
||||
function portLabel(port) { return typeof port === 'number' && port > 0 ? ':' + port : ''; }
|
||||
|
||||
// A box's name with its listening port ("Auth :1001"); a zone box has its instance's port while it has one instance
|
||||
function nameOf(n) {
|
||||
var port = portLabel(n.port);
|
||||
return port ? n.label + ' ' + port : n.label;
|
||||
}
|
||||
|
||||
// A machine for people: its address when the viewer may see it (`names`: token -> address), else its token
|
||||
function hostLabel(host, names) {
|
||||
if (host == null || host === '') return 'unknown machine';
|
||||
return (names && names[host]) || host;
|
||||
}
|
||||
|
||||
// A connection's address with its remote ports (only there with network_ips): "203.0.113.5:51234"
|
||||
function peerAddress(peer) {
|
||||
var ports = [];
|
||||
(peer.servers || []).forEach(function (e) { if (!e.http && typeof e.port === 'number' && ports.indexOf(e.port) === -1) ports.push(e.port); });
|
||||
return String(peer.address || '') + (ports.length ? ':' + ports.join('/') : '');
|
||||
}
|
||||
|
||||
// What a group's filter matches a member by
|
||||
function matches(member, filter) {
|
||||
filter = String(filter || '').trim().toLowerCase();
|
||||
@@ -69,10 +100,33 @@
|
||||
var expanded = options.expanded || {}, filters = options.filters || {};
|
||||
var nodes = {}, edges = {}, members = {};
|
||||
|
||||
// The machines the servers run on; with more than one, boxes are grouped by machine
|
||||
var hostList = [];
|
||||
keys.forEach(function (k) { var h = servers[k].host; if (h && hostList.indexOf(h) === -1) hostList.push(h); });
|
||||
var multi = hostList.length > 1;
|
||||
var collapsed = multi ? (options.collapsedHosts || {}) : {};
|
||||
var hostNames = options.hostNames || {};
|
||||
var masterHost = servers.master ? servers.master.host || null : null;
|
||||
var zoneHosts = {}; // zone -> {host: true}
|
||||
keys.forEach(function (k) {
|
||||
var s = servers[k];
|
||||
if (s.type === 'WORLD') (zoneHosts[num(s.zone)] = zoneHosts[num(s.zone)] || {})[s.host || ''] = true;
|
||||
});
|
||||
// The box a server is in: nodeOf's, a box per machine for a zone on several, or its machine's box when collapsed
|
||||
function place(server) {
|
||||
var spec = nodeOf(server);
|
||||
if (!multi) return spec;
|
||||
var h = server.host || null;
|
||||
if (h && collapsed[h]) return { id: 'host:' + h, kind: 'host', label: hostLabel(h, hostNames), column: h === masterHost ? 3 : 1, order: -1, host: h };
|
||||
if (spec.kind === 'zone' && Object.keys(zoneHosts[spec.zone] || {}).length > 1) spec.id += '@' + (h || '?');
|
||||
spec.host = h;
|
||||
return spec;
|
||||
}
|
||||
|
||||
function node(spec) {
|
||||
var n = nodes[spec.id];
|
||||
if (!n) {
|
||||
n = nodes[spec.id] = { id: spec.id, kind: spec.kind, label: spec.label, column: spec.column, order: spec.order, zone: spec.zone, servers: [], instances: 0,
|
||||
n = nodes[spec.id] = { id: spec.id, kind: spec.kind, label: spec.label, column: spec.column, order: spec.order, zone: spec.zone, host: spec.host, port: null, servers: [], instances: 0,
|
||||
connections: 0, pingSum: 0, pingCount: 0, resends: 0, resendQueue: 0, workersBusy: 0, workersThreads: 0, workersQueued: 0, websockets: 0,
|
||||
packetsIn: 0, packetsOut: 0, bytesIn: 0, bytesOut: 0, http: 0, noSplit: false, open: false, members: [], shown: [] };
|
||||
}
|
||||
@@ -91,6 +145,7 @@
|
||||
}
|
||||
// Adds a link's rates; links between the same two nodes add up (every instance of a zone)
|
||||
function edge(from, to, fwdPackets, fwdBytes, backPackets, backBytes, exact, kind, note) {
|
||||
if (from === to) return null; // inside a collapsed machine
|
||||
var id = from + '>' + to + (kind && kind !== 'packets' ? ':' + kind : '');
|
||||
var e = edges[id];
|
||||
if (!e) e = edges[id] = { id: id, from: from, to: to, kind: kind || 'packets', exact: true, notes: [], fwd: { packets: 0, bytes: 0 }, back: { packets: 0, bytes: 0 }, bytesKnown: { fwd: true, back: true } };
|
||||
@@ -103,7 +158,15 @@
|
||||
return e;
|
||||
}
|
||||
// Where a link to a server ends: the server's box, or its row while its zone is open
|
||||
function endOf(server) { return server.type === 'WORLD' && expanded[num(server.zone)] ? server.key : nodeOf(server).id; }
|
||||
function endOf(server) {
|
||||
var spec = place(server);
|
||||
return spec.kind === 'zone' && expanded[num(server.zone)] ? server.key : spec.id;
|
||||
}
|
||||
// Master, chat and the UGC server as link ends (their machine's box while it is collapsed)
|
||||
var byType = function (type) { return keys.filter(function (k) { return servers[k].type === type; }).map(function (k) { return servers[k]; })[0]; };
|
||||
var masterId = byType('MASTER') ? endOf(byType('MASTER')) : 'master';
|
||||
var chatId = byType('CHAT') ? endOf(byType('CHAT')) : 'chat';
|
||||
var ugcId = byType('UGC') ? endOf(byType('UGC')) : 'ugc';
|
||||
|
||||
var clients = node({ id: 'clients', kind: 'clients', label: 'Game clients', column: 0, order: 0 });
|
||||
var web = node({ id: 'web', kind: 'web', label: 'Web clients', column: 5, order: 0 });
|
||||
@@ -111,15 +174,18 @@
|
||||
|
||||
keys.forEach(function (key) {
|
||||
var s = servers[key];
|
||||
var n = node(nodeOf(s));
|
||||
var n = node(place(s));
|
||||
var link = s.link || {}, gauges = s.gauges || {};
|
||||
// Links end at the zone's box, or at the instance's row while the zone is open
|
||||
var id = s.type === 'WORLD' ? endOf(s) : n.id;
|
||||
n.servers.push(key);
|
||||
// A box of one server has its port (a zone's while it has one instance)
|
||||
n.port = n.kind !== 'host' && n.servers.length === 1 && typeof s.port === 'number' ? s.port : null;
|
||||
if (s.type === 'WORLD') {
|
||||
n.instances++;
|
||||
if (id !== n.id) {
|
||||
member(n, { id: key, kind: 'world', label: '#' + num(s.instance), title: s.label || key, server: key, instance: num(s.instance),
|
||||
port: typeof s.port === 'number' ? s.port : null, host: s.host || null,
|
||||
connections: num(link.connections), pingSum: num(link.connections) > 0 ? num(link.ping_ms) : 0, pingCount: num(link.connections) > 0 ? 1 : 0,
|
||||
resends: num(link.resends), search: (String(s.label || key) + ' #' + num(s.instance) + ' ' + key).toLowerCase() });
|
||||
}
|
||||
@@ -145,10 +211,10 @@
|
||||
if (s.type !== 'MASTER') {
|
||||
if (split) {
|
||||
var m = rates(split.master);
|
||||
if (any(m)) edge(id, 'master', m.packets_out, m.bytes_out, m.packets_in, m.bytes_in, true);
|
||||
if (any(m)) edge(id, masterId, m.packets_out, m.bytes_out, m.packets_in, m.bytes_in, true);
|
||||
} else if (s.type === 'DASHBOARD' || s.type === 'UGC' || s.type === 'CHAT') {
|
||||
// Nearly all of their packets are with master (chat's are with the worlds too)
|
||||
edge(id, 'master', totals.packets_out, totals.bytes_out, totals.packets_in, totals.bytes_in, false, 'packets', ESTIMATED);
|
||||
edge(id, masterId, totals.packets_out, totals.bytes_out, totals.packets_in, totals.bytes_in, false, 'packets', ESTIMATED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +225,7 @@
|
||||
var who = s.type === 'UGC' ? 'clients' : 'web';
|
||||
if (requests > 0 || bytesOut > 0) edge(who, id, requests, null, requests, bytesOut, !!split, 'http', split ? '' : 'other servers\' requests not told apart (older server)');
|
||||
// The dashboard's own requests to the UGC server, counted where they were made
|
||||
if (split && num(split.http_out) > 0) edge(id, 'ugc', split.http_out, null, split.http_out, split.http_out_bytes_in, true, 'http');
|
||||
if (split && num(split.http_out) > 0) edge(id, ugcId, split.http_out, null, split.http_out, split.http_out_bytes_in, true, 'http');
|
||||
return;
|
||||
}
|
||||
if (s.type === 'MASTER') return;
|
||||
@@ -171,8 +237,8 @@
|
||||
if (any(c) || players > 0) edge('clients', id, c.packets_in, c.bytes_in, c.packets_out, c.bytes_out, true);
|
||||
var o = rates(split.servers);
|
||||
// A world's "other servers" is its chat link; chat's are the worlds, drawn from their side
|
||||
if (s.type === 'WORLD' && any(o)) edge(id, hasChat ? 'chat' : 'master', o.packets_out, o.bytes_out, o.packets_in, o.bytes_in, hasChat, 'packets', hasChat ? '' : 'chat link, chat not reporting');
|
||||
else if (s.type !== 'CHAT' && any(o)) edge(id, 'master', o.packets_out, o.bytes_out, o.packets_in, o.bytes_in, false, 'servers', 'with other servers, drawn to master');
|
||||
if (s.type === 'WORLD' && any(o)) edge(id, hasChat ? chatId : masterId, o.packets_out, o.bytes_out, o.packets_in, o.bytes_in, hasChat, 'packets', hasChat ? '' : 'chat link, chat not reporting');
|
||||
else if (s.type !== 'CHAT' && any(o)) edge(id, masterId, o.packets_out, o.bytes_out, o.packets_in, o.bytes_in, false, 'servers', 'with other servers, drawn to master');
|
||||
} else if (s.type !== 'CHAT') {
|
||||
edge('clients', id, totals.packets_in, totals.bytes_in, totals.packets_out, totals.bytes_out, false, 'packets', ESTIMATED);
|
||||
}
|
||||
@@ -200,7 +266,7 @@
|
||||
seen[id] = true;
|
||||
var names = [peer.character, peer.account, peer.user];
|
||||
var p = member(nodes[groupId], { id: id, kind: 'peer', label: String(peer.character || peer.account || peer.user || peer.address || ''), peer: peer,
|
||||
search: names.concat([options.addressesShown ? peer.address : ''], (peer.servers || []).map(function (e) { return e.label; }))
|
||||
search: names.concat([options.addressesShown ? peerAddress(peer) : ''], (peer.servers || []).map(function (e) { return e.label; }))
|
||||
.filter(Boolean).join(' ').toLowerCase() });
|
||||
(peer.servers || []).forEach(function (entry) {
|
||||
var target = servers[entry.server];
|
||||
@@ -233,8 +299,33 @@
|
||||
var linked = {};
|
||||
Object.keys(edges).forEach(function (k) { linked[edges[k].from] = linked[edges[k].to] = true; });
|
||||
list = list.filter(function (n) { return n.servers.length || n.open || linked[n.id] || (n.kind === 'clients' && n.connections > 0); });
|
||||
list.sort(function (a, b) { return a.column - b.column || a.order - b.order || String(a.label).localeCompare(String(b.label)); });
|
||||
return { nodes: list, edges: edgeList, members: members };
|
||||
list.sort(function (a, b) { return a.column - b.column || a.order - b.order || String(a.label).localeCompare(String(b.label)) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0); });
|
||||
|
||||
// Several machines: each with its totals and its boxes, master's first; links between two machines are marked
|
||||
var hosts = [];
|
||||
if (multi) {
|
||||
var byHost = {};
|
||||
hosts = hostList.slice().sort(function (a, b) { return (b === masterHost) - (a === masterHost) || (a < b ? -1 : a > b ? 1 : 0); }).map(function (h) {
|
||||
return byHost[h] = { id: h, label: hostLabel(h, hostNames), master: h === masterHost, collapsed: !!collapsed[h], nodes: [], servers: 0, connections: 0,
|
||||
packetsIn: 0, packetsOut: 0, bytesIn: 0, bytesOut: 0 };
|
||||
});
|
||||
keys.forEach(function (k) {
|
||||
var s = servers[k], h = byHost[s.host];
|
||||
if (!h) return;
|
||||
h.servers++;
|
||||
h.connections += num((s.link || {}).connections);
|
||||
h.packetsIn += num(s.packets_in); h.packetsOut += num(s.packets_out);
|
||||
h.bytesIn += num(s.bytes_in); h.bytesOut += num(s.bytes_out);
|
||||
});
|
||||
list.forEach(function (n) { if (n.host && byHost[n.host]) byHost[n.host].nodes.push(n.id); });
|
||||
}
|
||||
var hostOf = function (id) { return (nodes[id] && nodes[id].host) || (members[id] && members[id].host) || null; };
|
||||
edgeList.forEach(function (e) {
|
||||
var a = multi ? hostOf(e.from) : null, b = multi ? hostOf(e.to) : null;
|
||||
e.cross = !!(a && b && a !== b);
|
||||
if (e.cross) e.hosts = [a, b];
|
||||
});
|
||||
return { nodes: list, edges: edgeList, members: members, hosts: hosts };
|
||||
}
|
||||
|
||||
// Load of a link against its recent peak: 'idle', 'low', 'mid' or 'high'
|
||||
@@ -279,15 +370,47 @@
|
||||
var xs = [0, 0.2, 0.4, 0.6, 0.8, 1].map(function (f) { return Math.round(pad + nodeWidth / 2 + f * usable); });
|
||||
var heightOf = function (n) { return n.open ? groupHeight(n.shown.length) : nodeHeight; };
|
||||
var at = {};
|
||||
// Rows at a fixed pitch from the top, each column centred on the tallest; an open box pushes the boxes under it
|
||||
// down by what it grew, so opening a group only moves the boxes of its own column
|
||||
byColumn.forEach(function (column, c) {
|
||||
var top = pad + (rows - column.length) * rowHeight / 2, extra = 0;
|
||||
// Rows at a fixed pitch from `top`; an open box pushes the boxes under it down by what it grew, so opening a group
|
||||
// only moves the boxes of its own column. Returns where the column ends.
|
||||
var stackColumn = function (column, c, top) {
|
||||
var extra = 0;
|
||||
column.forEach(function (n, i) {
|
||||
at[n.id] = { x: xs[c], y: Math.round(top + rowHeight * (i + 0.5) + extra), h: heightOf(n), column: c };
|
||||
extra += at[n.id].h - nodeHeight;
|
||||
});
|
||||
});
|
||||
return top + rowHeight * column.length + extra;
|
||||
};
|
||||
var hosts = graph.hosts || [];
|
||||
if (hosts.length < 2) {
|
||||
// One machine: each column centred on the tallest
|
||||
byColumn.forEach(function (column, c) { stackColumn(column, c, pad + (rows - column.length) * rowHeight / 2); });
|
||||
} else {
|
||||
// Several machines: each machine's boxes of columns 1 to 4 are a band, one under the other (master's first, the
|
||||
// servers of no known machine last, unframed); game and web clients are centred on them all
|
||||
var bandOf = {};
|
||||
hosts.forEach(function (h, i) { bandOf[h.id] = i; });
|
||||
var bands = hosts.map(function () { return COLUMNS.map(function () { return []; }); }).concat([COLUMNS.map(function () { return []; })]);
|
||||
graph.nodes.forEach(function (n) {
|
||||
if (n.column === 0 || n.column === COLUMNS.length - 1) return;
|
||||
var b = n.host != null && bandOf[n.host] != null ? bandOf[n.host] : hosts.length;
|
||||
bands[b][n.column].push(n);
|
||||
});
|
||||
var cursor = pad;
|
||||
bands.forEach(function (band, b) {
|
||||
var bandRows = Math.max.apply(null, band.map(function (c) { return c.length; }));
|
||||
if (!bandRows) return;
|
||||
var start = cursor + (b < hosts.length ? FRAME.head : 0), bottom = start;
|
||||
band.forEach(function (column, c) {
|
||||
if (column.length) bottom = Math.max(bottom, stackColumn(column, c, start + (bandRows - column.length) * rowHeight / 2));
|
||||
});
|
||||
cursor = bottom + FRAME.gap;
|
||||
});
|
||||
[0, COLUMNS.length - 1].forEach(function (c) {
|
||||
var column = byColumn[c];
|
||||
stackColumn(column, c, pad + Math.max(0, (cursor - pad - column.length * rowHeight) / 2));
|
||||
});
|
||||
height = Math.max(height, cursor + pad);
|
||||
}
|
||||
moved = moved || {};
|
||||
scroll = scroll || {};
|
||||
var clampX = function (x) { return Math.round(Math.min(totalWidth - pad - nodeWidth / 2, Math.max(pad + nodeWidth / 2, x))); };
|
||||
@@ -300,6 +423,22 @@
|
||||
});
|
||||
graph.nodes.forEach(function (n) { var a = at[n.id]; height = Math.max(height, a.y - nodeHeight / 2 + a.h + pad); });
|
||||
|
||||
// Each machine's frame: around its boxes (wherever they were dragged), its label above them
|
||||
var frames = {};
|
||||
if (hosts.length > 1) {
|
||||
hosts.forEach(function (h) {
|
||||
var ids = h.nodes.filter(function (id) { return at[id]; });
|
||||
if (!ids.length) return;
|
||||
var x1 = Infinity, x2 = -Infinity, y1 = Infinity, y2 = -Infinity;
|
||||
ids.forEach(function (id) {
|
||||
var a = at[id];
|
||||
x1 = Math.min(x1, a.x - nodeWidth / 2); x2 = Math.max(x2, a.x + nodeWidth / 2);
|
||||
y1 = Math.min(y1, a.y - nodeHeight / 2); y2 = Math.max(y2, a.y - nodeHeight / 2 + a.h);
|
||||
});
|
||||
frames[h.id] = { x: Math.round(x1 - FRAME.side), y: Math.round(y1 - FRAME.head + 2), w: Math.round(x2 - x1 + FRAME.side * 2), h: Math.round(y2 - y1 + FRAME.head - 2 + FRAME.side) };
|
||||
});
|
||||
}
|
||||
|
||||
// An open box's rows in view of its list are link ends of their own, on the box's sides
|
||||
var lists = {}, anchors = {};
|
||||
graph.nodes.forEach(function (n) {
|
||||
@@ -343,9 +482,12 @@
|
||||
var a = endOf(e.from), b = endOf(e.to);
|
||||
if (!a || !b) return;
|
||||
var pa = withPort(a, ports[e.id] && ports[e.id].a), pb = withPort(b, ports[e.id] && ports[e.id].b);
|
||||
paths[e.id] = { fwd: curve(pa, pb, nodeWidth, 3.5, nodeHeight), back: curve(pb, pa, nodeWidth, 3.5, nodeHeight) };
|
||||
var fwd = curve(pa, pb, nodeWidth, 3.5, nodeHeight);
|
||||
paths[e.id] = { fwd: fwd.d, back: curve(pb, pa, nodeWidth, 3.5, nodeHeight).d };
|
||||
// A link between two machines gets a mark half way
|
||||
if (e.cross) paths[e.id].mid = fwd.mid;
|
||||
});
|
||||
return { width: totalWidth, height: height, nodeWidth: nodeWidth, nodeHeight: nodeHeight, nodes: at, lists: lists, anchors: anchors, paths: paths };
|
||||
return { width: totalWidth, height: height, nodeWidth: nodeWidth, nodeHeight: nodeHeight, nodes: at, lists: lists, anchors: anchors, paths: paths, frames: frames };
|
||||
}
|
||||
|
||||
// A curve between two boxes (or an open box's rows), leaving and entering on the sides that face each other
|
||||
@@ -381,9 +523,12 @@
|
||||
var dx = x2 - x1, dy = y2 - y1, len = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
var nx = -dy / len * offset, ny = dx / len * offset;
|
||||
var p = function (x, y) { return (Math.round((x + nx) * 10) / 10) + ',' + (Math.round((y + ny) * 10) / 10); };
|
||||
return 'M' + p(x1, y1) + ' C' + p(c1[0], c1[1]) + ' ' + p(c2[0], c2[1]) + ' ' + p(x2, y2);
|
||||
// The curve's point half way (t = 0.5)
|
||||
var mid = { x: Math.round(((x1 + 3 * c1[0] + 3 * c2[0] + x2) / 8 + nx) * 10) / 10, y: Math.round(((y1 + 3 * c1[1] + 3 * c2[1] + y2) / 8 + ny) * 10) / 10 };
|
||||
return { d: 'M' + p(x1, y1) + ' C' + p(c1[0], c1[1]) + ' ' + p(c2[0], c2[1]) + ' ' + p(x2, y2), mid: mid };
|
||||
}
|
||||
|
||||
var api = { build: build, layout: layout, loadClass: loadClass, width: width, speed: speed, matches: matches, groupHeight: groupHeight, GROUP: GROUP };
|
||||
var api = { build: build, layout: layout, loadClass: loadClass, width: width, speed: speed, matches: matches, groupHeight: groupHeight, GROUP: GROUP, FRAME: FRAME,
|
||||
portLabel: portLabel, nameOf: nameOf, hostLabel: hostLabel, peerAddress: peerAddress };
|
||||
root.NetworkGraph = api;
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
|
||||
@@ -164,6 +164,87 @@ same(G.layout(graph, 1000), L, 'the same layout every time');
|
||||
same(Z.paths['clients>world:1200:1'] && Z.paths['world:1200:1>chat'] ? true : false, true, 'instance rows are linked on both sides');
|
||||
}
|
||||
|
||||
// Listening ports: on a server's box, and on an open zone's instance rows; remote ports only when the list has them
|
||||
{
|
||||
const withPorts = JSON.parse(JSON.stringify(summary));
|
||||
withPorts.servers.auth.port = 1001;
|
||||
withPorts.servers['world:1200:1'].port = 3015;
|
||||
withPorts.servers['world:1200:2'].port = 3016;
|
||||
same(G.portLabel(1001), ':1001', 'port label');
|
||||
same(G.portLabel(null), '', 'no port, no label');
|
||||
const g = G.build(withPorts, {});
|
||||
same(G.nameOf(g.nodes.find((n) => n.id === 'auth')), 'Auth :1001', 'auth box with its port');
|
||||
same(g.nodes.find((n) => n.id === 'zone:1200').port, null, 'a zone of two instances has no one port');
|
||||
same(G.nameOf(g.nodes.find((n) => n.id === 'chat')), 'Chat', 'no port known: just the name');
|
||||
const one = JSON.parse(JSON.stringify(withPorts));
|
||||
delete one.servers['world:1200:2'];
|
||||
same(G.nameOf(G.build(one, {}).nodes.find((n) => n.id === 'zone:1200')), 'World 1200 Nimbus Station :3015', 'a zone of one instance has its port');
|
||||
const open = G.build(withPorts, { expanded: { 1200: true } });
|
||||
same(open.members['world:1200:2'].port, 3016, 'an instance row has its port');
|
||||
same(G.peerAddress({ address: '203.0.113.5', servers: [{ port: 51234 }, { http: true, port: 80 }] }), '203.0.113.5:51234', 'a player\'s remote port');
|
||||
same(G.peerAddress({ address: 'peer-0001', servers: [{ port: null }] }), 'peer-0001', 'no remote port without network_ips');
|
||||
same(G.build(withPorts, { clientsExpanded: true, addressesShown: true, peers: [{ address: '203.0.113.5', kind: 'game', account: 'x', servers: [{ server: 'auth', port: 51234 }] }],
|
||||
filters: { clients: '51234' } }).nodes.find((n) => n.id === 'clients').shown.length, 1, 'filter by remote port with network_ips');
|
||||
}
|
||||
|
||||
// One machine: no frames, nothing marked, the same layout as without machines
|
||||
{
|
||||
const same1 = JSON.parse(JSON.stringify(summary));
|
||||
Object.values(same1.servers).forEach((s) => { s.host = 'peer-aaaa'; });
|
||||
const g = G.build(same1, {});
|
||||
same(g.hosts, [], 'one machine: no hosts');
|
||||
same(g.edges.some((e) => e.cross), false, 'no link crosses machines');
|
||||
same(g.nodes.map((n) => n.id), ids, 'the same boxes');
|
||||
const L1 = G.layout(g, 1000);
|
||||
same(L1.frames, {}, 'no frames');
|
||||
same(L1.nodes, G.layout(graph, 1000).nodes, 'the same places');
|
||||
}
|
||||
|
||||
// Two machines: the second world, chat and UGC on another; frames, totals and marked links
|
||||
{
|
||||
const two = JSON.parse(JSON.stringify(summary));
|
||||
Object.values(two.servers).forEach((s) => { s.host = 'peer-aaaa'; });
|
||||
two.servers['world:1200:2'].host = 'peer-bbbb';
|
||||
two.servers.chat.host = 'peer-bbbb';
|
||||
delete two.servers['service:99:0:0'];
|
||||
const g = G.build(two, { hostNames: { 'peer-bbbb': '198.51.100.20' } });
|
||||
same(g.hosts.map((h) => h.id), ['peer-aaaa', 'peer-bbbb'], 'two machines, master\'s first');
|
||||
same(g.hosts[0].master, true, 'master\'s machine is marked');
|
||||
same(g.hosts[1].label, '198.51.100.20', 'an address when the viewer may see it');
|
||||
same(g.hosts[0].label, 'peer-aaaa', 'else its token');
|
||||
same(g.hosts[1].servers, 2, 'servers on the second machine');
|
||||
same(g.hosts[1].connections, 6 + 2, 'its connections');
|
||||
same(g.hosts[1].packetsIn, 10 + 2, 'its packets');
|
||||
same(g.nodes.some((n) => n.id === 'zone:1200@peer-aaaa') && g.nodes.some((n) => n.id === 'zone:1200@peer-bbbb'), true, 'a zone on both machines is a box on each');
|
||||
same(g.hosts[1].nodes.sort(), ['chat', 'zone:1200@peer-bbbb'], 'the second machine\'s boxes');
|
||||
same(g.edges.find((e) => e.id === 'zone:1200@peer-aaaa>chat').cross, true, 'a world to chat on another machine is marked');
|
||||
same(g.edges.find((e) => e.id === 'zone:1200@peer-bbbb>master').cross, true, 'the other machine\'s world to master too');
|
||||
same(g.edges.find((e) => e.id === 'zone:1200@peer-aaaa>master').cross, false, 'on the same machine it is not');
|
||||
same(g.edges.find((e) => e.id === 'auth>master').cross, false, 'auth and master share a machine');
|
||||
same(g.edges.find((e) => e.id === 'clients>auth').cross, false, 'players are no machine');
|
||||
const L2 = G.layout(g, 1000);
|
||||
same(Object.keys(L2.frames), ['peer-aaaa', 'peer-bbbb'], 'a frame per machine');
|
||||
const fa = L2.frames['peer-aaaa'], fb = L2.frames['peer-bbbb'];
|
||||
same(fa.y + fa.h <= fb.y, true, 'the frames do not overlap: one under the other');
|
||||
const inside = (f, id) => { const a = L2.nodes[id]; return a.x - L2.nodeWidth / 2 >= f.x && a.x + L2.nodeWidth / 2 <= f.x + f.w && a.y - L2.nodeHeight / 2 >= f.y && a.y - L2.nodeHeight / 2 + a.h <= f.y + f.h; };
|
||||
same(['auth', 'master', 'dashboard', 'ugc', 'zone:1200@peer-aaaa'].every((id) => inside(fa, id)), true, 'master\'s machine frames its boxes');
|
||||
same(['chat', 'zone:1200@peer-bbbb'].every((id) => inside(fb, id)), true, 'and the other its own');
|
||||
same(L2.paths['zone:1200@peer-aaaa>chat'].mid && typeof L2.paths['zone:1200@peer-aaaa>chat'].mid.x === 'number', true, 'a crossing link has a mark half way');
|
||||
same(L2.paths['auth>master'].mid, undefined, 'others have none');
|
||||
// A dragged box takes its frame along
|
||||
const D = G.layout(g, 1000, { chat: { fx: 0.5, y: 1500 } });
|
||||
same(D.frames['peer-bbbb'].y + D.frames['peer-bbbb'].h >= 1500 + D.nodeHeight / 2, true, 'the frame follows a dragged box');
|
||||
// Collapsed: the machine is one box, its links go to it, links inside it are gone
|
||||
const c = G.build(two, { collapsedHosts: { 'peer-bbbb': true } });
|
||||
same(c.nodes.some((n) => n.id === 'chat'), false, 'the collapsed machine\'s boxes are gone');
|
||||
const hostBox = c.nodes.find((n) => n.id === 'host:peer-bbbb');
|
||||
same(hostBox && hostBox.kind, 'host', 'one box for the machine');
|
||||
same(c.edges.find((e) => e.id === 'zone:1200@peer-aaaa>host:peer-bbbb').cross, true, 'links into it are marked');
|
||||
same(c.edges.some((e) => e.from === e.to), false, 'links inside it are gone');
|
||||
same(c.hosts[1].collapsed, true, 'collapsed');
|
||||
same(Object.keys(G.layout(c, 1000).frames).length, 2, 'still framed');
|
||||
}
|
||||
|
||||
if (failures) {
|
||||
console.error(`${failures} failure(s)`);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user