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