feat(all): 科学计算正确性修复、调度竞态消除、节点优雅退出、安全加固与收敛分析重构
科学计算正确性: - 修复 Fortran 无-E 科学记数法(指数≥100 时 E 被挤掉,如 -1.35+118)导致 发散行被静默跳过、误判收敛的 bug;扩展大气无效检测覆盖 Inf 与 *** 溢出标记 - 种子匹配改为 CNO 有向距离(富金属方向重罚 4×、贫金属方向轻罚 1×), 基于 1191 个真实种子配对回测标定,回测净改善 314 个点 - GridAxisValue 反序列化拒绝非法文本(不再静默 NaN);chmax≤0 显式报错 - ions 行宽列宽对齐真实 fort.5 格式 调度与队列竞态: - 原子选点(IMMEDIATE 事务 SELECT+UPDATE)消除并发调度重复派发 (#5) - 调度互斥锁 + 冷启动优先策略(SeedStep 仅作失败后救援,不再正常路径热启动) - 毒消息 dead_letter 标记防出队死循环;clear_queue 保留 claimed 行 (#6) - 孤儿 running 点回收兜底;stale_sec 默认 7800→21600s(3 倍超时缓冲) 节点生命周期: - SIGTERM+SIGINT 双信号监听(修复 Docker stop 发 SIGTERM 不触发优雅退出) - SlotGuard RAII 防活动 slot 泄漏;子进程超时增加二级 30s wait 防 Fortran hang - SeedStep 种子下载 fail-fast + 沙盒私有副本解耦 LRU 清理竞争 - reqwest Client 增加连接/请求超时;启动清理残留 task_* 沙盒 安全加固: - 节点注册 registration_secret 二次凭据 (H8),恒定时间比对防时序旁路 - token 缓存 generation 机制消除 reissue 后旧 token TOCTOU 复活窗口 - 新增 /api/auth/logout 服务端 session 即时撤销;fail-closed 鉴权启动策略 - 前端 token 迁移 sessionStorage;YAML 高亮改 DOM API 消除 XSS 注入面 - 备份文件权限收紧 0600;点表动态值全面 escapeHtml 前端 Dashboard: - 收敛性分析从热力图重构为 Parallel Sets 平行集合图(6 维+状态轴,手写 SVG 零依赖) - 进度曲线横轴改为真实时间(服务端 now 锚定,停滞期诚实留白);轮询指数退避 - 移除 imported 收敛途径分类,导入点按实际途径 cold_run/seed_step 归类 - 初始化时 /api/auth/check 校验 token;401 toast 提示替代静默 reload 服务端恢复与工具链: - 启动恢复 initializing 态工作流;默认工作流 INSERT-only 不覆盖 API 编辑 - body limit 分层(10MB 不再截断 256MB report);multipart 显式错误处理 - 嵌入二进制原子写(tmp+rename)防半写损坏 - import_results 判定收敛途径透传 success_method;conv.json 格式对齐本项目 - push_import_results.sh 退出码修复 + .bat UTF-8 BOM + scp 上传 - Docker USE_MIRRORS 默认关闭;移除无用 assets 挂载;删除 hosts.ini 入库
This commit is contained in:
+86
-15
@@ -1,17 +1,35 @@
|
||||
/* DCTS Dashboard API & Authentication Module */
|
||||
|
||||
import { showToast } from './components/toast.js';
|
||||
|
||||
// 安全:token 存储在 sessionStorage 而非 localStorage。
|
||||
// sessionStorage 生命周期仅限当前标签页会话(关闭即失效),缩小了 XSS 窃取后的利用窗口;
|
||||
// localStorage 会跨标签页/重启长期持久化,一旦泄露(配合 CSP 的 'unsafe-inline')即等于
|
||||
// 长期账号接管。服务端 session 仍有 24h 过期 + 容量淘汰,前端不再持有永久凭据。
|
||||
const TOKEN_KEY = 'dcts_admin_token';
|
||||
|
||||
export function getAdminToken() {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
try {
|
||||
return sessionStorage.getItem(TOKEN_KEY);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setAdminToken(token) {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
try {
|
||||
sessionStorage.setItem(TOKEN_KEY, token);
|
||||
} catch (_) {
|
||||
/* sessionStorage 不可用时静默,鉴权将走 401 流程 */
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAdminToken() {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
try {
|
||||
sessionStorage.removeItem(TOKEN_KEY);
|
||||
} catch (_) {
|
||||
/* 同上 */
|
||||
}
|
||||
}
|
||||
|
||||
// 安全 HTML 转义,防止存储型 XSS
|
||||
@@ -25,7 +43,14 @@ export function escapeHtml(s) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// 统一 fetch 包装:自动注入 Authorization 头,支持 10s 超时处理与 401 处理
|
||||
// 401 处理去重:并发请求(如详情页 Promise.allSettled 三路拉取)可能各自命中 401 并
|
||||
// 各触发一次 location.reload,竞态下不规范。用一个模块级 guard 保证只处理一次。
|
||||
let handling401 = false;
|
||||
|
||||
/**
|
||||
* 统一 fetch 包装:自动注入 Authorization 头,支持超时处理与 401 处理。
|
||||
* 401 时清 token、提示会话过期并跳回登录,而非静默 reload(避免详情页突然闪回无说明)。
|
||||
*/
|
||||
export async function apiFetch(url, opts = {}, timeoutMs = 10000) {
|
||||
const token = getAdminToken();
|
||||
const headers = { ...(opts.headers || {}) };
|
||||
@@ -35,30 +60,76 @@ export async function apiFetch(url, opts = {}, timeoutMs = 10000) {
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const signal = opts.signal || controller.signal;
|
||||
// 调用方传入的 signal(如视图卸载 abortCtl)转发给内部 controller,
|
||||
// 否则 fetch 绑定的是调用方 signal,内部 timeoutMs 兜底会失效(请求挂起无法超时)。
|
||||
const callerSignal = opts.signal;
|
||||
const onCallerAbort = () => controller.abort();
|
||||
if (callerSignal) {
|
||||
if (callerSignal.aborted) {
|
||||
controller.abort();
|
||||
} else {
|
||||
callerSignal.addEventListener('abort', onCallerAbort, { once: true });
|
||||
}
|
||||
}
|
||||
// fetch 统一用内部 controller.signal:既能响应超时,也能响应调用方 abort。
|
||||
const { signal: _ignored, ...optsWithoutSignal } = opts;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { ...opts, headers, signal });
|
||||
const res = await fetch(url, { ...optsWithoutSignal, headers, signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
if (res.status === 401) {
|
||||
if (res.status === 401 && !handling401) {
|
||||
handling401 = true;
|
||||
clearAdminToken();
|
||||
location.reload();
|
||||
// 延迟提示让 toast 可见,再回到登录视图
|
||||
try {
|
||||
showToast('会话已过期,请重新登录', 'error');
|
||||
} catch (_) {
|
||||
/* toast 不可用时跳过 */
|
||||
}
|
||||
setTimeout(() => {
|
||||
location.hash = '';
|
||||
location.reload();
|
||||
}, 800);
|
||||
}
|
||||
return res;
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
throw err;
|
||||
} finally {
|
||||
// 清理调用方 signal 监听,避免泄漏(尤其长期存活的 abortCtl)。
|
||||
if (callerSignal) callerSignal.removeEventListener('abort', onCallerAbort);
|
||||
}
|
||||
}
|
||||
|
||||
// API 请求接口定义
|
||||
export async function loginAdmin(password) {
|
||||
const res = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
return res;
|
||||
// loginAdmin 走统一超时包装,避免裸 fetch 在网络挂起时永不返回、按钮永久 disabled。
|
||||
export async function loginAdmin(password, timeoutMs = 10000) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
return res;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出:调用后端 /api/auth/logout 撤销当前 session(服务端立即失效),
|
||||
* 再清除前端 token。即便 token 已被窃取,登出也能阻断重放(不再依赖 24h 自然过期)。
|
||||
*/
|
||||
export async function logoutAdmin() {
|
||||
try {
|
||||
await apiFetch('/api/auth/logout', { method: 'POST' }, 5000);
|
||||
} catch (_) {
|
||||
/* 网络异常时仍清前端 token,不阻断登出交互 */
|
||||
}
|
||||
clearAdminToken();
|
||||
}
|
||||
|
||||
export async function fetchClusterStatus() {
|
||||
|
||||
Reference in New Issue
Block a user