131 lines
4.1 KiB
Python
131 lines
4.1 KiB
Python
"""YakPanel - Crontab API"""
|
|
import json
|
|
import tempfile
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from pydantic import BaseModel
|
|
|
|
from app.core.database import get_db
|
|
from app.core.utils import exec_shell_sync
|
|
from app.api.auth import get_current_user
|
|
from app.models.user import User
|
|
from app.models.crontab import Crontab
|
|
|
|
router = APIRouter(prefix="/crontab", tags=["crontab"])
|
|
|
|
_CRON_TEMPLATES = Path(__file__).resolve().parent.parent / "data" / "cron_templates.json"
|
|
|
|
|
|
@router.get("/templates")
|
|
async def crontab_templates(current_user: User = Depends(get_current_user)):
|
|
"""YakPanel starter cron templates (edit before apply; no external branding)."""
|
|
if not _CRON_TEMPLATES.is_file():
|
|
return {"templates": []}
|
|
try:
|
|
data = json.loads(_CRON_TEMPLATES.read_text(encoding="utf-8"))
|
|
return {"templates": data if isinstance(data, list) else []}
|
|
except (json.JSONDecodeError, OSError):
|
|
return {"templates": []}
|
|
|
|
|
|
class CreateCrontabRequest(BaseModel):
|
|
name: str = ""
|
|
type: str = "shell"
|
|
schedule: str
|
|
execstr: str
|
|
|
|
|
|
@router.get("/list")
|
|
async def crontab_list(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""List cron jobs"""
|
|
result = await db.execute(select(Crontab).order_by(Crontab.id))
|
|
rows = result.scalars().all()
|
|
return [{"id": r.id, "name": r.name, "type": r.type, "schedule": r.schedule, "execstr": r.execstr} for r in rows]
|
|
|
|
|
|
@router.post("/create")
|
|
async def crontab_create(
|
|
body: CreateCrontabRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Create cron job"""
|
|
cron = Crontab(name=body.name, type=body.type, schedule=body.schedule, execstr=body.execstr)
|
|
db.add(cron)
|
|
await db.commit()
|
|
return {"status": True, "msg": "Cron job created", "id": cron.id}
|
|
|
|
|
|
@router.post("/apply")
|
|
async def crontab_apply(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Sync panel cron jobs to system crontab (root)"""
|
|
result = await db.execute(select(Crontab).order_by(Crontab.id))
|
|
rows = result.scalars().all()
|
|
lines = [
|
|
"# YakPanel managed crontab - do not edit manually",
|
|
"",
|
|
]
|
|
for r in rows:
|
|
if r.name:
|
|
lines.append(f"# {r.name}")
|
|
lines.append(f"{r.schedule} {r.execstr}")
|
|
lines.append("")
|
|
content = "\n".join(lines).strip() + "\n"
|
|
fd, path = tempfile.mkstemp(suffix=".crontab", prefix="cit_")
|
|
try:
|
|
os.write(fd, content.encode("utf-8"))
|
|
os.close(fd)
|
|
out, err = exec_shell_sync(f"crontab {path}", timeout=10)
|
|
if err and "error" in err.lower():
|
|
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
|
finally:
|
|
if os.path.exists(path):
|
|
os.unlink(path)
|
|
return {"status": True, "msg": "Crontab applied", "count": len(rows)}
|
|
|
|
|
|
@router.put("/{cron_id}")
|
|
async def crontab_update(
|
|
cron_id: int,
|
|
body: CreateCrontabRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Update cron job"""
|
|
result = await db.execute(select(Crontab).where(Crontab.id == cron_id))
|
|
cron = result.scalar_one_or_none()
|
|
if not cron:
|
|
raise HTTPException(status_code=404, detail="Cron job not found")
|
|
cron.name = body.name
|
|
cron.type = body.type
|
|
cron.schedule = body.schedule
|
|
cron.execstr = body.execstr
|
|
await db.commit()
|
|
return {"status": True, "msg": "Cron job updated"}
|
|
|
|
|
|
@router.delete("/{cron_id}")
|
|
async def crontab_delete(
|
|
cron_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Delete cron job"""
|
|
result = await db.execute(select(Crontab).where(Crontab.id == cron_id))
|
|
cron = result.scalar_one_or_none()
|
|
if not cron:
|
|
raise HTTPException(status_code=404, detail="Cron job not found")
|
|
await db.delete(cron)
|
|
await db.commit()
|
|
return {"status": True, "msg": "Cron job deleted"}
|