diff --git a/static/index.html b/static/index.html index 351662e..fd2b551 100644 --- a/static/index.html +++ b/static/index.html @@ -2,763 +2,332 @@ - - - DNS服务器管理控制台 - - - - - -
-
-
- -
- - - - -
- -
-
-

服务器状态

-
- - 加载中... -
-
- -
-
- -
--
-
屏蔽请求
-
- -
-
-
- -
--
-
允许请求
-
-
- -
--
-
错误请求
-
-
- -
--
-
总请求数
-
- -
-
-
- -
--
-
屏蔽规则数
-
- -
-
-
- -
--
-
Hosts条目
-
- -
-
-
- - - -
-
-

最常屏蔽的域名(每5秒刷新)

-
- - - - - - - - - - -
域名屏蔽次数
加载中...
-
-
-
-

最常解析的域名(每5秒刷新)

-
- - - - - - - - - - -
域名解析次数
加载中...
-
-
-
+ + + + + +
+

DNS服务器 v1.0.0

+

正常运行中

- - -
-
-

屏蔽规则管理

- +

仪表盘

-
-
- - +
+ +
+ 用户头像 + +
+
+ + + +
+ +
+ +
+ +
+
+

查询总量

+
+ +
+
+
+

0

+ + + 0% + +
+
+ + +
+
+

屏蔽数量

+
+ +
+
+
+

0

+ + + 0% + +
+
+ + +
+
+

正常解析

+
+ +
+
+
+

0

+ + + 0% + +
+
+ + +
+
+

错误数量

+
+ +
+
+
+

0

+ + + 0% + +
+
-
- + +
+ +
+
+

查询趋势

+
+ + + +
+
+
+ +
+
+ + +
+

解析与屏蔽比例

+
+ +
+
-
-
-
- + +
+ +
+

最常屏蔽域名

+
+
- - - - + + + - - + + + +
序号规则内容操作
域名屏蔽次数
加载中...
加载中...
- -
-
- 共0条规则 -
-
-
- - -
- + +
+

最近屏蔽域名

+
+ + + + + + + + + + + + +
域名时间
加载中...
-
- - -
-
-

Hosts管理

-
- -
-
-
-
- - -
-
- - -
-
- - -
-
-
- -
- -
- -
-
- - - - - - - - - - - -
IP地址域名操作
加载中...
-
-
-
-
- - -
-
-

远程黑名单管理

- -
- -
-
-
-
- - -
-
- - -
-
- - -
-
-
- -
-
- - - - - - - - - - - - - - -
名称URL状态规则数最后更新操作
加载中...
-
-
-
-
- - -
-
-

DNS查询测试

-
- -
-
- - - -
-
-
- - - -
-
-

系统配置

-
-
-
-

屏蔽设置

