XNSim/XNSimHtml/components/system-info.js
2025-04-28 12:25:20 +08:00

805 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class SystemInfo extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.charts = [];
this.chartOptions = [
'内存使用率',
'磁盘使用率',
'网络带宽'
];
// 动态添加CPU核心选项
this.cpuCoreCount = 8; // 默认值,后续会从系统获取
for (let i = 0; i < this.cpuCoreCount; i++) {
this.chartOptions.push(`CPU${i}使用率`);
}
// 每个图表的默认选择
this.chartSelections = [
'CPU0使用率',
'CPU1使用率',
'内存使用率',
'网络带宽'
];
// 存储历史数据
this.historyData = {};
this.chartOptions.forEach(option => {
this.historyData[option] = Array(60).fill(0);
});
// 为上传带宽单独存储数据
this.historyData['上传带宽'] = Array(60).fill(0);
this.historyData['下载带宽'] = Array(60).fill(0);
// 初始化状态
this.chartsInitialized = false;
this.setupInProgress = false;
this.chartJsLoaded = false;
this.domInitialized = false;
this.isActive = false; // 添加活动状态标记
}
connectedCallback() {
this.isActive = true; // 组件连接到DOM时设置为活动状态
this.render();
// 创建一个MutationObserver来监听shadowRoot内容变化
this.observer = new MutationObserver(this.onDomChange.bind(this));
this.observer.observe(this.shadowRoot, { childList: true, subtree: true });
// 立即尝试初始化事件监听器
setTimeout(() => this.setupEventListeners(), 0);
// 获取系统信息
this.fetchSystemInfo();
// 加载Chart.js
this.loadChartJs();
// 设置定时刷新
this.refreshInterval = setInterval(() => {
if (!this.isActive) return; // 非活动状态时不执行更新
this.fetchSystemInfo();
// 图表初始化后才更新
if (this.chartsInitialized) {
this.updateCharts();
}
}, 2000);
}
disconnectedCallback() {
this.isActive = false; // 组件断开连接时设置为非活动状态
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
if (this.observer) {
this.observer.disconnect();
}
this.cleanupCharts();
}
// 清理所有图表
cleanupCharts() {
// 销毁图表
if (this.charts) {
this.charts.forEach((chart, index) => {
if (chart) {
try {
chart.destroy();
} catch (e) {
console.error(`销毁图表${index}失败:`, e);
}
this.charts[index] = null;
}
});
}
this.chartsInitialized = false;
}
// 重新激活组件的方法(当标签页重新被选中时调用)
reactivate() {
if (this.isActive) return; // 如果已经是活动状态,不重复处理
this.isActive = true;
// 重置图表状态
this.cleanupCharts();
this.domInitialized = false;
// 重新设置刷新定时器
if (!this.refreshInterval) {
this.refreshInterval = setInterval(() => {
if (!this.isActive) return;
this.fetchSystemInfo();
if (this.chartsInitialized) {
this.updateCharts();
}
}, 2000);
}
// 重新初始化图表
setTimeout(() => {
if (this.chartJsLoaded) {
// 确保DOM已初始化
const canvasElements = this.shadowRoot.querySelectorAll('canvas');
if (canvasElements.length === 4) {
this.domInitialized = true;
this.initializeCharts();
} else {
// 如果DOM还没准备好等待DOM变化
this.observer = new MutationObserver(this.onDomChange.bind(this));
this.observer.observe(this.shadowRoot, { childList: true, subtree: true });
}
} else {
// 如果Chart.js还没加载重新加载
this.loadChartJs();
}
}, 300);
}
// 监听DOM变化
onDomChange(mutations) {
if (this.domInitialized) return;
// 检查canvas元素是否已添加到DOM
const canvasElements = this.shadowRoot.querySelectorAll('canvas');
if (canvasElements.length === 4) {
this.domInitialized = true;
// 如果Chart.js已加载初始化图表
if (this.chartJsLoaded) {
this.initializeCharts();
}
}
}
// 加载Chart.js
loadChartJs() {
if (typeof Chart !== 'undefined') {
this.chartJsLoaded = true;
// 如果DOM已准备就绪初始化图表
if (this.domInitialized) {
this.initializeCharts();
}
return;
}
const script = document.createElement('script');
script.src = './chart.min.js';
script.onload = () => {
this.chartJsLoaded = true;
// 如果DOM已准备就绪初始化图表
if (this.domInitialized) {
this.initializeCharts();
}
};
script.onerror = () => {
const cdnScript = document.createElement('script');
cdnScript.src = 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js';
cdnScript.onload = () => {
this.chartJsLoaded = true;
// 如果DOM已准备就绪初始化图表
if (this.domInitialized) {
this.initializeCharts();
}
};
document.head.appendChild(cdnScript);
};
document.head.appendChild(script);
}
render() {
// 提前生成图表选项HTML字符串
const options0 = this.generateChartOptions(0);
const options1 = this.generateChartOptions(1);
const options2 = this.generateChartOptions(2);
const options3 = this.generateChartOptions(3);
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
}
.system-info-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e0e0e0;
}
.info-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.system-overview {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
background-color: #f5f5f5;
border-radius: 6px;
padding: 16px;
}
.overview-item {
display: flex;
flex-direction: column;
}
.overview-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.overview-value {
font-size: 16px;
font-weight: bold;
color: #333;
}
.overview-subvalue {
font-size: 12px;
color: #666;
margin-top: 4px;
}
.charts-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 16px;
flex-grow: 1;
min-height: 400px;
}
.chart-card {
background-color: #f5f5f5;
border-radius: 6px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
min-height: 200px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.chart-title {
font-size: 14px;
font-weight: bold;
color: #333;
}
.chart-select {
padding: 4px;
border-radius: 4px;
border: 1px solid #ccc;
font-size: 12px;
}
.chart-container {
flex-grow: 1;
position: relative;
min-height: 150px;
}
canvas {
width: 100% !important;
height: 100% !important;
position: absolute;
}
</style>
<div class="system-info-container">
<div class="info-header">
<div class="info-title">系统信息</div>
</div>
<div class="system-overview">
<div class="overview-item">
<div class="overview-label">操作系统</div>
<div class="overview-value" id="os-info">Loading...</div>
<div class="overview-subvalue" id="kernel-info">Loading...</div>
</div>
<div class="overview-item">
<div class="overview-label">CPU核心数</div>
<div class="overview-value" id="cpu-cores">Loading...</div>
</div>
<div class="overview-item">
<div class="overview-label">IP地址</div>
<div class="overview-value" id="ip-address">Loading...</div>
</div>
<div class="overview-item">
<div class="overview-label">已隔离CPU核心号</div>
<div class="overview-value" id="isolated-cores">Loading...</div>
</div>
</div>
<div class="charts-container">
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">监控图表 1</div>
<select class="chart-select" data-chart-index="0">
${options0}
</select>
</div>
<div class="chart-container">
<canvas id="chart0"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">监控图表 2</div>
<select class="chart-select" data-chart-index="1">
${options1}
</select>
</div>
<div class="chart-container">
<canvas id="chart1"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">监控图表 3</div>
<select class="chart-select" data-chart-index="2">
${options2}
</select>
</div>
<div class="chart-container">
<canvas id="chart2"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">监控图表 4</div>
<select class="chart-select" data-chart-index="3">
${options3}
</select>
</div>
<div class="chart-container">
<canvas id="chart3"></canvas>
</div>
</div>
</div>
</div>
`;
}
setupEventListeners() {
// 设置图表选择监听器
const selects = this.shadowRoot.querySelectorAll('.chart-select');
if (selects.length !== 4) {
setTimeout(() => this.setupEventListeners(), 100);
return;
}
selects.forEach(select => {
select.addEventListener('change', (e) => {
const chartIndex = parseInt(e.target.getAttribute('data-chart-index'));
const newMetric = e.target.value;
const oldMetric = this.chartSelections[chartIndex];
// 更新选择
this.chartSelections[chartIndex] = newMetric;
// 如果图表已初始化,重新创建图表而不是更新
if (this.charts[chartIndex]) {
this.recreateChart(chartIndex);
}
});
});
}
// 初始化所有图表
initializeCharts() {
if (this.setupInProgress || this.chartsInitialized) {
return;
}
this.setupInProgress = true;
try {
for (let i = 0; i < 4; i++) {
this.createChart(i);
}
this.chartsInitialized = true;
} catch (error) {
console.error('图表初始化失败:', error);
} finally {
this.setupInProgress = false;
}
}
// 创建单个图表
createChart(index) {
const canvas = this.shadowRoot.getElementById(`chart${index}`);
if (!canvas) {
throw new Error(`找不到图表${index}的canvas元素`);
}
// 确保canves宽高被设置
canvas.style.width = '100%';
canvas.style.height = '100%';
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error(`无法获取图表${index}的2D上下文`);
}
// 获取指标和数据
const metric = this.chartSelections[index];
const data = [...(this.historyData[metric] || Array(60).fill(0))];
// 针对不同指标设置不同的y轴配置和数据集
const yAxisConfig = this.getYAxisConfig(metric);
let datasets = [];
if (metric === '网络带宽') {
// 网络带宽显示两条线:上传和下载
datasets = [
{
label: '下载带宽',
data: [...this.historyData['下载带宽']],
borderColor: '#4CAF50', // 绿色
borderWidth: 2,
fill: false,
tension: 0.4,
pointRadius: 0, // 去掉点
pointHoverRadius: 0 // 去掉悬停点
},
{
label: '上传带宽',
data: [...this.historyData['上传带宽']],
borderColor: '#F44336', // 红色
borderWidth: 2,
fill: false,
tension: 0.4,
pointRadius: 0, // 去掉点
pointHoverRadius: 0 // 去掉悬停点
}
];
} else {
// 其他指标只显示一条线
datasets = [
{
label: metric,
data: data,
borderColor: this.getChartColor(metric),
borderWidth: 2,
fill: false,
tension: 0.4,
pointRadius: 0, // 去掉点
pointHoverRadius: 0 // 去掉悬停点
}
];
}
// 创建图表
this.charts[index] = new Chart(ctx, {
type: 'line',
data: {
labels: Array(60).fill(''),
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // 关闭动画提高性能
},
elements: {
point: {
radius: 0 // 全局去掉点
},
line: {
tension: 0.4 // 平滑曲线
}
},
scales: {
y: yAxisConfig,
x: {
display: false
}
},
plugins: {
legend: {
display: metric === '网络带宽', // 只在网络带宽图表显示图例
position: 'top',
labels: {
boxWidth: 12,
font: {
size: 10
}
}
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
const value = context.raw;
// 为网络带宽添加单位
if (metric === '网络带宽' ||
context.dataset.label === '上传带宽' ||
context.dataset.label === '下载带宽') {
return `${value} Mbps`;
}
// 为其他指标添加百分比单位
return `${value}%`;
}
}
}
}
}
});
}
// 根据指标类型获取y轴配置
getYAxisConfig(metric) {
// 网络带宽使用Mbps单位其他使用百分比
if (metric === '网络带宽') {
return {
beginAtZero: true,
// 自动计算最大值但最少为1
suggestedMax: 1,
title: {
display: true,
text: 'Mbps'
}
};
} else {
return {
beginAtZero: true,
max: 100,
title: {
display: true,
text: '%'
}
};
}
}
// 根据指标类型获取图表颜色
getChartColor(metric) {
if (metric === '下载带宽') {
return '#4CAF50'; // 绿色
} else if (metric === '上传带宽') {
return '#F44336'; // 红色
} else if (metric === '内存使用率') {
return '#2196F3'; // 蓝色
} else if (metric === '磁盘使用率') {
return '#FF9800'; // 橙色
} else if (metric.includes('CPU')) {
// CPU使用率显示紫色色调
return '#9C27B0';
}
// 默认颜色
const colors = [
'#4285F4', // 蓝色
'#EA4335', // 红色
'#34A853', // 绿色
'#FBBC05' // 黄色
];
// 为不同的指标分配不同的默认颜色
const hash = metric.split('').reduce((hash, char) => {
return char.charCodeAt(0) + ((hash << 5) - hash);
}, 0);
return colors[Math.abs(hash) % colors.length];
}
// 更新单个图表
updateChart(index) {
try {
if (!this.charts[index]) {
return;
}
const metric = this.chartSelections[index];
if (!this.historyData[metric] && metric !== '网络带宽') {
return;
}
if (metric === '网络带宽') {
// 获取上传和下载数据
const downloadData = [...this.historyData['下载带宽']];
const uploadData = [...this.historyData['上传带宽']];
// 找出最大值来设置Y轴
const maxDownload = Math.max(...downloadData);
const maxUpload = Math.max(...uploadData);
const maxValue = Math.max(maxDownload, maxUpload);
// 网络带宽图表根据数据自动调整y轴刻度
if (this.charts[index].options.scales.y) {
// 根据当前最大值动态设置y轴但至少为1
const suggestedMax = Math.max(maxValue * 1.2, 1);
this.charts[index].options.scales.y.suggestedMax = suggestedMax;
}
// 更新两个数据集
this.charts[index].data.datasets[0].data = downloadData;
this.charts[index].data.datasets[1].data = uploadData;
} else {
// 其他指标只有一个数据集
const currentData = [...this.historyData[metric]];
this.charts[index].data.datasets[0].data = currentData;
}
this.charts[index].update();
} catch (error) {
console.error(`更新图表${index}失败:`, error);
}
}
// 更新所有图表
updateCharts() {
if (!this.chartsInitialized) {
return;
}
for (let i = 0; i < 4; i++) {
this.updateChart(i);
}
}
// 重新创建单个图表
recreateChart(index) {
try {
// 销毁现有图表
if (this.charts[index]) {
this.charts[index].destroy();
this.charts[index] = null;
}
// 重新创建
this.createChart(index);
} catch (error) {
console.error(`重新创建图表${index}失败:`, error);
}
}
// 获取系统信息
async fetchSystemInfo() {
try {
const response = await fetch('/api/system-info');
if (!response.ok) {
throw new Error(`获取系统信息失败: ${response.status}`);
}
const data = await response.json();
// 更新系统概览
const osInfo = this.shadowRoot.getElementById('os-info');
const kernelInfo = this.shadowRoot.getElementById('kernel-info');
const cpuCores = this.shadowRoot.getElementById('cpu-cores');
const ipAddress = this.shadowRoot.getElementById('ip-address');
const isolatedCores = this.shadowRoot.getElementById('isolated-cores');
if (osInfo) osInfo.textContent = data.os;
if (kernelInfo) kernelInfo.textContent = data.kernel || '';
if (cpuCores) cpuCores.textContent = data.cpuCores;
if (ipAddress) ipAddress.textContent = data.ipAddress;
if (isolatedCores) isolatedCores.textContent = data.isolatedCores.join(',') || '无';
// 更新CPU核心数并重新生成选项
if (this.cpuCoreCount !== data.cpuCores) {
this.cpuCoreCount = data.cpuCores;
this.updateCpuOptions();
}
// 更新历史数据
for (const [key, value] of Object.entries(data.metrics)) {
if (this.historyData[key]) {
this.historyData[key].shift();
this.historyData[key].push(value);
}
}
// 如果图表已初始化,则更新图表
if (this.chartsInitialized) {
this.updateCharts();
}
} catch (error) {
console.error('获取系统信息失败:', error);
}
}
// 更新CPU选项
updateCpuOptions() {
// 清除旧的CPU选项
this.chartOptions = this.chartOptions.filter(opt => !opt.startsWith('CPU'));
// 添加新的CPU选项
for (let i = 0; i < this.cpuCoreCount; i++) {
const option = `CPU${i}使用率`;
this.chartOptions.push(option);
// 确保历史数据中有此项
if (!this.historyData[option]) {
this.historyData[option] = Array(60).fill(0);
}
}
// 更新图表选择下拉框
const selects = this.shadowRoot.querySelectorAll('.chart-select');
if (selects.length === 4) {
selects.forEach((select, index) => {
// 保存当前选择
const currentValue = this.chartSelections[index];
// 更新选项
select.innerHTML = this.generateChartOptions(index);
// 如果当前选择的值不在新选项中,则更新为第一个选项
if (!this.chartOptions.includes(currentValue)) {
this.chartSelections[index] = this.chartOptions[0];
if (this.charts[index]) {
this.recreateChart(index);
}
}
});
}
}
// 生成图表选项HTML
generateChartOptions(chartIndex) {
let html = '';
this.chartOptions.forEach(option => {
const selected = option === this.chartSelections[chartIndex] ? 'selected' : '';
html += `<option value="${option}" ${selected}>${option}</option>`;
});
return html;
}
}
customElements.define('system-info', SystemInfo);