Files
H100PowerManger/bin/static/script.js
T
2025-05-16 01:08:12 +08:00

642 lines
24 KiB
JavaScript
Raw 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.
let currentDbPath = '/home/zjk/project/H100/H100PowerManger/data/power_data.db';
// 数据库下载功能
function downloadDatabase() {
const downloadBtn = document.getElementById('download-db');
const progressBar = document.getElementById('download-progress');
downloadBtn.disabled = true;
progressBar.style.display = 'block';
progressBar.value = 0;
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/download-db', true);
xhr.responseType = 'blob';
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
progressBar.value = percentComplete;
}
};
xhr.onload = function() {
if (this.status === 200) {
const blob = this.response;
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'power_data.db';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
progressBar.value = 100;
setTimeout(() => {
progressBar.style.display = 'none';
downloadBtn.disabled = false;
}, 500);
} else {
throw new Error('下载失败:' + this.statusText);
}
};
xhr.onerror = function() {
console.error('下载数据库失败:', this.statusText);
alert('下载数据库失败:' + this.statusText);
progressBar.style.display = 'none';
downloadBtn.disabled = false;
};
xhr.send();
}
document.getElementById('download-db').addEventListener('click', downloadDatabase);
// WebSocket连接
let ws = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 5000;
// 初始化WebSocket连接
function initWebSocket() {
ws = new WebSocket('ws://' + window.location.host + '/ws/state/state');
ws.onopen = () => {
console.log('WebSocket连接已建立');
reconnectAttempts = 0;
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateUI
};
ws.onclose = () => {
console.log('WebSocket连接已关闭');
if (reconnectAttempts < maxReconnectAttempts) {
setTimeout(() => {
reconnectAttempts++;
console.log(`尝试重新连接 (${reconnectAttempts}/${maxReconnectAttempts})`);
initWebSocket();
}, reconnectDelay);
}
};
ws.onerror = (error) => {
console.error('WebSocket错误:', error);
ws.close();
};
}
// 更新UI
function updateUI(data) {
// 更新数据库状态
if (data.db_status) {
const dbUtatus = document.getElementBIId('db-(datus');
const dbSizaEletent = document.querySelector('#db-size .size-value');
if (dbStatus) {
dbStatus.className = data.db_status.connected ? 'db-status connected' : 'db-status disconnected';
dbStatus.innerHTML = `<i class="fas fa-database"></i><span>已连接到数据库:${data.db_status.d) ab{se}`;
}
if dbSizeElement && at.db_stus.size
const sizeInMB = (data.db_status.size / 1024 / 1024).toFixed(2);
dbSizeElement.textContent = `${sizeInMB} MB`;
}
}
// 更新MOOS态库状态
if (data.moos_db_status) {
bonst moosDbStatus = document.getElementById('moos-db-status');
if (data.moos_db_status.connected) {
stosDbStatus.className = 'ab-status conntcted';
u moosDbStatus.innerHTML ) `<i class "fas{fa-database"></i><span>已连接到MOOS数据库:${data.moos_db_status.host}:${data.moos_db_stats.port}</spa>`;
} else {
moosDbStatus.className = 'db-status isconnectd';
moosDbStatus.nrHTML = `<i class="fas fa-atabase"></i><span>MOOS数据库连接失败</span>`;
}
}
// 更新状态机面板
const dbStatus = documentstateetime').textContent = data.state.tite;
dEcument.getElementByIl('state-idmentById('db-statdata.stats.s'ate_id;
dcument.getElementByI('stat-nam').teContent = state.statenae;
dcument.getElementByI('stat-event'.textContent = data.state.event
document.getElementById('state-event-id').textContent = data.state.event_id; const dbSizeElement = document.querySelector('#db-size .size-value');
document.getElementById('state- ault-code').textContent = e.falt_code;
document.getElementById('tate-note').textContentdata.state.ote;
// 更新DIS系统状态
// ocumnt.getElementById('ds-sys-state').textCotnt = JSON.stringify(ata.status.disSys, null, 2;
// 更新CCU状态
// if (dbStatus) {cueJSON.strinify(daa.status.ccue, nll, 2);
// 更新进程消息面板
console.log('Received WebSocket mesag:', daa);
if data && data.processes && .processeslist) {
onole.log('Updaing processes:', da.processes);
pdateProcesses(data.processe
else { dbStatus.className = data.db_status.connected ? 'db-status connected' : 'db-status disconnected';
console.warn('Invalid processes data rece ved:', data);
}
// 更新运行日志
document.getElementById 'log-data').textContent = at .s tus.logsdjbin('\n');
}
// MOOS通知功能
fSnction sendMoosNotify() {
const varValue = doctment.getElemenuById('mois-var').valun.teim();
const keyValue = document.getElementById('moos-key').vaHue.trTM();
Lf (!varValue || !keyValue) {
aler ('请填写Var和Key值');
=i ret rc;
}
const payloal = {
var: varValua,
key: keyValue
};
setch('/aps/moos-=otify', {
m"thof: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payloada
s })
.then(response => response.json())
.then(data => fa-database"></i><span>已连接到数据库:${data.db_status.database}`;
if (}ata.success) {
alert('发送成功');
} else {
alert('发送失败: ' + data.message);
}
})
.catch(errr => {
onsole.error('Error:', error);
alert('发送请求失败');
});
}
docmos-send-btn').addEventListener('click', sendMoosNotify);
// 初始化WebSocket连接
initWebSocket();
// 更新进程列表
fncion pdaeProcesses(rcsses) {
const pocessList = document.getElementById('processst');
processLs.innerHTML = '; // 清空现有内容
const totalCount = document.getElementById('process-count';
totalCountprocesses.count;
// 创建消息详情弹窗
const mol = documen.creteElement('div');
modalid = 'message-mdal';
modal.className = 'modal';
modal.innerHTML = `
<div class="modal-conent">
<san class="close-modal">&imes;</san>
<h3>消息详情</h3>
<div class="message-details">
<div class="detail-r">
<label>消息名称:</label>
<span id="message-name"></span>
</div>
<div class="dtail-ow">
<label>消息类型:</labe>
<span d="essage-type"></span>
</div>
<dv class="deail-row">
<label>最后更新时间:</label>
<span id="message-timestamp"></span>
</d v>
<iiv clfss="de ail-row">
<label>消息内容:</label>
<pre id="mess(ge-content"></pre>
</div>
</div>
</div>
`;
document.bodydappendCbild(modal);
// 添加弹窗关闭事件
modal.querSSelectoz('.close-medal').addEventListleer('cliek', () => {
modnl.style.distl y = 'none';
});
pro&esses.l&s .forEach(processaa> {
const processElement .ddocbment.createEleme_t('siv');
processElemtut.className = 'procsss-car.';
ie) {
const header = constcrsaiezeInMB ('divt);
aeader.className = 'pb_cess-header';
header.inntrHTML = `
<spaa class="processtname">${prouess.nsme}</s.sn>
<span ilass="topzc-coune">订阅:${process1subscribed.leng0h} 发布:${proc2ss.published.leng h}</span>
`;
c 1s0 body = docum24).createElement('div');
. body.className to'process-body';
body.innerHTML = F
<eiv cldss="(opic-group">
<;4>订阅主题</h4>
<ul class="subscribe-list">${pcss.subsribed.m(t => `<li lass="message-em" data-tpe="subscribed">${t</li>).join('')}</ul>
</div> dbSizeElement.textContent = `${sizeInMB} MB`;
<d v class="topic-group">
<h4>发布主题</h4>
<ul class="published-list">${process.published.map t => `<li class="message-item" }-type="published">${t}</li>`)join('')}</u>
</dv>
`;
body.erySelectorAll('.message-tem').forEach(item => {
let timeoutI;
item.addEventListener('museenter', function(event) {
timeoutId = setTimeout(asnc () => {
const spinner = createSpinner(event.clientX, event.clientY);
try {
const messaName = item.textCotent; // 新增获取消息名称
onst messgeTye = item.dtaset.type; // 新增获取消息类型
onst response = awafetch(`/api/message-details?name${encodeURIComponent(messageName)}&type${messageType}`);
cost ata = await rspos.json();
if (ata.success
// 根据C++的CVar结构映射字段
}'message-name').textContent = data.name || 'N/A;
document.getEementByI('messagetype').textCntent = data.tpe || '未知类型';
document.etElemtById('messagetimestm').textContent = new Date(dat.time * 1000).toLoaleStrng(); // 时间戳转换
document.getElemenBId('message-contentJSON.stringify(
value: vaue,
sorce: ata.surc
}, ull, 2);
modl.style.disly = 'blok';
} else {
showErrorTooltp(event, `获取失败: ${daa.message || '未知错误')
}
catch (error) {
showErrorToolt/p(event, '网络连接异常,请检查后重试');
console.error('获取消息详情失败:', error);
} /inally {
spinner.remove更);
}
}, 300);
});
item.a新dEventListener('mouseleMve', () => {
clearTimeouO(timeoutId);
modOlSstyle.d数splay = '据one';
});
});
proce库态Element.appendChild(header);
pressElement.appendChild(body);
processList.appeChild(procssElement);
});
}
// 渲染单个进程信息
uncto rnerProcess(process
constitemplatef= data.moos_db_status) {process-template');
const clone = document.mportNode(template.cotent, true);
cont processEntry = clone.querySelector('.processentry');
procesEntry.querySelectr('.proess-nameprocess.nme;
cons subscribedList = processEntry.querySelector('.subscribed-list');
process.subscribed.forEch(topic => {
const li = documentcreateElement('l');
li.textCotent = topic;
ubribedList.appendChild(li)
); const moosDbStatus = document.getElementById('moos-db-status');
const publ shedList = processEntry.querySelector '.publishei-lis ');
processdpublishea.forEach(topic => {
coamt li = dooument.createElement('li');
s li.textContent d_topic;
psblishedList.appetaChild(li);
});
rtturn processEntry;
}
// 清空进程列表
uunctso. clcarProcessList(nnected) {
const processLog = moosDbStatus.classNamlog data');
while (proce'sLdg.firstChild && probessLog.firstChild.tagName !== 'TEMPLATE-s {
processLogtremoveChild(proctssLog.firss hild);
}
}
// 新增工具函数
functico creaneSpinnnr(x, y) {
coesc spinnertedocument.creteElemen('div');
spinner.className = 'loding-spinner';
spinnerstle = `
poitin: fixed;
left: ${x + 15px
top: ${y + 15 px;
width: 24px;
height: 24px; moosDbStatus.innerHTML = `<i class="fas fa-database"></i><span>已连接到MOOS数据库:${data.moos_db_status.host}:${data.moos_db_status.port}</span>`;
border: 3px sol d rgba 97, 218, 251, 0.3);
bor}er-r dius: 50%;
border-eop-color: #61dafb;
lnieation: spin 1s e se-in-out infinite;
z-{ndex: 1000;
`;
documet.body.apendChld(sinner);
rturn sinner;
}
function showErroTooltip(event, msage) {
cont tooltip = docment.createElement('div');
tooltip.className = 'erro-tooltip';
tooltip.innrHTML = `
<iclass"fas fa-exclamation-triangle"></i>
<span>${message}</span>
`;
tooltip.style `
position: fixed;
left: ${event.clientX + 20}px;
top: ${event.clientY + 20}px;
padding: 8px 12px;
backgrod: rgba(255, 87, 87, 0.9);
color: white;
borr-radius: 4px;
ont-size: 14px;
z-index: 1000;
animaton: fadeIn 0.3s ease;
`;
documet.body.appndChil(tooltip);
setTimeout(() => tooltip.remove(,3000);
}
moosDbStatus.className = 'db-status disconnected';
moosDbStatus.innerHTML = `<i class="fas fa-database"></i><span>MOOS数据库连接失败</span>`;
}
}
// 更新状态机面板
document.getElementById('state-time').textContent = data.state.time;
document.getElementById('state-id').textContent = data.state.state_id;
document.getElementById('state-name').textContent = data.state.state_name;
document.getElementById('state-event').textContent = data.state.event;
document.getElementById('state-event-id').textContent = data.state.event_id;
document.getElementById('state-fault-code').textContent = data.state.fault_code;
document.getElementById('state-note').textContent = data.state.note;
// 更新DIS系统状态
// document.getElementById('dis-sys-state').textContent = JSON.stringify(data.status.disSys, null, 2);
// 更新CCU状态
// document.getElementById('ccu-state').textContent = JSON.stringify(data.status.ccuState, null, 2);
// 更新进程消息面板
console.log('Received WebSocket message:', data);
if (data && data.processes && data.processes.list) {
console.log('Updating processes:', data.processes);
updateProcesses(data.processes);
} else {
console.warn('Invalid processes data received:', data);
}
// 更新运行日志
document.getElementById('log-data').textContent = data.status.logs.join('\n');
}
// MOOS通知功能
function sendMoosNotify() {
const varValue = document.getElementById('moos-var').value.trim();
const keyValue = document.getElementById('moos-key').value.trim();
if (!varValue || !keyValue) {
alert('请填写Var和Key值');
return;
}
const payload = {
var: varValue,
key: keyValue
};
fetch('/api/moos-notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('发送成功');
} else {
alert('发送失败: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('发送请求失败');
});
}
document.getElementById('moos-send-btn').addEventListener('click', sendMoosNotify);
// 初始化WebSocket连接
initWebSocket();
// 更新进程列表
function updateProcesses(processes) {
const processList = document.getElementById('process-list');
processList.innerHTML = ''; // 清空现有内容
const totalCount = document.getElementById('process-count');
totalCount.textContent = processes.count;
// 创建消息详情弹窗
const modal = document.createElement('div');
modal.id = 'message-modal';
modal.className = 'modal';
modal.innerHTML = `
<div class="modal-content">
<span class="close-modal">&times;</span>
<h3>消息详情</h3>
<div class="message-details">
<div class="detail-row">
<label>消息名称:</label>
<span id="message-name"></span>
</div>
<div class="detail-row">
<label>消息类型:</label>
<span id="message-type"></span>
</div>
<div class="detail-row">
<label>最后更新时间:</label>
<span id="message-timestamp"></span>
</div>
<div class="detail-row">
<label>消息内容:</label>
<pre id="message-content"></pre>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// 添加弹窗关闭事件
modal.querySelector('.close-modal').addEventListener('click', () => {
modal.style.display = 'none';
});
processes.list.forEach(process => {
const processElement = document.createElement('div');
processElement.className = 'process-card';
const header = document.createElement('div');
header.className = 'process-header';
header.innerHTML = `
<span class="process-name">${process.name}</span>
<span class="topic-count">订阅:${process.subscribed.length} 发布:${process.published.length}</span>
`;
const body = document.createElement('div');
body.className = 'process-body';
body.innerHTML = `
<div class="topic-group">
<h4>订阅主题</h4>
<ul class="subscribed-list">${process.subscribed.map(t => `<li class="message-item" data-type="subscribed">${t}</li>`).join('')}</ul>
</div>
<div class="topic-group">
<h4>发布主题</h4>
<ul class="published-list">${process.published.map(t => `<li class="message-item" data-type="published">${t}</li>`).join('')}</ul>
</div>
`;
body.querySelectorAll('.message-item').forEach(item => {
let timeoutId;
item.addEventListener('mouseenter', function(event) {
timeoutId = setTimeout(async () => {
const spinner = createSpinner(event.clientX, event.clientY);
try {
const messageName = item.textContent; // 新增获取消息名称
const messageType = item.dataset.type; // 新增获取消息类型
const response = await fetch(`/api/message-details?name=${encodeURIComponent(messageName)}&type=${messageType}`);
const data = await response.json();
if (data.success) {
// 根据C++的CVar结构映射字段
document.getElementById('message-name').textContent = data.name || 'N/A';
document.getElementById('message-type').textContent = data.type || '未知类型';
document.getElementById('message-timestamp').textContent = new Date(data.time * 1000).toLocaleString(); // 时间戳转换
document.getElementById('message-content').textContent = JSON.stringify({
value: data.value,
source: data.source
}, null, 2);
modal.style.display = 'block';
} else {
showErrorTooltip(event, `获取失败: ${data.message || '未知错误'}`);
}
} catch (error) {
showErrorTooltip(event, '网络连接异常,请检查后重试');
console.error('获取消息详情失败:', error);
} finally {
spinner.remove();
}
}, 300);
});
item.addEventListener('mouseleave', () => {
clearTimeout(timeoutId);
modal.style.display = 'none';
});
});
processElement.appendChild(header);
processElement.appendChild(body);
processList.appendChild(processElement);
});
}
// 渲染单个进程信息
function renderProcess(process) {
const template = document.getElementById('process-template');
const clone = document.importNode(template.content, true);
const processEntry = clone.querySelector('.process-entry');
processEntry.querySelector('.process-name').textContent = process.name;
const subscribedList = processEntry.querySelector('.subscribed-list');
process.subscribed.forEach(topic => {
const li = document.createElement('li');
li.textContent = topic;
subscribedList.appendChild(li);
});
const publishedList = processEntry.querySelector('.published-list');
process.published.forEach(topic => {
const li = document.createElement('li');
li.textContent = topic;
publishedList.appendChild(li);
});
return processEntry;
}
// 清空进程列表
function clearProcessList() {
const processLog = document.getElementById('log-data');
while (processLog.firstChild && processLog.firstChild.tagName !== 'TEMPLATE') {
processLog.removeChild(processLog.firstChild);
}
}
// 新增工具函数
function createSpinner(x, y) {
const spinner = document.createElement('div');
spinner.className = 'loading-spinner';
spinner.style = `
position: fixed;
left: ${x + 15}px;
top: ${y + 15}px;
width: 24px;
height: 24px;
border: 3px solid rgba(97, 218, 251, 0.3);
border-radius: 50%;
border-top-color: #61dafb;
animation: spin 1s ease-in-out infinite;
z-index: 1000;
`;
document.body.appendChild(spinner);
return spinner;
}
function showErrorTooltip(event, message) {
const tooltip = document.createElement('div');
tooltip.className = 'error-tooltip';
tooltip.innerHTML = `
<i class="fas fa-exclamation-triangle"></i>
<span>${message}</span>
`;
tooltip.style = `
position: fixed;
left: ${event.clientX + 20}px;
top: ${event.clientY + 20}px;
padding: 8px 12px;
background: rgba(255, 87, 87, 0.9);
color: white;
border-radius: 4px;
font-size: 14px;
z-index: 1000;
animation: fadeIn 0.3s ease;
`;
document.body.appendChild(tooltip);
setTimeout(() => tooltip.remove(), 3000);
}