feat: add admin API endpoints for video management, update frontend to use new API routes

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 18:58:04 +02:00
parent 37edef716a
commit 2ca7ae538f
2 changed files with 39 additions and 10 deletions
+33
View File
@@ -523,6 +523,39 @@ def admin_videos():
return render_template("admin/videos.html") return render_template("admin/videos.html")
# ── Admin API proxies (same-origin for browser JS, avoids mixed-content) ──
@app.get("/api/admin/videos")
@admin_required
def api_admin_list_videos():
resp = _api("GET", "/admin/videos", token=session["access_token"])
return jsonify(resp.json()), resp.status_code
@app.post("/api/admin/videos/<job_id>/retry")
@admin_required
def api_admin_retry_video(job_id: str):
resp = _api(
"POST", f"/admin/videos/{job_id}/retry", token=session["access_token"])
return jsonify(resp.json()), resp.status_code
@app.post("/api/admin/videos/<job_id>/cancel")
@admin_required
def api_admin_cancel_video(job_id: str):
resp = _api(
"POST", f"/admin/videos/{job_id}/cancel", token=session["access_token"])
return jsonify(resp.json()), resp.status_code
@app.delete("/api/admin/videos/<job_id>")
@admin_required
def api_admin_delete_video(job_id: str):
resp = _api(
"DELETE", f"/admin/videos/{job_id}", token=session["access_token"])
return jsonify(resp.json()), resp.status_code
# ── Profile ─────────────────────────────────────────────────────────────── # ── Profile ───────────────────────────────────────────────────────────────
@app.route("/users/profile", methods=["GET", "POST"]) @app.route("/users/profile", methods=["GET", "POST"])
+6 -10
View File
@@ -139,17 +139,13 @@
<script> <script>
(function () { (function () {
const BACKEND = "{{ config['BACKEND_URL'] }}";
const TOKEN = "{{ session['access_token'] }}";
const headers = { Authorization: "Bearer " + TOKEN };
let allJobs = []; let allJobs = [];
async function loadJobs() { async function loadJobs() {
document.getElementById("vj-tbody").innerHTML = document.getElementById("vj-tbody").innerHTML =
'<tr><td colspan="7" class="text-muted">Loading…</td></tr>'; '<tr><td colspan="7" class="text-muted">Loading…</td></tr>';
try { try {
const r = await fetch(BACKEND + "/admin/videos", { headers }); const r = await fetch("/api/admin/videos");
if (!r.ok) throw new Error(await r.text()); if (!r.ok) throw new Error(await r.text());
allJobs = await r.json(); allJobs = await r.json();
renderJobs(); renderJobs();
@@ -233,7 +229,7 @@
} }
async function apiPost(path) { async function apiPost(path) {
const r = await fetch(BACKEND + path, { method: "POST", headers }); const r = await fetch(path, { method: "POST" });
if (!r.ok) { if (!r.ok) {
const d = await r.json().catch(() => ({})); const d = await r.json().catch(() => ({}));
throw new Error(d.detail || r.statusText); throw new Error(d.detail || r.statusText);
@@ -242,7 +238,7 @@
} }
async function apiDelete(path) { async function apiDelete(path) {
const r = await fetch(BACKEND + path, { method: "DELETE", headers }); const r = await fetch(path, { method: "DELETE" });
if (!r.ok) { if (!r.ok) {
const d = await r.json().catch(() => ({})); const d = await r.json().catch(() => ({}));
throw new Error(d.detail || r.statusText); throw new Error(d.detail || r.statusText);
@@ -258,12 +254,12 @@
const id = btn.dataset.id; const id = btn.dataset.id;
try { try {
if (btn.classList.contains("vj-retry")) if (btn.classList.contains("vj-retry"))
await apiPost(`/admin/videos/${id}/retry`); await apiPost(`/api/admin/videos/${id}/retry`);
if (btn.classList.contains("vj-cancel")) if (btn.classList.contains("vj-cancel"))
await apiPost(`/admin/videos/${id}/cancel`); await apiPost(`/api/admin/videos/${id}/cancel`);
if (btn.classList.contains("vj-delete")) { if (btn.classList.contains("vj-delete")) {
if (!confirm("Permanently delete this video job?")) return; if (!confirm("Permanently delete this video job?")) return;
await apiDelete(`/admin/videos/${id}`); await apiDelete(`/api/admin/videos/${id}`);
} }
await loadJobs(); await loadJobs();
} catch (err) { } catch (err) {