62 lines
3.5 KiB
JavaScript
62 lines
3.5 KiB
JavaScript
|
|
import {api, el, note, heading, errorBox, pageContext, bytes, date} from './ui.js';
|
||
|
|
import {scriptList, scriptDetail} from './scripts.js';
|
||
|
|
import {runList, runDetail} from './runs.js';
|
||
|
|
|
||
|
|
const main = document.querySelector('#main');
|
||
|
|
let context;
|
||
|
|
let summaryBusy = false;
|
||
|
|
|
||
|
|
async function summary() {
|
||
|
|
if (summaryBusy || document.visibilityState !== 'visible') return;
|
||
|
|
summaryBusy = true;
|
||
|
|
const health = document.querySelector('#worker-status');
|
||
|
|
const notice = document.querySelector('#global-notice');
|
||
|
|
try {
|
||
|
|
const data = await api('/api/summary');
|
||
|
|
document.querySelector('#nav-count').textContent = data.scripts;
|
||
|
|
document.querySelector('#summary').replaceChildren(...[
|
||
|
|
['测试项目', data.scripts, '按测试行为统计', ''], ['可运行', data.ready, '业务测试已就绪', 'accent'],
|
||
|
|
['进行中', data.active, '排队 / 执行', ''], ['运行记录', data.runs, `归档 ${bytes(data.storage.run_bytes)}`, '']
|
||
|
|
].map(([label, value, hint, style]) => el('div', {class: `stat ${style}`}, el('span', {class: 'stat-label'}, label), el('strong', {class: 'stat-value'}, value), el('span', {class: 'stat-foot'}, hint))));
|
||
|
|
health.replaceChildren(el('span', {class: `worker-light ${data.worker.online ? '' : 'offline'}`, 'aria-hidden': 'true'}), data.worker.online ? '执行器在线 · 单任务队列' : '执行器离线');
|
||
|
|
health.title = `最近心跳:${date(data.worker.last_seen)}`;
|
||
|
|
notice.hidden = data.worker.online;
|
||
|
|
notice.textContent = data.worker.online ? '' : `执行器当前离线,排队任务需要等待执行器恢复。最近心跳:${date(data.worker.last_seen)}。`;
|
||
|
|
} catch (error) {
|
||
|
|
health.replaceChildren(el('span', {class: 'worker-light offline', 'aria-hidden': 'true'}), '连接状态未知');
|
||
|
|
notice.hidden = false;
|
||
|
|
notice.textContent = `工作台状态更新失败:${error.message} 显示的数据可能已过期。`;
|
||
|
|
} finally { summaryBusy = false; }
|
||
|
|
}
|
||
|
|
|
||
|
|
async function route() {
|
||
|
|
context?.dispose();
|
||
|
|
context = pageContext(main);
|
||
|
|
main.setAttribute('aria-busy', 'true');
|
||
|
|
const value = location.hash.slice(1) || '/scripts';
|
||
|
|
const [path, query = ''] = value.split('?');
|
||
|
|
const segments = path.split('/').filter(Boolean);
|
||
|
|
const current = segments[0] || 'scripts';
|
||
|
|
document.querySelectorAll('[data-nav]').forEach(nav => {
|
||
|
|
if (nav.dataset.nav === current) nav.setAttribute('aria-current', 'page');
|
||
|
|
else nav.removeAttribute('aria-current');
|
||
|
|
});
|
||
|
|
document.title = `${current === 'runs' ? '运行记录' : '测试项目'} · XAS 测试控制台`;
|
||
|
|
const ctx = context;
|
||
|
|
summary();
|
||
|
|
try {
|
||
|
|
if (segments.length > 2) throw new Error('页面地址无效。');
|
||
|
|
const id = segments[1] ? decodeURIComponent(segments[1]) : null;
|
||
|
|
if (current === 'scripts') await (id ? scriptDetail(ctx, id) : scriptList(ctx));
|
||
|
|
else if (current === 'runs') await (id ? runDetail(ctx, id) : runList(ctx, new URLSearchParams(query).get('script_id') || ''));
|
||
|
|
else ctx.show(heading('找不到此页面', '请选择左侧的测试项目或运行记录。'), note('页面地址不存在。'));
|
||
|
|
} catch (error) { if (ctx.alive) ctx.show(errorBox(error, route)); }
|
||
|
|
}
|
||
|
|
|
||
|
|
document.querySelector('.skip-link').addEventListener('click', event => { event.preventDefault(); main.focus(); });
|
||
|
|
window.addEventListener('hashchange', route);
|
||
|
|
window.addEventListener('pagehide', () => context?.dispose());
|
||
|
|
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') summary(); });
|
||
|
|
setInterval(summary, 15000);
|
||
|
|
route();
|