feat: 添加IP白名单/黑名单功能 - 支持单IP、CIDR、IP范围
This commit is contained in:
@@ -49,6 +49,9 @@
|
||||
<li data-page="online">
|
||||
<span class="icon">🔗</span> 在线用户
|
||||
</li>
|
||||
<li data-page="ip-rules">
|
||||
<span class="icon">🛡</span> IP白/黑名单
|
||||
</li>
|
||||
<li data-page="settings">
|
||||
<span class="icon">⚙</span> 系统设置
|
||||
</li>
|
||||
@@ -200,6 +203,37 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- IP白/黑名单 -->
|
||||
<div id="page-ip-rules" class="page">
|
||||
<div class="page-header">
|
||||
<h2>IP 白名单/黑名单</h2>
|
||||
<button class="btn btn-primary" onclick="showAddIPRule()">添加规则</button>
|
||||
</div>
|
||||
<div class="ip-rules-info" style="margin-bottom:16px;padding:12px;background:#fff;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,0.08)">
|
||||
<p style="color:#666;font-size:13px;line-height:1.8">
|
||||
<strong>规则说明:</strong><br>
|
||||
- 支持<strong>单IP</strong>(如 192.168.1.1)、<strong>CIDR</strong>(如 192.168.1.0/24)、<strong>IP范围</strong>(如 192.168.1.1-192.168.1.100)<br>
|
||||
- <strong>白名单</strong>:启用后只有白名单中的IP才能连接,黑名单中的IP会被拒绝<br>
|
||||
- <strong>黑名单</strong>:黑名单中的IP将被禁止连接<br>
|
||||
- 如果没有白名单规则,则所有IP默认允许(除非在黑名单中)
|
||||
</p>
|
||||
</div>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>IP地址/网段</th>
|
||||
<th>类型</th>
|
||||
<th>备注</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ip-rules-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 系统设置 -->
|
||||
<div id="page-settings" class="page">
|
||||
<h2>系统设置</h2>
|
||||
@@ -333,6 +367,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP规则弹窗 -->
|
||||
<div id="ip-rule-modal" class="modal" style="display:none">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="ip-rule-modal-title">添加IP规则</h3>
|
||||
<span class="modal-close" onclick="closeIPRuleModal()">×</span>
|
||||
</div>
|
||||
<form id="ip-rule-form">
|
||||
<input type="hidden" id="ip-rule-edit-id" value="">
|
||||
<div class="form-group">
|
||||
<label>IP地址/网段</label>
|
||||
<input type="text" id="ip-rule-ip" placeholder="如: 192.168.1.1 或 192.168.1.0/24 或 10.0.0.1-10.0.0.255" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>规则类型</label>
|
||||
<select id="ip-rule-type">
|
||||
<option value="blacklist">黑名单(禁止连接)</option>
|
||||
<option value="whitelist">白名单(允许连接)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>备注说明</label>
|
||||
<input type="text" id="ip-rule-note" placeholder="可选,填写备注说明">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="ip-rule-enabled" checked> 启用此规则
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" onclick="closeIPRuleModal()">取消</button>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示消息 -->
|
||||
<div id="toast" class="toast"></div>
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ function loadPage(page) {
|
||||
case 'files': loadFiles(currentPath); break;
|
||||
case 'logs': loadLogs(); break;
|
||||
case 'online': loadOnline(); break;
|
||||
case 'ip-rules': loadIPRules(); break;
|
||||
case 'settings': loadConfig(); break;
|
||||
}
|
||||
}
|
||||
@@ -458,3 +459,107 @@ if (token) {
|
||||
} else {
|
||||
showLogin();
|
||||
}
|
||||
|
||||
// --- IP规则管理 ---
|
||||
async function loadIPRules() {
|
||||
try {
|
||||
const rules = await api('GET', '/api/ip-rules');
|
||||
const tbody = document.getElementById('ip-rules-tbody');
|
||||
if (!rules || !rules.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:#999;padding:40px">暂无IP规则,所有IP默认允许连接</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = rules.map(r => {
|
||||
const typeLabel = r.type === 'whitelist'
|
||||
? '<span style="color:#667eea;font-weight:600">白名单</span>'
|
||||
: '<span style="color:#ff4d4f;font-weight:600">黑名单</span>';
|
||||
const statusLabel = r.enabled
|
||||
? '<span class="status-enabled">启用</span>'
|
||||
: '<span class="status-disabled">禁用</span>';
|
||||
return `<tr>
|
||||
<td>${r.id}</td>
|
||||
<td><code style="background:#f5f5f5;padding:2px 6px;border-radius:3px">${r.ip}</code></td>
|
||||
<td>${typeLabel}</td>
|
||||
<td>${r.note || '-'}</td>
|
||||
<td>${statusLabel}</td>
|
||||
<td>${formatTime(r.created_at)}</td>
|
||||
<td class="action-btns">
|
||||
<button class="btn btn-sm" onclick="editIPRule(${r.id}, '${r.ip}', '${r.type}', '${(r.note||'').replace(/'/g, "\\'")}', ${r.enabled})">编辑</button>
|
||||
<button class="btn btn-sm" onclick="toggleIPRule(${r.id}, '${r.ip}', '${r.type}', '${(r.note||'').replace(/'/g, "\\'")}', ${r.enabled})">${r.enabled ? '禁用' : '启用'}</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteIPRule(${r.id})">删除</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch (err) {
|
||||
showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showAddIPRule() {
|
||||
document.getElementById('ip-rule-modal-title').textContent = '添加IP规则';
|
||||
document.getElementById('ip-rule-edit-id').value = '';
|
||||
document.getElementById('ip-rule-form').reset();
|
||||
document.getElementById('ip-rule-enabled').checked = true;
|
||||
document.getElementById('ip-rule-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function editIPRule(id, ip, type, note, enabled) {
|
||||
document.getElementById('ip-rule-modal-title').textContent = '编辑IP规则';
|
||||
document.getElementById('ip-rule-edit-id').value = id;
|
||||
document.getElementById('ip-rule-ip').value = ip;
|
||||
document.getElementById('ip-rule-type').value = type;
|
||||
document.getElementById('ip-rule-note').value = note;
|
||||
document.getElementById('ip-rule-enabled').checked = enabled;
|
||||
document.getElementById('ip-rule-modal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeIPRuleModal() {
|
||||
document.getElementById('ip-rule-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('ip-rule-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const editId = document.getElementById('ip-rule-edit-id').value;
|
||||
const data = {
|
||||
ip: document.getElementById('ip-rule-ip').value,
|
||||
type: document.getElementById('ip-rule-type').value,
|
||||
note: document.getElementById('ip-rule-note').value,
|
||||
enabled: document.getElementById('ip-rule-enabled').checked
|
||||
};
|
||||
try {
|
||||
if (editId) {
|
||||
await api('PUT', '/api/ip-rules/' + editId, data);
|
||||
showToast('规则已更新');
|
||||
} else {
|
||||
await api('POST', '/api/ip-rules', data);
|
||||
showToast('规则添加成功');
|
||||
}
|
||||
closeIPRuleModal();
|
||||
loadIPRules();
|
||||
} catch (err) {
|
||||
showToast(err.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
async function toggleIPRule(id, ip, type, note, enabled) {
|
||||
try {
|
||||
await api('PUT', '/api/ip-rules/' + id, {
|
||||
ip, type, note, enabled: !enabled
|
||||
});
|
||||
showToast(!enabled ? '规则已启用' : '规则已禁用');
|
||||
loadIPRules();
|
||||
} catch (err) {
|
||||
showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIPRule(id) {
|
||||
if (!confirm('确定删除此IP规则吗?')) return;
|
||||
try {
|
||||
await api('DELETE', '/api/ip-rules/' + id);
|
||||
showToast('规则已删除');
|
||||
loadIPRules();
|
||||
} catch (err) {
|
||||
showToast(err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user