From ba63085de91486302fad2521662a984dd619a936 Mon Sep 17 00:00:00 2001 From: openclaw Date: Fri, 6 Mar 2026 15:20:33 +0800 Subject: [PATCH] feat: admin settings and audit UI --- web/index.html | 73 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/web/index.html b/web/index.html index de11235..67999fc 100644 --- a/web/index.html +++ b/web/index.html @@ -195,6 +195,44 @@ +
+
高级设置(越权能力)
+
仅系统管理员可见。开启会记录审计日志。
+
+ + + +
+ +
+ +
+
审计日志
+
+ + +
+
+ + + + + + + + + + + + + + + + +
IDActorActionTargetDetailIPTime
{{ a.id }}{{ a.actor_type }}:{{ a.actor_id }}{{ a.action }}{{ a.target_type }}:{{ a.target_id }}{{ a.detail }}{{ a.ip }}{{ fmtTime(a.created_at*1000) }}
暂无审计记录
+
+
+
创建租户
@@ -297,7 +335,8 @@ createApp({ const tab = ref('dashboard'); const tabs = [ {id:'dashboard',name:'仪表盘'},{id:'nodes',name:'节点'},{id:'sdwan',name:'SDWAN'},{id:'p2p',name:'P2P'}, - {id:'tenants',name:'租户'},{id:'apikeys',name:'API Key'},{id:'users',name:'用户'},{id:'enroll',name:'Enroll'} + {id:'tenants',name:'租户'},{id:'apikeys',name:'API Key'},{id:'users',name:'用户'},{id:'enroll',name:'Enroll'}, + {id:'settings',name:'高级设置'},{id:'audit',name:'审计日志'} ]; const loggedIn = ref(false), busy = ref(false), msg = ref(''), msgType = ref('ok'); @@ -315,8 +354,11 @@ createApp({ const userForm = ref({ role:'operator', email:'', password:'' }); const tokenType = ref(''); + const settings = ref({ advanced_impersonate:'0', advanced_force_network:'0', advanced_cross_tenant:'0' }); + const audit = ref([]); + const auditTenant = ref(''); const isAdmin = computed(() => role.value === 'admin'); - const filteredTabs = computed(() => isAdmin.value ? tabs : tabs.filter(t => !['tenants','apikeys','users','enroll'].includes(t.id))); + const filteredTabs = computed(() => isAdmin.value ? tabs : tabs.filter(t => !['tenants','apikeys','users','enroll','settings','audit'].includes(t.id))); const filteredNodes = computed(() => { const k = (nodeKeyword.value || '').trim().toLowerCase(); if (!k) return nodes.value; @@ -392,6 +434,10 @@ createApp({ const nd = await api('/api/v1/nodes'); nodes.value = nd.nodes || []; sd.value = await api('/api/v1/sdwans'); + if (isAdmin.value) { + try { settings.value = await api('/api/v1/admin/settings'); } catch(_) {} + try { const a = await api('/api/v1/admin/audit?limit=50'); audit.value = a.logs || []; } catch(_) {} + } } catch (e) { toast(e.message || '刷新失败', 'error'); } finally { @@ -545,6 +591,27 @@ createApp({ try { const d = await api('/api/v1/tenants/enroll'); enrolls.value = d.enrolls || []; } catch(e){ toast(e.message, 'error'); } }; + + const saveSettings = async () => { + if (!isAdmin.value) return; + try { + for (const k of Object.keys(settings.value || {})) { + await api('/api/v1/admin/settings', { method:'POST', body: JSON.stringify({ key: k, value: String(settings.value[k]) }) }); + } + toast('高级设置已保存'); + settings.value = await api('/api/v1/admin/settings'); + } catch (e) { toast(e.message, 'error'); } + }; + + const loadAudit = async () => { + if (!isAdmin.value) return; + try { + const q = auditTenant.value ? `?tenant=${auditTenant.value}&limit=100` : '?limit=100'; + const d = await api('/api/v1/admin/audit' + q); + audit.value = d.logs || []; + toast('审计日志已刷新'); + } catch (e) { toast(e.message, 'error'); } + }; const createEnroll = async () => { try { const d = await api('/api/v1/tenants/enroll', { method:'POST', body:'{}' }); @@ -590,6 +657,7 @@ createApp({ loginUser, loginPass, loginErr, refreshSec, health, stats, nodes, nodeKeyword, filteredNodes, sd, connectForm, tenants, activeTenant, keys, users, enrolls, tenantForm, keyForm, userForm, + settings, audit, auditTenant, natText, uptime, fmtTime, login, logout, refreshAll, saveSDWAN, addSDWANNode, removeSDWANNode, addSubnetProxy, removeSubnetProxy, autoAssignIPs, kickNode, renameNode, changeNodeIP, openAppManager, pushAppConfigs, openConnect, doConnect, @@ -597,6 +665,7 @@ createApp({ createKey, loadKeys, setKeyStatus, createUser, loadUsers, setUserStatus, resetUserPassword, deleteUser, createEnroll, loadEnrolls, setEnrollStatus, consumeEnroll, + saveSettings, loadAudit, updateCharts }; }