604 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			604 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// 系统监控页面JavaScript
 | 
						||
 | 
						||
class SystemMonitor {
 | 
						||
    constructor() {
 | 
						||
        this.charts = {};
 | 
						||
        this.refreshInterval = null;
 | 
						||
        this.refreshProgressInterval = null;
 | 
						||
        this.isRefreshing = false;
 | 
						||
 | 
						||
        this.init();
 | 
						||
    }
 | 
						||
 | 
						||
    async init() {
 | 
						||
        console.log('初始化系统监控页面...');
 | 
						||
 | 
						||
        // 初始化图表
 | 
						||
        this.initCharts();
 | 
						||
 | 
						||
        // 加载初始数据
 | 
						||
        await this.loadSystemStatus();
 | 
						||
        await this.loadRecentData();
 | 
						||
 | 
						||
        // 启动自动刷新
 | 
						||
        this.startAutoRefresh();
 | 
						||
 | 
						||
        // 启动刷新进度条
 | 
						||
        this.startRefreshProgress();
 | 
						||
 | 
						||
        console.log('系统监控页面初始化完成');
 | 
						||
    }
 | 
						||
 | 
						||
    initCharts() {
 | 
						||
        // 代理池趋势图
 | 
						||
        const proxyPoolCtx = document.getElementById('proxyPoolChart');
 | 
						||
        if (proxyPoolCtx) {
 | 
						||
            this.charts.proxyPool = new Chart(proxyPoolCtx, {
 | 
						||
                type: 'line',
 | 
						||
                data: {
 | 
						||
                    labels: [],
 | 
						||
                    datasets: [{
 | 
						||
                        label: '总代理数',
 | 
						||
                        data: [],
 | 
						||
                        borderColor: '#007bff',
 | 
						||
                        backgroundColor: 'rgba(0, 123, 255, 0.1)',
 | 
						||
                        tension: 0.4,
 | 
						||
                        fill: true
 | 
						||
                    }, {
 | 
						||
                        label: '可用代理',
 | 
						||
                        data: [],
 | 
						||
                        borderColor: '#28a745',
 | 
						||
                        backgroundColor: 'rgba(40, 167, 69, 0.1)',
 | 
						||
                        tension: 0.4,
 | 
						||
                        fill: true
 | 
						||
                    }]
 | 
						||
                },
 | 
						||
                options: {
 | 
						||
                    responsive: true,
 | 
						||
                    maintainAspectRatio: false,
 | 
						||
                    plugins: {
 | 
						||
                        legend: {
 | 
						||
                            position: 'top',
 | 
						||
                        }
 | 
						||
                    },
 | 
						||
                    scales: {
 | 
						||
                        y: {
 | 
						||
                            beginAtZero: true,
 | 
						||
                            ticks: {
 | 
						||
                                precision: 0
 | 
						||
                            }
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
 | 
						||
        // 任务执行率图
 | 
						||
        const taskRateCtx = document.getElementById('taskRateChart');
 | 
						||
        if (taskRateCtx) {
 | 
						||
            this.charts.taskRate = new Chart(taskRateCtx, {
 | 
						||
                type: 'line',
 | 
						||
                data: {
 | 
						||
                    labels: [],
 | 
						||
                    datasets: [{
 | 
						||
                        label: '成功率 (%)',
 | 
						||
                        data: [],
 | 
						||
                        borderColor: '#17a2b8',
 | 
						||
                        backgroundColor: 'rgba(23, 162, 184, 0.1)',
 | 
						||
                        tension: 0.4,
 | 
						||
                        fill: true
 | 
						||
                    }]
 | 
						||
                },
 | 
						||
                options: {
 | 
						||
                    responsive: true,
 | 
						||
                    maintainAspectRatio: false,
 | 
						||
                    plugins: {
 | 
						||
                        legend: {
 | 
						||
                            position: 'top',
 | 
						||
                        }
 | 
						||
                    },
 | 
						||
                    scales: {
 | 
						||
                        y: {
 | 
						||
                            beginAtZero: true,
 | 
						||
                            max: 100,
 | 
						||
                            ticks: {
 | 
						||
                                callback: function(value) {
 | 
						||
                                    return value + '%';
 | 
						||
                                }
 | 
						||
                            }
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            });
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    async loadSystemStatus() {
 | 
						||
        try {
 | 
						||
            this.setRefreshing(true);
 | 
						||
 | 
						||
            // 获取系统状态
 | 
						||
            const statusResponse = await fetch('/api/dashboard/status');
 | 
						||
            const statusData = await statusResponse.json();
 | 
						||
 | 
						||
            if (statusData.success) {
 | 
						||
                this.updateSystemOverview(statusData.data);
 | 
						||
                this.updateTasksStatus(statusData.data);
 | 
						||
                this.updateProxyPoolStatus(statusData.data.proxies);
 | 
						||
                this.updateResourceUsage(statusData.data);
 | 
						||
            }
 | 
						||
 | 
						||
            // 更新最后刷新时间
 | 
						||
            this.updateLastRefreshTime();
 | 
						||
 | 
						||
        } catch (error) {
 | 
						||
            console.error('加载系统状态失败:', error);
 | 
						||
            this.showAlert('加载系统状态失败: ' + error.message, 'danger');
 | 
						||
        } finally {
 | 
						||
            this.setRefreshing(false);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    async loadRecentData() {
 | 
						||
        try {
 | 
						||
            // 加载图表数据
 | 
						||
            await this.updateCharts();
 | 
						||
 | 
						||
            // 加载最近日志
 | 
						||
            await this.loadRecentLogs();
 | 
						||
 | 
						||
            // 加载最近事件
 | 
						||
            await this.loadRecentEvents();
 | 
						||
 | 
						||
        } catch (error) {
 | 
						||
            console.error('加载最近数据失败:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    updateSystemOverview(data) {
 | 
						||
        // 更新系统状态指示器
 | 
						||
        const indicator = document.getElementById('systemStatusIndicator');
 | 
						||
        const statusText = document.getElementById('systemStatusText');
 | 
						||
        const healthBadge = document.getElementById('systemHealth');
 | 
						||
 | 
						||
        if (data.proxies && data.proxies.valid > 0) {
 | 
						||
            indicator.className = 'status-indicator online';
 | 
						||
            statusText.textContent = '在线';
 | 
						||
            healthBadge.textContent = '健康';
 | 
						||
            healthBadge.className = 'badge bg-success';
 | 
						||
        } else {
 | 
						||
            indicator.className = 'status-indicator offline';
 | 
						||
            statusText.textContent = '异常';
 | 
						||
            healthBadge.textContent = '异常';
 | 
						||
            healthBadge.className = 'badge bg-danger';
 | 
						||
        }
 | 
						||
 | 
						||
        // 更新运行时间
 | 
						||
        document.getElementById('systemUptime').textContent = this.formatUptime(data.uptime);
 | 
						||
 | 
						||
        // 更新内存使用
 | 
						||
        const memoryHtml = `
 | 
						||
            <div>已使用: ${data.memory.heapUsed}MB</div>
 | 
						||
            <div class="progress mt-1" style="height: 8px;">
 | 
						||
                <div class="progress-bar" style="width: ${(data.memory.heapUsed / data.memory.heapTotal * 100)}%"></div>
 | 
						||
            </div>
 | 
						||
            <small class="text-muted">总计: ${data.memory.heapTotal}MB</small>
 | 
						||
        `;
 | 
						||
        document.getElementById('memoryUsage').innerHTML = memoryHtml;
 | 
						||
 | 
						||
        // 更新定时任务状态
 | 
						||
        if (data.scheduler) {
 | 
						||
            const taskCount = data.scheduler.taskCount || 0;
 | 
						||
            document.getElementById('schedulerStatus').innerHTML = `
 | 
						||
                <div>${taskCount} 个任务运行中</div>
 | 
						||
                <small class="text-muted">自动调度正常</small>
 | 
						||
            `;
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    updateTasksStatus(data) {
 | 
						||
        const container = document.getElementById('tasksStatus');
 | 
						||
 | 
						||
        if (data.today_tasks) {
 | 
						||
            const scrape = data.today_tasks.scrape;
 | 
						||
            const validation = data.today_tasks.validation;
 | 
						||
 | 
						||
            container.innerHTML = `
 | 
						||
                <div class="row">
 | 
						||
                    <div class="col-6">
 | 
						||
                        <div class="card border-primary">
 | 
						||
                            <div class="card-body text-center p-2">
 | 
						||
                                <h6 class="card-title mb-1">抓取任务</h6>
 | 
						||
                                <div class="d-flex justify-content-between">
 | 
						||
                                    <small>成功: <span class="text-success">${scrape.success}</span></small>
 | 
						||
                                    <small>失败: <span class="text-danger">${scrape.failed}</span></small>
 | 
						||
                                </div>
 | 
						||
                                <div class="progress mt-1" style="height: 6px;">
 | 
						||
                                    <div class="progress-bar bg-success" style="width: ${scrape.success_rate}%"></div>
 | 
						||
                                </div>
 | 
						||
                                <small class="text-muted">成功率: ${scrape.success_rate}%</small>
 | 
						||
                            </div>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                    <div class="col-6">
 | 
						||
                        <div class="card border-success">
 | 
						||
                            <div class="card-body text-center p-2">
 | 
						||
                                <h6 class="card-title mb-1">验证任务</h6>
 | 
						||
                                <div class="d-flex justify-content-between">
 | 
						||
                                    <small>成功: <span class="text-success">${validation.success}</span></small>
 | 
						||
                                    <small>失败: <span class="text-danger">${validation.failed}</span></small>
 | 
						||
                                </div>
 | 
						||
                                <div class="progress mt-1" style="height: 6px;">
 | 
						||
                                    <div class="progress-bar bg-success" style="width: ${validation.success_rate}%"></div>
 | 
						||
                                </div>
 | 
						||
                                <small class="text-muted">成功率: ${validation.success_rate}%</small>
 | 
						||
                            </div>
 | 
						||
                        </div>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            `;
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    updateProxyPoolStatus(proxies) {
 | 
						||
        const container = document.getElementById('proxyPoolStatus');
 | 
						||
 | 
						||
        container.innerHTML = `
 | 
						||
            <div class="row text-center">
 | 
						||
                <div class="col-4">
 | 
						||
                    <div class="p-2">
 | 
						||
                        <h4 class="text-primary mb-0">${proxies.total}</h4>
 | 
						||
                        <small class="text-muted">总代理数</small>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                <div class="col-4">
 | 
						||
                    <div class="p-2 border-start border-end">
 | 
						||
                        <h4 class="text-success mb-0">${proxies.valid}</h4>
 | 
						||
                        <small class="text-muted">可用代理</small>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
                <div class="col-4">
 | 
						||
                    <div class="p-2">
 | 
						||
                        <h4 class="text-danger mb-0">${proxies.invalid}</h4>
 | 
						||
                        <small class="text-muted">无效代理</small>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
            <hr>
 | 
						||
            <div class="text-center">
 | 
						||
                <div class="badge bg-info fs-6">可用率: ${proxies.validRate}</div>
 | 
						||
            </div>
 | 
						||
        `;
 | 
						||
    }
 | 
						||
 | 
						||
    updateResourceUsage(data) {
 | 
						||
        if (data.memory) {
 | 
						||
            const memoryPercent = Math.round((data.memory.heapUsed / data.memory.heapTotal) * 100);
 | 
						||
            document.getElementById('memoryPercent').textContent = memoryPercent + '%';
 | 
						||
            document.getElementById('memoryProgressBar').style.width = memoryPercent + '%';
 | 
						||
        }
 | 
						||
 | 
						||
        if (data.proxies) {
 | 
						||
            const validRate = parseFloat(data.proxies.validRate);
 | 
						||
            document.getElementById('proxyValidRate').textContent = data.proxies.validRate;
 | 
						||
            document.getElementById('proxyValidProgressBar').style.width = validRate + '%';
 | 
						||
        }
 | 
						||
 | 
						||
        // 模拟CPU使用率(实际项目中可以从系统API获取)
 | 
						||
        const cpuUsage = Math.round(Math.random() * 30 + 10); // 10-40%
 | 
						||
        document.getElementById('cpuUsage').textContent = cpuUsage + '%';
 | 
						||
        document.getElementById('cpuProgressBar').style.width = cpuUsage + '%';
 | 
						||
    }
 | 
						||
 | 
						||
    async updateCharts() {
 | 
						||
        try {
 | 
						||
            // 更新代理池趋势图
 | 
						||
            const proxyResponse = await fetch('/api/dashboard/charts/proxies');
 | 
						||
            const proxyResult = await proxyResponse.json();
 | 
						||
 | 
						||
            if (proxyResult.success && this.charts.proxyPool) {
 | 
						||
                const labels = proxyResult.data.map(item =>
 | 
						||
                    new Date(item.date).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })
 | 
						||
                );
 | 
						||
 | 
						||
                // 计算累计数据
 | 
						||
                let totalRunning = 0;
 | 
						||
                let validRunning = 0;
 | 
						||
                const totalData = [];
 | 
						||
                const validData = [];
 | 
						||
 | 
						||
                proxyResult.data.forEach(item => {
 | 
						||
                    totalRunning += item.total_added || 0;
 | 
						||
                    validRunning += item.valid_added || 0;
 | 
						||
                    totalData.push(totalRunning);
 | 
						||
                    validData.push(validRunning);
 | 
						||
                });
 | 
						||
 | 
						||
                this.charts.proxyPool.data.labels = labels;
 | 
						||
                this.charts.proxyPool.data.datasets[0].data = totalData;
 | 
						||
                this.charts.proxyPool.data.datasets[1].data = validData;
 | 
						||
                this.charts.proxyPool.update();
 | 
						||
            }
 | 
						||
 | 
						||
            // 更新任务执行率图
 | 
						||
            const taskResponse = await fetch('/api/history/stats/summary');
 | 
						||
            const taskResult = await taskResponse.json();
 | 
						||
 | 
						||
            if (taskResult.success && this.charts.taskRate) {
 | 
						||
                // 使用最近7天的数据
 | 
						||
                const labels = [];
 | 
						||
                const successRates = [];
 | 
						||
 | 
						||
                for (let i = 6; i >= 0; i--) {
 | 
						||
                    const date = new Date();
 | 
						||
                    date.setDate(date.getDate() - i);
 | 
						||
                    labels.push(date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }));
 | 
						||
                    successRates.push(Math.random() * 30 + 70); // 模拟70-100%成功率
 | 
						||
                }
 | 
						||
 | 
						||
                this.charts.taskRate.data.labels = labels;
 | 
						||
                this.charts.taskRate.data.datasets[0].data = successRates;
 | 
						||
                this.charts.taskRate.update();
 | 
						||
            }
 | 
						||
        } catch (error) {
 | 
						||
            console.error('更新图表失败:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    async loadRecentLogs() {
 | 
						||
        try {
 | 
						||
            const response = await fetch('/api/history/logs/system?limit=10');
 | 
						||
            const result = await response.json();
 | 
						||
 | 
						||
            if (result.success) {
 | 
						||
                this.renderRecentLogs(result.data);
 | 
						||
            }
 | 
						||
        } catch (error) {
 | 
						||
            console.error('加载最近日志失败:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    async loadRecentEvents() {
 | 
						||
        try {
 | 
						||
            const response = await fetch('/api/history?limit=10');
 | 
						||
            const result = await response.json();
 | 
						||
 | 
						||
            if (result.success) {
 | 
						||
                this.renderRecentEvents(result.data);
 | 
						||
            }
 | 
						||
        } catch (error) {
 | 
						||
            console.error('加载最近事件失败:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    renderRecentLogs(logs) {
 | 
						||
        const container = document.getElementById('recentLogs');
 | 
						||
 | 
						||
        if (!logs || logs.length === 0) {
 | 
						||
            container.innerHTML = '<div class="text-center text-muted p-4">暂无日志</div>';
 | 
						||
            return;
 | 
						||
        }
 | 
						||
 | 
						||
        container.innerHTML = logs.map(log => `
 | 
						||
            <div class="d-flex align-items-start mb-2 p-2 border-bottom">
 | 
						||
                <div class="me-2">
 | 
						||
                    <span class="badge ${this.getLogLevelClass(log.level)}">${log.level.toUpperCase()}</span>
 | 
						||
                </div>
 | 
						||
                <div class="flex-grow-1">
 | 
						||
                    <div class="small">${log.message}</div>
 | 
						||
                    <div class="text-muted" style="font-size: 0.75rem;">
 | 
						||
                        ${this.formatDateTime(log.timestamp)} - ${log.source}
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
        `).join('');
 | 
						||
    }
 | 
						||
 | 
						||
    renderRecentEvents(events) {
 | 
						||
        const container = document.getElementById('recentEvents');
 | 
						||
 | 
						||
        if (!events || events.length === 0) {
 | 
						||
            container.innerHTML = '<div class="text-center text-muted p-4">暂无事件</div>';
 | 
						||
            return;
 | 
						||
        }
 | 
						||
 | 
						||
        container.innerHTML = events.map(event => `
 | 
						||
            <div class="d-flex align-items-start mb-2 p-2 border-bottom">
 | 
						||
                <div class="me-2">
 | 
						||
                    <i class="bi bi-${this.getTaskIcon(event.task_type)} text-${this.getTaskColor(event.status)}"></i>
 | 
						||
                </div>
 | 
						||
                <div class="flex-grow-1">
 | 
						||
                    <div class="small fw-bold">${event.task_name}</div>
 | 
						||
                    <div class="text-muted" style="font-size: 0.75rem;">
 | 
						||
                        ${this.formatDateTime(event.start_time)} - ${this.getStatusText(event.status)}
 | 
						||
                    </div>
 | 
						||
                    ${event.result_summary ? `<div class="small text-muted">${event.result_summary}</div>` : ''}
 | 
						||
                </div>
 | 
						||
                <div>
 | 
						||
                    <span class="badge ${this.getStatusClass(event.status)}">${this.getStatusText(event.status)}</span>
 | 
						||
                </div>
 | 
						||
            </div>
 | 
						||
        `).join('');
 | 
						||
    }
 | 
						||
 | 
						||
    startAutoRefresh() {
 | 
						||
        // 每30秒刷新一次数据
 | 
						||
        this.refreshInterval = setInterval(() => {
 | 
						||
            this.loadSystemStatus();
 | 
						||
        }, 30000);
 | 
						||
 | 
						||
        // 每5分钟刷新最近数据
 | 
						||
        setInterval(() => {
 | 
						||
            this.loadRecentData();
 | 
						||
        }, 300000);
 | 
						||
    }
 | 
						||
 | 
						||
    startRefreshProgress() {
 | 
						||
        this.refreshProgressInterval = setInterval(() => {
 | 
						||
            const progressBar = document.getElementById('refreshProgress');
 | 
						||
            if (progressBar) {
 | 
						||
                const currentWidth = parseFloat(progressBar.style.width) || 100;
 | 
						||
                const newWidth = Math.max(0, currentWidth - 3.33); // 30秒内从100%到0%
 | 
						||
                progressBar.style.width = newWidth + '%';
 | 
						||
            }
 | 
						||
        }, 1000);
 | 
						||
    }
 | 
						||
 | 
						||
    updateLastRefreshTime() {
 | 
						||
        const now = new Date();
 | 
						||
        const timeString = now.toLocaleTimeString('zh-CN');
 | 
						||
        document.getElementById('lastUpdateTime').textContent = timeString;
 | 
						||
    }
 | 
						||
 | 
						||
    setRefreshing(refreshing) {
 | 
						||
        this.isRefreshing = refreshing;
 | 
						||
        if (refreshing) {
 | 
						||
            // 重置进度条
 | 
						||
            const progressBar = document.getElementById('refreshProgress');
 | 
						||
            if (progressBar) {
 | 
						||
                progressBar.style.width = '100%';
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    // 工具函数
 | 
						||
    formatUptime(seconds) {
 | 
						||
        const hours = Math.floor(seconds / 3600);
 | 
						||
        const minutes = Math.floor((seconds % 3600) / 60);
 | 
						||
 | 
						||
        if (hours > 24) {
 | 
						||
            const days = Math.floor(hours / 24);
 | 
						||
            const remainingHours = hours % 24;
 | 
						||
            return `${days}天 ${remainingHours}小时 ${minutes}分钟`;
 | 
						||
        }
 | 
						||
 | 
						||
        return `${hours}小时 ${minutes}分钟`;
 | 
						||
    }
 | 
						||
 | 
						||
    formatDateTime(dateString) {
 | 
						||
        const date = new Date(dateString);
 | 
						||
        return date.toLocaleString('zh-CN', {
 | 
						||
            month: 'short',
 | 
						||
            day: 'numeric',
 | 
						||
            hour: '2-digit',
 | 
						||
            minute: '2-digit'
 | 
						||
        });
 | 
						||
    }
 | 
						||
 | 
						||
    getLogLevelClass(level) {
 | 
						||
        const classes = {
 | 
						||
            'error': 'bg-danger',
 | 
						||
            'warning': 'bg-warning',
 | 
						||
            'info': 'bg-info',
 | 
						||
            'debug': 'bg-secondary'
 | 
						||
        };
 | 
						||
        return classes[level] || 'bg-secondary';
 | 
						||
    }
 | 
						||
 | 
						||
    getStatusClass(status) {
 | 
						||
        const classes = {
 | 
						||
            'success': 'bg-success',
 | 
						||
            'failed': 'bg-danger',
 | 
						||
            'running': 'bg-warning',
 | 
						||
            'pending': 'bg-secondary'
 | 
						||
        };
 | 
						||
        return classes[status] || 'bg-secondary';
 | 
						||
    }
 | 
						||
 | 
						||
    getStatusText(status) {
 | 
						||
        const texts = {
 | 
						||
            'success': '成功',
 | 
						||
            'failed': '失败',
 | 
						||
            'running': '运行中',
 | 
						||
            'pending': '等待中'
 | 
						||
        };
 | 
						||
        return texts[status] || status;
 | 
						||
    }
 | 
						||
 | 
						||
    getTaskIcon(taskType) {
 | 
						||
        const icons = {
 | 
						||
            'scrape': 'download',
 | 
						||
            'validation': 'check2-square',
 | 
						||
            'health_check': 'heart-pulse'
 | 
						||
        };
 | 
						||
        return icons[taskType] || 'gear';
 | 
						||
    }
 | 
						||
 | 
						||
    getTaskColor(status) {
 | 
						||
        const colors = {
 | 
						||
            'success': 'success',
 | 
						||
            'failed': 'danger',
 | 
						||
            'running': 'warning',
 | 
						||
            'pending': 'secondary'
 | 
						||
        };
 | 
						||
        return colors[status] || 'secondary';
 | 
						||
    }
 | 
						||
 | 
						||
    showAlert(message, type = 'info') {
 | 
						||
        const alertContainer = document.getElementById('alertContainer');
 | 
						||
        if (!alertContainer) return;
 | 
						||
 | 
						||
        const alertId = 'alert-' + Date.now();
 | 
						||
        const alertHtml = `
 | 
						||
            <div class="alert alert-${type} alert-dismissible fade show" role="alert" id="${alertId}">
 | 
						||
                <i class="bi bi-${this.getAlertIcon(type)}"></i>
 | 
						||
                ${message}
 | 
						||
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 | 
						||
            </div>
 | 
						||
        `;
 | 
						||
 | 
						||
        alertContainer.insertAdjacentHTML('beforeend', alertHtml);
 | 
						||
 | 
						||
        // 自动移除提示
 | 
						||
        setTimeout(() => {
 | 
						||
            const alertElement = document.getElementById(alertId);
 | 
						||
            if (alertElement) {
 | 
						||
                alertElement.remove();
 | 
						||
            }
 | 
						||
        }, 5000);
 | 
						||
    }
 | 
						||
 | 
						||
    getAlertIcon(type) {
 | 
						||
        const icons = {
 | 
						||
            'success': 'check-circle-fill',
 | 
						||
            'danger': 'exclamation-triangle-fill',
 | 
						||
            'warning': 'exclamation-triangle-fill',
 | 
						||
            'info': 'info-circle-fill'
 | 
						||
        };
 | 
						||
        return icons[type] || 'info-circle-fill';
 | 
						||
    }
 | 
						||
 | 
						||
    stopAutoRefresh() {
 | 
						||
        if (this.refreshInterval) {
 | 
						||
            clearInterval(this.refreshInterval);
 | 
						||
            this.refreshInterval = null;
 | 
						||
        }
 | 
						||
        if (this.refreshProgressInterval) {
 | 
						||
            clearInterval(this.refreshProgressInterval);
 | 
						||
            this.refreshProgressInterval = null;
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
// 全局函数(供HTML调用)
 | 
						||
let systemMonitor;
 | 
						||
 | 
						||
async function refreshMonitoring() {
 | 
						||
    if (systemMonitor && !systemMonitor.isRefreshing) {
 | 
						||
        await systemMonitor.loadSystemStatus();
 | 
						||
        await systemMonitor.loadRecentData();
 | 
						||
        systemMonitor.showAlert('监控数据已刷新', 'info');
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
// 页面加载完成后初始化
 | 
						||
document.addEventListener('DOMContentLoaded', () => {
 | 
						||
    systemMonitor = new SystemMonitor();
 | 
						||
});
 | 
						||
 | 
						||
// 页面卸载时清理
 | 
						||
window.addEventListener('beforeunload', () => {
 | 
						||
    if (systemMonitor) {
 | 
						||
        systemMonitor.stopAutoRefresh();
 | 
						||
    }
 | 
						||
}); |