diff --git a/app/api/routers/service.py b/app/api/routers/service.py
index 70c0a3e..350c879 100644
--- a/app/api/routers/service.py
+++ b/app/api/routers/service.py
@@ -1,13 +1,14 @@
import os
import platform
-import subprocess
import time
from threading import Thread
from fastapi import APIRouter
from app.services.runtime import (
+ is_container_runtime,
is_supervisor_running,
+ restart_method_name,
restart_via_supervisor,
terminate_current_process,
)
@@ -18,38 +19,35 @@ router = APIRouter(tags=["service"])
@router.post("/api/service/restart")
async def restart_service():
- if is_supervisor_running():
+ supervisor_running = is_supervisor_running()
+ container_runtime = is_container_runtime()
+
+ if supervisor_running:
success, message = restart_via_supervisor()
- return {"success": success, "message": message, "method": "supervisor"}
-
- if platform.system() != "Windows":
- try:
- result = subprocess.run(
- ["supervisorctl", "restart", "fastapi"],
- capture_output=True,
- text=True,
- timeout=30,
- )
- if result.returncode == 0:
- return {
- "success": True,
- "message": "服务正在通过 supervisor 重启...",
- "method": "supervisor",
- }
- except FileNotFoundError:
- pass
- except subprocess.TimeoutExpired:
+ if success:
return {
- "success": False,
- "message": "重启操作超时,请检查 supervisor 状态",
+ "success": True,
+ "message": message,
"method": "supervisor",
+ "container": container_runtime,
}
- except Exception:
- pass
+ method = restart_method_name(supervisor_running)
thread = Thread(target=_delayed_exit, daemon=True)
thread.start()
- return {"success": True, "message": "服务正在重启,请稍后刷新页面...", "method": "signal"}
+
+ message = "服务正在重启,请稍后刷新页面..."
+ if supervisor_running:
+ message = "supervisor 命令不可用,已切换为进程重启..."
+ elif container_runtime:
+ message = "服务进程正在退出,容器或进程管理器将拉起服务..."
+
+ return {
+ "success": True,
+ "message": message,
+ "method": method,
+ "container": container_runtime,
+ }
@router.get("/api/service/status")
@@ -59,6 +57,7 @@ async def get_service_status():
"version": "2.0.2",
"platform": platform.system(),
"supervisor": is_supervisor_running(),
+ "container": is_container_runtime(),
"pid": os.getpid(),
"python_version": platform.python_version(),
}
@@ -67,4 +66,3 @@ async def get_service_status():
def _delayed_exit() -> None:
time.sleep(1)
terminate_current_process()
-
diff --git a/app/services/runtime.py b/app/services/runtime.py
index 97393c5..b04bee0 100644
--- a/app/services/runtime.py
+++ b/app/services/runtime.py
@@ -25,6 +25,24 @@ def is_supervisor_running() -> bool:
return _command_succeeds(["pgrep", "-f", "supervisord"], timeout=2)
+def is_container_runtime() -> bool:
+ if os.environ.get("container"):
+ return True
+ if Path("/.dockerenv").exists():
+ return True
+
+ cgroup_path = Path("/proc/1/cgroup")
+ if not cgroup_path.exists():
+ return False
+
+ try:
+ cgroup = cgroup_path.read_text(encoding="utf-8", errors="ignore").lower()
+ except Exception:
+ return False
+
+ return any(marker in cgroup for marker in ("docker", "kubepods", "containerd"))
+
+
def restart_via_supervisor() -> tuple[bool, str]:
try:
result = subprocess.run(
@@ -52,10 +70,19 @@ def terminate_current_process() -> None:
os.kill(os.getpid(), signal.SIGTERM)
+def restart_method_name(supervisor_running: bool) -> str:
+ if supervisor_running:
+ return "supervisor"
+ if platform.system() == "Windows":
+ return "windows-process"
+ if is_container_runtime():
+ return "container-process"
+ return "process"
+
+
def _command_succeeds(command: list[str], timeout: int) -> bool:
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=timeout)
return result.returncode == 0 and bool(result.stdout.strip() or command[0] == "supervisorctl")
except Exception:
return False
-
diff --git a/docs/project_context.md b/docs/project_context.md
index ddfad86..aa213d9 100644
--- a/docs/project_context.md
+++ b/docs/project_context.md
@@ -1,5 +1,13 @@
# 项目上下文记录
+## 2026-05-18:完善服务重启交互和运行时兼容
+
+- `frontend/src/AppShell.vue` 的重启按钮点击后会先弹出确认框,确认后显示全屏“正在重启服务”遮罩和旋转加载动画,避免用户重复操作。
+- 重启请求发出后前端会轮询 `/api/service/status`,服务恢复时自动刷新页面;重启过程中请求中断会被视为正常情况继续等待。
+- `app/api/routers/service.py` 和 `app/services/runtime.py` 统一重启逻辑:优先使用 supervisor 重启,失败时退回进程退出;Windows 本地依赖 `run.bat` 循环拉起,容器环境会识别 Docker/containerd/k8s 并交给 supervisor 或容器重启策略拉起。
+- `/api/service/status` 返回值新增 `container` 字段,用于前端或排障区分容器运行时。
+- 已执行 `.venv\Scripts\python.exe -m compileall app` 和 `npm run build`,均通过;未启动浏览器或 headless Chrome。
+
## 2026-05-18:修正字段映射标题换行
- `frontend/src/styles.css` 调整设置页字段映射卡片头部布局,“字段映射配置”和字段数量标签不再被搜索框挤压换行。
diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue
index 8e6d057..a40b57a 100644
--- a/frontend/src/AppShell.vue
+++ b/frontend/src/AppShell.vue
@@ -63,7 +63,13 @@
-