Add theme handling in setup and login processes, enhance UI theme toggle functionality

This commit is contained in:
Théophile Diot 2024-11-08 12:07:49 +01:00
parent d598cb4276
commit 175cc70b70
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
13 changed files with 138 additions and 19 deletions

View file

@ -41,6 +41,20 @@ def login_page():
flask_flash("Couldn't log you in, please try again", "error")
return (render_template("login.html", error="Couldn't log you in, please try again"),)
ret = DB.update_ui_user(
**{
"username": current_user.get_id(),
"password": current_user.password.encode("utf-8"),
"email": current_user.email,
"totp_secret": current_user.totp_secret,
"method": current_user.method,
"theme": request.form["theme"],
},
old_username=current_user.get_id(),
)
if ret:
LOGGER.error(f"Couldn't update the user {current_user.get_id()}: {ret}")
LOGGER.info(f"User {ui_user.username} logged in successfully" + (" with remember me" if request.form.get("remember-me") == "on" else ""))
if not ui_user.totp_secret:

View file

@ -51,7 +51,7 @@ def setup_page():
if DB.readonly:
return handle_error("Database is in read-only mode", "setup")
required_keys = []
required_keys = ["theme"]
if not ui_reverse_proxy:
required_keys.extend(
[
@ -102,6 +102,7 @@ def setup_page():
gen_password_hash(request.form["admin_password"]),
["admin"],
request.form["admin_email"] or None,
theme=request.form["theme"],
totp_secret=totp_secret,
totp_recovery_codes=totp_recovery_codes,
method="ui",

View file

@ -978,12 +978,6 @@ a.courier-prime:hover {
background-color: #0a4b69;
}
.dark-style .btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
color: #07354a;
}
.dark-style .breadcrumb-item + .breadcrumb-item::before {
color: #9caeb7;
}
@ -1198,3 +1192,26 @@ html[dir="rtl"].dark-style .border-primary {
.dark-style .leaflet-control-zoom-out {
filter: invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%);
}
.dark-style .was-validated .input-group .form-control:invalid:focus,
.input-group .form-control.is-invalid:focus {
border-color: var(--bs-form-invalid-color) !important;
}
.dark-style
.was-validated
.input-group
.form-control:invalid:focus
~ .input-group-text,
.dark-style .input-group .form-control.is-invalid:focus ~ .input-group-text {
border-color: var(--bs-form-invalid-color) !important;
}
.dark-style .was-validated .input-group .form-control:valid:focus,
.dark-style .input-group .form-control.is-valid:focus {
border-color: var(--bs-form-valid-color) !important;
}
.dark-style table.table.dataTable > tbody > tr.selected a {
color: initial !important;
}

View file

@ -0,0 +1,41 @@
$(document).ready(() => {
// If no saved preference, use the system's preferred color scheme
const systemPrefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
savedTheme = systemPrefersDark ? "dark" : "light";
// Apply the preferred theme
applyTheme(savedTheme);
// Toggle theme on change
$("#dark-mode-toggle").on("change", function () {
const darkMode = $(this).prop("checked");
const theme = darkMode ? "dark" : "light";
applyTheme(theme);
localStorage.setItem("theme", theme); // Save user preference
});
// Function to apply the theme
function applyTheme(theme) {
if (theme === "dark") {
$("html").removeClass("light-style").addClass("dark-style");
$(".bs-toast.bg-white").addClass("bg-dark").removeClass("bg-white");
$(".bg-light-subtle")
.addClass("bg-dark-subtle")
.removeClass("bg-light-subtle");
$("#dark-mode-toggle").prop("checked", true);
} else {
$("html").removeClass("dark-style").addClass("light-style");
$(".bs-toast.bg-dark").addClass("bg-white").removeClass("bg-dark");
$(".bg-dark-subtle")
.addClass("bg-light-subtle")
.removeClass("bg-dark-subtle");
$("#dark-mode-toggle").prop("checked", false);
}
// Update input values
$("#theme").val(theme);
$("[name='theme']").val(theme);
}
});

View file

@ -519,6 +519,9 @@ $(document).ready(() => {
// Append the CSRF token
formData.append("csrf_token", $csrfTokenInput.val() || "");
// Append the Theme
formData.append("theme", $("[name='theme']").val());
const server_name = getServerName();
const ui_url = $("#REVERSE_PROXY_URL").val();

View file

@ -424,6 +424,9 @@ $(document).ready(() => {
});
$("#dark-mode-toggle").on("change", function () {
// If endpoint is "setup", ignore the theme change
if (window.location.pathname.includes("/setup")) return;
const darkMode = $(this).prop("checked");
if (darkMode) {
$("html").removeClass("light-style").addClass("dark-style");

View file

@ -200,7 +200,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
class="x-logo"
alt="X logo"
width="20px"
height="20px">
@ -248,7 +248,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
class="x-logo"
alt="X logo"
width="20px"
height="20px">

View file

@ -67,9 +67,9 @@
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.css') }}"
nonce="{{ style_nonce }}" />
{% if current_user.theme == 'dark' %}
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.dark.css') }}"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.dark.css') }}"
nonce="{{ style_nonce }}" />
{% endif %}
{% endif %}
<!-- Core CSS -->
@ -174,6 +174,10 @@
<!-- Main JS -->
<script src="{{ url_for('static', filename='js/main.js') }}"
nonce="{{ script_nonce }}"></script>
{% if current_endpoint in ("setup", "login", "totp", "loading") %}
<script src="{{ url_for('static', filename='js/pages/login.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
{% if current_endpoint not in ("login", "totp") %}
<script async
defer

View file

@ -1,6 +1,25 @@
{% extends "base.html" %}
{% block page %}
<!-- Content -->
<!-- Dark Mode Toggle - Enhanced Floating Button -->
<div class="theme-toggle position-fixed top-0 end-0 p-6"
style="z-index: 1030">
<div class="toggle-container d-flex align-items-center bg-white p-3 rounded-pill shadow-lg">
<label class="setting-checkbox-label pe-2 mb-0 fw-bold text-secondary"
for="dark-mode-toggle">Light</label>
<div class="form-switch">
<input id="dark-mode-toggle"
name="dark-mode-toggle"
class="form-check-input"
type="checkbox"
role="switch"
{% if current_user.theme == "dark" %}checked{% endif %} />
</div>
<label class="setting-checkbox-label mb-0 fw-bold text-secondary"
for="dark-mode-toggle">Dark</label>
</div>
</div>
<!-- /Dark Mode Toggle -->
<div class="bg-light-subtle">
<div class="login-background">
<div class="container-xxl">
@ -23,11 +42,12 @@
</a>
</div>
<!-- /Logo -->
<form class="mb-6" method="POST">
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden"
name="next"
value="{{ request.values.get('next', '') }}" />
<input type="hidden" name="theme" value="light" />
<div class="mb-6">
<label for="username" class="form-label">Username</label>
<input type="text"

View file

@ -74,7 +74,8 @@
value="{{ csrf_token() }}" />
<label class="setting-checkbox-label d-flex align-items-center ps-0"
for="dark-mode-toggle">Light</label>
<div class="form-switch ms-2 mb-1" {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly mode, therefore the theme cannot be changed"{% endif %}>
<div class="form-switch ms-2 mb-1"
{% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly mode, therefore the theme cannot be changed"{% endif %}>
<input id="dark-mode-toggle"
name="dark-mode-toggle"
class="form-check-input"

View file

@ -11,10 +11,25 @@
id="csrf_token"
name="csrf_token"
value="{{ csrf_token() }}" />
<input type="hidden" name="theme" value="light" />
<div class="container-xxl">
<div class="position-fixed top-0 end-0 m-5">
<!-- prettier-ignore -->
<div class="d-flex justify-content-end align-items-center mb-5">
<div class="toggle-container d-flex align-items-center bg-white p-2 rounded-pill shadow-lg me-3">
<label class="setting-checkbox-label pe-2 mb-0 fw-bold text-secondary"
for="dark-mode-toggle">Light</label>
<div class="form-switch">
<input id="dark-mode-toggle"
name="dark-mode-toggle"
class="form-check-input"
type="checkbox"
role="switch"
{% if current_user.theme == "dark" %}checked{% endif %} />
</div>
<label class="setting-checkbox-label mb-0 fw-bold text-secondary"
for="dark-mode-toggle">Dark</label>
</div>
<ul class="d-flex mb-0 list-unstyled">
<li class="me-3">
<a role="button"
@ -571,8 +586,8 @@
<input id="CUSTOM_SSL_KEY"
name="CUSTOM_SSL_KEY"
type="text"
placeholder="/tmp/key.pem"
value="{{ custom_ssl_key }}"
placeholder="/tmp/key.pem"
value="{{ custom_ssl_key }}"
class="form-control plugin-setting mt-1"
aria-labelledby="label-CUSTOM_SSL_KEY"
pattern="^.*$" />
@ -721,7 +736,7 @@
</div>
</div>
<div id="feedback-toast"
class="bs-toast toast fade bg-white border"
class="bs-toast toast fade bg-{% if current_user.theme == 'light' %}white{% else %}dark{% endif %} border"
role="alert"
aria-live="assertive"
aria-atomic="true"

View file

@ -34,7 +34,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
class="x-logo"
alt="X logo"
width="20px"
height="20px">

View file

@ -34,7 +34,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
class="x-logo"
alt="X logo"
width="20px"
height="20px">