5bb84c159a
- Add EvictClient method to DHCP Server - Add /api/dhcp/leases/evict endpoint to force client IP release - Add 'Evict' button in Web UI for online clients - Update table layout to include Action column - Evicted client will be forced to get a new IP on next DHCP request
859 sor
29 KiB
JavaScript
859 sor
29 KiB
JavaScript
let sessionId = localStorage.getItem('session_id') || null;
|
|
let autoRefreshInterval = null;
|
|
let autoRefreshEnabled = false;
|
|
|
|
// Restore session on page load
|
|
if (sessionId) {
|
|
// Verify session is still valid
|
|
fetch('/api/session/verify', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ session_id: sessionId })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.valid) {
|
|
document.getElementById('loginSection').style.display = 'none';
|
|
document.getElementById('dashboard').style.display = 'block';
|
|
document.getElementById('logoutBtn').style.display = 'block';
|
|
loadDashboard();
|
|
loadDHCPConfig();
|
|
loadDNSConfig();
|
|
} else {
|
|
// Session expired, clear and show login
|
|
localStorage.removeItem('session_id');
|
|
sessionId = null;
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('Session verify error:', err);
|
|
// On error, clear and show login
|
|
localStorage.removeItem('session_id');
|
|
sessionId = null;
|
|
});
|
|
}
|
|
|
|
// Auto Refresh
|
|
function toggleAutoRefresh() {
|
|
autoRefreshEnabled = !autoRefreshEnabled;
|
|
const btn = document.getElementById('autoRefreshBtn');
|
|
|
|
if (autoRefreshEnabled) {
|
|
btn.textContent = '▶️ 自动刷新: 开';
|
|
autoRefreshInterval = setInterval(loadClients, 10000); // 每10秒刷新
|
|
} else {
|
|
btn.textContent = '⏸️ 自动刷新: 关';
|
|
clearInterval(autoRefreshInterval);
|
|
}
|
|
}
|
|
|
|
// Login
|
|
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
|
|
try {
|
|
const response = await fetch('/api/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
sessionId = data.session_id;
|
|
localStorage.setItem('session_id', sessionId);
|
|
document.getElementById('loginSection').style.display = 'none';
|
|
document.getElementById('dashboard').style.display = 'block';
|
|
document.getElementById('logoutBtn').style.display = 'block';
|
|
loadDashboard();
|
|
loadDHCPConfig();
|
|
loadDNSConfig();
|
|
} else {
|
|
alert(data.error || '登录失败');
|
|
}
|
|
} catch (error) {
|
|
alert('登录失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Logout
|
|
document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
localStorage.removeItem('session_id');
|
|
location.reload();
|
|
});
|
|
|
|
// Navigation
|
|
document.querySelectorAll('nav a').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const target = e.target.getAttribute('href').substring(1);
|
|
|
|
document.querySelectorAll('section').forEach(section => {
|
|
if (section.id !== 'loginSection') {
|
|
section.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
document.getElementById(target).style.display = 'block';
|
|
|
|
if (target === 'dashboard') loadDashboard();
|
|
if (target === 'clients') loadClients();
|
|
if (target === 'dhcp') loadDHCPConfig();
|
|
if (target === 'dns') loadDNSConfig();
|
|
if (target === 'settings') loadSystemInfo();
|
|
});
|
|
});
|
|
|
|
// Load Dashboard
|
|
async function loadDashboard() {
|
|
try {
|
|
const response = await fetch('/api/dashboard', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
document.getElementById('activeLeases').textContent = data.active_leases || 0;
|
|
document.getElementById('staticBindings').textContent = data.static_bindings || 0;
|
|
document.getElementById('dnsRecords').textContent = data.dns_records || 0;
|
|
document.getElementById('onlineDevices').textContent = data.online_devices || 0;
|
|
} catch (error) {
|
|
console.error('Failed to load dashboard:', error);
|
|
}
|
|
}
|
|
|
|
// Load DHCP Clients
|
|
async function loadClients() {
|
|
try {
|
|
const response = await fetch('/api/dhcp/leases', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const tbody = document.querySelector('#clientsTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
if (!data.leases || data.leases.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:#999;">暂无客户端</td></tr>';
|
|
updatePoolStats(0, 0);
|
|
return;
|
|
}
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
let activeCount = 0;
|
|
|
|
data.leases.forEach(lease => {
|
|
const expiresAt = lease.ExpiresAt || 0;
|
|
const remaining = expiresAt - now;
|
|
const isActive = remaining > 0;
|
|
if (isActive) activeCount++;
|
|
|
|
const row = document.createElement('tr');
|
|
|
|
// MAC
|
|
const macCell = document.createElement('td');
|
|
macCell.textContent = lease.MAC || '-';
|
|
row.appendChild(macCell);
|
|
|
|
// IP
|
|
const ipCell = document.createElement('td');
|
|
ipCell.textContent = lease.IP || '-';
|
|
row.appendChild(ipCell);
|
|
|
|
// Hostname
|
|
const hostCell = document.createElement('td');
|
|
hostCell.textContent = lease.Hostname || '-';
|
|
row.appendChild(hostCell);
|
|
|
|
// Remaining time
|
|
const remainCell = document.createElement('td');
|
|
remainCell.textContent = isActive ? formatTimeRemaining(remaining) : '已过期';
|
|
remainCell.style.color = isActive ? '#27ae60' : '#e74c3c';
|
|
row.appendChild(remainCell);
|
|
|
|
// Expiry time
|
|
const expireCell = document.createElement('td');
|
|
expireCell.textContent = expiresAt > 0 ? new Date(expiresAt * 1000).toLocaleString() : '-';
|
|
row.appendChild(expireCell);
|
|
|
|
// Status
|
|
const statusCell = document.createElement('td');
|
|
statusCell.innerHTML = isActive ? '<span class="status-active">● 在线</span>' : '<span class="status-expired">● 已过期</span>';
|
|
row.appendChild(statusCell);
|
|
|
|
// Action buttons
|
|
const actionCell = document.createElement('td');
|
|
if (isActive) {
|
|
const evictBtn = document.createElement('button');
|
|
evictBtn.textContent = '剔除';
|
|
evictBtn.style.cssText = 'background-color: #f39c12; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; margin-right: 5px;';
|
|
evictBtn.onclick = () => evictClient(lease.MAC, lease.IP);
|
|
actionCell.appendChild(evictBtn);
|
|
}
|
|
row.appendChild(actionCell);
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
|
|
updatePoolStats(activeCount, data.leases.length);
|
|
} catch (error) {
|
|
console.error('Failed to load clients:', error);
|
|
}
|
|
}
|
|
|
|
// Update Pool Stats
|
|
async function updatePoolStats(activeCount, totalCount) {
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const cfg = data.config;
|
|
|
|
if (cfg) {
|
|
const startIP = cfg.ip_pool_start || '192.168.1.100';
|
|
const endIP = cfg.ip_pool_end || '192.168.1.200';
|
|
|
|
document.getElementById('poolRange').textContent = `${startIP} - ${endIP}`;
|
|
document.getElementById('poolUsed').textContent = activeCount;
|
|
|
|
// Calculate pool size
|
|
const startBytes = ipToBytes(startIP);
|
|
const endBytes = ipToBytes(endIP);
|
|
const poolSize = bytesToIP(endBytes) - bytesToIP(startBytes) + 1;
|
|
const available = poolSize - activeCount;
|
|
const usage = poolSize > 0 ? Math.round((activeCount / poolSize) * 100) : 0;
|
|
|
|
document.getElementById('poolAvailable').textContent = available;
|
|
document.getElementById('poolUsage').textContent = usage + '%';
|
|
|
|
const barFill = document.getElementById('poolBarFill');
|
|
barFill.style.width = usage + '%';
|
|
barFill.textContent = usage + '%';
|
|
|
|
// Color based on usage
|
|
if (usage > 90) {
|
|
barFill.style.background = 'linear-gradient(90deg, #e74c3c, #c0392b)';
|
|
} else if (usage > 70) {
|
|
barFill.style.background = 'linear-gradient(90deg, #f39c12, #e67e22)';
|
|
} else {
|
|
barFill.style.background = 'linear-gradient(90deg, #27ae60, #2ecc71)';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update pool stats:', error);
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
function ipToBytes(ip) {
|
|
return ip.split('.').map(Number);
|
|
}
|
|
|
|
function bytesToIP(bytes) {
|
|
return (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
|
|
}
|
|
|
|
function formatTimeRemaining(seconds) {
|
|
if (seconds <= 0) return '已过期';
|
|
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (days > 0) return `${days}天${hours}小时`;
|
|
if (hours > 0) return `${hours}小时${minutes}分钟`;
|
|
return `${minutes}分钟`;
|
|
}
|
|
|
|
// Load DHCP Config
|
|
async function loadDHCPConfig() {
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const cfg = data.config;
|
|
|
|
if (cfg) {
|
|
document.getElementById('dhcpEnabled').checked = cfg.enabled;
|
|
document.getElementById('dhcpInterface').value = cfg.interface || '';
|
|
document.getElementById('dhcpNetwork').value = cfg.network || '';
|
|
document.getElementById('dhcpNetmask').value = cfg.netmask || '';
|
|
document.getElementById('dhcpGateway').value = cfg.gateway || '';
|
|
document.getElementById('dhcpDomain').value = cfg.domain_name || '';
|
|
document.getElementById('dhcpPoolStart').value = cfg.ip_pool_start || '';
|
|
document.getElementById('dhcpPoolEnd').value = cfg.ip_pool_end || '';
|
|
document.getElementById('dhcpLeaseTime').value = cfg.lease_time || 86400;
|
|
document.getElementById('dhcpDnsServers').value = (cfg.dns_servers || []).join(',');
|
|
document.getElementById('dhcpExcludedIps').value = (cfg.excluded_ips || []).join(',');
|
|
}
|
|
|
|
loadBindings();
|
|
} catch (error) {
|
|
console.error('Failed to load DHCP config:', error);
|
|
}
|
|
}
|
|
|
|
// Save DHCP Basic Config
|
|
document.getElementById('dhcpBasicForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const config = {
|
|
enabled: document.getElementById('dhcpEnabled').checked,
|
|
interface: document.getElementById('dhcpInterface').value,
|
|
network: document.getElementById('dhcpNetwork').value,
|
|
netmask: document.getElementById('dhcpNetmask').value,
|
|
gateway: document.getElementById('dhcpGateway').value,
|
|
domain_name: document.getElementById('dhcpDomain').value
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('基础配置已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Save DHCP Pool Config
|
|
document.getElementById('dhcpPoolForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const config = {
|
|
ip_pool_start: document.getElementById('dhcpPoolStart').value,
|
|
ip_pool_end: document.getElementById('dhcpPoolEnd').value,
|
|
lease_time: parseInt(document.getElementById('dhcpLeaseTime').value)
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('地址池配置已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Save DHCP DNS Config
|
|
document.getElementById('dhcpDnsForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const dnsServers = document.getElementById('dhcpDnsServers').value.split(',').map(s => s.trim()).filter(s => s);
|
|
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ dns_servers: dnsServers })
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('DNS 配置已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Save DHCP Excluded IPs
|
|
document.getElementById('dhcpExcludedForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const excludedIps = document.getElementById('dhcpExcludedIps').value.split(',').map(s => s.trim()).filter(s => s);
|
|
|
|
try {
|
|
const response = await fetch('/api/dhcp/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ excluded_ips: excludedIps })
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('排除列表已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Load Bindings
|
|
async function loadBindings() {
|
|
try {
|
|
const response = await fetch('/api/dhcp/bindings', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const tbody = document.querySelector('#bindingsTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.bindings.forEach(binding => {
|
|
const row = tbody.insertRow();
|
|
row.insertCell(0).textContent = binding.MAC;
|
|
row.insertCell(1).textContent = binding.IP;
|
|
row.insertCell(2).textContent = binding.Hostname || '-';
|
|
row.insertCell(3).textContent = binding.Description || '-';
|
|
|
|
const actionCell = row.insertCell(4);
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.textContent = '删除';
|
|
deleteBtn.onclick = () => deleteBinding(binding.ID);
|
|
actionCell.appendChild(deleteBtn);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to load bindings:', error);
|
|
}
|
|
}
|
|
|
|
function showAddBindingForm() {
|
|
// TODO: Implement add binding form
|
|
alert('添加绑定功能开发中...');
|
|
}
|
|
|
|
async function deleteBinding(id) {
|
|
if (!confirm('确定要删除这个绑定吗?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/dhcp/bindings/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadBindings();
|
|
} else {
|
|
alert('删除失败');
|
|
}
|
|
} catch (error) {
|
|
alert('删除失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
async function evictClient(mac, ip) {
|
|
if (!confirm(`确定要剔除主机 ${mac} (${ip}) 吗?\n\n主机将被迫释放该 IP 并重新获取。`)) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/dhcp/leases/evict`, {
|
|
method: 'POST',
|
|
headers: { 'X-Session-ID': sessionId, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ mac, ip })
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('主机已剔除,将重新获取 IP');
|
|
loadClients();
|
|
} else {
|
|
const data = await response.json();
|
|
alert('剔除失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
alert('剔除失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
// Load DNS Config
|
|
async function loadDNSConfig() {
|
|
try {
|
|
const response = await fetch('/api/dns/config', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const cfg = data.config;
|
|
|
|
if (cfg) {
|
|
document.getElementById('dnsEnabled').checked = cfg.enabled;
|
|
document.getElementById('dnsListenAddr').value = cfg.listen_addr || '0.0.0.0';
|
|
document.getElementById('dnsListenPort').value = cfg.listen_port || 53;
|
|
document.getElementById('dnsRecursion').checked = cfg.recursion !== false;
|
|
document.getElementById('dnsUpstream').value = (cfg.upstream || []).join(',');
|
|
}
|
|
|
|
loadDNSRecords();
|
|
loadZones();
|
|
loadLogs();
|
|
} catch (error) {
|
|
console.error('Failed to load DNS config:', error);
|
|
}
|
|
}
|
|
|
|
// Save DNS Basic Config
|
|
document.getElementById('dnsBasicForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const config = {
|
|
enabled: document.getElementById('dnsEnabled').checked,
|
|
listen_addr: document.getElementById('dnsListenAddr').value,
|
|
listen_port: parseInt(document.getElementById('dnsListenPort').value),
|
|
recursion: document.getElementById('dnsRecursion').checked
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/dns/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('DNS 基础配置已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Save DNS Upstream
|
|
document.getElementById('dnsUpstreamForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const upstream = document.getElementById('dnsUpstream').value.split(',').map(s => s.trim()).filter(s => s);
|
|
|
|
try {
|
|
const response = await fetch('/api/dns/config', {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-Session-ID': sessionId,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ upstream })
|
|
});
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
const text = await response.text();
|
|
console.error('Non-JSON response:', text);
|
|
alert('服务器返回了非 JSON 格式响应,请查看控制台');
|
|
return;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('上游 DNS 已保存');
|
|
} else {
|
|
alert('保存失败:' + (data.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
alert('保存失败:' + error.message);
|
|
}
|
|
});
|
|
|
|
// Load DNS Records
|
|
async function loadDNSRecords() {
|
|
try {
|
|
const response = await fetch('/api/dns/records', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const tbody = document.querySelector('#recordsTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.records.forEach(record => {
|
|
const row = tbody.insertRow();
|
|
row.insertCell(0).textContent = record.Name;
|
|
row.insertCell(1).textContent = record.Type;
|
|
row.insertCell(2).textContent = record.Value;
|
|
row.insertCell(3).textContent = record.TTL;
|
|
|
|
const actionCell = row.insertCell(4);
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.textContent = '删除';
|
|
deleteBtn.onclick = () => deleteRecord(record.ID);
|
|
actionCell.appendChild(deleteBtn);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to load records:', error);
|
|
}
|
|
}
|
|
|
|
function showAddRecordForm() {
|
|
// TODO: Implement add record form
|
|
alert('添加记录功能开发中...');
|
|
}
|
|
|
|
async function deleteRecord(id) {
|
|
if (!confirm('确定要删除这条记录吗?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/dns/records/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadDNSRecords();
|
|
} else {
|
|
alert('删除失败');
|
|
}
|
|
} catch (error) {
|
|
alert('删除失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
// Load Zones
|
|
async function loadZones() {
|
|
try {
|
|
const response = await fetch('/api/dns/zones', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const tbody = document.querySelector('#zonesTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.zones.forEach(zone => {
|
|
const row = tbody.insertRow();
|
|
row.insertCell(0).textContent = zone.name;
|
|
row.insertCell(1).textContent = zone.type;
|
|
row.insertCell(2).textContent = zone.record_count;
|
|
|
|
const actionCell = row.insertCell(3);
|
|
const deleteBtn = document.createElement('button');
|
|
deleteBtn.textContent = '删除';
|
|
deleteBtn.onclick = () => deleteZone(zone.ID);
|
|
actionCell.appendChild(deleteBtn);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to load zones:', error);
|
|
}
|
|
}
|
|
|
|
function showAddZoneForm() {
|
|
// TODO: Implement add zone form
|
|
alert('添加区域功能开发中...');
|
|
}
|
|
|
|
async function deleteZone(id) {
|
|
if (!confirm('确定要删除这个区域吗?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/dns/zones/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadZones();
|
|
} else {
|
|
alert('删除失败');
|
|
}
|
|
} catch (error) {
|
|
alert('删除失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
// Load Logs
|
|
async function loadLogs() {
|
|
try {
|
|
const response = await fetch('/api/dns/logs', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
const tbody = document.querySelector('#logsTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.logs.forEach(log => {
|
|
const row = tbody.insertRow();
|
|
row.insertCell(0).textContent = new Date(log.Timestamp * 1000).toLocaleString();
|
|
row.insertCell(1).textContent = log.ClientIP;
|
|
row.insertCell(2).textContent = log.QueryName;
|
|
row.insertCell(3).textContent = log.QueryType;
|
|
row.insertCell(4).textContent = log.Response || '-';
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to load logs:', error);
|
|
}
|
|
}
|
|
|
|
// Load System Info
|
|
async function loadSystemInfo() {
|
|
try {
|
|
const response = await fetch('/api/config', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const data = await response.json();
|
|
// TODO: Update system info display
|
|
} catch (error) {
|
|
console.error('Failed to load system info:', error);
|
|
}
|
|
}
|
|
|
|
// Export Config
|
|
async function exportConfig() {
|
|
try {
|
|
const response = await fetch('/api/config/export', {
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'dhcp-dns-config.json';
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
} catch (error) {
|
|
alert('导出失败:' + error.message);
|
|
}
|
|
}
|
|
|
|
// Import Config
|
|
async function importConfig() {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = '.json';
|
|
|
|
input.onchange = async (e) => {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append('config', file);
|
|
|
|
try {
|
|
const response = await fetch('/api/config/import', {
|
|
method: 'POST',
|
|
headers: { 'X-Session-ID': sessionId },
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('配置已导入');
|
|
} else {
|
|
const data = await response.json();
|
|
alert('导入失败:' + data.error);
|
|
}
|
|
} catch (error) {
|
|
alert('导入失败:' + error.message);
|
|
}
|
|
};
|
|
|
|
input.click();
|
|
}
|
|
|
|
// Restart Service
|
|
async function restartService() {
|
|
if (!confirm('确定要重启服务吗?服务将短暂中断。')) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/service/restart', {
|
|
method: 'POST',
|
|
headers: { 'X-Session-ID': sessionId }
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('服务重启请求已发送');
|
|
} else {
|
|
const data = await response.json();
|
|
alert('重启失败:' + data.error);
|
|
}
|
|
} catch (error) {
|
|
alert('重启失败:' + error.message);
|
|
}
|
|
}
|