186 lines
7.6 KiB
HTML
186 lines
7.6 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>IP-账户映射管理</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
:root {
|
||
--bg: linear-gradient(135deg, #0f172a, #1e293b);
|
||
--card: #0b1224;
|
||
--accent: #22d3ee;
|
||
--accent-2: #a855f7;
|
||
--text: #e2e8f0;
|
||
--muted: #94a3b8;
|
||
--danger: #f87171;
|
||
}
|
||
body { margin:0; min-height:100vh; background:var(--bg); color:var(--text); font-family:"Segoe UI","Helvetica Neue",Arial,sans-serif; padding:24px; }
|
||
.shell { max-width: 820px; margin:0 auto; background:var(--card); border:1px solid rgba(255,255,255,0.08); border-radius:14px; padding:24px; box-shadow:0 25px 60px rgba(0,0,0,0.45); }
|
||
h1 { margin:0 0 10px; }
|
||
label { display:block; font-weight:600; color:#cbd5e1; margin:8px 0 4px; }
|
||
input { width:100%; padding:10px 12px; border-radius:10px; border:1px solid rgba(255,255,255,0.08); background:rgba(255,255,255,0.04); color:var(--text); }
|
||
button { padding:10px 14px; border:none; border-radius:10px; cursor:pointer; background:linear-gradient(135deg,var(--accent),var(--accent-2)); color:var(--text); font-weight:700; }
|
||
table { width:100%; border-collapse:collapse; margin-top:16px; }
|
||
th, td { padding:10px; border-bottom:1px solid rgba(255,255,255,0.08); text-align:left; }
|
||
.muted { color:var(--muted); }
|
||
.status { margin-top:12px; padding:10px; border-radius:10px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08); }
|
||
.error { color: var(--danger); }
|
||
.row { display:flex; gap:10px; }
|
||
.row > div { flex:1; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="shell">
|
||
<h1>IP-账户映射管理</h1>
|
||
<p class="muted" style="margin-top:0;">操作需输入额外密码(ZHIYUN_PASS)。通过校验后可增删改。</p>
|
||
|
||
<div class="status" id="auth-box">
|
||
<div class="row">
|
||
<div>
|
||
<label for="auth-pass">校验密码</label>
|
||
<input id="auth-pass" type="password" placeholder="输入 ZHIYUN_PASS">
|
||
</div>
|
||
<div style="align-self:flex-end;">
|
||
<button id="auth-btn" style="width:100%;">校验</button>
|
||
</div>
|
||
</div>
|
||
<div id="auth-msg" class="muted" style="margin-top:6px;"></div>
|
||
</div>
|
||
|
||
<div id="form-area" style="display:none; margin-top:16px;">
|
||
<div class="row">
|
||
<div>
|
||
<label for="ip">IP 地址</label>
|
||
<input id="ip" placeholder="例如:54.12.34.56">
|
||
</div>
|
||
<div>
|
||
<label for="account">账户名</label>
|
||
<input id="account" placeholder="与 config/accounts.yaml 中的 name 对应">
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:10px; display:flex; gap:10px;">
|
||
<button id="save-btn">新增/更新</button>
|
||
<button id="delete-btn" style="background:#ef4444;">删除</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="msg" class="status" style="display:none;"></div>
|
||
<table id="table" style="display:none;">
|
||
<thead>
|
||
<tr><th>IP</th><th>账户</th></tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<script>
|
||
const authBtn = document.getElementById('auth-btn');
|
||
const authPass = document.getElementById('auth-pass');
|
||
const authMsg = document.getElementById('auth-msg');
|
||
const formArea = document.getElementById('form-area');
|
||
const msg = document.getElementById('msg');
|
||
const table = document.getElementById('table');
|
||
const tbody = table.querySelector('tbody');
|
||
const saveBtn = document.getElementById('save-btn');
|
||
const delBtn = document.getElementById('delete-btn');
|
||
const ipInput = document.getElementById('ip');
|
||
const accountInput = document.getElementById('account');
|
||
|
||
function showMsg(text, isError=false) {
|
||
msg.style.display = 'block';
|
||
msg.className = 'status' + (isError ? ' error' : '');
|
||
msg.textContent = text;
|
||
}
|
||
|
||
async function auth() {
|
||
authBtn.disabled = true;
|
||
authMsg.textContent = '校验中...';
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('password', authPass.value);
|
||
const resp = await fetch('/mapping/auth', { method:'POST', body: formData });
|
||
const data = await resp.json();
|
||
if (!resp.ok) throw new Error(data.error || '校验失败');
|
||
authMsg.textContent = '校验成功,可进行操作';
|
||
formArea.style.display = 'block';
|
||
await loadList();
|
||
} catch (err) {
|
||
authMsg.textContent = err.message;
|
||
formArea.style.display = 'none';
|
||
table.style.display = 'none';
|
||
} finally {
|
||
authBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function loadList() {
|
||
try {
|
||
const resp = await fetch('/mapping/list');
|
||
const data = await resp.json();
|
||
if (!resp.ok) throw new Error(data.error || '获取失败');
|
||
const items = data.items || [];
|
||
tbody.innerHTML = items.map(item => `
|
||
<tr>
|
||
<td>${item.ip_address}</td>
|
||
<td>${item.account_name}</td>
|
||
</tr>
|
||
`).join('');
|
||
table.style.display = 'table';
|
||
} catch (err) {
|
||
showMsg(err.message, true);
|
||
}
|
||
}
|
||
|
||
async function save() {
|
||
const ip = ipInput.value.trim();
|
||
const acc = accountInput.value.trim();
|
||
if (!ip || !acc) {
|
||
showMsg('IP 和账户名不能为空', true);
|
||
return;
|
||
}
|
||
saveBtn.disabled = true;
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('ip', ip);
|
||
formData.append('account', acc);
|
||
const resp = await fetch('/mapping/upsert', { method:'POST', body: formData });
|
||
const data = await resp.json();
|
||
if (!resp.ok) throw new Error(data.error || '保存失败');
|
||
showMsg('保存成功');
|
||
await loadList();
|
||
} catch (err) {
|
||
showMsg(err.message, true);
|
||
} finally {
|
||
saveBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function remove() {
|
||
const ip = ipInput.value.trim();
|
||
if (!ip) {
|
||
showMsg('删除前请填写 IP', true);
|
||
return;
|
||
}
|
||
delBtn.disabled = true;
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('ip', ip);
|
||
const resp = await fetch('/mapping/delete', { method:'POST', body: formData });
|
||
const data = await resp.json();
|
||
if (!resp.ok) throw new Error(data.error || '删除失败');
|
||
showMsg('删除成功');
|
||
await loadList();
|
||
} catch (err) {
|
||
showMsg(err.message, true);
|
||
} finally {
|
||
delBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
authBtn.addEventListener('click', auth);
|
||
saveBtn.addEventListener('click', save);
|
||
delBtn.addEventListener('click', remove);
|
||
</script>
|
||
</body>
|
||
</html>
|