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();