feat: audit api, sdwan persist, relay fallback updates
This commit is contained in:
@@ -31,11 +31,9 @@
|
||||
<h1 class="text-2xl font-black text-white mb-2">INP2P 控制台</h1>
|
||||
<p class="text-slate-500 text-sm mb-6">登录后可管理节点、SDWAN、连接与租户</p>
|
||||
<div class="space-y-3">
|
||||
<input v-model="loginTenant" class="ipt" placeholder="Tenant ID(用户登录)" @keyup.enter="login">
|
||||
<input v-model="loginUser" class="ipt" placeholder="用户名(如 admin)" @keyup.enter="login">
|
||||
<input v-model="loginUser" class="ipt" placeholder="用户名(全局唯一,字母≥6位)" @keyup.enter="login">
|
||||
<input v-model="loginPass" class="ipt" type="password" placeholder="密码" @keyup.enter="login">
|
||||
<div class="text-xs text-slate-500 text-center">或使用主 Token 登录(管理员)</div>
|
||||
<input v-model="loginToken" class="ipt" type="password" placeholder="Master Token" @keyup.enter="login">
|
||||
<div class="text-xs text-slate-500 text-center">用户名要求:仅字母、长度≥6、全局唯一</div>
|
||||
<button class="btn w-full" :disabled="busy" @click="login">{{ busy ? '登录中...' : '登录' }}</button>
|
||||
<div class="text-[11px] text-slate-500 text-center">Build: {{ buildVersion }}</div>
|
||||
<div v-if="loginErr" class="text-red-400 text-sm">{{ loginErr }}</div>
|
||||
@@ -151,6 +149,23 @@
|
||||
</div>
|
||||
<button class="btn2 mt-3" @click="addSDWANNode">+ 添加节点</button>
|
||||
</div>
|
||||
|
||||
<div class="glass rounded-xl p-4">
|
||||
<div class="font-bold mb-3">子网代理(Subnet Proxy)</div>
|
||||
<div class="text-xs text-slate-400 mb-2">示例:local 192.168.0.0/24 → virtual 10.0.100.0/24(掩码需一致)</div>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(s,i) in sd.subnetProxies" :key="i" class="grid grid-cols-1 md:grid-cols-6 gap-2">
|
||||
<select class="ipt" v-model="s.node">
|
||||
<option value="">选择节点</option>
|
||||
<option v-for="x in nodes" :key="'sp'+x.name" :value="x.name">{{ x.name }}</option>
|
||||
</select>
|
||||
<input class="ipt md:col-span-2" v-model="s.localCIDR" placeholder="192.168.0.0/24">
|
||||
<input class="ipt md:col-span-2" v-model="s.virtualCIDR" placeholder="10.0.100.0/24">
|
||||
<button class="btn2" @click="removeSubnetProxy(i)">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn2 mt-3" @click="addSubnetProxy">+ 添加代理</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="tab==='p2p'" class="space-y-4">
|
||||
@@ -287,11 +302,11 @@ createApp({
|
||||
|
||||
const loggedIn = ref(false), busy = ref(false), msg = ref(''), msgType = ref('ok');
|
||||
const role = ref(''), status = ref(1);
|
||||
const loginTenant = ref('1'), loginUser = ref('admin'), loginPass = ref('admin'), loginToken = ref(''), loginErr = ref('');
|
||||
const loginUser = ref(''), loginPass = ref(''), loginErr = ref('');
|
||||
const refreshSec = ref(15), timer = ref(null);
|
||||
|
||||
const health = ref({}), stats = ref({}), nodes = ref([]), nodeKeyword = ref('');
|
||||
const sd = ref({ enabled:false, name:'sdwan-main', gatewayCIDR:'10.10.0.0/24', mode:'mesh', hubNode:'', mtu:1420, nodes:[], routes:['10.10.0.0/24'] });
|
||||
const sd = ref({ enabled:false, name:'sdwan-main', gatewayCIDR:'10.10.0.0/24', mode:'mesh', hubNode:'', mtu:1420, nodes:[], routes:['10.10.0.0/24'], subnetProxies:[] });
|
||||
const connectForm = ref({ from:'', to:'', srcPort:80, dstPort:80, appName:'manual-connect' });
|
||||
|
||||
const tenants = ref([]), activeTenant = ref(1), keys = ref([]), users = ref([]), enrolls = ref([]);
|
||||
@@ -300,7 +315,7 @@ createApp({
|
||||
const userForm = ref({ role:'operator', email:'', password:'' });
|
||||
|
||||
const tokenType = ref('');
|
||||
const isAdmin = computed(() => role.value === 'admin' && tokenType.value !== 'session');
|
||||
const isAdmin = computed(() => role.value === 'admin');
|
||||
const filteredTabs = computed(() => isAdmin.value ? tabs : tabs.filter(t => !['tenants','apikeys','users','enroll'].includes(t.id)));
|
||||
const filteredNodes = computed(() => {
|
||||
const k = (nodeKeyword.value || '').trim().toLowerCase();
|
||||
@@ -341,19 +356,14 @@ createApp({
|
||||
loginErr.value = '';
|
||||
busy.value = true;
|
||||
try {
|
||||
let d;
|
||||
if ((loginToken.value || '').trim()) {
|
||||
d = await fetch('/api/v1/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ token: loginToken.value.trim() }) }).then(r=>r.json());
|
||||
if (d.error) throw new Error(d.message || 'token 登录失败');
|
||||
localStorage.setItem('master_t', d.token || '');
|
||||
} else {
|
||||
d = await fetch('/api/v1/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ tenant: Number(loginTenant.value || 1), username: loginUser.value, password: loginPass.value }) }).then(r=>r.json());
|
||||
if (d.error) throw new Error(d.message || '用户名密码登录失败');
|
||||
}
|
||||
const uname = (loginUser.value || '').trim();
|
||||
if (!/^[A-Za-z]{6,}$/.test(uname)) throw new Error('用户名需仅字母且≥6位');
|
||||
const d = await fetch('/api/v1/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ username: uname, password: loginPass.value }) }).then(r=>r.json());
|
||||
if (d.error) throw new Error(d.message || '用户名密码登录失败');
|
||||
localStorage.setItem('t', d.token || '');
|
||||
role.value = d.role || '';
|
||||
status.value = d.status ?? 1;
|
||||
tokenType.value = d.token_type || (localStorage.getItem('t') === localStorage.getItem('master_t') ? 'master' : 'apikey');
|
||||
tokenType.value = d.token_type || 'session';
|
||||
if (status.value !== 1) throw new Error('账号已停用');
|
||||
loggedIn.value = true;
|
||||
await refreshAll();
|
||||
@@ -398,6 +408,8 @@ createApp({
|
||||
};
|
||||
const addSDWANNode = () => sd.value.nodes = [...(sd.value.nodes || []), { node:'', ip:'' }];
|
||||
const removeSDWANNode = i => sd.value.nodes.splice(i, 1);
|
||||
const addSubnetProxy = () => sd.value.subnetProxies = [...(sd.value.subnetProxies || []), { node:'', localCIDR:'', virtualCIDR:'' }];
|
||||
const removeSubnetProxy = i => sd.value.subnetProxies.splice(i, 1);
|
||||
const autoAssignIPs = () => {
|
||||
const used = new Set();
|
||||
(sd.value.nodes || []).forEach(n => { const p = (n.ip||'').split('.'); if (p.length===4) used.add(Number(p[3])); });
|
||||
@@ -575,11 +587,11 @@ createApp({
|
||||
|
||||
return {
|
||||
buildVersion, tab, filteredTabs, loggedIn, busy, msg, msgType, role, status, tokenType,
|
||||
loginTenant, loginUser, loginPass, loginToken, loginErr, refreshSec,
|
||||
loginUser, loginPass, loginErr, refreshSec,
|
||||
health, stats, nodes, nodeKeyword, filteredNodes, sd, connectForm,
|
||||
tenants, activeTenant, keys, users, enrolls, tenantForm, keyForm, userForm,
|
||||
natText, uptime, fmtTime,
|
||||
login, logout, refreshAll, saveSDWAN, addSDWANNode, removeSDWANNode, autoAssignIPs,
|
||||
login, logout, refreshAll, saveSDWAN, addSDWANNode, removeSDWANNode, addSubnetProxy, removeSubnetProxy, autoAssignIPs,
|
||||
kickNode, renameNode, changeNodeIP, openAppManager, pushAppConfigs, openConnect, doConnect,
|
||||
createTenant, loadTenants, setTenantStatus,
|
||||
createKey, loadKeys, setKeyStatus,
|
||||
|
||||
Reference in New Issue
Block a user