d8e6f5716a
Deploy Dashboard (Dev) / deploy-dev (push) Has been cancelled
Add backend start/stop endpoints and wire cc-connect UI controls to toggle container state with post-action health refresh and clear error handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
176 lines
4.2 KiB
JavaScript
176 lines
4.2 KiB
JavaScript
const BASE = "/api";
|
|
let token = localStorage.getItem("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;
|
|
}
|
|
|
|
export function setRefreshToken(t) {
|
|
if (t) localStorage.setItem("refresh_token", t);
|
|
else localStorage.removeItem("refresh_token");
|
|
}
|
|
|
|
function getRefreshToken() {
|
|
return localStorage.getItem("refresh_token") || "";
|
|
}
|
|
|
|
let refreshPromise = null;
|
|
|
|
async function tryRefresh() {
|
|
if (refreshPromise) return refreshPromise;
|
|
const rt = getRefreshToken();
|
|
if (!rt) return false;
|
|
refreshPromise = (async () => {
|
|
try {
|
|
const r = await fetch(BASE + "/auth/refresh", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ refresh_token: rt }),
|
|
});
|
|
if (!r.ok) return false;
|
|
const data = await r.json();
|
|
setToken(data.access_token);
|
|
setRefreshToken(data.refresh_token);
|
|
return true;
|
|
} 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;
|
|
}
|
|
|
|
try {
|
|
let r = await fetch(BASE + path, { ...opts, headers });
|
|
|
|
if (r.status === 401 && !path.includes("/auth/refresh")) {
|
|
const refreshed = await tryRefresh();
|
|
if (refreshed) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
r = await fetch(BASE + path, { ...opts, headers });
|
|
}
|
|
}
|
|
|
|
if (r.status === 401) {
|
|
setToken("");
|
|
setRefreshToken("");
|
|
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 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 });
|
|
}
|