Finish dark mode in web UI (awaiting review)

This commit is contained in:
Théophile Diot 2024-10-29 14:09:26 +01:00
parent c9c479b04f
commit f6c343b547
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
17 changed files with 430 additions and 211 deletions

View file

@ -264,7 +264,7 @@ def edit_profile():
ret = DB.update_ui_user(**user_data, old_username=current_user.get_id())
if ret:
return handle_error(f"Couldn't update the admin user in the database: {ret}", "profile")
return handle_error(f"Couldn't update the {current_user.get_id()} user in the database: {ret}", "profile")
if "new_password" in request.form:
session.clear()

View file

@ -910,11 +910,8 @@ a.courier-prime:hover {
.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;
@ -976,8 +973,8 @@ a.courier-prime:hover {
}
.dark-style .nav-pills .nav-link.active,
.nav-pills .nav-link.active:hover,
.nav-pills .nav-link.active:focus {
.dark-style .nav-pills .nav-link.active:hover,
.dark-style .nav-pills .nav-link.active:focus {
background-color: #0a4b69;
}
@ -996,10 +993,10 @@ a.courier-prime:hover {
}
.dark-style .btn-light {
background-color: #0a4b69;
border-color: #0a4b69;
background-color: #fff;
border-color: #fff;
box-shadow: none;
color: #fff;
color: #000;
}
.dark-style .card {
@ -1027,17 +1024,17 @@ a.courier-prime:hover {
}
.dark-style h6,
.h6,
h5,
.h5,
h4,
.h4,
h3,
.h3,
h2,
.h2,
h1,
.h1 {
.dark-style .h6,
.dark-style h5,
.dark-style .h5,
.dark-style h4,
.dark-style .h4,
.dark-style h3,
.dark-style .h3,
.dark-style h2,
.dark-style .h2,
.dark-style h1,
.dark-style .h1 {
color: #fff;
}
@ -1058,6 +1055,7 @@ h1,
.dark-style .text-muted {
color: #9caeb7 !important;
}
.dark-style .sticky-card {
background-color: rgba(12, 40, 58, 0.95) !important;
}
@ -1181,3 +1179,22 @@ html[dir="rtl"].dark-style .border-primary {
color: #fff;
border: 1px solid #555;
}
.dark-style .btn-check:checked + .btn-primary,
.dark-style .btn-check:active + .btn-primary,
.dark-style .btn-primary:active,
.dark-style .btn-primary.active,
.dark-style .btn-primary.show.dropdown-toggle,
.dark-style .show > .btn-primary.dropdown-toggle {
color: #07354a !important;
}
.dark-style .leaflet-container {
background: #000;
}
.dark-style .leaflet-layer,
.dark-style .leaflet-control-zoom-in,
.dark-style .leaflet-control-zoom-out {
filter: invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%);
}

View file

@ -2,7 +2,26 @@ $(document).ready(function () {
const editorElement = $("#cache-value");
const initialContent = editorElement.text().trim();
const editor = ace.edit(editorElement[0]);
editor.setTheme("ace/theme/cloud9_day"); // cloud9_night when dark mode is supported
var theme = $("#theme").val();
function setEditorTheme() {
if (theme === "dark") {
editor.setTheme("ace/theme/cloud9_night");
} else {
editor.setTheme("ace/theme/cloud9_day");
}
}
setEditorTheme();
$("#dark-mode-toggle").on("change", function () {
setTimeout(() => {
theme = $("#theme").val();
setEditorTheme();
}, 30);
});
editor.session.setMode("ace/mode/text");
editor.setReadOnly(true);

View file

@ -8,7 +8,25 @@ $(document).ready(function () {
const editorElement = $("#config-value");
const initialContent = editorElement.text().trim();
const editor = ace.edit(editorElement[0]);
editor.setTheme("ace/theme/cloud9_day"); // cloud9_night when dark mode is supported
var theme = $("#theme").val();
function setEditorTheme() {
if (theme === "dark") {
editor.setTheme("ace/theme/cloud9_night");
} else {
editor.setTheme("ace/theme/cloud9_day");
}
}
setEditorTheme();
$("#dark-mode-toggle").on("change", function () {
setTimeout(() => {
theme = $("#theme").val();
setEditorTheme();
}, 30);
});
if (isReadOnly && window.location.pathname.endsWith("/new"))
window.location.href = window.location.href.split("/new")[0];

View file

@ -2,7 +2,7 @@ $(function () {
var headingColor = config.colors.headingColor;
var legendColor = config.colors.bodyColor;
const theme = $("#theme").val();
var theme = $("#theme").val();
if (theme === "dark") {
headingColor = config.colors.white;
@ -232,94 +232,105 @@ $(function () {
100
).toFixed(2);
const requestsOptions = {
chart: {
type: "donut",
},
labels: Object.keys(requestsData),
series: Object.values(requestsData).map((value) => parseInt(value, 10)),
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 200,
},
legend: {
position: "bottom",
},
},
},
],
legend: {
show: false,
},
dataLabels: {
enabled: false,
formatter: function (val, opt) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
},
},
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
},
},
states: {
hover: {
filter: { type: "none" },
},
active: {
filter: { type: "none" },
},
},
plotOptions: {
pie: {
donut: {
size: "75%",
labels: {
show: true,
value: {
fontSize: "18px",
fontFamily: "Public Sans",
fontWeight: 500,
color: headingColor,
offsetY: -17,
formatter: function (val) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
},
},
name: {
offsetY: 17,
fontFamily: "Public Sans",
},
total: {
show: true,
fontSize: "13px",
color: legendColor,
label: "Blocked",
formatter: function (w) {
return blockedRequestsPercent + "%";
},
},
},
},
},
},
};
var requestsChart;
const requestsChart = new ApexCharts(
document.querySelector("#requests-stats"),
requestsOptions,
);
requestsChart.render();
function renderStatsChart() {
const requestsOptions = {
chart: {
type: "donut",
},
labels: Object.keys(requestsData),
series: Object.values(requestsData).map((value) => parseInt(value, 10)),
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 200,
},
legend: {
position: "bottom",
},
},
},
],
legend: {
show: false,
},
dataLabels: {
enabled: false,
formatter: function (val, opt) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
},
},
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
},
},
states: {
hover: {
filter: { type: "none" },
},
active: {
filter: { type: "none" },
},
},
plotOptions: {
pie: {
donut: {
size: "75%",
labels: {
show: true,
value: {
fontSize: "18px",
fontFamily: "Public Sans",
fontWeight: 500,
color: headingColor,
offsetY: -17,
formatter: function (val) {
return (
((parseInt(val) / totalRequests) * 100).toFixed(2) + "%"
);
},
},
name: {
offsetY: 17,
fontFamily: "Public Sans",
},
total: {
show: true,
fontSize: "13px",
color: legendColor,
label: "Blocked",
formatter: function (w) {
return blockedRequestsPercent + "%";
},
},
},
},
},
},
};
requestsChart = new ApexCharts(
document.querySelector("#requests-stats"),
requestsOptions,
);
requestsChart.render();
}
renderStatsChart();
// Requests IPs chart
const $ipsData = $("#requests-ips-data");
if ($ipsData.length) {
var ipsChart;
function renderIpsChart() {
const requestsIpsData = JSON.parse($("#requests-ips-data").text());
const topIpsData = Object.entries(requestsIpsData)
@ -401,13 +412,17 @@ $(function () {
},
};
const ipsChart = new ApexCharts(
ipsChart = new ApexCharts(
document.querySelector("#requests-ips"),
ipsOptions,
);
ipsChart.render();
}
if ($ipsData.length) {
renderIpsChart();
}
// Requests Blocking status
const blockingData = JSON.parse($("#requests-blocking-data").text());
@ -434,97 +449,124 @@ $(function () {
return getColorFromRatio(ratio);
});
const blockingOptions = {
chart: {
type: "bar",
width: "100%",
height: 400,
toolbar: {
var blockingChart;
function renderBlockingStatus() {
const blockingOptions = {
chart: {
type: "bar",
width: "100%",
height: 400,
toolbar: {
show: false,
},
},
title: {
text: "Blocked Requests per Hour",
align: "center",
style: {
color: headingColor,
},
},
series: [
{
name: "Blocked Requests",
data: dataValues,
},
],
colors: colorValues,
plotOptions: {
bar: {
distributed: true,
},
},
xaxis: {
categories: categories,
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: {
show: false,
},
},
title: {
text: "Blocked Requests per Hour",
align: "center",
style: {
color: headingColor,
},
},
series: [
{
name: "Blocked Requests",
data: dataValues,
},
],
colors: colorValues,
plotOptions: {
bar: {
distributed: true,
},
},
xaxis: {
categories: categories,
labels: {
rotate: -45,
hideOverlappingLabels: true,
style: {
colors: headingColor,
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
},
},
title: {
text: "Time",
style: {
color: headingColor,
states: {
hover: {
filter: { type: "none" },
},
active: {
filter: { type: "none" },
},
},
},
yaxis: {
title: {
text: "Number of Blocked Requests",
style: {
color: headingColor,
},
},
},
legend: {
show: false,
},
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
},
},
states: {
hover: {
filter: { type: "none" },
},
active: {
filter: { type: "none" },
},
},
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: "100%",
},
legend: {
position: "bottom",
labels: {
color: legendColor,
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: "100%",
},
legend: {
position: "bottom",
labels: {
color: legendColor,
},
},
},
},
},
],
};
],
};
const blockingChart = new ApexCharts(
document.querySelector("#requests-blocking"),
blockingOptions,
);
blockingChart.render();
blockingChart = new ApexCharts(
document.querySelector("#requests-blocking"),
blockingOptions,
);
blockingChart.render();
}
renderBlockingStatus();
$("#dark-mode-toggle").on("change", function () {
setTimeout(() => {
theme = $("#theme").val();
headingColor = config.colors.headingColor;
legendColor = config.colors.bodyColor;
if (theme === "dark") {
headingColor = config.colors.white;
legendColor = config.colors.white;
}
requestsChart.destroy();
renderStatsChart();
if ($ipsData.length) {
ipsChart.destroy();
renderIpsChart();
}
blockingChart.destroy();
renderBlockingStatus();
}, 30);
});
});

