152 lines
5.3 KiB
JavaScript
152 lines
5.3 KiB
JavaScript
'use strict'
|
|
|
|
const el = {
|
|
version: document.getElementById('app-version'),
|
|
stateDot: document.getElementById('state-dot'),
|
|
stateLabel: document.getElementById('state-label'),
|
|
stateDetail: document.getElementById('state-detail'),
|
|
url: document.getElementById('k-url'),
|
|
pid: document.getElementById('k-pid'),
|
|
restarts: document.getElementById('k-restarts'),
|
|
power: document.getElementById('k-power'),
|
|
exe: document.getElementById('k-exe'),
|
|
config: document.getElementById('k-config'),
|
|
configWarning: document.getElementById('config-warning'),
|
|
displayList: document.getElementById('display-list'),
|
|
log: document.getElementById('log'),
|
|
autoscroll: document.getElementById('autoscroll'),
|
|
btnStart: document.getElementById('btn-start'),
|
|
btnStop: document.getElementById('btn-stop'),
|
|
btnRestart: document.getElementById('btn-restart'),
|
|
btnRelayout: document.getElementById('btn-relayout'),
|
|
btnOpenAdmin: document.getElementById('btn-open-admin'),
|
|
btnClearLog: document.getElementById('btn-clear-log'),
|
|
titlebar: document.getElementById('titlebar'),
|
|
winMin: document.getElementById('win-min'),
|
|
winMax: document.getElementById('win-max'),
|
|
winClose: document.getElementById('win-close')
|
|
}
|
|
|
|
const DOT_CLASS = {
|
|
running: 'ok',
|
|
starting: 'warn',
|
|
stopping: 'warn',
|
|
crashed: 'err',
|
|
stopped: ''
|
|
}
|
|
|
|
function renderStatus(status) {
|
|
el.version.textContent = status.appVersion ? `v${status.appVersion}` : ''
|
|
|
|
el.stateDot.className = `dot ${status.healthy ? 'ok' : DOT_CLASS[status.state] || ''}`
|
|
el.stateLabel.textContent = status.stateLabel || status.state
|
|
el.stateDetail.textContent = status.healthy
|
|
? '健康检查通过'
|
|
: status.lastError || (status.state === 'stopped' ? '服务未运行' : '等待健康检查')
|
|
|
|
el.url.textContent = status.url || '—'
|
|
el.pid.textContent = status.pid || '—'
|
|
el.restarts.textContent = status.restarts ? `${status.restarts} 次` : '0 次'
|
|
el.power.textContent = status.powerSaveBlocked ? '已启用(阻止显示器休眠)' : '未启用'
|
|
el.exe.textContent = status.exePath || '—'
|
|
el.config.textContent = status.configPath || '—'
|
|
|
|
if (status.configWarning) {
|
|
el.configWarning.hidden = false
|
|
el.configWarning.textContent = status.configWarning
|
|
} else {
|
|
el.configWarning.hidden = true
|
|
}
|
|
|
|
const busy = ['starting', 'running', 'stopping'].includes(status.state)
|
|
el.btnStart.disabled = busy
|
|
el.btnStop.disabled = !busy
|
|
el.btnRestart.disabled = !busy
|
|
|
|
renderDisplays(status.displays || [])
|
|
}
|
|
|
|
function renderDisplays(displays) {
|
|
el.displayList.textContent = ''
|
|
if (!displays.length) {
|
|
const li = document.createElement('li')
|
|
li.className = 'muted'
|
|
li.textContent = '尚未打开大屏窗口'
|
|
el.displayList.append(li)
|
|
return
|
|
}
|
|
for (const item of displays) {
|
|
const li = document.createElement('li')
|
|
const page = document.createElement('span')
|
|
page.className = 'page'
|
|
page.textContent = `屏 ${item.displayId} · ${item.page}`
|
|
const url = document.createElement('span')
|
|
url.className = 'url'
|
|
url.textContent = item.url || '(加载中)'
|
|
li.append(page, url)
|
|
el.displayList.append(li)
|
|
}
|
|
}
|
|
|
|
function appendLog(line) {
|
|
const atBottom = el.log.scrollTop + el.log.clientHeight >= el.log.scrollHeight - 8
|
|
el.log.textContent += `${line}\n`
|
|
if (el.autoscroll.checked && atBottom) {
|
|
el.log.scrollTop = el.log.scrollHeight
|
|
}
|
|
}
|
|
|
|
function setLog(lines) {
|
|
el.log.textContent = lines.length ? `${lines.join('\n')}\n` : ''
|
|
el.log.scrollTop = el.log.scrollHeight
|
|
}
|
|
|
|
async function refresh() {
|
|
renderStatus(await window.consoleApi.getStatus())
|
|
}
|
|
|
|
function renderMaximized(maximized) {
|
|
el.winMax.classList.toggle('is-maximized', maximized)
|
|
const label = maximized ? '还原' : '最大化'
|
|
el.winMax.title = label
|
|
el.winMax.setAttribute('aria-label', label)
|
|
}
|
|
|
|
function bindWindowControls() {
|
|
el.winMin.addEventListener('click', () => window.consoleApi.minimize())
|
|
el.winMax.addEventListener('click', () => window.consoleApi.maximize())
|
|
el.winClose.addEventListener('click', () => window.consoleApi.close())
|
|
|
|
// 双击标题栏切换最大化;点在按钮上的双击不算,否则连点会误触
|
|
el.titlebar.addEventListener('dblclick', (event) => {
|
|
if (event.target instanceof Element && event.target.closest('.win-btn')) return
|
|
window.consoleApi.maximize()
|
|
})
|
|
|
|
window.consoleApi.onMaximizeChange(renderMaximized)
|
|
window.consoleApi.isMaximized().then(renderMaximized)
|
|
}
|
|
|
|
function bindActions() {
|
|
el.btnStart.addEventListener('click', async () => renderStatus(await window.consoleApi.startService()))
|
|
el.btnStop.addEventListener('click', async () => renderStatus(await window.consoleApi.stopService()))
|
|
el.btnRestart.addEventListener('click', async () => renderStatus(await window.consoleApi.restartService()))
|
|
el.btnRelayout.addEventListener('click', async () => renderStatus(await window.consoleApi.relayoutDisplays()))
|
|
el.btnClearLog.addEventListener('click', async () => {
|
|
await window.consoleApi.clearLog()
|
|
setLog([])
|
|
})
|
|
el.btnOpenAdmin.addEventListener('click', () => window.consoleApi.openServerAdmin())
|
|
}
|
|
|
|
async function boot() {
|
|
bindActions()
|
|
bindWindowControls()
|
|
window.consoleApi.onStatus(renderStatus)
|
|
window.consoleApi.onLog(appendLog)
|
|
setLog(await window.consoleApi.getLog())
|
|
await refresh()
|
|
}
|
|
|
|
boot()
|