Release v2.0.3
This commit is contained in:
parent
2f022bf085
commit
3543a9b079
@ -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
8
RELEASE_NOTES_v2.0.3.md
Normal 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
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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 %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user