Jobs dashboard changes.

This commit is contained in:
Michael Woods
2025-12-26 15:33:44 -05:00
parent ac7569833a
commit c81fd68ea2
4 changed files with 84 additions and 14 deletions

View File

@@ -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,
@@ -140,3 +147,44 @@ async def job_detail_page(
"job": job
}
)
@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")

View File

@@ -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):

View File

@@ -70,11 +70,19 @@
<div class="card-header">Job Details</div>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Status:</strong>
{% if job.status == "QUEUED" %}<span class="badge bg-secondary">Queued</span>
{% elif job.status == "RUNNING" %}<span class="badge bg-primary">Running</span>
{% elif job.status == "COMPLETED" %}<span class="badge bg-success">Completed</span>
{% elif job.status == "FAILED" %}<span class="badge bg-danger">Failed</span>
{% elif job.status == "CANCELLED" %}<span class="badge bg-warning">Cancelled</span>
{% set status = job.status | upper %}
{% if status == "QUEUED" %}
<span class="badge bg-secondary">Queued</span>
{% elif status == "RUNNING" %}
<span class="badge bg-primary">Running</span>
{% elif status == "COMPLETED" %}
<span class="badge bg-success">Completed</span>
{% elif status == "FAILED" %}
<span class="badge bg-danger">Failed</span>
{% elif status == "CANCELLED" %}
<span class="badge bg-warning">Cancelled</span>
{% else %}
<span class="badge bg-light text-dark">{{ job.status }}</span>
{% endif %}
</li>
<li class="list-group-item"><strong>Owner:</strong> {{ job.owner }}</li>

View File

@@ -25,23 +25,26 @@
<td>
<code>
{% if job.cmd is string %}
{{ job.cmd[:80] }}{% if job.cmd|length > 80 %}...{% endif %}
{{ job.cmd | truncate(80, True, '...') }}
{% else %}
{{ job.cmd|join(' ')[:80] }}{% if job.cmd|join(' ')|length > 80 %}...{% endif %}
{{ job.cmd | join(' ') | truncate(80, True, '...') }}
{% endif %}
</code>
</td>
<td>
{% if job.status == "QUEUED" %}
{% set status = job.status | upper %}
{% if status == "QUEUED" %}
<span class="badge bg-secondary">Queued</span>
{% elif job.status == "RUNNING" %}
{% elif status == "RUNNING" %}
<span class="badge bg-primary">Running</span>
{% elif job.status == "COMPLETED" %}
{% elif status == "COMPLETED" %}
<span class="badge bg-success">Completed</span>
{% elif job.status == "FAILED" %}
{% elif status == "FAILED" %}
<span class="badge bg-danger">Failed</span>
{% elif job.status == "CANCELLED" %}
{% elif status == "CANCELLED" %}
<span class="badge bg-warning">Cancelled</span>
{% else %}
<span class="badge bg-light text-dark">{{ job.status }}</span>
{% endif %}
</td>
<td>{{ job.created_at.strftime('%b %d, %Y %H:%M') }}</td>