mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
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:
parent
3049047a20
commit
bcb5321583
45 changed files with 1534 additions and 1419 deletions
|
|
@ -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"),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
29
src/ui/app/static/fonts/Courier_Prime.css
Normal file
29
src/ui/app/static/fonts/Courier_Prime.css
Normal 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");
|
||||
}
|
||||
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime.ttf
Normal file
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime.ttf
Normal file
Binary file not shown.
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime_bold.ttf
Normal file
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime_bold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime_italic.ttf
Normal file
BIN
src/ui/app/static/fonts/Courier_prime/Courier_prime_italic.ttf
Normal file
Binary file not shown.
6
src/ui/app/static/fonts/DonJose.css
Normal file
6
src/ui/app/static/fonts/DonJose.css
Normal 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");
|
||||
}
|
||||
BIN
src/ui/app/static/fonts/DonJose_Black.otf
Normal file
BIN
src/ui/app/static/fonts/DonJose_Black.otf
Normal file
Binary file not shown.
|
|
@ -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> 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
|
||||
|
|
|
|||
|
|
@ -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> 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();
|
||||
|
|
|
|||
|
|
@ -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 ? "–" + to : "+"),
|
||||
(to ? "–" + 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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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> 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();
|
||||
|
|
|
|||
46
src/ui/app/static/js/pages/loading.js
Normal file
46
src/ui/app/static/js/pages/loading.js
Normal 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);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -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> 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();
|
||||
|
|
|
|||
|
|
@ -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> 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();
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
|
|
|||
1
src/ui/app/static/json/blockhaus.min.json
Normal file
1
src/ui/app/static/json/blockhaus.min.json
Normal file
File diff suppressed because one or more lines are too long
1
src/ui/app/static/json/periscop.min.json
Normal file
1
src/ui/app/static/json/periscop.min.json
Normal file
File diff suppressed because one or more lines are too long
77
src/ui/app/static/libs/lottie-player/lottie-player.min.js
vendored
Normal file
77
src/ui/app/static/libs/lottie-player/lottie-player.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
src/ui/app/static/libs/purify/purify.min.js
vendored
5
src/ui/app/static/libs/purify/purify.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -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',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
{% if mode == 'easy' %}aria-selected="true"{% endif %}>
|
||||
<i class="bx bx-customize bx-sm"></i>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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"
|
||||
|
|
|
|||
|
|
@ -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&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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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> {{ 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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"] }} - v{{ plugin_data["version"] }} - {{ 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"
|
||||
|
|
|
|||
|
|
@ -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 }} - {{ 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">
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 }}">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{% extends "dashboard.html" %}
|
||||
{% block content %}
|
||||
<!-- Content -->
|
||||
|
||||
<!--/ Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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&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&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 %}
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
Loading…
Reference in a new issue