Release v2.0.3

This commit is contained in:
Christian 2026-01-28 10:35:02 +01:00
parent 2f022bf085
commit 3543a9b079
5 changed files with 57 additions and 6 deletions

View File

@ -8,7 +8,7 @@
# RELEASE VERSION # RELEASE VERSION
# ===================================================== # =====================================================
# Tag fra Gitea (f.eks. v1.0.0, v1.2.3) # Tag fra Gitea (f.eks. v1.0.0, v1.2.3)
RELEASE_VERSION=v2.0.2 RELEASE_VERSION=v2.0.3
# ===================================================== # =====================================================
# GITEA AUTHENTICATION # GITEA AUTHENTICATION

8
RELEASE_NOTES_v2.0.3.md Normal file
View File

@ -0,0 +1,8 @@
# Release Notes v2.0.3
## Changes
- Allow executing SQL migration files directly from `/settings/migrations`, including user feedback on success/failure.
- Pipe the migration SQL files into the Postgres container so the execution works across Docker and Podman.
---
Release Date: 28. januar 2026

View File

@ -1 +1 @@
2.0.2 2.0.3

View File

@ -7,6 +7,8 @@ from pathlib import Path
from fastapi import APIRouter, Request, HTTPException from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
import shlex
import subprocess import subprocess
from app.core.config import settings from app.core.config import settings
@ -49,11 +51,15 @@ async def migrations_page(request: Request):
}) })
class MigrationExecution(BaseModel):
file_name: str
@router.post("/settings/migrations/execute", tags=["Frontend"]) @router.post("/settings/migrations/execute", tags=["Frontend"])
def execute_migration(file_name: str): def execute_migration(payload: MigrationExecution):
"""Execute a migration SQL file""" """Execute a migration SQL file"""
migrations_dir = Path(__file__).resolve().parents[3] / "migrations" migrations_dir = Path(__file__).resolve().parents[3] / "migrations"
migration_file = migrations_dir / file_name migration_file = migrations_dir / payload.file_name
if not migration_file.exists(): if not migration_file.exists():
raise HTTPException(status_code=404, detail="Migration file not found") raise HTTPException(status_code=404, detail="Migration file not found")
@ -62,7 +68,11 @@ def execute_migration(file_name: str):
container_runtime = "podman" if settings.CONTAINER_RUNTIME == "podman" else "docker" container_runtime = "podman" if settings.CONTAINER_RUNTIME == "podman" else "docker"
# Execute the migration file # Execute the migration file
command = f"{container_runtime} exec bmc-hub-postgres psql -U {settings.POSTGRES_USER} -d {settings.POSTGRES_DB} -f {migration_file}" command = (
f"cat {shlex.quote(str(migration_file))} | "
f"{container_runtime} exec -i bmc-hub-postgres "
f"psql -U {settings.POSTGRES_USER} -d {settings.POSTGRES_DB}"
)
result = subprocess.run(command, shell=True, capture_output=True, text=True) result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0: if result.returncode != 0:

View File

@ -67,10 +67,13 @@
</td> </td>
<td>{{ migration.size_kb }} KB</td> <td>{{ migration.size_kb }} KB</td>
<td>{{ migration.modified }}</td> <td>{{ migration.modified }}</td>
<td class="text-end"> <td class="text-end d-flex gap-2 justify-content-end">
<button class="btn btn-sm btn-outline-primary" onclick="showCommand('{{ migration.name }}')"> <button class="btn btn-sm btn-outline-primary" onclick="showCommand('{{ migration.name }}')">
<i class="bi bi-terminal me-1"></i>Vis kommando <i class="bi bi-terminal me-1"></i>Vis kommando
</button> </button>
<button class="btn btn-sm btn-success" onclick="runMigration('{{ migration.name }}', this)">
<i class="bi bi-play-circle me-1"></i>Kør migration
</button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -103,6 +106,7 @@
<i class="bi bi-clipboard-plus me-2"></i>Kopiér alt <i class="bi bi-clipboard-plus me-2"></i>Kopiér alt
</button> </button>
</div> </div>
<div id="migrationFeedback" class="alert d-none mt-3" role="alert"></div>
</div> </div>
</div> </div>
@ -148,5 +152,34 @@
if (!currentAltCommand) return; if (!currentAltCommand) return;
await navigator.clipboard.writeText(currentAltCommand); await navigator.clipboard.writeText(currentAltCommand);
} }
async function runMigration(migrationName, button) {
const feedback = document.getElementById('migrationFeedback');
const url = '/settings/migrations/execute';
button.disabled = true;
feedback.className = 'alert alert-info mt-3';
feedback.textContent = 'Kører migration...';
feedback.classList.remove('d-none');
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_name: migrationName })
});
const data = await response.json();
if (!response.ok) throw new Error(data.detail || data.message);
feedback.className = 'alert alert-success mt-3';
feedback.innerHTML = `<strong>Migration kørt</strong><br><pre class="mb-0">${data.output}</pre>`;
} catch (error) {
feedback.className = 'alert alert-danger mt-3';
feedback.innerHTML = `<strong>Fejl</strong><br>${error.message}`;
} finally {
button.disabled = false;
}
}
</script> </script>
{% endblock %} {% endblock %}