From 076293f80ddd5ce109a7843fdf06f57e3dcf1dc2 Mon Sep 17 00:00:00 2001 From: cato447 Date: Sat, 21 Feb 2026 23:09:22 +0100 Subject: [PATCH] refactored templates --- app/blueprints/admin/routes.py | 9 + app/templates/admin.html | 349 +++++++++++------------------ app/templates/admin_base.html | 54 +++++ app/templates/admin_login.html | 166 +++++++------- app/templates/banner.html | 146 +++++------- app/templates/base.html | 24 ++ app/templates/centered_layout.html | 13 ++ app/templates/confused.html | 141 +++++------- app/templates/dashboard.html | 14 ++ app/templates/index.html | 181 +++++++-------- app/templates/macros/ui.html | 133 +++++++++++ app/templates/register.html | 91 +++----- app/templates/set_token.html | 67 ++---- docker-compose.yml | 5 +- 14 files changed, 694 insertions(+), 699 deletions(-) create mode 100644 app/templates/admin_base.html create mode 100644 app/templates/base.html create mode 100644 app/templates/centered_layout.html create mode 100644 app/templates/dashboard.html create mode 100644 app/templates/macros/ui.html diff --git a/app/blueprints/admin/routes.py b/app/blueprints/admin/routes.py index 58de4be..2f7803e 100644 --- a/app/blueprints/admin/routes.py +++ b/app/blueprints/admin/routes.py @@ -195,3 +195,12 @@ def hide_active_banner(): @flask_login.login_required def confused_notifications(): return render_template("confused.html") + + +@admin_bp.route("/dashboard") +@flask_login.login_required +def dashboard(): + return render_template( + 'dashboard.html', + grafana_url='/dashboard/d/tutor-tool' + ) diff --git a/app/templates/admin.html b/app/templates/admin.html index a52663d..e11220c 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -1,229 +1,144 @@ - - - - - - - Deck Admin +{% extends "admin_base.html" %} +{% from "macros/ui.html" import course_switcher, delete_button, styled_field %} - - +{% set active_page = 'admin' %} +{% set title = 'Deck Admin' %} +{# Override left nav to include course switcher #} +{% block nav_left_extra %} + {{ course_switcher(courses, active_course_name, '/admin/switch_course') }} +{% endblock %} - - - -
- -
- - -
-

Add New Deck

-
- {{ deck_form.hidden_tag() }} - -
- - {{ deck_form.name(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- - {{ deck_form.deck(multiple=True, class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- - {{ deck_form.description(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- {{ deck_form.submit_deck(class="w-full bg-primary hover:bg-blue-700 text-white py-2 px-4 rounded-lg") }} -
-
- - -

Existing Decks

- {% if decks %} -
- - - - - - - - - - {% for deck in decks %} - - - - - - {% endfor %} - -
NamePasswordsAction
{{ deck.name }} - {% if deck.passwords %} -
    - {% for password in deck.passwords %} -
  • {{ password.value }}
  • - {% endfor %} -
- {% else %} - None - {% endif %} -
-
- -
-
-
- {% else %} -

No decks available.

- {% endif %} -
- - -
-

Add New Password

-
- {{ password_form.hidden_tag() }} - -
- - {{ password_form.value(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- - {{ password_form.expires_at(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary", type="datetime-local") }} -
- -
- - {{ password_form.decks(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- {{ password_form.submit_password(class="w-full bg-primary hover:bg-blue-700 text-white py-2 px-4 rounded-lg") }} -
-
- - -

Existing Passwords

- {% if passwords %} -
- - - - - - - - - - - {% for password in passwords %} - - - - - - - {% endfor %} - -
PasswordExpiresDecksAction
{{ password.value }} - {{ password.expires_at }} - - {% if password.decks %} -
    - {% for deck in password.decks %} -
  • {{ deck.name }}
  • - {% endfor %} -
- {% else %} - None - {% endif %} -
-
- -
-
-
- {% else %} -

No passwords available.

- {% endif %} -
- + {% else %} +

No decks available.

+ {% endif %}
-
- - - - - + }); + +{% endblock %} diff --git a/app/templates/admin_base.html b/app/templates/admin_base.html new file mode 100644 index 0000000..cfd10b4 --- /dev/null +++ b/app/templates/admin_base.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{# + Admin base layout. + Child templates can set `active_page` to one of: 'admin', 'banner', 'confused' + to highlight the correct nav link. +#} + +{% block body %} + {% block admin_nav %} + + {% endblock admin_nav %} + + {% block admin_content %}{% endblock %} +{% endblock body %} diff --git a/app/templates/admin_login.html b/app/templates/admin_login.html index 43cec8f..51812ed 100644 --- a/app/templates/admin_login.html +++ b/app/templates/admin_login.html @@ -1,101 +1,85 @@ - - - - - Login - - - - - +{% extends "base.html" %} +{% from "macros/ui.html" import flash_messages %} -
-

Admin Login

+{% set title = 'Admin Login' %} - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} -
- {{ message }} -
- {% endfor %} -
- {% endif %} - {% endwith %} +{% block body_class %}bg-gray-900 text-gray-100 min-h-screen flex items-center justify-center{% endblock %} -
- {{ form.hidden_tag() }} +{% block body %} +
+

Admin Login

- -
- - {{ form.user_name(class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500") }} - {% for error in form.user_name.errors %} -

{{ error }}

- {% endfor %} -
+ {{ flash_messages() }} - -
- -
- {{ form.password( - class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500", - id="password" - ) }} - -
- {% for error in form.password.errors %} -

{{ error }}

- {% endfor %} -
+ + {{ form.hidden_tag() }} - -
- -
- + {# Username #} +
+ + {{ form.user_name(class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500") }} + {% for error in form.user_name.errors %} +

{{ error }}

+ {% endfor %}
- - + {# Password with visibility toggle #} +
+ +
+ {{ form.password( + class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500", + id="password" + ) }} + +
+ {% for error in form.password.errors %} +

{{ error }}

+ {% endfor %} +
- - + + +
+{% endblock %} +{% block scripts %}{% endblock %}{# No Flowbite needed #} + +{% block scripts_extra %} + +{% endblock %} diff --git a/app/templates/banner.html b/app/templates/banner.html index 4f81567..ad7a014 100644 --- a/app/templates/banner.html +++ b/app/templates/banner.html @@ -1,105 +1,59 @@ - - - - - - Banner +{% extends "admin_base.html" %} +{% from "macros/ui.html" import styled_field %} - +{% set active_page = 'banner' %} +{% set title = 'Banner' %} - +{% block admin_content %} +
- - - - - + - -
- - -
-
-
-

Enter new Banner

-
- {{ banner_form.hidden_tag() }} -
- - {{ banner_form.content(class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary") }} -
- -
- {% if error %} -

{{ error }}

- {% endif %} -
+ {% else %} +
+ +
+ {% endif %}
-
+ {% endfor %} +
+ + {% else %} +

No banners created yet.

+ {% endif %} - - {% if banners %} -
-
- {% for banner in banners %} -
-

{{ banner.content }}

- {% if banner.is_active %} -
- -
- {% else %} -
- -
- {% endif %} -
- {% endfor %} -
-
- {% else %} -

No banners created yet.

- {% endif %} - - - - - - + +{% endblock admin_content %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..da9a2e7 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,24 @@ + + + + + + + {% block title %}Tutortool{% endblock %} + + + + + {% block head_extra %}{% endblock %} + + + + {% block body %}{% endblock %} + + {% block scripts %} + + {% endblock %} + + {% block scripts_extra %}{% endblock %} + + diff --git a/app/templates/centered_layout.html b/app/templates/centered_layout.html new file mode 100644 index 0000000..feb22a1 --- /dev/null +++ b/app/templates/centered_layout.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{# Centered card layout used by login, register, and set_token pages. #} + +{% block body_class %}flex items-center justify-center min-h-screen bg-background{% endblock %} + +{% block body %} +
+ {% block card_content %}{% endblock %} +
+{% endblock %} + +{% block scripts %}{% endblock %}{# No Flowbite needed on auth pages #} diff --git a/app/templates/confused.html b/app/templates/confused.html index 400af4b..3c77f88 100644 --- a/app/templates/confused.html +++ b/app/templates/confused.html @@ -1,99 +1,60 @@ - - - - - Live Confusion Alerts - - +{% extends "admin_base.html" %} + +{% set active_page = 'confused' %} +{% set title = 'Live Confusion Alerts' %} + +{% block head_extra %} - - - + +{% endblock admin_content %} +{% block scripts %}{% endblock %}{# No Flowbite needed #} -
-

Live Confusion Alerts

-
-

Waiting for students to click “I’m confused…”

-
-
+{% block scripts_extra %} + - - + const btn = document.createElement("button"); + btn.className = "ml-4 text-red-600 hover:text-red-900 focus:outline-none"; + btn.setAttribute("aria-label", "Dismiss"); + btn.innerHTML = "×"; + btn.onclick = () => box.remove(); + box.appendChild(btn); + notifList.prepend(box); + setTimeout(() => box.remove(), 20000); + }); + +{% endblock %} diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html new file mode 100644 index 0000000..1ff0ace --- /dev/null +++ b/app/templates/dashboard.html @@ -0,0 +1,14 @@ +{% extends "admin_base.html" %} + +{% set active_page = 'dashboard' %} +{% set title = 'Dashboard' %} + +{% block admin_content %} +
+ +
+{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html index 77ef4c8..283b8f5 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,60 +1,57 @@ - - - - - - Tutorial Materials - - +{% extends "base.html" %} +{% from "macros/ui.html" import course_switcher %} - - +{% set title = 'Tutorial Materials' %} + +{% block head_extra %} - - +{% endblock %} +{% block body %} - - - + {# ── Header ────────────────────────────────────────────────────── #}
-
+
-
- - User: {{ user_name }} + {# Left: user + logout #} +
+ + {{ user_name }} -
- +
-
-
- - - - - - + {# Center: course switcher #} +
+ {{ course_switcher(courses, active_course_name, '/switch_course') }} +
+ + {# Right: social links + admin #} +
-
+ + {# ── Main Content ──────────────────────────────────────────────── #}
- + {% if active_banner %}
{% endif %} -
-
- - -
-
- - + {# ── Password Form ─────────────────────────────────────────── #}
@@ -100,68 +75,66 @@ autofocus class="w-full px-4 py-3 rounded-md bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary" /> - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} - {% if category == 'error' %} -

{{ message }}

- {% elif category == 'expired' %} -

{{ message }}

- {% else %} -

{{ message }}

- {% endif %} - {% endfor %} -
- {% endif %} + {% if messages %} +
+ {% for category, message in messages %} + {% if category == 'error' %} +

{{ message }}

+ {% elif category == 'expired' %} +

{{ message }}

+ {% else %} +

{{ message }}

+ {% endif %} + {% endfor %} +
+ {% endif %} {% endwith %}
- + {# ── Deck Cards ────────────────────────────────────────────── #} {% if decks %}
-
+
{% for deck in decks %}
- -
- - -

{{ deck.name }}

-

{{ deck.description }}

-
- {% if deck.export_file %} - - - - - - {% endif %} -
- +
+ + +

{{ deck.name }}

+

{{ deck.description }}

+
+ + {% if deck.export_file %} +
+ + + + + + + {% endif %} + +
{% endfor %} -
+
{% else %}

No decks unlocked yet.

{% endif %}
- - - - - +{% endblock body %} diff --git a/app/templates/macros/ui.html b/app/templates/macros/ui.html new file mode 100644 index 0000000..79db418 --- /dev/null +++ b/app/templates/macros/ui.html @@ -0,0 +1,133 @@ +{# + Reusable UI macros for Tutortool templates. + Usage: {% from "macros/ui.html" import flash_messages, styled_field, text_input %} +#} + + +{# Renders all flashed messages with colour-coded styling. #} +{% macro flash_messages() %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} + {% if category == 'error' %} + {% set alert_class = 'bg-red-900 border-red-400 text-red-100' %} + {% elif category == 'expired' %} + {% set alert_class = 'bg-orange-900 border-orange-400 text-orange-100' %} + {% else %} + {% set alert_class = 'bg-green-900 border-green-400 text-green-100' %} + {% endif %} + + {% endfor %} +
+ {% endif %} + {% endwith %} +{% endmacro %} + + +{# + Renders a WTForms field with a label and standard Tailwind styling. + Pass extra_kwargs as a dict for additional HTML attributes (e.g. type, multiple). +#} +{% macro styled_field(field, extra_kwargs={}) %} +
+ + {{ field( + class="w-full rounded-lg p-3 bg-background border border-border text-text placeholder-muted focus:outline-none focus:ring-2 focus:ring-primary", + **extra_kwargs + ) }} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+{% endmacro %} + + +{# + Renders a plain (not WTForms) with standard styling. + `type` defaults to 'text'. +#} +{% macro text_input(name, label, placeholder='', type='text', required=true, autofocus=false) %} +
+ + +
+{% endmacro %} + + +{# Renders a delete button wrapped in a form. #} +{% macro delete_button(action_url, label='Delete') %} +
+ +
+{% endmacro %} + + +{# Course-switcher dropdown (used in admin and index pages). #} +{% macro course_switcher(courses, active_course_name, switch_url_prefix) %} +
+ + + +
+ + +{% endmacro %} diff --git a/app/templates/register.html b/app/templates/register.html index 02e7482..60bda3c 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -1,61 +1,42 @@ - - - - - - Register for Tutortool - - - - -
-
-

Create an Account

-

Choose a username to get started.

-
+{% extends "centered_layout.html" %} +{% from "macros/ui.html" import flash_messages, text_input %} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} - {% set alert_class = 'bg-red-900 border-red-400 text-red-100' if category == 'error' else 'bg-green-900 border-green-400 text-green-100' %} - - {% endfor %} -
- {% endif %} - {% endwith %} +{% set title = 'Register – Tutortool' %} -
- -
- - -
+{% block card_content %} +
+

Create an Account

+

Choose a username to get started.

+
-
-

Important Information

-

- Your username is tied to the slides using a browser cookie. If you clear your cookies, you can regain access to your slides by logging in with the same username. -

-
- - -
-
- - -
+ {{ flash_messages() }} -

- Already have an account? Log In -

+
+ {{ text_input('username', 'Username', placeholder='Your unique username') }} + +
+

Important Information

+

+ Your username is tied to the slides using a browser cookie. If you clear your cookies, + you can regain access to your slides by logging in with the same username. +

+
+ + +
- - + +
+ +

+ Already have an account? + Log In +

+{% endblock %} diff --git a/app/templates/set_token.html b/app/templates/set_token.html index 3d2b81f..0833c82 100644 --- a/app/templates/set_token.html +++ b/app/templates/set_token.html @@ -1,47 +1,26 @@ - - - - - - Register for Tutortool - - - - -
-
-

Login

-
+{% extends "centered_layout.html" %} +{% from "macros/ui.html" import flash_messages, text_input %} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} - {% set alert_class = 'bg-red-900 border-red-400 text-red-100' if category == 'error' else 'bg-green-900 border-green-400 text-green-100' %} - - {% endfor %} -
- {% endif %} - {% endwith %} - -
- -
- - -
- - -
+{% set title = 'Login – Tutortool' %} -

- Do not have an Account? Create one -

-
- - +{% block card_content %} +
+

Login

+
+ {{ flash_messages() }} + +
+ {{ text_input('username', 'Username', placeholder='Your unique username') }} + + +
+ +

+ Do not have an Account? + Create one +

+{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index 29b375f..0d4a7ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,8 +69,9 @@ services: - ./grafana/provisioning:/etc/grafana/provisioning environment: - GF_USERS_ALLOW_SIGN_UP=false - # Only allow access from localhost/reverse proxy - - GF_SERVER_ROOT_URL=https://tutor.cato447.de/dashboard + - GF_SERVER_ROOT_URL=https://tutor.cato447.de/dashboard/ + - GF_SERVER_SERVE_FROM_SUB_PATH=true + - GF_SECURITY_ALLOW_EMBEDDING=true depends_on: - prometheus - db