moving things around

This commit is contained in:
lda
2025-08-24 13:51:48 +07:00 Unverified
parent 1e5bdfc225
commit 93bb04bbab
13 changed files with 486 additions and 381 deletions
+113
View File
@@ -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;
}
+142
View File
@@ -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");
}