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:
1 parent
4d56c73ae6
commit
2c9ff65fc4
42 files changed
+1271
-275
No files matched your search
@@ -15,6 +15,7 @@ const { loadConfig, resolveExecutable } = require('./config')
|
||||
const { LogBus } = require('./log-bus')
|
||||
const { ServiceSupervisor } = require('./service')
|
||||
const { DisplayManager } = require('./displays')
|
||||
const { fetchDisplayInstances } = require('./instances')
|
||||
const { createTrayIcon } = require('./tray-icon')
|
||||
const { validateAccountField, buildPasswdArgs, runPasswd } = require('./account')
|
||||
|
||||
@@ -39,6 +40,9 @@ function main() {
|
||||
let quitting = false
|
||||
let shutdownDone = false
|
||||
let exePath = null
|
||||
// 大屏实例看板只在管理窗口可见时轮询,窗口隐藏即停表
|
||||
let instancesTimer = null
|
||||
let instancesLastReason = null
|
||||
// passwd 子进程同一时刻只允许一个:并发写同一个 sqlite 库没有意义,也会让输出互相串
|
||||
let accountBusy = false
|
||||
|
||||
@@ -118,7 +122,12 @@ function main() {
|
||||
log.push('console', '管理窗口已最小化到托盘(服务端继续运行)')
|
||||
})
|
||||
|
||||
// 实例看板只在窗口可见时轮询,隐藏/关闭即停表
|
||||
adminWindow.on('show', startInstancesPolling)
|
||||
adminWindow.on('hide', stopInstancesPolling)
|
||||
|
||||
adminWindow.on('closed', () => {
|
||||
stopInstancesPolling()
|
||||
adminWindow = null
|
||||
})
|
||||
}
|
||||
@@ -130,6 +139,48 @@ function main() {
|
||||
return `http://${host || '127.0.0.1'}:${cfg.values.server.port}/admin`
|
||||
}
|
||||
|
||||
function serverHost() {
|
||||
if (service) return service.healthHost()
|
||||
const host = cfg.values.server.host || '127.0.0.1'
|
||||
return host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host
|
||||
}
|
||||
|
||||
// 大屏实例看板:读服务端 GET /api/display/instances 并推给管理窗口。
|
||||
// 失败只按状态变化记一次日志,避免未登录/服务未启动时刷屏。
|
||||
async function pollDisplayInstances() {
|
||||
let result
|
||||
try {
|
||||
result = await fetchDisplayInstances({ host: serverHost(), port: cfg.values.server.port })
|
||||
} catch (err) {
|
||||
result = { ok: false, reason: 'error', message: `读取大屏实例失败:${err.message}` }
|
||||
}
|
||||
|
||||
if (result.ok) {
|
||||
if (instancesLastReason) {
|
||||
instancesLastReason = null
|
||||
log.push('console', `大屏实例列表恢复读取(${result.instances.length} 个实例)`)
|
||||
}
|
||||
} else if (result.reason !== instancesLastReason) {
|
||||
instancesLastReason = result.reason
|
||||
log.push('console', `读取大屏实例失败:${result.message}`)
|
||||
}
|
||||
|
||||
sendToAdmin('display:instances', result)
|
||||
return result
|
||||
}
|
||||
|
||||
function startInstancesPolling() {
|
||||
if (instancesTimer) return
|
||||
pollDisplayInstances()
|
||||
instancesTimer = setInterval(pollDisplayInstances, 5000)
|
||||
}
|
||||
|
||||
function stopInstancesPolling() {
|
||||
if (!instancesTimer) return
|
||||
clearInterval(instancesTimer)
|
||||
instancesTimer = null
|
||||
}
|
||||
|
||||
function showServerAdmin() {
|
||||
if (!serverAdminWindow || serverAdminWindow.isDestroyed()) {
|
||||
createServerAdminWindow()
|
||||
@@ -352,6 +403,7 @@ function main() {
|
||||
showServerAdmin()
|
||||
return true
|
||||
})
|
||||
ipcMain.handle('display:instances-get', () => pollDisplayInstances())
|
||||
|
||||
// 窗口控制统一从 event.sender 反查窗口,多个窗口共用同一套通道
|
||||
ipcMain.handle('window:minimize', (event) => {
|
||||
@@ -476,6 +528,7 @@ function main() {
|
||||
event.preventDefault()
|
||||
|
||||
const finish = () => {
|
||||
stopInstancesPolling()
|
||||
if (displays) displays.closeAll()
|
||||
if (powerBlockerId !== null) powerSaveBlocker.stop(powerBlockerId)
|
||||
if (log) log.close()
|
||||
@@ -491,6 +544,7 @@ function main() {
|
||||
})
|
||||
|
||||
app.on('quit', () => {
|
||||
stopInstancesPolling()
|
||||
if (service) service.dispose()
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user