710 lines
24 KiB
JavaScript
710 lines
24 KiB
JavaScript
// MosDNS 管理面板 JavaScript 应用
|
|
|
|
class MosDNSAdmin {
|
|
constructor() {
|
|
this.apiBase = '/api';
|
|
this.currentTab = 'dashboard';
|
|
this.refreshInterval = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.loadInitialData();
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// 导航栏切换
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.addEventListener('click', (e) => {
|
|
const tab = e.target.dataset.tab;
|
|
this.switchTab(tab);
|
|
});
|
|
});
|
|
|
|
// 页面可见性变化时处理自动刷新
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
this.stopAutoRefresh();
|
|
} else {
|
|
this.startAutoRefresh();
|
|
}
|
|
});
|
|
}
|
|
|
|
switchTab(tab) {
|
|
// 更新导航栏状态
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
document.querySelector(`[data-tab="${tab}"]`).classList.add('active');
|
|
|
|
// 更新内容区域
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
document.getElementById(tab).classList.add('active');
|
|
|
|
this.currentTab = tab;
|
|
this.loadTabData(tab);
|
|
}
|
|
|
|
async loadInitialData() {
|
|
try {
|
|
await this.loadServerInfo();
|
|
await this.loadTabData(this.currentTab);
|
|
} catch (error) {
|
|
this.showMessage('加载初始数据失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async loadServerInfo() {
|
|
try {
|
|
const response = await this.apiCall('/server/info');
|
|
if (response.success) {
|
|
const info = response.data;
|
|
document.getElementById('version').textContent = info.version || 'v5.0.0';
|
|
document.getElementById('service-status').textContent = info.status;
|
|
|
|
// 使用秒数来格式化运行时间,修复 NaN 问题
|
|
if (info.uptime_seconds !== undefined) {
|
|
document.getElementById('uptime').textContent = this.formatUptimeFromSeconds(info.uptime_seconds);
|
|
} else {
|
|
document.getElementById('uptime').textContent = info.uptime || '-';
|
|
}
|
|
|
|
// 显示 DNS 端口
|
|
if (info.dns_ports && info.dns_ports.length > 0) {
|
|
document.getElementById('dns-ports').textContent = info.dns_ports.join(', ');
|
|
} else {
|
|
document.getElementById('dns-ports').textContent = '未检测到';
|
|
}
|
|
|
|
// 显示 API 地址
|
|
if (info.api_address) {
|
|
document.getElementById('api-address').textContent = info.api_address;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load server info:', error);
|
|
}
|
|
}
|
|
|
|
formatUptimeFromSeconds(seconds) {
|
|
if (!seconds || seconds < 0) {
|
|
return '0分钟';
|
|
}
|
|
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
let parts = [];
|
|
if (days > 0) parts.push(`${days}天`);
|
|
if (hours > 0) parts.push(`${hours}小时`);
|
|
if (minutes > 0 || parts.length === 0) parts.push(`${minutes}分钟`);
|
|
|
|
return parts.join(' ');
|
|
}
|
|
|
|
async loadTabData(tab) {
|
|
switch (tab) {
|
|
case 'dashboard':
|
|
await this.loadDashboardData();
|
|
break;
|
|
case 'mikrotik':
|
|
await this.loadMikroTikTab();
|
|
break;
|
|
case 'domains':
|
|
await this.loadDomainFiles();
|
|
break;
|
|
case 'logs':
|
|
await this.loadLogs();
|
|
break;
|
|
case 'stats':
|
|
await this.loadDetailedStats();
|
|
break;
|
|
}
|
|
}
|
|
|
|
async loadDashboardData() {
|
|
try {
|
|
// 加载详细统计
|
|
const statsResponse = await this.apiCall('/stats/detailed');
|
|
if (statsResponse.success) {
|
|
const stats = statsResponse.data;
|
|
document.getElementById('total-queries').textContent = stats.totalQueries?.toLocaleString() || '-';
|
|
document.getElementById('cache-hits').textContent = stats.cacheHits?.toLocaleString() || '-';
|
|
document.getElementById('avg-response').textContent = stats.avgResponseTime ? `${stats.avgResponseTime}ms` : '-';
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load dashboard data:', error);
|
|
}
|
|
}
|
|
|
|
// MikroTik 标签页加载
|
|
async loadMikroTikTab() {
|
|
try {
|
|
// 直接加载 MikroTik 配置列表(不再需要加载域名文件下拉框)
|
|
await this.loadMikrotikList();
|
|
} catch (error) {
|
|
console.error('Failed to load MikroTik tab:', error);
|
|
}
|
|
}
|
|
|
|
// 加载 MikroTik 配置列表
|
|
async loadMikrotikList() {
|
|
const listDiv = document.getElementById('mikrotik-list');
|
|
|
|
try {
|
|
console.log('开始加载 MikroTik 配置列表...');
|
|
listDiv.innerHTML = '<div class="loading">加载中...</div>';
|
|
|
|
const response = await this.apiCall('/mikrotik/list');
|
|
console.log('MikroTik API 响应:', response);
|
|
|
|
if (!response) {
|
|
throw new Error('API 响应为空');
|
|
}
|
|
|
|
if (!response.success) {
|
|
throw new Error(response.message || '加载失败');
|
|
}
|
|
|
|
const configs = response.data || [];
|
|
|
|
if (configs.length === 0) {
|
|
listDiv.innerHTML = '<div style="text-align:center;padding:20px;color:#909399;">暂无 MikroTik 配置</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
configs.forEach(config => {
|
|
const args = config.args || {};
|
|
const domainFiles = args.domain_files || [];
|
|
const domainFilesStr = Array.isArray(domainFiles) ? domainFiles.join(', ') : domainFiles;
|
|
|
|
html += `
|
|
<div class="mikrotik-item">
|
|
<div class="mikrotik-item-header">
|
|
<div class="mikrotik-item-title">${this.escapeHtml(config.tag || '')}</div>
|
|
<div class="mikrotik-item-actions">
|
|
<button class="btn btn-danger" onclick="deleteMikrotikConfig('${this.escapeHtml(config.tag || '')}')">🗑️ 删除</button>
|
|
</div>
|
|
</div>
|
|
<div class="mikrotik-item-content">
|
|
<div class="mikrotik-item-field">
|
|
<div class="mikrotik-item-label">主机地址</div>
|
|
<div class="mikrotik-item-value">${this.escapeHtml(args.host || '-')}</div>
|
|
</div>
|
|
<div class="mikrotik-item-field">
|
|
<div class="mikrotik-item-label">端口</div>
|
|
<div class="mikrotik-item-value">${args.port || '-'}</div>
|
|
</div>
|
|
<div class="mikrotik-item-field">
|
|
<div class="mikrotik-item-label">用户名</div>
|
|
<div class="mikrotik-item-value">${this.escapeHtml(args.username || '-')}</div>
|
|
</div>
|
|
<div class="mikrotik-item-field">
|
|
<div class="mikrotik-item-label">地址列表</div>
|
|
<div class="mikrotik-item-value">${this.escapeHtml(args.address_list4 || '-')}</div>
|
|
</div>
|
|
<div class="mikrotik-item-field">
|
|
<div class="mikrotik-item-label">域名文件</div>
|
|
<div class="mikrotik-item-value">${this.escapeHtml(domainFilesStr || '-')}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
listDiv.innerHTML = html;
|
|
console.log(`成功加载 ${configs.length} 个 MikroTik 配置`);
|
|
|
|
} catch (error) {
|
|
console.error('加载 MikroTik 列表失败:', error);
|
|
listDiv.innerHTML = `
|
|
<div style="color:#f56c6c;padding:20px;text-align:center;">
|
|
<p>❌ 加载失败</p>
|
|
<p style="font-size:13px;margin-top:8px;">${this.escapeHtml(error.message)}</p>
|
|
<button class="btn btn-secondary" onclick="app.loadMikrotikList()" style="margin-top:12px;">重试</button>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// HTML 转义
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
async loadConfigData() {
|
|
try {
|
|
const response = await this.apiCall('/config');
|
|
if (response.success) {
|
|
document.getElementById('config-editor').value = response.data.content || '';
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('加载配置失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async loadDomainFiles() {
|
|
try {
|
|
const response = await this.apiCall('/domain-files');
|
|
if (response.success) {
|
|
this.renderDomainFilesList(response.data);
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('加载域名文件失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async loadLogs() {
|
|
try {
|
|
const response = await this.apiCall('/logs');
|
|
if (response.success) {
|
|
document.getElementById('logs-content').textContent = response.data.content || '暂无日志内容';
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('加载日志失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async loadDetailedStats() {
|
|
try {
|
|
const response = await this.apiCall('/stats/detailed');
|
|
if (response.success) {
|
|
this.renderDetailedStats(response.data);
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('加载统计信息失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
renderPluginsList(plugins) {
|
|
const container = document.getElementById('plugins-list');
|
|
if (!plugins || plugins.length === 0) {
|
|
container.innerHTML = '<div class="loading">暂无插件信息</div>';
|
|
return;
|
|
}
|
|
|
|
const html = plugins.map(plugin => `
|
|
<div class="stat-item">
|
|
<span class="stat-label">${plugin.tag}:</span>
|
|
<span class="stat-value">${plugin.status || '运行中'}</span>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
renderDomainFilesList(files) {
|
|
const container = document.getElementById('domain-files-list');
|
|
if (!files || files.length === 0) {
|
|
container.innerHTML = '<div class="loading">暂无域名文件</div>';
|
|
return;
|
|
}
|
|
|
|
const html = files.map(file => `
|
|
<div class="domain-file-item">
|
|
<div class="domain-file-info">
|
|
<div class="domain-file-name">${file.filename}</div>
|
|
<div class="domain-file-meta">
|
|
大小: ${this.formatFileSize(file.size)} |
|
|
修改时间: ${new Date(file.modTime).toLocaleString()}
|
|
</div>
|
|
</div>
|
|
<div class="domain-file-actions">
|
|
<button class="btn btn-secondary" onclick="app.viewDomainFile('${file.filename}')">查看</button>
|
|
<button class="btn btn-secondary" onclick="app.editDomainFile('${file.filename}')">编辑</button>
|
|
<button class="btn btn-secondary" onclick="app.deleteDomainFile('${file.filename}')">删除</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
renderDetailedStats(stats) {
|
|
const container = document.getElementById('detailed-stats');
|
|
if (!stats) {
|
|
container.innerHTML = '<div class="loading">暂无统计信息</div>';
|
|
return;
|
|
}
|
|
|
|
const html = `
|
|
<div class="stat-item">
|
|
<span class="stat-label">DNS 查询总数:</span>
|
|
<span class="stat-value">${stats.totalQueries?.toLocaleString() || '-'}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">成功响应:</span>
|
|
<span class="stat-value">${stats.successfulQueries?.toLocaleString() || '-'}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">失败响应:</span>
|
|
<span class="stat-value">${stats.failedQueries?.toLocaleString() || '-'}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">缓存命中:</span>
|
|
<span class="stat-value">${stats.cacheHits?.toLocaleString() || '-'}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">缓存未命中:</span>
|
|
<span class="stat-value">${stats.cacheMisses?.toLocaleString() || '-'}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">平均响应时间:</span>
|
|
<span class="stat-value">${stats.avgResponseTime ? stats.avgResponseTime + 'ms' : '-'}</span>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async apiCall(endpoint, options = {}) {
|
|
const url = this.apiBase + endpoint;
|
|
const defaultOptions = {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
};
|
|
|
|
const response = await fetch(url, { ...defaultOptions, ...options });
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
startAutoRefresh() {
|
|
this.stopAutoRefresh();
|
|
this.refreshInterval = setInterval(() => {
|
|
if (this.currentTab === 'dashboard') {
|
|
this.loadDashboardData();
|
|
}
|
|
}, 30000); // 30秒刷新一次
|
|
}
|
|
|
|
stopAutoRefresh() {
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
this.refreshInterval = null;
|
|
}
|
|
}
|
|
|
|
showMessage(message, type = 'success') {
|
|
const container = document.getElementById('message-container');
|
|
const messageEl = document.createElement('div');
|
|
messageEl.className = `message ${type}`;
|
|
messageEl.textContent = message;
|
|
|
|
container.appendChild(messageEl);
|
|
|
|
setTimeout(() => {
|
|
messageEl.remove();
|
|
}, 5000);
|
|
}
|
|
|
|
formatUptime(seconds) {
|
|
if (!seconds) 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}小时 ${minutes}分钟`;
|
|
if (hours > 0) return `${hours}小时 ${minutes}分钟`;
|
|
return `${minutes}分钟`;
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
if (!bytes) return '0 B';
|
|
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
// 域名文件操作方法
|
|
async viewDomainFile(filename) {
|
|
try {
|
|
const response = await this.apiCall(`/domain-files/${filename}`);
|
|
if (response.success) {
|
|
alert(`文件内容:\n\n${response.data.content}`);
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('查看文件失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async editDomainFile(filename) {
|
|
this.showMessage('编辑功能正在开发中', 'warning');
|
|
}
|
|
|
|
async deleteDomainFile(filename) {
|
|
if (!confirm(`确定要删除文件 ${filename} 吗?`)) return;
|
|
|
|
try {
|
|
const response = await this.apiCall(`/domain-files/${filename}`, {
|
|
method: 'DELETE'
|
|
});
|
|
if (response.success) {
|
|
this.showMessage('文件删除成功');
|
|
this.loadDomainFiles();
|
|
}
|
|
} catch (error) {
|
|
this.showMessage('删除文件失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 全局函数,供 HTML 中的按钮调用
|
|
async function reloadConfig() {
|
|
try {
|
|
const response = await app.apiCall('/config/reload', { method: 'POST' });
|
|
if (response.success) {
|
|
app.showMessage('配置重载成功');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('配置重载失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function flushCache() {
|
|
try {
|
|
const response = await app.apiCall('/cache/flush', { method: 'POST' });
|
|
if (response.success) {
|
|
app.showMessage('缓存清空成功');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('缓存清空失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function refreshStats() {
|
|
app.loadDashboardData();
|
|
app.showMessage('统计信息已刷新');
|
|
}
|
|
|
|
async function restartService() {
|
|
if (!confirm('确定要重启服务吗?服务将在 3 秒后重启。')) return;
|
|
|
|
try {
|
|
const response = await app.apiCall('/system/restart', { method: 'POST' });
|
|
if (response.success) {
|
|
app.showMessage('重启请求已发送,服务将在 3 秒后重启', 'success');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('重启失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// MikroTik 管理函数
|
|
async function saveMikrotikConfig() {
|
|
const tag = document.getElementById('mikrotik-tag').value.trim();
|
|
const host = document.getElementById('mikrotik-host').value.trim();
|
|
const port = document.getElementById('mikrotik-port').value.trim();
|
|
const username = document.getElementById('mikrotik-username').value.trim();
|
|
const password = document.getElementById('mikrotik-password').value;
|
|
const addresslist = document.getElementById('mikrotik-addresslist').value.trim();
|
|
const domainFilePath = document.getElementById('mikrotik-domains').value.trim();
|
|
|
|
// 验证必填字段
|
|
if (!tag) {
|
|
app.showMessage('请填写配置标签', 'error');
|
|
return;
|
|
}
|
|
if (!host) {
|
|
app.showMessage('请填写 MikroTik 地址', 'error');
|
|
return;
|
|
}
|
|
if (!username) {
|
|
app.showMessage('请填写用户名', 'error');
|
|
return;
|
|
}
|
|
if (!password) {
|
|
app.showMessage('请填写密码', 'error');
|
|
return;
|
|
}
|
|
if (!addresslist) {
|
|
app.showMessage('请填写地址列表名', 'error');
|
|
return;
|
|
}
|
|
if (!domainFilePath) {
|
|
app.showMessage('请填写域名文件路径', 'error');
|
|
return;
|
|
}
|
|
|
|
// 构建配置对象
|
|
const config = {
|
|
tag: tag,
|
|
type: 'mikrotik_addresslist',
|
|
args: {
|
|
domain_files: [domainFilePath], // 使用用户输入的完整路径
|
|
host: host,
|
|
port: parseInt(port) || 9728,
|
|
username: username,
|
|
password: password,
|
|
use_tls: false,
|
|
timeout: 3,
|
|
address_list4: addresslist,
|
|
mask4: 24,
|
|
comment: `${addresslist}-AutoAdd`,
|
|
timeout_addr: 43200,
|
|
cache_ttl: 3600,
|
|
verify_add: false,
|
|
add_all_ips: true,
|
|
max_ips: 50
|
|
}
|
|
};
|
|
|
|
try {
|
|
const response = await app.apiCall('/mikrotik/add', {
|
|
method: 'POST',
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
if (response.success) {
|
|
app.showMessage(response.message || 'MikroTik 配置已保存', 'success');
|
|
// 清空表单
|
|
clearMikrotikForm();
|
|
// 刷新列表
|
|
await app.loadMikrotikList();
|
|
} else {
|
|
app.showMessage(response.message || '保存失败', 'error');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('保存失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteMikrotikConfig(tag) {
|
|
if (!confirm(`确定要删除 MikroTik 配置 "${tag}" 吗?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await app.apiCall(`/mikrotik/${encodeURIComponent(tag)}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (response.success) {
|
|
app.showMessage(response.message || '配置已删除', 'success');
|
|
// 刷新列表
|
|
await app.loadMikrotikList();
|
|
} else {
|
|
app.showMessage(response.message || '删除失败', 'error');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('删除失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function clearMikrotikForm() {
|
|
document.getElementById('mikrotik-tag').value = '';
|
|
document.getElementById('mikrotik-host').value = '';
|
|
document.getElementById('mikrotik-port').value = '9728';
|
|
document.getElementById('mikrotik-username').value = 'admin';
|
|
document.getElementById('mikrotik-password').value = '';
|
|
document.getElementById('mikrotik-addresslist').value = '';
|
|
document.getElementById('mikrotik-domains').value = '';
|
|
app.showMessage('表单已清空', 'info');
|
|
}
|
|
|
|
// 重新加载 MikroTik 列表
|
|
async function loadMikrotikList() {
|
|
await app.loadMikrotikList();
|
|
}
|
|
|
|
async function saveConfig() {
|
|
const content = document.getElementById('config-editor').value;
|
|
try {
|
|
const response = await app.apiCall('/config', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ content })
|
|
});
|
|
if (response.success) {
|
|
app.showMessage('配置保存成功');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('配置保存失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function validateConfig() {
|
|
const content = document.getElementById('config-editor').value;
|
|
try {
|
|
const response = await app.apiCall('/config/validate', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ content })
|
|
});
|
|
if (response.success) {
|
|
app.showMessage('配置验证通过');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('配置验证失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function backupConfig() {
|
|
try {
|
|
const response = await app.apiCall('/config/backup', { method: 'POST' });
|
|
if (response.success) {
|
|
app.showMessage('配置备份成功');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('配置备份失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function addDomainFile() {
|
|
app.showMessage('添加文件功能正在开发中', 'warning');
|
|
}
|
|
|
|
async function refreshDomainFiles() {
|
|
app.loadDomainFiles();
|
|
app.showMessage('域名文件列表已刷新');
|
|
}
|
|
|
|
async function clearLogs() {
|
|
if (!confirm('确定要清空日志吗?')) return;
|
|
|
|
try {
|
|
const response = await app.apiCall('/logs/clear', { method: 'POST' });
|
|
if (response.success) {
|
|
document.getElementById('logs-content').textContent = '';
|
|
app.showMessage('日志清空成功');
|
|
}
|
|
} catch (error) {
|
|
app.showMessage('日志清空失败: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function refreshLogs() {
|
|
app.loadLogs();
|
|
app.showMessage('日志已刷新');
|
|
}
|
|
|
|
async function exportStats() {
|
|
app.showMessage('导出功能正在开发中', 'warning');
|
|
}
|
|
|
|
async function refreshDetailedStats() {
|
|
app.loadDetailedStats();
|
|
app.showMessage('统计信息已刷新');
|
|
}
|
|
|
|
// 初始化应用
|
|
let app;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
app = new MosDNSAdmin();
|
|
});
|