Optimize and made some tweaks for QOL reasons in web UI

This commit is contained in:
Théophile Diot 2024-10-22 15:49:42 +02:00
parent 5529f312c1
commit 3a6caf1644
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
15 changed files with 206 additions and 138 deletions

View file

@ -46,7 +46,7 @@ def login_page():
if not ui_user.totp_secret:
flash(
f'Please enable two-factor authentication to secure your account <a href="{url_for("profile.profile_page", _anchor="security")}">here</a>',
"error",
"warning",
)
# redirect him to the page he originally wanted or to the home page

View file

@ -13,25 +13,6 @@ from app.routes.utils import cors_required, handle_error, verify_data_in_form
profile = Blueprint("profile", __name__)
BROWSERS = {
"Chrome": "bxl-chrome",
"Firefox": "bxl-firefox",
"Safari": "bx-compass",
"Edge": "bxl-edge",
"Opera": "bxl-opera",
"Internet Explorer": "bxl-internet-explorer",
}
OS = {
"Windows": "bxl-windows",
"Mac": "bxl-apple",
"Linux": "bxl-tux",
}
DEVICES = {
"PC": "bx-desktop",
}
def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Union[str, bool]], None, None], int]:
db_sessions = DB.get_ui_user_sessions(current_user.username, session.get("session_id"))
@ -53,7 +34,7 @@ def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Uni
for db_session in additional_sessions + db_sessions[(page - 1) * per_page : page * per_page]: # noqa: E203
ua_data = parse(db_session["user_agent"])
last_session = {
yield {
"current": db_session["id"] == session.get("session_id") if "session_id" in session else "id" not in db_session,
"browser": ua_data.get_browser(),
"os": ua_data.get_os(),
@ -67,20 +48,6 @@ def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Uni
),
}
for browser, icon in BROWSERS.items():
if browser in last_session["browser"]:
last_session["browser"] = f"<i class='bx {icon} text-primary'></i>&nbsp;{last_session['browser']}"
break
for os, icon in OS.items():
if os in last_session["os"]:
last_session["os"] = f"<i class='bx {icon} text-primary'></i>&nbsp;{last_session['os']}"
break
last_session["device"] = f"<i class='bx {DEVICES.get(last_session['device'], 'bx-mobile')} text-primary'></i>&nbsp;{last_session['device']}"
yield last_session
return session_generator(page, per_page), total_sessions

View file

