Files
sub2api-cn-relay-manager/deploy/tksea-portal/admin-common.js
phamnazage-jpg 77b7f7f660
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled
feat: harden runtime import and frontend verification workflows
2026-06-04 20:02:36 +08:00

314 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
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 `<a href="${escapeHTML(link.href)}"${target}${rel} class="${currentClass.trim()}">${escapeHTML(link.label)}</a>`;
}).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);