fix: 自动恢复失效令牌并保留身份入口
This commit is contained in:
@@ -149,9 +149,12 @@ class OperationsCenterTests(unittest.TestCase):
|
||||
def test_navigation_is_role_aware_and_keeps_project_context(self):
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
nav = (root / "src/components/xingbang/AgentNav.tsx").read_text(encoding="utf-8")
|
||||
selector = (root / "src/components/xingbang/IdentitySelector.tsx").read_text(encoding="utf-8")
|
||||
self.assertNotIn("四 Agent 工作区",nav)
|
||||
for label in ("业务助手","项目工作台","业务时效","配置治理","当前身份"):
|
||||
for label in ("业务助手","项目工作台","业务时效","配置治理"):
|
||||
self.assertIn(label,nav)
|
||||
self.assertIn("当前身份",selector)
|
||||
self.assertIn("选择身份",selector)
|
||||
for label in ("运行中心","检查依据","演示与验收","兼容入口"):
|
||||
self.assertNotIn(label,nav)
|
||||
self.assertIn("ROLE_LABEL",nav)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = path.resolve(__dirname, "..");
|
||||
const nav = fs.readFileSync(path.join(root, "src/components/xingbang/AgentNav.tsx"), "utf8");
|
||||
const selector = fs.readFileSync(path.join(root, "src/components/xingbang/IdentitySelector.tsx"), "utf8");
|
||||
|
||||
assert.match(nav, /response\.status===401/, "导航应识别无效访问令牌");
|
||||
assert.match(nav, /removeItem\("xingbang-access-token"\)/, "无效旧令牌应自动清理");
|
||||
assert.match(nav, /setItem\("xingbang-demo-role","manager"\)/, "清理后应恢复到客户经理演示身份");
|
||||
assert.match(nav, /pending=\{!identity\}/, "身份连接失败时仍应显示身份选择入口");
|
||||
assert.match(selector, /pending&&<option value="" disabled>选择身份<\/option>/, "待连接状态应给出明确的身份选择提示");
|
||||
|
||||
console.log("Identity recovery behavior checks passed");
|
||||
@@ -209,6 +209,7 @@ def main() -> int:
|
||||
("repository-diff", [git, "diff", "--check"], ROOT),
|
||||
("backend-unit", [project_python(), "-m", "unittest", "discover", "-s", ".", "-p", "test_*.py", "-v"], BACKEND),
|
||||
("reviewer-workspace-ui", [executable("node"), "scripts/test_reviewer_workspace.cjs"], ROOT),
|
||||
("identity-recovery-ui", [executable("node"), "scripts/test_identity_recovery.cjs"], ROOT),
|
||||
("frontend-build", [npm, "run", "build"], ROOT),
|
||||
("compose-local-config", [docker, "compose", "-f", "docker-compose.yml", "config", "--quiet"], ROOT),
|
||||
("compose-aliyun-config", [docker, "compose", "--env-file", "deploy/aliyun.env.example", "-f", "deploy/docker-compose.aliyun.yml", "config", "--quiet"], ROOT),
|
||||
|
||||
@@ -24,7 +24,14 @@ export default function AgentNav({active,project,identity:provided}:{active:stri
|
||||
useEffect(()=>{
|
||||
setRoleHint(sessionStorage.getItem("xingbang-demo-role")||"");
|
||||
if(provided)return;
|
||||
fetch("/api/risk-agent/access/context",{headers:identityHeaders()}).then(async r=>r.ok?setLoaded(await r.json()):null).catch(()=>null);
|
||||
fetch("/api/risk-agent/access/context",{headers:identityHeaders()}).then(async response=>{
|
||||
if(response.ok){setLoaded(await response.json());return;}
|
||||
if(response.status===401&&sessionStorage.getItem("xingbang-access-token")&&!sessionStorage.getItem("xingbang-demo-role")){
|
||||
sessionStorage.removeItem("xingbang-access-token");
|
||||
sessionStorage.setItem("xingbang-demo-role","manager");
|
||||
location.reload();
|
||||
}
|
||||
}).catch(()=>null);
|
||||
},[provided]);
|
||||
const identity=provided||loaded;
|
||||
const role=identity?.role||roleHint;
|
||||
@@ -42,6 +49,6 @@ export default function AgentNav({active,project,identity:provided}:{active:stri
|
||||
{canConfigure&&<a href="/data-access" className={active==="risk"?"selected":""}><Settings2 size={16}/>配置治理</a>}
|
||||
<span className="xb-nav-spacer"/>
|
||||
{project&&<span className="xb-project-context">项目 · {project}</span>}
|
||||
{identity?<IdentitySelector identity={identity}/>:<span className="xb-identity"><i/><span>当前身份</span><b>待连接</b></span>}
|
||||
<IdentitySelector identity={identity||{role:roleHint}} pending={!identity}/>
|
||||
</nav>;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import {ShieldCheck} from "lucide-react";
|
||||
|
||||
export const ROLE_LABEL:Record<string,string>={manager:"客户经理",reviewer:"评审员",admin:"配置管理员"};
|
||||
|
||||
export default function IdentitySelector({identity,dark=false}:{identity:any;dark?:boolean}){
|
||||
const role=identity?.role||"manager";
|
||||
export default function IdentitySelector({identity,dark=false,pending=false}:{identity:any;dark?:boolean;pending?:boolean}){
|
||||
const role=identity?.role||(pending?"":"manager");
|
||||
function switchRole(next:string){
|
||||
sessionStorage.removeItem("xingbang-access-token");
|
||||
sessionStorage.setItem("xingbang-demo-role",next);
|
||||
@@ -14,6 +14,7 @@ export default function IdentitySelector({identity,dark=false}:{identity:any;dar
|
||||
return <label className={`xb-role-switch role-${role}${dark?" dark":""}`}>
|
||||
<span>当前身份</span><ShieldCheck size={16}/>
|
||||
<select aria-label="切换演示身份" value={role} onChange={event=>switchRole(event.target.value)}>
|
||||
{pending&&<option value="" disabled>选择身份</option>}
|
||||
<option value="manager">客户经理</option><option value="reviewer">评审员</option><option value="admin">配置管理员</option>
|
||||
</select>
|
||||
</label>;
|
||||
|
||||
Reference in New Issue
Block a user