Files
2026-04-07 13:23:35 +05:30

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"}