mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Add theme handling in setup and login processes, enhance UI theme toggle functionality
This commit is contained in:
parent
d598cb4276
commit
175cc70b70
13 changed files with 138 additions and 19 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
41
src/ui/app/static/js/pages/login.js
Normal file
41
src/ui/app/static/js/pages/login.js
Normal 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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue