组件审查结论:全站组件为自绘 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 正确激活。
148 lines
5.9 KiB
JavaScript
148 lines
5.9 KiB
JavaScript
/* 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);
|
||
});
|
||
}
|
||
})();
|