From c81fd68ea229330e8a66345b3d020da7a1a0067d Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Fri, 26 Dec 2025 15:33:44 -0500 Subject: [PATCH] Jobs dashboard changes. --- packetserver/http/routers/jobs.py | 52 ++++++++++++++++++++- packetserver/http/server.py | 11 +++++ packetserver/http/templates/job_detail.html | 18 +++++-- packetserver/http/templates/jobs.html | 17 ++++--- 4 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packetserver/http/routers/jobs.py b/packetserver/http/routers/jobs.py index 531a105..4c88c04 100644 --- a/packetserver/http/routers/jobs.py +++ b/packetserver/http/routers/jobs.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse -from typing import List, Optional, Union, Tuple +from typing import List, Optional, Union, Tuple, Dict, Any from pydantic import BaseModel from datetime import datetime import logging @@ -30,6 +30,13 @@ class JobDetail(JobSummary): errors: str # base64-encoded artifacts: List[Tuple[str, str]] # list of (filename, base64_data) +class JobCreate(BaseModel): + cmd: Union[str, List[str]] + description: Optional[str] = None + env: Optional[Dict[str, str]] = None + workdir: Optional[str] = None + artifact_paths: Optional[List[str]] = None + @router.get("/jobs", response_model=List[JobSummary]) async def list_user_jobs( db: DbDependency, @@ -139,4 +146,45 @@ async def job_detail_page( "current_user": current_user.username, "job": job } - ) \ No newline at end of file + ) + +@router.post("/jobs", response_model=JobSummary, status_code=201) +async def create_job( + payload: JobCreate, + db: DbDependency, + current_user: HttpUser = Depends(get_current_http_user) +): + username = current_user.username.upper().strip() + + try: + with db.transaction() as conn: + root = conn.root() + + # Queue the job using existing method + new_job = Job.queue( + cmd=payload.cmd, + owner=username, + description=payload.description or "", + env=payload.env or {}, + workdir=payload.workdir or "", + artifact_paths=payload.artifact_paths or [], + db=root # or root, depending on your queue() signature + ) + + logging.info(f"User {username} queued job {new_job.id}: {payload.cmd}") + + # Return summary (reuse the same format as list) + return JobSummary( + id=new_job.id, + cmd=new_job.cmd, + owner=new_job.owner, + created_at=new_job.created_at, + started_at=new_job.started_at, + finished_at=new_job.finished_at, + status=new_job.status.name, + return_code=new_job.return_code + ) + + except Exception as e: + logging.error(f"Job creation failed for {username}: {e}\n{format_exc()}") + raise HTTPException(status_code=500, detail="Failed to queue job") \ No newline at end of file diff --git a/packetserver/http/server.py b/packetserver/http/server.py index 801594a..3b8db06 100644 --- a/packetserver/http/server.py +++ b/packetserver/http/server.py @@ -3,6 +3,7 @@ from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pathlib import Path +import base64 from .database import init_db from .routers import public, profile, messages, send @@ -21,6 +22,16 @@ app = FastAPI( # Define templates EARLY (before importing dashboard) templates = Jinja2Templates(directory=BASE_DIR / "templates") +def b64decode_filter(value: str) -> str: + try: + decoded_bytes = base64.b64decode(value) + # Assume UTF-8 text (common for job output/errors) + return decoded_bytes.decode('utf-8', errors='replace') + except Exception: + return "[Invalid base64 data]" + +templates.env.filters["b64decode"] = b64decode_filter + from datetime import datetime, timezone def timestamp_to_date(ts): diff --git a/packetserver/http/templates/job_detail.html b/packetserver/http/templates/job_detail.html index ebfd4b2..43b02fc 100644 --- a/packetserver/http/templates/job_detail.html +++ b/packetserver/http/templates/job_detail.html @@ -70,11 +70,19 @@
Job Details