Other campuses set themselves up: one install line, one join line per computer, keys made from what is found
settings/discover.py reads Companion's connections (and their addresses and channel counts), the Dante devices, the network MIDI sessions here and on joined Macs, this Mac's wired network, and what runs on each joined computer. settings/autoconf.py turns that into keys, checks, start and restart fixes and a page layout the way North Attleboro's were made (group keys only when not everything fits; receivers fold first), all through the existing builders and rules. settings/campus.py prepares Companion through its internal API (the panel's custom variables and the blink trigger), picks free pages, applies the first config, serves the program kit, the campus install line and the Mac/PC join lines, and looks every ten minutes to add new gear (discover.auto; never during a service or a recording, never removes, never re-adds what someone took off). Settings > Auto-setup shows what was found and the lines to paste. install.sh keeps the kit and makes the panel's login key. Checked on North Attleboro read-only: discovery finds all 43 connections, 19 Dante devices, four Macs' MIDI sessions and seven computers, and a from-scratch build reproduces NA's layout. The Companion calls were tried with a throwaway variable and a disabled trigger, then removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+27
-1
@@ -151,7 +151,7 @@
|
||||
if (f.type === "dict") { try { return raw.trim() ? JSON.parse(raw) : undefined; } catch (e) { return undefined; } }
|
||||
return raw === "" ? undefined : raw;
|
||||
}
|
||||
const LABELS = { after_seconds: "wait before acting (seconds)", note_seconds: "keep showing ON … after (seconds)", interval_seconds: "look every (seconds)", failures: "misses before it counts", debounce_seconds: "a fault must last (seconds)", editing_code: "PIN", ssh: "log-in address", mac: "hardware address", ingest_ips: "this Mac's addresses", ingest_name: "this Mac's name", ingest_hostname: "network name", hold_always: "hold also when green", page_hold_ms: "hold time (ms)", optional: "may have no disk" };
|
||||
const LABELS = { auto: "add new gear by itself", after_seconds: "wait before acting (seconds)", note_seconds: "keep showing ON … after (seconds)", interval_seconds: "look every (seconds)", failures: "misses before it counts", debounce_seconds: "a fault must last (seconds)", editing_code: "PIN", ssh: "log-in address", mac: "hardware address", ingest_ips: "this Mac's addresses", ingest_name: "this Mac's name", ingest_hostname: "network name", hold_always: "hold also when green", page_hold_ms: "hold time (ms)", optional: "may have no disk" };
|
||||
function inputHtml(path, f, value, extra) {
|
||||
if (!f) return "";
|
||||
const id = "f-" + path.replace(/[^A-Za-z0-9]/g, "-");
|
||||
@@ -877,6 +877,7 @@
|
||||
["outlets", "Outlets", "Smart plugs the panel may switch, for a device that only a power cycle brings back"],
|
||||
["companion", "Companion", "Connections, and names the keys use that are not here"],
|
||||
["network", "Network", "This Mac's network port and the Dante switches"],
|
||||
["setup", "Auto-setup", "What this Mac found, the line each computer runs to join, and the line that sets up another campus"],
|
||||
["site", "Site & PIN", "The campus, contacts, this Mac, the PIN"],
|
||||
["program", "Program & campus", "Update the code, export or start a campus"],
|
||||
["advanced", "Advanced", "Every number and name, by screen"],
|
||||
@@ -902,6 +903,7 @@
|
||||
else if (which === "outlets") body = await outletsHtml();
|
||||
else if (which === "companion") body = (await namesHtml()) + (await companionHtml());
|
||||
else if (which === "network") body = fieldsHtml("Network & Dante");
|
||||
else if (which === "setup") body = await setupHtml(c);
|
||||
else if (which === "site") body = siteHtml(c);
|
||||
else if (which === "program") body = await programHtml();
|
||||
else if (which === "advanced") body = advancedHtml();
|
||||
@@ -928,6 +930,28 @@
|
||||
const screens = [...new Set(Object.values(state.schema.fields).map((f) => f.screen))];
|
||||
return screens.map((s) => { const h = fieldsHtml(s, true); return h ? `<h3>${esc(s)}</h3>${h}` : ""; }).join("") || "<p class='muted'>Nothing.</p>";
|
||||
}
|
||||
// Auto-setup: what was found and added, the join lines, and the install line for another campus.
|
||||
async function setupHtml(c) {
|
||||
const d = await api("/api/discover").catch(() => null);
|
||||
if (!d) return "<p class='muted'>Auto-setup is not answering.</p>";
|
||||
const last = d.last || {}, inv = last.inventory || {};
|
||||
const line = (text, h) => `<div class="field"><span class="fl">${h}</span><input class="mono small copyline" readonly value="${esc(text)}"></div>`;
|
||||
const comps = Object.entries(inv.computers || {}).map(([a, x]) => `<tr><td>${lightDot(x.reachable ? "ok" : "problem", x.reachable ? "logs in" : x.error || "does not let this Mac in yet")}${esc(x.name || a)}</td><td class="small">${esc(x.os || "")}</td><td class="mono small">${esc(x.ip || "")}</td><td class="small muted">${x.reachable ? "" : esc(x.error || "run the join line on it")}</td></tr>`).join("");
|
||||
const joined = Object.entries(d.joined || {}).filter(([n]) => !Object.values(inv.computers || {}).some((x) => x.name === n)).map(([n, j]) => `<tr><td>${lightDot("warn", "joined; the next look sets it up")}${esc(n)}</td><td class="small">${esc(j.os)}</td><td class="mono small">${esc((j.ips || [])[0] || "")}</td><td class="small muted">joined ${esc(when(j.at))}</td></tr>`).join("");
|
||||
const list = (xs) => xs && xs.length ? `<ul class="small">${xs.map((x) => `<li>${esc(x)}</li>`).join("")}</ul>` : "<p class='small muted'>nothing</p>";
|
||||
const campuses = [["ab", "Apollo Beach"], ["sc", "Southcoast"], ["ws", "Woonsocket"], ["na", "North Attleboro"]].filter(([k]) => k !== (c.site || {}).id);
|
||||
return `<div class="card"><p style="margin-top:0">${inputHtml("discover.auto", F("discover.auto"), (c.discover || {}).auto)}</p>
|
||||
<dl class="kv small"><dt>last look</dt><dd>${last.at ? esc(when(last.at)) : "not yet"}${last.error ? ` · <span style="color:var(--problem)">${esc(last.error)}</span>` : ""}</dd>
|
||||
<dt>found</dt><dd>${inv.connections != null ? `${inv.connections} Companion connections · ${inv.dante || 0} Dante devices · ${inv.midi_sessions || 0} MIDI sessions · wired network ${esc(inv.wired || "?")}` : "–"}</dd></dl>
|
||||
<div class="row"><button class="small" id="looknow" ${editing() ? "" : "disabled"} data-hint="Look now and add what is new (also when adding by itself is off); never during a service or a recording">Look now</button></div></div>
|
||||
<div class="card"><h3 style="margin-top:0">Added last time</h3>${list(last.added)}${last.waiting && last.waiting.length ? `<h3>Waiting${hint(last.why || "")}</h3>${list(last.waiting)}<p class="help">${esc(last.why || "")}</p>` : ""}
|
||||
${inv.not_known && inv.not_known.length ? `<details><summary>Companion connections auto-setup has no recipe for (${inv.not_known.length})</summary>${list(inv.not_known)}<p class="help">Add a key for one by hand (Keys › + Key) if it matters.</p></details>` : ""}</div>
|
||||
<div class="card"><h3 style="margin-top:0">Computers${hint("Each computer runs the join line once: it lets this Mac log in with its key and says it is here. The next look gives its programs keys.")}</h3>
|
||||
<div class="tablewrap"><table>${comps}${joined}${comps || joined ? "" : "<tr><td class='muted'>none yet</td></tr>"}</table></div>
|
||||
${line(d.join.mac, "Mac: Terminal, paste")}${line(d.join.pc, "PC: PowerShell as administrator, paste")}</div>
|
||||
<div class="card"><h3 style="margin-top:0">Set up another campus${hint("On that campus's Mac that runs Companion: Terminal, paste. It installs the panel from here, finds the gear and makes the pages. Then each computer there runs that Mac's join line.")}</h3>
|
||||
${campuses.map(([k, n]) => line(`curl -fsSL "${d.base}/campus/install?id=${k}" | zsh`, n)).join("")}</div>`;
|
||||
}
|
||||
function siteHtml(c) {
|
||||
const f = F("site.editing_code");
|
||||
return `<div class="card">${inputHtml("site.editing_code", Object.assign({}, f, { help: "The PIN that switches Editing on. Digits are easiest. It lives in the settings file, so everyone with the repo has it." }), (c.site || {}).editing_code)}</div>` + fieldsHtml("Site");
|
||||
@@ -1347,6 +1371,8 @@
|
||||
function wireSettings(which) {
|
||||
const q = (s) => main.querySelector(s);
|
||||
wireCalendars();
|
||||
for (const el of main.querySelectorAll(".copyline")) el.onclick = () => el.select();
|
||||
if (q("#looknow")) q("#looknow").onclick = async () => { try { await post("/api/discover/now", {}); alertBox("Looking now. This page fills in within a minute."); setTimeout(() => route(), 30000); } catch (e) { alertBox(e.message); } };
|
||||
for (const b of main.querySelectorAll("[data-changepress]")) b.onclick = () => {
|
||||
const id = b.dataset.changepress, f0 = cfg().fixes[id];
|
||||
pickButtonDialog(`Which button is ${(f0.params || {}).button || id}?`, { page: f0.page, row: f0.row, col: f0.col }, async (p) => {
|
||||
|
||||
Reference in New Issue
Block a user