SLM return to project button added.
This commit is contained in:
@@ -7,7 +7,7 @@ from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Dict
|
||||
from typing import List, Dict, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Configure logging
|
||||
@@ -158,11 +158,24 @@ async def sound_level_meters_page(request: Request):
|
||||
|
||||
|
||||
@app.get("/slm/{unit_id}", response_class=HTMLResponse)
|
||||
async def slm_legacy_dashboard(request: Request, unit_id: str):
|
||||
async def slm_legacy_dashboard(
|
||||
request: Request,
|
||||
unit_id: str,
|
||||
from_project: Optional[str] = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Legacy SLM control center dashboard for a specific unit"""
|
||||
# Get project details if from_project is provided
|
||||
project = None
|
||||
if from_project:
|
||||
from backend.models import Project
|
||||
project = db.query(Project).filter_by(id=from_project).first()
|
||||
|
||||
return templates.TemplateResponse("slm_legacy_dashboard.html", {
|
||||
"request": request,
|
||||
"unit_id": unit_id
|
||||
"unit_id": unit_id,
|
||||
"from_project": from_project,
|
||||
"project": project
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
|
||||
from backend.database import get_db
|
||||
from backend.models import (
|
||||
@@ -31,6 +32,7 @@ from backend.models import (
|
||||
|
||||
router = APIRouter(prefix="/api/projects", tags=["projects"])
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -565,6 +567,183 @@ async def get_project_files(
|
||||
})
|
||||
|
||||
|
||||
@router.get("/{project_id}/ftp-browser", response_class=HTMLResponse)
|
||||
async def get_ftp_browser(
|
||||
project_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get FTP browser interface for downloading files from assigned SLMs.
|
||||
Returns HTML partial with FTP browser.
|
||||
"""
|
||||
from backend.models import DataFile
|
||||
|
||||
# Get all assignments for this project
|
||||
assignments = db.query(UnitAssignment).filter(
|
||||
and_(
|
||||
UnitAssignment.project_id == project_id,
|
||||
UnitAssignment.status == "active",
|
||||
)
|
||||
).all()
|
||||
|
||||
# Enrich with unit and location details
|
||||
units_data = []
|
||||
for assignment in assignments:
|
||||
unit = db.query(RosterUnit).filter_by(id=assignment.unit_id).first()
|
||||
location = db.query(MonitoringLocation).filter_by(id=assignment.location_id).first()
|
||||
|
||||
# Only include SLM units
|
||||
if unit and unit.device_type == "sound_level_meter":
|
||||
units_data.append({
|
||||
"assignment": assignment,
|
||||
"unit": unit,
|
||||
"location": location,
|
||||
})
|
||||
|
||||
return templates.TemplateResponse("partials/projects/ftp_browser.html", {
|
||||
"request": request,
|
||||
"project_id": project_id,
|
||||
"units": units_data,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/{project_id}/ftp-download-to-server")
|
||||
async def ftp_download_to_server(
|
||||
project_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Download a file from an SLM to the server via FTP.
|
||||
Creates a DataFile record and stores the file in data/Projects/{project_id}/
|
||||
"""
|
||||
import httpx
|
||||
import os
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from backend.models import DataFile
|
||||
|
||||
data = await request.json()
|
||||
unit_id = data.get("unit_id")
|
||||
remote_path = data.get("remote_path")
|
||||
location_id = data.get("location_id")
|
||||
|
||||
if not unit_id or not remote_path:
|
||||
raise HTTPException(status_code=400, detail="Missing unit_id or remote_path")
|
||||
|
||||
# Get or create active session for this location/unit
|
||||
session = db.query(RecordingSession).filter(
|
||||
and_(
|
||||
RecordingSession.project_id == project_id,
|
||||
RecordingSession.location_id == location_id,
|
||||
RecordingSession.unit_id == unit_id,
|
||||
RecordingSession.status.in_(["recording", "paused"])
|
||||
)
|
||||
).first()
|
||||
|
||||
# If no active session, create one
|
||||
if not session:
|
||||
session = RecordingSession(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=project_id,
|
||||
location_id=location_id,
|
||||
unit_id=unit_id,
|
||||
status="completed",
|
||||
started_at=datetime.utcnow(),
|
||||
stopped_at=datetime.utcnow(),
|
||||
notes="Auto-created for FTP download"
|
||||
)
|
||||
db.add(session)
|
||||
db.commit()
|
||||
db.refresh(session)
|
||||
|
||||
# Download file from SLMM
|
||||
SLMM_BASE_URL = os.getenv("SLMM_BASE_URL", "http://localhost:8100")
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=300.0) as client:
|
||||
response = await client.post(
|
||||
f"{SLMM_BASE_URL}/api/nl43/{unit_id}/ftp/download",
|
||||
json={"remote_path": remote_path}
|
||||
)
|
||||
|
||||
if not response.is_success:
|
||||
raise HTTPException(
|
||||
status_code=response.status_code,
|
||||
detail=f"Failed to download from SLMM: {response.text}"
|
||||
)
|
||||
|
||||
# Extract filename from remote_path
|
||||
filename = os.path.basename(remote_path)
|
||||
|
||||
# Determine file type from extension
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
file_type_map = {
|
||||
'.wav': 'audio',
|
||||
'.mp3': 'audio',
|
||||
'.csv': 'data',
|
||||
'.txt': 'data',
|
||||
'.log': 'log',
|
||||
'.json': 'data',
|
||||
}
|
||||
file_type = file_type_map.get(ext, 'data')
|
||||
|
||||
# Create directory structure: data/Projects/{project_id}/{session_id}/
|
||||
project_dir = Path(f"data/Projects/{project_id}/{session.id}")
|
||||
project_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Save file to disk
|
||||
file_path = project_dir / filename
|
||||
file_content = response.content
|
||||
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(file_content)
|
||||
|
||||
# Calculate checksum
|
||||
checksum = hashlib.sha256(file_content).hexdigest()
|
||||
|
||||
# Create DataFile record
|
||||
data_file = DataFile(
|
||||
id=str(uuid.uuid4()),
|
||||
session_id=session.id,
|
||||
file_path=str(file_path.relative_to("data")), # Store relative to data/
|
||||
file_type=file_type,
|
||||
file_size_bytes=len(file_content),
|
||||
downloaded_at=datetime.utcnow(),
|
||||
checksum=checksum,
|
||||
file_metadata=json.dumps({
|
||||
"source": "ftp",
|
||||
"remote_path": remote_path,
|
||||
"unit_id": unit_id,
|
||||
"location_id": location_id,
|
||||
})
|
||||
)
|
||||
|
||||
db.add(data_file)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Downloaded {filename} to server",
|
||||
"file_id": data_file.id,
|
||||
"file_path": str(file_path),
|
||||
"file_size": len(file_content),
|
||||
}
|
||||
|
||||
except httpx.TimeoutException:
|
||||
raise HTTPException(
|
||||
status_code=504,
|
||||
detail="Timeout downloading file from SLM"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading file to server: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to download file to server: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Project Types
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user