feat: 全站 Ant Design Pro 风格重构 + 登录守卫与角色门户(issue #131/#134)
- 新增 web/shared:pro.css(AntD 5 设计系统,旧 CSS 变量统一映射 AntD 色板)、 session.js(后端优先+本地账号降级双轨会话,IAOP_AUTH 兼容层)、 pro-header.js(全站统一 Pro 导航条:logo/页题/用户角色 Tag/退出) - 门户 web/index.html:未登录跳登录页(next 回跳),登录后按角色过滤 可见模块(readonly→驾驶舱/助手/移动端;engineer→+配置台;admin→全部) - 登录页 auth/login.html 重写为 Ant Design Pro 风格,本地降级模式自动 播种演示账号(admin/engineer/viewer),保留 OIDC SSO 双轨入口 - user_store.js 新增 verify() + seedDefaults() 默认账号播种 - 六模块页统一接入 pro.css + 统一导航条 + 登录守卫(PRD 8.2) - 驾驶舱主题 dark→light(Ant Design 5 色板):renderer.py THEME_TOKENS、 ti-cl4/resin 模板 theme、告警三级配色对齐 AntD(P0 #ff4d4f/P1 #faad14/ P2 #1677ff),渲染计划已重编(build_plans.py) - 验证:core/cockpit 86 单测通过;JS 语法检查通过;静态冒烟全 200
This commit is contained in:
+341
-52
@@ -1,73 +1,362 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>iAOP 登录</title>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>登录 · iAOP 云美工业AI优化平台</title>
|
||||
<style>
|
||||
/* iAOP 登录页(Ant Design Pro 风格重构,2026-08)。
|
||||
* 布局对齐 Ant Design Pro 登录页:顶部品牌区 + 居中白卡 + 底部版权。
|
||||
* 认证走 shared/session.js「后端优先 + 本地账号降级」双轨。 */
|
||||
:root {
|
||||
--ant-primary: #1677ff;
|
||||
--ant-primary-hover: #4096ff;
|
||||
--ant-text: rgba(0, 0, 0, 0.88);
|
||||
--ant-text-secondary: rgba(0, 0, 0, 0.65);
|
||||
--ant-text-tertiary: rgba(0, 0, 0, 0.45);
|
||||
--ant-border: #d9d9d9;
|
||||
--ant-split: rgba(5, 5, 5, 0.06);
|
||||
--ant-bg-layout: #f0f2f5;
|
||||
--ant-error: #ff4d4f;
|
||||
--ant-success: #52c41a;
|
||||
--ant-radius: 6px;
|
||||
--ant-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "Noto Sans", sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: var(--ant-font);
|
||||
background: var(--ant-bg-layout);
|
||||
color: var(--ant-text);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ---- 品牌区(AntD Pro 登录页:logo + 标题 + slogan 居中) ------------- */
|
||||
.brand {
|
||||
text-align: center;
|
||||
padding: 72px 24px 24px;
|
||||
}
|
||||
.brand .logo-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.brand .logo-badge {
|
||||
width: 44px; height: 44px;
|
||||
border-radius: 10px;
|
||||
background: var(--ant-primary);
|
||||
color: #fff; font-size: 20px; font-weight: 600;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.35);
|
||||
}
|
||||
.brand h1 { font-size: 28px; font-weight: 600; letter-spacing: 0.5px; }
|
||||
.brand .slogan {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--ant-text-tertiary);
|
||||
}
|
||||
|
||||
/* ---- 登录卡片 ---------------------------------------------------------- */
|
||||
main { flex: 1; display: flex; justify-content: center; padding: 16px 24px 48px; }
|
||||
.login-card {
|
||||
width: 368px;
|
||||
max-width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,.03), 0 1px 6px -1px rgba(0,0,0,.02),
|
||||
0 2px 4px 0 rgba(0,0,0,.02);
|
||||
padding: 32px 32px 24px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.login-card h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid var(--ant-split);
|
||||
color: var(--ant-text);
|
||||
}
|
||||
.login-card h2 .active-tab { color: var(--ant-primary); }
|
||||
|
||||
.alert {
|
||||
display: none;
|
||||
font-size: 13px;
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--ant-radius);
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.alert.show { display: block; }
|
||||
.alert-error { color: #cf1322; background: #fff2f0; border: 1px solid #ffccc7; }
|
||||
.alert-ok { color: #237804; background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
|
||||
.field { margin-bottom: 20px; }
|
||||
.field label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: var(--ant-text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.field .input-wrap { position: relative; }
|
||||
.field .input-icon {
|
||||
position: absolute;
|
||||
left: 11px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--ant-text-tertiary);
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.field input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 4px 40px 4px 34px;
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--ant-border);
|
||||
border-radius: var(--ant-radius);
|
||||
transition: border-color .2s, box-shadow .2s;
|
||||
}
|
||||
.field input:hover { border-color: var(--ant-primary-hover); }
|
||||
.field input:focus {
|
||||
border-color: var(--ant-primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, .1);
|
||||
}
|
||||
.field input[aria-invalid="true"] { border-color: var(--ant-error); }
|
||||
.field .pwd-toggle {
|
||||
position: absolute;
|
||||
right: 4px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border: none; background: none;
|
||||
cursor: pointer; font-size: 14px;
|
||||
padding: 6px 8px;
|
||||
color: var(--ant-text-tertiary);
|
||||
}
|
||||
.field .pwd-toggle:hover { color: var(--ant-primary); }
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: var(--ant-radius);
|
||||
background: var(--ant-primary);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background .2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.btn-login:hover { background: var(--ant-primary-hover); }
|
||||
.btn-login:disabled { opacity: .6; cursor: not-allowed; }
|
||||
|
||||
.btn-sso {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--ant-border);
|
||||
border-radius: var(--ant-radius);
|
||||
background: #fff;
|
||||
color: var(--ant-text-secondary);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
.btn-sso:hover { color: var(--ant-primary); border-color: var(--ant-primary); }
|
||||
|
||||
/* ---- 演示账号提示(本地降级模式) --------------------------------------- */
|
||||
.demo-accounts {
|
||||
margin-top: 20px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border: 1px solid var(--ant-split);
|
||||
border-radius: var(--ant-radius);
|
||||
font-size: 12px;
|
||||
color: var(--ant-text-tertiary);
|
||||
line-height: 1.8;
|
||||
}
|
||||
.demo-accounts summary { cursor: pointer; color: var(--ant-text-secondary); }
|
||||
.demo-accounts code {
|
||||
background: rgba(0,0,0,.04);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--ant-text-secondary);
|
||||
}
|
||||
.demo-accounts .fill-link {
|
||||
color: var(--ant-primary);
|
||||
cursor: pointer;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
/* ---- 页脚 --------------------------------------------------------------- */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
font-size: 12px;
|
||||
color: var(--ant-text-tertiary);
|
||||
line-height: 1.8;
|
||||
}
|
||||
footer .links a {
|
||||
color: var(--ant-text-tertiary);
|
||||
text-decoration: none;
|
||||
margin: 0 10px;
|
||||
}
|
||||
footer .links a:hover { color: var(--ant-primary); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
iAOP 登录页(issue #150 / PRD 8.2)。
|
||||
后端:core/auth/auth_api.py(POST /auth/login → token + Set-Cookie)。
|
||||
会话:core/auth/session.py(HMAC token,HttpOnly cookie iaop_session)。
|
||||
写操作守卫:core/auth/auth_api.py require_auth / can_write(未登录/readonly 拒绝写)。
|
||||
iAOP 登录页(issue #150/#134 + 2026-08 Ant Design Pro 风格重构)。
|
||||
认证双轨(shared/session.js):
|
||||
· 后端 core/auth 可达 → POST /auth/login(HMAC 会话,HttpOnly cookie);
|
||||
· 纯静态部署 → UserStore 本地账号(首次访问自动播种演示账号)。
|
||||
角色三级(PRD 8.2):readonly 只读 / engineer 可配置 / admin 可发布回滚,
|
||||
登录后门户按角色过滤可见模块。
|
||||
-->
|
||||
<form class="auth-card" id="loginForm" autocomplete="on" novalidate>
|
||||
<div class="auth-brand">
|
||||
<div class="auth-logo">iA</div>
|
||||
<h1>云美工业AI优化平台</h1>
|
||||
<div class="brand">
|
||||
<div class="logo-row">
|
||||
<span class="logo-badge">iA</span>
|
||||
<h1>iAOP 云美工业AI优化平台</h1>
|
||||
</div>
|
||||
<div class="auth-sub">Template-Ti 一期 · 配置台登录</div>
|
||||
<div class="slogan">iAOP-Core 通用内核 · Template-Ti(海绵钛)一期 · 配置化驾驶舱 / 模板配置台 / LLM 助手</div>
|
||||
</div>
|
||||
|
||||
<div class="alert" id="alert" role="alert" aria-live="polite"></div>
|
||||
<main>
|
||||
<form class="login-card" id="loginForm" autocomplete="on" novalidate>
|
||||
<h2><span class="active-tab">账号密码登录</span></h2>
|
||||
|
||||
<div class="field">
|
||||
<label for="username">用户名</label>
|
||||
<input id="username" name="username" type="text" required
|
||||
autocomplete="username" placeholder="请输入用户名" maxlength="64">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">密码</label>
|
||||
<input id="password" name="password" type="password" required
|
||||
autocomplete="current-password" placeholder="请输入密码" minlength="8">
|
||||
</div>
|
||||
<div class="alert" id="alert" role="alert" aria-live="polite"></div>
|
||||
|
||||
<div class="auth-actions">
|
||||
<button type="submit" class="btn btn-primary" id="loginBtn">登录</button>
|
||||
<button type="button" class="btn btn-ghost" id="togglePwd" title="显示/隐藏密码" aria-label="显示或隐藏密码">👁</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="username">用户名</label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon">👤</span>
|
||||
<input id="username" name="username" type="text" required
|
||||
autocomplete="username" placeholder="请输入用户名" maxlength="64">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">密码</label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon">🔒</span>
|
||||
<input id="password" name="password" type="password" required
|
||||
autocomplete="current-password" placeholder="请输入密码" minlength="8">
|
||||
<button type="button" class="pwd-toggle" id="togglePwd"
|
||||
title="显示/隐藏密码" aria-label="显示或隐藏密码">👁</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- issue #152:OIDC SSO 双轨入口——配置了 issuer/client_id 时显示,
|
||||
未配置仅本地账号(PRD 8.2 双轨)。配置点在用户管理页维护。 -->
|
||||
<div class="auth-actions" id="sso-row" hidden>
|
||||
<button type="button" class="btn btn-ghost" id="ssoBtn" style="width:100%">企业 SSO 登录(OIDC)</button>
|
||||
</div>
|
||||
<button type="submit" class="btn-login" id="loginBtn">登 录</button>
|
||||
|
||||
<div class="role-hint">
|
||||
角色:readonly 只读 / engineer 可配置 / admin 可发布回滚。<br>
|
||||
未登录或只读角色不可执行写操作(PRD 8.2)。
|
||||
</div>
|
||||
<!-- issue #152:OIDC SSO 双轨入口——配置了 issuer/client_id 时显示 -->
|
||||
<button type="button" class="btn-sso" id="ssoBtn" hidden>企业 SSO 登录(OIDC)</button>
|
||||
|
||||
<div class="auth-foot">
|
||||
认证后端 core/auth · 会话 HttpOnly cookie · 密码 PBKDF2-HMAC-SHA256
|
||||
<details class="demo-accounts">
|
||||
<summary>演示账号(纯静态部署·本地账号模式)</summary>
|
||||
<div>
|
||||
管理员 <code>admin / Admin@12345</code><span class="fill-link" data-u="admin" data-p="Admin@12345">填入</span><br>
|
||||
工程师 <code>engineer / Engineer@12</code><span class="fill-link" data-u="engineer" data-p="Engineer@12">填入</span><br>
|
||||
只读 <code>viewer / Viewer@1234</code><span class="fill-link" data-u="viewer" data-p="Viewer@1234">填入</span><br>
|
||||
不同角色登录后可见模块不同;生产环境请立即改密。
|
||||
</div>
|
||||
</details>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="links">
|
||||
<a href="../index.html">平台门户</a>
|
||||
<a href="../cockpit/">驾驶舱</a>
|
||||
<a href="../chat/assistant.html">对话助手</a>
|
||||
</div>
|
||||
</form>
|
||||
iAOP-Core v1.0 · 认证后端 core/auth(PBKDF2-HMAC-SHA256)· 部署底座 K8s/Helm
|
||||
</footer>
|
||||
|
||||
<script src="user_store.js"></script>
|
||||
<script src="auth.js"></script>
|
||||
<script src="../shared/session.js"></script>
|
||||
<script>
|
||||
// issue #152:OIDC 双轨——已配置 issuer/client_id 则显示 SSO 入口,
|
||||
// 点击跳转标准授权码流程 authorize URL(state 已写入 sessionStorage);
|
||||
// 未配置则保持纯本地账号登录。
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (typeof UserStore === "undefined") return;
|
||||
var url = UserStore.oidcAuthorizeUrl("/auth/login.html");
|
||||
if (!url) return;
|
||||
var row = document.getElementById("sso-row");
|
||||
row.hidden = false;
|
||||
document.getElementById("ssoBtn").onclick = function () { location.href = url; };
|
||||
});
|
||||
(function () {
|
||||
"use strict";
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
function showAlert(msg, kind) {
|
||||
var el = $("alert");
|
||||
el.textContent = msg || "";
|
||||
el.className = "alert show " + (kind === "ok" ? "alert-ok" : "alert-error");
|
||||
}
|
||||
function clearAlert() { $("alert").className = "alert"; }
|
||||
|
||||
function nextUrl() {
|
||||
return new URLSearchParams(location.search).get("next") || "../index.html";
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// 首次访问播种本地演示账号(幂等;后端模式不影响)
|
||||
UserStore.seedDefaults().catch(function () {});
|
||||
|
||||
// 已登录直接跳转
|
||||
IAOP_SESSION.currentUser().then(function (u) {
|
||||
if (u) location.href = nextUrl();
|
||||
});
|
||||
|
||||
$("loginForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
var u = $("username").value.trim();
|
||||
var p = $("password").value;
|
||||
$("username").removeAttribute("aria-invalid");
|
||||
$("password").removeAttribute("aria-invalid");
|
||||
if (!u) {
|
||||
$("username").setAttribute("aria-invalid", "true");
|
||||
showAlert("请输入用户名", "error");
|
||||
return;
|
||||
}
|
||||
if (!p || p.length < 8) {
|
||||
$("password").setAttribute("aria-invalid", "true");
|
||||
showAlert("密码不少于 8 位", "error");
|
||||
return;
|
||||
}
|
||||
$("loginBtn").disabled = true;
|
||||
clearAlert();
|
||||
IAOP_SESSION.login(u, p).then(function (user) {
|
||||
showAlert("登录成功,欢迎 " + user.username + "(" +
|
||||
(IAOP_SESSION.ROLE_LABELS[user.role] || user.role) + ")", "ok");
|
||||
setTimeout(function () { location.href = nextUrl(); }, 400);
|
||||
}).catch(function (err) {
|
||||
$("password").setAttribute("aria-invalid", "true");
|
||||
showAlert(err.message || "登录失败", "error");
|
||||
}).finally(function () {
|
||||
$("loginBtn").disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
$("togglePwd").addEventListener("click", function () {
|
||||
var pwd = $("password");
|
||||
pwd.type = pwd.type === "password" ? "text" : "password";
|
||||
});
|
||||
|
||||
// 演示账号一键填入
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll(".fill-link"), function (link) {
|
||||
link.addEventListener("click", function () {
|
||||
$("username").value = link.getAttribute("data-u");
|
||||
$("password").value = link.getAttribute("data-p");
|
||||
$("password").focus();
|
||||
});
|
||||
});
|
||||
|
||||
// issue #152:OIDC 双轨——已配置 issuer/client_id 则显示 SSO 入口
|
||||
var url = UserStore.oidcAuthorizeUrl("/auth/login.html");
|
||||
if (url) {
|
||||
var btn = $("ssoBtn");
|
||||
btn.hidden = false;
|
||||
btn.addEventListener("click", function () { location.href = url; });
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -37,9 +37,19 @@ var UserStore = (function () {
|
||||
});
|
||||
}
|
||||
|
||||
/* 默认演示账号(Ant Design Pro 风格重构新增):纯静态部署(无 core/auth
|
||||
* 后端)时的开箱登录账号,首次进入登录页自动播种。生产环境登录后请立即
|
||||
* 在用户管理页改密或删除。密码均 ≥ 8 位(对齐 auth.js 前端校验)。 */
|
||||
var DEFAULT_ACCOUNTS = [
|
||||
{ username: "admin", password: "Admin@12345", role: "admin" },
|
||||
{ username: "engineer", password: "Engineer@12", role: "engineer" },
|
||||
{ username: "viewer", password: "Viewer@1234", role: "readonly" }
|
||||
];
|
||||
|
||||
return {
|
||||
ROLES: ROLES,
|
||||
ROLE_LABELS: ROLE_LABELS,
|
||||
DEFAULT_ACCOUNTS: DEFAULT_ACCOUNTS,
|
||||
|
||||
/* ---------- 用户 CRUD(对齐 core/auth/users.py 的 User 字段语义) ---------- */
|
||||
list: function () { return load(USERS_KEY, []); },
|
||||
@@ -91,6 +101,28 @@ var UserStore = (function () {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/* ---------- 登录校验 + 默认账号播种(纯静态部署降级,见 shared/session.js) ---------- */
|
||||
/* 校验用户名密码,成功返回 {username, role, active},失败返回 null。
|
||||
* 不区分「用户不存在/密码错误/已禁用」,防用户名枚举。 */
|
||||
verify: function (username, password) {
|
||||
var u = this.find(username);
|
||||
if (!u || !u.active) return Promise.resolve(null);
|
||||
return hashPassword(password, u.salt).then(function (h) {
|
||||
return h === u.hash ? { username: u.username, role: u.role,
|
||||
active: u.active } : null;
|
||||
});
|
||||
},
|
||||
/* 用户库为空时播种默认演示账号(幂等:已有任何用户则跳过)。 */
|
||||
seedDefaults: function () {
|
||||
var self = this;
|
||||
if (this.list().length > 0) return Promise.resolve(false);
|
||||
return DEFAULT_ACCOUNTS.reduce(function (p, acc) {
|
||||
return p.then(function () {
|
||||
return self.create(acc.username, acc.password, acc.role);
|
||||
});
|
||||
}, Promise.resolve()).then(function () { return true; });
|
||||
},
|
||||
|
||||
/* ---------- 写操作审计(template_registry._log 风格:时间/操作人/动作/资源/原因) ---------- */
|
||||
audit: function (action, resource, reason, actor) {
|
||||
var log = load(AUDIT_KEY, []);
|
||||
|
||||
+7
-5
@@ -29,16 +29,17 @@
|
||||
button.primary { background:var(--accent); color:#04202a; }
|
||||
button.danger { background:#7a1f1a; color:#ffb4ad; }
|
||||
.row { display:flex; gap:8px; margin:10px 0; flex-wrap:wrap; align-items:center; }
|
||||
.hint { font-size:11px; color:#64748b; }
|
||||
.role-readonly { color:#8aa0bd; } .role-engineer { color:#38bdf8; }
|
||||
.role-admin { color:var(--accent); }
|
||||
.hint { font-size:11px; color:rgba(0,0,0,.45); }
|
||||
.role-readonly { color:rgba(0,0,0,.45); } .role-engineer { color:#237804; }
|
||||
.role-admin { color:#ad6800; }
|
||||
.disabled-user { opacity:.45; }
|
||||
#audit-list { max-height:420px; overflow-y:auto; font-size:12px; font-family:Consolas,monospace; }
|
||||
#audit-list div { padding:3px 0; border-bottom:1px solid var(--line); color:var(--muted); }
|
||||
@media (max-width:1000px) { main { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="../shared/pro.css">
|
||||
</head>
|
||||
<body>
|
||||
<body data-pro-title="用户与角色管理" data-pro-home="../index.html" data-pro-login="login.html">
|
||||
<header id="topbar">
|
||||
<h1>用户/角色管理 <span class="sub">RBAC 三级:readonly / engineer / admin · PRD 8.2</span></h1>
|
||||
<div id="topbar-right">
|
||||
@@ -93,8 +94,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="auth.js"></script>
|
||||
<script src="user_store.js"></script>
|
||||
<script src="../shared/session.js"></script>
|
||||
<script src="../shared/pro-header.js"></script>
|
||||
<script src="users.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user