diff --git a/README.md b/README.md index eaf8b78..3e76cab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](./src/static/logo.svg) +![](./src/static/images/logo.svg) # Lagomare Gates @@ -161,6 +161,16 @@ All settings are read from environment variables (centralised in `src/core/confi |---|---|---| | `DATABASE_URL` | `sqlite:///data/gates.db` | SQLAlchemy database URL. | +### Map (home location) + +| Variable | Default | Description | +|---|---|---| +| `HOME_LAT` | *(none)* | WGS-84 latitude of the home/property marker shown on the frontend map. | +| `HOME_LON` | *(none)* | WGS-84 longitude of the home/property marker. | +| `HOME_NAME` | `Home` | Display name for the home marker popup. | + +If `HOME_LAT` and `HOME_LON` are both set, the map is always visible. If only gates have coordinates (set per-gate in the admin panel), the map is shown only when at least one gate has a location. + ### Network / reverse proxy | Variable | Default | Description | diff --git a/src/core/config.py b/src/core/config.py index 749854f..5d7a0de 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -55,3 +55,9 @@ ADMIN_PASSWORD: Optional[str] = os.environ.get("ADMIN_PASSWORD") or None # ── Server ──────────────────────────────────────────────────────────────────── APP_PORT: int = int(os.environ.get("APP_PORT", 8000)) + +# ── Map / home location ─────────────────────────────────────────────────────── +# Optional WGS-84 coordinates for the "home" marker shown on the frontend map. +HOME_LAT: Optional[float] = float(os.environ["HOME_LAT"]) if os.environ.get("HOME_LAT") else None +HOME_LON: Optional[float] = float(os.environ["HOME_LON"]) if os.environ.get("HOME_LON") else None +HOME_NAME: str = os.environ.get("HOME_NAME", "Home") diff --git a/src/core/database.py b/src/core/database.py index d4bb0a1..dc90389 100644 --- a/src/core/database.py +++ b/src/core/database.py @@ -2,7 +2,7 @@ import os from datetime import datetime from typing import Optional -from sqlalchemy import Boolean, String, Text, create_engine +from sqlalchemy import Boolean, Double, String, Text, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker from core.config import DATA_DIR, DATABASE_URL @@ -24,6 +24,8 @@ class GateDB(Base): avconnect_macro_id: Mapped[str] = mapped_column(String, nullable=False) # AVConnect macro ID status: Mapped[str] = mapped_column(String, default="enabled") # 'enabled' | 'disabled' group_name: Mapped[Optional[str]] = mapped_column(String, nullable=True) # display group label + lat: Mapped[Optional[float]] = mapped_column(Double, nullable=True) # WGS-84 latitude + lon: Mapped[Optional[float]] = mapped_column(Double, nullable=True) # WGS-84 longitude class ApiCredential(Base): diff --git a/src/core/schemas.py b/src/core/schemas.py index e0d9529..616c98a 100644 --- a/src/core/schemas.py +++ b/src/core/schemas.py @@ -84,6 +84,8 @@ class GateResponse(BaseModel): avconnect_macro_id: str status: str group_name: Optional[str] = None + lat: Optional[float] = None + lon: Optional[float] = None class GatePublicResponse(BaseModel): @@ -93,6 +95,8 @@ class GatePublicResponse(BaseModel): name: str gate_type: str group_name: Optional[str] = None + lat: Optional[float] = None + lon: Optional[float] = None class GateCreate(BaseModel): @@ -101,6 +105,8 @@ class GateCreate(BaseModel): avconnect_macro_id: str status: str = "enabled" group_name: Optional[str] = None + lat: Optional[float] = None + lon: Optional[float] = None # ── AVConnect Credentials ───────────────────────────────────────────────────── diff --git a/src/main.py b/src/main.py index 8e16283..e1c8f97 100644 --- a/src/main.py +++ b/src/main.py @@ -14,7 +14,7 @@ from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware # Ensure src/ root is importable for models/services/routers sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from core.config import ADMIN_PASSWORD, ADMIN_USERNAME, APP_PORT, CORS_ORIGINS, LOG_FILE, LOG_LEVEL, TRUSTED_PROXY_IPS +from core.config import ADMIN_PASSWORD, ADMIN_USERNAME, APP_PORT, CORS_ORIGINS, HOME_LAT, HOME_LON, HOME_NAME, LOG_FILE, LOG_LEVEL, TRUSTED_PROXY_IPS # ── Logging ─────────────────────────────────────────────────────────────────── _log_fmt = logging.Formatter( @@ -88,7 +88,8 @@ async def _security_headers(request: Request, call_next) -> Response: response.headers["X-Frame-Options"] = "DENY" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" response.headers["Content-Security-Policy"] = ( - "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:" + "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" + " img-src 'self' data: blob: https://*.tile.openstreetmap.org" ) return response @@ -101,11 +102,21 @@ app.include_router(admins_router) app.include_router(stats_router) app.include_router(telegram_router) + +@app.get("/api/site-config", include_in_schema=False) +async def _site_config(): + """Return public site configuration used by the frontend (e.g. home map coordinates).""" + return { + "home": {"name": HOME_NAME, "lat": HOME_LAT, "lon": HOME_LON} + if HOME_LAT is not None and HOME_LON is not None + else None + } + # ── Static / frontend ───────────────────────────────────────────────────────── @app.get("/favicon.ico", include_in_schema=False) async def _serve_favicon() -> FileResponse: return FileResponse( - os.path.join(_STATIC_DIR, "logo.svg"), media_type="image/svg+xml" + os.path.join(_STATIC_DIR, "images", "logo.svg"), media_type="image/svg+xml" ) diff --git a/src/static/admin.html b/src/static/admin.html index 6804ca2..8811ea0 100644 --- a/src/static/admin.html +++ b/src/static/admin.html @@ -5,7 +5,7 @@ Lagomare Gates - Admin - +
- Lagomare + Lagomare

Lagomare Gates

@@ -125,17 +144,31 @@ + + + @@ -154,7 +187,7 @@