Files
nas-tools/dashboard/frontend/src/lib/api.js
T

220 lines
5.4 KiB
JavaScript

const BASE = "/api";
let token = "";
// Current user info populated after auth
export let currentUser = { username: "", role: "", pages: [] };
export function setCurrentUser(u) {
currentUser = u;
}
export function setToken(t) {
token = t;
if (t) localStorage.setItem("token", t);
else localStorage.removeItem("token");
}
export function getToken() {
return token;
}
// Refresh token is cookie-managed server-side.
export function setRefreshToken() {}
let refreshPromise = null;
async function requestRefresh(body) {
const r = await fetch(BASE + "/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "same-origin",
body: body ? JSON.stringify(body) : undefined,
});
if (!r.ok) return false;
const data = await r.json();
setToken(data.access_token || "");
return !!data.access_token;
}
export async function tryRefreshSession() {
if (refreshPromise) return refreshPromise;
refreshPromise = (async () => {
try {
const cookieRefreshed = await requestRefresh();
if (cookieRefreshed) {
return true;
}
return false;
} catch {
return false;
} finally {
refreshPromise = null;
}
})();
return refreshPromise;
}
async function request(path, opts = {}) {
const headers = opts.headers || {};
if (token) headers["Authorization"] = `Bearer ${token}`;
if (opts.json) {
headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(opts.json);
delete opts.json;
}
const fetchOpts = { ...opts, headers };
if (path.startsWith("/auth/")) fetchOpts.credentials = "same-origin";
try {
let r = await fetch(BASE + path, fetchOpts);
if (r.status === 401 && !path.includes("/auth/refresh")) {
const refreshed = await tryRefreshSession();
if (refreshed) {
headers["Authorization"] = `Bearer ${token}`;
r = await fetch(BASE + path, fetchOpts);
}
}
if (r.status === 401) {
setToken("");
throw new Error("Unauthorized");
}
if (!r.ok) {
const error = new Error(`HTTP ${r.status}`);
error.status = r.status;
try { error.body = await r.json(); } catch {}
throw error;
}
return r.json();
} catch (e) {
console.error(`API ${opts.method || "GET"} ${path}:`, e);
throw e;
}
}
export function get(path) {
return request(path);
}
export function post(path, data) {
return request(path, { method: "POST", json: data });
}
export function del(path) {
return request(path, { method: "DELETE" });
}
export async function upload(path, file) {
const headers = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
const form = new FormData();
form.append("file", file);
const r = await fetch(`${BASE}/files/upload?path=${encodeURIComponent(path)}`, {
method: "POST",
headers,
body: form,
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
export function login(creds) {
return request("/auth/login", { method: "POST", json: creds });
}
export function checkAuth() {
return request("/auth/me");
}
export function logout() {
return request("/auth/logout", { method: "POST" });
}
export function getPreferences() {
return get("/auth/preferences");
}
export function savePreferences(data) {
return request("/auth/preferences", { method: "PUT", json: data });
}
export function getLiteLLMHealth() {
return get("/litellm/health");
}
export function getCcConnectHealth() {
return get("/cc-connect/health");
}
export function startCcConnect() {
return post("/cc-connect/start");
}
export function stopCcConnect() {
return post("/cc-connect/stop");
}
export function getInfoEngineItems(params = {}) {
const query = new URLSearchParams();
if (params.limit !== undefined) query.set("limit", String(params.limit));
if (params.offset !== undefined) query.set("offset", String(params.offset));
if (params.source) query.set("source", params.source);
if (params.tag) query.set("tag", params.tag);
if (params.since) query.set("since", params.since);
const suffix = query.toString() ? `?${query.toString()}` : "";
return get(`/info-engine/items${suffix}`);
}
export function getInfoEngineItem(id) {
return get(`/info-engine/items/${id}`);
}
export function put(path, data) {
return request(path, { method: "PUT", json: data });
}
export async function download(path, filename) {
const headers = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
try {
// Get a temporary download token
const tokenResponse = await fetch(`${BASE}/files/download-token?path=${encodeURIComponent(path)}`, {
method: "POST",
headers,
});
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error(`Download token failed: ${tokenResponse.status} - ${errorText}`);
throw new Error(`Download failed: ${tokenResponse.status}`);
}
const { token: downloadToken } = await tokenResponse.json();
// Use hidden iframe for download - avoids page navigation and popup blockers
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = `${BASE}/files/download?token=${downloadToken}`;
document.body.appendChild(iframe);
// Clean up iframe after download starts
setTimeout(() => {
document.body.removeChild(iframe);
}, 5000);
} catch (e) {
console.error("Download error:", e);
throw e;
}
}