mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 08:43:36 +02:00
Add better no-frontend-dir handling (and lint, whoops)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def register_frontend_static_routes(app: FastAPI, frontend_dir: Path) -> bool:
|
||||
"""Register frontend static file routes if a built frontend is available.
|
||||
|
||||
Returns True when routes are registered, False when frontend files are
|
||||
missing/incomplete. Missing frontend files are logged but are not fatal.
|
||||
"""
|
||||
frontend_dir = frontend_dir.resolve()
|
||||
index_file = frontend_dir / "index.html"
|
||||
assets_dir = frontend_dir / "assets"
|
||||
|
||||
if not frontend_dir.exists():
|
||||
logger.error(
|
||||
"Frontend build directory not found at %s. "
|
||||
"Run 'cd frontend && npm run build'. API will continue without frontend routes.",
|
||||
frontend_dir,
|
||||
)
|
||||
return False
|
||||
|
||||
if not frontend_dir.is_dir():
|
||||
logger.error(
|
||||
"Frontend build path is not a directory: %s. "
|
||||
"API will continue without frontend routes.",
|
||||
frontend_dir,
|
||||
)
|
||||
return False
|
||||
|
||||
if not index_file.exists():
|
||||
logger.error(
|
||||
"Frontend index file not found at %s. "
|
||||
"Run 'cd frontend && npm run build'. API will continue without frontend routes.",
|
||||
index_file,
|
||||
)
|
||||
return False
|
||||
|
||||
if assets_dir.exists() and assets_dir.is_dir():
|
||||
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
|
||||
else:
|
||||
logger.warning(
|
||||
"Frontend assets directory missing at %s; /assets files will not be served",
|
||||
assets_dir,
|
||||
)
|
||||
|
||||
@app.get("/")
|
||||
async def serve_index():
|
||||
"""Serve the frontend index.html."""
|
||||
return FileResponse(index_file)
|
||||
|
||||
@app.get("/{path:path}")
|
||||
async def serve_frontend(path: str):
|
||||
"""Serve frontend files, falling back to index.html for SPA routing."""
|
||||
file_path = (frontend_dir / path).resolve()
|
||||
try:
|
||||
file_path.relative_to(frontend_dir)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404, detail="Not found") from None
|
||||
|
||||
if file_path.exists() and file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
|
||||
return FileResponse(index_file)
|
||||
|
||||
logger.info("Serving frontend from %s", frontend_dir)
|
||||
return True
|
||||
+3
-27
@@ -2,13 +2,12 @@ import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.config import setup_logging
|
||||
from app.database import db
|
||||
from app.frontend_static import register_frontend_static_routes
|
||||
from app.radio import radio_manager
|
||||
from app.radio_sync import (
|
||||
stop_message_polling,
|
||||
@@ -88,27 +87,4 @@ app.include_router(ws.router, prefix="/api")
|
||||
|
||||
# Serve frontend static files in production
|
||||
FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist"
|
||||
|
||||
if FRONTEND_DIR.exists():
|
||||
# Serve static assets (JS, CSS, etc.)
|
||||
app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
|
||||
|
||||
# Serve other static files from frontend/dist (like wordlist)
|
||||
@app.get("/{path:path}")
|
||||
async def serve_frontend(path: str):
|
||||
"""Serve frontend files, falling back to index.html for SPA routing."""
|
||||
base_dir = FRONTEND_DIR.resolve()
|
||||
file_path = (FRONTEND_DIR / path).resolve()
|
||||
try:
|
||||
file_path.relative_to(base_dir)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404, detail="Not found") from None
|
||||
if file_path.exists() and file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
# Fall back to index.html for SPA routing
|
||||
return FileResponse(base_dir / "index.html")
|
||||
|
||||
@app.get("/")
|
||||
async def serve_index():
|
||||
"""Serve the frontend index.html."""
|
||||
return FileResponse(FRONTEND_DIR / "index.html")
|
||||
register_frontend_static_routes(app, FRONTEND_DIR)
|
||||
|
||||
+3
-1
@@ -598,7 +598,9 @@ async def sync_recent_contacts_to_radio(force: bool = False) -> dict:
|
||||
break
|
||||
|
||||
if len(selected_contacts) < max_contacts:
|
||||
recent_contacts = await ContactRepository.get_recent_non_repeaters(limit=max_contacts)
|
||||
recent_contacts = await ContactRepository.get_recent_non_repeaters(
|
||||
limit=max_contacts
|
||||
)
|
||||
for contact in recent_contacts:
|
||||
key = contact.public_key.lower()
|
||||
if key in selected_keys:
|
||||
|
||||
+12
-4
@@ -307,13 +307,17 @@ async def request_telemetry(public_key: str, request: TelemetryRequest) -> Telem
|
||||
status = None
|
||||
for attempt in range(1, 4):
|
||||
logger.debug("Status request attempt %d/3", attempt)
|
||||
status = await mc.commands.req_status_sync(contact.public_key, timeout=10, min_timeout=5)
|
||||
status = await mc.commands.req_status_sync(
|
||||
contact.public_key, timeout=10, min_timeout=5
|
||||
)
|
||||
if status:
|
||||
break
|
||||
logger.debug("Status request timeout, retrying...")
|
||||
|
||||
if not status:
|
||||
raise HTTPException(status_code=504, detail="No response from repeater after 3 attempts")
|
||||
raise HTTPException(
|
||||
status_code=504, detail="No response from repeater after 3 attempts"
|
||||
)
|
||||
|
||||
logger.info("Received telemetry from %s: %s", contact.public_key[:12], status)
|
||||
|
||||
@@ -482,7 +486,9 @@ async def send_repeater_command(public_key: str, request: CommandRequest) -> Com
|
||||
send_result = await mc.commands.send_cmd(contact.public_key, request.command)
|
||||
|
||||
if send_result.type == EventType.ERROR:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to send command: {send_result.payload}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to send command: {send_result.payload}"
|
||||
)
|
||||
|
||||
# Wait for response (MESSAGES_WAITING event, then get_msg)
|
||||
try:
|
||||
@@ -503,7 +509,9 @@ async def send_repeater_command(public_key: str, request: CommandRequest) -> Com
|
||||
response_event = await mc.commands.get_msg()
|
||||
|
||||
if response_event.type == EventType.ERROR:
|
||||
return CommandResponse(command=request.command, response=f"(error: {response_event.payload})")
|
||||
return CommandResponse(
|
||||
command=request.command, response=f"(error: {response_event.payload})"
|
||||
)
|
||||
|
||||
# Extract the response text and timestamp from the payload
|
||||
response_text = response_event.payload.get("text", str(response_event.payload))
|
||||
|
||||
Reference in New Issue
Block a user