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 import APIRouter, Depends, HTTPException, Request
from fastapi.responses import HTMLResponse 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 pydantic import BaseModel
from datetime import datetime from datetime import datetime
import logging import logging
@@ -30,6 +30,13 @@ class JobDetail(JobSummary):
errors: str # base64-encoded errors: str # base64-encoded
artifacts: List[Tuple[str, str]] # list of (filename, base64_data) 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]) @router.get("/jobs", response_model=List[JobSummary])
async def list_user_jobs( async def list_user_jobs(
db: DbDependency, db: DbDependency,
@@ -140,3 +147,44 @@ async def job_detail_page(
"job": job "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.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from pathlib import Path from pathlib import Path
import base64
from .database import init_db from .database import init_db
from .routers import public, profile, messages, send from .routers import public, profile, messages, send
@@ -21,6 +22,16 @@ app = FastAPI(
# Define templates EARLY (before importing dashboard) # Define templates EARLY (before importing dashboard)
templates = Jinja2Templates(directory=BASE_DIR / "templates") 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 from datetime import datetime, timezone
def timestamp_to_date(ts): def timestamp_to_date(ts):

View File

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

View File

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