Files
overlord/Overlord-Server/public/assets/data.js
T
2026-03-05 00:04:26 -06:00

144 lines
3.8 KiB
JavaScript

import { state } from "./state.js";
import { digestData } from "./utils.js";
const POLL_INTERVAL_MS = 5000;
let pollTimer = null;
let render = () => {};
export function registerRenderer(fn) {
render = fn;
}
export async function loadWithOptions(options = {}) {
const { force = false } = options;
if (state.isLoading) {
state.pendingForce = state.pendingForce || force;
return;
}
state.isLoading = true;
try {
const params = new URLSearchParams({
page: String(state.page),
pageSize: String(state.pageSize),
q: state.searchTerm,
sort: state.sort,
status: state.filterStatus || "all",
os: state.filterOs || "all",
});
const res = await fetch(`/api/clients?${params.toString()}`);
if (!res.ok) throw new Error(`status ${res.status}`);
const data = await res.json();
updateOsFilter(data.items);
const digest = digestData(data, state);
if (!force && digest === state.lastDigest) {
return;
}
state.lastDigest = digest;
render(data);
if (!state.thumbnailsRequested) {
state.thumbnailsRequested = true;
requestThumbnailsForClients(data.items);
}
} catch (err) {
console.error("load clients", err);
} finally {
state.isLoading = false;
if (state.pendingForce) {
const shouldForce = state.pendingForce;
state.pendingForce = false;
loadWithOptions({ force: shouldForce });
}
}
}
async function requestThumbnailsForClients(items) {
const onlineClientsWithoutThumbnail = items.filter(c => c.online && !c.thumbnail);
for (const client of onlineClientsWithoutThumbnail) {
await requestThumbnail(client.id);
}
}
function updateOsFilter(items) {
const osSelect = document.getElementById("filter-os");
if (!osSelect) return;
const osList = new Set();
items.forEach((item) => {
if (item.os) osList.add(item.os);
});
const currentValue = osSelect.value;
const osArray = Array.from(osList).sort();
osSelect.innerHTML = "";
const allOption = document.createElement("option");
allOption.value = "all";
allOption.textContent = "All OS";
osSelect.appendChild(allOption);
osArray.forEach((os) => {
const option = document.createElement("option");
option.value = os;
option.textContent = os;
osSelect.appendChild(option);
});
if (currentValue !== "all" && osList.has(currentValue)) {
osSelect.value = currentValue;
}
}
export function startAutoRefresh(intervalMs = POLL_INTERVAL_MS) {
clearInterval(pollTimer);
pollTimer = setInterval(() => {
loadWithOptions({ force: false });
}, intervalMs);
}
export async function sendCommand(clientId, action) {
try {
const res = await fetch(`/api/clients/${clientId}/command`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action }),
});
if (!res.ok) throw new Error(`command failed ${res.status}`);
return true;
} catch (err) {
console.error(err);
return false;
}
}
export async function requestPreview(clientId) {
const ok = await sendCommand(clientId, "screenshot");
if (ok) {
setTimeout(() => loadWithOptions({ force: true }), 250);
setTimeout(() => loadWithOptions({ force: true }), 800);
}
}
export async function requestThumbnail(clientId) {
try {
const res = await fetch(`/api/clients/${clientId}/thumbnail`, {
method: "POST",
});
if (res.ok) {
const data = await res.json();
if (data.ok) {
await loadWithOptions({ force: true });
setTimeout(() => loadWithOptions({ force: true }), 250);
setTimeout(() => loadWithOptions({ force: true }), 800);
return true;
}
}
return false;
} catch (err) {
console.error("Failed to request thumbnail:", err);
return false;
}
}