feat: 补齐第一批已知缺口(关闭握手 / 定时备份 / Origin 白名单 / DTO 收敛)

- httpapi:新增 POST /api/shutdown(admin 会话或 X-Shutdown-Token 常量时间比对)
  与 GET /api/rules/switches(recorder 可读,只回布尔开关);settings/standings 的
  重复 DTO 收敛到 internal/dto;standings.showIndividualMedals 贯通到设置读写与前端
- export:抽出 export.Service,新增自动备份定时任务(backup.intervalMinutes/keep),
  自动、手动、发布前三类备份共用同一把互斥锁与滚动清理
- realtime:/ws 去掉 InsecureSkipVerify,改用 OriginPatterns(同源与无 Origin 放行,
  跨站需命中 server.allowedOrigins)
- console:停止服务改走 POST /api/shutdown 优雅关闭(5 秒宽限后回退强杀),
  服务管理器新增「已连接的大屏实例」卡片
- deploy:build.bat 改用 for /f 读内部署名——set /p 在 chcp 65001 下返回空,
  导致最近这次构建的 exe 里根本没注入署名
- docs:备份策略、关闭超时、Origin 配置的说明与实现对齐
This commit is contained in:
AskaEth committed 2026-09-09 21:41:08 +08:00
1 parent 4d56c73ae6
commit 2c9ff65fc4
42 files changed
+1271 -275

No files matched your search

+44
View File
@@ -13,6 +13,8 @@ const el = {
config: document.getElementById('k-config'),
configWarning: document.getElementById('config-warning'),
displayList: document.getElementById('display-list'),
instanceList: document.getElementById('instance-list'),
instancesSummary: document.getElementById('instances-summary'),
log: document.getElementById('log'),
autoscroll: document.getElementById('autoscroll'),
btnStart: document.getElementById('btn-start'),
@@ -94,6 +96,45 @@ function renderDisplays(displays) {
}
}
function renderInstances(payload) {
el.instanceList.textContent = ''
if (!payload || !payload.ok) {
el.instancesSummary.textContent = ''
const li = document.createElement('li')
li.className = 'muted'
li.textContent = (payload && payload.message) || '暂时无法读取大屏实例'
el.instanceList.append(li)
return
}
const list = payload.instances || []
el.instancesSummary.textContent = `共 ${list.length} 个 · ${list.filter((item) => item.online).length} 在线`
if (!list.length) {
const li = document.createElement('li')
li.className = 'muted'
li.textContent = '当前没有大屏实例连接'
el.instanceList.append(li)
return
}
for (const item of list) {
const li = document.createElement('li')
const page = document.createElement('span')
page.className = 'page'
page.textContent = item.page || '未知页面'
const id = document.createElement('span')
id.className = 'url'
id.textContent = item.id || '—'
const state = document.createElement('span')
state.className = `state ${item.online ? 'online' : 'offline'}`
state.textContent = item.online ? '在线' : '离线'
li.append(page, id, state)
el.instanceList.append(li)
}
}
function appendLog(line) {
const atBottom = el.log.scrollTop + el.log.clientHeight >= el.log.scrollHeight - 8
el.log.textContent += `${line}\n`
@@ -233,8 +274,11 @@ async function boot() {
window.consoleApi.onStatus(renderStatus)
window.consoleApi.onLog(appendLog)
window.consoleApi.onAccountOutput(handleAccountOutput)
window.consoleApi.onDisplayInstances(renderInstances)
setLog(await window.consoleApi.getLog())
await refresh()
// 主进程只在窗口可见时轮询;这里补一次,保证窗口刚打开就有数据
renderInstances(await window.consoleApi.getDisplayInstances())
}
boot()