feat: 用户权限系统(权限位/管理员全权限/前端隐藏/最后管理员保护)
This commit is contained in:
+60
-9
@@ -20,6 +20,29 @@ function _csrfHeaders(){
|
||||
return _csrfToken?{'X-CSRF-Token':_csrfToken}:{};
|
||||
}
|
||||
|
||||
// ===== 当前用户与权限 =====
|
||||
// _me 来自 /api/me:{username, is_admin, perms:[tasks/devices/apks/logs]}
|
||||
// 管理员 perms 恒为全部。无权限的功能入口(tab/按钮)用 data-perm 标记或 _can() 判断隐藏。
|
||||
let _me={is_admin:false,perms:[],username:''};
|
||||
async function loadMe(){
|
||||
try{
|
||||
const r=await apiGet('/api/me');
|
||||
if(r&&r.ok)_me=r.user||{is_admin:false,perms:[],username:''};
|
||||
const un=document.getElementById('me-username');
|
||||
if(un)un.textContent=_me.username||'';
|
||||
// 隐藏无权限的 tab 和按钮(data-perm="admin" 表示仅管理员)
|
||||
document.querySelectorAll('[data-perm]').forEach(function(el){
|
||||
const p=el.dataset.perm;
|
||||
const ok=(p==='admin')?_me.is_admin:(_me.perms||[]).includes(p);
|
||||
if(!ok)el.style.display='none';
|
||||
});
|
||||
}catch(e){}
|
||||
}
|
||||
function _can(perm){
|
||||
return _me.is_admin||(_me.perms||[]).includes(perm);
|
||||
}
|
||||
const PERM_LABELS={tasks:'任务管理',devices:'设备控制',apks:'应用管理',logs:'日志查看'};
|
||||
|
||||
async function apiGet(url){
|
||||
const r=await fetch(url);
|
||||
if(r.status===401){window.location='/login';return null;}
|
||||
@@ -1892,8 +1915,10 @@ function renderActionRow(a){
|
||||
'<td>'+n+'</td>'+
|
||||
'<td style="font-size:12px;color:#94a3b8">'+esc(a.created_at||'-')+'</td>'+
|
||||
'<td>'+
|
||||
'<button class="btn btn-xs" onclick="openActionModal(\''+a.id+'\')">编辑</button> '+
|
||||
'<button class="btn btn-danger btn-xs" onclick="deleteAction(\''+a.id+'\')">删除</button>'+
|
||||
(_can('tasks')?(
|
||||
'<button class="btn btn-xs" onclick="openActionModal(\''+a.id+'\')">编辑</button> '+
|
||||
'<button class="btn btn-danger btn-xs" onclick="deleteAction(\''+a.id+'\')">删除</button>'
|
||||
):'<span class="text-muted">只读</span>')+
|
||||
'</td>'+
|
||||
'</tr>';
|
||||
}
|
||||
@@ -1976,8 +2001,10 @@ function renderGroupRow(g){
|
||||
'<td>'+serials.length+'</td>'+
|
||||
'<td class="text-muted" style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(serials.length?serials.map(esc).join(', '):'-')+'</td>'+
|
||||
'<td>'+
|
||||
'<button class="btn btn-xs" onclick="openGroupModal(\''+esc(g.name)+'\')">编辑</button>'+
|
||||
'<button class="btn btn-danger btn-xs" onclick="deleteGroup(\''+esc(g.name)+'\')">删除</button>'+
|
||||
(_can('tasks')?(
|
||||
'<button class="btn btn-xs" onclick="openGroupModal(\''+esc(g.name)+'\')">编辑</button>'+
|
||||
'<button class="btn btn-danger btn-xs" onclick="deleteGroup(\''+esc(g.name)+'\')">删除</button>'
|
||||
):'<span class="text-muted">只读</span>')+
|
||||
'</td>'+
|
||||
'</tr>';
|
||||
}
|
||||
@@ -2086,13 +2113,29 @@ function renderUserRow(u){
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
function togglePermBoxes(){
|
||||
// 勾选"管理员权限"时:权限位全选并禁用(管理员拥有全部权限)
|
||||
const isAdmin=document.getElementById('u-is_admin').checked;
|
||||
document.querySelectorAll('.u-perm').forEach(cb=>{
|
||||
cb.disabled=isAdmin;
|
||||
if(isAdmin)cb.checked=true;
|
||||
});
|
||||
}
|
||||
|
||||
function openUserModal(uid){
|
||||
const isEdit=!!uid;
|
||||
document.getElementById('modal-title').textContent=isEdit?'编辑用户':'新建用户';
|
||||
const permBoxes=Object.keys(PERM_LABELS).map(p=>
|
||||
'<label style="margin-right:12px"><input type="checkbox" class="u-perm" value="'+p+'">'+PERM_LABELS[p]+'</label>'
|
||||
).join('');
|
||||
document.getElementById('modal-body').innerHTML=
|
||||
'<div class="form-group"><label>用户名</label><input type="text" class="form-control" id="u-username" '+(isEdit?'disabled':'')+' placeholder="用户名"></div>'+
|
||||
'<div class="form-group"><label>密码'+(isEdit?'(留空不修改)':'')+'</label><input type="password" class="form-control" id="u-password" placeholder="密码"></div>'+
|
||||
'<div class="form-group form-check"><input type="checkbox" id="u-is_admin" checked><label for="u-is_admin">管理员权限</label></div>';
|
||||
'<div class="form-group form-check"><input type="checkbox" id="u-is_admin" onchange="togglePermBoxes()" checked><label for="u-is_admin">管理员权限(拥有全部权限)</label></div>'+
|
||||
'<div class="form-group"><label>业务权限(管理员自动拥有全部,无需勾选)</label>'+
|
||||
'<div>'+permBoxes+'</div>'+
|
||||
'<div class="help">任务管理=任务/自定义动作/分组;设备控制=停止/释放/清异常/扫描/抓元素;应用管理=APK上传安装;日志查看=日志页</div>'+
|
||||
'</div>';
|
||||
|
||||
document.getElementById('modal-footer').innerHTML=
|
||||
'<button class="btn" onclick="closeModal()">取消</button>'+
|
||||
@@ -2100,13 +2143,19 @@ function openUserModal(uid){
|
||||
|
||||
if(isEdit){
|
||||
// 预填充(从表格数据获取)
|
||||
fetch('/api/users').then(r=>r.json()).then(r=>{
|
||||
const u=r.users.find(x=>x.id===uid);
|
||||
apiGet('/api/users').then(r=>{
|
||||
const u=(r&&r.users||[]).find(x=>x.id===uid);
|
||||
if(u){
|
||||
document.getElementById('u-username').value=u.username;
|
||||
document.getElementById('u-is_admin').checked=u.is_admin;
|
||||
document.querySelectorAll('.u-perm').forEach(cb=>{
|
||||
cb.checked=(u.perms||[]).includes(cb.value);
|
||||
});
|
||||
togglePermBoxes();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
togglePermBoxes();
|
||||
}
|
||||
showModal();
|
||||
}
|
||||
@@ -2115,15 +2164,16 @@ async function saveUser(uid){
|
||||
const username=document.getElementById('u-username').value.trim();
|
||||
const password=document.getElementById('u-password').value;
|
||||
const is_admin=document.getElementById('u-is_admin').checked;
|
||||
const perms=[...document.querySelectorAll('.u-perm:checked')].map(cb=>cb.value);
|
||||
|
||||
if(uid){
|
||||
const data={is_admin:is_admin};
|
||||
const data={is_admin:is_admin,perms:perms};
|
||||
if(password)data.password=password;
|
||||
const r=await apiPut('/api/users/'+uid,data);
|
||||
handleResult(r,()=>{closeModal();loadUsers();});
|
||||
}else{
|
||||
if(!username||!password){showToast('用户名和密码不能为空','error');return;}
|
||||
const r=await apiPost('/api/users',{username:username,password:password,is_admin:is_admin});
|
||||
const r=await apiPost('/api/users',{username:username,password:password,is_admin:is_admin,perms:perms});
|
||||
handleResult(r,()=>{closeModal();loadUsers();});
|
||||
}
|
||||
}
|
||||
@@ -2520,4 +2570,5 @@ async function scanForeground(){
|
||||
|
||||
// ================== 初始化 ==================
|
||||
initCsrf(); // 获取 CSRF token(非 GET 请求需携带)
|
||||
loadMe(); // 获取当前用户权限,隐藏无权限的功能入口
|
||||
showTab('monitor');
|
||||
|
||||
Reference in New Issue
Block a user