- -
- - -
- -
- - -
- -
- - -
+
-
- - - - - - - - - - - - - - + + + + + \ No newline at end of file diff --git a/static/js/api.js b/static/js/api.js new file mode 100644 index 0000000..96dcbae --- /dev/null +++ b/static/js/api.js @@ -0,0 +1,100 @@ +// API模块 - 统一管理所有API调用 + +// API路径定义 +const API_BASE_URL = '/api'; + +// API请求封装 +async function apiRequest(endpoint, method = 'GET', data = null) { + const url = `${API_BASE_URL}${endpoint}`; + const options = { + method, + headers: { + 'Content-Type': 'application/json', + }, + }; + + if (data) { + options.body = JSON.stringify(data); + } + + try { + const response = await fetch(url, options); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || `请求失败: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('API请求错误:', error); + throw error; + } +} + +// API方法集合 +// API方法集合 +const api = { + // 获取统计信息 + getStats: () => apiRequest('/stats'), + + // 获取系统状态 + getStatus: () => apiRequest('/status'), + + // 获取Top屏蔽域名 + getTopBlockedDomains: () => apiRequest('/top-blocked'), + + // 获取Top解析域名 + getTopResolvedDomains: () => apiRequest('/top-resolved'), + + // 获取最近屏蔽域名 + getRecentBlockedDomains: () => apiRequest('/recent-blocked'), + + // 获取小时统计 + getHourlyStats: () => apiRequest('/hourly-stats'), + + // 获取屏蔽规则 + getShieldRules: () => apiRequest('/shield'), + + // 添加屏蔽规则 + addShieldRule: (rule) => apiRequest('/shield', 'POST', { rule }), + + // 删除屏蔽规则 + deleteShieldRule: (rule) => apiRequest('/shield', 'DELETE', { rule }), + + // 更新远程规则 + updateRemoteRules: () => apiRequest('/shield', 'PUT', { action: 'update' }), + + // 获取黑名单列表 + getBlacklists: () => apiRequest('/shield/blacklists'), + + // 添加黑名单 + addBlacklist: (url) => apiRequest('/shield/blacklists', 'POST', { url }), + + // 删除黑名单 + deleteBlacklist: (url) => apiRequest('/shield/blacklists', 'DELETE', { url }), + + // 获取Hosts内容 + getHosts: () => apiRequest('/shield/hosts'), + + // 保存Hosts内容 + saveHosts: (content) => apiRequest('/shield/hosts', 'POST', { content }), + + // 刷新Hosts + refreshHosts: () => apiRequest('/shield/hosts', 'PUT', { action: 'refresh' }), + + // 执行DNS查询 + queryDNS: (domain, recordType = 'A') => apiRequest(`/query?domain=${encodeURIComponent(domain)}&type=${recordType}`), + + // 获取系统配置 + getConfig: () => apiRequest('/config'), + + // 保存系统配置 + saveConfig: (config) => apiRequest('/config', 'POST', config), + + // 重启服务 + restartService: () => apiRequest('/config/restart', 'POST'), +}; + +// 导出API工具 +window.api = api; \ No newline at end of file diff --git a/static/js/config.js b/static/js/config.js new file mode 100644 index 0000000..a65cee6 --- /dev/null +++ b/static/js/config.js @@ -0,0 +1,154 @@ +// 配置管理页面功能实现 + +// 初始化配置管理页面 +function initConfigPage() { + loadConfig(); + setupConfigEventListeners(); +} + +// 加载系统配置 +async function loadConfig() { + try { + const config = await api.getConfig(); + populateConfigForm(config); + } catch (error) { + showErrorMessage('加载配置失败: ' + error.message); + } +} + +// 填充配置表单 +function populateConfigForm(config) { + // DNS配置 + document.getElementById('dns-port')?.value = config.DNSServer.Port || 53; + document.getElementById('dns-upstream-servers')?.value = (config.DNSServer.UpstreamServers || []).join(', '); + document.getElementById('dns-timeout')?.value = config.DNSServer.Timeout || 5; + document.getElementById('dns-stats-file')?.value = config.DNSServer.StatsFile || './stats.json'; + document.getElementById('dns-save-interval')?.value = config.DNSServer.SaveInterval || 300; + + // HTTP配置 + document.getElementById('http-port')?.value = config.HTTPServer.Port || 8080; + document.getElementById('http-host')?.value = config.HTTPServer.Host || '0.0.0.0'; + document.getElementById('http-api-enabled')?.checked = config.HTTPServer.APIEnabled !== false; + + // 屏蔽配置 + document.getElementById('shield-local-rules-file')?.value = config.Shield.LocalRulesFile || './rules.txt'; + document.getElementById('shield-remote-rules-urls')?.value = (config.Shield.RemoteRulesURLs || []).join('\n'); + document.getElementById('shield-update-interval')?.value = config.Shield.UpdateInterval || 3600; + document.getElementById('shield-hosts-file')?.value = config.Shield.HostsFile || '/etc/hosts'; + document.getElementById('shield-block-method')?.value = config.Shield.BlockMethod || '0.0.0.0'; +} + +// 保存配置 +async function handleSaveConfig() { + const formData = collectFormData(); + + try { + await api.saveConfig(formData); + showSuccessMessage('配置保存成功'); + } catch (error) { + showErrorMessage('保存配置失败: ' + error.message); + } +} + +// 重启服务 +async function handleRestartService() { + if (confirm('确定要重启DNS服务吗?重启期间服务可能会短暂不可用。')) { + try { + await api.restartService(); + showSuccessMessage('服务重启成功'); + } catch (error) { + showErrorMessage('重启服务失败: ' + error.message); + } + } +} + +// 收集表单数据 +function collectFormData() { + return { + DNSServer: { + Port: parseInt(document.getElementById('dns-port')?.value) || 53, + UpstreamServers: document.getElementById('dns-upstream-servers')?.value.split(',').map(s => s.trim()).filter(Boolean) || [], + Timeout: parseInt(document.getElementById('dns-timeout')?.value) || 5, + StatsFile: document.getElementById('dns-stats-file')?.value || './stats.json', + SaveInterval: parseInt(document.getElementById('dns-save-interval')?.value) || 300 + }, + HTTPServer: { + Port: parseInt(document.getElementById('http-port')?.value) || 8080, + Host: document.getElementById('http-host')?.value || '0.0.0.0', + APIEnabled: document.getElementById('http-api-enabled')?.checked !== false + }, + Shield: { + LocalRulesFile: document.getElementById('shield-local-rules-file')?.value || './rules.txt', + RemoteRulesURLs: document.getElementById('shield-remote-rules-urls')?.value.split('\n').map(s => s.trim()).filter(Boolean) || [], + UpdateInterval: parseInt(document.getElementById('shield-update-interval')?.value) || 3600, + HostsFile: document.getElementById('shield-hosts-file')?.value || '/etc/hosts', + BlockMethod: document.getElementById('shield-block-method')?.value || '0.0.0.0' + } + }; +} + +// 设置事件监听器 +function setupConfigEventListeners() { + // 保存配置按钮 + document.getElementById('save-config-btn')?.addEventListener('click', handleSaveConfig); + + // 重启服务按钮 + document.getElementById('restart-service-btn')?.addEventListener('click', handleRestartService); +} + +// 显示成功消息 +function showSuccessMessage(message) { + showNotification(message, 'success'); +} + +// 显示错误消息 +function showErrorMessage(message) { + showNotification(message, 'error'); +} + +// 显示通知 +function showNotification(message, type = 'info') { + // 移除现有通知 + const existingNotification = document.querySelector('.notification'); + if (existingNotification) { + existingNotification.remove(); + } + + // 创建新通知 + const notification = document.createElement('div'); + notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; + + // 设置通知样式 + if (type === 'success') { + notification.classList.add('bg-green-500', 'text-white'); + } else if (type === 'error') { + notification.classList.add('bg-red-500', 'text-white'); + } else { + notification.classList.add('bg-blue-500', 'text-white'); + } + + notification.textContent = message; + document.body.appendChild(notification); + + // 显示通知 + setTimeout(() => { + notification.classList.remove('opacity-0'); + notification.classList.add('opacity-100'); + }, 10); + + // 3秒后隐藏通知 + setTimeout(() => { + notification.classList.remove('opacity-100'); + notification.classList.add('opacity-0'); + setTimeout(() => { + notification.remove(); + }, 300); + }, 3000); +} + +// 页面加载完成后初始化 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initConfigPage); +} else { + initConfigPage(); +} \ No newline at end of file diff --git a/static/js/dashboard.js b/static/js/dashboard.js new file mode 100644 index 0000000..b056494 --- /dev/null +++ b/static/js/dashboard.js @@ -0,0 +1,270 @@ +// dashboard.js - 仪表盘页面功能 + +// 加载仪表盘数据 +async function loadDashboardData() { + try { + // 并行加载所有需要的数据 + const [statsData, topBlockedData, recentBlockedData, hourlyStatsData] = await Promise.all([ + api.getStats(), + api.getTopBlockedDomains(), + api.getRecentBlockedDomains(), + api.getHourlyStats() + ]); + + // 更新统计卡片 + updateStatsCards(statsData); + + // 更新表格数据 + updateTopBlockedTable(topBlockedData); + updateRecentBlockedTable(recentBlockedData); + + // 更新图表 + updateQueryTrendChart(hourlyStatsData); + updateRatioChart(statsData); + + } catch (error) { + console.error('加载仪表盘数据失败:', error); + showErrorMessage('数据加载失败,请刷新页面重试'); + } +} + +// 更新统计卡片 +function updateStatsCards(data) { + const dnsStats = data.dns; + + // 更新总查询数 + document.getElementById('total-queries').textContent = formatNumber(dnsStats.Queries); + + // 更新屏蔽数量 + document.getElementById('blocked-queries').textContent = formatNumber(dnsStats.Blocked); + + // 更新正常解析数量 + document.getElementById('allowed-queries').textContent = formatNumber(dnsStats.Allowed); + + // 更新错误数量 + document.getElementById('error-queries').textContent = formatNumber(dnsStats.Errors); + + // 计算百分比(简化计算,实际可能需要与历史数据比较) + if (dnsStats.Queries > 0) { + const blockedPercent = Math.round((dnsStats.Blocked / dnsStats.Queries) * 100); + const allowedPercent = Math.round((dnsStats.Allowed / dnsStats.Queries) * 100); + const errorPercent = Math.round((dnsStats.Errors / dnsStats.Queries) * 100); + + document.getElementById('blocked-percent').textContent = `${blockedPercent}%`; + document.getElementById('allowed-percent').textContent = `${allowedPercent}%`; + document.getElementById('error-percent').textContent = `${errorPercent}%`; + document.getElementById('queries-percent').textContent = '100%'; + } +} + +// 更新最常屏蔽域名表格 +function updateTopBlockedTable(data) { + const tableBody = document.getElementById('top-blocked-table'); + tableBody.innerHTML = ''; + + if (data.length === 0) { + const row = document.createElement('tr'); + row.innerHTML = `暂无数据`; + tableBody.appendChild(row); + return; + } + + data.forEach(item => { + const row = document.createElement('tr'); + row.className = 'border-b border-gray-100 hover:bg-gray-50'; + row.innerHTML = ` + ${item.domain} + ${formatNumber(item.count)} + `; + tableBody.appendChild(row); + }); +} + +// 更新最近屏蔽域名表格 +function updateRecentBlockedTable(data) { + const tableBody = document.getElementById('recent-blocked-table'); + tableBody.innerHTML = ''; + + if (data.length === 0) { + const row = document.createElement('tr'); + row.innerHTML = `暂无数据`; + tableBody.appendChild(row); + return; + } + + data.forEach(item => { + const row = document.createElement('tr'); + row.className = 'border-b border-gray-100 hover:bg-gray-50'; + row.innerHTML = ` + ${item.domain} + ${item.time} + `; + tableBody.appendChild(row); + }); +} + +// 更新查询趋势图表 +function updateQueryTrendChart(data) { + const ctx = document.getElementById('query-trend-chart').getContext('2d'); + + // 创建图表 + new Chart(ctx, { + type: 'line', + data: { + labels: data.labels, + datasets: [{ + label: '查询数量', + data: data.data, + borderColor: '#165DFF', + backgroundColor: 'rgba(22, 93, 255, 0.1)', + borderWidth: 2, + tension: 0.3, + fill: true, + pointBackgroundColor: '#FFFFFF', + pointBorderColor: '#165DFF', + pointBorderWidth: 2, + pointRadius: 4, + pointHoverRadius: 6 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 12, + cornerRadius: 8, + titleFont: { + size: 14, + weight: 'bold' + }, + bodyFont: { + size: 13 + } + } + }, + scales: { + x: { + grid: { + display: false + }, + ticks: { + font: { + size: 12 + } + } + }, + y: { + beginAtZero: true, + grid: { + color: 'rgba(0, 0, 0, 0.05)' + }, + ticks: { + font: { + size: 12 + }, + callback: function(value) { + return formatNumber(value); + } + } + } + }, + interaction: { + intersect: false, + mode: 'index' + } + } + }); +} + +// 更新比例图表 +function updateRatioChart(data) { + const dnsStats = data.dns; + const ctx = document.getElementById('ratio-chart').getContext('2d'); + + // 准备数据 + const chartData = [dnsStats.Allowed, dnsStats.Blocked, dnsStats.Errors]; + const chartColors = ['#00B42A', '#F53F3F', '#FF7D00']; + const chartLabels = ['正常解析', '屏蔽', '错误']; + + // 创建图表 + new Chart(ctx, { + type: 'doughnut', + data: { + labels: chartLabels, + datasets: [{ + data: chartData, + backgroundColor: chartColors, + borderWidth: 0, + hoverOffset: 10 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom',',\, + labels: { + padding: 20, + font: { + size: 13 + } + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 12, + cornerRadius: 8, + callbacks: { + label: function(context) { + const value = context.parsed; + const total = context.dataset.data.reduce((a, b) => a + b, 0); + const percentage = total > 0 ? Math.round((value / total) * 100) : 0; + return `${context.label}: ${formatNumber(value)} (${percentage}%)`; + } + } + } + }, + cutout: '70%' + } + }); +} + +// 格式化数字 +function formatNumber(num) { + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } else if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); +} + +// 显示错误消息 +function showErrorMessage(message) { + // 创建错误消息元素 + const errorElement = document.createElement('div'); + errorElement.className = 'fixed bottom-4 right-4 bg-danger text-white px-6 py-3 rounded-lg shadow-lg z-50 flex items-center'; + errorElement.innerHTML = ` + + ${message} + `; + + document.body.appendChild(errorElement); + + // 3秒后自动移除 + setTimeout(() => { + errorElement.classList.add('opacity-0', 'transition-opacity', 'duration-300'); + setTimeout(() => { + document.body.removeChild(errorElement); + }, 300); + }, 3000); +} + +// 定期刷新数据 +setInterval(loadDashboardData, 30000); // 每30秒刷新一次 \ No newline at end of file diff --git a/static/js/hosts.js b/static/js/hosts.js new file mode 100644 index 0000000..3a17f62 --- /dev/null +++ b/static/js/hosts.js @@ -0,0 +1,150 @@ +// Hosts管理页面功能实现 + +// 初始化Hosts管理页面 +function initHostsPage() { + loadHostsContent(); + setupHostsEventListeners(); +} + +// 加载Hosts内容 +async function loadHostsContent() { + try { + const hostsContent = await api.getHosts(); + document.getElementById('hosts-content').value = hostsContent; + } catch (error) { + showErrorMessage('加载Hosts文件失败: ' + error.message); + } +} + +// 保存Hosts内容 +async function handleSaveHosts() { + const hostsContent = document.getElementById('hosts-content').value; + + try { + await api.saveHosts(hostsContent); + showSuccessMessage('Hosts文件保存成功'); + } catch (error) { + showErrorMessage('保存Hosts文件失败: ' + error.message); + } +} + +// 刷新Hosts +async function handleRefreshHosts() { + try { + await api.refreshHosts(); + showSuccessMessage('Hosts刷新成功'); + loadHostsContent(); + } catch (error) { + showErrorMessage('刷新Hosts失败: ' + error.message); + } +} + +// 添加新的Hosts条目 +function handleAddHostsEntry() { + const ipInput = document.getElementById('hosts-ip'); + const domainInput = document.getElementById('hosts-domain'); + + const ip = ipInput.value.trim(); + const domain = domainInput.value.trim(); + + if (!ip || !domain) { + showErrorMessage('IP和域名不能为空'); + return; + } + + // 简单的IP验证 + const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/; + if (!ipRegex.test(ip)) { + showErrorMessage('请输入有效的IP地址'); + return; + } + + const hostsTextarea = document.getElementById('hosts-content'); + const newEntry = `\n${ip} ${domain}`; + hostsTextarea.value += newEntry; + + // 清空输入框 + ipInput.value = ''; + domainInput.value = ''; + + // 滚动到文本区域底部 + hostsTextarea.scrollTop = hostsTextarea.scrollHeight; + + showSuccessMessage('Hosts条目已添加到编辑器'); +} + +// 设置事件监听器 +function setupHostsEventListeners() { + // 保存按钮 + document.getElementById('save-hosts-btn')?.addEventListener('click', handleSaveHosts); + + // 刷新按钮 + document.getElementById('refresh-hosts-btn')?.addEventListener('click', handleRefreshHosts); + + // 添加Hosts条目按钮 + document.getElementById('add-hosts-entry-btn')?.addEventListener('click', handleAddHostsEntry); + + // 按回车键添加条目 + document.getElementById('hosts-domain')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handleAddHostsEntry(); + } + }); +} + +// 显示成功消息 +function showSuccessMessage(message) { + showNotification(message, 'success'); +} + +// 显示错误消息 +function showErrorMessage(message) { + showNotification(message, 'error'); +} + +// 显示通知 +function showNotification(message, type = 'info') { + // 移除现有通知 + const existingNotification = document.querySelector('.notification'); + if (existingNotification) { + existingNotification.remove(); + } + + // 创建新通知 + const notification = document.createElement('div'); + notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; + + // 设置通知样式 + if (type === 'success') { + notification.classList.add('bg-green-500', 'text-white'); + } else if (type === 'error') { + notification.classList.add('bg-red-500', 'text-white'); + } else { + notification.classList.add('bg-blue-500', 'text-white'); + } + + notification.textContent = message; + document.body.appendChild(notification); + + // 显示通知 + setTimeout(() => { + notification.classList.remove('opacity-0'); + notification.classList.add('opacity-100'); + }, 10); + + // 3秒后隐藏通知 + setTimeout(() => { + notification.classList.remove('opacity-100'); + notification.classList.add('opacity-0'); + setTimeout(() => { + notification.remove(); + }, 300); + }, 3000); +} + +// 页面加载完成后初始化 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initHostsPage); +} else { + initHostsPage(); +} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..7eb1fa9 --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,109 @@ +// main.js - 主脚本文件 + +// 页面导航功能 +function setupNavigation() { + // 侧边栏菜单项 + const menuItems = document.querySelectorAll('nav a'); + const contentSections = [ + document.getElementById('dashboard-content'), + document.getElementById('shield-content'), + document.getElementById('hosts-content'), + document.getElementById('blacklists-content'), + document.getElementById('query-content'), + document.getElementById('config-content') + ]; + const pageTitle = document.getElementById('page-title'); + + menuItems.forEach((item, index) => { + item.addEventListener('click', (e) => { + e.preventDefault(); + + // 更新活跃状态 + menuItems.forEach(menuItem => { + menuItem.classList.remove('sidebar-item-active'); + }); + item.classList.add('sidebar-item-active'); + + // 隐藏所有内容部分 + contentSections.forEach(section => { + section.classList.add('hidden'); + }); + + // 显示对应内容部分 + const target = item.getAttribute('href').substring(1); + const activeContent = document.getElementById(`${target}-content`); + if (activeContent) { + activeContent.classList.remove('hidden'); + } + + // 更新页面标题 + pageTitle.textContent = item.querySelector('span').textContent; + }); + }); + + // 移动端侧边栏切换 + const toggleSidebar = document.getElementById('toggle-sidebar'); + const sidebar = document.getElementById('sidebar'); + + if (toggleSidebar && sidebar) { + toggleSidebar.addEventListener('click', () => { + sidebar.classList.toggle('-translate-x-full'); + }); + } +} + +// 初始化函数 +function init() { + // 设置导航 + setupNavigation(); + + // 加载仪表盘数据 + if (typeof loadDashboardData === 'function') { + loadDashboardData(); + } + + // 定期更新系统状态 + setInterval(updateSystemStatus, 5000); +} + +// 更新系统状态 +function updateSystemStatus() { + fetch('/api/status') + .then(response => response.json()) + .then(data => { + const uptimeElement = document.getElementById('uptime'); + if (uptimeElement) { + uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`; + } + }) + .catch(error => { + console.error('更新系统状态失败:', error); + const uptimeElement = document.getElementById('uptime'); + if (uptimeElement) { + uptimeElement.textContent = '连接异常'; + uptimeElement.classList.add('text-danger'); + } + }); +} + +// 格式化运行时间 +function formatUptime(milliseconds) { + // 简化版的格式化,实际使用时需要根据API返回的数据格式调整 + const seconds = Math.floor(milliseconds / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + return `${days}天${hours % 24}小时`; + } else if (hours > 0) { + return `${hours}小时${minutes % 60}分钟`; + } else if (minutes > 0) { + return `${minutes}分钟${seconds % 60}秒`; + } else { + return `${seconds}秒`; + } +} + +// 页面加载完成后执行初始化 +window.addEventListener('DOMContentLoaded', init); \ No newline at end of file diff --git a/static/js/query.js b/static/js/query.js new file mode 100644 index 0000000..9a0436a --- /dev/null +++ b/static/js/query.js @@ -0,0 +1,157 @@ +// DNS查询工具页面功能实现 + +// 初始化查询工具页面 +function initQueryPage() { + setupQueryEventListeners(); +} + +// 执行DNS查询 +async function handleDNSQuery() { + const domainInput = document.getElementById('query-domain'); + const recordTypeSelect = document.getElementById('query-record-type'); + const resultDiv = document.getElementById('query-result'); + + const domain = domainInput.value.trim(); + const recordType = recordTypeSelect.value; + + if (!domain) { + showErrorMessage('请输入域名'); + return; + } + + // 清空之前的结果 + resultDiv.innerHTML = '
查询中...
'; + + try { + const result = await api.queryDNS(domain, recordType); + displayQueryResult(result, domain, recordType); + } catch (error) { + resultDiv.innerHTML = `
查询失败: ${error.message}
`; + } +} + +// 显示查询结果 +function displayQueryResult(result, domain, recordType) { + const resultDiv = document.getElementById('query-result'); + + if (!result || result.length === 0) { + resultDiv.innerHTML = `
未找到 ${domain} 的 ${recordType} 记录
`; + return; + } + + // 创建结果表格 + let html = ` +
+

