moving things around
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bg: #0b0b0b;
|
||||
--fg: #e6e6e6;
|
||||
--muted: #888;
|
||||
--ok: #17a34a;
|
||||
--warn: #d97706;
|
||||
--bad: #dc2626;
|
||||
--btn: #2563eb;
|
||||
--card: #111827;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100dvh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
.wrap {
|
||||
width: min(900px, 95vw);
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
input[type="text"] {
|
||||
flex: 1 1 240px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #2a2a2a;
|
||||
background: #0f0f0f;
|
||||
color: var(--fg);
|
||||
outline: none;
|
||||
}
|
||||
button {
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #2a2a2a;
|
||||
background: var(--btn);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.secondary {
|
||||
background: #1f2937;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
border: 1px solid #2a2a2a;
|
||||
}
|
||||
.ok {
|
||||
background: #052e1a;
|
||||
border-color: #064e3b;
|
||||
color: #86efac;
|
||||
}
|
||||
.warn {
|
||||
background: #2b1800;
|
||||
border-color: #7c2d12;
|
||||
color: #fbbf24;
|
||||
}
|
||||
.bad {
|
||||
background: #330b0b;
|
||||
border-color: #7f1d1d;
|
||||
color: #fca5a5;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
background: var(--card);
|
||||
}
|
||||
#out {
|
||||
min-height: 100px;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
a {
|
||||
color: #93c5fd;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
const qs = new URLSearchParams(location.search);
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const elName = $("name");
|
||||
const elCheck = $("check");
|
||||
const elWake = $("wake");
|
||||
const elLog = $("log");
|
||||
const elHtml = $("html");
|
||||
const pill = $("status-pill");
|
||||
const link = $("permalink");
|
||||
|
||||
function setPill(kind, text) {
|
||||
pill.className = `pill ${kind}`;
|
||||
pill.textContent = text;
|
||||
}
|
||||
function setLink(name) {
|
||||
const url = new URL(location.href);
|
||||
if (name) url.searchParams.set("name", name);
|
||||
else url.searchParams.delete("name");
|
||||
history.replaceState(null, "", url);
|
||||
link.href = url.toString();
|
||||
}
|
||||
function getName() {
|
||||
return (elName.value || "").trim();
|
||||
}
|
||||
function saveName(name) {
|
||||
try {
|
||||
localStorage.setItem("wakey:name", name);
|
||||
} catch {}
|
||||
}
|
||||
function loadName() {
|
||||
return qs.get("name") || localStorage.getItem("wakey:name") || "";
|
||||
}
|
||||
|
||||
function rankState(s) {
|
||||
return (
|
||||
{
|
||||
Permanent: 5,
|
||||
Reachable: 5,
|
||||
Stale: 4,
|
||||
Delay: 3,
|
||||
Probe: 3,
|
||||
Incomplete: 3,
|
||||
Noarp: 2,
|
||||
None: 1,
|
||||
Failed: 0,
|
||||
}[s] ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
function renderStatus(data) {
|
||||
// data: { name: string, table: Array<{ ip, dev, mac, state }>} | { name, error }
|
||||
const tbl = document.createElement("table");
|
||||
tbl.innerHTML = `<tr><th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr>`;
|
||||
for (const row of data.table || []) {
|
||||
const tr = document.createElement("tr");
|
||||
const mac = row.mac ?? "";
|
||||
const dev = row.dev ?? "";
|
||||
tr.innerHTML = `<td>${row.ip}</td><td>${mac}</td><td>${row.state}</td><td>${dev}</td>`;
|
||||
tbl.appendChild(tr);
|
||||
}
|
||||
elHtml.innerHTML = "";
|
||||
elHtml.appendChild(tbl);
|
||||
|
||||
if ((data.table || []).length > 0) {
|
||||
const best = data.table.reduce((a, b) =>
|
||||
rankState(b.state) > rankState(a.state) ? b : a
|
||||
);
|
||||
const r = rankState(best.state);
|
||||
if (r >= 5) setPill("ok", "online");
|
||||
else if (r >= 2) setPill("warn", "maybe");
|
||||
else setPill("bad", "offline");
|
||||
} else {
|
||||
setPill("warn", "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStatus(name) {
|
||||
setPill("warn", "checking…");
|
||||
elLog.textContent = "GET /api/status?name=" + name;
|
||||
elHtml.innerHTML = "";
|
||||
try {
|
||||
const r = await fetch(`/api/status?name=${encodeURIComponent(name)}`);
|
||||
if (!r.ok) {
|
||||
const err = await r.json().catch(() => ({}));
|
||||
elLog.textContent = `status error: ${err.error || r.status}`;
|
||||
setPill("bad", "error");
|
||||
return;
|
||||
}
|
||||
const data = await r.json();
|
||||
renderStatus(data);
|
||||
} catch (e) {
|
||||
elLog.textContent = "status error: " + e;
|
||||
setPill("bad", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function sendWake(name) {
|
||||
setPill("warn", "waking…");
|
||||
elLog.textContent = "POST /wake?name=" + name;
|
||||
try {
|
||||
const r = await fetch(`/wake?name=${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
// headers: { "X-Target-Name": name },
|
||||
});
|
||||
const t = await r.text();
|
||||
elLog.textContent = t || "ok";
|
||||
// Optional: recheck after small delay
|
||||
setTimeout(() => fetchStatus(name), 600);
|
||||
} catch (e) {
|
||||
elLog.textContent = "wake error: " + e;
|
||||
setPill("bad", "error");
|
||||
}
|
||||
}
|
||||
|
||||
elCheck.addEventListener("click", () => {
|
||||
const name = getName();
|
||||
if (!name) return;
|
||||
saveName(name);
|
||||
setLink(name);
|
||||
fetchStatus(name);
|
||||
});
|
||||
elWake.addEventListener("click", () => {
|
||||
const name = getName();
|
||||
if (!name) return;
|
||||
saveName(name);
|
||||
setLink(name);
|
||||
sendWake(name);
|
||||
});
|
||||
elName.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") elCheck.click();
|
||||
});
|
||||
|
||||
// init
|
||||
const initial = loadName();
|
||||
if (initial) {
|
||||
elName.value = initial;
|
||||
// setLink(initial); // when the permalink doing YOUR job
|
||||
// auto-check on load in this A/B page
|
||||
fetchStatus(initial);
|
||||
} else {
|
||||
setPill("warn", "unknown");
|
||||
}
|
||||
Reference in New Issue
Block a user