View file

@ -3,6 +3,7 @@ $(document).ready(() => {
let currentPlugin = "general";
let currentStep = 1;
const isReadOnly = $("#is-read-only").val().trim() === "True";
var isInit = true;
if (isReadOnly && window.location.pathname.endsWith("/new"))
window.location.href = window.location.href.split("/new")[0];
@ -139,7 +140,7 @@ $(document).ready(() => {
updateUrlParams(params);
}
} else if (targetClass.includes("navs-templates-")) {
resetTemplateConfig();
if (!isInit) resetTemplateConfig();
setTimeout(() => {
currentTemplate = targetClass
.substring(1)
@ -1064,10 +1065,11 @@ $(document).ready(() => {
}
}
var editors = [];
$(".ace-editor").each(function () {
const initialContent = $(this).text().trim();
const editor = ace.edit(this);
editor.setTheme("ace/theme/cloud9_day"); // cloud9_night when dark mode is supported
editor.session.setMode("ace/mode/nginx");
// const language = $(this).data("language"); // TODO: Support ModSecurity
@ -1094,6 +1096,28 @@ $(document).ready(() => {
});
editor.renderer.setScrollMargin(10, 10);
editors.push(editor);
});
var theme = $("#theme").val();
function setEditorTheme() {
editors.forEach((editor) => {
if (theme === "dark") {
editor.setTheme("ace/theme/cloud9_night");
} else {
editor.setTheme("ace/theme/cloud9_day");
}
});
}
setEditorTheme();
$("#dark-mode-toggle").on("change", function () {
setTimeout(() => {
theme = $("#theme").val();
setEditorTheme();
}, 30);
});
$(window).on("beforeunload", function (e) {
@ -1119,4 +1143,6 @@ $(document).ready(() => {
e.returnValue = message; // Standard for most browsers
return message; // Required for some browsers
});
isInit = false;
});

View file

@ -422,4 +422,55 @@ $(document).ready(() => {
sessionStorage.setItem("notificationsRead", notificationsRead);
updateNotificationsBadge();
});
$("#dark-mode-toggle").on("change", function () {
const darkMode = $(this).prop("checked");
if (darkMode) {
$("html").removeClass("light-style").addClass("dark-style");
$(".btn-outline-dark")
.addClass("btn-outline-light")
.removeClass("btn-outline-dark");
$(".btn-dark").addClass("btn-light").removeClass("btn-dark");
$(".bg-white").addClass("bg-dark").removeClass("bg-white");
$(".bg-light-subtle")
.addClass("bg-dark-subtle")
.removeClass("bg-light-subtle");
} else {
$("html").removeClass("dark-style").addClass("light-style");
$(".btn-outline-light")
.addClass("btn-outline-dark")
.removeClass("btn-outline-light");
$(".btn-light").addClass("btn-dark").removeClass("btn-light");
$(".bg-dark").addClass("bg-white").removeClass("bg-dark");
$(".bg-dark-subtle")
.addClass("bg-light-subtle")
.removeClass("bg-dark-subtle");
}
$("#theme").val(darkMode ? "dark" : "light");
const rootUrl = $(this)
.data("root-url")
.replace(/\/profile$/, "/set_theme");
const csrfToken = $("#csrf_token").val();
const theme = darkMode ? "dark" : "light";
const data = new FormData();
data.append("theme", theme);
data.append("csrf_token", csrfToken);
fetch(rootUrl, {
method: "POST",
body: data,
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
// Handle success, redirect, etc.
})
.catch((error) => {
console.error("There was a problem with the fetch operation:", error);
});
});
});

View file

@ -23,7 +23,7 @@
} %}
<!DOCTYPE html>
<html lang="en"
class="{{ theme }}-style layout-navbar-fixed layout-menu-fixed"
class="{{ current_user.theme }}-style layout-navbar-fixed layout-menu-fixed"
data-theme="theme-default">
<head>
<meta charset="utf-8" />
@ -65,7 +65,7 @@
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.css') }}"
nonce="{{ style_nonce }}" />
{% if theme == 'dark' %}
{% if current_user.theme == 'dark' %}
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/themes/airbnb.dark.css') }}"
nonce="{{ style_nonce }}" />
@ -124,7 +124,7 @@
</head>
<body>
<input type="hidden" id="is-read-only" value="{{ is_readonly }}" />
<input type="hidden" id="theme" value="{{ theme }}" />
<input type="hidden" id="theme" value="{{ current_user.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-{% if theme == 'light' %}white{% else %}dark{% endif %} p-2 rounded shadow-sm"
class="nav nav-pills flex-column bg-{% if current_user.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-{% if theme == 'light' %}dark{% else %}light{% endif %} d-flex align-items-center h-100"
class="btn btn-sm btn-outline-{% if current_user.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-{% if theme == 'light' %}dark{% else %}light{% endif %} text-uppercase d-flex align-items-center"
class="btn btn-sm btn-{% if current_user.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-{% if theme == 'light' %}white{% else %}dark{% endif %} border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}"
<div class="bs-toast toast fade show bg-{% if current_user.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-{% if theme == 'light' %}white{% else %}dark{% endif %} 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

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block page %}
<div class="bg-light-subtle">
<div class="bg-{% if current_user.theme == 'light' %}light{% else %}dark{% endif %}-subtle">
<div class="login-background">
<div class="container-xxl">
<div class="authentication-wrapper authentication-basic container-p-y">

