src-gui/js/api.js
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: GPL-3.0-or-later
/* === Data fetching === */
/* Desktop detection must not rely on the injected Tauri global alone: on this
stack (wry + WebKitGTK) it has been reported missing, and the page is served
from tauri://localhost regardless. Any of these signals is enough. */
const isDesktop = () => {
if (typeof window === "undefined") return false;
if (window.__TAURI_INTERNALS__ || window.__TAURI__) return true;
try {
return location.protocol === "tauri:" || location.hostname === "tauri.localhost";
} catch {
return false;
}
};
const STATIC_DATA = typeof window !== "undefined" && window.__DATA__ ? window.__DATA__ : null;
/* Ask the desktop shell's loopback service. Its errors carry the real reason
(missing CLI, bad resource copy) — surface them, never swallow them. */
async function desktopFetch(path, q = "") {
let res;
try {
res = await fetch("http://127.0.0.1:17321" + path + q, { cache: "no-store" });
} catch {
throw new Error("the Linux Doctor service is not reachable on 127.0.0.1:17321 (is the app still starting?)");
}
if (!res.ok) {
const body = await res.text().catch(() => "");
let msg = body;
try { msg = JSON.parse(body).error || body; } catch {}
throw new Error(msg || `report service error (HTTP ${res.status})`);
}
return res.json();
}
/* refresh=true forces a fresh scan; save=true records the run in history.
Background polling passes neither (the servers serve a short cached report,
and machine history only advances on an explicit run). */
async function fetchReport({ refresh = false, save = false } = {}) {
if (STATIC_DATA) return STATIC_DATA;
const p = new URLSearchParams();
if (refresh) p.set("refresh", "1");
if (save) p.set("save", "1");
const q = p.toString() ? `?${p}` : "";
if (isDesktop()) return desktopFetch("/report", q);
const res = await fetch("/api/report" + q, { cache: "no-store" });
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json();
}
async function fetchHistory() {
if (STATIC_DATA) return [];
if (isDesktop()) {
try {
const res = await fetch("http://127.0.0.1:17321/history", { cache: "no-store" });
if (res.ok) return (await res.json()).runs || [];
} catch {}
}
try {
const res = await fetch("/api/history", { cache: "no-store" });
if (res.ok) return (await res.json()).runs || [];
} catch {}
return [];
}
async function fetchChecks() {
if (STATIC_DATA) return [];
if (isDesktop()) {
try {
const res = await fetch("http://127.0.0.1:17321/checks", { cache: "no-store" });
if (res.ok) return (await res.json()).checks || [];
} catch {}
}
try {
const res = await fetch("/api/checks", { cache: "no-store" });
if (res.ok) return (await res.json()).checks || [];
} catch {}
return [];
}
/* check id → category map, fetched once and reused across renders.
Fills the shared checksCategoryMap; never throws. */
async function loadCategoryMap() {
try {
const list = await fetchChecks();
checksCategoryMap = new Map(list.map((c) => [c.id, c.category || "other"]));
} catch {
checksCategoryMap = new Map();
}
return checksCategoryMap;
}
/* Ignore list + history clearing. The desktop shell's loopback service answers
the same shapes on 17321; in the browser the dashboard serves them directly.
Both are best-effort: a missing route leaves the section empty, never throws. */
async function fetchIgnore() {
if (STATIC_DATA) return { patterns: [], codes: [], path: "" };
if (isDesktop()) {
try {
const res = await fetch("http://127.0.0.1:17321/api/ignore", { cache: "no-store" });
if (res.ok) return await res.json();
} catch {}
}
try {
const res = await fetch("/api/ignore", { cache: "no-store" });
if (res.ok) return await res.json();
} catch {}
return { patterns: [], codes: [], path: "" };
}
async function postJson(url, payload) {
try {
const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
return res.ok && (await res.json()).ok !== false;
} catch {
return false;
}
}
async function editIgnore(payload) {
if (isDesktop() && await postJson("http://127.0.0.1:17321/api/ignore", payload)) return true;
return postJson("/api/ignore", payload);
}
async function clearHistoryApi() {
const tryPost = async (url) => {
try {
const res = await fetch(url, { method: "POST" });
return res.ok && (await res.json()).ok !== false;
} catch {
return false;
}
};
if (isDesktop() && await tryPost("http://127.0.0.1:17321/api/history/clear")) return true;
return tryPost("/api/history/clear");
}