update core template + actions

*create script to get and setup core plugins data using ajax after page load instead of using jinja
*data will be get from actions.py, dummy values were moved from jinja to actions.py until real logic with db or instance
*update every core plugins with new logic
This commit is contained in:
Jordan Blasenhauer 2024-01-31 18:37:26 +01:00
parent 7377909976
commit 503868b5d6
40 changed files with 4469 additions and 506 deletions

View file

@ -110,9 +110,26 @@
value: "unknown",
type: "text",
},
/* EXAMPLE
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccess = [];
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
@ -131,24 +148,15 @@
})
.then((res) => res.json())
.then((res) => {
console.log(res);
console.log(JSON.stringify(res));
console.log(res.data);
console.log(res.data.data);
// Update data and DOM
this.getFetchDataByKey(res.data);
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
if (this.showOnSuccess.length > 0) {
this.showOnSuccess.forEach((el) => {
el.classList.remove("hidden");
});
}
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
console.log(error);
this.updateAlert("error");
});
});
@ -158,59 +166,103 @@
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
this.data[key]["value"] =
fetchDataObj[key] || this.data[key]["value"] || "";
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, value] of Object.entries(this.data)) {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (this.data[key]["type"] == "text") {
this.data[key]["el"].textContent = this.data[key]["value"] || "";
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (this.data[key]["type"] == "text") {
this.data[key]["el"].textContent = this.data[key]["value"] || "";
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(`[data-name="${nameKey}"]`).textContent =
nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
if (type == "fetch") {
this.alertStatusEl.textContent = "Fetching";
this.alertMsgEl.textContent = "Please wait...";
this.alertEl.classList.remove("bg-green-500");
this.alertEl.classList.remove("bg-red-500");
this.alertEl.classList.add("bg-sky-500");
}
if (type == "success") {
this.alertStatusEl.textContent = "Success";
this.alertMsgEl.textContent = "Data fetched successfully";
this.alertEl.classList.remove("bg-sky-500");
this.alertEl.classList.remove("bg-red-500");
this.alertEl.classList.add("bg-green-500");
}
if (type == "error") {
this.alertStatusEl.textContent = "Error";
this.alertMsgEl.textContent = "Something went wrong";
this.alertEl.classList.remove("bg-sky-500");
this.alertEl.classList.remove("bg-green-500");
this.alertEl.classList.add("bg-red-500");
}
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove("bg-sky-500", "bg-green-500", "bg-red-500");
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch") return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();

View file

@ -1 +1,8 @@
# Spoofing an action file
def authbasic():
return {
"message": "ok",
"data": {
"info": "test",
"count": 3,
},
}

View file

@ -9,12 +9,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ authbasic_info or "Basic Auth is a method for an HTTP user agent
(e.g. a web browser) to provide a user name and password when making a
request." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -29,9 +26,7 @@
>
AUTH BASIC
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ authbasic_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
@ -60,6 +55,213 @@
</div>
<!-- end icon -->
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Basic Auth is a method for an HTTP user agent
(e.g. a web browser) to provide a user name and password when making a
request.`,
type: "text",
},
count: {
el: document.querySelector("[data-count]"),
value: `unknown`,
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,9 @@
# Spoofing an action file
def badbehavior():
return {
"message": "ok",
"data": {
"info": "test",
"count": 3,
"items": [{"code": 400, "count": 24}, {"code": 403, "count": 845}, {"code": 402, "count": 12}],
},
}

View file

@ -1,6 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [{"code" : 400,
"count" : 24}, {"code" : 403, "count" : 845}, {"code" : 402, "count" : 12}]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div class="col-span-12 grid grid-cols-12 gap-4">
@ -11,11 +9,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ bad_behavior_info or "Ban IP generating too much 'bad' HTTP status
code in a period of time." }}
</p>
></p>
</div>
</div>
</div>
@ -31,9 +27,7 @@
>
BAD BEHAVIOR
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ bad_behavior_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
@ -64,9 +58,9 @@
<!-- end icon -->
</div>
{% if items|length != 0 %}
<div
class="2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden 2xl:col-span-4 3xl:col-span-3 w-full md:max-w-[350px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -91,29 +85,237 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="code"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
>
{{item['code']}}
</p>
></p>
<p
data-name="count"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-6 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
{% endblock %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Ban IP generating too much 'bad' HTTP status
code in a period of time.`,
type: "text",
},
count: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["code", "count"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
{% endblock %}
</div>

View file

@ -1 +1,11 @@
# Spoofing an action file
def blacklist():
return {
"message": "ok",
"data": {
"count_url": 4,
"count_ip": 2,
"count_rdns": 1,
"count_asn": 10,
"count_user_agent": 10,
},
}

View file

@ -10,11 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ blacklist_info or "Deny access based on internal and external
IP/network/rDNS/ASN blacklists." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -30,9 +28,7 @@
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_url_count or "unknown" }}
</h5>
<h5 data-count-url class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -72,9 +68,7 @@
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_ip_count or "unknown" }}
</h5>
<h5 data-count-ip class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -112,9 +106,7 @@
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_rdns_count or "unknown" }}
</h5>
<h5 data-count-rdns class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -160,9 +152,7 @@
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_asn_count or "unknown" }}
</h5>
<h5 data-count-asn class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -200,9 +190,7 @@
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ blacklist_user_agent_count or "unknown" }}
</h5>
<h5 data-count-user-agent class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -231,6 +219,231 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Deny access based on internal and external
IP/network/rDNS/ASN blacklists.`,
type: "text",
},
count_url: {
el: document.querySelector("[data-count-url]"),
value: "unknown",
type: "text",
},
count_ip: {
el: document.querySelector("[data-count-ip]"),
value: "unknown",
type: "text",
},
count_rdns: {
el: document.querySelector("[data-count-rdns]"),
value: "unknown",
type: "text",
},
count_asn: {
el: document.querySelector("[data-count-asn]"),
value: "unknown",
type: "text",
},
count_user_agent: {
el: document.querySelector("[data-count-user-agent]"),
value: "unknown",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def bunkernet():
return {
"message": "ok",
"data": {
"info": "test",
"status": "active",
},
}

View file

@ -3,57 +3,25 @@
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- status -->
<div class="col-span-12 grid grid-cols-12 gap-4">
{% if bunkernet_status %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
data-status-svg
class="w-8 h-8"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p
data-status-text
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Active
</p>
></p>
</div>
{% else %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
<p
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Inactive
</p>
</div>
{% endif %}
<!-- end status -->
</div>
@ -66,11 +34,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ bunkernet_info or "BunkerNet is a crowdsourced database of malicious
requests shared between all BunkerWeb instances over the world. " }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -180,6 +146,206 @@
</div>
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `BunkerNet is a crowdsourced database of malicious
requests shared between all BunkerWeb instances over the world. `,
type: "text",
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status-svg]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
<!-- end test -->
<script async>
function ping() {

View file

@ -1 +1,8 @@
# Spoofing an action file
def cors():
return {
"message": "ok",
"data": {
"info": "test",
"count": 3,
},
}

View file

@ -9,11 +9,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ cors_info or "Cross-Origin Resource Sharing lets you manage how your
service can be contacted from different origins." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -28,9 +26,7 @@
>
CORS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ cors_count or "unknown" }}
</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
@ -61,6 +57,211 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Cross-Origin Resource Sharing lets you manage how your
service can be contacted from different origins.`,
type: "text",
},
count: {
el: document.querySelector("[data-count]"),
value: "unknown",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,9 @@
# Spoofing an action file
def country():
return {
"message": "ok",
"data": {
"info": "test",
"blacklist_count": 3,
"whitelist_count": 23,
},
}

View file

@ -10,12 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ country_info or "The country security feature allows you to apply
policy based on the country of the IP address of clients (blacklist /
whitelist)." }}
</p>
></p>
</div>
</div>
</div>
@ -31,9 +28,7 @@
>
Country
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ country_blacklist_count or "unknown" }}
</h5>
<h5 data-count-blacklist class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
@ -75,9 +70,7 @@
>
Country
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ country_blacklist_count or "unknown" }}
</h5>
<h5 data-count-whitelist class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5"
@ -106,6 +99,216 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `The country security feature allows you to apply
policy based on the country of the IP address of clients (blacklist /
whitelist).`,
type: "text",
},
blacklist_count: {
el: document.querySelector("[data-count-blacklist]"),
value: "",
type: "text",
},
whitelist_count: {
el: document.querySelector("[data-count-whitelist]"),
value: "",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def customcert():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"server_name": "www.example.com", "cn": "Let's encrypt", "expire": "15/11/2024"}, {"server_name": "app1.com", "cn": "Self signed", "expire": "11/01/2028"}, {"server_name": "test.2.fr", "cn": "Default", "expire": "31/08/2035"}],
},
}

View file

@ -1,8 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -12,19 +8,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ custom_certificate_info or "Custom certificates allow you to get
HTTPS / SSL / TLS on your requests." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -54,34 +47,235 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
data-item
class="hidden items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="server_name"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
></p>
<p
data-name="cn"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
></p>
<p
data-name="expire"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Custom certificates allow you to get HTTPS / SSL / TLS on your requests.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,10 @@
# Spoofing an action file
def db():
return {
"message": "ok",
"data": {
"info": "test",
"driver": "SQLite",
"version": "13.2",
"size": "14.8",
},
}

View file

@ -10,12 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ db_info or "BunkerWeb securely stores its current configuration in
a backend database, which contains essential data for smooth
operation." }}
</p>
></p>
</div>
</div>
</div>
@ -33,9 +30,9 @@
>
DRIVER
<span
data-driver
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ db_driver or "unknown" }}
</span>
</p>
</div>
@ -45,9 +42,9 @@
>
VERSION
<span
data-version
class="ml-1 font-semibold transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ db_version or "unknown" }}
</span>
</p>
</div>
@ -64,9 +61,7 @@
>
SIZE
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ db_count or "unknown" }}
</h5>
<h5 data-size class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -102,6 +97,227 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `BunkerWeb securely stores its current configuration in
a backend database, which contains essential data for smooth
operation.`,
type: "text",
},
driver: {
el: document.querySelector("[data-driver]"),
value: "",
type: "text",
},
version: {
el: document.querySelector("[data-version]"),
value: "",
type: "text",
},
size: {
el: document.querySelector("[data-size]"),
value: "",
type: "text",
},
/* EXAMPLE
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def dnsbl():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"server_name": "www.example.com", "status": "ok"}, {"server_name": "app1.com", "status": "ok"}, {"server_name": "test.2.fr", "status": "ko"}],
},
}

View file

@ -1,6 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "status" : "ok"}, {"server_name" : "app1.com", "status" :
"ok"}, {"server_name" : "test.2.fr", "status" : "ko"} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,17 +9,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">DNSBL LIST</h5>
@ -43,55 +40,230 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="server_name"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['server_name']}}
</p>
{% if item['status'] == "ko"%}
<div class="col-span-4 ml-2 m-0 my-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
{% else %}
<div class="col-span-4 ml-2 m-0 my-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
</svg>
</div>
{% endif %}
></p>
<p
data-name="status"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 ml-2 m-0 my-1"
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Deny access based on external DNSBL servers.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "status"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def errors():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"count": 74, "code": "403"}, {"count": 82, "code": "404"}, {"count": "32", "code": "400"}],
},
}

View file

@ -1,6 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"count" : 74,
"code" : "403"}, {"count" : 82, "code" : "404"}, {"count" : "32", "code" :
"400"} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
@ -11,17 +9,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ dnsbl_info or "Deny access based on external DNSBL servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full md:max-w-[400px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">ERRORS LIST</h5>
@ -43,29 +40,230 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="code"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['code']}}
</p>
></p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Show number of occurences of each error code.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["code", "count"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,11 @@
# Spoofing an action file
def greylist():
return {
"message": "ok",
"data": {
"count_url": 4,
"count_ip": 2,
"count_rdns": 1,
"count_asn": 10,
"count_user_agent": 10,
},
}

View file

@ -10,11 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ greylist_info or "Allow access while keeping security features
based on internal and external IP/network/rDNS/ASN greylists. " }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -30,9 +28,7 @@
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_url_count or "unknown" }}
</h5>
<h5 data-count-url class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -72,9 +68,7 @@
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_ip_count or "unknown" }}
</h5>
<h5 data-count-ip class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -112,9 +106,7 @@
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_rdns_count or "unknown" }}
</h5>
<h5 data-count-rdns class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -160,9 +152,7 @@
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_asn_count or "unknown" }}
</h5>
<h5 data-count-asn class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -200,9 +190,7 @@
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ greylist_user_agent_count or "unknown" }}
</h5>
<h5 data-count-user-agent class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
@ -231,6 +219,231 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Deny access based on internal and external
IP/network/rDNS/ASN greylists.`,
type: "text",
},
count_url: {
el: document.querySelector("[data-count-url]"),
value: "unknown",
type: "text",
},
count_ip: {
el: document.querySelector("[data-count-ip]"),
value: "unknown",
type: "text",
},
count_rdns: {
el: document.querySelector("[data-count-rdns]"),
value: "unknown",
type: "text",
},
count_asn: {
el: document.querySelector("[data-count-asn]"),
value: "unknown",
type: "text",
},
count_user_agent: {
el: document.querySelector("[data-count-user-agent]"),
value: "unknown",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def letsencrypt():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"server_name": "www.example.com", "cn": "Let's encrypt", "expire": "15/11/2024"}, {"server_name": "app1.com", "cn": "Self signed", "expire": "11/01/2028"}, {"server_name": "test.2.fr", "cn": "Default", "expire": "31/08/2035"}],
},
}

View file

@ -1,8 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -12,18 +8,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ lets_encrypt_info or "Let's Encrypt certificates for secure HTTP
requests." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -53,34 +47,235 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
data-item
class="hidden items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="server_name"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
></p>
<p
data-name="cn"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
></p>
<p
data-name="expire"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Let's encrypt certificates allow you to get HTTPS / SSL / TLS on your requests.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def limit():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"url": "http://www.example.com", "count": 24}, {"url": "http://www.example.com", "count": 24}],
},
}

View file

@ -1,7 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"url" :
"http://www.example.com", "count" : 24},{"url" : "http://www.example.com",
"count" : 24} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -11,17 +8,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ limit_info or "Limit maximum number of requests and connections." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -46,29 +42,234 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="url"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-8 m-0 my-1"
>
{{item['url']}}
</p>
<p
data-name="count"
class="ml-1 dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['count']}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Limit maximum number of requests and connections.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["url", "count"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,9 @@
# Spoofing an action file
def misc():
return {
"message": "ok",
"data": {
"info": "test",
"count_disabled_servers": 0,
"count_disallowed_methods": 0,
},
}

View file

@ -10,10 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ misc_info or "Miscellaneous settings (methods, servers...)." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -29,9 +28,10 @@
>
DEFAULT SERVER DISABLED
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ default_server_count or "unknown" }}
</h5>
<h5
data-count-server-disabled
class="mb-1 font-bold dark:text-white/90"
></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -74,9 +74,10 @@
>
DISALLOWED METHODS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ disallowed_methods_count or "unknown" }}
</h5>
<h5
data-count-disallowed-methods
class="mb-1 font-bold dark:text-white/90"
></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-500 mx-0.5">
@ -103,6 +104,215 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Miscellaneous settings (methods, servers...).`,
type: "text",
},
count_disabled_servers: {
el: document.querySelector("[data-count-server-disabled]"),
value: "unknown",
type: "text",
},
count_disallowed_methods: {
el: document.querySelector("[data-count-disallowed-methods]"),
value: "unknown",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def modsecurity():
return {
"message": "ok",
"data": {
"info": "test",
"count": 3,
},
}

View file

@ -8,10 +8,9 @@
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex justify-start items-center my-4">
<p
<p data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ modsec_info or "ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs." }}
</p>
</div>
</div>
@ -27,7 +26,7 @@
>
MODSECURITY
</p>
<h5 class="mb-1 font-bold dark:text-white/90">{{ modsec_count or "unknown" }}</h5>
<h5 data-count class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
@ -59,6 +58,216 @@
</div>
<!-- end icon -->
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs.`,
type: "text",
},
count: {
el: document.querySelector("[data-count]"),
value: `ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs.`,
type: "text",
},
/* EXAMPLE
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]"
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500"
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def redis():
return {
"message": "ok",
"data": {
"info": "test",
"status": "active",
},
}

View file

@ -3,58 +3,25 @@
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- status -->
<div class="col-span-12 grid grid-cols-12 gap-4">
{% if redis_status %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
data-status-svg
class="w-8 h-8"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-green-500"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p
data-status-text
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Active
</p>
></p>
</div>
{% else %}
<div
class="col-span-12 md:col-span-6 2xl:col-span-3 3xl:col-span-2 w-fit h-fit transition hover:scale-102 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="mx-1 flex justify-start items-center">
<h5 class="mb-0 font-bold dark:text-white/90 mr-4">STATUS</h5>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-8 h-8 fill-red-500"
>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clip-rule="evenodd"
/>
</svg>
</div>
<p
class="mx-1 transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
Inactive
</p>
</div>
{% endif %}
<!-- end status -->
</div>
@ -66,11 +33,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ redis_info or "Redis server configuration when using BunkerWeb in
cluster mode. " }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -179,7 +144,205 @@
</div>
</div>
</div>
<!-- end test -->
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Redis server configuration when using BunkerWeb in
cluster mode.`,
type: "text",
},
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status-svg]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
<script async>
function ping() {
let data = new FormData();

View file

@ -1 +1,12 @@
# Spoofing an action file
def reversescan():
return {
"message": "ok",
"data": {
"info": "test",
"items": [
{"port": 4000, "count": 400},
{"port": 4400, "count": 780},
{"port": 5000, "count": 40},
],
},
}

View file

@ -1,7 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"port" : 4000,
"count" : 400}, {"port" : 4400, "count" : 780}, {"port" : 5000, "count" : 40},
]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -11,21 +8,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ reversescan_info or "Reverse scan is a feature designed to detect
open ports by establishing TCP connections with clients' IP addresses.
Consider adding this feature if you want to detect possible open proxies
or connections from servers." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[500px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -50,29 +42,232 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
data-item
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="port"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-5 m-0 my-1"
>
{{item['port']}}
</p>
></p>
<p
data-name="count"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-7 m-0 my-1"
>
{{item['count']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Reverse scan is a feature designed to detect
open ports by establishing TCP connections with clients' IP addresses.
Consider adding this feature if you want to detect possible open proxies
or connections from servers.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["port", "count"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,8 @@
# Spoofing an action file
def selfsigned():
return {
"message": "ok",
"data": {
"info": "test",
"items": [{"server_name": "www.example.com", "cn": "Let's encrypt", "expire": "15/11/2024"}, {"server_name": "app1.com", "cn": "Self signed", "expire": "11/01/2028"}, {"server_name": "test.2.fr", "cn": "Default", "expire": "31/08/2035"}],
},
}

View file

@ -1,8 +1,4 @@
{% extends "base.html" %} {% block content %} {% set items = [ {"server_name" :
"www.example.com", "cn" : "Let's encrypt", "expire" : "15/11/2024"},
{"server_name" : "app1.com", "cn" : "Self signed", "expire" : "11/01/2028"},
{"server_name" : "test.2.fr", "cn" : "Default", "expire" : "31/08/2035"} ]%}
{% extends "base.html" %} {% block content %}
<div class="col-span-12 grid grid-cols-12 gap-4">
<!-- info-->
<div
@ -12,18 +8,16 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ lets_encrypt_info or "Selfsigned certificates for secure HTTP
requests." }}
</p>
></p>
</div>
</div>
<!-- end info -->
{% if items|length != 0 %}
<div
class="col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
data-fetch-success-show
class="hidden col-span-12 md:col-span-8 3xl:col-span-9 w-full xl:max-w-[600px] overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">
@ -53,34 +47,235 @@
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in items %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
data-item
class="hidden items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
data-name="server_name"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['server_name']}}
</p>
></p>
<p
data-name="cn"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['cn']}}
</p>
></p>
<p
data-name="expire"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-4 m-0 my-1"
>
{{item['expire']}}
</p>
></p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Selfsigned certificates allow you to get HTTPS / SSL / TLS on your requests.`,
type: "text",
},
items: {
el: document.querySelector("[data-item]"),
value: [],
type: "list",
listNames: ["server_name", "cn", "expire"],
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -1 +1,11 @@
# Spoofing an action file
def whitelist():
return {
"message": "ok",
"data": {
"count_url": 4,
"count_ip": 2,
"count_rdns": 1,
"count_asn": 10,
"count_user_agent": 10,
},
}

View file

@ -10,11 +10,9 @@
<div class="mx-1 flex justify-start items-center my-4">
<p
data-info
class="transition duration-300 ease-in-out mb-0 font-sans text-sm leading-normal dark:text-gray-500 dark:opacity-80"
>
{{ whitelist_info or "Allow access based on internal and external
IP/network/rDNS/ASN whitelists." }}
</p>
></p>
</div>
</div>
<!-- end info -->
@ -30,13 +28,11 @@
>
URL
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_url_count or "unknown" }}
</h5>
<h5 data-count-url class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
@ -72,13 +68,11 @@
>
IP
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_ip_count or "unknown" }}
</h5>
<h5 data-count-ip class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
@ -112,13 +106,11 @@
>
RDNS
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_rdns_count or "unknown" }}
</h5>
<h5 data-count-rdns class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
@ -160,13 +152,11 @@
>
ASN
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_asn_count or "unknown" }}
</h5>
<h5 data-count-asn class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
@ -200,13 +190,11 @@
>
User Agent
</p>
<h5 class="mb-1 font-bold dark:text-white/90">
{{ whitelist_user_agent_count or "unknown" }}
</h5>
<h5 data-count-user-agent class="mb-1 font-bold dark:text-white/90"></h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-green-500 mx-0.5">
passed
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5">
denied
</span>
</p>
</div>
@ -231,6 +219,231 @@
</div>
<!-- end icon -->
</div>
</div>
<div
role="alert"
data-fetch
class="bg-sky-500 p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button data-fetch-close class="absolute right-7 top-1.5">
<svg
class="cursor-pointer fill-white dark:fill-gray-300 dark:opacity-80 absolute h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"
></path>
</svg>
</button>
<h5 data-fetch-status class="text-lg mb-0 text-white dark:text-gray-300">
Fetching
</h5>
<p data-fetch-msg class="text-white dark:text-gray-300 mb-0 text-sm">
Please wait...
</p>
</div>
<script>
class SetupPlugin {
constructor() {
// Alert elements
this.alertEl = document.querySelector("[data-fetch]");
this.alertCloseEl = document.querySelector("[data-fetch-close]");
this.alertStatusEl = document.querySelector("[data-fetch-status]");
this.alertMsgEl = document.querySelector("[data-fetch-msg]");
// Set data defaults elements and variables
// Key of this.data need to match key of fetch data json object to update values
// type<str> : text (target el), list (el need to be first element of list)
// listNames<arr> : list of names key on item, need to set data-name="nameKey" on el
this.data = {
info: {
el: document.querySelector("[data-info]"),
value: `Allow access based on internal and external
IP/network/rDNS/ASN whitelists.`,
type: "text",
},
count_url: {
el: document.querySelector("[data-count-url]"),
value: "unknown",
type: "text",
},
count_ip: {
el: document.querySelector("[data-count-ip]"),
value: "unknown",
type: "text",
},
count_rdns: {
el: document.querySelector("[data-count-rdns]"),
value: "unknown",
type: "text",
},
count_asn: {
el: document.querySelector("[data-count-asn]"),
value: "unknown",
type: "text",
},
count_user_agent: {
el: document.querySelector("[data-count-user-agent]"),
value: "unknown",
type: "text",
},
/* EXAMPLE
// value : active / inactive / unknown
status: {
el: document.querySelector("[data-status]"),
value: "unknown",
type: "status",
textEl: document.querySelector("[data-status-text]"),
},
*/
};
// Hidden elements that will be shown on success, like ping buttons or list rendering
this.showOnSuccessEls = document.querySelectorAll(
"[data-fetch-success-show]",
);
this.init();
}
init() {
window.addEventListener("DOMContentLoaded", () => {
// Set default values and fetch
this.updateDataDOM();
this.updateAlert("fetch");
fetch(location.href, {
method: "POST",
headers: {
"X-CSRFToken": "{{ csrf_token() }}",
},
})
.then((res) => res.json())
.then((res) => {
// Update data and DOM
this.getFetchDataByKey(res.data.data);
this.updateDataDOM();
// Show hidden elements
this.showSuccessEls();
// Feedback
this.updateAlert("success");
})
.catch((error) => {
this.updateAlert("error");
});
});
this.alertCloseEl.addEventListener("click", () => {
this.alertEl.classList.add("hidden");
});
}
showSuccessEls() {
this.showOnSuccessEls.forEach((el) => {
el.classList.remove("hidden");
});
}
// Key of fetch data need to match key of this.data
getFetchDataByKey(fetchDataObj) {
for (const [key, value] of Object.entries(this.data)) {
value["value"] = fetchDataObj[key] || value["value"] || "";
}
}
updateDataDOM() {
for (const [key, val] of Object.entries(this.data)) {
const el = val["el"];
const type = val["type"];
const value = val["value"];
// Case text
if (type === "text") {
el.textContent = value || "";
continue;
}
// Case status
if (type === "status") {
const textEl = val["textEl"] || null;
if (value === "active")
this.setStatus(el, textEl, "fill-green-500", "Active");
if (value === "inactive")
this.setStatus(el, textEl, "fill-red-500", "Inactive");
if (value === "unknown")
this.setStatus(el, textEl, "fill-sky-500", "Unknown");
continue;
}
// Case list, we will render elements after the selected elements
if (type === "list") {
// Case no list to render
if (!value || value.length <= 0) continue;
// Clone item element
const itemEl = el.cloneNode(true);
itemEl.classList.remove("hidden");
const parentEl = el.parentNode;
// Add item element after selected element
const items = value.forEach((item) => {
const newItemEl = itemEl.cloneNode(true);
// Update item element values
for (const [nameKey, nameValue] of Object.entries(item)) {
newItemEl.querySelector(
`[data-name="${nameKey}"]`,
).textContent = nameValue;
}
// Add item element after selected element
parentEl.appendChild(newItemEl);
});
// Delete schema
el.remove();
continue;
}
}
}
setStatus(el, textEl, colorClass, text) {
el.classList.remove("fill-green-500", "fill-red-500", "fill-sky-500");
el ? el.classList.add(colorClass) : null;
textEl ? (textEl.textContent = text) : null;
}
// Show fetch state alert
// type<str> : fetch, success, error
updateAlert(type) {
if (!type) return;
const [status, msg, color] = this.getAlertType(type);
this.alertEl.classList.remove(
"bg-sky-500",
"bg-green-500",
"bg-red-500",
);
this.alertStatusEl.textContent = status;
this.alertMsgEl.textContent = msg;
this.alertEl.classList.add(color);
this.alertEl.classList.remove("hidden");
if (type !== "fetch")
setTimeout(() => this.alertEl.classList.add("hidden"), 5000);
}
getAlertType(type) {
if (type === "fetch")
return ["Fetching", "Please wait...", "bg-sky-500"];
if (type === "error")
return ["Error", "Something went wrong", "bg-red-500"];
if (type === "success")
return ["Success", "Data fetched successfully", "bg-green-500"];
}
}
new SetupPlugin();
</script>
</div>
{% endblock %}

