252 lines
7.6 KiB
JavaScript
252 lines
7.6 KiB
JavaScript
// 全局配置
|
|
const API_BASE_URL = '/api';
|
|
|
|
// DOM 加载完成后执行
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// 初始化面板切换
|
|
initPanelNavigation();
|
|
|
|
// 初始化通知组件
|
|
initNotification();
|
|
|
|
// 加载初始数据
|
|
loadInitialData();
|
|
|
|
// 定时更新数据
|
|
setInterval(loadInitialData, 60000); // 每分钟更新一次
|
|
});
|
|
|
|
// 初始化面板导航
|
|
function initPanelNavigation() {
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
const panels = document.querySelectorAll('.panel');
|
|
|
|
navItems.forEach(item => {
|
|
item.addEventListener('click', function() {
|
|
// 移除所有活动类
|
|
navItems.forEach(nav => nav.classList.remove('active'));
|
|
panels.forEach(panel => panel.classList.remove('active'));
|
|
|
|
// 添加当前活动类
|
|
this.classList.add('active');
|
|
const target = this.getAttribute('data-target');
|
|
document.getElementById(target).classList.add('active');
|
|
|
|
// 面板激活时执行相应的初始化函数
|
|
if (window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]) {
|
|
window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 初始化通知组件
|
|
function initNotification() {
|
|
window.showNotification = function(message, type = 'info') {
|
|
const notification = document.getElementById('notification');
|
|
const notificationMessage = document.getElementById('notification-message');
|
|
|
|
// 设置消息和类型
|
|
notificationMessage.textContent = message;
|
|
notification.className = 'notification show ' + type;
|
|
|
|
// 自动关闭
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
}, 3000);
|
|
};
|
|
}
|
|
|
|
// 加载初始数据
|
|
function loadInitialData() {
|
|
// 加载服务器状态
|
|
fetch(`${API_BASE_URL}/status`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// 更新服务器状态指示器
|
|
const statusDot = document.querySelector('.status-dot');
|
|
const serverStatus = document.getElementById('server-status');
|
|
|
|
if (data && data.status === 'running') {
|
|
statusDot.classList.add('connected');
|
|
serverStatus.textContent = '运行中';
|
|
} else {
|
|
statusDot.classList.remove('connected');
|
|
serverStatus.textContent = '离线';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('获取服务器状态失败:', error);
|
|
|
|
// 更新状态为离线
|
|
const statusDot = document.querySelector('.status-dot');
|
|
const serverStatus = document.getElementById('server-status');
|
|
statusDot.classList.remove('connected');
|
|
serverStatus.textContent = '离线';
|
|
});
|
|
|
|
// 加载统计数据
|
|
fetch(`${API_BASE_URL}/stats`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// 更新统计数据
|
|
if (data && data.dns) {
|
|
updateStatCards(data.dns);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('获取统计数据失败:', error);
|
|
window.showNotification('获取统计数据失败', 'error');
|
|
});
|
|
}
|
|
|
|
// 更新统计卡片数据
|
|
function updateStatCards(stats) {
|
|
const statElements = {
|
|
'blocked-count': stats.Blocked || 0,
|
|
'allowed-count': stats.Allowed || 0,
|
|
'error-count': stats.Errors || 0,
|
|
'total-queries': stats.Queries || 0,
|
|
'rules-count': 0,
|
|
'hosts-count': 0
|
|
};
|
|
|
|
for (const [id, value] of Object.entries(statElements)) {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.textContent = formatNumber(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 通用API请求函数
|
|
function apiRequest(endpoint, method = 'GET', data = null) {
|
|
const headers = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
const config = {
|
|
method,
|
|
headers
|
|
};
|
|
|
|
if (data && (method === 'POST' || method === 'PUT' || method === 'DELETE')) {
|
|
config.body = JSON.stringify(data);
|
|
}
|
|
|
|
return fetch(`${API_BASE_URL}${endpoint}`, config)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
});
|
|
}
|
|
|
|
// 数字格式化函数
|
|
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 confirmAction(message, onConfirm) {
|
|
if (confirm(message)) {
|
|
onConfirm();
|
|
}
|
|
}
|
|
|
|
// 加载状态函数
|
|
function showLoading(element) {
|
|
if (element) {
|
|
element.innerHTML = '<td colspan="100%" class="loading">加载中...</td>';
|
|
}
|
|
}
|
|
|
|
// 错误状态函数
|
|
function showError(element, message) {
|
|
if (element) {
|
|
element.innerHTML = `<td colspan="100%" style="color: #e74c3c;">${message}</td>`;
|
|
}
|
|
}
|
|
|
|
// 空状态函数
|
|
function showEmpty(element, message) {
|
|
if (element) {
|
|
element.innerHTML = `<td colspan="100%" style="color: #7f8c8d; font-style: italic;">${message}</td>`;
|
|
}
|
|
}
|
|
|
|
// 表格排序功能
|
|
function initTableSort(tableId) {
|
|
const table = document.getElementById(tableId);
|
|
if (!table) return;
|
|
|
|
const headers = table.querySelectorAll('thead th');
|
|
headers.forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
const columnIndex = Array.from(headers).indexOf(this);
|
|
const isAscending = this.getAttribute('data-sort') !== 'asc';
|
|
|
|
// 重置所有标题
|
|
headers.forEach(h => h.setAttribute('data-sort', ''));
|
|
this.setAttribute('data-sort', isAscending ? 'asc' : 'desc');
|
|
|
|
// 排序行
|
|
sortTable(table, columnIndex, isAscending);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 表格排序实现
|
|
function sortTable(table, columnIndex, isAscending) {
|
|
const tbody = table.querySelector('tbody');
|
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
|
|
// 排序行
|
|
rows.sort((a, b) => {
|
|
const aValue = a.cells[columnIndex].textContent.trim();
|
|
const bValue = b.cells[columnIndex].textContent.trim();
|
|
|
|
// 尝试数字排序
|
|
const aNum = parseFloat(aValue);
|
|
const bNum = parseFloat(bValue);
|
|
|
|
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
return isAscending ? aNum - bNum : bNum - aNum;
|
|
}
|
|
|
|
// 字符串排序
|
|
return isAscending
|
|
? aValue.localeCompare(bValue)
|
|
: bValue.localeCompare(aValue);
|
|
});
|
|
|
|
// 重新添加行
|
|
rows.forEach(row => tbody.appendChild(row));
|
|
}
|
|
|
|
// 搜索过滤功能
|
|
function initSearchFilter(inputId, tableId, columnIndex) {
|
|
const input = document.getElementById(inputId);
|
|
const table = document.getElementById(tableId);
|
|
|
|
if (!input || !table) return;
|
|
|
|
input.addEventListener('input', function() {
|
|
const filter = this.value.toLowerCase();
|
|
const rows = table.querySelectorAll('tbody tr');
|
|
|
|
rows.forEach(row => {
|
|
const cell = row.cells[columnIndex];
|
|
if (cell) {
|
|
const text = cell.textContent.toLowerCase();
|
|
row.style.display = text.includes(filter) ? '' : 'none';
|
|
}
|
|
});
|
|
});
|
|
} |