Start adding dark mode in best effort for web UI

This commit is contained in:
Théophile Diot 2024-10-28 17:11:38 +01:00
parent 3ead15cdd1
commit d20f926078
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
16 changed files with 538 additions and 55 deletions

View file

@ -9,10 +9,12 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
from bcrypt import checkpw
from flask_login import AnonymousUserMixin, UserMixin
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy import TEXT, Boolean, DateTime, Column, Identity, Integer, String, ForeignKey, UnicodeText
from sqlalchemy import TEXT, Boolean, DateTime, Column, Enum, Identity, Integer, String, ForeignKey, UnicodeText
from model import METHODS_ENUM # type: ignore
THEMES_ENUM = Enum("light", "dark", name="themes_enum")
Base = declarative_base()
@ -44,6 +46,7 @@ class Users(Base, UserMixin):
password = Column(String(60), nullable=False)
method = Column(METHODS_ENUM, nullable=False, default="manual")
admin = Column(Boolean, nullable=False, default=False)
theme = Column(THEMES_ENUM, nullable=False, default="light")
# 2FA
totp_secret = Column(String(256), nullable=True)

View file

@ -4,7 +4,7 @@ from os import sep
from os.path import join
from sys import path as sys_path
from time import sleep
from typing import List, Optional, Tuple, Union
from typing import List, Literal, Optional, Tuple, Union
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
@ -163,6 +163,7 @@ class UIDatabase(Database):
"email": ui_user.email,
"password": ui_user.password.encode("utf-8"),
"method": ui_user.method,
"theme": ui_user.theme,
"totp_secret": ui_user.totp_secret,
"creation_date": ui_user.creation_date,
"update_date": ui_user.update_date,
@ -179,6 +180,7 @@ class UIDatabase(Database):
roles: List[str],
email: Optional[str] = None,
*,
theme: Union[Literal["light"], Literal["dark"]] = "light",
totp_secret: Optional[str] = None,
totp_recovery_codes: Optional[List[str]] = None,
method: str = "manual",
@ -209,6 +211,7 @@ class UIDatabase(Database):
password=password.decode("utf-8"),
method=method,
admin=admin,
theme=theme,
totp_secret=totp_secret,
creation_date=current_time,
update_date=current_time,
@ -231,6 +234,7 @@ class UIDatabase(Database):
password: bytes,
totp_secret: Optional[str],
*,
theme: Union[Literal["light"], Literal["dark"]] = "light",
old_username: Optional[str] = None,
email: Optional[str] = None,
totp_recovery_codes: Optional[List[str]] = None,
@ -263,6 +267,7 @@ class UIDatabase(Database):
user.password = password.decode("utf-8")
user.totp_secret = totp_secret
user.method = method
user.theme = theme
user.update_date = datetime.now().astimezone()
try:

View file