View file

@ -65,6 +65,30 @@
</div>
<!-- /Buttons -->
<ul class="navbar-nav flex-row align-items-center ms-auto">
<!-- Dark mode toggle -->
<li class="nav-item lh-1 me-4">
<div class="d-flex mt-1">
<input type="hidden"
id="csrf_token"
name="csrf_token"
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 %}>
<input id="dark-mode-toggle"
name="dark-mode-toggle"
class="form-check-input"
type="checkbox"
role="switch"
data-root-url="{{ url_for('profile') }}"
{% if current_user.theme == "dark" %}checked{% endif %}
{% if is_readonly %}disabled{% endif %} />
</div>
<label class="setting-checkbox-label d-flex align-items-center ps-0"
for="dark-mode-toggle">Dark</label>
</div>
</li>
<!--/ Dark mode toggle -->
<!-- Stars -->
<li class="d-none d-md-inline nav-item lh-1 me-4">
<a class="github-button"
@ -78,7 +102,7 @@
<!-- Version -->
<li class="nav-item lh-1 me-2 me-md-4">
<a role="button"
class="btn btn-sm btn-outline-{% if theme == 'light' %}dark{% else %}light{% endif %} px-2 px-md-3 position-relative"
class="btn btn-sm btn-outline-{% if current_user.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

