2025-12-06 11:04:19 +01:00
|
|
|
"""
|
|
|
|
|
Settings Frontend Views
|
|
|
|
|
"""
|
|
|
|
|
|
2026-01-28 08:03:17 +01:00
|
|
|
from datetime import datetime
|
|
|
|
|
from pathlib import Path
|
2026-01-28 10:25:21 +01:00
|
|
|
from fastapi import APIRouter, Request, HTTPException
|
2025-12-06 11:04:19 +01:00
|
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
|
from fastapi.templating import Jinja2Templates
|
2026-01-28 10:35:02 +01:00
|
|
|
from pydantic import BaseModel
|
2026-01-28 10:41:48 +01:00
|
|
|
import os
|
2026-01-28 10:25:21 +01:00
|
|
|
import subprocess
|
2025-12-06 11:04:19 +01:00
|
|
|
|
2026-01-28 08:03:17 +01:00
|
|
|
from app.core.config import settings
|
|
|
|
|
|
2025-12-06 11:04:19 +01:00
|
|
|
router = APIRouter()
|
|
|
|
|
templates = Jinja2Templates(directory="app")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/settings", response_class=HTMLResponse, tags=["Frontend"])
|
|
|
|
|
async def settings_page(request: Request):
|
|
|
|
|
"""Render settings page"""
|
|
|
|
|
return templates.TemplateResponse("settings/frontend/settings.html", {
|
|
|
|
|
"request": request,
|
|
|
|
|
"title": "Indstillinger"
|
|
|
|
|
})
|
2026-01-28 08:03:17 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/settings/migrations", response_class=HTMLResponse, tags=["Frontend"])
|
|
|
|
|
async def migrations_page(request: Request):
|
|
|
|
|
"""Render database migrations page"""
|
|
|
|
|
migrations_dir = Path(__file__).resolve().parents[3] / "migrations"
|
|
|
|
|
migrations = []
|
|
|
|
|
|
|
|
|
|
if migrations_dir.exists():
|
|
|
|
|
for migration_file in sorted(migrations_dir.glob("*.sql")):
|
|
|
|
|
stat = migration_file.stat()
|
|
|
|
|
migrations.append({
|
|
|
|
|
"name": migration_file.name,
|
|
|
|
|
"size_kb": round(stat.st_size / 1024, 1),
|
|
|
|
|
"modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return templates.TemplateResponse("settings/frontend/migrations.html", {
|
|
|
|
|
"request": request,
|
|
|
|
|
"title": "Database Migrationer",
|
|
|
|
|
"migrations": migrations,
|
|
|
|
|
"db_user": settings.POSTGRES_USER,
|
|
|
|
|
"db_name": settings.POSTGRES_DB,
|
|
|
|
|
"db_container": "bmc-hub-postgres"
|
|
|
|
|
})
|
2026-01-28 10:25:21 +01:00
|
|
|
|
|
|
|
|
|
2026-01-28 10:35:02 +01:00
|
|
|
class MigrationExecution(BaseModel):
|
|
|
|
|
file_name: str
|
|
|
|
|
|
|
|
|
|
|
2026-01-28 10:25:21 +01:00
|
|
|
@router.post("/settings/migrations/execute", tags=["Frontend"])
|
2026-01-28 10:35:02 +01:00
|
|
|
def execute_migration(payload: MigrationExecution):
|
2026-01-28 10:25:21 +01:00
|
|
|
"""Execute a migration SQL file"""
|
|
|
|
|
migrations_dir = Path(__file__).resolve().parents[3] / "migrations"
|
2026-01-28 10:35:02 +01:00
|
|
|
migration_file = migrations_dir / payload.file_name
|
2026-01-28 10:25:21 +01:00
|
|
|
|
|
|
|
|
if not migration_file.exists():
|
|
|
|
|
raise HTTPException(status_code=404, detail="Migration file not found")
|
|
|
|
|
|
|
|
|
|
# Determine the container runtime (Podman or Docker)
|
2026-01-28 10:41:48 +01:00
|
|
|
runtime = settings.CONTAINER_RUNTIME.lower()
|
|
|
|
|
if runtime not in {"docker", "podman"}:
|
|
|
|
|
runtime = "docker"
|
|
|
|
|
|
|
|
|
|
command = [
|
|
|
|
|
runtime,
|
|
|
|
|
"exec",
|
|
|
|
|
"-i",
|
|
|
|
|
"bmc-hub-postgres",
|
|
|
|
|
"psql",
|
|
|
|
|
"-U",
|
|
|
|
|
settings.POSTGRES_USER,
|
|
|
|
|
"-d",
|
|
|
|
|
settings.POSTGRES_DB,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
migration_sql = migration_file.read_text()
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
command,
|
|
|
|
|
input=migration_sql,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
env={**os.environ, "PGPASSWORD": settings.POSTGRES_PASSWORD},
|
2026-01-28 10:35:02 +01:00
|
|
|
)
|
2026-01-28 10:25:21 +01:00
|
|
|
|
|
|
|
|
if result.returncode != 0:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Migration failed: {result.stderr}")
|
|
|
|
|
|
|
|
|
|
return {"message": "Migration executed successfully", "output": result.stdout}
|