Add some QOL tweaks discussed in an early reunion to web UI

This commit is contained in:
Théophile Diot 2024-10-21 16:17:43 +02:00
parent 793bef233b
commit 56ffc1b290
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
34 changed files with 821 additions and 497 deletions

View file

@ -137,38 +137,38 @@ def configs_new():
verify_data_in_form(
data={"service": None},
err_message="Missing service parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
service = request.form["service"]
services = BW_CONFIG.get_config(global_only=True, with_drafts=True, methods=False, filtered_settings=("SERVER_NAME"))["SERVER_NAME"].split(" ")
if service != "no service" and service not in services:
return handle_error(f"Service {service} does not exist.", "configs/new", True)
if service != "global" and service not in services:
return handle_error(f"Service {service} does not exist.", "configs.configs_new", True)
verify_data_in_form(
data={"type": None},
err_message="Missing type parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
config_type = request.form["type"]
if config_type not in CONFIG_TYPES:
return handle_error("Invalid type parameter on /configs/new.", "configs/new", True)
return handle_error("Invalid type parameter on /configs/new.", "configs.configs_new", True)
verify_data_in_form(
data={"name": None},
err_message="Missing name parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
config_name = request.form["name"]
if not match(r"^[\w_-]{1,64}$", config_name):
return handle_error("Invalid name parameter on /configs/new.", "configs/new", True)
return handle_error("Invalid name parameter on /configs/new.", "configs.configs_new", True)
verify_data_in_form(
data={"value": None},
err_message="Missing value parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
config_value = request.form["value"].replace("\r\n", "\n").strip()
@ -191,7 +191,7 @@ def configs_new():
"data": config_value,
"method": "ui",
}
if service != "no service":
if service != "global":
new_config["service_id"] = service
error = DB.upsert_custom_config(config_type, config_name, new_config, service_id=new_config.get("service_id"), new=True)
@ -216,15 +216,13 @@ def configs_new():
DATA["RELOADING"] = False
DATA.update({"RELOADING": True, "LAST_RELOAD": time(), "CONFIG_CHANGED": True})
Thread(target=create_config, args=(service if service != "no service" else None, config_type, config_name, config_value)).start()
Thread(target=create_config, args=(service if service != "global" else None, config_type, config_name, config_value)).start()
return redirect(
url_for(
"loading",
next=url_for(
"configs.configs_edit", service="global" if service == "no service" else service, config_type=config_type.lower(), name=config_name
),
message=f"Creating custom configuration {config_type}/{config_name}{' for service' + service if service != 'no service' else ''}",
next=url_for("configs.configs_edit", service="global" if service == "global" else service, config_type=config_type.lower(), name=config_name),
message=f"Creating custom configuration {config_type}/{config_name}{' for service' + service if service != 'global' else ''}",
)
)
@ -271,42 +269,42 @@ def configs_edit(service: str, config_type: str, name: str):
verify_data_in_form(
data={"service": None},
err_message="Missing service parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
new_service = request.form["service"]
services = BW_CONFIG.get_config(global_only=True, with_drafts=True, methods=False, filtered_settings=("SERVER_NAME"))["SERVER_NAME"].split(" ")
if new_service != "no service" and new_service not in services:
return handle_error(f"Service {new_service} does not exist.", "configs/new", True)
if new_service != "global" and new_service not in services:
return handle_error(f"Service {new_service} does not exist.", "configs.configs_new", True)
if new_service == "no service":
if new_service == "global":
new_service = None
verify_data_in_form(
data={"type": None},
err_message="Missing type parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
new_type = request.form["type"]
if new_type not in CONFIG_TYPES:
return handle_error("Invalid type parameter on /configs/new.", "configs/new", True)
return handle_error("Invalid type parameter on /configs/new.", "configs.configs_new", True)
new_type = new_type.lower()
verify_data_in_form(
data={"name": None},
err_message="Missing name parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
new_name = secure_filename(request.form["name"])
if not match(r"^[\w_-]{1,64}$", new_name):
return handle_error("Invalid name parameter on /configs/new.", "configs/new", True)
return handle_error("Invalid name parameter on /configs/new.", "configs.configs_new", True)
verify_data_in_form(
data={"value": None},
err_message="Missing value parameter on /configs/new.",
redirect_url="configs/new",
redirect_url="configs.configs_new",
next=True,
)
config_value = request.form["value"].replace("\r\n", "\n").strip()

View file

@ -1,10 +1,10 @@
from datetime import datetime
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from flask import Blueprint, flash as flask_flash, redirect, render_template, request, session, url_for
from flask_login import current_user, login_user
from app.dependencies import DB
from app.utils import LOGGER
from app.utils import LOGGER, flash
login = Blueprint("login", __name__)
@ -38,15 +38,21 @@ def login_page():
session["session_id"] = ret
if not login_user(ui_user, remember=request.form.get("remember-me") == "on"):
flash("Couldn't log you in, please try again", "error")
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"),)
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:
flash(
f'Please enable two-factor authentication to secure your account <a href="{url_for("profile.profile_page", _anchor="security")}">here</a>',
"error",
)
# redirect him to the page he originally wanted or to the home page
return redirect(url_for("loading", next=request.form.get("next") or url_for("home.home_page")))
else:
flash("Invalid username or password", "error")
flask_flash("Invalid username or password", "error")
fail = True
kwargs = {

View file

@ -1,6 +1,7 @@
from os import getenv
from secrets import choice
from string import ascii_letters, digits
# from secrets import choice
# from string import ascii_letters, digits
from threading import Thread
from time import time
@ -43,7 +44,9 @@ def setup_page():
if not ui_reverse_proxy:
required_keys.extend(["server_name", "ui_host", "ui_url", "auto_lets_encrypt", "lets_encrypt_staging", "email_lets_encrypt"])
if not admin_user:
required_keys.extend(["admin_username", "admin_email", "admin_password", "admin_password_check", "2fa_code"])
required_keys.extend(
["admin_username", "admin_email", "admin_password", "admin_password_check"]
) # TODO: add "2fa_code" back when TOTP is implemented in setup wizard
if not any(key in request.form for key in required_keys):
return handle_error(f"Missing either one of the following parameters: {', '.join(required_keys)}.", "setup")
@ -64,12 +67,12 @@ def setup_page():
totp_secret = None
totp_recovery_codes = None
if request.form["2fa_code"]:
totp_secret = session.pop("tmp_totp_secret", "")
if not TOTP.verify_totp(request.form["2fa_code"], totp_secret=totp_secret, user=current_user):
return handle_error("The totp token is invalid.", "setup")
# if request.form["2fa_code"]: # TODO: uncomment when TOTP is implemented in setup wizard
# totp_secret = session.pop("tmp_totp_secret", "")
# if not TOTP.verify_totp(request.form["2fa_code"], totp_secret=totp_secret, user=current_user):
# return handle_error("The totp token is invalid.", "setup")
totp_recovery_codes = TOTP.generate_recovery_codes()
# totp_recovery_codes = TOTP.generate_recovery_codes()
ret = DB.create_ui_user(
request.form["admin_username"],
@ -148,7 +151,6 @@ def setup_page():
auto_lets_encrypt=db_config.get("AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT", "no")),
lets_encrypt_staging=db_config.get("USE_LETS_ENCRYPT_STAGING", getenv("USE_LETS_ENCRYPT_STAGING", "no")),
email_lets_encrypt=db_config.get("EMAIL_LETS_ENCRYPT", getenv("EMAIL_LETS_ENCRYPT", "")),
random_url=f"/{''.join(choice(ascii_letters + digits) for _ in range(10))}",
totp_qr_image=totp_qr_image,
totp_secret=TOTP.get_totp_pretty_key(session.get("tmp_totp_secret", "")),
)

View file

@ -158,10 +158,11 @@ def handle_error(err_message: str = "", redirect_url: str = "", next: bool = Fal
if not redirect_url:
return False
redirect_url = f"{redirect_url}.{redirect_url}_page" if "." not in redirect_url else redirect_url
if next:
return redirect(url_for("loading", next=url_for(f"{redirect_url}.{redirect_url}_page")))
return redirect(url_for("loading", next=url_for(redirect_url)))
return redirect(url_for(f"{redirect_url}.{redirect_url}_page"))
return redirect(url_for(redirect_url))
def error_message(msg: str):

File diff suppressed because one or more lines are too long

View file

@ -86,13 +86,13 @@ $(document).ready(function () {
const setupUnbanModal = (bans) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100">
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
`<ul class="list-group list-group-horizontal w-100">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">IP Address</div>
</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="fw-bold">Time left</div>
</li>
</ul>`,
@ -102,19 +102,17 @@ $(document).ready(function () {
bans.forEach((ban) => {
// Create the list item using template literals
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal w-100"></ul>`,
);
const listItem =
$(`<li class="list-group-item align-items-center" style="flex: 1 0;">
const listItem = $(`<li class="list-group-item" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">${ban.ip}</div>
</div>
</li>`);
list.append(listItem);
const timeLeft =
$(`<li class="list-group-item align-items-center" style="flex: 1 0;">
const timeLeft = $(`<li class="list-group-item" style="flex: 1 0;">
<div class="ms-2 me-auto">
${ban.time_remaining}
</div>
@ -147,15 +145,28 @@ $(document).ready(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [1, 4],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (banNumber > 10) {
@ -170,8 +181,11 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -234,7 +248,7 @@ $(document).ready(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "unban_ips",
@ -275,6 +289,15 @@ $(document).ready(function () {
},
};
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
bans_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.unban_ips = {
text: '<span class="tf-icons bx bxs-buoy bx-18px me-2"></span>Unban',
action: function (e, dt, node, config) {
@ -416,12 +439,6 @@ $(document).ready(function () {
},
targets: 4,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[4, "asc"]],
autoFill: false,
@ -448,7 +465,6 @@ $(document).ready(function () {
},
initComplete: function (settings, json) {
$("#bans_wrapper .btn-secondary").removeClass("btn-secondary");
$("#bans_wrapper th").addClass("text-center");
if (isReadOnly)
$("#bans_wrapper .dt-buttons")
.attr(
@ -460,6 +476,8 @@ $(document).ready(function () {
},
});
bans_table.searchPanes.container().hide();
$("#bans").removeClass("d-none");
$("#bans-waiting").addClass("visually-hidden");
@ -488,6 +506,19 @@ $(document).ready(function () {
.each((el) => el.classList.remove("highlight"));
});
bans_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
bans_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (bans_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -24,15 +24,28 @@ $(document).ready(function () {
});
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [1, 2, 3, 4],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (cacheNumber > 10) {
@ -47,8 +60,11 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -107,6 +123,15 @@ $(document).ready(function () {
},
];
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
cache_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$(".cache-last-update-date").each(function () {
const isoDateStr = $(this).text().trim();
@ -182,12 +207,6 @@ $(document).ready(function () {
},
targets: 4,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[2, "asc"]],
autoFill: false,
@ -202,7 +221,6 @@ $(document).ready(function () {
},
initComplete: function (settings, json) {
$("#cache_wrapper .btn-secondary").removeClass("btn-secondary");
$("#cache_wrapper th").addClass("text-center");
},
});
@ -218,6 +236,9 @@ $(document).ready(function () {
"click",
);
if (!cacheJobNameSelection && !cachePluginSelection && !cacheServiceSelection)
cache_table.searchPanes.container().hide();
$("#cache").removeClass("d-none");
$("#cache-waiting").addClass("visually-hidden");

View file

@ -59,8 +59,7 @@ $(document).ready(function () {
$typeDropdownItems.each(function () {
const item = $(this);
item.toggle(
selectedService === "no service" ||
item.data("context") === "multisite",
selectedService === "global" || item.data("context") === "multisite",
);
});
};
@ -104,7 +103,7 @@ $(document).ready(function () {
selectedService = $(this).text().trim();
changeTypesVisibility();
if (
selectedService !== "no service" &&
selectedService !== "global" &&
$(`#config-type-${selectedType}`).data("context") !== "multisite"
) {
const firstMultisiteType = $(

View file

@ -47,16 +47,16 @@ $(document).ready(function () {
const setupDeletionModal = (configs) => {
const delete_modal = $("#modal-delete-configs");
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100">
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
`<ul class="list-group list-group-horizontal w-100">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">Name</div>
</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Type</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Service</div>
</li>
</ul>`,
@ -65,12 +65,11 @@ $(document).ready(function () {
configs.forEach((config) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal w-100"></ul>`,
);
// Create the list item using template literals
const listItem =
$(`<li class="list-group-item align-items-center" style="flex: 1 1 0;">
const listItem = $(`<li class="list-group-item" style="flex: 1 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">${config.name}</div>
</div>
@ -85,7 +84,7 @@ $(document).ready(function () {
// Clone the type element and append it to the list item
const typeClone = $(`#type-${id}`).clone();
const typeListItem = $(
`<li class="list-group-item d-flex align-items-center" style="flex: 1 1 0;"></li>`,
`<li class="list-group-item" style="flex: 1 1 0;"></li>`,
);
typeListItem.append(typeClone.removeClass("highlight"));
list.append(typeListItem);
@ -93,7 +92,7 @@ $(document).ready(function () {
// Clone the service element and append it to the list item
const serviceClone = $(`#service-${id}`).clone();
const serviceListItem = $(
`<li class="list-group-item d-flex align-items-center" style="flex: 1 1 0;"></li>`,
`<li class="list-group-item" style="flex: 1 1 0;"></li>`,
);
serviceListItem.append(serviceClone.removeClass("highlight"));
list.append(serviceListItem);
@ -121,15 +120,28 @@ $(document).ready(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [2, 3, 4, 5],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (configNumber > 10) {
@ -144,8 +156,11 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -208,7 +223,7 @@ $(document).ready(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "delete_configs",
@ -248,6 +263,15 @@ $(document).ready(function () {
return configs;
};
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
configs_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.create_config = {
text: '<span class="tf-icons bx bx-plus"></span>&nbsp;Create<span class="d-none d-md-inline"> new custom config</span>',
className: `btn btn-sm btn-outline-bw-green${
@ -307,62 +331,58 @@ $(document).ready(function () {
show: true,
options: [
{
label: '<i class="bx bx-xs bx-window-alt"></i>&nbsp;HTTP',
label: '<i class="bx bx-xs bx-window-alt"></i>HTTP',
value: function (rowData, rowIdx) {
return / HTTP$/.test(rowData[2].trim());
$(rowData[2]).text().trim() === "HTTP";
},
},
{
label: '<i class="bx bx-xs bx-window-alt"></i>&nbsp;SERVER_HTTP',
label: '<i class="bx bx-xs bx-window-alt"></i>SERVER_HTTP',
value: function (rowData, rowIdx) {
return / SERVER_HTTP$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "SERVER_HTTP";
},
},
{
label:
'<i class="bx bx-xs bx-window-alt"></i>&nbsp;DEFAULT_SERVER_HTTP',
'<i class="bx bx-xs bx-window-alt"></i>DEFAULT_SERVER_HTTP',
value: function (rowData, rowIdx) {
return / DEFAULT_SERVER_HTTP$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "DEFAULT_SERVER_HTTP";
},
},
{
label:
'<i class="bx bx-xs bx-shield-quarter"></i>&nbsp;MODSEC_CRS',
label: '<i class="bx bx-xs bx-shield-quarter"></i>MODSEC_CRS',
value: function (rowData, rowIdx) {
return / MODSEC_CRS$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "MODSEC_CRS";
},
},
{
label: '<i class="bx bx-xs bx-shield-alt-2"></i>&nbsp;MODSEC',
label: '<i class="bx bx-xs bx-shield-alt-2"></i>MODSEC',
value: function (rowData, rowIdx) {
return / MODSEC$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "MODSEC";
},
},
{
label: '<i class="bx bx-xs bx-network-chart"></i>&nbsp;STREAM',
label: '<i class="bx bx-xs bx-network-chart"></i>STREAM',
value: function (rowData, rowIdx) {
return / STREAM$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "STREAM";
},
},
{
label:
'<i class="bx bx-xs bx-network-chart"></i>&nbsp;SERVER_STREAM',
label: '<i class="bx bx-xs bx-network-chart"></i>SERVER_STREAM',
value: function (rowData, rowIdx) {
return / SERVER_STREAM$/.test(rowData[2].trim());
return $(rowData[2]).text().trim() === "SERVER_STREAM";
},
},
{
label:
'<i class="bx bx-xs bx-shield-alt"></i>&nbsp;CRS_PLUGINS_BEFORE',
label: '<i class="bx bx-xs bx-shield-alt"></i>CRS_PLUGINS_BEFORE',
value: function (rowData, rowIdx) {
return rowData[2].includes("BEFORE");
return $(rowData[2]).text().trim() === "CRS_PLUGINS_BEFORE";
},
},
{
label:
'<i class="bx bx-xs bx-shield-alt"></i>&nbsp;CRS_PLUGINS_AFTER',
label: '<i class="bx bx-xs bx-shield-alt"></i>CRS_PLUGINS_AFTER',
value: function (rowData, rowIdx) {
return rowData[2].includes("AFTER");
return $(rowData[2]).text().trim() === "CRS_PLUGINS_AFTER";
},
},
],
@ -394,12 +414,6 @@ $(document).ready(function () {
},
targets: 5,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[1, "asc"]],
autoFill: false,
@ -426,7 +440,6 @@ $(document).ready(function () {
},
initComplete: function (settings, json) {
$("#configs_wrapper .btn-secondary").removeClass("btn-secondary");
$("#configs_wrapper th").addClass("text-center");
if (isReadOnly)
$("#configs_wrapper .dt-buttons")
.attr(
@ -446,6 +459,9 @@ $(document).ready(function () {
"click",
);
if (!configTypeSelection && !configServiceSelection)
configs_table.searchPanes.container().hide();
$("#configs").removeClass("d-none");
$("#configs-waiting").addClass("visually-hidden");
@ -474,6 +490,19 @@ $(document).ready(function () {
.each((el) => el.classList.remove("highlight"));
});
configs_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
configs_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (configs_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -66,13 +66,13 @@ $(document).ready(function () {
$("#selected-instances-input").val(instances.join(","));
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100">
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
`<ul class="list-group list-group-horizontal w-100">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">Hostname</div>
</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="fw-bold">Health</div>
</li>
</ul>`,
@ -82,12 +82,11 @@ $(document).ready(function () {
const delete_modal = $("#modal-delete-instances");
instances.forEach((instance) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal w-100"></ul>`,
);
// Create the list item using template literals
const listItem =
$(`<li class="list-group-item align-items-center" style="flex: 1 0;">
const listItem = $(`<li class="list-group-item" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">${instance}</div>
</div>
@ -97,7 +96,7 @@ $(document).ready(function () {
// Clone the status element and append it to the list item
const statusClone = $("#status-" + instance).clone();
const statusListItem = $(
`<li class="list-group-item d-flex align-items-center justify-content-center" style="flex: 1 0;"></li>`,
`<li class="list-group-item" style="flex: 1 0;"></li>`,
);
statusListItem.append(statusClone.removeClass("highlight"));
list.append(statusListItem);
@ -139,15 +138,28 @@ $(document).ready(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [3, 4, 5, 6, 7],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (instanceNumber > 10) {
@ -162,8 +174,11 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -226,7 +241,7 @@ $(document).ready(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "ping_instances",
@ -287,6 +302,15 @@ $(document).ready(function () {
},
};
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
instances_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.ping_instances = {
text: '<span class="tf-icons bx bx-bell bx-18px me-2"></span>Ping',
action: function (e, dt, node, config) {
@ -527,12 +551,6 @@ $(document).ready(function () {
},
targets: 7,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[7, "desc"]],
autoFill: false,
@ -559,7 +577,6 @@ $(document).ready(function () {
},
initComplete: function (settings, json) {
$("#instances_wrapper .btn-secondary").removeClass("btn-secondary");
$("#instances_wrapper th").addClass("text-center");
if (isReadOnly)
$("#instances_wrapper .dt-buttons")
.attr(
@ -571,6 +588,8 @@ $(document).ready(function () {
},
});
instances_table.searchPanes.container().hide();
$("#instances").removeClass("d-none");
$("#instances-waiting").addClass("visually-hidden");
@ -599,6 +618,19 @@ $(document).ready(function () {
.each((el) => el.classList.remove("highlight"));
});
instances_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
instances_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (instances_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -4,15 +4,28 @@ $(document).ready(function () {
const isReadOnly = $("#is-read-only").val().trim() === "True";
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [2, 3, 4, 5],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (jobNumber > 10) {
@ -27,13 +40,20 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
layout.topStart.buttons = [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
{
extend: "colvis",
columns: "th:not(:first-child)",
@ -88,7 +108,7 @@ $(document).ready(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "run_jobs",
@ -135,6 +155,15 @@ $(document).ready(function () {
form.appendTo("body").submit();
};
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
jobs_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.run_jobs = {
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Run selected jobs',
action: function (e, dt, node, config) {
@ -280,12 +309,6 @@ $(document).ready(function () {
},
targets: 5,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[2, "asc"]],
autoFill: false,
@ -312,10 +335,11 @@ $(document).ready(function () {
},
initComplete: function (settings, json) {
$("#jobs_wrapper .btn-secondary").removeClass("btn-secondary");
$("#jobs_wrapper th").addClass("text-center");
},
});
jobs_table.searchPanes.container().hide();
$("#jobs").removeClass("d-none");
$("#jobs-waiting").addClass("visually-hidden");
@ -344,6 +368,19 @@ $(document).ready(function () {
.each((el) => el.classList.remove("highlight"));
});
jobs_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
jobs_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (jobs_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -9,16 +9,16 @@ $(document).ready(function () {
const setupDeletionModal = (plugins) => {
const delete_modal = $("#modal-delete-plugins");
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100">
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
`<ul class="list-group list-group-horizontal w-100">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">Name</div>
</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Version</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Type</div>
</li>
</ul>`,
@ -27,12 +27,11 @@ $(document).ready(function () {
plugins.forEach((plugin) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal w-100"></ul>`,
);
// Create the list item using template literals
const listItem =
$(`<li class="list-group-item align-items-center" style="flex: 1 1 0;">
const listItem = $(`<li class="list-group-item" style="flex: 1 1 0;">
<div class="ms-2 me-auto">
${$(`#name-${plugin}`).html()}
</div>
@ -42,7 +41,7 @@ $(document).ready(function () {
// Clone the version element and append it to the list item
const versionClone = $(`#version-${plugin}`).clone();
const versionListItem = $(
`<li class="list-group-item d-flex align-items-center" style="flex: 1 1 0;"></li>`,
`<li class="list-group-item" style="flex: 1 1 0;"></li>`,
);
versionListItem.append(versionClone.removeClass("highlight"));
list.append(versionListItem);
@ -50,7 +49,7 @@ $(document).ready(function () {
// Clone the type element and append it to the list item
const typeClone = $(`#type-${plugin}`).clone();
const typeListItem = $(
`<li class="list-group-item d-flex align-items-center" style="flex: 1 1 0;"></li>`,
`<li class="list-group-item" style="flex: 1 1 0;"></li>`,
);
typeListItem.append(typeClone.removeClass("highlight"));
list.append(typeListItem);
@ -152,15 +151,28 @@ $(document).ready(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [5, 6, 7],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (pluginNumber > 10) {
@ -175,8 +187,11 @@ $(document).ready(function () {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -239,7 +254,7 @@ $(document).ready(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "delete_plugins",
@ -265,6 +280,15 @@ $(document).ready(function () {
return plugins;
};
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
plugins_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.add_plugin = {
text: '<span class="tf-icons bx bx-plus"></span>&nbsp;Add<span class="d-none d-md-inline"> plugin(s)</span>',
className: `btn btn-sm btn-outline-bw-green${
@ -395,12 +419,6 @@ $(document).ready(function () {
},
targets: 7,
},
{
targets: "_all", // Target all columns
createdCell: function (td, cellData, rowData, row, col) {
$(td).addClass("align-items-center"); // Apply 'text-center' class to <td>
},
},
],
order: [[2, "asc"]],
autoFill: false,
@ -424,16 +442,9 @@ $(document).ready(function () {
1: "Selected 1 plugin",
},
},
// searchPanes: {
// collapse: {
// 0: '<span class="tf-icons bx bx-search bx-18px me-2"></span>Filters',
// _: '<span class="tf-icons bx bx-search bx-18px me-2"></span>Filters (%d)',
// },
// },
},
initComplete: function (settings, json) {
$("#plugins_wrapper .btn-secondary").removeClass("btn-secondary");
$("#plugins_wrapper th").addClass("text-center");
if (isReadOnly)
$("#plugins_wrapper .dt-buttons")
.attr(
@ -445,6 +456,8 @@ $(document).ready(function () {
},
});
plugins_table.searchPanes.container().hide();
$("#plugins").removeClass("d-none");
$("#plugins-waiting").addClass("visually-hidden");
@ -473,6 +486,19 @@ $(document).ready(function () {
.each((el) => el.classList.remove("highlight"));
});
plugins_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
plugins_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (plugins_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -291,15 +291,28 @@ $(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [1, 2, 3, 4, 5, 7],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (reportsNumber > 10) {
@ -308,7 +321,12 @@ $(function () {
if (reportsNumber > 50) menu.push(50);
if (reportsNumber > 100) menu.push(100);
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = { menu };
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -360,6 +378,15 @@ $(function () {
},
];
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
reports_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$(".report-date").each(function () {
const $this = $(this);
const isoDateStr = $this.text().trim();
@ -386,12 +413,6 @@ $(function () {
searchPanes: { show: true },
targets: [1, 2, 3, 4, 5, 7],
},
{
targets: "_all",
createdCell: function (td) {
$(td).addClass("align-items-center");
},
},
],
order: [[0, "desc"]],
autoFill: false,
@ -414,11 +435,12 @@ $(function () {
initComplete: function () {
const $wrapper = $("#reports_wrapper");
$wrapper.find(".btn-secondary").removeClass("btn-secondary");
$wrapper.find("th").addClass("text-center");
updateCountryTooltips();
},
});
reports_table.searchPanes.container().hide();
$("#reports").removeClass("d-none");
$("#reports-waiting").addClass("visually-hidden");

View file

@ -6,13 +6,13 @@ $(function () {
const setupModal = (services, modal) => {
const headerList = $(`
<ul class="list-group list-group-horizontal d-flex w-100">
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<ul class="list-group list-group-horizontal w-100">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">Service name</div>
</div>
</li>
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<li class="list-group-item bg-secondary text-white" style="flex: 1 0;">
<div class="fw-bold">Type</div>
</li>
</ul>
@ -22,11 +22,11 @@ $(function () {
services.forEach((service) => {
const sanitizedService = service.replace(/\./g, "-");
const serviceList = $(
'<ul class="list-group list-group-horizontal d-flex w-100"></ul>',
'<ul class="list-group list-group-horizontal w-100"></ul>',
);
const listItem = $(`
<li class="list-group-item align-items-center" style="flex: 1 0;">
<li class="list-group-item" style="flex: 1 0;">
<div class="ms-2 me-auto">
<div class="fw-bold">${service}</div>
</div>
@ -38,7 +38,7 @@ $(function () {
.clone()
.removeClass("highlight");
const typeListItem = $(`
<li class="list-group-item d-flex align-items-center justify-content-center" style="flex: 1 0;"></li>
<li class="list-group-item" style="flex: 1 0;"></li>
`);
typeListItem.append(typeClone);
serviceList.append(typeListItem);
@ -85,15 +85,28 @@ $(function () {
};
const layout = {
topStart: {},
bottomEnd: {},
bottom1: {
top1: {
searchPanes: {
viewTotal: true,
cascadePanes: true,
collapse: false,
columns: [2, 3, 4, 5],
},
},
topStart: {},
topEnd: {
buttons: [
{
extend: "toggle_filters",
className: "btn btn-sm btn-outline-primary toggle-filters",
},
],
search: true,
},
bottomStart: {
info: true,
},
bottomEnd: {},
};
if (serviceNumber > 10) {
@ -102,7 +115,12 @@ $(function () {
if (serviceNumber > num) menu.push(num);
});
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = { menu };
layout.bottomStart = {
pageLength: {
menu: menu,
},
info: true,
};
layout.bottomEnd.paging = true;
}
@ -158,7 +176,7 @@ $(function () {
{
extend: "collection",
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Actions',
className: "btn btn-sm btn-outline-primary",
className: "btn btn-sm btn-outline-primary action-button disabled",
buttons: [
{
extend: "convert_services",
@ -205,6 +223,15 @@ $(function () {
})
.get();
$.fn.dataTable.ext.buttons.toggle_filters = {
text: '<span class="tf-icons bx bx-filter bx-18px me-2"></span><span id="show-filters">Show</span><span id="hide-filters" class="d-none">Hide</span><span class="d-none d-md-inline"> filters</span>',
action: function (e, dt, node, config) {
services_table.searchPanes.container().slideToggle(); // Smoothly hide or show the container
$("#show-filters").toggleClass("d-none"); // Toggle the visibility of the 'Show' span
$("#hide-filters").toggleClass("d-none"); // Toggle the visibility of the 'Hide' span
},
};
$.fn.dataTable.ext.buttons.create_service = {
text: '<span class="tf-icons bx bx-plus"></span>&nbsp;Create<span class="d-none d-md-inline"> new service</span>',
className: `btn btn-sm btn-outline-bw-green${
@ -373,10 +400,6 @@ $(function () {
},
targets: 5,
},
{
targets: "_all",
createdCell: (td) => $(td).addClass("align-items-center"),
},
],
order: [[5, "desc"]],
autoFill: false,
@ -410,7 +433,6 @@ $(function () {
initComplete: function () {
const $wrapper = $("#services_wrapper");
$wrapper.find(".btn-secondary").removeClass("btn-secondary");
$wrapper.find("th").addClass("text-center");
if (isReadOnly) {
$wrapper
.find(".dt-buttons")
@ -424,6 +446,8 @@ $(function () {
},
});
services_table.searchPanes.container().hide();
$("#services").removeClass("d-none");
$("#services-waiting").addClass("visually-hidden");
@ -452,6 +476,19 @@ $(function () {
.each((el) => el.classList.remove("highlight"));
});
services_table.on("select", function (e, dt, type, indexes) {
// Enable the actions button
$(".action-button").removeClass("disabled");
});
services_table.on("deselect", function (e, dt, type, indexes) {
// If no rows are selected, disable the actions button
if (services_table.rows({ selected: true }).count() === 0) {
$(".action-button").addClass("disabled");
$("#select-all-rows").prop("checked", false);
}
});
// Event listener for the select-all checkbox
$("#select-all-rows").on("change", function () {
const isChecked = $(this).prop("checked");

View file

@ -9,10 +9,10 @@ $(document).ready(() => {
// Cache jQuery selectors for performance
const $window = $(window);
const $passwordInput = $("#password");
const $2faInput = $("#2fa_code");
// const $2faInput = $("#2fa_code");
const $confirmPasswordInput = $("#confirm_password");
const $serverNameInput = $("#SERVER_NAME");
const $overview2faEnabled = $("#overview-2fa-enabled");
// const $overview2faEnabled = $("#overview-2fa-enabled");
const $overviewUniqueServerName = $("#overview-unique-server-name");
const $saveSettingsButton = $(".save-settings");
const $previousStepButton = $(".previous-step");
@ -355,19 +355,9 @@ $(document).ready(() => {
if (adminEmail) {
$overviewEmail.val(adminEmail);
$overviewEmail.closest(".col-12").removeClass("d-none");
$overviewUsername.closest(".col-12").addClass("col-md-4");
$overviewPassword
.closest(".col-12")
.addClass("col-md-4")
.removeClass("col-sm-6");
$overviewEmail.parent().removeClass("d-none");
} else {
$overviewEmail.closest(".col-12").addClass("d-none");
$overviewUsername.closest(".col-12").removeClass("col-md-4");
$overviewPassword
.closest(".col-12")
.removeClass("col-md-4")
.addClass("col-sm-6");
$overviewEmail.parent().addClass("d-none");
}
$overviewUsername.val($("#username").val());
$overviewPassword.val($("#password").val());
@ -500,7 +490,7 @@ $(document).ready(() => {
formData.append("admin_email", $("#email").val());
formData.append("admin_password", $("#password").val());
formData.append("admin_password_check", $("#confirm_password").val());
formData.append("2fa_code", $("#2fa_code").val());
// formData.append("2fa_code", $("#2fa_code").val());
}
if (!uiReverseProxy) {
@ -526,7 +516,7 @@ $(document).ready(() => {
if (!ui_url.startsWith("/")) {
api = `${api}/`;
}
api = `${api}${ui_url}/check`;
api = `${api}${ui_url}${ui_url !== "/" ? "/" : ""}check`;
var redirect = `https://${server_name}/setup/loading?target_endpoint=${api}`;
} else {
var redirect = window.location.href.replace("setup", "login");
@ -572,35 +562,41 @@ $(document).ready(() => {
handleStepNavigation(isNext, confirmDNS);
});
$2faInput.on("input", function () {
if (uiUser) return;
const $this = $(this);
const value = $this.val();
const isValid = /^[0-9]{6}$/.test(value);
updateValidationState(this, isValid);
$overview2faEnabled
.find("i")
.toggleClass("bx-x text-danger bx-check text-success", false)
.toggleClass("bx-question-mark text-warning", value === "");
$overview2faEnabled.tooltip("enable");
if (value) {
if (isValid) {
$overview2faEnabled
.find("i")
.toggleClass("bx-x text-danger", false)
.toggleClass("bx-check text-success", true);
$overview2faEnabled.tooltip("disable");
} else {
$overview2faEnabled
.find("i")
.toggleClass("bx-check text-success", false)
.toggleClass("bx-x text-danger", true);
}
$(document).on("keydown", ".plugin-setting", function (e) {
if (e.key === "Enter" || e.keyCode === 13) {
$("#next-step").trigger("click");
}
});
// $2faInput.on("input", function () {
// if (uiUser) return;
// const $this = $(this);
// const value = $this.val();
// const isValid = /^[0-9]{6}$/.test(value);
// updateValidationState(this, isValid);
// $overview2faEnabled
// .find("i")
// .toggleClass("bx-x text-danger bx-check text-success", false)
// .toggleClass("bx-question-mark text-warning", value === "");
// $overview2faEnabled.tooltip("enable");
// if (value) {
// if (isValid) {
// $overview2faEnabled
// .find("i")
// .toggleClass("bx-x text-danger", false)
// .toggleClass("bx-check text-success", true);
// $overview2faEnabled.tooltip("disable");
// } else {
// $overview2faEnabled
// .find("i")
// .toggleClass("bx-check text-success", false)
// .toggleClass("bx-x text-danger", true);
// }
// }
// });
// Before Unload Event to Warn Users About Unsaved Changes
$window.on("beforeunload", function (e) {
const message =

View file

@ -260,9 +260,6 @@ class News {
// Initialize the News class
$(document).ready(() => {
const news = new News();
news.init();
// Define news items array
let newsItems = [
`Get the most of BunkerWeb by upgrading to the PRO version. More info and free trial <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=banner#pro">here</a>.`,
@ -274,6 +271,32 @@ $(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());
});
// 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();
function loadData() {
const nowStamp = Math.round(Date.now() / 1000);

View file

@ -93,7 +93,7 @@
{% endif %}
<!-- Page CSS -->
<!-- Page -->
{% if current_endpoint in ("setup", "login", "totp") %}
{% if current_endpoint in ("setup", "login", "totp", "loading") %}
<link rel="stylesheet"
href="{{ url_for('static', filename='css/pages/login.css') }}"
nonce="{{ style_nonce }}" />
@ -167,7 +167,7 @@
<!-- Main JS -->
<script src="{{ url_for('static', filename='js/main.js') }}"
nonce="{{ script_nonce }}"></script>
{% if current_endpoint not in ("setup", "login", "totp") %}
{% if current_endpoint not in ("login", "totp") %}
<script async
defer
src="{{ url_for('static', filename='js/utils.js') }}"

View file

@ -15,6 +15,7 @@
{% set breadcrumbs_ns.working_url = breadcrumb_url %}
{% endif %}
{% if current_endpoint != "configs" and "configs" in request.path %}
{% set breadcrumb_url = url_for("configs") %}
{% if loop.index == 3 %}
{% set breadcrumb_prefix = "?service=" + breadcrumb %}
{% elif loop.index == 4 %}
@ -22,6 +23,7 @@
{% endif %}
{% endif %}
{% if current_endpoint != "cache" and "cache" in request.path %}
{% set breadcrumb_url = url_for("cache") %}
{% if loop.index == 3 %}
{% set breadcrumb_prefix = "?service=" + breadcrumb %}
{% elif loop.index == 4 %}

View file

@ -51,6 +51,7 @@
<tr>
<td>
<a href="{{ url_for("cache") }}/{{ service_id }}/{{ cache['plugin_id'] }}/{{ cache['job_name'] }}/{{ cache['file_name'].replace('/', '_') if cache['file_name'].startswith('folder:') else cache['file_name'] }}"
class="d-flex align-items-center"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="View {{ cache['file_name'] }}"><i class="bx bx-show bx-xs"></i>&nbsp;{{ cache['file_name'] }}</a>
@ -60,6 +61,7 @@
<td id="service-{{ cache['job_name'] }}-{{ service_id.replace('.', '_') }}-{{ cache['file_name'].replace('.', '_') }}">
{% if cache["service_id"] %}
<a href="{{ url_for("services") }}/{{ cache['service_id'] }}"
class="d-flex align-items-center"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="Edit service {{ cache['service_id'] }}"><i class="bx bx-edit bx-xs"></i>&nbsp;{{ cache["service_id"] }}</a>

View file

@ -4,7 +4,7 @@
<input type="hidden"
id="selected-service"
name="selected_service"
value="{{ config_service if config_service and config_service != "global" else "no service" }}">
value="{{ config_service if config_service and config_service != "global" else "global" }}">
<input type="hidden"
id="selected-type"
name="selected_type"
@ -19,7 +19,7 @@
{% if not config_template and config_method and config_method != "ui" or is_readonly %}
<button type="button" class="btn btn-sm btn-secondary ms-2 disabled">
<i class="bx bx-xs bx-cube"></i>
&nbsp;Service: {{ config_service or "no service" }}
&nbsp;Service: {{ config_service or "global" }}
</button>
<button type="button" class="btn btn-sm btn-secondary ms-2 disabled">
<i class="bx bx-xs bx-window"></i>
@ -52,7 +52,7 @@
class="dropdown-item{% if not config_service or config_service == 'global' %} active{% endif %}"
role="tab"
data-bs-toggle="tab"
{% if not config_service %}aria-selected="true"{% endif %}>no service</button>
{% if not config_service %}aria-selected="true"{% endif %}>global</button>
</li>
{% for service in services %}
<li class="nav-item">

View file

@ -75,18 +75,22 @@
<td></td>
<td>
<a href="{{ url_for("configs") }}/{{ service_id }}/{{ config['type'] }}/{{ config['name'] }}"
class="d-flex align-items-center"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="{% if config['method'] != 'ui' and not config['template'] or is_readonly %}View{% else %}Edit{% endif %} custom config {{ config['name'] }}"><i class="bx bx-{% if config['method'] != 'ui' and not config['template'] or is_readonly %}show{% else %}edit{% endif %} bx-xs"></i>&nbsp;{{ config["name"] }}</a>
</td>
<td id="type-{{ config['type'] }}-{{ service_id.replace('.', '_') }}-{{ config['name'] }}">
<i class="bx bx-{{ config_icon }}"></i>
{{ config["type"]|upper }}
<div class="d-flex align-items-center">
<i class="bx bx-{{ config_icon }} me-1"></i>
{{ config["type"]|upper }}
</div>
</td>
<td>{{ config["method"] }}</td>
<td id="service-{{ config['type'] }}-{{ service_id.replace('.', '_') }}-{{ config['name'] }}">
{% if config["service_id"] %}
<a href="{{ url_for("services") }}/{{ config['service_id'] }}"
class="d-flex align-items-center"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="Edit service {{ config['service_id'] }}"><i class="bx bx-edit bx-xs"></i>&nbsp;{{ config["service_id"] }}</a>

View file

@ -18,7 +18,7 @@
{% if category != 'message' %}
{{ category|capitalize }}
{% else %}
Success
Info
{% endif %}
</span>
<small class="text-body-secondary">just now</small>

View file

@ -81,13 +81,15 @@
{% endif %}
</td>
<td>
{% if instance.type == "container" %}
<i class="bx bxl-docker"></i>&nbsp;Container
{% elif instance.type == "pod" %}
<i class="bx bxl-kubernetes"></i>&nbsp;Pod
{% else %}
<i class="bx bx-microchip"></i>&nbsp;Static
{% endif %}
<div class="d-flex align-items-center">
{% if instance.type == "container" %}
<i class="bx bxl-docker"></i>&nbsp;Container
{% elif instance.type == "pod" %}
<i class="bx bxl-kubernetes"></i>&nbsp;Pod
{% else %}
<i class="bx bx-microchip"></i>&nbsp;Static
{% endif %}
</div>
</td>
<td class="instance-creation-date">{{ instance.creation_date.astimezone().isoformat() }}</td>
<td class="instance-last-seen-date">{{ instance.last_seen.astimezone().isoformat() }}</td>

View file

@ -46,7 +46,9 @@
<td>{{ job }}</td>
<td>{{ job_data["plugin_id"] }}</td>
<td>
<i class="bx {% if job_data['every'] == 'once' %}bx-revision{% elif job_data['every'] == 'day' %}bx-calendar-event{% elif job_data['every'] == 'week' %}bx-calendar-week{% else %}bxs-hourglass{% endif %}"></i>&nbsp;{{ job_data["every"] }}
<div class="d-flex align-items-center">
<i class="bx {% if job_data['every'] == 'once' %}bx-revision{% elif job_data['every'] == 'day' %}bx-calendar-event{% elif job_data['every'] == 'week' %}bx-calendar-week{% else %}bxs-hourglass{% endif %}"></i>&nbsp;{{ job_data["every"] }}
</div>
</td>
<td class="text-center">
<i class="bx bx-sm bx-{% if job_data['reload'] %}check text-success{% else %}x text-danger{% endif %}"></i>
@ -121,7 +123,7 @@
<i class="bx bx-data"></i>
<span class="d-none d-md-inline">&nbsp;Cache</span>
</button>
<ul class="dropdown-menu nav-pills max-vh-60 overflow-auto pt-0"
<ul class="dropdown-menu nav-pills max-vh-60 overflow-auto pt-0 pb-0"
role="tablist">
{% for cache in job_data['cache'] %}
{% set service_id = cache['service_id'] if cache['service_id'] else 'global' %}

View file

@ -1,10 +1,27 @@
{% extends "base.html" %}
{% block page %}
<div class="layout-wrapper bg-primary">
<div class="layout-container">
<div class="content-wrapper">
<div class="container-xxl d-flex justify-content-center align-items-center min-vh-100">
<div class="layout-main-wrapper mt-0 pb-10">
<div class="container-xxl">
<div class="authentication-wrapper authentication-basic container-p-y">
<div class="position-absolute top-0 p-4 pe-6 ps-6 w-70">
<div class="bg-bw-green position-relative w-100 p-2 text-white rounded fw-bold overflow-hidden">
<div class="d-flex justify-content-between align-items-center">
<div class="flex-grow-1 overflow-hidden me-2">
<div id="banner-container">
<p id="banner-text" class="mb-0 slide-in">
Get the most of BunkerWeb by upgrading to the PRO version. More info and free trial <a class="light-href text-white-80"
target="_blank"
rel="noopener"
href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=banner#pro">here</a>.
</p>
</div>
</div>
<i id="next-news" role="button" class='bx bx-sm bx-chevron-right'></i>
</div>
</div>
</div>
<div class="container-xxl d-flex justify-content-center align-items-center">
<div class="authentication-inner loading">
<div class="layout-main-wrapper mt-0 mb-0">
<div class="layout-main-placeholder d-flex justify-content-center align-items-center">
<lottie-player src="{{ url_for('static', filename='json/blockhaus.min.json') }}" background="transparent" speed="1" class="img-fluid" loop autoplay></lottie-player>
</div>
@ -13,23 +30,6 @@
<h3 class="mb-0 don-jose">{{ message }}</h3>
</div>
{% endif %}
<div class="fixed-bottom p-4 pe-6 ps-6">
<div class="bg-bw-green position-relative w-100 p-2 text-white rounded fw-bold overflow-hidden">
<div class="d-flex justify-content-between align-items-center">
<div class="flex-grow-1 overflow-hidden me-2">
<div id="banner-container">
<p id="banner-text" class="mb-0 slide-in">
Get the most of BunkerWeb by upgrading to the PRO version. More info and free trial <a class="light-href text-white-80"
target="_blank"
rel="noopener"
href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=banner#pro">here</a>.
</p>
</div>
</div>
<i id="next-news" role="button" class='bx bx-sm bx-chevron-right'></i>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -75,5 +75,6 @@
</div>
</div>
</div>
<!-- / Content -->
</div>
<!-- / Content -->
{% endblock %}

View file

@ -107,13 +107,13 @@
data-type="{{ plugin_data['type'] }}">
<div class="card-header d-flex justify-content-between align-items-center mw-100">
<div class="pt-1 flex-grow-1 me-2" style="min-width: 0;">
<h5 class="card-title d-inline border p-2 don-jose{{ plugin_types[plugin_data['type']].get('text-class', '') }}{{ plugin_types[plugin_data['type']].get('title-class', '') }}">
<h5 class="card-title d-flex align-items-center don-jose{{ plugin_types[plugin_data['type']].get('text-class', '') }}{{ plugin_types[plugin_data['type']].get('title-class', '') }}">
{{ plugin_data["name"] }}&nbsp;&nbsp;v{{ plugin_data["version"] }}&nbsp;&nbsp;{{ plugin_types[plugin_data["type"]].get('icon', '<img src="' + pro_diamond_url + '"
alt="Pro plugin"
width="18px"
height="15.5px">') |safe }}
</h5>
<p class="card-subtitle text-muted text-truncate mt-3 courier-prime">{{ plugin_data["description"] }}</p>
<p class="card-subtitle text-muted text-truncate mt-1 courier-prime">{{ plugin_data["description"] }}</p>
</div>
<div class="d-flex flex-grow-0 flex-shrink-0 justify-content-end align-items-center">
<a href="https://docs.bunkerweb.io/latest/quickstart-guide/?utm_campaign=self&utm_source=ui#protect-udptcp-applications"
@ -134,12 +134,12 @@
{% if plugin_data["page"] %}
<a href="{{ url_for("plugins") }}/{{ plugin }}"
class="btn btn-sm btn-primary rounded-pill ms-2">
<i class="bx bxs-file-html"></i>&nbsp;Custom page
<i class="bx bxs-file-html"></i>&nbsp;Plugin page
</a>
{% endif %}
</div>
</div>
<div class="card-body row pb-0">
<div class="card-body row g-2 gx-6 pb-0">
{% for setting, setting_data in filtered_settings.items() if not setting_data.get('multiple', false) and setting not in blacklisted_settings and (not service_endpoint or setting_data['context'] == "multisite") %}
{% set setting_id_prefix = "setting-" + plugin + "-" %}
{% set setting_config = config.get(setting, {}) %}
@ -230,7 +230,7 @@
</h5>
<p class="card-subtitle text-muted mt-2">This is where you can configure multiple settings for this plugin.</p>
</div>
<div class="card-body row pt-0">
<div class="card-body row g-2 gx-6 pt-0">
{% for multiple, multiples in plugin_multiples.items() %}
<div id="multiple-{{ plugin }}-{{ multiple }}"
class="col-12{% if plugin_multiples|length > 1 %} col-md-6{% endif %}{% if plugin_multiples|length > 2 and not settings|length > 1 %} col-lg-4{% endif %}">
@ -279,7 +279,7 @@
</div>
<div id="multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
class="collapse show multiple-collapse pt-0">
<div class="row mt-2 pt-2">
<div class="row g-2 gx-6 mt-2 pt-2">
{% for setting, setting_data in settings.items() if setting not in blacklisted_settings %}
{% set setting_config = config.get(setting, {}) %}
{% set setting_default = setting_data.get("default", "") %}

View file

@ -76,13 +76,13 @@
aria-labelledby="navs-templates-{{ template }}-tab">
<div class="card-header d-flex align-items-center mw-100">
<div class="pt-1">
<h5 class="card-title d-inline border p-2 don-jose{{ plugin_types[template_plugin['type']].get('text-class', '') }}{{ plugin_types[template_plugin['type']].get('title-class', '') }}">
<h5 class="card-title d-flex align-items-center don-jose{{ plugin_types[template_plugin['type']].get('text-class', '') }}{{ plugin_types[template_plugin['type']].get('title-class', '') }}">
{{ template|capitalize }}&nbsp;&nbsp;{{ plugin_types[template_plugin["type"]].get('icon', '<img src="' + pro_diamond_url + '"
alt="Pro plugin"
width="18px"
height="15.5px">') |safe }}
</h5>
<p class="card-subtitle text-muted text-truncate mt-3 courier-prime">{{ template_data["name"] }}</p>
<p class="card-subtitle text-muted text-truncate mt-1 courier-prime">{{ template_data["name"] }}</p>
</div>
</div>
<div class="card-body">
@ -279,7 +279,9 @@
<h5 class="text-dark fw-bold mt-5">
This service currently uses template "{{ selected_template }}" which is of method "{{ template_method }}"
</h5>
<p class="text-muted">Therefore no changes outside of the current template are allowed, please make changes to the current template or via the advanced/raw mode</p>
<p class="text-muted">
Therefore no changes outside of the current template are allowed, please make changes to the current template or via the advanced/raw mode
</p>
</div>
{% endif %}
</div>

View file

@ -128,7 +128,7 @@
<li>
<a class="dropdown-item d-flex align-items-center"
href="{{ url_for('logout') }}">
<i class="bx bx-power-off bx-md me-2"></i><span>Log Out</span>
<i class="bx bx-power-off bx-sm me-2"></i><span>Log Out</span>
</a>
</li>
</ul>

View file

@ -58,7 +58,7 @@
</td>
<td>{{ plugin_data["description"] }}</td>
<td id="version-{{ plugin }}">
<div class="{{ plugin_types[plugin_data['type']].get('text-class', '') }}">{{ plugin_data["version"] }}</div>
<div class="{{ plugin_types[plugin_data['type']].get('text-class', '') }}">v{{ plugin_data["version"] }}</div>
</td>
<td class="text-center">
<div data-bs-toggle="tooltip"
@ -68,13 +68,13 @@
</td>
</div>
<td id="type-{{ plugin }}">
<div {% if not is_pro_version and plugin_data['type'] == "pro" %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" data-bs-original-title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
<div class="d-flex align-items-center" {% if not is_pro_version and plugin_data['type'] == "pro" %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" data-bs-original-title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
{% endif %}
>
{% if plugin_data["type"] == "pro" %}
<a href="{{ url_for('pro') }}" target="_blank" rel="noopener" {% else %}
<div {% endif %}
class="{{ plugin_types[plugin_data['type']].get('text-class', '') }}">
class="d-flex align-items-center{{ plugin_types[plugin_data['type']].get('text-class', '') }}">
{{ plugin_types[plugin_data["type"]].get('icon', '<img src="' + pro_diamond_url + '"
alt="Pro plugin"
width="16px"

View file

@ -45,6 +45,7 @@
<td></td>
<td>
<a href="{{ url_for("services") }}/{{ service['id'] }}"
class="d-flex align-items-center"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="{% if service['method'] == 'autoconf' or is_readonly %}View{% else %}Edit{% endif %} service {{ service['id'] }}"><i class="bx bx-{% if service['method'] == 'autoconf' or is_readonly %}show{% else %}edit{% endif %} bx-xs"></i>&nbsp;{{ service["id"] }}</a>

View file

@ -10,6 +10,36 @@
name="csrf_token"
value="{{ csrf_token() }}" />
<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">
<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"
aria-pressed="true"
href="https://panel.bunkerweb.io/order/support/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener">
<span class="bx bx-help-circle me-0 me-md-2"></span>
<span class="d-none d-md-inline">Need help?</span>
</a>
</li>
<li class="position-relative">
<button id="news-button"
type="button"
class="btn btn-sm btn-dark text-uppercase d-flex align-items-center"
aria-pressed="true"
data-bs-toggle="offcanvas"
data-bs-target="#side-offcanvas-news"
aria-controls="side-offcanvas-news">
<span class="bx bx-news me-0 me-md-2"></span>
<span class="d-none d-md-inline">News</span>
</button>
</li>
</ul>
</div>
</div>
<div class="authentication-wrapper authentication-basic container-p-y">
<div class="authentication-inner w-100">
<!-- Setup -->
@ -96,11 +126,13 @@
</span>
</div>
</div>
{% set setting = "username" %}
{% set setting_value = username %}
{% set setting_data = {"type": "text", "id": "username", "regex": "^.{1,256}$"} %}
{% set required = true %}
{% include "models/input_setting.html" %}
<input id="username"
name="username"
type="text"
class="form-control plugin-setting mt-1"
aria-labelledby="label-username"
pattern="^.{1,256}$"
required />
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
@ -116,15 +148,17 @@
</span>
</div>
</div>
{% set setting = "email" %}
{% set setting_data = {"type": "email", "id": "email", "regex": "^.{1,256}$"} %}
{% set required = false %}
{% include "models/input_setting.html" %}
<input id="email"
name="email"
type="email"
placeholder="John.doe@example.com"
class="form-control plugin-setting mt-1"
aria-labelledby="label-email" />
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-password"
for="password"
class="form-label fw-semibold text-truncate">
Admin Password
</label>
@ -137,11 +171,20 @@
</span>
</div>
</div>
{% set setting = "password" %}
{% set setting_value = password %}
{% set setting_data = {"type": "password", "id": "password", "regex": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[ -~]).{8,}$"} %}
{% set required = true %}
{% include "models/input_setting.html" %}
<div class="form-password-toggle">
<div class="input-group input-group-merge mt-1">
<input class="form-control plugin-setting"
type="password"
id="password"
name="password"
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-labelledby="label-password"
autocomplete="off"
required
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ -~]).{8,}$" />
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
</div>
</div>
<div class="mt-3">
<ul class="list-unstyled" id="password-requirements">
<li id="length-check">
@ -165,8 +208,8 @@
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-confirm_password"
for="confirm_password"
class="form-label fw-semibold text-truncate">
Confirm Password
</label>
@ -179,34 +222,34 @@
</span>
</div>
</div>
{% set setting = "confirm_password" %}
{% set setting_data = {"type": "password", "id": "confirm_password", "regex": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[ -~]).{8,}$"} %}
{% set required = true %}
{% include "models/input_setting.html" %}
<div class="form-password-toggle">
<div class="input-group input-group-merge mt-1">
<input class="form-control plugin-setting"
type="password"
id="confirm_password"
name="confirm_password"
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-labelledby="label-confirm_password"
autocomplete="off"
required
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ -~]).{8,}$" />
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
</div>
</div>
</div>
<div class="pt-1 pb-4">
<!-- <div class="pt-1 pb-4">
<h5 class="mb-1 fw-bold">Enable 2FA</h5>
<p class="card-subtitle text-muted">Enable Two-Factor Authentication (Strongly Recommended)</p>
</div>
<div class="col-12 pb-3 d-flex justify-content-center">
<div class="g-3 w-75">
<div class="d-flex justify-content-center">
<img class="img-fluid"
src="{{ totp_qr_image }}"
alt="2FA QR Code"
height="200"
width="200" />
<img class="img-fluid" src="{{ totp_qr_image }}" alt="2FA QR Code" height="200" width="200" />
</div>
<div class="form-password-toggle">
<label for="secret_token" class="form-label text-start d-block">Secret Token</label>
<div class="input-group input-group-merge">
<input type="password"
id="secret_token"
name="secret_token"
class="form-control"
placeholder="Secret Token"
value="{{ totp_secret }}"
readonly />
<input type="password" id="secret_token" name="secret_token" class="form-control" placeholder="Secret Token" value="{{ totp_secret }}" readonly />
{% if request.is_secure %}
<span class="input-group-text cursor-pointer copy-to-clipboard">
<i class="bx bx-copy-alt"></i>
@ -219,7 +262,7 @@
<small class="form-text text-muted">Save it in a safe place, you will need the generated code to setup your 2FA in the last step.</small>
</div>
</div>
</div>
</div> -->
</div>
{% else %}
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
@ -240,8 +283,8 @@
<div class="row pb-0">
<div class="col-12 pb-6">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-SERVER_NAME"
for="SERVER_NAME"
class="form-label fw-semibold text-truncate">Server name</label>
<div class="d-flex align-items-center">
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
@ -252,16 +295,20 @@
</span>
</div>
</div>
{% set setting = "SERVER_NAME" %}
{% set setting_value = "www.example.com" %}
{% set setting_data = {"type": "text", "id": "SERVER_NAME", "regex": "^.+$"} %}
{% set required = true %}
{% include "models/input_setting.html" %}
<input id="SERVER_NAME"
name="SERVER_NAME"
type="text"
placeholder="www.example.com"
value="www.example.com"
class="form-control plugin-setting mt-1"
aria-labelledby="label-SERVER_NAME"
pattern="^.+$"
required />
</div>
<div class="col-12 col-sm-6 pb-6">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-REVERSE_PROXY_HOST"
for="REVERSE_PROXY_HOST"
class="form-label fw-semibold text-truncate">UI Host</label>
<div class="d-flex align-items-center">
{% if ui_host %}
@ -280,11 +327,14 @@
</span>
</div>
</div>
{% set setting = "REVERSE_PROXY_HOST" %}
{% set setting_value = ui_host %}
{% set setting_data = {"type": "text", "id": "REVERSE_PROXY_HOST", "regex": "^.+$"} %}
{% set required = true %}
{% include "models/input_setting.html" %}
<input id="REVERSE_PROXY_HOST"
name="REVERSE_PROXY_HOST"
type="text"
value="{{ ui_host }}"
class="form-control plugin-setting mt-1"
aria-labelledby="label-REVERSE_PROXY_HOST"
pattern="^.+$"
required />
</div>
<div class="col-12 col-sm-6 pb-6">
<div class="d-flex justify-content-between align-items-center">
@ -292,6 +342,12 @@
for="username"
class="form-label fw-semibold text-truncate">UI URL</label>
<div class="d-flex align-items-center">
<span class="badge badge-center rounded-pill bg-warning text-dark d-flex align-items-center justify-content-center p-1 me-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="We recommend using a random URL">
<span class="bx bx-shield bx-xs"></span>
</span>
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
@ -300,16 +356,18 @@
</span>
</div>
</div>
{% set setting = "REVERSE_PROXY_URL" %}
{% set setting_value = random_url %}
{% set setting_data = {"type": "text", "id": "REVERSE_PROXY_URL", "regex": "^.+$"} %}
{% set required = false %}
{% include "models/input_setting.html" %}
<input id="REVERSE_PROXY_URL"
name="REVERSE_PROXY_URL"
type="text"
value="/"
class="form-control plugin-setting mt-1"
aria-labelledby="label-REVERSE_PROXY_URL"
pattern="^.*$" />
</div>
<div class="col-6 col-sm-3 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-AUTO_LETS_ENCRYPT"
for="AUTO_LETS_ENCRYPT"
class="form-label fw-semibold text-truncate">
Auto Let's Encrypt
</label>
@ -330,15 +388,20 @@
</span>
</div>
</div>
{% set setting = "AUTO_LETS_ENCRYPT" %}
{% set setting_value = auto_lets_encrypt %}
{% set setting_data = {"id": "AUTO_LETS_ENCRYPT"} %}
{% include "models/checkbox_setting.html" %}
<div class="form-check form-switch mt-1">
<input id="AUTO_LETS_ENCRYPT"
name="AUTO_LETS_ENCRYPT"
class="form-check-input"
type="checkbox"
role="switch"
aria-labelledby="label-AUTO_LETS_ENCRYPT"
{% if auto_lets_encrypt == "yes" %}checked{% endif %} />
</div>
</div>
<div class="col-6 col-sm-3 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-USE_LETS_ENCRYPT_STAGING"
for="USE_LETS_ENCRYPT_STAGING"
class="form-label fw-semibold text-truncate">
Use Let's Encrypt Staging
</label>
@ -359,15 +422,20 @@
</span>
</div>
</div>
{% set setting = "USE_LETS_ENCRYPT_STAGING" %}
{% set setting_value = lets_encrypt_staging %}
{% set setting_data = {"id": "USE_LETS_ENCRYPT_STAGING"} %}
{% include "models/checkbox_setting.html" %}
<div class="form-check form-switch mt-1">
<input id="USE_LETS_ENCRYPT_STAGING"
name="USE_LETS_ENCRYPT_STAGING"
class="form-check-input"
type="checkbox"
role="switch"
aria-labelledby="label-USE_LETS_ENCRYPT_STAGING"
{% if lets_encrypt_staging == "yes" %}checked{% endif %} />
</div>
</div>
<div class="col-sm-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
<label id="label-EMAIL_LETS_ENCRYPT"
for="EMAIL_LETS_ENCRYPT"
class="form-label fw-semibold text-truncate">
Email for Let's Encrypt
</label>
@ -388,10 +456,13 @@
</span>
</div>
</div>
{% set setting = "EMAIL_LETS_ENCRYPT" %}
{% set setting_value = email_lets_encrypt %}
{% set setting_data = {"type": "text", "id": "EMAIL_LETS_ENCRYPT", "regex": "^.*$"} %}
{% include "models/input_setting.html" %}
<input id="EMAIL_LETS_ENCRYPT"
name="EMAIL_LETS_ENCRYPT"
type="text"
placeholder="John.doe@example.com"
class="form-control plugin-setting mt-1"
aria-labelledby="label-EMAIL_LETS_ENCRYPT"
pattern="^.*$" />
</div>
</div>
{% else %}
@ -409,63 +480,82 @@
<h5 class="mb-1 fw-bold">Overview</h5>
<p class="card-subtitle text-muted">Review your settings</p>
</div>
<div class="row pb-0 mb-4">
{% if not ui_user %}
<h6 class="mt-2 mb-1 fw-bold">Admin User</h6>
<div class="d-flex justify-content-center pb-0 mb-4">
<div class="col-12 col-md-8">
{% set input_required = false %}
{% set input_readonly = true %}
<div class="col-12 col-sm-6 col-md-4 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_username"
for="overview_username"
class="form-label fw-semibold text-truncate">
Admin Username
</label>
{% if not ui_user %}
<div class="pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_username"
for="overview_username"
class="form-label fw-semibold text-truncate">
Admin Username
</label>
</div>
<input id="overview_username"
name="overview_username"
type="text"
class="form-control plugin-setting mt-1"
aria-labelledby="label-overview_username"
pattern="^.{1,256}$"
readonly />
</div>
{% set setting = "username" %}
{% set setting_data = {"type": "text", "id": "overview_username", "regex": "^.{1,256}$"} %}
{% include "models/input_setting.html" %}
</div>
<div class="col-12 col-sm-6 col-md-4 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_email"
for="overview_email"
class="form-label fw-semibold text-truncate">Admin Email</label>
<div class="pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_email"
for="overview_email"
class="form-label fw-semibold text-truncate">
Admin Email
</label>
</div>
<input id="overview_email"
name="overview_email"
type="text"
class="form-control plugin-setting mt-1"
aria-labelledby="label-overview_email"
pattern="^.*$"
readonly />
</div>
{% set setting = "email" %}
{% set setting_data = {"type": "email", "id": "overview_email", "regex": "^.*$"} %}
{% include "models/input_setting.html" %}
</div>
<div class="col-12 col-md-4 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_password"
for="overview_password"
class="form-label fw-semibold text-truncate">
Admin Password
</label>
<div class="pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-overview_password"
for="overview_password"
class="form-label fw-semibold text-truncate">
Admin Password
</label>
</div>
<div class="form-password-toggle">
<div class="input-group input-group-merge mt-1">
<input class="form-control plugin-setting"
type="password"
id="overview_password"
name="overview_password"
aria-labelledby="label-overview_password"
autocomplete="off"
readonly
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ -~]).{8,}$" />
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
</div>
</div>
</div>
{% set setting = "password" %}
{% set setting_data = {"type": "password", "id": "overview_password", "regex": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[ -~]).{8,}$"} %}
{% include "models/input_setting.html" %}
</div>
{% endif %}
{% if not ui_reverse_proxy %}
<div class="col-6 pb-3">
<h6 class="mt-2 mb-2 fw-bold">BunkerWeb UI final URL</h6>
{% set setting = "SERVER_NAME + REVERSE_PROXY_HOST" %}
{% set setting_data = {"type": "text", "id": "overview_service_url", "regex": "^.+$"} %}
{% include "models/input_setting.html" %}
</div>
{% endif %}
{% endif %}
{% if not ui_reverse_proxy %}
<div class="pb-3">
<h6 class="mt-2 mb-2 fw-bold">BunkerWeb UI final URL</h6>
{% set setting = "SERVER_NAME + REVERSE_PROXY_HOST" %}
{% set setting_data = {"type": "text", "id": "overview_service_url", "regex": "^.+$"} %}
{% include "models/input_setting.html" %}
</div>
{% endif %}
</div>
{% if not ui_user %}
<div class="col-6">
<!-- <div class="col-6">
{% set input_readonly = false %}
<div class="d-flex justify-content-between align-items-center">
<h6 class="mt-2 mb-1 fw-bold">2FA Setup</h6>
<div class="d-flex align-items-center">
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="The 6-digit code generated by your 2FA app">
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The 6-digit code generated by your 2FA app">
<span class="bx bx-question-mark bx-xs"></span>
</span>
</div>
@ -473,33 +563,30 @@
{% set setting = "2FA Code" %}
{% set setting_data = {"type": "number", "id": "2fa_code", "regex": "^[0-9]{6}$"} %}
{% include "models/input_setting.html" %}
</div> -->
{% endif %}
</div>
<div class="d-flex justify-content-center text-center mt-4">
{% if not ui_user %}
<div>
<i class="bx bx-check text-success"></i> Secure password
</div>
<!-- <div id="overview-2fa-enabled" class="ms-4" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title='Please enter a valid 2FA code in the 2FA setup input'>
<i class="bx bx-question-mark text-warning"></i> 2FA enabled
</div> -->
{% endif %}
{% if not ui_reverse_proxy %}
<div id="overview-unique-server-name" class="ms-4">
<i class="bx bx-question-mark text-warning"></i> Unique Server Name
</div>
{% endif %}
<div class="d-flex justify-content-center text-center mt-4">
{% if not ui_user %}
<div>
<i class="bx bx-check text-success"></i> Secure password
</div>
<div id="overview-2fa-enabled"
class="ms-4"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title='Please enter a valid 2FA code in the 2FA setup input'>
<i class="bx bx-question-mark text-warning"></i> 2FA enabled
</div>
{% endif %}
{% if not ui_reverse_proxy %}
<div id="overview-unique-server-name" class="ms-4">
<i class="bx bx-question-mark text-warning"></i> Unique Server Name
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<button class="btn btn-primary btn-prev previous-step disabled">
<button id="previous-step"
class="btn btn-primary btn-prev previous-step disabled">
<i class="bx bx-chevron-left bx-sm ms-sm-n2"></i>
<span class="align-middle d-sm-inline-block d-none">Previous</span>
</button>
@ -507,7 +594,7 @@
<i class="bx bx-save bx-sm ms-sm-n2"></i>
<span class="align-middle d-sm-inline-block d-none ms-sm-1">Setup</span>
</button>
<button class="btn btn-primary btn-next next-step">
<button id="next-step" class="btn btn-primary btn-next next-step">
<span class="align-middle d-sm-inline-block d-none me-sm-1">Next</span>
<i class="bx bx-chevron-right bx-sm me-sm-n2"></i>
</button>
@ -516,6 +603,8 @@
<!-- /Setup -->
</div>
</div>
<!-- prettier-ignore -->
{% include "sidebar-news.html" %}
</div>
</div>
<div id="feedback-toast"
@ -557,14 +646,14 @@
class="alert alert-danger text-center"
role="alert">Are you sure you want to proceed to the next step?</div>
<p class="mt-1 mb-0 text-center">
In case of issues, you can also click <a id="check-url"
In case of issues, you can manually accept the unsafe cert <a id="check-url"
class="fw-semibold"
href="https://www.example.com/setup/check"
target="_blank"
rel="noreferrer"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title='If the shown text is "ok", that means that the server name is available'>here</a> to perform a manual check.
data-bs-original-title='If the shown text is "ok", that means that the server name is available'>here</a>.
</p>
</div>
<div class="modal-footer justify-content-center">
@ -595,48 +684,5 @@
</div>
</div>
</div>
<div class="position-fixed bottom-0 start-0 m-3" id="floating-modes-menu">
<button class="btn btn-sm btn-primary d-flex align-items-center justify-content-center rounded-pill me-1"
type="button"
data-bs-toggle="collapse"
data-bs-target="#newsletter-floating"
aria-controls="newsletter-floating"
aria-expanded="true"
aria-label="Toggle navigation">
<i class="bx bx-xs bx-menu me-1"></i>News from the Bunker
</button>
<!-- Collapsible floating menu -->
<div class="collapse show mt-2" id="newsletter-floating">
<div class="card card-body bg-white border-0 shadow-sm">
<h5 class="mb-3 text-dark">Join the Newsletter</h5>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
method="POST"
target="_blank"
rel="noopener">
<div class="mb-3">
<input type="email"
name="EMAIL"
class="form-control"
placeholder="John.doe@example.com"
required />
</div>
<div class="form-check mb-3">
<input type="checkbox"
class="form-check-input"
name="newsletter-check"
required />
<label class="form-check-label" for="privacyPolicyCheck">
I've read and agree to the
<a class="fst-italic"
href="https://www.bunkerity.com/en/privacy-policy?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener">privacy policy</a>
</label>
</div>
<button type="submit" class="btn btn-primary w-100 text-uppercase don-jose">Subscribe</button>
</form>
</div>
</div>
</div>
<!-- / Content -->
{% endblock %}

View file

@ -53,7 +53,7 @@
</div>
</div>
<!-- Newsletter Signup Section -->
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-white border-top">
<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>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
method="POST">

View file

@ -57,7 +57,7 @@
{% if category != 'message' %}
{{ category|capitalize }}
{% else %}
Success
Info
{% endif %}
</span>
<small class="text-body-secondary toast-datetime">{{ datetime }}</small>
@ -74,7 +74,7 @@
{% endif %}
</div>
<!-- Newsletter Signup Section -->
<div class="newsletter-signup position-sticky bottom-0 start-0 w-100 p-4 bg-white border-top">
<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>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&id=37076d9d67"
method="POST">