Add file download for job models
- GET /files/{filename}: serves file from STL_UPLOAD_DIR with path-traversal guard
- Download button in File & Geometry card header (always visible when file attached)
- Download button also appears at the top of the Start Job modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, Form, Request, UploadFile
|
from fastapi import APIRouter, Depends, File, Form, Request, UploadFile
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy import case, or_
|
from sqlalchemy import case, or_
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -297,6 +297,18 @@ def edit_job(
|
|||||||
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/files/{filename}")
|
||||||
|
def download_file(filename: str):
|
||||||
|
from fastapi import HTTPException
|
||||||
|
stl_dir = get_stl_dir()
|
||||||
|
path = (stl_dir / filename).resolve()
|
||||||
|
if not str(path).startswith(str(stl_dir.resolve())):
|
||||||
|
raise HTTPException(400, "Invalid filename")
|
||||||
|
if not path.exists():
|
||||||
|
raise HTTPException(404, "File not found")
|
||||||
|
return FileResponse(path, filename=filename, media_type="application/octet-stream")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jobs/{job_id}/start")
|
@router.post("/jobs/{job_id}/start")
|
||||||
def start_job(job_id: int, printer_id: int = Form(...), db: Session = Depends(get_db)):
|
def start_job(job_id: int, printer_id: int = Form(...), db: Session = Depends(get_db)):
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|||||||
@@ -98,12 +98,19 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header border-secondary d-flex align-items-center justify-content-between" style="border-color:#21262d">
|
<div class="card-header border-secondary d-flex align-items-center justify-content-between" style="border-color:#21262d">
|
||||||
<span class="text-secondary small text-uppercase fw-normal">File & Geometry</span>
|
<span class="text-secondary small text-uppercase fw-normal">File & Geometry</span>
|
||||||
{% if job.file_name and not job.file_name.endswith('.gcode') %}
|
{% if job.file_name %}
|
||||||
<form method="POST" action="/jobs/{{ job.id }}/analyse">
|
<div class="d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-outline-secondary btn-sm py-0 px-2" style="font-size:.75rem">
|
<a href="/files/{{ job.file_name }}" class="btn btn-outline-primary btn-sm py-0 px-2" style="font-size:.75rem" download>
|
||||||
<i class="bi bi-cpu me-1"></i>Compute now
|
<i class="bi bi-download me-1"></i>Download
|
||||||
</button>
|
</a>
|
||||||
</form>
|
{% if not job.file_name.endswith('.gcode') %}
|
||||||
|
<form method="POST" action="/jobs/{{ job.id }}/analyse">
|
||||||
|
<button type="submit" class="btn btn-outline-secondary btn-sm py-0 px-2" style="font-size:.75rem">
|
||||||
|
<i class="bi bi-cpu me-1"></i>Compute now
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -350,6 +357,13 @@
|
|||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
{% if job.file_name %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<a href="/files/{{ job.file_name }}" class="btn btn-outline-primary btn-sm w-100" download>
|
||||||
|
<i class="bi bi-download me-1"></i>Download {{ job.file_name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if available_printers %}
|
{% if available_printers %}
|
||||||
<label class="form-label text-secondary small">Select printer <span class="text-danger">*</span></label>
|
<label class="form-label text-secondary small">Select printer <span class="text-danger">*</span></label>
|
||||||
<select name="printer_id" class="form-select bg-dark border-secondary text-white" required>
|
<select name="printer_id" class="form-select bg-dark border-secondary text-white" required>
|
||||||
|
|||||||
Reference in New Issue
Block a user