查询结果: ${domain} (${recordType})

+
+ + + + + + + + + + `; + + // 添加查询结果 + result.forEach(record => { + const type = record.Type || recordType; + let value = record.Value; + + // 格式化不同类型的记录值 + if (type === 'MX' && record.Preference) { + value = `${record.Preference} ${value}`; + } else if (type === 'SRV') { + value = `${record.Priority} ${record.Weight} ${record.Port} ${value}`; + } + + html += ` + + + + + + `; + }); + + html += ` + +
类型TTL
${type}${value}${record.TTL || '-'}
+
+
+ `; + + resultDiv.innerHTML = html; +} + +// 设置事件监听器 +function setupQueryEventListeners() { + // 查询按钮 + document.getElementById('query-btn')?.addEventListener('click', handleDNSQuery); + + // 按回车键查询 + document.getElementById('query-domain')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handleDNSQuery(); + } + }); +} + +// 显示成功消息 +function showSuccessMessage(message) { + showNotification(message, 'success'); +} + +// 显示错误消息 +function showErrorMessage(message) { + showNotification(message, 'error'); +} + +// 显示通知 +function showNotification(message, type = 'info') { + // 移除现有通知 + const existingNotification = document.querySelector('.notification'); + if (existingNotification) { + existingNotification.remove(); + } + + // 创建新通知 + const notification = document.createElement('div'); + notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; + + // 设置通知样式 + if (type === 'success') { + notification.classList.add('bg-green-500', 'text-white'); + } else if (type === 'error') { + notification.classList.add('bg-red-500', 'text-white'); + } else { + notification.classList.add('bg-blue-500', 'text-white'); + } + + notification.textContent = message; + document.body.appendChild(notification); + + // 显示通知 + setTimeout(() => { + notification.classList.remove('opacity-0'); + notification.classList.add('opacity-100'); + }, 10); + + // 3秒后隐藏通知 + setTimeout(() => { + notification.classList.remove('opacity-100'); + notification.classList.add('opacity-0'); + setTimeout(() => { + notification.remove(); + }, 300); + }, 3000); +} + +// 页面加载完成后初始化 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initQueryPage); +} else { + initQueryPage(); +} \ No newline at end of file diff --git a/static/js/shield.js b/static/js/shield.js new file mode 100644 index 0000000..e914431 --- /dev/null +++ b/static/js/shield.js @@ -0,0 +1,167 @@ +// 屏蔽管理页面功能实现 + +// 初始化屏蔽管理页面 +function initShieldPage() { + loadShieldRules(); + setupShieldEventListeners(); +} + +// 加载屏蔽规则 +async function loadShieldRules() { + try { + const rules = await api.getShieldRules(); + updateShieldRulesTable(rules); + } catch (error) { + showErrorMessage('加载屏蔽规则失败: ' + error.message); + } +} + +// 更新屏蔽规则表格 +function updateShieldRulesTable(rules) { + const tbody = document.getElementById('shield-rules-tbody'); + tbody.innerHTML = ''; + + if (!rules || rules.length === 0) { + tbody.innerHTML = '暂无屏蔽规则'; + return; + } + + rules.forEach((rule, index) => { + const tr = document.createElement('tr'); + tr.className = 'border-b border-gray-200 hover:bg-gray-50'; + tr.innerHTML = ` + ${index + 1} + ${rule} + + + + `; + tbody.appendChild(tr); + }); + + // 添加删除按钮事件监听器 + document.querySelectorAll('.delete-rule-btn').forEach(btn => { + btn.addEventListener('click', handleDeleteRule); + }); +} + +// 处理删除规则 +async function handleDeleteRule(e) { + const rule = e.currentTarget.getAttribute('data-rule'); + + if (confirm(`确定要删除规则: ${rule} 吗?`)) { + try { + await api.deleteShieldRule(rule); + showSuccessMessage('规则删除成功'); + loadShieldRules(); + } catch (error) { + showErrorMessage('删除规则失败: ' + error.message); + } + } +} + +// 添加新规则 +async function handleAddRule() { + const ruleInput = document.getElementById('new-rule-input'); + const rule = ruleInput.value.trim(); + + if (!rule) { + showErrorMessage('规则不能为空'); + return; + } + + try { + await api.addShieldRule(rule); + showSuccessMessage('规则添加成功'); + loadShieldRules(); + ruleInput.value = ''; + } catch (error) { + showErrorMessage('添加规则失败: ' + error.message); + } +} + +// 更新远程规则 +async function handleUpdateRemoteRules() { + try { + await api.updateRemoteRules(); + showSuccessMessage('远程规则更新成功'); + loadShieldRules(); + } catch (error) { + showErrorMessage('远程规则更新失败: ' + error.message); + } +} + +// 设置事件监听器 +function setupShieldEventListeners() { + // 添加规则按钮 + document.getElementById('add-rule-btn')?.addEventListener('click', handleAddRule); + + // 按回车键添加规则 + document.getElementById('new-rule-input')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handleAddRule(); + } + }); + + // 更新远程规则按钮 + document.getElementById('update-remote-rules-btn')?.addEventListener('click', handleUpdateRemoteRules); +} + +// 显示成功消息 +function showSuccessMessage(message) { + showNotification(message, 'success'); +} + +// 显示错误消息 +function showErrorMessage(message) { + showNotification(message, 'error'); +} + +// 显示通知 +function showNotification(message, type = 'info') { + // 移除现有通知 + const existingNotification = document.querySelector('.notification'); + if (existingNotification) { + existingNotification.remove(); + } + + // 创建新通知 + const notification = document.createElement('div'); + notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; + + // 设置通知样式 + if (type === 'success') { + notification.classList.add('bg-green-500', 'text-white'); + } else if (type === 'error') { + notification.classList.add('bg-red-500', 'text-white'); + } else { + notification.classList.add('bg-blue-500', 'text-white'); + } + + notification.textContent = message; + document.body.appendChild(notification); + + // 显示通知 + setTimeout(() => { + notification.classList.remove('opacity-0'); + notification.classList.add('opacity-100'); + }, 10); + + // 3秒后隐藏通知 + setTimeout(() => { + notification.classList.remove('opacity-100'); + notification.classList.add('opacity-0'); + setTimeout(() => { + notification.remove(); + }, 300); + }, 3000); +} + +// 页面加载完成后初始化 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initShieldPage); +} else { + initShieldPage(); +} \ No newline at end of file