final
release / build (push) Successful in 1m57s

Added wake indicator. moved to new wake API endpoint.

commented out some things. we dont need none of ts.
This commit is contained in:
lda
2025-08-31 03:51:08 +07:00 Unverified
parent a5966f0b43
commit d8050db20c
11 changed files with 273 additions and 95 deletions
+1
View File
@@ -1,5 +1,6 @@
export const qs = new URLSearchParams(location.search);
// not real cuh
export const $ = (id) => document.getElementById(id);
export const elName = $("name");
export const elCheck = $("check");
+11 -6
View File
@@ -8,7 +8,12 @@ export function renderLeases(leases) {
}
const tbl = document.createElement("table");
tbl.className = "table";
tbl.innerHTML = `<tr><th></th><th>IP</th><th>MAC</th><th>Name</th><th>Expires</th></tr>`;
(
tbl.tHead || tbl.createTHead()
).innerHTML = `<tr><th></th><th>IP</th><th>MAC</th><th>Name</th><th>Expires</th></tr>`;
const tbd = tbl.tBodies.item(0) || tbl.createTBody();
const nowSec = Math.floor(Date.now() / 1000);
for (const l of leases) {
const tr = document.createElement("tr");
@@ -34,17 +39,17 @@ export function renderLeases(leases) {
const name = l.name || "";
tr.innerHTML = `
<td><span class="${dotClass}" title="${title}"></span></td>
${[ip, mac, name]
${Object.entries({ ip, mac, name })
.map(
(f) =>
([name, value]) =>
`<td>${
f &&
`<a href="#" class="pick" data-value="${f}" title="filter by ip">${f}</a>`
value &&
`<a href="#" class="pick" data-value="${value}" title="filter by ${name}">${value}</a>`
}</td>`
)
.join("\n")}
<td><span class="tiny">${whenText}</span></td>`;
tbl.appendChild(tr);
tbd.appendChild(tr);
}
elLeases.innerHTML = "";
elLeases.appendChild(tbl);
+37 -17
View File
@@ -1,5 +1,6 @@
import { elHtml, elLog, setPill, qs } from "./dom.js";
import { elHtml, elLog, setPill, qs, pill } from "./dom.js";
import { rankState } from "./utils.js";
import { merge_wake_data, translate_wake_message } from "./wake.js";
const status_map = {
ip: "ip",
@@ -11,7 +12,7 @@ const status_map = {
const filter_array = ["ip", "dev", "nud", "mac"];
function buildStatusUrl(name) {
const hasExtraFilters = f_array.some((k) => qs.getAll(k).length);
const hasExtraFilters = filter_array.some((k) => qs.getAll(k).length);
if (name && !hasExtraFilters) {
return new URL(`/api/smart/${encodeURIComponent(name)}`, location.origin);
}
@@ -27,21 +28,33 @@ function buildStatusUrl(name) {
export function renderStatus(data) {
const tbl = document.createElement("table");
tbl.className = "table";
tbl.innerHTML = `<tr><th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr>`;
tbl.innerHTML = `<thead><tr>${
data.has_wake ? "<th>Wake status</th>" : ""
}<th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr></thead>`;
for (const row of data.table || []) {
// sum hax
const tbd = tbl.tBodies.item(0) || tbl.createTBody();
for (const row of data.table) {
if (data.has_wake && !row.wake_status)
throw TypeError("specified has wake but no wake stats");
const tr = document.createElement("tr");
tr.innerHTML = Object.entries(status_map)
.map(
(field, description) =>
`<td>${
row[field]
? `<a href="#" class="pick" data-value="${row[field]}" title="filter by ${description}">${row[field]}</a>`
: ""
}</td>`
)
.join();
tbl.appendChild(tr);
tr.innerHTML = `${
row.wake_status
? `<td><span class="dot ${
row.wake_status == "succeed" ? "ok" : "bad"
}" title="${translate_wake_message(row.wake_status)}"></span></td>`
: ""
}${Object.entries(status_map)
.map(([field, description]) => {
const value = row[field];
return `<td>${
value
? `<a href="#" class="pick" data-value="${value}" title="filter by ${description}">${value}</a>`
: ""
}</td>`;
})
.join("")}`;
tbd.appendChild(tr);
}
elHtml.innerHTML = "";
@@ -69,12 +82,13 @@ export function renderStatus(data) {
if (r >= 5) setPill("ok", "online");
else if (r >= 2) setPill("warn", "maybe");
else setPill("bad", "offline");
if (data.filters.nud?.length > 0) pill.textContent += " (filtered)";
} else {
setPill("warn", "unknown");
}
}
export async function fetchStatus(name) {
export async function fetchStatus(name, render = true, data_wake) {
setPill("warn", "checking…");
const u = buildStatusUrl(name);
elLog.textContent = "GET " + u.pathname + u.search;
@@ -93,7 +107,13 @@ export async function fetchStatus(name) {
return;
}
const data = await r.json();
renderStatus(data);
if (data_wake) {
data.table = merge_wake_data(data.table, data_wake);
data.has_wake = true;
}
if (render) renderStatus(data);
return data;
} catch (e) {
elLog.textContent = "status error: " + e;
setPill("bad", "error");
+96 -7
View File
@@ -1,18 +1,107 @@
import { elLog, setPill } from "./dom.js";
import { fetchStatus } from "./status.js";
import { elHtml, elLog, setPill } from "./dom.js";
import { fetchStatus, renderStatus } from "./status.js";
export async function sendWake(name) {
const data = await fetchStatus(name, false);
if (!data) return; // can not proceed; theres nothing.
const wake_targets = data.table.map(({ ip, mac }) => {
return { ip, mac };
});
setPill("warn", "waking…");
elLog.textContent = "POST /wake?name=" + name;
elLog.textContent = "POST /api/wake";
try {
const r = await fetch(`/wake?name=${encodeURIComponent(name)}`, {
const r = await fetch(`/api/wake`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(wake_targets),
});
const t = await r.text();
elLog.textContent = t || "ok";
setTimeout(() => fetchStatus(name), 800);
if (!r.ok) {
let msg = String(r.status);
try {
const err = await r.clone().json();
msg = err.error || JSON.stringify(err);
} catch {
msg = await r.text();
}
elLog.textContent = `wake error: ${msg}`;
setPill("bad", "error");
return;
}
const j = await r.json();
if (!j.success) {
elLog.textContent = `wake error: ${j.error}`;
setPill("bad", "error");
return;
}
const result = j.result;
data.table = merge_wake_data(data.table, result);
data.has_wake = true;
renderStatus(data);
setTimeout(() => fetchStatus(name, undefined, result), 2000); // long ass timeout
} catch (e) {
elLog.textContent = "wake error: " + e;
setPill("bad", "error");
}
}
/**
*
* @param {Array} table
* @param {Array} wake
*/
export function merge_wake_data(table, wake) {
if (!wake) return table;
if (wake.length != table.length)
throw TypeError(
"wake status table and status table not of the same length"
);
const return_array = [];
let linear_failed = false;
for (const [index, entry] of table.entries()) {
if (wake[index].ip != entry.ip || wake[index].mac != entry.mac) {
linear_failed = true;
break;
} // use alternative method
return_array.push({ wake_status: wake[index].status, ...entry });
}
// never happening AHH
if (linear_failed) {
return_array = [];
const wake_map = new Map();
wake.forEach(({ ip, mac, status }) => {
wake_map.set(JSON.stringify({ ip, mac }), status);
});
return_array = table.map((entry) => {
const { ip, mac } = entry;
return {
wake_status: wake_map.get(JSON.stringify({ ip, mac })),
...entry,
};
});
}
//
return return_array;
}
export function translate_wake_message(wake_msg) {
switch (wake_msg) {
case "incomplete":
return "Incomplete address (both ip and MAC required)"
case "succeed":
return "Wake request sent successfully"
case "nonexistent_address":
return "Errored pinging this address (nonexistent address)"
case "wrong_size":
return "Wake request malformed"
default:
return "Unknown"
}
}