Files
bot_dev1 8b68d015c2 fix: 组件配色全面对齐 Ant Design 5 token + 修复 iframe 内页签初始激活(issue #131)
组件审查结论:全站组件为自绘 HTML+CSS 仿 AntD(纯静态无组件库),
pro.css 已统一设计 token,但各页 CSS/JS 残留一批旧配色硬编码,
pro.css 覆盖不到,本次全部清理:

- 旧 iOS 红 #ff3b30 → --ant-error #ff4d4f(告警 P0/严重度/DLP 条/
  工艺 alarm 态/diff 删除行)
- 旧橙 #f5a623 → --ant-warning #faad14(P1 告警/warning 态/暂存徽章)
- 旧 iOS 绿 #2ecc71 → --ant-success #52c41a(running 态/prod 徽章/
  索引完成/路由本地标签/权限矩阵)
- 杂色蓝统一 → --ant-primary #1677ff:发送键 #0ea5e9、P2 告警
  #3aa0ff、编排画布残留青 rgba(24,211,200)、旧气泡 #1d4ed8
- 主色按钮上的深色文字 #04202a → #fff(对比度修正)
- 灰 #64748b → --ant-text-tertiary rgba(0,0,0,0.45)
- 各页 CSS :root 深色默认变量值(#0b1220/#13203a/#18d3c8 等死值)
  对齐为 AntD 亮色默认值,修正"深色主题"过时注释
- 删除已无任何引用的旧深色登录页样式 auth/auth.css

顺带修复实测发现的 bug:SPA 外壳内直达 admin/studio 子页时页签
不切换——studio.js/admin.js 的 Tab 事件在异步 boot(user) 中绑定,
bindPageTabs 的 window load 初始激活可能早于绑定完成,改为带重试
的激活(直到 Tab 点亮,最多 10s)。实测 #/admin/#alerts 直达后
page-alerts 正确激活。
2026-08-06 08:20:17 +08:00

148 lines
5.9 KiB
JavaScript
Raw Permalink 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.
/* iAOP 用户/角色管理页逻辑(issue #134)。
* 会话守卫复用 #150 的 IAOP_AUTH.requireLoginElseRedirect(core/auth 后端 /auth/me);
* 仅 admin(manage 权限)可访问;CRUD/审计/OIDC 走 user_store.js(Demo 存储)。
*/
"use strict";
(function () {
IAOP_AUTH.requireLoginElseRedirect("login.html").then(function (user) {
if (!user) return; // 正在跳转登录页
if (user.role !== "admin") {
alert("仅 Publisher(admin)可访问用户管理");
location.href = "../studio/index.html";
return;
}
document.getElementById("who").textContent =
user.username + " · " + UserStore.ROLE_LABELS[user.role];
function el(tag, text) {
var n = document.createElement(tag);
if (text !== undefined) n.textContent = text;
return n;
}
function renderUsers() {
var tbody = document.getElementById("user-tbody");
tbody.innerHTML = "";
UserStore.list().forEach(function (u) {
var tr = el("tr");
if (!u.active) tr.className = "disabled-user";
tr.appendChild(el("td", u.username));
// 角色分配(下拉即改)
var tdRole = el("td");
var sel = el("select");
UserStore.ROLES.forEach(function (r) {
var opt = el("option", UserStore.ROLE_LABELS[r]);
opt.value = r;
if (r === u.role) opt.selected = true;
sel.appendChild(opt);
});
sel.className = "role-" + u.role;
sel.onchange = function () {
UserStore.update(u.username, { role: sel.value }, user.username).then(renderUsers);
};
tdRole.appendChild(sel);
tr.appendChild(tdRole);
tr.appendChild(el("td", u.active ? "启用" : "禁用"));
tr.appendChild(el("td", (u.created_at || "").slice(0, 10)));
var tdOps = el("td");
var toggle = el("button", u.active ? "禁用" : "启用");
toggle.onclick = function () {
UserStore.update(u.username, { active: !u.active }, user.username).then(renderUsers);
};
var reset = el("button", "重置密码");
reset.onclick = function () {
var pwd = prompt("为 " + u.username + " 设置新密码(≥6 位)");
if (pwd) UserStore.update(u.username, { password: pwd }, user.username).then(renderUsers);
};
var del = el("button", "删除");
del.className = "danger";
del.onclick = function () {
if (u.username === user.username) { alert("不能删除当前登录账号"); return; }
if (confirm("确认删除用户 " + u.username + "?")) {
UserStore.remove(u.username, user.username).then(renderUsers);
}
};
[toggle, reset, del].forEach(function (b) {
b.style.marginRight = "4px"; tdOps.appendChild(b);
});
tr.appendChild(tdOps);
tbody.appendChild(tr);
});
renderAudit();
}
function renderAudit() {
var box = document.getElementById("audit-list");
box.innerHTML = "";
UserStore.auditLog().slice().reverse().forEach(function (e) {
box.appendChild(el("div",
e.time.slice(0, 19).replace("T", " ") + " | " + e.actor + " | " +
e.action + " | " + e.resource + " | " + e.reason));
});
}
document.getElementById("btn-add").onclick = function () {
var err = document.getElementById("form-err");
err.textContent = "";
UserStore.create(document.getElementById("new-username").value.trim(),
document.getElementById("new-password").value,
document.getElementById("new-role").value)
.then(function () {
document.getElementById("new-username").value = "";
document.getElementById("new-password").value = "";
renderUsers();
})
.catch(function (e) { err.textContent = e.message; });
};
// OIDC 配置点
var cfg = UserStore.oidcConfig() || {};
document.getElementById("oidc-issuer").value = cfg.issuer || "";
document.getElementById("oidc-client").value = cfg.client_id || "";
document.getElementById("oidc-redirect").value = cfg.redirect_uri || "";
document.getElementById("btn-oidc").onclick = function () {
UserStore.configureOidc({
issuer: document.getElementById("oidc-issuer").value.trim(),
client_id: document.getElementById("oidc-client").value.trim(),
redirect_uri: document.getElementById("oidc-redirect").value.trim()
}, user.username);
alert("OIDC 配置已保存;登录页将出现 SSO 入口");
};
document.getElementById("logout-btn").onclick = function () {
fetch((IAOP_AUTH.AUTH_BASE || "") + "/auth/logout",
{ method: "POST", credentials: "include" })
.finally(function () { location.href = "login.html"; });
};
renderUsers();
renderPermMatrix();
});
/* 角色权限矩阵展示(issue #151 验收:矩阵生效可见;与 studio 门控同源) */
var PERM_MATRIX = {
readonly: { view: true, edit: false, publish: false, manage: false },
engineer: { view: true, edit: true, publish: false, manage: false },
admin: { view: true, edit: true, publish: true, manage: true }
};
function renderPermMatrix() {
var tbody = document.querySelector("#perm-matrix tbody");
tbody.innerHTML = "";
UserStore.ROLES.forEach(function (r) {
var tr = document.createElement("tr");
var td = document.createElement("td");
td.textContent = UserStore.ROLE_LABELS[r];
td.className = "role-" + r;
tr.appendChild(td);
["view", "edit", "publish", "manage"].forEach(function (a) {
var c = document.createElement("td");
c.textContent = PERM_MATRIX[r][a] ? "✓" : "✗";
c.style.color = PERM_MATRIX[r][a] ? "#52c41a" : "rgba(0,0,0,0.45)";
tr.appendChild(c);
});
tbody.appendChild(tr);
});
}
})();