From a9a1a6cdbe067974c05c60a29eb1866457e53285 Mon Sep 17 00:00:00 2001 From: bramval Date: Sat, 24 Jan 2026 19:28:25 +0100 Subject: [PATCH] Add display deletion endpoint and admin UI tweaks --- app/routes/admin.py | 29 +++++ app/static/favicon.png | Bin 0 -> 6008 bytes app/static/logo.svg | 139 ++++++++++++++++++++++++ app/static/styles.css | 6 + app/templates/admin/company_detail.html | 12 +- app/templates/base.html | 26 +++-- scripts/smoke_test.py | 1 + 7 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 app/static/favicon.png create mode 100644 app/static/logo.svg diff --git a/app/routes/admin.py b/app/routes/admin.py index 561a6a9..409dc12 100644 --- a/app/routes/admin.py +++ b/app/routes/admin.py @@ -498,3 +498,32 @@ def update_display_name(display_id: int): db.session.commit() flash("Display name updated", "success") return redirect(url_for("admin.company_detail", company_id=display.company_id)) + + +@bp.post("/displays//delete") +@login_required +def delete_display(display_id: int): + """Admin: delete a display.""" + + admin_required() + + display = db.session.get(Display, display_id) + if not display: + abort(404) + + company_id = display.company_id + display_name = display.name + + # If FK constraints are enabled, delete in a safe order. + # 1) Unassign playlist + display.assigned_playlist_id = None + + # 2) Delete active sessions for this display + DisplaySession.query.filter_by(display_id=display.id).delete(synchronize_session=False) + + # 3) Delete display + db.session.delete(display) + db.session.commit() + + flash(f"Display '{display_name}' deleted.", "success") + return redirect(url_for("admin.company_detail", company_id=company_id)) diff --git a/app/static/favicon.png b/app/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..91fee84a29d2594abd316f5f7297e8fa04740e4f GIT binary patch literal 6008 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 mMCY7n2etsOiB5GK82=Ah9xaIx+YD&{0000 + + + + + + + + + + + + + + + + + + + + + + + OpenSlide + + diff --git a/app/static/styles.css b/app/static/styles.css index 8514db1..7318433 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -56,6 +56,12 @@ body { letter-spacing: -0.02em; } +.brand-logo { + width: 160px; + height: 45px; + display: block; +} + .navbar-brand { font-weight: 700; } diff --git a/app/templates/admin/company_detail.html b/app/templates/admin/company_detail.html index 4433fe8..17addb3 100644 --- a/app/templates/admin/company_detail.html +++ b/app/templates/admin/company_detail.html @@ -45,7 +45,17 @@ {{ d.token }} {{ d.assigned_playlist.name if d.assigned_playlist else "(none)" }} - Open +
+ Open +
+ +
+
{% else %} diff --git a/app/templates/base.html b/app/templates/base.html index da8cacd..4505579 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,6 +4,8 @@ {{ title or "Signage" }} + + @@ -11,8 +13,14 @@