- Printer: bought_at, last_maintenance_at, decommissioned_at (all Date, nullable)
- PrinterLog: per-printer timestamped text entries (CASCADE delete)
- Migration 0003: adds columns + printer_logs table
- /printers/{id}: detail page showing lifecycle info + scrollable log
- POST /printers/{id}/log: add a log entry via modal form
- Printer names in list are now links to the detail page
- Add Printer modal includes optional purchase date
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
5.2 KiB
Python
145 lines
5.2 KiB
Python
from fastapi import APIRouter, Depends, Form, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from sqlalchemy.orm import Session
|
|
from starlette.status import HTTP_303_SEE_OTHER
|
|
|
|
from app.db import get_db
|
|
from app.models.filament import Filament
|
|
from app.models.print_job import JobStatus, PrintJob
|
|
from app.models.printer import Printer, PrinterStatus
|
|
from app.models.printer_log import PrinterLog
|
|
from app.models.printer_type import PrinterType
|
|
from app.models.pricing import PricingConfig
|
|
from app.models.todo import Todo
|
|
|
|
router = APIRouter()
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
def dashboard(request: Request, db: Session = Depends(get_db)):
|
|
printers = db.query(Printer).order_by(Printer.name).all()
|
|
return templates.TemplateResponse("dashboard.html", {
|
|
"request": request,
|
|
"active": "dashboard",
|
|
"printers": printers,
|
|
"total_printers": len(printers),
|
|
"printing_count": sum(1 for p in printers if p.status == PrinterStatus.printing),
|
|
"active_jobs": db.query(PrintJob).filter(
|
|
PrintJob.status.in_([JobStatus.queued, JobStatus.printing])
|
|
).count(),
|
|
"total_filaments": db.query(Filament).count(),
|
|
"recent_jobs": db.query(PrintJob).order_by(PrintJob.created_at.desc()).limit(5).all(),
|
|
})
|
|
|
|
|
|
@router.get("/printers", response_class=HTMLResponse)
|
|
def printers_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("printers.html", {
|
|
"request": request,
|
|
"active": "printers",
|
|
"printers": db.query(Printer).order_by(Printer.name).all(),
|
|
"printer_types": db.query(PrinterType).order_by(PrinterType.name).all(),
|
|
})
|
|
|
|
|
|
@router.post("/printers")
|
|
def create_printer(
|
|
name: str = Form(...),
|
|
location: str = Form(""),
|
|
type_id: str = Form(""),
|
|
bought_at: str = Form(""),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
from datetime import date
|
|
printer = Printer(
|
|
name=name.strip(),
|
|
location=location.strip() or None,
|
|
type_id=int(type_id) if type_id else None,
|
|
bought_at=date.fromisoformat(bought_at) if bought_at else None,
|
|
)
|
|
db.add(printer)
|
|
db.commit()
|
|
return RedirectResponse(url="/printers", status_code=HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/printers/{printer_id}", response_class=HTMLResponse)
|
|
def printer_detail(printer_id: int, request: Request, db: Session = Depends(get_db)):
|
|
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
|
if not printer:
|
|
from fastapi import HTTPException
|
|
raise HTTPException(status_code=404, detail="Printer not found")
|
|
return templates.TemplateResponse("printer_detail.html", {
|
|
"request": request,
|
|
"active": "printers",
|
|
"printer": printer,
|
|
})
|
|
|
|
|
|
@router.post("/printers/{printer_id}/log")
|
|
def add_printer_log(printer_id: int, message: str = Form(...), db: Session = Depends(get_db)):
|
|
entry = PrinterLog(printer_id=printer_id, message=message.strip())
|
|
db.add(entry)
|
|
db.commit()
|
|
return RedirectResponse(url=f"/printers/{printer_id}", status_code=HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/printer-types", response_class=HTMLResponse)
|
|
def printer_types_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("printer_types.html", {
|
|
"request": request,
|
|
"active": "printers",
|
|
"printer_types": db.query(PrinterType).order_by(PrinterType.name).all(),
|
|
})
|
|
|
|
|
|
@router.post("/printer-types")
|
|
def create_printer_type(
|
|
name: str = Form(...),
|
|
bed_x_mm: int = Form(...),
|
|
bed_y_mm: int = Form(...),
|
|
bed_z_mm: int = Form(...),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
pt = PrinterType(name=name.strip(), bed_x_mm=bed_x_mm, bed_y_mm=bed_y_mm, bed_z_mm=bed_z_mm)
|
|
db.add(pt)
|
|
db.commit()
|
|
return RedirectResponse(url="/printer-types", status_code=HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/jobs", response_class=HTMLResponse)
|
|
def jobs_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("jobs.html", {
|
|
"request": request,
|
|
"active": "jobs",
|
|
"jobs": db.query(PrintJob).order_by(PrintJob.created_at.desc()).all(),
|
|
})
|
|
|
|
|
|
@router.get("/filaments", response_class=HTMLResponse)
|
|
def filaments_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("filaments.html", {
|
|
"request": request,
|
|
"active": "filaments",
|
|
"filaments": db.query(Filament).order_by(Filament.brand, Filament.material).all(),
|
|
})
|
|
|
|
|
|
@router.get("/todos", response_class=HTMLResponse)
|
|
def todos_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("todos.html", {
|
|
"request": request,
|
|
"active": "todos",
|
|
"todos": db.query(Todo).order_by(Todo.done, Todo.created_at.desc()).all(),
|
|
})
|
|
|
|
|
|
@router.get("/pricing", response_class=HTMLResponse)
|
|
def pricing_page(request: Request, db: Session = Depends(get_db)):
|
|
return templates.TemplateResponse("pricing.html", {
|
|
"request": request,
|
|
"active": "pricing",
|
|
"configs": db.query(PricingConfig).order_by(PricingConfig.valid_from.desc()).all(),
|
|
})
|