Initial YakPanel commit
This commit is contained in:
136
YakPanel-server/backend/app/api/node.py
Normal file
136
YakPanel-server/backend/app/api/node.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""YakPanel - Node.js / PM2 API"""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.utils import exec_shell_sync
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter(prefix="/node", tags=["node"])
|
||||
|
||||
|
||||
class AddProcessRequest(BaseModel):
|
||||
script: str
|
||||
name: str = ""
|
||||
|
||||
|
||||
@router.get("/processes")
|
||||
async def node_processes(current_user: User = Depends(get_current_user)):
|
||||
"""List PM2 processes (pm2 jlist)"""
|
||||
out, err = exec_shell_sync("pm2 jlist", timeout=10)
|
||||
if err and "not found" in err.lower():
|
||||
return {"processes": [], "error": "PM2 not installed"}
|
||||
try:
|
||||
data = json.loads(out) if out.strip() else []
|
||||
processes = []
|
||||
now_ms = int(time.time() * 1000)
|
||||
for p in data if isinstance(data, list) else []:
|
||||
name = p.get("name", "")
|
||||
pm2_env = p.get("pm2_env", {})
|
||||
start_ms = pm2_env.get("pm_uptime", 0)
|
||||
uptime_ms = (now_ms - start_ms) if start_ms and pm2_env.get("status") == "online" else 0
|
||||
processes.append({
|
||||
"id": p.get("pm_id"),
|
||||
"name": name,
|
||||
"status": pm2_env.get("status", "unknown"),
|
||||
"pid": p.get("pid"),
|
||||
"uptime": uptime_ms,
|
||||
"restarts": pm2_env.get("restart_time", 0),
|
||||
"memory": p.get("monit", {}).get("memory", 0),
|
||||
"cpu": p.get("monit", {}).get("cpu", 0),
|
||||
})
|
||||
return {"processes": processes}
|
||||
except json.JSONDecodeError:
|
||||
return {"processes": [], "error": "Failed to parse PM2 output"}
|
||||
|
||||
|
||||
@router.post("/add")
|
||||
async def node_add(
|
||||
body: AddProcessRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Add and start a new PM2 process (pm2 start script --name name)"""
|
||||
script = (body.script or "").strip()
|
||||
if not script:
|
||||
raise HTTPException(status_code=400, detail="Script path required")
|
||||
if ".." in script or ";" in script or "|" in script or "`" in script:
|
||||
raise HTTPException(status_code=400, detail="Invalid script path")
|
||||
name = (body.name or "").strip()
|
||||
if name and ("'" in name or '"' in name or ";" in name):
|
||||
raise HTTPException(status_code=400, detail="Invalid process name")
|
||||
cmd = f"pm2 start {script}"
|
||||
if name:
|
||||
cmd += f" --name '{name}'"
|
||||
out, err = exec_shell_sync(cmd, timeout=15)
|
||||
if err and "error" in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Process started"}
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
async def node_version(current_user: User = Depends(get_current_user)):
|
||||
"""Get Node.js version"""
|
||||
out, err = exec_shell_sync("node -v", timeout=5)
|
||||
version = out.strip() if out else ""
|
||||
if err and "not found" in err.lower():
|
||||
return {"version": None, "error": "Node.js not installed"}
|
||||
return {"version": version or None}
|
||||
|
||||
|
||||
@router.post("/{proc_id}/start")
|
||||
async def node_start(
|
||||
proc_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Start PM2 process"""
|
||||
if not re.match(r"^\d+$", proc_id):
|
||||
raise HTTPException(status_code=400, detail="Invalid process ID")
|
||||
out, err = exec_shell_sync(f"pm2 start {proc_id}", timeout=15)
|
||||
if err and "error" in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Started"}
|
||||
|
||||
|
||||
@router.post("/{proc_id}/stop")
|
||||
async def node_stop(
|
||||
proc_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Stop PM2 process"""
|
||||
if not re.match(r"^\d+$", proc_id):
|
||||
raise HTTPException(status_code=400, detail="Invalid process ID")
|
||||
out, err = exec_shell_sync(f"pm2 stop {proc_id}", timeout=15)
|
||||
if err and "error" in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Stopped"}
|
||||
|
||||
|
||||
@router.post("/{proc_id}/restart")
|
||||
async def node_restart(
|
||||
proc_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Restart PM2 process"""
|
||||
if not re.match(r"^\d+$", proc_id):
|
||||
raise HTTPException(status_code=400, detail="Invalid process ID")
|
||||
out, err = exec_shell_sync(f"pm2 restart {proc_id}", timeout=15)
|
||||
if err and "error" in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Restarted"}
|
||||
|
||||
|
||||
@router.delete("/{proc_id}")
|
||||
async def node_delete(
|
||||
proc_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Delete PM2 process"""
|
||||
if not re.match(r"^\d+$", proc_id):
|
||||
raise HTTPException(status_code=400, detail="Invalid process ID")
|
||||
out, err = exec_shell_sync(f"pm2 delete {proc_id}", timeout=15)
|
||||
if err and "error" in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Deleted"}
|
||||
Reference in New Issue
Block a user