diff --git a/web/api.py b/web/api.py index 2de2ff0..1e11d9f 100644 --- a/web/api.py +++ b/web/api.py @@ -154,6 +154,22 @@ async def api_logs(limit: int = Query(default=100, ge=1, le=300)): return JSONResponse(_log_buffer[-limit:]) +@app.post("/api/restart") +async def api_restart(): + """重启服务:返回响应后退出当前进程,由 Docker restart 策略拉起。""" + import os + import signal + + logging.getLogger("spam_guard").warning("🔄 Web 管理端请求重启服务") + + async def delayed_exit(): + await asyncio.sleep(1) + os.kill(os.getpid(), signal.SIGTERM) + + asyncio.create_task(delayed_exit()) + return JSONResponse({"ok": True, "message": "服务正在重启"}) + + @app.post("/api/send") async def api_send(request: Request): import config diff --git a/web/static/index.html b/web/static/index.html index 51caeab..ae744b8 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -184,6 +184,7 @@
{{ sseConnected ? '实时连接' : '连接中...' }} + 重启
@@ -294,6 +295,7 @@ new Vue({ logs: [], newMessage: '', sending: false, + restarting: false, stats: { today_bans: 0, total_bans: 0, active_bans: 0 }, } }, @@ -375,6 +377,28 @@ new Vue({ async loadStats() { try { this.stats = await (await fetch('/api/stats')).json() } catch (e) {} }, + async restartService() { + this.$confirm('确认重启 TG Spam Guard 服务?重启期间 Web 和 Bot 会短暂不可用。', '重启服务', { + confirmButtonText: '确认重启', + cancelButtonText: '取消', + type: 'warning' + }).then(async () => { + this.restarting = true + try { + const data = await (await fetch('/api/restart', { method: 'POST' })).json() + if (data.ok) { + this.$message.success('✅ 服务正在重启,请稍等刷新') + setTimeout(() => location.reload(), 8000) + } else { + this.$message.error(`❌ 重启失败: ${data.error || '未知错误'}`) + this.restarting = false + } + } catch (e) { + this.$message.warning('服务可能已开始重启,请稍等刷新') + setTimeout(() => location.reload(), 8000) + } + }).catch(() => {}) + }, async loadLogs() { try { this.logs = await (await fetch('/api/logs')).json()