Add Don José and Courier Prime fonts + Fix shenanigans with setup wizard in web UI + Add lottie to loading screens

This commit is contained in:
Théophile Diot 2024-10-06 14:29:06 +02:00
parent 3049047a20
commit bcb5321583
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
45 changed files with 1534 additions and 1419 deletions

View file

@ -21,8 +21,7 @@ setup = Blueprint("setup", __name__)
def setup_page():
if current_user.is_authenticated:
return redirect(url_for("home.home_page"))
db_config = BW_CONFIG.get_config(
methods=False,
db_config = DB.get_config(
filtered_settings=("SERVER_NAME", "MULTISITE", "USE_UI", "UI_HOST", "AUTO_LETS_ENCRYPT", "USE_LETS_ENCRYPT_STAGING", "EMAIL_LETS_ENCRYPT"),
)

View file

@ -206,7 +206,13 @@ button.list-group-item-secondary.active {
******************************************************************************/
:root {
--dt-row-selected: 29, 123, 167;
--dt-row-selected: 230, 234, 237;
--font-don-jose: "Don Jose", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
--font-courier-prime: "Courier Prime", -apple-system, BlinkMacSystemFont,
"Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
}
.badge-dot {
@ -347,6 +353,11 @@ td.highlight {
border-color: var(--bs-primary) !important;
}
.btn-primary:hover {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
.btn-bw-green {
color: #fff;
background-color: var(--bs-bw-green);
@ -674,24 +685,8 @@ a.badge:hover {
font-style: italic;
}
table.table.dataTable > tbody > tr.selected a {
color: #fff;
border-color: #fff;
}
table.table.dataTable > tbody > tr.selected .btn-outline-secondary {
color: #fff !important;
border-color: #fff !important;
}
table.table.dataTable > tbody > tr.selected .btn-outline-secondary:hover {
color: black !important;
background-color: #fff !important;
border-color: #fff !important;
}
table.table.dataTable > tbody > tr.selected .text-secondary {
color: #fff !important;
table.table.dataTable > tbody > tr.selected > * {
color: initial !important;
}
.border-dashed {
@ -720,10 +715,6 @@ a.text-white-80:hover {
margin-top: 0.125rem !important;
}
a.text-decoration-underline.link-underline-primary:hover {
text-decoration-color: var(--bs-bw-green) !important;
}
.slide-out {
transition: transform 0.7s ease-in-out !important;
transform: translateX(-100%) !important;
@ -764,3 +755,11 @@ a.text-decoration-underline.link-underline-primary:hover {
.w-70 {
width: 70% !important;
}
.don-jose {
font-family: var(--font-don-jose);
}
.courier-prime {
font-family: var(--font-courier-prime);
}

View file

@ -0,0 +1,29 @@
@font-face {
font-family: "Courier Prime";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(../fonts/Courier_prime/Courier_prime.ttf), format("truetype");
}
@font-face {
font-family: "Courier Prime";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(../fonts/Courier_prime/Courier_prime_bold.ttf), format("truetype");
}
@font-face {
font-family: "Courier Prime";
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(../fonts/Courier_prime/Courier_prime_italic.ttf), format("truetype");
}
@font-face {
font-family: "Courier Prime";
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(../fonts/Courier_prime/Courier_prime_bold_italic.ttf),
format("truetype");
}

View file

@ -0,0 +1,6 @@
@font-face {
font-family: "Don Jose";
font-style: normal;
font-display: swap;
src: url(../fonts/DonJose_Black.otf), format("truetype");
}

Binary file not shown.

View file

@ -95,14 +95,14 @@ $(document).ready(function () {
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<div class="fw-bold">Time left</div>
</li>
</ul>`,
</ul>`
);
$("#selected-ips-unban").append(list);
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 d-flex w-100"></ul>`
);
const listItem =
@ -130,8 +130,8 @@ $(document).ready(function () {
.find(".alert")
.text(
`Are you sure you want to unban the selected IP address${"es".repeat(
bans.length > 1,
)}?`,
bans.length > 1
)}?`
);
modal.show();
@ -170,6 +170,9 @@ $(document).ready(function () {
}
layout.topStart.buttons = [
{
extend: "add_ban",
},
{
extend: "colvis",
columns: "th:not(:first-child):not(:nth-child(2)):not(:last-child)",
@ -232,9 +235,6 @@ $(document).ready(function () {
},
],
},
{
extend: "add_ban",
},
];
$("#modal-unban-ips").on("hidden.bs.modal", function () {
@ -253,7 +253,7 @@ $(document).ready(function () {
};
$.fn.dataTable.ext.buttons.add_ban = {
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Add<span class="d-none d-md-inline"> ban(s)</span>',
text: '<span class="tf-icons bx bx-plus"></span>&nbsp;Add<span class="d-none d-md-inline"> ban(s)</span>',
className: `btn btn-sm btn-outline-bw-green${
isReadOnly ? " disabled" : ""
}`,
@ -361,7 +361,7 @@ $(document).ready(function () {
$("#bans_wrapper .dt-buttons")
.attr(
"data-bs-original-title",
"The database is in readonly, therefore you cannot add bans.",
"The database is in readonly, therefore you cannot add bans."
)
.attr("data-bs-placement", "right")
.tooltip();
@ -484,7 +484,7 @@ $(document).ready(function () {
});
const ipRegex = new RegExp(
/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?!$)|$)){4}$|^((?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}|(?:[A-Fa-f0-9]{1,4}:){1,7}:|:(?::[A-Fa-f0-9]{1,4}){1,7}|::)$/i,
/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?!$)|$)){4}$|^((?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}|(?:[A-Fa-f0-9]{1,4}:){1,7}:|:(?::[A-Fa-f0-9]{1,4}){1,7}|::)$/i
);
const validateBan = (ban, ipSet) => {
@ -587,14 +587,14 @@ $(document).ready(function () {
type: "hidden",
name: "csrf_token",
value: $("#csrf_token").val(),
}),
})
);
form.append(
$("<input>", {
type: "hidden",
name: "bans",
value: JSON.stringify(bans),
}),
})
);
// Append the form to the body and submit it

View file

@ -18,13 +18,13 @@ $(document).ready(function () {
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Service</div>
</li>
</ul>`,
</ul>`
);
$("#selected-configs-delete").append(list);
configs.forEach((config) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`
);
// Create the list item using template literals
@ -38,13 +38,13 @@ $(document).ready(function () {
const id = `${config.type.toLowerCase()}-${config.service.replaceAll(
".",
"_",
"_"
)}-${config.name}`;
// 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 d-flex align-items-center" style="flex: 1 1 0;"></li>`
);
typeListItem.append(typeClone.removeClass("highlight"));
list.append(typeListItem);
@ -52,7 +52,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 d-flex align-items-center" style="flex: 1 1 0;"></li>`
);
serviceListItem.append(serviceClone.removeClass("highlight"));
list.append(serviceListItem);
@ -66,8 +66,8 @@ $(document).ready(function () {
.find(".alert")
.text(
`Are you sure you want to delete the selected custom configuration${"s".repeat(
configs.length > 1,
)}?`,
configs.length > 1
)}?`
);
modal.show();
@ -110,6 +110,9 @@ $(document).ready(function () {
}
layout.topStart.buttons = [
{
extend: "create_config",
},
{
extend: "colvis",
columns: "th:not(:first-child):not(:nth-child(2)):not(:last-child)",
@ -172,9 +175,6 @@ $(document).ready(function () {
},
],
},
{
extend: "create_config",
},
];
$(document).on("hidden.bs.toast", ".toast", function (event) {
@ -208,7 +208,7 @@ $(document).ready(function () {
};
$.fn.dataTable.ext.buttons.create_config = {
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new custom config</span>',
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${
isReadOnly ? " disabled" : ""
}`,
@ -369,7 +369,7 @@ $(document).ready(function () {
$("#configs_wrapper .dt-buttons")
.attr(
"data-bs-original-title",
"The database is in readonly, therefore you cannot create new custom configurations.",
"The database is in readonly, therefore you cannot create new custom configurations."
)
.attr("data-bs-placement", "right")
.tooltip();

View file

@ -1,100 +1,7 @@
$(function () {
// 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>.`,
`Need premium support or tailored consulting around BunkerWeb? Check out our <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=banner#services">professional services</a>.`,
`Be part of the Bunker community by joining the <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://discord.bunkerweb.io/?utm_campaign=self&utm_source=banner">Discord chat</a> and following us on <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://www.linkedin.com/company/bunkerity/">LinkedIn</a>.`,
];
let currentIndex = 0;
const intervalTime = 9000;
let interval;
const cardColor = config.colors.cardColor;
const headingColor = config.colors.headingColor;
const legendColor = config.colors.bodyColor;
function loadData() {
const nowStamp = Math.round(Date.now() / 1000);
const bannerRefetch = sessionStorage.getItem("bannerRefetch");
let bannerNews = sessionStorage.getItem("bannerNews");
// Check if cached data is expired
if (bannerRefetch && nowStamp > bannerRefetch) {
sessionStorage.removeItem("bannerRefetch");
sessionStorage.removeItem("bannerNews");
bannerNews = null;
}
if (bannerNews) {
// Use cached data
const data = JSON.parse(bannerNews);
newsItems = data.map((item) => item.content);
} else {
console.log("TODO: Fetch data from API when endpoint is available");
// TODO: Fetch data from API when endpoint is available
/*
$.getJSON("https://www.bunkerweb.io/api/bw-ui-news-16")
.done(function (res) {
const data = res.data[0].data;
sessionStorage.setItem("bannerNews", JSON.stringify(data));
sessionStorage.setItem("bannerRefetch", nowStamp + 3600); // Refetch after one hour
newsItems = data.map((item) => item.content);
})
.fail(function (e) {
console.error("Failed to fetch banner news:", e);
});
*/
}
startInterval();
}
// Function to update the banner text with animation
function updateBannerText(nextIndex) {
const $bannerText = $("#banner-text");
// Remove any existing slide-in class to reset
$bannerText.removeClass("slide-in").addClass("slide-out");
setTimeout(() => {
$bannerText.removeClass("slide-out");
// Update the text content
$bannerText.html(newsItems[nextIndex]);
// Trigger reflow to ensure the browser applies the changes
$bannerText[0].offsetHeight;
// Add the slide-in class to start the animation
$bannerText.addClass("slide-in");
}, 700);
}
// Function to start the automatic news rotation
function startInterval() {
if (newsItems.length > 0) {
interval = setInterval(() => {
currentIndex = (currentIndex + 1) % newsItems.length;
updateBannerText(currentIndex);
}, intervalTime);
}
}
// Reset interval when user interacts
function resetInterval() {
clearInterval(interval);
startInterval();
}
// Click event handler for the "next-news" icon
$("#next-news").on("click", function () {
currentIndex = (currentIndex + 1) % newsItems.length;
updateBannerText(currentIndex);
resetInterval();
});
loadData();
// Requests countries map
const requestsMapData = JSON.parse($("#requests-map-data").text());
@ -135,7 +42,6 @@ $(function () {
<h5 class="card-title mb-0">${props.ADMIN}</h5>
</div>
<div class="card-body p-1 pt-1">
<p class="card-text mb-0">Total Requests: ${props.value}</p>
<p class="card-text">Blocked Requests: ${props.blocked}</p>
</div>
</div>
@ -287,7 +193,7 @@ $(function () {
getColor(from + 1) +
'"></i> ' +
from +
(to ? "&ndash;" + to : "+"),
(to ? "&ndash;" + to : "+")
);
}
@ -304,7 +210,7 @@ $(function () {
// Ensure each value is properly converted to a number
const totalRequests = Object.values(requestsData).reduce(
(acc, curr) => acc + parseInt(curr, 10), // Parse as integer
0, // Initial value for the accumulator
0 // Initial value for the accumulator
);
const blockedRequests = Object.keys(requestsData).reduce((acc, key) => {
@ -399,122 +305,111 @@ $(function () {
const requestsChart = new ApexCharts(
document.querySelector("#requests-stats"),
requestsOptions,
requestsOptions
);
requestsChart.render();
// Requests IPs chart
const requestsIpsData = JSON.parse($("#requests-ips-data").text());
const totalIps = Object.values(requestsIpsData).filter(
(ipData) => parseInt(ipData["request"], 10) > 0,
).length;
const blockedIps = Object.values(requestsIpsData).filter(
(ipData) => parseInt(ipData["blocked"], 10) > 0,
).length;
const blockedIpsPercent = ((blockedIps / totalIps) * 100).toFixed(2);
const $ipsData = $("#requests-ips-data");
if ($ipsData.length) {
const requestsIpsData = JSON.parse($("#requests-ips-data").text());
const topIpsData = Object.entries(requestsIpsData)
.sort((a, b) => b[1]["blocked"] - a[1]["blocked"])
.slice(0, 10)
.reduce((obj, [key, value]) => {
obj[key] = value["blocked"];
return obj;
}, {});
const topIpsData = Object.entries(requestsIpsData)
.sort((a, b) => b[1]["blocked"] - a[1]["blocked"])
.slice(0, 10)
.reduce((obj, [key, value]) => {
obj[key] = value["blocked"];
return obj;
}, {});
const ipsOptions = {
chart: {
type: "donut",
},
labels: Object.keys(topIpsData),
series: Object.values(topIpsData).map((value) => parseInt(value, 10)),
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 400,
},
legend: {
position: "bottom",
const ipsOptions = {
chart: {
type: "donut",
},
labels: Object.keys(topIpsData),
series: Object.values(topIpsData).map((value) => parseInt(value, 10)),
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 400,
},
legend: {
position: "bottom",
},
},
},
],
legend: {
show: false,
},
],
legend: {
show: false,
},
dataLabels: {
enabled: false,
formatter: function (val, opt) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
dataLabels: {
enabled: false,
formatter: function (val, opt) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
},
},
},
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
grid: {
padding: {
top: 0,
bottom: 0,
right: 15,
},
},
},
states: {
hover: {
filter: { type: "none" },
states: {
hover: {
filter: { type: "none" },
},
active: {
filter: { type: "none" },
},
},
active: {
filter: { type: "none" },
},
},
plotOptions: {
pie: {
donut: {
size: "75%",
labels: {
show: true,
value: {
fontSize: "18px",
fontFamily: "Public Sans",
fontWeight: 500,
color: headingColor,
offsetY: -17,
formatter: function (val) {
return ((parseInt(val) / totalRequests) * 100).toFixed(2) + "%";
},
},
name: {
offsetY: 17,
fontFamily: "Public Sans",
},
total: {
plotOptions: {
pie: {
donut: {
size: "75%",
labels: {
show: true,
fontSize: "13px",
color: legendColor,
label: "Blocked",
formatter: function (w) {
return blockedIpsPercent + "%";
value: {
fontSize: "18px",
fontFamily: "Public Sans",
fontWeight: 500,
color: headingColor,
offsetY: -17,
formatter: function (val) {
return (
((parseInt(val) / totalRequests) * 100).toFixed(2) + "%"
);
},
},
name: {
offsetY: 17,
fontFamily: "Public Sans",
},
},
},
},
},
},
};
};
const ipsChart = new ApexCharts(
document.querySelector("#requests-ips"),
ipsOptions,
);
ipsChart.render();
const ipsChart = new ApexCharts(
document.querySelector("#requests-ips"),
ipsOptions
);
ipsChart.render();
}
// Requests Blocking status
const blockingData = JSON.parse($("#requests-blocking-data").text());
const dataValues = Object.values(blockingData).map((value) =>
parseInt(value, 10),
parseInt(value, 10)
);
const categories = Object.keys(blockingData).map((key) =>
new Date(key).toLocaleTimeString(),
new Date(key).toLocaleTimeString()
);
const minValue = Math.min(...dataValues);
@ -603,7 +498,7 @@ $(function () {
const blockingChart = new ApexCharts(
document.querySelector("#requests-blocking"),
blockingOptions,
blockingOptions
);
blockingChart.render();
});

View file

@ -75,14 +75,14 @@ $(document).ready(function () {
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
<div class="fw-bold">Health</div>
</li>
</ul>`,
</ul>`
);
$("#selected-instances").append(list);
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 d-flex w-100"></ul>`
);
// Create the list item using template literals
@ -97,7 +97,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 d-flex align-items-center justify-content-center" style="flex: 1 0;"></li>`
);
statusListItem.append(statusClone.removeClass("highlight"));
list.append(statusListItem);
@ -124,14 +124,14 @@ $(document).ready(function () {
type: "hidden",
name: "csrf_token",
value: $("#csrf_token").val(),
}),
})
);
form.append(
$("<input>", {
type: "hidden",
name: "instances",
value: instances.join(","),
}),
})
);
// Append the form to the body and submit it
@ -169,6 +169,9 @@ $(document).ready(function () {
}
layout.topStart.buttons = [
{
extend: "create_instance",
},
{
extend: "colvis",
columns: "th:not(:first-child):not(:nth-child(2))",
@ -242,9 +245,6 @@ $(document).ready(function () {
},
],
},
{
extend: "create_instance",
},
];
$(document).on("hidden.bs.toast", ".toast", function (event) {
@ -269,7 +269,7 @@ $(document).ready(function () {
};
$.fn.dataTable.ext.buttons.create_instance = {
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new instance</span>',
text: '<span class="tf-icons bx bx-plus"></span>&nbsp;Create<span class="d-none d-md-inline"> new instance</span>',
className: `btn btn-sm btn-outline-bw-green${
isReadOnly ? " disabled" : ""
}`,
@ -561,7 +561,7 @@ $(document).ready(function () {
$("#instances_wrapper .dt-buttons")
.attr(
"data-bs-original-title",
"The database is in readonly, therefore you cannot create new instances.",
"The database is in readonly, therefore you cannot create new instances."
)
.attr("data-bs-placement", "right")
.tooltip();

View file

@ -0,0 +1,46 @@
$(document).ready(function () {
// Retrieve targetEndpoint and nextEndpoint from server-side variables safely
const $targetEndpoint = $("#target-endpoint");
const targetEndpoint = $targetEndpoint.length
? $targetEndpoint.val().trim()
: null;
const nextEndpoint = $("#next-endpoint").val().trim();
// Start the reloading interval to check every 2 seconds
var reloadingInterval = setInterval(check_reloading, 2000);
check_reloading(); // Run immediately on load
// Function to check if reloading is needed
function check_reloading() {
$.ajax({
url: targetEndpoint
? targetEndpoint
: location.href.replace("/loading", "/check_reloading"),
method: "GET",
dataType: "json",
timeout: 2000, // 2 seconds timeout
success: function (response) {
if (
response &&
(response.message === "ok" || response.reloading === false)
) {
// Clear all intervals and timeout
clearInterval(reloadingInterval);
// Redirect to the next page with the current hash if present
window.location.replace(nextEndpoint + (window.location.hash || ""));
}
},
error: function (jqXHR, textStatus, errorThrown) {
if (textStatus === "timeout") {
// Handle timeout specifically if needed
console.warn("Request timed out.");
} else {
// Handle other errors
console.error("AJAX request failed: ", textStatus, errorThrown);
}
},
});
}
});

View file

@ -21,13 +21,13 @@ $(document).ready(function () {
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 1 0;">
<div class="fw-bold">Type</div>
</li>
</ul>`,
</ul>`
);
$("#selected-plugins-delete").append(list);
plugins.forEach((plugin) => {
const list = $(
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`
);
// Create the list item using template literals
@ -42,7 +42,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 d-flex align-items-center" style="flex: 1 1 0;"></li>`
);
versionListItem.append(versionClone.removeClass("highlight"));
list.append(versionListItem);
@ -50,7 +50,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 d-flex align-items-center" style="flex: 1 1 0;"></li>`
);
typeListItem.append(typeClone.removeClass("highlight"));
list.append(typeListItem);
@ -64,8 +64,8 @@ $(document).ready(function () {
.find(".alert")
.text(
`Are you sure you want to delete the selected plugin${"s".repeat(
plugins.length > 1,
)}?`,
plugins.length > 1
)}?`
);
modal.show();
@ -113,7 +113,7 @@ $(document).ready(function () {
// Create a progress bar element
const progressBar = $(
'<div class="progress-bar" role="progressbar" style="width: 0%;"></div>',
'<div class="progress-bar" role="progressbar" style="width: 0%;"></div>'
);
const progress = $('<div class="progress mt-2"></div>').append(progressBar);
fileList.append(progress);
@ -182,6 +182,9 @@ $(document).ready(function () {
}
layout.topStart.buttons = [
{
extend: "add_plugin",
},
{
extend: "colvis",
columns: "th:not(:first-child):not(:nth-child(2)):not(:last-child)",
@ -254,9 +257,6 @@ $(document).ready(function () {
},
],
},
{
extend: "add_plugin",
},
];
$("#modal-delete-plugins").on("hidden.bs.modal", function () {
@ -276,7 +276,7 @@ $(document).ready(function () {
};
$.fn.dataTable.ext.buttons.add_plugin = {
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Add<span class="d-none d-md-inline"> plugin(s)</span>',
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${
isReadOnly ? " disabled" : ""
}`,
@ -339,19 +339,19 @@ $(document).ready(function () {
{
label: "No",
value: function (rowData, rowIdx) {
return rowData[5].includes("x-circle");
return rowData[5].includes("bx-x");
},
},
{
label: "Yes",
value: function (rowData, rowIdx) {
return rowData[5].includes("check-circle");
return rowData[5].includes("bx-check");
},
},
{
label: "Partial",
value: function (rowData, rowIdx) {
return rowData[5].includes("minus-circle");
return rowData[5].includes("bx-minus");
},
},
],
@ -445,7 +445,7 @@ $(document).ready(function () {
$("#plugins_wrapper .dt-buttons")
.attr(
"data-bs-original-title",
"The database is in readonly, therefore you cannot create add plugins.",
"The database is in readonly, therefore you cannot create add plugins."
)
.attr("data-bs-placement", "right")
.tooltip();

View file

@ -22,7 +22,7 @@ $(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 d-flex w-100"></ul>'
);
const listItem = $(`
@ -57,7 +57,7 @@ $(function () {
.text(
`Are you sure you want to convert the selected service${
services.length > 1 ? "s" : ""
} to ${conversionType}?`,
} to ${conversionType}?`
);
convertModal
.find("button[type=submit]")
@ -78,7 +78,7 @@ $(function () {
.text(
`Are you sure you want to delete the selected service${
services.length > 1 ? "s" : ""
}?`,
}?`
);
const modalInstance = new bootstrap.Modal(deleteModal);
modalInstance.show();
@ -107,6 +107,9 @@ $(function () {
}
layout.topStart.buttons = [
{
extend: "create_service",
},
{
extend: "colvis",
columns: "th:not(:first-child):not(:nth-child(2)):not(:last-child)",
@ -163,9 +166,6 @@ $(function () {
},
],
},
{
extend: "create_service",
},
];
$(document).on("hidden.bs.toast", ".toast", function (event) {
@ -184,10 +184,10 @@ $(function () {
.empty();
$(this)
.find(
"#selected-services-input-convert, #selected-services-input-delete",
"#selected-services-input-convert, #selected-services-input-delete"
)
.val("");
},
}
);
const getSelectedServices = () =>
@ -198,7 +198,7 @@ $(function () {
.get();
$.fn.dataTable.ext.buttons.create_service = {
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new service</span>',
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${
isReadOnly ? " disabled" : ""
}`,
@ -230,7 +230,7 @@ $(function () {
const filteredServices = services.filter((service) => {
const serviceType = $(`#type-${service.replace(/\./g, "-")}`).data(
"value",
"value"
);
return serviceType !== conversionType;
});
@ -408,7 +408,7 @@ $(function () {
.find(".dt-buttons")
.attr(
"data-bs-original-title",
"The database is in read-only mode; you cannot create new services.",
"The database is in read-only mode; you cannot create new services."
)
.attr("data-bs-placement", "right")
.tooltip();

View file

@ -154,10 +154,6 @@ $(document).ready(() => {
result = await fetchCheck(fallbackURL);
}
$overviewUniqueServerName
.find("i")
.toggleClass("bx-question-mark text-warning", false);
const $input = $("#SERVER_NAME");
const isValid = result && typeof result !== "string";
@ -186,7 +182,10 @@ $(document).ready(() => {
if (typeof result !== "string")
$overviewUniqueServerName
.find("i")
.toggleClass("bx-check text-success", false)
.toggleClass(
"bx-question-mark text-warning bx-check text-success",
false
)
.toggleClass("bx-x text-danger", true);
} else {
$feedback.text("");
@ -199,7 +198,7 @@ $(document).ready(() => {
$overviewUniqueServerName
.find("i")
.toggleClass("bx-check text-success", true)
.toggleClass("bx-x text-danger", false);
.toggleClass("bx-question-mark text-warning bx-x text-danger", false);
}
feedbackToast.appendTo("#feedback-toast-container"); // Ensure the toast is appended to the container
@ -585,12 +584,14 @@ $(document).ready(() => {
.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")

View file

@ -35,20 +35,20 @@ class News {
) {
sessionStorage.setItem(
"lastRefetch",
Math.round(Date.now() / 1000) + 3600,
Math.round(Date.now() / 1000) + 3600
);
sessionStorage.setItem("lastNews", JSON.stringify(lastNews));
const newsNumber = lastNews.length;
$("#news-pill").append(
DOMPurify.sanitize(
`<span class="badge rounded-pill badge-center-sm bg-danger ms-1_5">${newsNumber}</span>`,
),
`<span class="badge rounded-pill badge-center-sm bg-danger ms-1_5">${newsNumber}</span>`
)
);
$("#news-button").after(
DOMPurify.sanitize(
`<span class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">${newsNumber}<span class="visually-hidden">unread news</span></span>`,
),
`<span class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">${newsNumber}<span class="visually-hidden">unread news</span></span>`
)
);
}
@ -84,7 +84,7 @@ class News {
news.tags,
news.date,
isLast,
false, // isHome is false
false // isHome is false
);
newsRow.append(cardElement);
@ -98,7 +98,7 @@ class News {
news.tags,
news.date,
isLast,
true, // isHome is true
true // isHome is true
);
homeNewsRow.append(homeCardElement);
}
@ -131,13 +131,13 @@ class News {
class: "card-img card-img-left",
src: img,
alt: "News image",
}),
})
);
imgCol.append(imgLink);
const contentCol = $("<div>", { class: "col-md-7" }).appendTo(row);
const cardBody = $("<div>", { class: "card-body p-3" }).appendTo(
contentCol,
contentCol
);
const cardTitle = $("<h6>", { class: "card-title lh-sm mb-2" }).append(
@ -146,7 +146,7 @@ class News {
target: "_blank",
rel: "noopener",
text: title,
}),
})
);
const cardText = $("<small>", { class: "card-text lh-1", text: excerpt });
@ -157,7 +157,7 @@ class News {
});
$("<p>", { class: "card-text mb-0" })
.append(
$("<small>", { class: "text-muted", text: `Posted on: ${date}` }),
$("<small>", { class: "text-muted", text: `Posted on: ${date}` })
)
.appendTo(cardFooter);
@ -175,7 +175,7 @@ class News {
$("<span>", {
class: "tf-icons bx bx-xs bx-purchase-tag me-1",
}),
tag.name,
tag.name
)
.appendTo(tagsContainer);
});
@ -195,7 +195,7 @@ class News {
class: "card-img-top",
src: img,
alt: "News image",
}),
})
);
const cardBody = $("<div>", { class: "card-body" });
@ -205,7 +205,7 @@ class News {
target: "_blank",
rel: "noopener",
text: title,
}),
})
);
const cardText = $("<p>", { class: "card-text", text: excerpt });
@ -223,13 +223,13 @@ class News {
$("<span>", {
class: "tf-icons bx bx-xs bx-purchase-tag bx-18px me-2",
}),
tag.name,
tag.name
)
.appendTo(tagsContainer);
});
const dateText = $("<p>", { class: "card-text" }).append(
$("<small>", { class: "text-muted", text: `Posted on: ${date}` }),
$("<small>", { class: "text-muted", text: `Posted on: ${date}` })
);
cardBody.append(cardTitle, cardText, tagsContainer, dateText);
@ -250,4 +250,98 @@ class News {
$(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>.`,
`Need premium support or tailored consulting around BunkerWeb? Check out our <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=banner#services">professional services</a>.`,
`Be part of the Bunker community by joining the <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://discord.bunkerweb.io/?utm_campaign=self&utm_source=banner">Discord chat</a> and following us on <a class="light-href text-white-80" target="_blank" rel="noopener" href="https://www.linkedin.com/company/bunkerity/">LinkedIn</a>.`,
];
let currentIndex = 0;
const intervalTime = 7000;
let interval;
function loadData() {
const nowStamp = Math.round(Date.now() / 1000);
const bannerRefetch = sessionStorage.getItem("bannerRefetch");
let bannerNews = sessionStorage.getItem("bannerNews");
// Check if cached data is expired
if (bannerRefetch && nowStamp > bannerRefetch) {
sessionStorage.removeItem("bannerRefetch");
sessionStorage.removeItem("bannerNews");
bannerNews = null;
}
if (bannerNews) {
// Use cached data
const data = JSON.parse(bannerNews);
newsItems = data.map((item) => item.content);
} else {
console.log("TODO: Fetch data from API when endpoint is available");
// TODO: Fetch data from API when endpoint is available
/*
$.getJSON("https://www.bunkerweb.io/api/bw-ui-news-16")
.done(function (res) {
const data = res.data[0].data;
sessionStorage.setItem("bannerNews", JSON.stringify(data));
sessionStorage.setItem("bannerRefetch", nowStamp + 3600); // Refetch after one hour
newsItems = data.map((item) => item.content);
})
.fail(function (e) {
console.error("Failed to fetch banner news:", e);
});
*/
}
newsItems.sort(() => Math.random() - 0.5);
startInterval();
}
// Function to update the banner text with animation
function updateBannerText(nextIndex) {
const $bannerText = $("#banner-text");
// Remove any existing slide-in class to reset
$bannerText.removeClass("slide-in").addClass("slide-out");
setTimeout(() => {
$bannerText.removeClass("slide-out");
// Update the text content
$bannerText.html(newsItems[nextIndex]);
// Trigger reflow to ensure the browser applies the changes
$bannerText[0].offsetHeight;
// Add the slide-in class to start the animation
$bannerText.addClass("slide-in");
}, 700);
}
// Function to start the automatic news rotation
function startInterval() {
if (newsItems.length > 0) {
interval = setInterval(() => {
currentIndex = (currentIndex + 1) % newsItems.length;
updateBannerText(currentIndex);
}, intervalTime);
}
}
// Reset interval when user interacts
function resetInterval() {
clearInterval(interval);
startInterval();
}
// Click event handler for the "next-news" icon
$("#next-news").on("click", function () {
currentIndex = (currentIndex + 1) % newsItems.length;
updateBannerText(currentIndex);
resetInterval();
});
loadData();
$("#next-news").trigger("click");
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,366 +1,371 @@
import { freeze } from "./utils.js";
import { freeze } from './utils.js';
export const html = freeze([
"accept",
"action",
"align",
"alt",
"autocapitalize",
"autocomplete",
"autopictureinpicture",
"autoplay",
"background",
"bgcolor",
"border",
"capture",
"cellpadding",
"cellspacing",
"checked",
"cite",
"class",
"clear",
"color",
"cols",
"colspan",
"controls",
"controlslist",
"coords",
"crossorigin",
"datetime",
"decoding",
"default",
"dir",
"disabled",
"disablepictureinpicture",
"disableremoteplayback",
"download",
"draggable",
"enctype",
"enterkeyhint",
"face",
"for",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"id",
"inputmode",
"integrity",
"ismap",
"kind",
"label",
"lang",
"list",
"loading",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"minlength",
"multiple",
"muted",
"name",
"nonce",
"noshade",
"novalidate",
"nowrap",
"open",
"optimum",
"pattern",
"placeholder",
"playsinline",
"popover",
"popovertarget",
"popovertargetaction",
"poster",
"preload",
"pubdate",
"radiogroup",
"readonly",
"rel",
"required",
"rev",
"reversed",
"role",
"rows",
"rowspan",
"spellcheck",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"srclang",
"start",
"src",
"srcset",
"step",
"style",
"summary",
"tabindex",
"title",
"translate",
"type",
"usemap",
"valign",
"value",
"width",
"wrap",
"xmlns",
"slot",
'accept',
'action',
'align',
'alt',
'autocapitalize',
'autocomplete',
'autopictureinpicture',
'autoplay',
'background',
'bgcolor',
'border',
'capture',
'cellpadding',
'cellspacing',
'checked',
'cite',
'class',
'clear',
'color',
'cols',
'colspan',
'controls',
'controlslist',
'coords',
'crossorigin',
'datetime',
'decoding',
'default',
'dir',
'disabled',
'disablepictureinpicture',
'disableremoteplayback',
'download',
'draggable',
'enctype',
'enterkeyhint',
'face',
'for',
'headers',
'height',
'hidden',
'high',
'href',
'hreflang',
'id',
'inputmode',
'integrity',
'ismap',
'kind',
'label',
'lang',
'list',
'loading',
'loop',
'low',
'max',
'maxlength',
'media',
'method',
'min',
'minlength',
'multiple',
'muted',
'name',
'nonce',
'noshade',
'novalidate',
'nowrap',
'open',
'optimum',
'pattern',
'placeholder',
'playsinline',
'popover',
'popovertarget',
'popovertargetaction',
'poster',
'preload',
'pubdate',
'radiogroup',
'readonly',
'rel',
'required',
'rev',
'reversed',
'role',
'rows',
'rowspan',
'spellcheck',
'scope',
'selected',
'shape',
'size',
'sizes',
'span',
'srclang',
'start',
'src',
'srcset',
'step',
'style',
'summary',
'tabindex',
'title',
'translate',
'type',
'usemap',
'valign',
'value',
'width',
'wrap',
'xmlns',
'slot',
]);
export const svg = freeze([
"accent-height",
"accumulate",
"additive",
"alignment-baseline",
"ascent",
"attributename",
"attributetype",
"azimuth",
"basefrequency",
"baseline-shift",
"begin",
"bias",
"by",
"class",
"clip",
"clippathunits",
"clip-path",
"clip-rule",
"color",
"color-interpolation",
"color-interpolation-filters",
"color-profile",
"color-rendering",
"cx",
"cy",
"d",
"dx",
"dy",
"diffuseconstant",
"direction",
"display",
"divisor",
"dur",
"edgemode",
"elevation",
"end",
"fill",
"fill-opacity",
"fill-rule",
"filter",
"filterunits",
"flood-color",
"flood-opacity",
"font-family",
"font-size",
"font-size-adjust",
"font-stretch",
"font-style",
"font-variant",
"font-weight",
"fx",
"fy",
"g1",
"g2",
"glyph-name",
"glyphref",
"gradientunits",
"gradienttransform",
"height",
"href",
"id",
"image-rendering",
"in",
"in2",
"k",
"k1",
"k2",
"k3",
"k4",
"kerning",
"keypoints",
"keysplines",
"keytimes",
"lang",
"lengthadjust",
"letter-spacing",
"kernelmatrix",
"kernelunitlength",
"lighting-color",
"local",
"marker-end",
"marker-mid",
"marker-start",
"markerheight",
"markerunits",
"markerwidth",
"maskcontentunits",
"maskunits",
"max",
"mask",
"media",
"method",
"mode",
"min",
"name",
"numoctaves",
"offset",
"operator",
"opacity",
"order",
"orient",
"orientation",
"origin",
"overflow",
"paint-order",
"path",
"pathlength",
"patterncontentunits",
"patterntransform",
"patternunits",
"points",
"preservealpha",
"preserveaspectratio",
"primitiveunits",
"r",
"rx",
"ry",
"radius",
"refx",
"refy",
"repeatcount",
"repeatdur",
"restart",
"result",
"rotate",
"scale",
"seed",
"shape-rendering",
"specularconstant",
"specularexponent",
"spreadmethod",
"startoffset",
"stddeviation",
"stitchtiles",
"stop-color",
"stop-opacity",
"stroke-dasharray",
"stroke-dashoffset",
"stroke-linecap",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke",
"stroke-width",
"style",
"surfacescale",
"systemlanguage",
"tabindex",
"targetx",
"targety",
"transform",
"transform-origin",
"text-anchor",
"text-decoration",
"text-rendering",
"textlength",
"type",
"u1",
"u2",
"unicode",
"values",
"viewbox",
"visibility",
"version",
"vert-adv-y",
"vert-origin-x",
"vert-origin-y",
"width",
"word-spacing",
"wrap",
"writing-mode",
"xchannelselector",
"ychannelselector",
"x",
"x1",
"x2",
"xmlns",
"y",
"y1",
"y2",
"z",
"zoomandpan",
'accent-height',
'accumulate',
'additive',
'alignment-baseline',
'amplitude',
'ascent',
'attributename',
'attributetype',
'azimuth',
'basefrequency',
'baseline-shift',
'begin',
'bias',
'by',
'class',
'clip',
'clippathunits',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cx',
'cy',
'd',
'dx',
'dy',
'diffuseconstant',
'direction',
'display',
'divisor',
'dur',
'edgemode',
'elevation',
'end',
'exponent',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'filterunits',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphref',
'gradientunits',
'gradienttransform',
'height',
'href',
'id',
'image-rendering',
'in',
'in2',
'intercept',
'k',
'k1',
'k2',
'k3',
'k4',
'kerning',
'keypoints',
'keysplines',
'keytimes',
'lang',
'lengthadjust',
'letter-spacing',
'kernelmatrix',
'kernelunitlength',
'lighting-color',
'local',
'marker-end',
'marker-mid',
'marker-start',
'markerheight',
'markerunits',
'markerwidth',
'maskcontentunits',
'maskunits',
'max',
'mask',
'media',
'method',
'mode',
'min',
'name',
'numoctaves',
'offset',
'operator',
'opacity',
'order',
'orient',
'orientation',
'origin',
'overflow',
'paint-order',
'path',
'pathlength',
'patterncontentunits',
'patterntransform',
'patternunits',
'points',
'preservealpha',
'preserveaspectratio',
'primitiveunits',
'r',
'rx',
'ry',
'radius',
'refx',
'refy',
'repeatcount',
'repeatdur',
'restart',
'result',
'rotate',
'scale',
'seed',
'shape-rendering',
'slope',
'specularconstant',
'specularexponent',
'spreadmethod',
'startoffset',
'stddeviation',
'stitchtiles',
'stop-color',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke',
'stroke-width',
'style',
'surfacescale',
'systemlanguage',
'tabindex',
'tablevalues',
'targetx',
'targety',
'transform',
'transform-origin',
'text-anchor',
'text-decoration',
'text-rendering',
'textlength',
'type',
'u1',
'u2',
'unicode',
'values',
'viewbox',
'visibility',
'version',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'width',
'word-spacing',
'wrap',
'writing-mode',
'xchannelselector',
'ychannelselector',
'x',
'x1',
'x2',
'xmlns',
'y',
'y1',
'y2',
'z',
'zoomandpan',
]);
export const mathMl = freeze([
"accent",
"accentunder",
"align",
"bevelled",
"close",
"columnsalign",
"columnlines",
"columnspan",
"denomalign",
"depth",
"dir",
"display",
"displaystyle",
"encoding",
"fence",
"frame",
"height",
"href",
"id",
"largeop",
"length",
"linethickness",
"lspace",
"lquote",
"mathbackground",
"mathcolor",
"mathsize",
"mathvariant",
"maxsize",
"minsize",
"movablelimits",
"notation",
"numalign",
"open",
"rowalign",
"rowlines",
"rowspacing",
"rowspan",
"rspace",
"rquote",
"scriptlevel",
"scriptminsize",
"scriptsizemultiplier",
"selection",
"separator",
"separators",
"stretchy",
"subscriptshift",
"supscriptshift",
"symmetric",
"voffset",
"width",
"xmlns",
'accent',
'accentunder',
'align',
'bevelled',
'close',
'columnsalign',
'columnlines',
'columnspan',
'denomalign',
'depth',
'dir',
'display',
'displaystyle',
'encoding',
'fence',
'frame',
'height',
'href',
'id',
'largeop',
'length',
'linethickness',
'lspace',
'lquote',
'mathbackground',
'mathcolor',
'mathsize',
'mathvariant',
'maxsize',
'minsize',
'movablelimits',
'notation',
'numalign',
'open',
'rowalign',
'rowlines',
'rowspacing',
'rowspan',
'rspace',
'rquote',
'scriptlevel',
'scriptminsize',
'scriptsizemultiplier',
'selection',
'separator',
'separators',
'stretchy',
'subscriptshift',
'supscriptshift',
'symmetric',
'voffset',
'width',
'xmlns',
]);
export const xml = freeze([
"xlink:href",
"xml:id",
"xlink:title",
"xml:space",
"xmlns:xlink",
'xlink:href',
'xml:id',
'xlink:title',
'xml:space',
'xmlns:xlink',
]);

View file

@ -1,6 +1,6 @@
import * as TAGS from "./tags.js";
import * as ATTRS from "./attrs.js";
import * as EXPRESSIONS from "./regexp.js";
import * as TAGS from './tags.js';
import * as ATTRS from './attrs.js';
import * as EXPRESSIONS from './regexp.js';
import {
addToSet,
clone,
@ -20,7 +20,7 @@ import {
lookupGetter,
create,
objectHasOwnProperty,
} from "./utils.js";
} from './utils.js';
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
const NODE_TYPE = {
@ -39,7 +39,7 @@ const NODE_TYPE = {
};
const getGlobal = function () {
return typeof window === "undefined" ? null : window;
return typeof window === 'undefined' ? null : window;
};
/**
@ -52,8 +52,8 @@ const getGlobal = function () {
*/
const _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) {
if (
typeof trustedTypes !== "object" ||
typeof trustedTypes.createPolicy !== "function"
typeof trustedTypes !== 'object' ||
typeof trustedTypes.createPolicy !== 'function'
) {
return null;
}
@ -62,12 +62,12 @@ const _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) {
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
// Policy creation with duplicate names throws in Trusted Types.
let suffix = null;
const ATTR_NAME = "data-tt-policy-suffix";
const ATTR_NAME = 'data-tt-policy-suffix';
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
suffix = purifyHostElement.getAttribute(ATTR_NAME);
}
const policyName = "dompurify" + (suffix ? "#" + suffix : "");
const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
try {
return trustedTypes.createPolicy(policyName, {
@ -83,7 +83,7 @@ const _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) {
// already run). Skip creating the policy, as this will only cause errors
// if TT are enforced.
console.warn(
"TrustedTypes policy " + policyName + " could not be created.",
'TrustedTypes policy ' + policyName + ' could not be created.'
);
return null;
}
@ -134,11 +134,11 @@ function createDOMPurify(window = getGlobal()) {
const ElementPrototype = Element.prototype;
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
const remove = lookupGetter(ElementPrototype, "remove");
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
const getParentNode = lookupGetter(ElementPrototype, "parentNode");
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
const remove = lookupGetter(ElementPrototype, 'remove');
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
// As per issue #47, the web-components registry is inherited by a
// new document created via createHTMLDocument. As per the spec
@ -146,15 +146,15 @@ function createDOMPurify(window = getGlobal()) {
// a new empty registry is used when creating a template contents owner
// document, so we use that as our parent document to ensure nothing
// is inherited.
if (typeof HTMLTemplateElement === "function") {
const template = document.createElement("template");
if (typeof HTMLTemplateElement === 'function') {
const template = document.createElement('template');
if (template.content && template.content.ownerDocument) {
document = template.content.ownerDocument;
}
}
let trustedTypesPolicy;
let emptyHTML = "";
let emptyHTML = '';
const {
implementation,
@ -170,8 +170,8 @@ function createDOMPurify(window = getGlobal()) {
* Expose whether this browser supports running the full DOMPurify.
*/
DOMPurify.isSupported =
typeof entries === "function" &&
typeof getParentNode === "function" &&
typeof entries === 'function' &&
typeof getParentNode === 'function' &&
implementation &&
implementation.createHTMLDocument !== undefined;
@ -238,7 +238,7 @@ function createDOMPurify(window = getGlobal()) {
enumerable: true,
value: false,
},
}),
})
);
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
@ -313,7 +313,7 @@ function createDOMPurify(window = getGlobal()) {
* with a constant string, i.e., `user-content-`
*/
let SANITIZE_NAMED_PROPS = false;
const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
/* Keep element content when removing element? */
let KEEP_CONTENT = true;
@ -328,66 +328,66 @@ function createDOMPurify(window = getGlobal()) {
/* Tags to ignore content of when KEEP_CONTENT is true */
let FORBID_CONTENTS = null;
const DEFAULT_FORBID_CONTENTS = addToSet({}, [
"annotation-xml",
"audio",
"colgroup",
"desc",
"foreignobject",
"head",
"iframe",
"math",
"mi",
"mn",
"mo",
"ms",
"mtext",
"noembed",
"noframes",
"noscript",
"plaintext",
"script",
"style",
"svg",
"template",
"thead",
"title",
"video",
"xmp",
'annotation-xml',
'audio',
'colgroup',
'desc',
'foreignobject',
'head',
'iframe',
'math',
'mi',
'mn',
'mo',
'ms',
'mtext',
'noembed',
'noframes',
'noscript',
'plaintext',
'script',
'style',
'svg',
'template',
'thead',
'title',
'video',
'xmp',
]);
/* Tags that are safe for data: URIs */
let DATA_URI_TAGS = null;
const DEFAULT_DATA_URI_TAGS = addToSet({}, [
"audio",
"video",
"img",
"source",
"image",
"track",
'audio',
'video',
'img',
'source',
'image',
'track',
]);
/* Attributes safe for values like "javascript:" */
let URI_SAFE_ATTRIBUTES = null;
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
"alt",
"class",
"for",
"id",
"label",
"name",
"pattern",
"placeholder",
"role",
"summary",
"title",
"value",
"style",
"xmlns",
'alt',
'class',
'for',
'id',
'label',
'name',
'pattern',
'placeholder',
'role',
'summary',
'title',
'value',
'style',
'xmlns',
]);
const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
/* Document namespace */
let NAMESPACE = HTML_NAMESPACE;
let IS_EMPTY_INPUT = false;
@ -397,13 +397,13 @@ function createDOMPurify(window = getGlobal()) {
const DEFAULT_ALLOWED_NAMESPACES = addToSet(
{},
[MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE],
stringToString,
stringToString
);
/* Parsing of strict XHTML documents */
let PARSER_MEDIA_TYPE = null;
const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"];
const DEFAULT_PARSER_MEDIA_TYPE = "text/html";
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
let transformCaseFunc = null;
/* Keep a reference to config to pass to hooks */
@ -412,7 +412,7 @@ function createDOMPurify(window = getGlobal()) {
/* Ideally, do not touch anything below this line */
/* ______________________________________________ */
const formElement = document.createElement("form");
const formElement = document.createElement('form');
const isRegexOrFunction = function (testValue) {
return testValue instanceof RegExp || testValue instanceof Function;
@ -430,7 +430,7 @@ function createDOMPurify(window = getGlobal()) {
}
/* Shield configuration object from tampering */
if (!cfg || typeof cfg !== "object") {
if (!cfg || typeof cfg !== 'object') {
cfg = {};
}
@ -445,44 +445,44 @@ function createDOMPurify(window = getGlobal()) {
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
transformCaseFunc =
PARSER_MEDIA_TYPE === "application/xhtml+xml"
PARSER_MEDIA_TYPE === 'application/xhtml+xml'
? stringToString
: stringToLowerCase;
/* Set configuration parameters */
ALLOWED_TAGS = objectHasOwnProperty(cfg, "ALLOWED_TAGS")
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS')
? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)
: DEFAULT_ALLOWED_TAGS;
ALLOWED_ATTR = objectHasOwnProperty(cfg, "ALLOWED_ATTR")
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR')
? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc)
: DEFAULT_ALLOWED_ATTR;
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, "ALLOWED_NAMESPACES")
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES')
? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString)
: DEFAULT_ALLOWED_NAMESPACES;
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR")
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR')
? addToSet(
clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent
cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent
transformCaseFunc, // eslint-disable-line indent
transformCaseFunc // eslint-disable-line indent
) // eslint-disable-line indent
: DEFAULT_URI_SAFE_ATTRIBUTES;
DATA_URI_TAGS = objectHasOwnProperty(cfg, "ADD_DATA_URI_TAGS")
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS')
? addToSet(
clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent
cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent
transformCaseFunc, // eslint-disable-line indent
transformCaseFunc // eslint-disable-line indent
) // eslint-disable-line indent
: DEFAULT_DATA_URI_TAGS;
FORBID_CONTENTS = objectHasOwnProperty(cfg, "FORBID_CONTENTS")
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS')
? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc)
: DEFAULT_FORBID_CONTENTS;
FORBID_TAGS = objectHasOwnProperty(cfg, "FORBID_TAGS")
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS')
? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc)
: {};
FORBID_ATTR = objectHasOwnProperty(cfg, "FORBID_ATTR")
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR')
? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc)
: {};
USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES")
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES')
? cfg.USE_PROFILES
: false;
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
@ -522,7 +522,7 @@ function createDOMPurify(window = getGlobal()) {
if (
cfg.CUSTOM_ELEMENT_HANDLING &&
typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements ===
"boolean"
'boolean'
) {
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
@ -595,30 +595,30 @@ function createDOMPurify(window = getGlobal()) {
/* Add #text in case KEEP_CONTENT is set to true */
if (KEEP_CONTENT) {
ALLOWED_TAGS["#text"] = true;
ALLOWED_TAGS['#text'] = true;
}
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
if (WHOLE_DOCUMENT) {
addToSet(ALLOWED_TAGS, ["html", "head", "body"]);
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
}
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
if (ALLOWED_TAGS.table) {
addToSet(ALLOWED_TAGS, ["tbody"]);
addToSet(ALLOWED_TAGS, ['tbody']);
delete FORBID_TAGS.tbody;
}
if (cfg.TRUSTED_TYPES_POLICY) {
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") {
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
throw typeErrorCreate(
'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.',
'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'
);
}
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") {
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
throw typeErrorCreate(
'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.',
'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'
);
}
@ -626,19 +626,19 @@ function createDOMPurify(window = getGlobal()) {
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
// Sign local variables required by `sanitize`.
emptyHTML = trustedTypesPolicy.createHTML("");
emptyHTML = trustedTypesPolicy.createHTML('');
} else {
// Uninitialized policy, attempt to initialize the internal dompurify policy.
if (trustedTypesPolicy === undefined) {
trustedTypesPolicy = _createTrustedTypesPolicy(
trustedTypes,
currentScript,
currentScript
);
}
// If creating the internal policy succeeded sign internal variables.
if (trustedTypesPolicy !== null && typeof emptyHTML === "string") {
emptyHTML = trustedTypesPolicy.createHTML("");
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
emptyHTML = trustedTypesPolicy.createHTML('');
}
}
@ -652,28 +652,25 @@ function createDOMPurify(window = getGlobal()) {
};
const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [
"mi",
"mo",
"mn",
"ms",
"mtext",
'mi',
'mo',
'mn',
'ms',
'mtext',
]);
const HTML_INTEGRATION_POINTS = addToSet({}, [
"foreignobject",
"annotation-xml",
]);
const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
// Certain elements are allowed in both SVG and HTML
// namespace. We need to specify them explicitly
// so that they don't get erroneously deleted from
// HTML namespace.
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
"title",
"style",
"font",
"a",
"script",
'title',
'style',
'font',
'a',
'script',
]);
/* Keep track of all possible SVG and MathML tags
@ -703,7 +700,7 @@ function createDOMPurify(window = getGlobal()) {
if (!parent || !parent.tagName) {
parent = {
namespaceURI: NAMESPACE,
tagName: "template",
tagName: 'template',
};
}
@ -719,7 +716,7 @@ function createDOMPurify(window = getGlobal()) {
// is via <svg>. If it happens via any other tag, then
// it should be killed.
if (parent.namespaceURI === HTML_NAMESPACE) {
return tagName === "svg";
return tagName === 'svg';
}
// The only way to switch from MathML to SVG is via`
@ -727,8 +724,8 @@ function createDOMPurify(window = getGlobal()) {
// text integration points.
if (parent.namespaceURI === MATHML_NAMESPACE) {
return (
tagName === "svg" &&
(parentTagName === "annotation-xml" ||
tagName === 'svg' &&
(parentTagName === 'annotation-xml' ||
MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
);
}
@ -743,13 +740,13 @@ function createDOMPurify(window = getGlobal()) {
// is via <math>. If it happens via any other tag, then
// it should be killed.
if (parent.namespaceURI === HTML_NAMESPACE) {
return tagName === "math";
return tagName === 'math';
}
// The only way to switch from SVG to MathML is via
// <math> and HTML integration points
if (parent.namespaceURI === SVG_NAMESPACE) {
return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName];
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
}
// We only allow elements that are defined in MathML
@ -785,7 +782,7 @@ function createDOMPurify(window = getGlobal()) {
// For XHTML and XML documents that support custom namespaces
if (
PARSER_MEDIA_TYPE === "application/xhtml+xml" &&
PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&
ALLOWED_NAMESPACES[element.namespaceURI]
) {
return true;
@ -836,14 +833,14 @@ function createDOMPurify(window = getGlobal()) {
node.removeAttribute(name);
// We void attribute values for unremovable "is"" attributes
if (name === "is" && !ALLOWED_ATTR[name]) {
if (name === 'is' && !ALLOWED_ATTR[name]) {
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
try {
_forceRemove(node);
} catch (_) {}
} else {
try {
node.setAttribute(name, "");
node.setAttribute(name, '');
} catch (_) {}
}
}
@ -861,7 +858,7 @@ function createDOMPurify(window = getGlobal()) {
let leadingWhitespace = null;
if (FORCE_BODY) {
dirty = "<remove></remove>" + dirty;
dirty = '<remove></remove>' + dirty;
} else {
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
@ -869,14 +866,14 @@ function createDOMPurify(window = getGlobal()) {
}
if (
PARSER_MEDIA_TYPE === "application/xhtml+xml" &&
PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&
NAMESPACE === HTML_NAMESPACE
) {
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
dirty =
'<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' +
dirty +
"</body></html>";
'</body></html>';
}
const dirtyPayload = trustedTypesPolicy
@ -894,7 +891,7 @@ function createDOMPurify(window = getGlobal()) {
/* Use createHTMLDocument in case DOMParser is not available */
if (!doc || !doc.documentElement) {
doc = implementation.createDocument(NAMESPACE, "template", null);
doc = implementation.createDocument(NAMESPACE, 'template', null);
try {
doc.documentElement.innerHTML = IS_EMPTY_INPUT
? emptyHTML
@ -909,7 +906,7 @@ function createDOMPurify(window = getGlobal()) {
if (dirty && leadingWhitespace) {
body.insertBefore(
document.createTextNode(leadingWhitespace),
body.childNodes[0] || null,
body.childNodes[0] || null
);
}
@ -917,7 +914,7 @@ function createDOMPurify(window = getGlobal()) {
if (NAMESPACE === HTML_NAMESPACE) {
return getElementsByTagName.call(
doc,
WHOLE_DOCUMENT ? "html" : "body",
WHOLE_DOCUMENT ? 'html' : 'body'
)[0];
}
@ -940,7 +937,7 @@ function createDOMPurify(window = getGlobal()) {
NodeFilter.SHOW_TEXT |
NodeFilter.SHOW_PROCESSING_INSTRUCTION |
NodeFilter.SHOW_CDATA_SECTION,
null,
null
);
};
@ -953,15 +950,15 @@ function createDOMPurify(window = getGlobal()) {
const _isClobbered = function (elm) {
return (
elm instanceof HTMLFormElement &&
(typeof elm.nodeName !== "string" ||
typeof elm.textContent !== "string" ||
typeof elm.removeChild !== "function" ||
(typeof elm.nodeName !== 'string' ||
typeof elm.textContent !== 'string' ||
typeof elm.removeChild !== 'function' ||
!(elm.attributes instanceof NamedNodeMap) ||
typeof elm.removeAttribute !== "function" ||
typeof elm.setAttribute !== "function" ||
typeof elm.namespaceURI !== "string" ||
typeof elm.insertBefore !== "function" ||
typeof elm.hasChildNodes !== "function")
typeof elm.removeAttribute !== 'function' ||
typeof elm.setAttribute !== 'function' ||
typeof elm.namespaceURI !== 'string' ||
typeof elm.insertBefore !== 'function' ||
typeof elm.hasChildNodes !== 'function')
);
};
@ -972,7 +969,7 @@ function createDOMPurify(window = getGlobal()) {
* @return {Boolean} true is object is a DOM node
*/
const _isNode = function (object) {
return typeof Node === "function" && object instanceof Node;
return typeof Node === 'function' && object instanceof Node;
};
/**
@ -1007,7 +1004,7 @@ function createDOMPurify(window = getGlobal()) {
let content = null;
/* Execute a hook if present */
_executeHook("beforeSanitizeElements", currentNode, null);
_executeHook('beforeSanitizeElements', currentNode, null);
/* Check if element is clobbered or can clobber */
if (_isClobbered(currentNode)) {
@ -1019,7 +1016,7 @@ function createDOMPurify(window = getGlobal()) {
const tagName = transformCaseFunc(currentNode.nodeName);
/* Execute a hook if present */
_executeHook("uponSanitizeElement", currentNode, {
_executeHook('uponSanitizeElement', currentNode, {
tagName,
allowedTags: ALLOWED_TAGS,
});
@ -1098,9 +1095,9 @@ function createDOMPurify(window = getGlobal()) {
/* Make sure that older browsers don't get fallback-tag mXSS */
if (
(tagName === "noscript" ||
tagName === "noembed" ||
tagName === "noframes") &&
(tagName === 'noscript' ||
tagName === 'noembed' ||
tagName === 'noframes') &&
regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)
) {
_forceRemove(currentNode);
@ -1113,7 +1110,7 @@ function createDOMPurify(window = getGlobal()) {
content = currentNode.textContent;
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
content = stringReplace(content, expr, " ");
content = stringReplace(content, expr, ' ');
});
if (currentNode.textContent !== content) {
@ -1123,7 +1120,7 @@ function createDOMPurify(window = getGlobal()) {
}
/* Execute a hook if present */
_executeHook("afterSanitizeElements", currentNode, null);
_executeHook('afterSanitizeElements', currentNode, null);
return false;
};
@ -1141,7 +1138,7 @@ function createDOMPurify(window = getGlobal()) {
/* Make sure attribute cannot clobber */
if (
SANITIZE_DOM &&
(lcName === "id" || lcName === "name") &&
(lcName === 'id' || lcName === 'name') &&
(value in document || value in formElement)
) {
return false;
@ -1176,7 +1173,7 @@ function createDOMPurify(window = getGlobal()) {
CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)))) ||
// Alternative, second condition checks if it's an `is`-attribute, AND
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
(lcName === "is" &&
(lcName === 'is' &&
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements &&
((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) ||
@ -1194,15 +1191,15 @@ function createDOMPurify(window = getGlobal()) {
/* Check no script, data or unknown possibly unsafe URI
unless we know URI values are safe for that attribute */
} else if (
regExpTest(IS_ALLOWED_URI, stringReplace(value, ATTR_WHITESPACE, ""))
regExpTest(IS_ALLOWED_URI, stringReplace(value, ATTR_WHITESPACE, ''))
) {
// This attribute is safe
/* Keep image data URIs alive if src/xlink:href is allowed */
/* Further prevent gadget XSS for dynamically built script tags */
} else if (
(lcName === "src" || lcName === "xlink:href" || lcName === "href") &&
lcTag !== "script" &&
stringIndexOf(value, "data:") === 0 &&
(lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') &&
lcTag !== 'script' &&
stringIndexOf(value, 'data:') === 0 &&
DATA_URI_TAGS[lcTag]
) {
// This attribute is safe
@ -1211,7 +1208,7 @@ function createDOMPurify(window = getGlobal()) {
time, e.g. fb:, spotify: */
} else if (
ALLOW_UNKNOWN_PROTOCOLS &&
!regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ""))
!regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))
) {
// This attribute is safe
/* Check for binary attributes */
@ -1234,7 +1231,7 @@ function createDOMPurify(window = getGlobal()) {
* @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
*/
const _isBasicCustomElement = function (tagName) {
return tagName !== "annotation-xml" && stringMatch(tagName, CUSTOM_ELEMENT);
return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
};
/**
@ -1249,7 +1246,7 @@ function createDOMPurify(window = getGlobal()) {
*/
const _sanitizeAttributes = function (currentNode) {
/* Execute a hook if present */
_executeHook("beforeSanitizeAttributes", currentNode, null);
_executeHook('beforeSanitizeAttributes', currentNode, null);
const { attributes } = currentNode;
@ -1259,8 +1256,8 @@ function createDOMPurify(window = getGlobal()) {
}
const hookEvent = {
attrName: "",
attrValue: "",
attrName: '',
attrValue: '',
keepAttr: true,
allowedAttributes: ALLOWED_ATTR,
};
@ -1272,22 +1269,16 @@ function createDOMPurify(window = getGlobal()) {
const { name, namespaceURI, value: attrValue } = attr;
const lcName = transformCaseFunc(name);
let value = name === "value" ? attrValue : stringTrim(attrValue);
let value = name === 'value' ? attrValue : stringTrim(attrValue);
/* Execute a hook if present */
hookEvent.attrName = lcName;
hookEvent.attrValue = value;
hookEvent.keepAttr = true;
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
_executeHook("uponSanitizeAttribute", currentNode, hookEvent);
_executeHook('uponSanitizeAttribute', currentNode, hookEvent);
value = hookEvent.attrValue;
/* Work around a security issue with comments inside attributes */
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
_removeAttribute(name, currentNode);
continue;
}
/* Did the hooks approve of the attribute? */
if (hookEvent.forceKeepAttr) {
continue;
@ -1310,7 +1301,7 @@ function createDOMPurify(window = getGlobal()) {
/* Sanitize attribute content to be template-safe */
if (SAFE_FOR_TEMPLATES) {
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
value = stringReplace(value, expr, " ");
value = stringReplace(value, expr, ' ');
});
}
@ -1323,7 +1314,7 @@ function createDOMPurify(window = getGlobal()) {
/* Full DOM Clobbering protection via namespace isolation,
* Prefix id and name attributes with `user-content-`
*/
if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name")) {
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
// Remove the attribute with this value
_removeAttribute(name, currentNode);
@ -1331,22 +1322,28 @@ function createDOMPurify(window = getGlobal()) {
value = SANITIZE_NAMED_PROPS_PREFIX + value;
}
/* Work around a security issue with comments inside attributes */
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
_removeAttribute(name, currentNode);
continue;
}
/* Handle attributes that require Trusted Types */
if (
trustedTypesPolicy &&
typeof trustedTypes === "object" &&
typeof trustedTypes.getAttributeType === "function"
typeof trustedTypes === 'object' &&
typeof trustedTypes.getAttributeType === 'function'
) {
if (namespaceURI) {
/* Namespaces are not yet supported, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293 */
} else {
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
case "TrustedHTML": {
case 'TrustedHTML': {
value = trustedTypesPolicy.createHTML(value);
break;
}
case "TrustedScriptURL": {
case 'TrustedScriptURL': {
value = trustedTypesPolicy.createScriptURL(value);
break;
}
@ -1376,7 +1373,7 @@ function createDOMPurify(window = getGlobal()) {
}
/* Execute a hook if present */
_executeHook("afterSanitizeAttributes", currentNode, null);
_executeHook('afterSanitizeAttributes', currentNode, null);
};
/**
@ -1389,11 +1386,11 @@ function createDOMPurify(window = getGlobal()) {
const shadowIterator = _createNodeIterator(fragment);
/* Execute a hook if present */
_executeHook("beforeSanitizeShadowDOM", fragment, null);
_executeHook('beforeSanitizeShadowDOM', fragment, null);
while ((shadowNode = shadowIterator.nextNode())) {
/* Execute a hook if present */
_executeHook("uponSanitizeShadowNode", shadowNode, null);
_executeHook('uponSanitizeShadowNode', shadowNode, null);
/* Sanitize tags and elements */
if (_sanitizeElements(shadowNode)) {
@ -1410,7 +1407,7 @@ function createDOMPurify(window = getGlobal()) {
}
/* Execute a hook if present */
_executeHook("afterSanitizeShadowDOM", fragment, null);
_executeHook('afterSanitizeShadowDOM', fragment, null);
};
/**
@ -1431,18 +1428,18 @@ function createDOMPurify(window = getGlobal()) {
the user has requested a DOM object rather than a string */
IS_EMPTY_INPUT = !dirty;
if (IS_EMPTY_INPUT) {
dirty = "<!-->";
dirty = '<!-->';
}
/* Stringify, in case dirty is an object */
if (typeof dirty !== "string" && !_isNode(dirty)) {
if (typeof dirty.toString === "function") {
if (typeof dirty !== 'string' && !_isNode(dirty)) {
if (typeof dirty.toString === 'function') {
dirty = dirty.toString();
if (typeof dirty !== "string") {
throw typeErrorCreate("dirty is not a string, aborting");
if (typeof dirty !== 'string') {
throw typeErrorCreate('dirty is not a string, aborting');
}
} else {
throw typeErrorCreate("toString is not a function");
throw typeErrorCreate('toString is not a function');
}
}
@ -1460,7 +1457,7 @@ function createDOMPurify(window = getGlobal()) {
DOMPurify.removed = [];
/* Check if dirty is correctly typed for IN_PLACE */
if (typeof dirty === "string") {
if (typeof dirty === 'string') {
IN_PLACE = false;
}
@ -1470,22 +1467,22 @@ function createDOMPurify(window = getGlobal()) {
const tagName = transformCaseFunc(dirty.nodeName);
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
throw typeErrorCreate(
"root node is forbidden and cannot be sanitized in-place",
'root node is forbidden and cannot be sanitized in-place'
);
}
}
} else if (dirty instanceof Node) {
/* If dirty is a DOM element, append to an empty document to avoid
elements being stripped by the parser */
body = _initDocument("<!---->");
body = _initDocument('<!---->');
importedNode = body.ownerDocument.importNode(dirty, true);
if (
importedNode.nodeType === NODE_TYPE.element &&
importedNode.nodeName === "BODY"
importedNode.nodeName === 'BODY'
) {
/* Node is already a body, use as is */
body = importedNode;
} else if (importedNode.nodeName === "HTML") {
} else if (importedNode.nodeName === 'HTML') {
body = importedNode;
} else {
// eslint-disable-next-line unicorn/prefer-dom-node-append
@ -1498,7 +1495,7 @@ function createDOMPurify(window = getGlobal()) {
!SAFE_FOR_TEMPLATES &&
!WHOLE_DOCUMENT &&
// eslint-disable-next-line unicorn/prefer-includes
dirty.indexOf("<") === -1
dirty.indexOf('<') === -1
) {
return trustedTypesPolicy && RETURN_TRUSTED_TYPE
? trustedTypesPolicy.createHTML(dirty)
@ -1510,7 +1507,7 @@ function createDOMPurify(window = getGlobal()) {
/* Check we have a DOM node from the data */
if (!body) {
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
}
}
@ -1575,20 +1572,20 @@ function createDOMPurify(window = getGlobal()) {
/* Serialize doctype if allowed */
if (
WHOLE_DOCUMENT &&
ALLOWED_TAGS["!doctype"] &&
ALLOWED_TAGS['!doctype'] &&
body.ownerDocument &&
body.ownerDocument.doctype &&
body.ownerDocument.doctype.name &&
regExpTest(EXPRESSIONS.DOCTYPE_NAME, body.ownerDocument.doctype.name)
) {
serializedHTML =
"<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML;
'<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
}
/* Sanitize final string template-safe */
if (SAFE_FOR_TEMPLATES) {
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
serializedHTML = stringReplace(serializedHTML, expr, " ");
serializedHTML = stringReplace(serializedHTML, expr, ' ');
});
}
@ -1647,7 +1644,7 @@ function createDOMPurify(window = getGlobal()) {
* @param {Function} hookFunction function to execute
*/
DOMPurify.addHook = function (entryPoint, hookFunction) {
if (typeof hookFunction !== "function") {
if (typeof hookFunction !== 'function') {
return;
}

View file

@ -1,4 +1,4 @@
import { seal } from "./utils.js";
import { seal } from './utils.js';
// eslint-disable-next-line unicorn/better-regex
export const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
@ -7,11 +7,11 @@ export const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
export const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
export const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
export const IS_ALLOWED_URI = seal(
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, // eslint-disable-line no-useless-escape
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
);
export const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
export const ATTR_WHITESPACE = seal(
/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g, // eslint-disable-line no-control-regex
/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
);
export const DOCTYPE_NAME = seal(/^html$/i);
export const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);

View file

@ -1,198 +1,198 @@
import { freeze } from "./utils.js";
import { freeze } from './utils.js';
export const html = freeze([
"a",
"abbr",
"acronym",
"address",
"area",
"article",
"aside",
"audio",
"b",
"bdi",
"bdo",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"cite",
"code",
"col",
"colgroup",
"content",
"data",
"datalist",
"dd",
"decorator",
"del",
"details",
"dfn",
"dialog",
"dir",
"div",
"dl",
"dt",
"element",
"em",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"main",
"map",
"mark",
"marquee",
"menu",
"menuitem",
"meter",
"nav",
"nobr",
"ol",
"optgroup",
"option",
"output",
"p",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"section",
"select",
"shadow",
"small",
"source",
"spacer",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
'a',
'abbr',
'acronym',
'address',
'area',
'article',
'aside',
'audio',
'b',
'bdi',
'bdo',
'big',
'blink',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'center',
'cite',
'code',
'col',
'colgroup',
'content',
'data',
'datalist',
'dd',
'decorator',
'del',
'details',
'dfn',
'dialog',
'dir',
'div',
'dl',
'dt',
'element',
'em',
'fieldset',
'figcaption',
'figure',
'font',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
'main',
'map',
'mark',
'marquee',
'menu',
'menuitem',
'meter',
'nav',
'nobr',
'ol',
'optgroup',
'option',
'output',
'p',
'picture',
'pre',
'progress',
'q',
'rp',
'rt',
'ruby',
's',
'samp',
'section',
'select',
'shadow',
'small',
'source',
'spacer',
'span',
'strike',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'tr',
'track',
'tt',
'u',
'ul',
'var',
'video',
'wbr',
]);
// SVG
export const svg = freeze([
"svg",
"a",
"altglyph",
"altglyphdef",
"altglyphitem",
"animatecolor",
"animatemotion",
"animatetransform",
"circle",
"clippath",
"defs",
"desc",
"ellipse",
"filter",
"font",
"g",
"glyph",
"glyphref",
"hkern",
"image",
"line",
"lineargradient",
"marker",
"mask",
"metadata",
"mpath",
"path",
"pattern",
"polygon",
"polyline",
"radialgradient",
"rect",
"stop",
"style",
"switch",
"symbol",
"text",
"textpath",
"title",
"tref",
"tspan",
"view",
"vkern",
'svg',
'a',
'altglyph',
'altglyphdef',
'altglyphitem',
'animatecolor',
'animatemotion',
'animatetransform',
'circle',
'clippath',
'defs',
'desc',
'ellipse',
'filter',
'font',
'g',
'glyph',
'glyphref',
'hkern',
'image',
'line',
'lineargradient',
'marker',
'mask',
'metadata',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialgradient',
'rect',
'stop',
'style',
'switch',
'symbol',
'text',
'textpath',
'title',
'tref',
'tspan',
'view',
'vkern',
]);
export const svgFilters = freeze([
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feDropShadow",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
]);
// List of SVG elements that are disallowed by default.
@ -200,81 +200,81 @@ export const svgFilters = freeze([
// checks properly in case one wants to add them to
// allow-list.
export const svgDisallowed = freeze([
"animate",
"color-profile",
"cursor",
"discard",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
"foreignobject",
"hatch",
"hatchpath",
"mesh",
"meshgradient",
"meshpatch",
"meshrow",
"missing-glyph",
"script",
"set",
"solidcolor",
"unknown",
"use",
'animate',
'color-profile',
'cursor',
'discard',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignobject',
'hatch',
'hatchpath',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'missing-glyph',
'script',
'set',
'solidcolor',
'unknown',
'use',
]);
export const mathMl = freeze([
"math",
"menclose",
"merror",
"mfenced",
"mfrac",
"mglyph",
"mi",
"mlabeledtr",
"mmultiscripts",
"mn",
"mo",
"mover",
"mpadded",
"mphantom",
"mroot",
"mrow",
"ms",
"mspace",
"msqrt",
"mstyle",
"msub",
"msup",
"msubsup",
"mtable",
"mtd",
"mtext",
"mtr",
"munder",
"munderover",
"mprescripts",
'math',
'menclose',
'merror',
'mfenced',
'mfrac',
'mglyph',
'mi',
'mlabeledtr',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msup',
'msubsup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover',
'mprescripts',
]);
// Similarly to SVG, we want to know all MathML elements,
// even those that we disallow by default.
export const mathMlDisallowed = freeze([
"maction",
"maligngroup",
"malignmark",
"mlongdiv",
"mscarries",
"mscarry",
"msgroup",
"mstack",
"msline",
"msrow",
"semantics",
"annotation",
"annotation-xml",
"mprescripts",
"none",
'maction',
'maligngroup',
'malignmark',
'mlongdiv',
'mscarries',
'mscarry',
'msgroup',
'mstack',
'msline',
'msrow',
'semantics',
'annotation',
'annotation-xml',
'mprescripts',
'none',
]);
export const text = freeze(["#text"]);
export const text = freeze(['#text']);

View file

@ -7,7 +7,7 @@ const {
} = Object;
let { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports
let { apply, construct } = typeof Reflect !== "undefined" && Reflect;
let { apply, construct } = typeof Reflect !== 'undefined' && Reflect;
if (!freeze) {
freeze = function (x) {
@ -91,7 +91,7 @@ function addToSet(set, array, transformCaseFunc = stringToLowerCase) {
let l = array.length;
while (l--) {
let element = array[l];
if (typeof element === "string") {
if (typeof element === 'string') {
const lcElement = transformCaseFunc(element);
if (lcElement !== element) {
// Config presets (e.g. tags.js, attrs.js) are immutable.
@ -144,7 +144,7 @@ function clone(object) {
newObject[property] = cleanArray(value);
} else if (
value &&
typeof value === "object" &&
typeof value === 'object' &&
value.constructor === Object
) {
newObject[property] = clone(value);
@ -173,7 +173,7 @@ function lookupGetter(object, prop) {
return unapply(desc.get);
}
if (typeof desc.value === "function") {
if (typeof desc.value === 'function') {
return unapply(desc.value);
}
}

View file

@ -39,7 +39,7 @@
<div class="card p-2 position-relative">
<i class='bx bx-hard-hat bx-sm position-absolute top-0 end-0 m-3'></i>
<div class="card-header">
<h5 class="card-title fw-semibold">Build with</h5>
<h5 class="card-title don-jose">Build with</h5>
</div>
<div class="card-body">
<div class="row g-6 pt-0">
@ -152,7 +152,7 @@
<div class="card p-2 position-relative">
<i class='bx bx-paperclip bx-sm position-absolute top-0 end-0 m-3'></i>
<div class="card-header">
<h5 class="card-title fw-semibold">License</h5>
<h5 class="card-title don-jose">License</h5>
</div>
<div class="card-body text-center">
<p class="fs-5 mb-4">GNU Affero General Public License v3.0</p>
@ -167,7 +167,7 @@
<div class="card p-2 position-relative">
<i class='bx bxs-megaphone bx-sm position-absolute top-0 end-0 m-3'></i>
<div class="card-header">
<h5 class="card-title fw-semibold">Socials</h5>
<h5 class="card-title don-jose">Socials</h5>
</div>
<div class="card-body text-center">
<div class="social-buttons">
@ -211,7 +211,7 @@
<div class="card p-2 position-relative">
<i class='bx bxs-megaphone bx-sm position-absolute top-0 end-0 m-3'></i>
<div class="card-header">
<h5 class="card-title fw-semibold">Socials</h5>
<h5 class="card-title don-jose">Socials</h5>
</div>
<div class="card-body text-center">
<div class="social-buttons">

View file

@ -41,6 +41,12 @@
<link rel="stylesheet"
href="{{ url_for('static', filename='fonts/Public_sans.css') }}"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="{{ url_for('static', filename='fonts/DonJose.css') }}"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="{{ url_for('static', filename='fonts/Courier_Prime.css') }}"
nonce="{{ style_nonce }}" />
<!-- Icons -->
<link rel="stylesheet"
href="{{ url_for('static', filename='fonts/boxicons.min.css') }}"
@ -135,6 +141,12 @@
<script src="{{ url_for('static', filename='libs/datatables/plugins/ip-address.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
{% if current_endpoint == "home" %}
<script src="{{ url_for('static', filename='libs/apexcharts/apexcharts.min.js') }}"
nonce="{{ script_nonce }}"></script>
<script src="{{ url_for('static', filename='libs/leaflet/leaflet.min.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
{% if current_endpoint in ("bans") %}
<script src="{{ url_for('static', filename='libs/flatpickr/flatpickr.min.js') }}"
nonce="{{ script_nonce }}"></script>
@ -145,15 +157,13 @@
<script src="{{ url_for('static', filename='libs/ace/src-min/ace.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
{% if current_endpoint in ("loading", "instances") %}
<script src="{{ url_for('static', filename='libs/lottie-player/lottie-player.min.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
<!-- Core JS -->
<script src="{{ url_for('static', filename='js/menu.js') }}"
nonce="{{ script_nonce }}"></script>
{% if current_endpoint == "home" %}
<script src="{{ url_for('static', filename='libs/apexcharts/apexcharts.min.js') }}"
nonce="{{ script_nonce }}"></script>
<script src="{{ url_for('static', filename='libs/leaflet/leaflet.min.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
<!-- Main JS -->
<script src="{{ url_for('static', filename='js/main.js') }}"
nonce="{{ script_nonce }}"></script>
@ -173,6 +183,9 @@
{% if current_endpoint == "setup" %}
<script src="{{ url_for('static', filename='js/pages/setup.js') }}"
nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "loading" %}
<script src="{{ url_for('static', filename='js/pages/loading.js') }}"
nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "home" %}
<script async
defer

View file

@ -41,7 +41,7 @@
{% if mode == 'easy' %}aria-selected="true"{% endif %}>
<i class="bx bx-customize bx-sm"></i>
&nbsp;
<span>Easy</span>
<span class="don-jose">Easy</span>
</button>
</li>
<li class="nav-item mb-1" role="presentation">
@ -54,7 +54,7 @@
{% if mode == 'advanced' %}aria-selected="true"{% endif %}>
<i class="bx bx-shield-quarter bx-sm"></i>
&nbsp;
<span>Advanced</span>
<span class="don-jose">Advanced</span>
</button>
</li>
<li class="nav-item" role="presentation">
@ -67,7 +67,7 @@
{% if mode == 'raw' %}aria-selected="true"{% endif %}>
<i class="bx bx-notepad bx-sm"></i>
&nbsp;
<span>Raw</span>
<span class="don-jose">Raw</span>
</button>
</li>
</ul>
@ -86,7 +86,7 @@
{% if mode == 'easy' %}aria-selected="true"{% endif %}>
<i class="bx bx-customize bx-xs"></i>
&nbsp;
<span class="d-none d-sm-inline">Easy</span>
<span class="d-none d-sm-inline don-jose">Easy</span>
</button>
</li>
<li class="nav-item me-0 me-sm-3" role="presentation">
@ -99,7 +99,7 @@
{% if mode == 'advanced' %}aria-selected="true"{% endif %}>
<i class="bx bx-shield-quarter bx-xs"></i>
&nbsp;
<span class="d-none d-sm-inline">Advanced</span>
<span class="d-none d-sm-inline don-jose">Advanced</span>
</button>
</li>
<li class="nav-item" role="presentation">
@ -112,7 +112,7 @@
{% if mode == 'raw' %}aria-selected="true"{% endif %}>
<i class="bx bx-notepad bx-xs"></i>
&nbsp;
<span class="d-none d-sm-inline">Raw</span>
<span class="d-none d-sm-inline don-jose">Raw</span>
</button>
</li>
</ul>
@ -168,7 +168,7 @@
{% include "sidebar-notifications.html" %}
{% include "sidebar-news.html" %}
{% if not is_pro_version %}
<div class="buy-now">
<div class="buy-now courier-prime">
<a class="btn btn-responsive btn-buy-now"
role="button"
aria-pressed="true"

View file

@ -8,7 +8,7 @@
<p class="ps-4 fs-4 mb-2{% if is_pro_version %} text-primary shine{% else %} text-white{% endif %}">
Plan
<br />
<span class="fs-3 fw-bold">
<span class="fs-3 fw-bold don-jose">
{% if is_pro_version %}
PRO
{% else %}
@ -17,7 +17,7 @@
</span>
</p>
<a class="ps-4 text-underline{% if not is_pro_version %} text-white-80{% endif %}"
href="#">Upgrade?</a>
href="{{ url_for('pro') }}">Upgrade?</a>
</div>
</div>
<div class="col-sm-3 mb-2">
@ -28,7 +28,7 @@
<p class="ps-4 fs-4 mb-2">
Instances
<br />
<span class="fs-3 fw-bold">
<span class="fs-3 don-jose">
{{ '0' if instances|length < 10 else '' }}{{ instances|length }}
</span>
</p>
@ -55,7 +55,7 @@
<p class="ps-4 fs-4 mb-2">
Services
<br />
<span class="fs-3 fw-bold">
<span class="fs-3 don-jose">
{{ '0' if services|length < 10 else '' }}{{ services|length }}
</span>
</p>
@ -80,7 +80,7 @@
<p class="ps-4 fs-4 mb-2">
Plugins
<br />
<span class="fs-3 fw-bold">
<span class="fs-3 don-jose">
{{ '0' if plugins|length < 10 else '' }}{{ plugins|length }}
</span>
</p>
@ -103,7 +103,7 @@
<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">Requests countries</h5>
<h5 class="mb-1 me-2 don-jose">Blocked Requests countries</h5>
</div>
</div>
<div class="card-body p-0">
@ -116,7 +116,7 @@
<div class="card p-4 position-relative shadow-sm rounded-3">
<div class="card-header p-2">
<div class="card-title mb-0 d-flex justify-content-between">
<h5>News :</h5>
<h5 class="don-jose">News</h5>
<a class="text-decoration-underline link-underline-primary"
href="https://www.bunkerweb.io/blog?utm_campaign=self&amp;utm_source=ui"
target="_blank"
@ -143,41 +143,25 @@
</div>
</div>
</div>
<div class="col-sm-3 mb-2">
<div class="col-sm-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">
Total Requests
<br />
<span class="fs-3 fw-bold">{{ human_readable_number(request_errors.values() |sum) }}</span>
</p>
</div>
</div>
{% set ips_ns = namespace(ips=0, blocked_ips=0) %}
{% for data in request_ips.values() %}
{% if data["blocked"] > 0 %}
{% set ips_ns.blocked_ips = ips_ns.blocked_ips + 1 %}
{% endif %}
{% endfor %}
<div class="col-sm-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-secondary'></i>
<p class="ps-4 fs-4 mb-2">
Unique Ips
<br />
<span class="fs-3 fw-bold">{{ human_readable_number(request_ips|length) }}</span>
<span class="fs-3 don-jose">{{ human_readable_number(request_errors.values() |sum) }}</span>
</p>
</div>
</div>
<div class="col-sm-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('reports') }}">
href="{{ url_for('loading', next=url_for('reports') ) }}">
<i class='bx bx-block bx-sm position-absolute top-0 end-0 m-3 text-danger'></i>
<p class="ps-4 fs-4 mb-2">
Blocked Requests
<br />
<span class="fs-3 fw-bold">
<span class="fs-3 don-jose">
{% set ns = namespace(blocked_requests=0) %}
{% for status, count in request_errors.items() %}
{% if status in (403, 429, 444) %}
@ -195,7 +179,13 @@
<p class="ps-4 fs-4 mb-2">
Blocked Unique Ips
<br />
<span class="fs-3 fw-bold">{{ human_readable_number(ips_ns.blocked_ips) }}</span>
{% set ips_ns = namespace(ips=0, blocked_ips=0) %}
{% for data in request_ips.values() %}
{% if data["blocked"] > 0 %}
{% set ips_ns.blocked_ips = ips_ns.blocked_ips + 1 %}
{% endif %}
{% endfor %}
<span class="fs-3 don-jose">{{ human_readable_number(ips_ns.blocked_ips) }}</span>
</p>
</div>
</div>
@ -203,7 +193,7 @@
<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">Request status</h5>
<h5 class="mb-1 me-2 don-jose">Request status</h5>
</div>
</div>
<div class="card-body">
@ -218,16 +208,24 @@
<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">Request ips</h5>
<h5 class="mb-1 me-2 don-jose">Blocked Request ips</h5>
</div>
</div>
<div class="card-body">
{% 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>
{% 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>
@ -235,7 +233,7 @@
<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">Blocking status</h5>
<h5 class="mb-1 me-2 don-jose">Blocking status</h5>
</div>
</div>
<div class="card-body">

View file

@ -8,11 +8,11 @@
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-transparent border-0 shadow-none">
<div class="modal-body text-center">
<div class="spinner-border text-bw-green" role="status">
<span class="visually-hidden">Loading...</span>
<div class="modal-body">
<div class="d-flex justify-content-center align-items-center w-100">
<lottie-player src="{{ url_for('static', filename='json/periscop.min.json') }}" background="transparent" speed="1" style="width: 300px; height: 300px;" class="img-fluid" loop autoplay></lottie-player>
</div>
<p class="mt-3 text-white">Pinging instances, please wait...</p>
<p class="mt-3 text-white text-center">Pinging instances, please wait...</p>
</div>
</div>
</div>

View file

@ -49,11 +49,11 @@
<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"] }}
</td>
<td class="text-center">
<i class="bx bx-sm bx-{% if job_data['reload'] %}check-circle text-success{% else %}x-circle text-danger{% endif %}"></i>
<i class="bx bx-sm bx-{% if job_data['reload'] %}check text-success{% else %}x text-danger{% endif %}"></i>
</td>
<td class="text-center">
{% if job_data['history'] %}
<i class="bx bx-sm bx-{% if job_data['history'][0]['success'] %}check-circle text-success{% else %}x-circle text-danger{% endif %}"></i>
<i class="bx bx-sm bx-{% if job_data['history'][0]['success'] %}check text-success{% else %}x text-danger{% endif %}"></i>
{% else %}
No history
{% endif %}
@ -97,7 +97,7 @@
style="flex: 1 1 0">{{ history['end_date'] }}</li>
<li class="list-group-item align-items-center text-center rounded-0{% if loop.index == job_data['history']|length %} rounded-bottom{% endif %}"
style="flex: 1 1 0">
<i class="bx bx-{% if history['success'] %}check-circle text-success{% else %}x-circle text-danger{% endif %}"></i>
<i class="bx bx-{% if history['success'] %}check text-success{% else %}x text-danger{% endif %}"></i>
</li>
</ul>
{% endfor %}

View file

@ -4,80 +4,38 @@
<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">
<div class="layout-main-placeholder">
<img src="{{ url_for('static', filename='img/logo-menu-2.png') }}"
class="img-fluid pulsating"
alt="Logo" />
<div class="layout-main-wrapper mt-0 pb-10">
<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>
{% if message %}
<div class="layout-main-info mb-1">
<h3>{{ message }}</h3>
<div class="layout-main-info mt-1 mb-1">
<h3 class="mb-0 don-jose">{{ message }}</h3>
</div>
{% endif %}
<div id="auto-redirect" class="layout-main-info">
<h3>Auto Redirecting in 60 seconds...</h3>
<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>
</div>
</div>
<input type="hidden" id="next-endpoint" value="{{ next }}" />
{% if target_endpoint %}<input type="hidden" id="target-endpoint" value="{{ target_endpoint }}" />{% endif %}
<!-- / Content -->
<script nonce="{{ script_nonce }}">
const reloading = setInterval(check_reloading, 2000);
{% if target_endpoint %}
var target_endpoint = "{{ target_endpoint }}";
{% else %}
var target_endpoint = null;
{% endif %}
check_reloading();
// Set the full timeout for redirection after 60 seconds
const fullTimeout = setTimeout(() => {
window.location.replace(
target_endpoint ? target_endpoint : ("{{ next }}" + (window.location.hash ? window.location.hash : ""))
);
}, 60000); // 60 seconds
// Initialize timeRemaining
let timeRemaining = 60;
// Start the countdown interval
const countdownInterval = setInterval(() => {
timeRemaining--;
// Update the countdown display
const countdownElement = document.getElementById("auto-redirect");
if (countdownElement) {
countdownElement.innerHTML = `<h3>Auto Redirecting in ${timeRemaining} seconds...</h3>`;
}
// If time runs out, clear the interval
if (timeRemaining <= 0) {
clearInterval(countdownInterval);
}
}, 1000);
async function check_reloading() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000);
const response = await fetch(
target_endpoint ? target_endpoint : `${location.href.replace("/loading", "/check_reloading")}`,
{ signal: controller.signal },
);
if (response.status === 200) {
try {
const res = await response.json();
if (res.message === "ok" || res.reloading === false) {
clearInterval(reloading);
clearInterval(countdownInterval);
clearTimeout(fullTimeout);
window.location.replace("{{ next }}" + (window.location.hash ? window.location.hash : ""));
}
} catch (error) {}
}
}
</script>
{% endblock %}

View file

@ -13,7 +13,7 @@
target="_blank"
rel="noopener"
class="app-brand-link gap-2">
<span class="app-brand-logo login w-75">
<span class="app-brand-logo login w-50">
<img class="img-fluid"
src="{{ url_for('static', filename='img/logo-menu.png') }}"
alt="BunkerWeb logo">
@ -21,7 +21,6 @@
</a>
</div>
<!-- /Logo -->
<p class="mb-6">Please sign-in to your account</p>
<form class="mb-6" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden"
@ -67,7 +66,7 @@
</div>
</div>
<div class="mb-6">
<button class="btn btn-primary d-grid w-100" type="submit">Login</button>
<button class="btn btn-primary d-grid w-100 don-jose" type="submit">Login</button>
</div>
</form>
</div>

View file

@ -4,7 +4,7 @@
<a href="https://www.bunkerweb.io/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener"
class="app-brand-link">
class="app-brand-link p-5">
<span class="app-brand-logo main w-100">
<img class="img-fluid"
src="{{ url_for('static', filename='img/logo-menu.png') }}"
@ -16,7 +16,6 @@
<i class="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
</a>
</div>
<div class="border border-primary border-0 border-top"></div>
{% with menu_items = {
"home": {"url": url_for('home'), "icon": "bx-home-smile"},
"instances": {"url": url_for('instances'), "icon": "bx-server"},
@ -117,8 +116,8 @@
</li>
<li class="menu-item">
<a href="{{ url_for('pro') }}" class="menu-link">
<img class="menu-icon tf-icons"
src="{{ pro_diamond_url }}"
<img class="menu-icon tf-icons"
src="{{ pro_diamond_url }}"
alt="Pro plugin">
<div class="text-truncate" data-i18n="Pro">Pro</div>
</a>

View file

@ -107,17 +107,17 @@
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{{ plugin_types[plugin_data['type']].get('text-class', '') }}{{ plugin_types[plugin_data['type']].get('title-class', '') }}">
<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', '') }}">
{{ 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-2">{{ plugin_data["description"] }}</p>
<p class="card-subtitle text-muted text-truncate mt-3">{{ 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/#protect-udptcp-applications"
class="btn btn-sm btn-{% if plugin_data['stream'] == 'yes' %}bw-green{% elif plugin_data['stream'] == 'partial' %}warning{% else %}danger{% endif %} rounded-pill"
class="btn btn-sm btn-outline-{% if plugin_data['stream'] == 'yes' %}bw-green{% elif plugin_data['stream'] == 'partial' %}warning{% else %}danger{% endif %} rounded-pill"
target="_blank"
rel="noopener"
data-bs-toggle="tooltip"

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{{ plugin_types[template_plugin['type']].get('text-class', '') }}{{ plugin_types[template_plugin['type']].get('title-class', '') }}">
<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', '') }}">
{{ 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-2">{{ template_data["name"] }}</p>
<p class="card-subtitle text-muted text-truncate mt-3">{{ template_data["name"] }}</p>
</div>
</div>
<div class="card-body">

View file

@ -33,7 +33,7 @@
<div class="navbar-nav align-items-center">
<div class="nav-item d-flex align-items-center">
<a role="button"
class="btn btn-outline-secondary p-1 p-md-2"
class="btn btn-text-secondary p-1 p-md-2"
aria-pressed="true"
href="https://docs.bunkerweb.io/latest{{ documentation_endpoint }}/?utm_campaign=self&utm_source=ui{{ documentation_fragment }}"
target="_blank"
@ -81,13 +81,13 @@
href="https://github.com/bunkerity/bunkerweb/releases/latest"
target="_blank"
rel="noreferrer"
{% if bw_version != latest_version %} data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="New version available" {% endif %}>
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="{% if bw_version != latest_version %}New version available{% else %}Latest version installed{% endif %}">
Version {{ bw_version }}
{% if bw_version != latest_version %}
<span class="badge-dot position-absolute top-0 start-100 translate-middle bg-danger border border-light rounded-circle">
<span class="visually-hidden">New version</span>
</span>
{% endif %}
<span class="badge-dot position-absolute top-0 start-100 translate-middle bg-{% if bw_version != latest_version %}danger{% else %}success{% endif %} border border-light rounded-circle">
<span class="visually-hidden">New version</span>
</span>
</a>
</li>
<!--/ Version -->

View file

@ -63,7 +63,7 @@
<div data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="{% if plugin_data['stream'] != 'no' %}Supports{% else %}Doesn't support{% endif %} STREAM mode{% if plugin_data['stream'] == 'partial' %} partially{% endif %}">
<i class="bx bx-sm bx-{% if plugin_data['stream'] == 'yes' %}check-circle text-success{% elif plugin_data['stream'] == 'partial' %}minus-circle text-warning{% else %}x-circle text-danger{% endif %}"></i>
<i class="bx bx-sm bx-{% if plugin_data['stream'] == 'yes' %}check text-success{% elif plugin_data['stream'] == 'partial' %}minus text-warning{% else %}x text-danger{% endif %}"></i>
</td>
</div>
<td id="type-{{ plugin }}">

View file

@ -1,6 +1,5 @@
{% extends "dashboard.html" %}
{% block content %}
<!-- Content -->
<!--/ Content -->
{% endblock %}

View file

@ -16,24 +16,14 @@
<div class="card px-sm-6 px-0">
<div class="card-header d-flex justify-content-between align-items-center mw-100">
<div class="pt-1">
<h4 class="card-title d-inline border fw-bold p-2">Setup Wizard</h4>
<p class="card-subtitle text-muted text-truncate mt-2">Follow the steps to complete the setup wizard</p>
<h4 class="card-title d-inline border fw-bold p-2 don-jose">Setup Wizard</h4>
<p class="card-subtitle text-muted text-truncate mt-3">Follow the steps to complete the setup wizard</p>
</div>
<!-- Logo -->
<div class="app-brand justify-content-center">
<a href="https://www.bunkerweb.io/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener"
class="app-brand-link gap-2">
<span class="app-brand-logo login">
<img class="img-fluid"
src="{{ url_for('static', filename='img/icon.svg') }}"
alt="BunkerWeb logo"
width="50" />
</span>
</a>
<span class="app-brand-logo login">
<i class="bx bx-lg bxs-magic-wand bx-tada"></i>
</span>
</div>
<!-- /Logo -->
</div>
<div class="card-body p-2">
<div class="d-flex justify-content-center">
@ -459,152 +449,115 @@
</div>
{% endif %}
{% if not ui_reverse_proxy %}
<div class="row justify-content-center">
<div class="col-6 pb-3">
<h6 class="mt-2 mb-1 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>
<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 %}
<div class="row p-0">
<div class="col-md-{% if not ui_user %}4{% else %}6{% endif %}">
<h5 class="mb-3 text-dark">Join the Newsletter</h5>
<form action="https://bunkerity.us1.list-manage.com/subscribe/post?u=ec5b1577cf427972b9bd491a6&amp;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 />
{% if not ui_user %}
<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="bx bx-question-mark bx-xs"></span>
</span>
</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">Subscribe</button>
</form>
</div>
<div class="col-md-{% if not ui_user %}4{% else %}6{% endif %} d-flex align-items-center justify-content-center">
<div class="p-5 fs-5 fw-semibold border border-primary">
<ul class="list-unstyled mb-0" id="password-requirements">
{% if not ui_user %}
<li>
<i class="bx bx-check text-success"></i> Secure password
</li>
<li id="overview-2fa-enabled" class="mt-1">
<i class="bx bx-question-mark text-warning"></i> 2FA enabled
</li>
{% endif %}
{% if not ui_reverse_proxy %}
<li id="overview-unique-server-name" class="mt-1">
<i class="bx bx-question-mark text-warning"></i> Unique Server Name
</li>
{% endif %}
</ul>
</div>
{% set setting = "2FA Code" %}
{% set setting_data = {"type": "number", "id": "2fa_code", "regex": "^[0-9]{6}$"} %}
{% include "models/input_setting.html" %}
</div>
{% endif %}
<div class="d-flex justify-content-around text-center mt-4">
{% if not ui_user %}
<div class="col-md-4">
<h5 class="mb-3 text-dark">2FA Setup</h5>
{% set input_readonly = false %}
<div class="d-flex justify-content-between align-items-center">
<label id="label-2fa_code"
for="2fa_code"
class="form-label fw-semibold text-truncate">
2FA Code
</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"
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>
</div>
{% set setting = "2FA Code" %}
{% set setting_data = {"type": "number", "id": "2fa_code", "regex": "^[0-9]{6}$"} %}
{% include "models/input_setting.html" %}
<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 class="d-flex justify-content-between mt-2">
<button 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>
<button type="button" class="btn btn-outline-bw-green save-settings d-none">
<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">
<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>
</div>
</div>
<!-- /Setup -->
<div class="d-flex justify-content-between mt-2">
<button 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>
<button type="button" class="btn btn-outline-bw-green save-settings d-none">
<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">
<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>
</div>
</div>
<!-- /Setup -->
</div>
</div>
</div>
<div id="feedback-toast"
class="bs-toast toast fade"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-autohide="true"
data-bs-delay="10000">
<div class="toast-header">
<i class="d-block w-px-20 h-auto rounded me-2 tf-icons bx bx-bell"></i>
<span class="fw-medium me-auto">BunkerWeb Forever</span>
<small class="text-body-secondary">just now</small>
<button type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
<div class="toast-body">If you read this, it means that you're curious 👀</div>
</div>
<div id="feedback-toast"
class="bs-toast toast fade"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-autohide="true"
data-bs-delay="10000">
<div class="toast-header">
<i class="d-block w-px-20 h-auto rounded me-2 tf-icons bx bx-bell"></i>
<span class="fw-medium me-auto">BunkerWeb Forever</span>
<small class="text-body-secondary">just now</small>
<button type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
{% if not ui_reverse_proxy %}
<div class="modal fade"
id="modal-confirm-dns"
data-bs-backdrop="static"
tabindex="-1"
aria-hidden="true"
role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 id="dns-check-title" class="modal-title">Server name is not unique</h5>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="dns-check-result"
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"
<div class="toast-body">If you read this, it means that you're curious 👀</div>
</div>
{% if not ui_reverse_proxy %}
<div class="modal fade"
id="modal-confirm-dns"
data-bs-backdrop="static"
tabindex="-1"
aria-hidden="true"
role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 id="dns-check-title" class="modal-title">Server name is not unique</h5>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="dns-check-result"
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"
class="fw-semibold"
href="https://www.example.com/setup/check"
target="_blank"
@ -612,35 +565,78 @@
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.
</p>
</div>
<div class="modal-footer justify-content-center">
<button id="confirm-dns"
class="btn btn-outline-danger next-step me-2"
data-bs-dismiss="modal">Proceed</button>
<button type="reset"
class="btn btn-outline-secondary"
data-bs-dismiss="modal">Cancel</button>
</div>
</p>
</div>
</div>
</div>
{% endif %}
<div class="modal fade"
id="loadingModal"
data-bs-backdrop="static"
tabindex="-1"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-transparent border-0 shadow-none">
<div class="modal-body text-center">
<div class="spinner-border text-bw-green" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-white">Setting up your BunkerWeb, please wait...</p>
<div class="modal-footer justify-content-center">
<button id="confirm-dns"
class="btn btn-outline-danger next-step me-2"
data-bs-dismiss="modal">Proceed</button>
<button type="reset"
class="btn btn-outline-secondary"
data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<!-- / Content -->
{% endif %}
<div class="modal fade"
id="loadingModal"
data-bs-backdrop="static"
tabindex="-1"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-transparent border-0 shadow-none">
<div class="modal-body text-center">
<div class="spinner-border text-bw-green" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-white">Setting up your BunkerWeb, please wait...</p>
</div>
</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&amp;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

@ -77,7 +77,7 @@
rel="noopener">privacy policy</a>
</label>
</div>
<button type="submit" class="btn btn-primary w-100 text-uppercase">Subscribe</button>
<button type="submit" class="btn btn-primary w-100 text-uppercase don-jose">Subscribe</button>
</form>
</div>
<!-- End Newsletter Signup Section -->

View file

@ -98,7 +98,7 @@
rel="noopener">privacy policy</a>
</label>
</div>
<button type="submit" class="btn btn-primary w-100 text-uppercase">Subscribe</button>
<button type="submit" class="btn btn-primary w-100 text-uppercase don-jose">Subscribe</button>
</form>
</div>
<!-- End Newsletter Signup Section -->

View file

@ -411,7 +411,9 @@ def index():
@app.route("/loading")
@login_required
def loading():
return render_template("loading.html", message=request.values.get("message", "Loading"), next=request.values.get("next", None) or url_for("home.home_page"))
return render_template(
"loading.html", message=request.values.get("message", "Loading..."), next=request.values.get("next", None) or url_for("home.home_page")
)
@app.route("/check", methods=["GET"])