(function initSub2ApiAdminCommon(global) { const ADMIN_LINKS = [ { key: "home", href: "/portal/admin/", label: "管理首页" }, { key: "logical-groups", href: "/portal/admin/logical-groups.html", label: "逻辑分组 / 路由" }, { key: "route-health", href: "/portal/admin/route-health.html", label: "Route 健康视图" }, { key: "accounts", href: "/portal/admin/accounts.html", label: "帐号资产" }, { key: "providers", href: "/portal/admin/providers.html", label: "新增模型 / 供应商目录" }, { key: "batch-import", href: "/portal/admin/batch-import.html", label: "导入供应商帐号" }, { key: "portal", href: "/portal/", label: "用户 Portal", target: "_blank", rel: "noreferrer" }, ]; function escapeHTML(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function defaultApiBase() { const origin = global.location && typeof global.location.origin === "string" ? global.location.origin : ""; if (!origin || origin === "null") { return "/portal-admin-api"; } return `${origin}/portal-admin-api`; } function normalizeApiBase(value) { return (String(value || "").trim() || defaultApiBase()).replace(/\/+$/, ""); } function authHeaders(tokenValue) { const token = String(tokenValue || "").trim(); return token ? { Authorization: `Bearer ${token}` } : {}; } function normalizeTone(tone) { const value = String(tone || "").trim().toLowerCase(); if (!value || value === "note" || value === "info") { return "note"; } if (value === "warn") { return "warning"; } if (value === "error") { return "danger"; } return value; } function setStatus(element, message, tone = "note") { if (!element) { return; } element.textContent = message; const normalizedTone = normalizeTone(tone); if (normalizedTone !== "note") { element.setAttribute("data-tone", normalizedTone); } else { element.removeAttribute("data-tone"); } } function describeSessionPayload(payload, options = {}) { const fallbackUsername = String(options.usernameFallback || "admin").trim() || "admin"; const currentUsername = String(payload?.username || "").trim(); const effectiveUsername = currentUsername || fallbackUsername; if (payload?.authenticated) { const suffix = options.includeSessionSuffix ? "(session)" : ""; return { tone: "success", message: `管理员会话已建立:${currentUsername || "unknown"}${suffix}`, }; } if (payload?.login_enabled) { if (options.allowBearerFallback) { return { tone: "warning", message: `当前未登录。可用管理员用户名 ${effectiveUsername} 建立 session,或继续使用 Bearer token。`, }; } return { tone: "warning", message: "当前未登录。可直接使用管理员用户名密码建立会话。", }; } return { tone: "warning", message: "当前实例未启用管理员登录,只能使用 Bearer token。", }; } function sessionStateSpec(kind, context = {}, options = {}) { const username = String(context.username || options.usernameFallback || "unknown").trim() || "unknown"; const message = context.error instanceof Error ? context.error.message : String(context.error || "").trim(); switch (kind) { case "missing_credentials": return { tone: "warning", message: "请先输入管理员用户名和密码。", }; case "login_success": return { tone: "success", message: `管理员会话已建立:${username}`, }; case "login_failed": return { tone: "danger", message: `管理员登录失败:${message || "未知错误"}`, }; case "logout_success": return { tone: "warning", message: "管理员会话已退出。", }; case "logout_failed": return { tone: "danger", message: `退出会话失败:${message || "未知错误"}`, }; case "check_failed": return { tone: "danger", message: `检查管理员会话失败:${message || "未知错误"}`, }; default: return { tone: "note", message: String(context.message || ""), }; } } function applySessionPayload(element, payload, options = {}) { const status = describeSessionPayload(payload, options); setStatus(element, status.message, status.tone); return status; } function setSessionState(element, kind, context = {}, options = {}) { const status = sessionStateSpec(kind, context, options); setStatus(element, status.message, status.tone); return status; } async function requestJSON(client, path, options = {}) { const { skipAuth = false, headers = {}, ...rest } = options; const finalHeaders = { Accept: "application/json", ...headers }; if (!skipAuth) { Object.assign(finalHeaders, authHeaders(client.adminTokenInput && client.adminTokenInput.value)); } const response = await fetch(`${normalizeApiBase(client.apiBaseInput && client.apiBaseInput.value)}${path}`, { ...rest, credentials: "include", headers: finalHeaders, }); const text = await response.text(); let payload = {}; try { payload = text ? JSON.parse(text) : {}; } catch { payload = { raw: text }; } if (!response.ok) { const message = payload?.error?.message || payload?.message || payload?.error || payload?.raw || `HTTP ${response.status}`; throw new Error(message); } return payload; } function readStoredConfig(storageKey) { try { return JSON.parse(global.localStorage.getItem(storageKey) || "{}"); } catch (error) { console.warn(`failed to parse ${storageKey}`, error); return {}; } } function writeStoredConfig(storageKey, payload) { global.localStorage.setItem(storageKey, JSON.stringify(payload)); } function renderAdminNav(container, currentKey) { if (!container) { return; } container.innerHTML = ADMIN_LINKS.map((link) => { const currentClass = currentKey === link.key ? " is-current" : ""; const target = link.target ? ` target="${link.target}"` : ""; const rel = link.rel ? ` rel="${link.rel}"` : ""; return `${escapeHTML(link.label)}`; }).join(""); } function createAdminPageRuntime(options) { const client = { apiBaseInput: options.apiBaseInput, adminTokenInput: options.adminTokenInput, }; const sessionPresentation = options.sessionPresentation || {}; async function refreshAdminSession() { try { const payload = await requestJSON(client, "/api/admin/session", { skipAuth: !options.includeAuthOnSessionCheck, }); if (payload.username && options.adminUsernameInput && !options.adminUsernameInput.value.trim()) { options.adminUsernameInput.value = payload.username; } applySessionPayload(options.adminSessionStatus, payload, sessionPresentation); return payload; } catch (error) { setSessionState(options.adminSessionStatus, "check_failed", { error }, sessionPresentation); throw error; } } async function loginAdminSession() { const username = options.adminUsernameInput ? options.adminUsernameInput.value.trim() : ""; const password = options.adminPasswordInput ? options.adminPasswordInput.value : ""; if (!username || !password) { const error = new Error("管理员用户名和密码不能为空"); setSessionState(options.adminSessionStatus, "missing_credentials", {}, sessionPresentation); throw error; } try { const payload = await requestJSON(client, "/api/admin/session/login", { method: "POST", skipAuth: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); if (options.adminPasswordInput) { options.adminPasswordInput.value = ""; } if (typeof options.onSessionPersist === "function") { options.onSessionPersist(); } setSessionState(options.adminSessionStatus, "login_success", { username: payload.username || username }, sessionPresentation); return payload; } catch (error) { setSessionState(options.adminSessionStatus, "login_failed", { error }, sessionPresentation); throw error; } } async function logoutAdminSession() { try { const response = await fetch(`${normalizeApiBase(options.apiBaseInput && options.apiBaseInput.value)}/api/admin/session/logout`, { method: "POST", credentials: "include", headers: authHeaders(options.adminTokenInput && options.adminTokenInput.value), }); if (!response.ok) { const text = await response.text(); throw new Error(text || `HTTP ${response.status}`); } if (options.adminPasswordInput) { options.adminPasswordInput.value = ""; } setSessionState(options.adminSessionStatus, "logout_success", {}, sessionPresentation); } catch (error) { setSessionState(options.adminSessionStatus, "logout_failed", { error }, sessionPresentation); throw error; } } return { defaultApiBase, normalizeApiBase() { return normalizeApiBase(options.apiBaseInput && options.apiBaseInput.value); }, authHeaders() { return authHeaders(options.adminTokenInput && options.adminTokenInput.value); }, requestJSON(path, requestOptions = {}) { return requestJSON(client, path, requestOptions); }, readStoredConfig, writeStoredConfig, renderAdminNav, setStatus, applySessionPayload(element, payload) { return applySessionPayload(element, payload, sessionPresentation); }, setSessionState(element, kind, context = {}) { return setSessionState(element, kind, context, sessionPresentation); }, refreshAdminSession, loginAdminSession, logoutAdminSession, }; } global.Sub2ApiAdminCommon = { ADMIN_LINKS, createAdminPageRuntime, defaultApiBase, normalizeApiBase, authHeaders, normalizeTone, setStatus, describeSessionPayload, applySessionPayload, setSessionState, readStoredConfig, writeStoredConfig, renderAdminNav, }; })(window);