@ -202,20 +202,21 @@ def edit_profile():
if DB.readonly:
return handle_error("Database is in read-only mode", "profile")
verify_data_in_form(data={"password": None}, err_message="Missing current password parameter on /profile/edit.", redirect_url="profile")
if not current_user.check_password(request.form["password"]):
return handle_error("The current password is incorrect.", "profile")
user_data = {
"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": current_user.theme,
}
if "username" in request.form:
verify_data_in_form(data={"password": None}, err_message="Missing current password parameter on /profile/edit.", redirect_url="profile")
if not current_user.check_password(request.form["password"]):
return handle_error("The current password is incorrect.", "profile")
verify_data_in_form(data={"email": None}, err_message="Missing email parameter on /profile/edit.", redirect_url="profile")
if request.form["email"] and request.form["email"] != current_user.email:
@ -231,6 +232,11 @@ def edit_profile():
if request.form["email"] == (current_user.email or "") and request.form["username"] == current_user.get_id():
return handle_error("The username and email are the same as the current ones.", "profile")
elif "new_password" in request.form:
verify_data_in_form(data={"password": None}, err_message="Missing current password parameter on /profile/edit.", redirect_url="profile")
if not current_user.check_password(request.form["password"]):
return handle_error("The current password is incorrect.", "profile")
verify_data_in_form(
data={"new_password_confirm": None},
err_message="Missing new password confirm parameter on /profile/edit.",
@ -248,6 +254,11 @@ def edit_profile():
return handle_error("The new password is the same as the current one.", "profile")
user_data["password"] = gen_password_hash(request.form["new_password"])
elif "theme" in request.form:
if request.form["theme"] not in ("dark", "light"):
return handle_error("The theme is invalid.", "profile")
user_data["theme"] = request.form["theme"]
else:
return handle_error("No fields were updated.", "profile")

View file

@ -892,3 +892,292 @@ a.courier-prime:hover {
text-align: center;
font-weight: bold;
}
.layout-page::before {
display: none;
}
/*
* Dark mode overrides
******************************************************************************/
.dark-style body {
color: #fff;
background-color: #0d2f45;
color-scheme: dark;
}
.dark-style,
[data-bs-theme="dark"] {
--bs-primary: #fff;
--bs-secondary: #9caeb7;
--bs-primary-rgb: 255, 255, 255;
--bs-secondary-rgb: 156, 174, 183;
--bs-primary-text-emphasis: #fff;
--bs-secondary-text-emphasis: #b3cad4;
--bs-link-color: #fff;
--bs-link-color-rgb: 255, 255, 255;
--bs-breadcrumb-divider-color: #fff;
--bs-body-color-rgb: 255, 255, 255;
--dt-row-selected: 31, 31, 31;
--bs-breadcrumb-item-active-color: #fff;
}
.dark-style .list-group {
--bs-list-group-color: #fff;
}
.dark-style .btn {
--bs-btn-color: #fff;
}
.dark-style .btn:hover {
--bs-btn-hover-color: #fff;
}
.dark-style .card {
--bs-card-title-color: #fff;
}
.dark-style .dropdown-menu {
--bs-dropdown-bg: #0c283a;
}
.dark-style .offcanvas,
.dark-style .offcanvas-xxl,
.dark-style .offcanvas-xl,
.dark-style .offcanvas-lg,
.dark-style .offcanvas-md,
.dark-style .offcanvas-sm {
--bs-offcanvas-bg: #0d2f45;
}
.dark-style .nav-link,
.dark-style .breadcrumb-item.active,
.dark-style .card .card-header,
.dark-style .card .card-body,
.dark-style .form-label,
.dark-style .form-control,
.dark-style .form-select,
.dark-style .input-group-text {
color: #fff !important;
}
.dark-style .breadcrumb-item,
.dark-style .breadcrumb-item a {
color: #9caeb7;
}
.dark-style .breadcrumb-item:hover,
.dark-style .breadcrumb-item:focus,
.dark-style .breadcrumb-item a:hover,
.dark-style .breadcrumb-item a:focus {
color: #fff;
}
.dark-style .nav-pills .nav-link.active,
.nav-pills .nav-link.active:hover,
.nav-pills .nav-link.active:focus {
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;
}
.dark-style .btn-primary:hover {
color: #07354a !important;
}
.dark-style .btn-light {
background-color: #0a4b69;
border-color: #0a4b69;
box-shadow: none;
color: #fff;
}
.dark-style .card {
background-color: #0c283a;
border-color: #0c283a;
color: #fff;
}
.dark-style .modal {
--bs-modal-color: #fff;
--bs-modal-bg: #0c283a;
}
.dark-style .input-group:focus-within .form-control,
.dark-style .input-group:focus-within .input-group-text {
border-color: #fff !important;
}
.dark-style .text-body {
color: #fff !important;
}
.dark-style .bg-footer-theme .footer-link {
color: #cdd7db;
}
.dark-style h6,
.h6,
h5,
.h5,
h4,
.h4,
h3,
.h3,
h2,
.h2,
h1,
.h1 {
color: #fff;
}
.dark-style .img-fluid.qr-code,
.dark-style .x-logo {
filter: brightness(0) invert(1);
}
.dark-style .text-primary {
color: var(--bs-primary) !important;
}
.dark-style .text-primary.shine {
color: rgba(var(--bs-primary-rgb), 0.1) !important;
background-color: var(--bs-primary) !important;
}
.dark-style .text-muted {
color: #9caeb7 !important;
}
.dark-style .sticky-card {
background-color: rgba(12, 40, 58, 0.95) !important;
}
.dark-style .layout-navbar,
.dark-style .layout-menu {
background-color: #0c283a !important;
color: #fff !important;
}
.dark-style .bg-menu-theme .menu-link,
.dark-style .bg-menu-theme .menu-horizontal-prev,
.dark-style .bg-menu-theme .menu-horizontal-next {
color: #fff;
}
.dark-style .bg-menu-theme .menu-inner > .menu-item.active > .menu-link {
color: #07354a !important;
background-color: #fff !important;
}
.dark-style .bg-menu-theme .menu-inner > .menu-item.active:before {
background-color: #fff !important;
}
.dark-style .btn-outline-primary {
color: var(--bs-primary);
border-color: var(--bs-primary);
}
html:not([dir="rtl"]).dark-style .border-primary,
html[dir="rtl"].dark-style .border-primary {
border-color: var(--bs-primary) !important;
}
.dark-style .form-floating > .form-control:focus ~ label,
.dark-style
.form-floating
> .form-control:focus:not(:placeholder-shown)
~ label,
.dark-style .form-floating > .form-select:focus ~ label,
.dark-style
.form-floating
> .form-select:focus:not(:placeholder-shown)
~ label {
color: #fff;
}
.dark-style .form-select {
--bs-form-select-bg-img: url('data:image/svg+xml,%3csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 22" fill="none"%3e%3cpath d="M10.9999 12.0743L15.5374 7.53676L16.8336 8.83292L10.9999 14.6666L5.16626 8.83292L6.46243 7.53676L10.9999 12.0743Z" fill="%23ffffff" fill-opacity="0.9"/%3e%3c/svg%3e');
background-color: #0c283a;
}
.dark-style .dropdown-item {
color: #fff;
}
.dark-style .dropdown-item:not(.disabled).active,
.dark-style .dropdown-item:not(.disabled):active {
color: #fff !important;
background-color: #0a4b69;
}
.dark-style .bg-secondary {
color: #07354a;
}
.dark-style .table th {
color: #fff;
}
.dark-style .table > :not(caption) > * > * {
color: #fff;
}
.dark-style .form-control:focus,
.dark-style .form-select:focus {
border-color: #fff !important;
}
.dark-style .btn-outline-primary.disabled,
.dark-style .btn-outline-primary:disabled {
color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
.dark-style td.highlight {
background-color: rgba(255, 255, 255, 0.1) !important;
}
.dark-style .form-check-label {
color: #fff;
}
.dark-style a {
color: #cdd7db;
}
.dark-style a:hover {
color: #fff;
}
.dark-style .bg-label-secondary {
color: #000 !important;
}
.dark-style .bg-bw-green {
background-color: var(--bs-bw-green);
}
.dark-style .apexcharts-text {
fill: #fff;
}
.dark-style .apexcharts-tooltip,
.dark-style .apexcharts-tooltip-title {
background-color: #333;
color: #fff;
border: 1px solid #555;
}
.dark-style .apexcharts-legend-text {
color: #fff;
border: 1px solid #555;
}

View file

@ -1,6 +1,13 @@
$(function () {
const headingColor = config.colors.headingColor;
const legendColor = config.colors.bodyColor;
var headingColor = config.colors.headingColor;
var legendColor = config.colors.bodyColor;
const theme = $("#theme").val();
if (theme === "dark") {
headingColor = config.colors.white;
legendColor = config.colors.white;
}
// Requests countries map
@ -432,10 +439,16 @@ $(function () {
type: "bar",
width: "100%",
height: 400,
toolbar: {
show: false,
},
},
title: {
text: "Blocked Requests per Hour",
align: "center",
style: {
color: headingColor,
},
},
series: [
{
@ -446,7 +459,7 @@ $(function () {
colors: colorValues,
plotOptions: {
bar: {
distributed: true, // This line enables individual bar colors
distributed: true,
},
},
xaxis: {
@ -454,14 +467,23 @@ $(function () {
labels: {
rotate: -45,
hideOverlappingLabels: true,
style: {
colors: headingColor,
},
},
title: {
text: "Time",
style: {
color: headingColor,
},
},
},
yaxis: {
title: {
text: "Number of Blocked Requests",
style: {
color: headingColor,
},
},
},
legend: {
@ -491,6 +513,9 @@ $(function () {
},
legend: {
position: "bottom",
labels: {
color: legendColor,
},
},
},
},

View file

@ -0,0 +1,137 @@
.dark-style .flatpickr-calendar {
background: #222;
color: #e0e0e0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
}
.dark-style .flatpickr-calendar .hasTime {
border-top: 1px solid #333;
}
.dark-style .flatpickr-calendar .hasWeeks .dayContainer {
border-left: 1px solid #333;
}
.dark-style .flatpickr-months .flatpickr-month {
background: #333;
color: #ddd;
fill: #ddd;
}
.dark-style .flatpickr-months .flatpickr-prev-month,
.dark-style .flatpickr-months .flatpickr-next-month {
color: #aaa;
fill: #aaa;
}
.dark-style .flatpickr-months .flatpickr-prev-month:hover,
.dark-style .flatpickr-months .flatpickr-next-month:hover {
color: #ff6666;
}
.dark-style .flatpickr-current-month {
color: #e0e0e0;
}
.dark-style .flatpickr-weekdays .flatpickr-weekday {
color: #999;
}
.dark-style .flatpickr-day {
color: #ccc;
background: none;
border: 1px solid #333;
}
.dark-style .flatpickr-day:hover,
.dark-style .flatpickr-day:focus {
background: #444;
}
.dark-style .flatpickr-day.today {
border-color: #ff6666;
background: #333;
color: #ff6666;
}
.dark-style .flatpickr-day.today:hover,
.dark-style .flatpickr-day.today:focus {
background: #ff6666;
color: #fff;
}
.dark-style .flatpickr-day.selected,
.dark-style .flatpickr-day.startRange,
.dark-style .flatpickr-day.endRange {
background: #4f9aff;
border-color: #4f9aff;
color: #fff;
}
.dark-style .flatpickr-day.inRange {
background: #333;
color: #bbb;
}
.dark-style .flatpickr-time input,
.dark-style .flatpickr-time .flatpickr-am-pm {
color: #ccc;
background: #333;
}
.dark-style .flatpickr-time input:hover,
.dark-style .flatpickr-time input:focus,
.dark-style .flatpickr-time .flatpickr-am-pm:hover,
.dark-style .flatpickr-time .flatpickr-am-pm:focus {
background: #444;
}
.dark-style .numInputWrapper span {
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.1);
}
.dark-style .numInputWrapper span:hover {
background: rgba(255, 255, 255, 0.25);
}
.dark-style .numInputWrapper span.arrowUp:after {
border-bottom-color: rgba(255, 255, 255, 0.8);
}
.dark-style .numInputWrapper span.arrowDown:after {
border-top-color: rgba(255, 255, 255, 0.8);
}
.dark-style .flatpickr-monthDropdown-months {
background: #333;
color: #ddd;
}
.dark-style .flatpickr-weekwrapper span.flatpickr-day {
color: rgba(255, 255, 255, 0.4);
}
.dark-style .flatpickr-day.prevMonthDay,
.dark-style .flatpickr-day.nextMonthDay {
color: #666;
background: #222;
}
.dark-style .flatpickr-day.prevMonthDay:hover,
.dark-style .flatpickr-day.nextMonthDay:hover {
background: #333;
color: #888;
}
.dark-style span.flatpickr-day.today:not(.selected), .dark-style span.flatpickr-day.prevMonthDay.today:not(.selected), .dark-style span.flatpickr-day.nextMonthDay.today:not(.selected) {
border-left-color: transparent;
}
.dark-style span.flatpickr-day:nth-child(n+8), .dark-style span.flatpickr-day.prevMonthDay, .dark-style span.flatpickr-day.nextMonthDay {
border-color: transparent;
}
.dark-style .flatpickr-current-month .flatpickr-monthDropdown-months:hover {
background: #444;
}

View file

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

View file

@ -23,7 +23,7 @@
} %}
<!DOCTYPE html>
<html lang="en"
class="light-style layout-navbar-fixed layout-menu-fixed"
class="{{ theme }}-style layout-navbar-fixed layout-menu-fixed"
data-theme="theme-default">
<head>
<meta charset="utf-8" />
@ -65,6 +65,11 @@
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.css') }}"
nonce="{{ style_nonce }}" />
{% if theme == 'dark' %}
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.dark.css') }}"
nonce="{{ style_nonce }}" />
{% endif %}
{% endif %}
<!-- Core CSS -->
<link rel="stylesheet"
@ -119,6 +124,7 @@
</head>
<body>
<input type="hidden" id="is-read-only" value="{{ is_readonly }}" />
<input type="hidden" id="theme" value="{{ theme }}" />
<!-- prettier-ignore -->
{% if current_endpoint != "loading" %}
{% include "flash.html" %}

View file

@ -29,7 +29,7 @@
<!-- Collapsible floating menu -->
<div class="collapse mt-2" id="service-modes-floating">
<ul id="service-modes-menu"
class="nav nav-pills flex-column bg-white p-2 rounded shadow-sm"
class="nav nav-pills flex-column bg-{% if theme == 'light' %}white{% else %}dark{% endif %} p-2 rounded shadow-sm"
role="tablist">
<li class="nav-item mb-1" role="presentation">
<button type="button"
@ -120,7 +120,7 @@
<ul class="d-flex mb-0 list-unstyled">
<li class="me-3">
<a role="button"
class="btn btn-sm btn-outline-dark d-flex align-items-center h-100"
class="btn btn-sm btn-outline-{% if theme == 'light' %}dark{% else %}light{% endif %} d-flex align-items-center h-100"
aria-pressed="true"
href="https://panel.bunkerweb.io/order/support/?utm_campaign=self&utm_source=ui"
target="_blank"
@ -151,7 +151,7 @@
<li class="position-relative">
<button id="news-button"
type="button"
class="btn btn-sm btn-dark text-uppercase d-flex align-items-center"
class="btn btn-sm btn-{% if theme == 'light' %}dark{% else %}light{% endif %} text-uppercase d-flex align-items-center"
aria-pressed="true"
data-bs-toggle="offcanvas"
data-bs-target="#side-offcanvas-news"

View file

@ -8,7 +8,7 @@
{% endif %}
<!-- flash message-->
{% for category, message in messages %}
<div class="bs-toast toast fade show bg-white border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}"
<div class="bs-toast toast fade show bg-{% if theme == 'light' %}white{% else %}dark{% endif %} border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}"
role="alert"
aria-live="assertive"
aria-atomic="true">

View file

@ -141,7 +141,7 @@
</table>
</div>
<div id="feedback-toast"
class="bs-toast toast fade bg-white border"
class="bs-toast toast fade bg-{% if theme == 'light' %}white{% else %}dark{% endif %} border"
role="alert"
aria-live="assertive"
aria-atomic="true"

View file

@ -78,7 +78,7 @@
<!-- Version -->
<li class="nav-item lh-1 me-2 me-md-4">
<a role="button"
class="btn btn-sm btn-outline-dark px-2 px-md-3 position-relative"
class="btn btn-sm btn-outline-{% if theme == 'light' %}dark{% else %}light{% endif %} px-2 px-md-3 position-relative"
aria-pressed="true"
href="https://github.com/bunkerity/bunkerweb/releases/latest"
target="_blank"

View file

@ -79,10 +79,11 @@
<div class="tab-pane fade show active"
id="navs-pills-profile"
role="tabpanel">
<div class="d-flex row">
<div class="d-flex row g-4">
<div class="col-md-6">
<!-- Profile -->
<div class="card">
<div class="card position-relative">
<i class='bx bx-user bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header">Edit Profile</h5>
<div class="card-body pb-2">
<div class="d-flex align-items-start align-items-sm-center gap-6 pb-4 border-bottom">
@ -171,32 +172,29 @@
</div>
<div class="col-md-6">
<!-- Theme -->
<div class="card">
<div class="card position-relative">
<i class='bx bx-{% if theme == 'light' %}sun{% else %}moon{% endif %} bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header">Change Theme</h5>
<div class="card-body pb-4"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-html="true"
data-bs-original-title="<i class='bx bx-rocket bx-xs'></i><span>Coming soon</span>">
<!-- <form method="POST" action="{{ profile_url }}/edit"> -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="row g-3">
<div class="col-md-12 form-floating">
<select class="form-select" id="theme" name="theme" disabled>
<option value="light"
{% if current_user.theme == "light" %}selected{% endif %}>
Light
</option>
<option value="dark" {% if current_user.theme == "dark" %}selected{% endif %}>Dark</option>
</select>
<label for="theme">Theme</label>
<div class="card-body pb-4">
<form method="POST" action="{{ profile_url }}/edit">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="row g-3">
<div class="col-md-12 form-floating">
<select class="form-select" id="theme" name="theme">
<option value="light"
{% if theme == "light" %}selected{% endif %}>
Light
</option>
<option value="dark" {% if theme == "dark" %}selected{% endif %}>Dark</option>
</select>
<label for="theme">Theme</label>
</div>
</div>
</div>
<div class="mt-4 justify-content-center d-flex">
<button type="submit" class="btn btn-primary me-2 disabled">Save Changes</button>
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
</div>
<!-- </form> -->
<div class="mt-4 justify-content-center d-flex">
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
</div>
</form>
</div>
</div>
<!-- /Theme -->
@ -204,10 +202,11 @@
</div>
</div>
<div class="tab-pane fade" id="navs-pills-security" role="tabpanel">
<div class="d-flex row">
<div class="d-flex row g-4">
<div class="col-md-6">
<!-- Password -->
<div class="card mb-4">
<div class="card mb-4 position-relative">
<i class='bx bx-check-shield bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header">Change Password</h5>
<div class="card-body pb-4">
<form method="POST" action="{{ profile_url }}/edit" autocomplete="off">
@ -291,7 +290,8 @@
</div>
<div class="col-md-6">
<!-- Two-Factor Authentication (2FA) -->
<div class="card mb-4">
<div class="card mb-4 position-relative">
<i class='bx bx-qr bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header text-center">Two-Factor Authentication (2FA)</h5>
<div class="card-body">
{% if not is_totp %}
@ -302,7 +302,7 @@
<!-- QR Code Section -->
<div class="col-md-12 text-center">
<h5 class="mb-0">Enable 2FA</h5>
<img class="img-fluid"
<img class="img-fluid qr-code"
src="{{ totp_qr_image }}"
alt="2FA QR Code"
height="200"
@ -417,7 +417,8 @@
<!-- /Two-Factor Authentication (2FA) -->
{% if is_totp %}
<!-- Refresh Recovery Codes -->
<div class="card mb-4">
<div class="card mb-4 position-relative">
<i class='bx bx-data bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header">Refresh Recovery Codes</h5>
<div class="card-body">
<div class="alert alert-danger text-center" role="alert">
@ -461,11 +462,12 @@
</div>
</div>
<div class="tab-pane fade" id="navs-pills-sessions" role="tabpanel">
<div class="d-flex row justify-content-center">
<div class="d-flex row g-2 justify-content-center">
<div class="col-md-4">
<form method="POST" action="{{ profile_url }}/wipe-other-sessions">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="card mb-4">
<div class="card mb-4 position-relative">
<i class='bx bx-history bx-sm position-absolute top-0 end-0 m-3'></i>
<h5 class="card-header">Sessions</h5>
<div class="card-body">
<div class="row g-3">

View file

@ -34,6 +34,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
alt="X logo"
width="20px"
height="20px">
@ -56,8 +57,8 @@
</div>
</div>
<!-- Newsletter Signup Section -->
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-light-subtle border-top">
<h5 class="mb-3 text-dark">Join the Newsletter</h5>
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-{% if theme == 'light' %}light{% else %}dark{% endif %}-subtle border-top">
<h5 class="mb-3 text-{% if theme == 'light' %}dark{% else %}primary{% endif %}">Join the Newsletter</h5>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
method="POST">
<div class="mb-3">

View file

@ -34,6 +34,7 @@
target="_blank"
rel="noreferrer">
<img src="{{ url_for('static', filename='img/brands/Twitter-X-Logo.svg') }}"
class="x-logo"
alt="X logo"
width="20px"
height="20px">
@ -53,7 +54,7 @@
{% set content = message[0] %}
{% set category = message[1] %}
{% set datetime = message[2] %}
<div class="col-12 bs-toast toast show bw-white border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}">
<div class="col-12 bs-toast toast show bg-{% if theme == 'light' %}white{% else %}dark{% endif %} border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}">
<div class="toast-header d-flex align-items-center{% if category == 'error' %} text-danger{% elif category == 'warning' %} text-warning{% else %} text-primary{% endif %}">
<i class="d-block h-auto rounded tf-icons bx bx-xs bx-bell me-2"></i>
<span class="fw-medium me-auto">
@ -77,8 +78,8 @@
{% endif %}
</div>
<!-- Newsletter Signup Section -->
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-light-subtle border-top">
<h5 class="mb-3 text-dark">Join the Newsletter</h5>
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-{% if theme == 'light' %}light{% else %}dark{% endif %}-subtle border-top">
<h5 class="mb-3 text-{% if theme == 'light' %}dark{% else %}primary{% endif %}">Join the Newsletter</h5>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
method="POST">
<div class="mb-3">

View file

@ -175,6 +175,7 @@ def inject_variables():
plugins=BW_CONFIG.get_plugins(),
flash_messages=session.get("flash_messages", []),
is_readonly=DATA.get("READONLY_MODE", False),
theme=current_user.theme if current_user.is_authenticated else "light",
)