@ -768,3 +768,8 @@ a.courier-prime:hover {
.courier-prime {
font-family: var(--font-courier-prime);
}
.warning-tooltip {
--bs-tooltip-bg: var(--bs-warning);
--bs-tooltip-color: var(--bs-white);
}

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 45.6 39.3">
<defs>
<style>
.cls-1 {
fill: #eaeaea;
}
.cls-2 {
fill: #efefef;
}
.cls-3 {
fill: #e5e5e5;
}
.cls-4 {
fill: #fff;
}
.cls-5 {
fill: #fafafa;
}
.cls-6 {
fill: #f5f5f5;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.7.1, SVG Export Plug-In . SVG Version: 1.2.0 Build 142) -->
<g>
<g id="Calque_2">
<g id="Calque_2-2">
<g id="Calque_1-2">
<g>
<polygon class="cls-5" points="40.4 5.2 31.4 10.4 14.1 10.4 5.2 5.2 13.9 0 31.6 0 40.4 5.2"/>
<polygon class="cls-3" points="45.6 12.8 34.9 19.4 31.4 10.4 40.4 5.2 45.6 12.8"/>
<polygon class="cls-1" points="45.6 12.8 22.7 39.3 34.9 19.4 45.6 12.8"/>
<polygon class="cls-2" points="14.1 10.4 10.3 19.4 0 12.8 5.2 5.2 14.1 10.4"/>
<polygon class="cls-1" points="22.7 39.3 0 12.8 10.3 19.4 22.7 39.3"/>
<polygon class="cls-6" points="34.9 19.4 22.7 39.3 10.3 19.4 34.9 19.4"/>
<polygon class="cls-4" points="34.9 19.4 10.3 19.4 14.1 10.4 31.4 10.4 34.9 19.4"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -111,6 +111,7 @@ $(document).ready(function () {
).first();
$("#select-type")
.parent()
.attr("data-bs-custom-class", "warning-tooltip")
.attr(
"data-bs-original-title",
`Switched to ${firstMultisiteType

View file

@ -431,6 +431,7 @@ $(function () {
chart: {
type: "bar",
width: "100%",
height: 400,
},
title: {
text: "Blocked Requests per Hour",

View file

@ -217,7 +217,7 @@ $(document).ready(() => {
$currentStepContainer.find(".plugin-setting").each(function () {
const $input = $(this);
const value = $input.val().trim();
const value = $input.val();
const isRequired = $input.prop("required");
const pattern = $input.attr("pattern");
const fieldName =

View file

@ -14,7 +14,6 @@ $(document).ready(() => {
}
let currentTemplate = $("#selected-template").val();
let currentTemplateMethod = $("#selected-template-method").val();
let currentMode = $("#selected-mode").val();
let currentType = $("#selected-type").val();
@ -159,7 +158,7 @@ $(document).ready(() => {
currentStepContainer.find(".plugin-setting").each(function () {
const $input = $(this);
const value = $input.val().trim();
const value = $input.val();
const isRequired = $input.prop("required");
const pattern = $input.attr("pattern");
const fieldName =

View file

@ -17,7 +17,7 @@ class News {
if (lastNews) {
this.render(JSON.parse(lastNews));
} else {
$.getJSON("https://www.bunkerweb.io/api/posts/0/2")
$.getJSON("https://www.bunkerweb.io/api/posts/0/3")
.done((res) => {
const reverseData = res.data.reverse();
this.render(reverseData);
@ -271,30 +271,41 @@ $(document).ready(() => {
const intervalTime = 7000;
let interval;
const $bannerText = $("#banner-text");
// Create a hidden element to measure the max height
const $measuringElement = $("<div>")
.css({
position: "absolute",
visibility: "hidden",
height: "auto",
width: $bannerText.width(),
whiteSpace: "nowrap",
})
.appendTo("body");
// Calculate the minimum height required for the banner text
let minHeight = 0;
newsItems.forEach((item) => {
$measuringElement.html(item);
minHeight = Math.max(minHeight, $measuringElement.outerHeight());
function calculateMinHeight() {
// Create a hidden element to measure the max height
const $measuringElement = $("<div>")
.css({
position: "absolute",
visibility: "hidden",
height: "auto",
width: $bannerText.width(), // Set width to match the current width of the banner text container
whiteSpace: "normal", // Change to normal for wrapping if needed
})
.appendTo("body");
// Calculate the minimum height required for the banner text
let minHeight = 0;
newsItems.forEach((item) => {
$measuringElement.html(item);
minHeight = Math.max(minHeight, $measuringElement.outerHeight());
});
// Set the minimum height to avoid layout shifts
$bannerText.css("min-height", minHeight);
// Remove the measuring element from the DOM
$measuringElement.remove();
}
// Calculate the min-height on page load
calculateMinHeight();
// Recalculate the min-height on window resize
$(window).on("resize", function () {
calculateMinHeight();
});
// Set the minimum height to avoid layout shifts
$bannerText.css("min-height", minHeight);
// Remove the measuring element from the DOM
$measuringElement.remove();
const news = new News();
news.init();
@ -380,4 +391,35 @@ $(document).ready(() => {
loadData();
$("#next-news").trigger("click");
}
var notificationsRead =
parseInt(sessionStorage.getItem("notificationsRead")) || 0;
function updateNotificationsBadge() {
const $notificationsBadge = $("#unread-notifications");
let unreadNotifications = parseInt($notificationsBadge.text());
unreadNotifications = isNaN(unreadNotifications) ? 0 : unreadNotifications;
if ($notificationsBadge.length) {
const updatedUnread = unreadNotifications - notificationsRead;
if (updatedUnread > 0) {
$notificationsBadge.text(updatedUnread);
$notificationsBadge.removeClass("d-none");
} else {
$notificationsBadge.addClass("d-none");
}
}
}
updateNotificationsBadge();
$("#notifications-button").on("click", function () {
const readNotifications = $(
"#notifications-toast-container .bs-toast",
).length;
notificationsRead = readNotifications;
sessionStorage.setItem("notificationsRead", notificationsRead);
updateNotificationsBadge();
});
});

View file

@ -141,7 +141,8 @@
<span class="d-none d-md-inline">Notifications</span>
</button>
{% if flash_messages %}
<span class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<span id="unread-notifications"
class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger d-none">
{{ flash_messages|length }}
<span class="visually-hidden">unread notifications</span>
</span>
@ -174,7 +175,7 @@
aria-pressed="true"
href="{{ url_for('pro') }}">
<span class="me-1 me-md-2 d-flex h-100 justify-content-center align-items-center">
<img src="{{ pro_diamond_url }}"
<img src="{{ url_for('static', filename='img/diamond-white.svg') }}"
alt="Pro plugin"
width="18px"
height="15.5px">

View file

@ -8,12 +8,12 @@
{% endif %}
<!-- flash message-->
{% for category, message in messages %}
<div class="bs-toast toast fade show {% if category == 'error' %}bg-danger{% elif category == 'warning' %}bg-warning{% else %}bg-primary text-white{% endif %}"
<div class="bs-toast toast fade show bg-white border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}"
role="alert"
aria-live="assertive"
aria-atomic="true">
<div class="toast-header">
<i class="d-block w-px-20 h-auto rounded me-2 tf-icons bx bx-bell"></i>
<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 bx-tada me-2"></i>
<span class="fw-medium me-auto">
{% if category != 'message' %}
{{ category|capitalize }}

View file

@ -2,7 +2,7 @@
{% block content %}
<!-- Content -->
<div class="row g-4">
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100 text-white {% if is_pro_version %}bg-primary{% else %}bg-bw-green{% endif %}">
{% if is_pro_version %}
<img class='position-absolute top-0 end-0 m-3'
@ -32,7 +32,7 @@
</a>
</div>
</div>
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<a role="button"
href="{{ url_for('instances') }}"
class="card p-4 position-relative shadow-sm rounded-3 h-100">
@ -59,7 +59,7 @@
</small>
</a>
</div>
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<a role="button"
href="{{ url_for('services') }}"
class="card p-4 position-relative shadow-sm rounded-3 h-100">
@ -84,7 +84,7 @@
</small>
</a>
</div>
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<a role="button"
href="{{ url_for('plugins') }}"
class="card p-4 position-relative shadow-sm rounded-3 h-100">
@ -111,21 +111,63 @@
</small>
</a>
</div>
<div class="col-sm-7 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="me-2 don-jose mb-0">Blocked Requests countries</h5>
<div class="col-md-7 row g-4 m-0 p-0">
<div class="col-12 col-lg-6 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">Request status</h5>
</div>
</div>
<div class="card-body">
<div class="d-flex justify-content-center align-items-center">
<div id="requests-data" class="visually-hidden">{{ request_errors|tojson }}</div>
<div id="requests-stats"></div>
</div>
</div>
</div>
<div class="card-body p-0">
<div id="requests-map-data" class="visually-hidden">{{ request_countries|tojson }}</div>
<div id="requests-map" class="rounded h-100"></div>
</div>
<div class="col-12 col-lg-6 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">Top 10 - Blocked ips</h5>
</div>
</div>
<div class="card-body">
{% if request_ips %}
{% set limited_request_ips = request_ips.items() | list %}
{% set top_ips = limited_request_ips[:10] %}
<div class="d-flex justify-content-center align-items-center mb-6">
<div id="requests-ips-data" class="visually-hidden">{{ request_ips|tojson }}</div>
<div id="requests-ips"></div>
</div>
{% else %}
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center mt-2">
<p class="g-3 p-2 text-primary rounded-lg fw-bold">No data to show</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-12 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="me-2 don-jose mb-0">Blocked Requests countries</h5>
</div>
</div>
<div class="card-body p-0">
<div id="requests-map-data" class="visually-hidden">{{ request_countries|tojson }}</div>
<div id="requests-map" class="rounded h-100"></div>
</div>
</div>
</div>
</div>
<div class="col-sm-5 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3">
<div class="col-md-5 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<div class="card-header p-2">
<div class="card-title mb-0 d-flex justify-content-between">
<h5 class="don-jose mb-0">News</h5>
@ -155,7 +197,7 @@
</div>
</div>
</div>
<div class="col-sm-6 mb-2">
<div class="col-md-6 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 bg-secondary text-white h-100">
<i class='bx bx-broadcast bx-sm position-absolute top-0 end-0 m-3 text-white'></i>
<p class="ps-4 fs-4 mb-2">
@ -165,7 +207,7 @@
</p>
</div>
</div>
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<a role="button"
class="card p-4 position-relative shadow-sm rounded-3 h-100 text-color-hover-danger"
href="{{ url_for('loading', next=url_for('reports') ) }}">
@ -185,7 +227,7 @@
</p>
</a>
</div>
<div class="col-sm-3 mb-2">
<div class="col-md-3 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100">
<i class='bx bx-globe bx-sm position-absolute top-0 end-0 m-3 text-danger'></i>
<p class="ps-4 fs-4 mb-2">
@ -201,47 +243,7 @@
</p>
</div>
</div>
<div class="col-sm-3 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">Request status</h5>
</div>
</div>
<div class="card-body">
<div class="d-flex justify-content-center align-items-center mb-6">
<div id="requests-data" class="visually-hidden">{{ request_errors|tojson }}</div>
<div id="requests-stats"></div>
</div>
</div>
</div>
</div>
<div class="col-sm-3 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3">
<div class="card-header p-2">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">Blocked Request ips</h5>
</div>
</div>
<div class="card-body">
{% if request_ips %}
{% set limited_request_ips = request_ips.items() | list %}
{% set top_ips = limited_request_ips[:10] %}
<div class="d-flex justify-content-center align-items-center mb-6">
<div id="requests-ips-data" class="visually-hidden">{{ request_ips|tojson }}</div>
<div id="requests-ips"></div>
</div>
{% else %}
<div class="d-flex align-items-center justify-content-center">
<div class="text-center mt-2">
<p class="g-3 p-2 text-primary rounded-lg fw-bold">No data to show</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6 mt-2 mb-2">
<div class="col-12 mt-2 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3">
<div class="card-header p-2">
<div class="card-title mb-0">

View file

@ -66,16 +66,18 @@
<td class="service-last-update-date">{{ service['last_update'].astimezone().isoformat() }}</td>
<td>
<div class="d-flex justify-content-center">
<a role="button"
class="btn btn-outline-primary btn-sm me-1"
href="https://{{ service['id'] }}"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="Access service {{ service['id'] }}"
target="_blank"
rel="noreferrer">
<i class="bx bx-link-external bx-xs"></i>
</a>
<div {% if service['is_draft'] %}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="Disabled by draft mode"{% endif %}>
<a role="button"
class="btn btn-outline-primary btn-sm me-1{% if service['is_draft'] %} disabled{% endif %}"
href="https://{{ service['id'] }}"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="Access service {{ service['id'] }}"
target="_blank"
rel="noreferrer">
<i class="bx bx-link-external bx-xs"></i>
</a>
</div>
<a role="button"
class="btn btn-primary btn-sm me-1"
href="{{ url_for("services") }}/{{ service['id'] }}"

View file

@ -44,15 +44,15 @@
<div id="data-notifications-container"
class="offcanvas-body row justify-content-center ps-3 pe-3{% if flash_messages %} pt-0{% endif %}">
{% if flash_messages %}
<div id="feedback-toast-container"
<div id="notifications-toast-container"
class="toast-container position-relative g-3 pt-6">
{% for message in flash_messages|reverse %}
{% set content = message[0] %}
{% set category = message[1] %}
{% set datetime = message[2] %}
<div class="col-12 bs-toast toast show border{% if category == 'error' %} border-danger{% elif category == 'warning' %} border-warning{% else %} border-primary{% endif %}">
<div class="toast-header{% if category == 'error' %} text-danger{% elif category == 'warning' %} text-warning{% else %} text-primary{% endif %}">
<i class="d-block w-px-20 h-auto rounded me-2 tf-icons bx bx-bell"></i>
<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="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">
{% if category != 'message' %}
{{ category|capitalize }}

View file

@ -370,7 +370,7 @@ def set_security_headers(response):
+ (
" connect-src *;"
if request.path.startswith(("/check", "/setup"))
else " connect-src 'self' https://api.github.com/repos/bunkerity/bunkerweb https://www.bunkerweb.io/api/posts/0/2;"
else " connect-src 'self' https://api.github.com/repos/bunkerity/bunkerweb https://www.bunkerweb.io/api/posts/0/3;"
)
)