View file

@ -19,10 +19,10 @@ class ServiceModal {
//modal forms
this.formNewEdit = this.modal.querySelector("[data-services-modal-form]");
this.formDelete = this.modal.querySelector(
"[data-services-modal-form-delete]"
"[data-services-modal-form-delete]",
);
this.submitBtn = document.querySelector(
"button[data-services-modal-submit]"
"button[data-services-modal-submit]",
);
//container
this.container = document.querySelector("main");
@ -92,7 +92,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
const oldServName = e.target
.closest("[data-services-service]")
@ -104,7 +104,7 @@ class ServiceModal {
oldServName,
this.formNewEdit,
isDraft,
method
method,
);
//get service data and parse it
//multiple type logic is launch at same time on relate class
@ -128,7 +128,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -136,7 +136,7 @@ class ServiceModal {
serviceName,
this.formNewEdit,
isDraft,
method
method,
);
//set default value with method default
//get service data and parse it
@ -168,7 +168,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -176,7 +176,7 @@ class ServiceModal {
serviceName,
this.formNewEdit,
isDraft,
method
method,
);
//set default value with method default
this.setSettingsDefault();
@ -200,7 +200,7 @@ class ServiceModal {
) {
//set form info and right form
const [action, serviceName, isDraft, method] = this.getActionData(
e.target
e.target,
);
this.setForm(
action,
@ -208,7 +208,7 @@ class ServiceModal {
serviceName,
this.formDelete,
isDraft,
method
method,
);
//show modal
this.openModal();
@ -229,7 +229,7 @@ class ServiceModal {
"delete-btn",
"valid-btn",
"edit-btn",
"info-btn"
"info-btn",
);
this.submitBtn.classList.add(btnType);
}
@ -290,15 +290,15 @@ class ServiceModal {
//click the custom select dropdown to update select value
select.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
)
.click();
//set state to custom visible el
const btnCustom = document.querySelector(
`[data-setting-select=${select.getAttribute(
"data-setting-select-default"
)}]`
"data-setting-select-default",
)}]`,
);
this.setDisabledDefault(btnCustom, defaultMethod);
@ -374,9 +374,8 @@ class ServiceModal {
if (action === "delete") {
this.showDeleteForm();
formEl.querySelector(
`[data-services-modal-text]`
).textContent = `Are you sure you want to delete ${serviceName} ?`;
formEl.querySelector(`[data-services-modal-text]`).textContent =
`Are you sure you want to delete ${serviceName} ?`;
const nameInp = formEl.querySelector(`input[name="SERVER_NAME"]`);
nameInp.setAttribute("value", serviceName);
nameInp.value = serviceName;
@ -487,7 +486,7 @@ class ServiceModal {
if (inp.tagName === "SELECT") {
inp.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${value}']`
`button[data-setting-select-dropdown-btn][value='${value}']`,
)
.click();
inp.setAttribute("data-method", method);
@ -599,7 +598,7 @@ class Multiple {
const attName = btn.getAttribute(`data-${this.prefix}-multiple-add`);
//get all multiple groups
const multipleEls = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple*="${attName}"]`
`[data-${this.prefix}-settings-multiple*="${attName}"]`,
);
//case no schema
if (multipleEls.length <= 0) return;
@ -611,7 +610,7 @@ class Multiple {
//and keep the highest num
multipleEls.forEach((container) => {
const ctnrName = container.getAttribute(
"data-services-settings-multiple"
"data-services-settings-multiple",
);
const num = this.getSuffixNumOrFalse(ctnrName);
if (!isNaN(num) && num > topNum) topNum = num;
@ -622,7 +621,7 @@ class Multiple {
const setNum = +currNum === 0 ? `` : `_${currNum}`;
//the default (schema) group is the last group
const schema = document.querySelector(
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`,
);
//clone schema to create a group with new num
const schemaClone = schema.cloneNode(true);
@ -660,7 +659,7 @@ class Multiple {
.hasAttribute(`data-${this.prefix}-multiple-delete`)
) {
const multContainer = e.target.closest(
"[data-services-settings-multiple]"
"[data-services-settings-multiple]",
);
multContainer.remove();
}
@ -682,13 +681,13 @@ class Multiple {
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
: name.trim();
const relateSetting = document.querySelector(
`[data-setting-container=${nameSuffixLess}_SCHEMA]`
`[data-setting-container=${nameSuffixLess}_SCHEMA]`,
);
const relateCtnr = relateSetting.closest(
"[data-services-settings-multiple]"
"[data-services-settings-multiple]",
);
const relateCtnrName = relateCtnr.getAttribute(
"data-services-settings-multiple"
"data-services-settings-multiple",
);
//then we sort the setting on the right container name by suffixe number
if (!(relateCtnrName in sortMultiples)) {
@ -706,7 +705,7 @@ class Multiple {
addOneMultGroup() {
const settings = document.querySelector("[data-services-modal-form]");
const multAddBtns = settings.querySelectorAll(
"[data-services-multiple-add]"
"[data-services-multiple-add]",
);
multAddBtns.forEach((btn) => {
//check if already one (SCHEMA exclude so length >= 2)
@ -721,7 +720,7 @@ class Multiple {
showMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
@ -735,7 +734,7 @@ class Multiple {
toggleMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
@ -751,7 +750,7 @@ class Multiple {
//get schema settings
const multiples = {};
const schemaSettings = document.querySelectorAll(
`[data-setting-container$="SCHEMA"]`
`[data-setting-container$="SCHEMA"]`,
);
// loop on every schema settings
schemaSettings.forEach((schema) => {
@ -777,11 +776,11 @@ class Multiple {
setMultipleToDOM(sortMultObj, setMethodUI = false) {
//we loop on each multiple that contains values to render to DOM
for (const [schemaCtnrName, multGroupBySuffix] of Object.entries(
sortMultObj
sortMultObj,
)) {
//we need to access the DOM schema container
const schemaCtnr = document.querySelector(
`[data-services-settings-multiple="${schemaCtnrName}"]`
`[data-services-settings-multiple="${schemaCtnrName}"]`,
);
//now we have to loop on each multiple settings group
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
@ -797,14 +796,14 @@ class Multiple {
for (const [name, data] of Object.entries(settings)) {
//get setting container of clone container
const settingContainer = schemaCtnrClone.querySelector(
`[data-setting-container="${name}"]`
`[data-setting-container="${name}"]`,
);
//replace input info and disabled state
this.setSetting(
data["value"],
setMethodUI ? "ui" : data["method"],
data["global"],
settingContainer
settingContainer,
);
}
//send schema clone to DOM and show it
@ -819,7 +818,7 @@ class Multiple {
"data-services-settings-multiple",
schemaCtnrClone
.getAttribute("data-services-settings-multiple")
.replace("_SCHEMA", suffix)
.replace("_SCHEMA", suffix),
);
//rename title
@ -833,18 +832,18 @@ class Multiple {
//rename setting container
const settingCtnrs = schemaCtnrClone.querySelectorAll(
"[data-setting-container]"
"[data-setting-container]",
);
settingCtnrs.forEach((settingCtnr) => {
settingCtnr.setAttribute(
"data-setting-container",
settingCtnr
.getAttribute("data-setting-container")
.replace("_SCHEMA", suffix)
.replace("_SCHEMA", suffix),
);
settingCtnr.setAttribute(
"id",
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix)
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix),
);
});
@ -922,15 +921,15 @@ class Multiple {
//click the custom select dropdown btn value to update select value
select.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
)
.click();
//set state to custom visible el
const btnCustom = document.querySelector(
`[data-setting-select=${select.getAttribute(
"data-setting-select-default"
)}]`
"data-setting-select-default",
)}]`,
);
this.setDisabledMultServ(btnCustom, method, global);
@ -966,10 +965,10 @@ class Multiple {
selects.forEach((select) => {
const method = select.getAttribute("data-default-method");
const name = select.getAttribute(
"data-services-setting-select-default"
"data-services-setting-select-default",
);
const selDOM = document.querySelector(
`button[data-services-setting-select='${name}']`
`button[data-services-setting-select='${name}']`,
);
if (method === "ui" || method === "default") {
selDOM.removeAttribute("disabled", "");
@ -1004,7 +1003,7 @@ class Multiple {
hiddenIfNoMultiples() {
//hide multiple btn if no multiple exist on a plugin
const multiples = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`,
);
multiples.forEach((container) => {
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
@ -1016,7 +1015,7 @@ class Multiple {
removePrevMultiples() {
const multiPlugins = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`,
);
multiPlugins.forEach((multiGrp) => {
if (
@ -1054,7 +1053,7 @@ const setModal = new ServiceModal();
const format = new FormatValue();
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[data-service-content='settings']"
"[data-service-content='settings']",
);
const setMultiple = new Multiple("services");