@ -173,7 +173,7 @@
<div class="col-md-6">
<!-- Theme -->
<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>
<i class='bx bx-{% if current_user.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">
<form method="POST" action="{{ profile_url }}/edit">
@ -182,10 +182,10 @@
<div class="col-md-12 form-floating">
<select class="form-select" id="theme" name="theme">
<option value="light"
{% if theme == "light" %}selected{% endif %}>
{% if current_user.theme == "light" %}selected{% endif %}>
Light
</option>
<option value="dark" {% if theme == "dark" %}selected{% endif %}>Dark</option>
<option value="dark" {% if current_user.theme == "dark" %}selected{% endif %}>Dark</option>
</select>
<label for="theme">Theme</label>
</div>

View file

@ -57,8 +57,8 @@
</div>
</div>
<!-- Newsletter Signup Section -->
<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>
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-{% if current_user.theme == 'light' %}light{% else %}dark{% endif %}-subtle border-top">
<h5 class="mb-3 text-primary">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

@ -54,7 +54,7 @@
{% set content = message[0] %}
{% set category = message[1] %}
{% set datetime = message[2] %}
<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="col-12 bs-toast toast show bg-{% if current_user.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">
@ -78,8 +78,8 @@
{% endif %}
</div>
<!-- Newsletter Signup Section -->
<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>
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-{% if current_user.theme == 'light' %}light{% else %}dark{% endif %}-subtle border-top">
<h5 class="mb-3 text-primary">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

@ -9,6 +9,7 @@ from signal import SIGINT, signal, SIGTERM
from sys import path as sys_path
from time import time
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
@ -175,7 +176,6 @@ 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",
)
@ -442,6 +442,28 @@ def check_reloading():
return jsonify({"reloading": DATA.get("RELOADING", False)})
@app.route("/set_theme", methods=["POST"])
@login_required
def set_theme():
if DB.readonly or request.form["theme"] not in ("dark", "light"):
return
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": request.form["theme"],
}
ret = DB.update_ui_user(**user_data, old_username=current_user.get_id())
if ret:
LOGGER.error(f"Couldn't update the user {current_user.get_id()}: {ret}")
return Response(status=200, response=dumps({"message": "ok"}), content_type="application/json")
BLUEPRINTS = (about, services, profile, jobs, reports, totp, home, logout, instances, plugins, global_config, pro, cache, logs, login, configs, bans, setup)
for blueprint in BLUEPRINTS:
app.register_blueprint(blueprint)