Fix DHCP client unable to get IP and config not persisting
- Fixed verifyAssignment being too strict for new clients - Fixed parseRequestedIP string conversion bug - Fixed response sent to 0.0.0.0 instead of broadcast address - Added SO_BROADCAST support for UDP socket - Fixed session persistence after page refresh (localStorage) - Added in-memory session store for auth middleware - Added config reloader so DHCP server picks up web UI changes dynamically
This commit is contained in:
@@ -0,0 +1,804 @@
|
||||
let sessionId = localStorage.getItem('session_id') || null;
|
||||
let autoRefreshInterval = null;
|
||||
let autoRefreshEnabled = false;
|
||||
|
||||
// Restore session on page load
|
||||
if (sessionId) {
|
||||
document.getElementById('loginSection').style.display = 'none';
|
||||
document.getElementById('dashboard').style.display = 'block';
|
||||
document.getElementById('logoutBtn').style.display = 'block';
|
||||
loadDashboard();
|
||||
loadDHCPConfig();
|
||||
loadDNSConfig();
|
||||
}
|
||||
|
||||
// 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', () => {
|
||||
sessionId = null;
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user