新增访问记录(仅超管): 登录成功/失败/退出/账号操作审计含IP, 顶栏入口在账号管理旁, 弹框展示+可清空
This commit is contained in:
+33
-1
@@ -86,6 +86,8 @@ tr.today:hover td{background:#fde68a!important}
|
||||
#detailModal.hidden{display:none}
|
||||
#userModal{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:8;display:flex;align-items:center;justify-content:center}
|
||||
#userModal.hidden{display:none}
|
||||
#accessModal{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:8;display:flex;align-items:center;justify-content:center}
|
||||
#accessModal.hidden{display:none}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -93,6 +95,7 @@ tr.today:hover td{background:#fde68a!important}
|
||||
<b>📋 招聘数据后台</b><span id="whoami" style="color:#94a3b8;font-size:12px"></span>
|
||||
<span class="sp"></span>
|
||||
<button id="userMgrBtn" class="hidden" onclick="openUserModal()">账号管理</button>
|
||||
<button id="accessLogBtn" class="hidden" onclick="openAccessModal()">访问记录</button>
|
||||
<button id="logoutBtn" class="hidden" onclick="logout()">退出登录</button>
|
||||
</div>
|
||||
<div class="wrap">
|
||||
@@ -178,8 +181,20 @@ tr.today:hover td{background:#fde68a!important}
|
||||
<tbody id="utable"></tbody></table>
|
||||
<div style="color:var(--sub);font-size:12px;margin-top:8px">提示:停用后该账号无法登录,已登录的会话在下次请求时失效;启用后恢复。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accessModal" onclick="if(event.target===this)this.classList.add('hidden')" class="hidden">
|
||||
<div class="panel" style="width:820px;max-width:94vw;max-height:88vh;overflow:auto">
|
||||
<h3>访问记录(最近500条)
|
||||
<span style="float:right">
|
||||
<button class="btn sub small" onclick="clearAccessLog()">清空</button>
|
||||
<button class="btn sub small" onclick="document.getElementById('accessModal').classList.add('hidden')">关闭</button>
|
||||
</span>
|
||||
</h3>
|
||||
<table><thead><tr><th>时间</th><th>用户</th><th>动作</th><th>说明</th><th>IP</th></tr></thead>
|
||||
<tbody id="alogBody"></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="detailModal" onclick="if(event.target===this)this.classList.add('hidden')" class="hidden">
|
||||
<div class="panel detail" style="width:820px;max-width:94vw;max-height:88vh;overflow:auto" id="detailBody"></div>
|
||||
</div>
|
||||
@@ -231,7 +246,7 @@ async function logout(){await api('/api/logout',{method:'POST'});location.reload
|
||||
async function enter(m){
|
||||
me=m;$('loginBox').classList.add('hidden');$('main').classList.remove('hidden');$('logoutBtn').classList.remove('hidden');
|
||||
$('whoami').textContent=`${m.username} · ${m.role==='super'?'超管':'HR'}`;
|
||||
if(m.role==='super'){$('userMgrBtn').classList.remove('hidden')}
|
||||
if(m.role==='super'){$('userMgrBtn').classList.remove('hidden');$('accessLogBtn').classList.remove('hidden')}
|
||||
loadStats().catch(e=>dataErr(e));loadList(1).catch(e=>dataErr(e));
|
||||
}
|
||||
function dataErr(e){
|
||||
@@ -366,6 +381,23 @@ async function addUser(){
|
||||
try{await api('/api/users',{method:'POST',body:JSON.stringify({username:$('nu_user').value,password:$('nu_pass').value})});toast('已创建');$('nu_user').value=$('nu_pass').value='';loadUsers()}
|
||||
catch(e){toast(e.message)}
|
||||
}
|
||||
async function openAccessModal(){
|
||||
$('accessModal').classList.remove('hidden');
|
||||
loadAccessLog().catch(()=>toast('访问记录加载失败'));
|
||||
}
|
||||
async function loadAccessLog(){
|
||||
const list=await api('/api/access-log');
|
||||
const actNames={login_ok:['登录成功','rec'],login_fail:['登录失败','irr'],logout:['退出','no'],register_super:['初始化超管','yes'],user_create:['创建账号','yes'],user_toggle:['停用/启用','maybe'],user_resetpwd:['重置密码','maybe'],log_clear:['清空记录','maybe']};
|
||||
$('alogBody').innerHTML=list.length?list.map(l=>`<tr>
|
||||
<td>${esc(l.created_at)}</td><td><b>${esc(l.username)||'-'}</b></td>
|
||||
<td><span class="tag ${(actNames[l.action]||['','no'])[1]}">${(actNames[l.action]||[l.action,'no'])[0]}</span></td>
|
||||
<td>${esc(l.detail)||'-'}</td><td style="color:var(--sub)">${esc(l.ip)||'-'}</td></tr>`).join('')
|
||||
:'<tr><td colspan="5" style="text-align:center;color:var(--sub)">暂无记录</td></tr>';
|
||||
}
|
||||
async function clearAccessLog(){
|
||||
if(!confirm('确定清空全部访问记录吗?此操作不可恢复'))return;
|
||||
try{await api('/api/access-log/clear',{method:'POST'});toast('已清空');loadAccessLog()}catch(e){toast(e.message)}
|
||||
}
|
||||
async function toggleUser(id,name,disabled){
|
||||
const verb=disabled?'启用':'停用';
|
||||
if(!confirm(`确定${verb}账号「${name}」吗?`))return;
|
||||
|
||||
Reference in New Issue
Block a user