web重做
This commit is contained in:
252
static/js/app.js
Normal file
252
static/js/app.js
Normal file
@@ -0,0 +1,252 @@
|
||||
// 全局配置
|
||||
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.error || 0,
|
||||
'total-queries': stats.totalQueries || 0,
|
||||
'rules-count': stats.rulesCount || 0,
|
||||
'hosts-count': stats.hostsCount || 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';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user