Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev

This commit is contained in:
fl0ppy-d1sk 2024-01-22 10:52:11 +01:00
commit 20d3f48411
No known key found for this signature in database
GPG key ID: 93EE47CC3D061500
34 changed files with 2663 additions and 299 deletions

View file

@ -2,4 +2,4 @@
"dependencies": {
"puppeteer": "^21.3.6"
}
}
}

View file

@ -550,4 +550,3 @@ Allow access based on internal and external IP/network/rDNS/ASN whitelists.
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |

View file

@ -465,4 +465,4 @@ In case you lost your UI credentials or have 2FA issues, you can connect to the
1|<username>|<password_hash>|0||(manual or ui)
```
You should now be able to log into the web UI only using your username and password.
You should now be able to log into the web UI only using your username and password.

View file

@ -4,9 +4,9 @@ from dotenv import dotenv_values
from os import getenv, sep
from os.path import join
from pathlib import Path
from redis import StrictRedis
from redis import StrictRedis, Sentinel
from sys import path as sys_path
from typing import Optional, Tuple
from typing import Any, Optional, Tuple
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("utils",), ("db",))]:
@ -49,6 +49,8 @@ class CLI(ApiCaller):
if Path(sep, "usr", "share", "bunkerweb", "db").exists():
from Database import Database # type: ignore
self.__logger.info("Getting variables from database")
db = Database(self.__logger, sqlalchemy_string=self.__get_variable("DATABASE_URI", None))
self.__variables = db.get_config()
@ -58,6 +60,7 @@ class CLI(ApiCaller):
self.__use_redis = self.__get_variable("USE_REDIS", "no") == "yes"
self.__redis = None
if self.__use_redis:
self.__logger.info("Fetching redis configuration")
redis_host = self.__get_variable("REDIS_HOST")
if redis_host:
redis_port = self.__get_variable("REDIS_PORT", "6379")
@ -89,16 +92,71 @@ class CLI(ApiCaller):
redis_keepalive_pool = "10"
redis_keepalive_pool = int(redis_keepalive_pool)
self.__redis = StrictRedis(
host=redis_host,
port=redis_port,
db=redis_db,
socket_timeout=redis_timeout,
socket_connect_timeout=redis_timeout,
socket_keepalive=True,
max_connections=redis_keepalive_pool,
ssl=self.__get_variable("REDIS_SSL", "no") == "yes",
)
self.__logger.info("Redis configuration is valid")
redis_ssl = self.__get_variable("REDIS_SSL", "no") == "yes"
username = self.__get_variable("REDIS_USERNAME", None) or None
password = self.__get_variable("REDIS_PASSWORD", None) or None
sentinel_hosts = self.__get_variable("REDIS_SENTINEL_HOSTS", [])
if isinstance(sentinel_hosts, str):
sentinel_hosts = [host.split(":") if ":" in host else host for host in sentinel_hosts.split(" ") if host]
if sentinel_hosts:
sentinel_username = self.__get_variable("REDIS_SENTINEL_USERNAME", None) or None
sentinel_password = self.__get_variable("REDIS_SENTINEL_PASSWORD", None) or None
sentinel_master = self.__get_variable("REDIS_SENTINEL_MASTER", "")
self.__logger.info(
f"Connecting to redis sentinel cluster with the following parameters:\n{sentinel_hosts=}\n{sentinel_username=}\n{sentinel_password=}\n{sentinel_master=}\n{redis_timeout=}\nmax_connections={redis_keepalive_pool}\n{redis_ssl=}"
)
sentinel = Sentinel(
sentinel_hosts,
username=sentinel_username,
password=sentinel_password,
ssl=redis_ssl,
socket_timeout=redis_timeout,
socket_connect_timeout=redis_timeout,
socket_keepalive=True,
max_connections=redis_keepalive_pool,
)
try:
sentinel.discover_master(sentinel_master)
except Exception as e:
self.__logger.error(f"Failed to connect to redis sentinel cluster: {e}, disabling redis")
self.__use_redis = False
if self.__use_redis:
self.__logger.info(f"Connected to redis sentinel cluster, getting master with the following parameters:\n{sentinel_master=}\n{redis_db=}\n{username=}\n{password=}")
self.__redis = sentinel.master_for(
sentinel_master,
db=redis_db,
username=username,
password=password,
)
else:
self.__logger.info(f"Connecting to redis with the following parameters:\n{redis_host=}\n{redis_port=}\n{redis_db=}\n{username=}\n{password=}\n{redis_timeout=}\nmax_connections={redis_keepalive_pool}\n{redis_ssl=}")
self.__redis = StrictRedis(
host=redis_host,
port=redis_port,
db=redis_db,
username=username,
password=password,
socket_timeout=redis_timeout,
socket_connect_timeout=redis_timeout,
socket_keepalive=True,
max_connections=redis_keepalive_pool,
ssl=redis_ssl,
)
try:
if self.__use_redis:
assert self.__redis, "Redis connection is None"
self.__redis.ping()
except Exception as e:
self.__logger.error(f"Failed to connect to redis: {e}, disabling redis")
self.__use_redis = False
self.__logger.info("Connected to redis")
else:
self.__logger.error("USE_REDIS is set to yes but REDIS_HOST is not set, disabling redis")
self.__use_redis = False
@ -116,7 +174,7 @@ class CLI(ApiCaller):
super().__init__()
self.auto_setup(self.__integration)
def __get_variable(self, variable: str, default: Optional[str] = None) -> Optional[str]:
def __get_variable(self, variable: str, default: Optional[Any] = None) -> Optional[str]:
return getenv(variable, self.__variables.get(variable, default))
def __detect_integration(self) -> str:

View file

@ -48,7 +48,7 @@ from src.Config import Config
from src.ReverseProxied import ReverseProxied
from src.User import AnonymousUser, User
from utils import check_settings, get_b64encoded_qr_image, path_to_dict
from utils import check_settings, get_b64encoded_qr_image, path_to_dict, get_remain, get_term_from_remain
from Database import Database # type: ignore
from logging import getLogger
@ -1595,6 +1595,119 @@ def logs_container(container_id):
return jsonify({"logs": logs, "last_update": int(time() * 1000)})
@app.route("/block_requests", methods=["GET"])
@login_required
def block_requests():
# TODO : Get block requests from database to send it
block_requests = [
{"ip": "124.0.0.1", "url": "/test", "date": "12/51/9851", "reason": "antibot", "method": "GET", "status": 403, "data": "{fesfmk fesfsf sfesfes}"},
{"ip": "124.0.0.2", "url": "/test", "date": "12/51/9851", "reason": "test", "method": "GET", "status": 403, "data": "{fesfmk fesfsf sfesfes}"},
{"ip": "124.0.0.3", "url": "/test", "date": "12/51/9851", "reason": "antibot", "method": "GET", "status": 403, "data": "{fesfmk fesfsf sfesfes}"},
]
# Prepare data
reasons = {}
codes = {}
for request in block_requests:
# Get top reasons
if not request["reason"] in reasons:
reasons[request["reason"]] = 0
reasons[request["reason"]] = reasons[request["reason"]] + 1
# Get top status code
if not request["status"] in codes:
codes[request["status"]] = 0
codes[request["status"]] = codes[request["status"]] + 1
top_reason = [k for k, v in reasons.items() if v == max(reasons.values())][0]
top_code = [k for k, v in codes.items() if v == max(codes.values())][0]
return render_template(
"block_requests.html",
block_requests=block_requests,
top_code=top_code,
top_reason=top_reason,
username=current_user.get_id(),
dark_mode=app.config["DARK_MODE"],
)
@app.route("/bans", methods=["GET", "POST"])
@login_required
def bans():
if request.method == "POST":
# Check variables
if not request.form:
flash("Missing form data.", "error")
return redirect(url_for("bans"))
if "operation" not in request.form:
flash("Operation unknown", "error")
return redirect(url_for("bans"))
if "data" not in request.form:
flash("No data to proceed", "error")
return redirect(url_for("bans"))
# Multiple operations : add ban or unban
operation = request.form["operation"]
# data = request.form["data"]
# TODO : unban logic
# data format for unban is the same as bans send on client
if operation == "unban":
pass
# TODO : add ban logic
# data format : [{"ip": string, "ban_start": timestamp, "ban_end": timestamp, "reason": string}]
# "ban_start" is optional : default is time.time()
# "ban_end" is optional : default is one month
if operation == "ban":
pass
return redirect(
url_for(
"loading",
next=url_for("bans"),
message="Update bans",
)
)
# TODO : Get bans list from database and send it
# Need to limit the number of bans around 100 last ones
bans = [
{"ip": "124.0.0.1", "ban_start": 1705663430, "ban_end": 1705758948, "reason": "antibot"},
{"ip": "124.0.0.2", "ban_start": 1705663430, "ban_end": 1708437348, "reason": "test"},
{"ip": "124.0.0.3", "ban_start": 1705663430, "ban_end": 1740059748, "reason": "unknown"},
]
# Prepare data
reasons = {}
now_stamp = int(time()) # in seconds
for ban in bans:
# Add remain
remain = "unknown" if ban["ban_end"] - now_stamp < 0 else get_remain(ban["ban_end"] - now_stamp)
ban["remain"] = remain
ban["term"] = get_term_from_remain(remain)
# Convert stamp to date
ban["ban_start"] = datetime.fromtimestamp(ban["ban_start"])
ban["ban_end"] = datetime.fromtimestamp(ban["ban_end"])
# Get top reason
if not ban["reason"] in reasons:
reasons[ban["reason"]] = 0
reasons[ban["reason"]] = reasons[ban["reason"]] + 1
top_reason = [k for k, v in reasons.items() if v == max(reasons.values())][0]
return render_template(
"bans.html",
bans=bans,
top_reason=top_reason,
username=current_user.get_id(),
dark_mode=app.config["DARK_MODE"],
)
@app.route("/jobs", methods=["GET"])
@login_required
def jobs():

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -45,7 +45,7 @@ class SubmitAccount {
"focus:valid:!ring-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
"valid:!border-red-500",
);
this.pwAlertEl.classList.add("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "true");
@ -59,7 +59,7 @@ class SubmitAccount {
"focus:valid:!ring-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
"valid:!border-red-500",
);
this.pwAlertEl.classList.remove("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "false");
@ -77,14 +77,14 @@ class PwBtn {
const passwordContainer = e.target.closest("[data-input-group]");
const inpEl = passwordContainer.querySelector("input");
const invBtn = passwordContainer.querySelector(
'[data-setting-password="invisible"]'
'[data-setting-password="invisible"]',
);
const visBtn = passwordContainer.querySelector(
'[data-setting-password="visible"]'
'[data-setting-password="visible"]',
);
inpEl.setAttribute(
"type",
inpEl.getAttribute("type") === "password" ? "text" : "password"
inpEl.getAttribute("type") === "password" ? "text" : "password",
);
if (inpEl.getAttribute("type") === "password") {

583
src/ui/static/js/bans.js Normal file
View file

@ -0,0 +1,583 @@
class Filter {
constructor(prefix = "bans") {
this.prefix = prefix;
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.termValue = "all";
this.reasonValue = "all";
this.initHandler();
}
initHandler() {
// REASON HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"reason"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="reason"]`,
)
.textContent.trim();
this.reasonValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
// TERM HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"term"
) {
setTimeout(() => {
const value = document
.querySelector(`[data-${this.prefix}-setting-select-text="term"]`)
.textContent.trim();
this.termValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//KEYWORD HANDLER
this.keyInp.addEventListener("input", (e) => {
this.filter();
});
}
filter() {
const bans = document.querySelector(`[data-${this.prefix}-list]`).children;
if (bans.length === 0) return;
//reset
for (let i = 0; i < bans.length; i++) {
const el = bans[i];
el.classList.remove("hidden");
}
//filter type
this.setFilterKeyword(bans);
this.setFilterReason(bans);
this.setFilterTerm(bans);
}
setFilterKeyword(bans) {
const keyword = this.keyInp.value.trim().toLowerCase();
if (!keyword) return;
for (let i = 0; i < bans.length; i++) {
const el = bans[i];
const ip = this.getElAttribut(el, "ip");
const banStart = this.getElAttribut(el, "ban_sart");
const banEnd = this.getElAttribut(el, "ban_end");
const remain = this.getElAttribut(el, "remain");
if (
!ip.includes(keyword) &&
!banStart.includes(keyword) &&
!banEnd.includes(keyword) &&
!remain.includes(keyword)
)
el.classList.add("hidden");
}
}
setFilterTerm(bans) {
if (this.termValue === "all") return;
for (let i = 0; i < bans.length; i++) {
const el = bans[i];
const type = this.getElAttribut(el, "term");
if (type !== this.termValue) el.classList.add("hidden");
}
}
setFilterReason(bans) {
if (this.reasonValue === "all") return;
for (let i = 0; i < bans.length; i++) {
const el = bans[i];
const type = this.getElAttribut(el, "reason");
if (type !== this.reasonValue) el.classList.add("hidden");
}
}
getElAttribut(el, attr) {
return el
.querySelector(`[data-${this.prefix}-${attr}]`)
.getAttribute(`data-${this.prefix}-${attr}`)
.trim();
}
}
class Dropdown {
constructor(prefix = "bans") {
this.prefix = prefix;
this.container = document.querySelector("main");
this.lastDrop = "";
this.initDropdown();
}
initDropdown() {
this.container.addEventListener("click", (e) => {
//SELECT BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
//SELECT DROPDOWN BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`data-${this.prefix}-setting-select-dropdown-btn`,
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
if (isSameVal) return this.hideDropdown(btnSetting);
//else, add new value to custom
this.setSelectNewValue(btnSetting, btnValue);
//close dropdown and change style
this.hideDropdown(btnSetting);
if (
!e.target.closest("button").hasAttribute(`data-${this.prefix}-file`)
) {
this.changeDropBtnStyle(btnSetting, btn);
}
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[data-${this.prefix}-setting-select-dropdown]`,
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`,
)}"]`,
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`,
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
}
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
selectCustom.querySelector(
`[data-${this.prefix}-setting-select-text]`,
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"bg-primary",
"dark:bg-primary",
"text-gray-300",
"text-gray-300",
);
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
});
//highlight clicked btn
selectedBtn.classList.remove(
"bg-white",
"dark:bg-slate-700",
"text-gray-700",
);
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
}
toggleSelectBtn(e) {
const attribute = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${attribute}"]`,
);
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${attribute}"]`,
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
dropdownChevron.classList.toggle("rotate-180");
}
//hide date filter on local
hideFilterOnLocal(type) {
if (type === "local") {
this.hideInp(`input#from-date`);
this.hideInp(`input#to-date`);
}
if (type !== "local") {
this.showInp(`input#from-date`);
this.showInp(`input#to-date`);
}
}
showInp(selector) {
document.querySelector(selector).closest("div").classList.add("flex");
document.querySelector(selector).closest("div").classList.remove("hidden");
}
hideInp(selector) {
document.querySelector(selector).closest("div").classList.add("hidden");
document.querySelector(selector).closest("div").classList.remove("flex");
}
}
class Unban {
constructor(prefix = "bans") {
this.prefix = prefix;
this.container = document.querySelector("main");
this.listEl = document.querySelector(`[data-${this.prefix}-list]`);
this.unbanForm = document.querySelector("#unban-items");
this.unbanBtn = document.querySelector(`button[data-unban-btn]`);
this.unbanInp = document.querySelector(`input[data-unban-inp]`);
this.init();
}
init() {
// Look if an item is select to enable unban button
this.container.addEventListener("click", (e) => {
try {
if (
e.target.closest("div").hasAttribute(`data-${this.prefix}-ban-select`)
) {
// timeout to wait for select value to change
setTimeout(() => {
// Check if at least one item is selected
const selected = this.listEl.querySelectorAll(
`input[data-checked="true"]`,
);
// Case true, enable unban button
if (selected.length > 0) {
this.unbanBtn.removeAttribute("disabled");
}
// Case false, disable unban button
if (selected.length === 0) {
this.unbanBtn.setAttribute("disabled", "");
}
}, 100);
}
} catch (err) {}
});
// unban button
this.unbanForm.addEventListener("submit", (e) => {
e.preventDefault();
if (this.unbanBtn.hasAttribute("disabled")) return;
// Get all selected items
const selected = this.listEl.querySelectorAll(
`input[data-checked="true"]`,
);
const getDatas = [];
selected.forEach((el) => {
const data = el
.closest(`li[data-${this.prefix}-list-item]`)
.getAttribute(`data-${this.prefix}-list-item`);
getDatas.push(data);
});
this.unbanInp.value = JSON.stringify(getDatas);
this.unbanInp.setAttribute("value", JSON.stringify(getDatas));
this.unbanForm.submit();
});
}
}
class AddBanModal {
constructor() {
//modal elements
this.modal = document.querySelector("[data-bans-modal]");
this.openBtn = document.querySelector("button[data-add-ban]");
this.addBanInp = document.querySelector("input[data-ban-add-inp]");
this.addFieldBtn = document.querySelector("button[data-ban-add-new]");
this.listEl = document.querySelector(`[data-bans-add-ban-list]`);
this.submitBtn = document.querySelector(`button[data-bans-modal-submit]`);
this.removeAllFieldBtn = document.querySelector(
"button[data-add-ban-delete-all-item]",
);
this.formEl = document.querySelector("form[data-ban-add-form]");
this.itemCount = 1;
this.setDatepicker("0"); // for default field
this.init();
}
init() {
// delete item
this.modal.addEventListener("click", (e) => {
try {
if (e.target.hasAttribute("data-add-ban-delete-item")) {
e.target.closest("li").remove();
this.updateActionBtns();
}
} catch (e) {}
try {
if (e.target.hasAttribute("data-add-ban-delete-all-item")) {
this.listEl.querySelectorAll("li").forEach((item) => {
item.remove();
});
}
} catch (e) {}
try {
if (e.target.closest("button").hasAttribute("data-bans-modal-close")) {
this.closeModal();
}
} catch (e) {}
this.updateActionBtns();
});
//open modal
this.openBtn.addEventListener("click", (e) => {
this.openModal();
});
// add field
this.addFieldBtn.addEventListener("click", (e) => {
this.addItem();
this.updateActionBtns();
});
// Check that all inputs have a value to submit
this.listEl.addEventListener("input", (e) => {
this.checkInpValidity();
});
this.listEl.addEventListener("change", (e) => {
this.checkInpValidity();
});
this.formEl.addEventListener("submit", (e) => {
e.preventDefault();
// prepare data
const data = [];
this.listEl.querySelectorAll("li").forEach((item) => {
const ip = item.querySelector("input[data-bans-add-ip]").value;
const banEnd = item
.querySelector("input[data-bans-add-ban-end]")
.getAttribute("data-timestamp");
data.push({
ip: ip,
ban_end: +banEnd,
ban_start: +Date.now().toString().substring(0, 10),
reason: "ui",
});
});
console.log(data);
this.addBanInp.setAttribute("value", data);
this.formEl.submit();
});
}
openModal() {
this.modal.classList.remove("hidden");
this.modal.classList.add("flex");
}
closeModal() {
this.modal.classList.add("hidden");
this.modal.classList.remove("flex");
}
checkInpValidity() {
const inps = this.listEl.querySelectorAll("input");
let isAllValid = true;
for (let i = 0; i < inps.length; i++) {
const inpEl = inps[i];
if (!inpEl.checkValidity() || !inpEl.value) {
isAllValid = false;
break;
}
}
isAllValid
? this.submitBtn.removeAttribute("disabled")
: this.submitBtn.setAttribute("disabled", "");
}
// Check if items and update button disabled/enabled states
updateActionBtns() {
const items = this.listEl.querySelectorAll("li");
const itemsCount = items.length;
itemsCount
? this.removeAllFieldBtn.removeAttribute("disabled")
: this.removeAllFieldBtn.setAttribute("disabled", "");
itemsCount ? null : this.submitBtn.setAttribute("disabled", "");
}
addItem() {
// add item
this.itemCount++;
let item = `<li
data-bans-add-ban-list
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<div class="mx-1.5 col-span-5">
<label for="ip-${this.itemCount}" class="sr-only">ip</label>
<input
data-bans-add-ip
type="text"
id="ip-${this.itemCount}"
name="ip-${this.itemCount}"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="127.0.0.1"
pattern="((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])))$"
required
/>
</div>
<div class="mx-1.5 col-span-5">
<label for="ban-end-${this.itemCount}" class="sr-only">Ban end</label>
<input
data-bans-add-ban-end
type="text"
id="ban-end-${this.itemCount}"
name="ban-end-${this.itemCount}"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="01/01/2021 00:00:00"
pattern="(.*?)"
required
/>
</div>
<div class="mx-1.5 col-span-2 flex justify-center items-center">
<button
data-add-ban-delete-item
type="button"
class="dark:bg-red-500/90 duration-300 dark:opacity-90 flex justify-center items-center p-2 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 pointer-events-none"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</div>
</li>`;
let cleanHTML = DOMPurify.sanitize(item);
this.listEl.insertAdjacentHTML("beforeend", cleanHTML);
this.setDatepicker(this.itemCount);
}
setDatepicker(id) {
// instanciate datepicker
const dateOptions = {
locale: "en",
dateFormat: "m/d/Y H:i:S",
defaultDate: false,
enableTime: true,
enableSeconds: true,
time_24hr: true,
minuteIncrement: 1,
onChange(selectedDates, dateStr, instance) {
const inpEl = document.querySelector(`input#ban-end-${id}`);
// Get date to timestamp
const pickStamp = +Date.parse(new Date(dateStr).toString());
const nowStamp = +Date.now();
// Case pick is before current date
if (pickStamp < nowStamp) {
inpEl.setAttribute("data-timestamp", Date.now());
return instance.setDate(nowStamp);
}
// Case right value
// Set timestamp in seconds in the related input
const convertToS = +pickStamp.toString().substring(0, 10);
inpEl.setAttribute("data-timestamp", convertToS);
},
};
flatpickr(`input#ban-end-${id}`, dateOptions);
}
}
const setDropdown = new Dropdown();
const setFilter = new Filter();
const setUnban = new Unban();
const setModal = new AddBanModal();

View file

@ -47,7 +47,9 @@ class Dropdown {
//close dropdown and change style
this.hideDropdown(btnSetting);
if (!e.target.closest("button").hasAttribute(`data-${prefix}-file`)) {
if (
!e.target.closest("button").hasAttribute(`data-${this.prefix}-file`)
) {
this.changeDropBtnStyle(btnSetting, btn);
}
//show / hide filter

View file

@ -111,13 +111,7 @@ class Dropdown {
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"dark:bg-primary",
"bg-primary",
"bg-primary",
"text-gray-300",
"text-gray-300",
);
btn.classList.remove("dark:bg-primary", "bg-primary", "text-gray-300");
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
});
//highlight clicked btn

View file

@ -0,0 +1,338 @@
class Filter {
constructor(prefix = "block_requests") {
this.prefix = prefix;
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.methodValue = "all";
this.statusValue = "all";
this.reasonValue = "all";
this.initHandler();
}
initHandler() {
//METHOD HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"method"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="method"]`,
)
.textContent.trim();
this.methodValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//STATUS HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"status"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="status"]`,
)
.textContent.trim();
this.statusValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
// REASON HANDLER
+this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"reason"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="reason"]`,
)
.textContent.trim();
this.reasonValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//KEYWORD HANDLER
this.keyInp.addEventListener("input", (e) => {
this.filter();
});
}
filter() {
const requests = document.querySelector(
`[data-${this.prefix}-list]`,
).children;
if (requests.length === 0) return;
//reset
for (let i = 0; i < requests.length; i++) {
const el = requests[i];
el.classList.remove("hidden");
}
//filter type
this.setFilterMethod(requests);
this.setFilterKeyword(requests);
this.setFilterStatus(requests);
this.setFilterReason(requests);
}
setFilterMethod(requests) {
if (this.methodValue === "all") return;
for (let i = 0; i < requests.length; i++) {
const el = requests[i];
const type = this.getElAttribut(el, "method");
if (type !== this.methodValue) el.classList.add("hidden");
}
}
setFilterKeyword(requests) {
const keyword = this.keyInp.value.trim().toLowerCase();
if (!keyword) return;
for (let i = 0; i < requests.length; i++) {
const el = requests[i];
const url = this.getElAttribut(el, "url");
const date = this.getElAttribut(el, "date");
const ip = this.getElAttribut(el, "ip");
const data = this.getElAttribut(el, "data");
if (
!url.includes(keyword) &&
!date.includes(keyword) &&
!ip.includes(keyword) &&
!data.includes(keyword)
)
el.classList.add("hidden");
}
}
setFilterStatus(requests) {
if (this.statusValue === "all") return;
for (let i = 0; i < requests.length; i++) {
const el = requests[i];
const type = this.getElAttribut(el, "status");
if (type !== this.statusValue) el.classList.add("hidden");
}
}
setFilterReason(requests) {
if (this.reasonValue === "all") return;
for (let i = 0; i < requests.length; i++) {
const el = requests[i];
const type = this.getElAttribut(el, "reason");
if (type !== this.reasonValue) el.classList.add("hidden");
}
}
getElAttribut(el, attr) {
return el
.querySelector(`[data-${this.prefix}-${attr}]`)
.getAttribute(`data-${this.prefix}-${attr}`)
.trim();
}
}
class Dropdown {
constructor(prefix = "block_requests") {
this.prefix = prefix;
this.container = document.querySelector("main");
this.lastDrop = "";
this.initDropdown();
}
initDropdown() {
this.container.addEventListener("click", (e) => {
//SELECT BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
//SELECT DROPDOWN BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`data-${this.prefix}-setting-select-dropdown-btn`,
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
if (isSameVal) return this.hideDropdown(btnSetting);
//else, add new value to custom
this.setSelectNewValue(btnSetting, btnValue);
//close dropdown and change style
this.hideDropdown(btnSetting);
if (
!e.target.closest("button").hasAttribute(`data-${this.prefix}-file`)
) {
this.changeDropBtnStyle(btnSetting, btn);
}
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[data-${this.prefix}-setting-select-dropdown]`,
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`,
)}"]`,
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`,
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
}
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
selectCustom.querySelector(
`[data-${this.prefix}-setting-select-text]`,
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`,
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`,
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"bg-primary",
"dark:bg-primary",
"text-gray-300",
"text-gray-300",
);
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
});
//highlight clicked btn
selectedBtn.classList.remove(
"bg-white",
"dark:bg-slate-700",
"text-gray-700",
);
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
}
toggleSelectBtn(e) {
const attribute = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[data-${this.prefix}-setting-select-dropdown="${attribute}"]`,
);
const dropdownChevron = document.querySelector(
`svg[data-${this.prefix}-setting-select="${attribute}"]`,
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
dropdownChevron.classList.toggle("rotate-180");
}
//hide date filter on local
hideFilterOnLocal(type) {
if (type === "local") {
this.hideInp(`input#from-date`);
this.hideInp(`input#to-date`);
}
if (type !== "local") {
this.showInp(`input#from-date`);
this.showInp(`input#to-date`);
}
}
showInp(selector) {
document.querySelector(selector).closest("div").classList.add("flex");
document.querySelector(selector).closest("div").classList.remove("hidden");
}
hideInp(selector) {
document.querySelector(selector).closest("div").classList.add("hidden");
document.querySelector(selector).closest("div").classList.remove("flex");
}
}
const setDropdown = new Dropdown();
const setFilter = new Filter();

View file

@ -83,6 +83,37 @@ class ServiceModal {
this.openModal();
}
} catch (err) {}
// clone action
try {
if (
e.target.closest("button").getAttribute("data-services-action") ===
"clone"
) {
//set form info and right form
const [action, serviceName] = this.getActionAndServName(e.target);
this.setForm(action, serviceName, serviceName, this.formNewEdit);
//set default value with method default
//get service data and parse it
//multiple type logic is launch at same time on relate class
const servicesSettings = e.target
.closest("[data-services-service]")
.querySelector("[data-services-settings]")
.getAttribute("data-value");
const obj = JSON.parse(servicesSettings);
this.updateModalData(obj, true);
// server name is unset
const inpServName = document.querySelector("input#SERVER_NAME");
inpServName.getAttribute("value", "");
inpServName.removeAttribute("disabled", "");
inpServName.value = "";
// clone is UI creation, so no setting should be disabled
//show modal
this.resetFilterInp();
this.changeSubmitBtn("CREATE", "valid-btn");
this.openModal(); //server name is unset
}
} catch (err) {}
//new action
try {
if (
@ -219,10 +250,11 @@ class ServiceModal {
setForm(action, serviceName, oldServName, formEl) {
this.modalTitle.textContent = `${action} ${serviceName}`;
formEl.setAttribute("id", `form-${action}-${serviceName}`);
const operation = action === "clone" ? "new" : action;
formEl.setAttribute("id", `form-${operation}-${serviceName}`);
const opeInp = formEl.querySelector(`input[name="operation"]`);
opeInp.setAttribute("value", action);
opeInp.value = action;
opeInp.setAttribute("value", operation);
opeInp.value = operation;
if (action === "edit" || action === "new") {
this.showNewEditForm();
@ -231,6 +263,13 @@ class ServiceModal {
oldNameInp.value = oldServName;
}
if (action === "clone") {
this.showNewEditForm();
const oldNameInp = formEl.querySelector(`input[name="OLD_SERVER_NAME"]`);
oldNameInp.setAttribute("value", "");
oldNameInp.value = "";
}
if (action === "delete") {
this.showDeleteForm();
formEl.querySelector(`[data-services-modal-text]`).textContent =
@ -286,7 +325,7 @@ class ServiceModal {
this.modalTabsHeader.classList.remove("hidden");
}
updateModalData(settings) {
updateModalData(settings, forceEnabled = false) {
//use this to select inputEl and change value
for (const [key, data] of Object.entries(settings)) {
//change format to match id
@ -350,14 +389,14 @@ class ServiceModal {
inp.setAttribute("data-method", method);
}
//check disabled/enabled after setting values and methods
this.setDisabledServ(inp, method, global);
if (!forceEnabled) this.setDisabledState(inp, method, global);
if (forceEnabled) inp.removeAttribute("disabled");
});
} catch (err) {}
}
}
setDisabledServ(inp, method, global) {
setDisabledState(inp, method, global) {
if (global) return inp.removeAttribute("disabled");
if (method === "ui" || method === "default") {

View file

@ -12,8 +12,8 @@ class BackLogin {
"href",
window.location.href.replace(
`/${this.currEndpoint}`,
`/${this.backEndpoint}`
)
`/${this.backEndpoint}`,
),
);
});
});

View file

@ -25,23 +25,23 @@
}
.close-btn {
@apply dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-red-500 border border-red-500 uppercase align-middle transition-all rounded-lg cursor-pointer dark:bg-gray-200 dark:hover:brightness-75 bg-white hover:bg-white/80 focus:bg-white/80 leading-normal ease-in tracking-tight-rem shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md;
@apply dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-red-500 border border-red-500 uppercase align-middle transition-all rounded-lg cursor-pointer dark:bg-gray-200 dark:hover:brightness-75 bg-white hover:bg-white/80 focus:bg-white/80 leading-normal ease-in tracking-tight-rem shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.valid-btn {
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md;
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.delete-btn {
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md;
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.edit-btn {
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md;
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.info-btn {
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md;
@apply tracking-wide dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
/*----------------------------------------------*/

364
src/ui/templates/bans.html vendored Normal file
View file

@ -0,0 +1,364 @@
{% extends "base.html" %} {% block content %} {% set current_endpoint =
url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% set reasons = [] %}
{% set terms = [] %}
{% for ban in bans %}
{% if ban["reason"] not in reasons %}
{% set reasons = reasons.append(ban["reason"]) %}
{% endif %}
{% if ban["term"] not in terms %}
{% set terms = terms.append(ban["term"]) %}
{% endif %}
{% endfor %}
<!-- actions -->
<div
class="col-span-12 relative flex justify-center min-w-0 break-words rounded-2xl bg-clip-border"
>
<button
data-add-ban
type="button"
class="dark:bg-green-500/90 duration-300 dark:opacity-90 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
<span class="mr-2">Add ban</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</button>
</div>
<!-- end actions -->
<!-- info-->
<div
class="col-span-12 md:col-span-4 3xl:col-span-3 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"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
BANS TOTAL
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{bans|length}}
</p>
</div>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
TOP REASON
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{top_reason}}
</p>
</div>
</div>
<!-- end info -->
<!-- filter -->
<div
data-{{current_endpoint}}-filter
class="h-fit col-span-12 md:col-span-8 2xl:col-span-6 3xl:col-span-5 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- search inpt-->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Search
</h5>
<label for="keyword" class="sr-only">search</label>
<input
type="text"
id="keyword"
name="keyword"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="ip, ban start, ban end"
pattern="(.*?)"
required
/>
</div>
<!-- end search inpt-->
<!-- select reason -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Reason
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="reason"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
aria-description="current filter state value"
id="{{current_endpoint}}-reason"
data-name="{{current_endpoint}}-reason"
data-{{current_endpoint}}-setting-select-text="reason"
>all</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="reason"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="reason"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="reason"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
>
all
</button>
{% for reason in reasons %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="reason"
value="{{reason}}"
class="{% if loop.last %}rounded-b{%endif%} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{reason}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select reason -->
<!-- select term -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Term
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="term"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
aria-description="current filter state value"
id="{{current_endpoint}}-term"
data-name="{{current_endpoint}}-term"
data-{{current_endpoint}}-setting-select-text="term"
>all</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="term"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="term"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="term"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
>
all
</button>
{% for term in terms %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="term"
value="{{term}}"
class="{% if loop.last %}rounded-b{%endif%} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{term}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select term -->
</div>
</div>
<!-- end filter -->
<div class="w-full 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">BANS LIST</h5>
</div>
<div
class="col-span-12 overflow-y-auto overflow-x-auto"
>
<div data-{{current_endpoint}}-bans-list>
<!-- list container-->
<div class="overflow-hidden min-w-[1150px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Select
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
IP
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Reason
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Ban start
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Ban end
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Remain
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Term
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
{% for ban in bans %}
<li
data-{{current_endpoint}}-list-item="{{ban}}"
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<div
data-{{current_endpoint}}-ban-select
data-checkbox-handler="ban-item-{{loop.index}}" class="relative mb-7 md:mb-0 z-10 ml-2">
<label class="sr-only" for="ban-item-{{loop.index}}">Ban ip {{ loop.index}}</label>
<input id="ban-item-{{loop.index}}" name="ban-item-{{loop.index}}"
data-default-method="ui"
data-default-value="no"
data-checked="false"
id="checkbox-ban-item-{{loop.index}}"
class="checkbox" type="checkbox"
value="no" />
<svg
data-checkbox-handler="ban-item-{{loop.index}}"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path class="pointer-events-none"
d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
></path>
</svg>
</div>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-ip="{{ban["ip"]}}"
>
{{ban['ip']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-reason="{{ban["reason"]}}"
>
{{ban["reason"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-ban_start="{{ban["ban_start"]}}"
>
{{ban["ban_start"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-ban_end="{{ban["ban_end"]}}"
>
{{ban["ban_end"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-remain="{{ban["remain"]}}"
>
{{ban["remain"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-term="{{ban["term"]}}"
>
{{ban["term"]}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<form id="unban-items" action="/bans" method="post" class="w-full col-span-12 justify-center flex mt-6 mb-3">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<input type="hidden" name="operation" value="unban">
<input data-unban-inp type="hidden" name="data" value="">
<button data-unban-btn disabled type="submit" class="valid-btn mr-3 text-base">
UNBAN SELECTED
</button>
</form>
</div>
{% include "bans_modal.html" %}
{% endblock %}

195
src/ui/templates/bans_modal.html vendored Normal file
View file

@ -0,0 +1,195 @@
<!-- modal -->
<div
data-bans-modal
class="dark:brightness-110 hidden w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 justify-center items-center"
>
<div
data-bans-modal-card
class="overflow-y-auto mx-0 sm:mx-6 lg:mx-8 my-3 px-4 pt-4 pb-8 w-full sm:min-w-[500px] max-w-[650px] flex flex-col justify-between break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div>
<div class="w-full flex justify-between mb-2">
<p
data-bans-modal-title
class="transition duration-300 ease-in-out dark:opacity-90 dark:text-gray-200 mb-2 font-sans font-semibold leading-normal uppercase text-md"
>
ADD BANS
</p>
<button
class="-translate-y-1"
aria-label="close modal"
data-bans-modal-close
>
<svg
class="transition duration-300 ease-in-out dark:opacity-90 h-6 w-6 sm:h-7 sm:w-7 fill-slate-800 dark:fill-gray-300"
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"
/>
</svg>
</button>
</div>
<div
data-bans-tabs-header
class="flex flex-col sm:flex-row justify-start items-start sm:items-center gap-x-4 gap-y-2 my-3"
>
<button
data-ban-add-new
type="button"
class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:bg-green-500/90 duration-300 dark:opacity-90 flex justify-center items-center px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
<span class="mr-2 pointer-events-none">Add field</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-7 h-7 pointer-events-none"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</button>
<button
data-add-ban-delete-all-item
type="button"
class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:bg-red-500/90 duration-300 dark:opacity-90 flex justify-center items-center px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
<span class="mr-2 pointer-events-none">Delete all</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-7 h-7 pointer-events-none"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</div>
<div class="col-span-12 overflow-y-auto overflow-x-auto">
<div data-{{current_endpoint}}-bans-list>
<!-- list container-->
<div
class="overflow-hidden min-w-[600px] w-full grid grid-cols-12 rounded p-2"
>
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-5 m-0 pb-2 border-b border-gray-400"
>
IP
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-5 m-0 pb-2 border-b border-gray-400"
>
Ban end
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 pl-4 border-b border-gray-400"
>
Delete
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full h-full" data-bans-add-ban-list>
<li
data-bans-add-ban-list
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<div class="mx-1.5 col-span-5">
<label for="ip-0" class="sr-only">ip</label>
<input
data-bans-add-ip
type="text"
id="ip-0"
name="ip-0"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="127.0.0.1"
pattern="((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])))$"
required
/>
</div>
<div class="mx-1.5 col-span-5">
<label for="ban-end-0" class="sr-only">Ban end</label>
<input
data-bans-add-ban-end
type="text"
id="ban-end-0"
name="ban-end-0"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="01/01/2021 00:00:00"
pattern="(.*?)"
required
/>
</div>
<div class="mx-1.5 col-span-2 flex justify-center items-center">
<button
data-add-ban-delete-item
type="button"
class="dark:bg-red-500/90 duration-300 dark:opacity-90 flex justify-center items-center p-2 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 pointer-events-none"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</div>
</li>
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
</div>
<form
data-ban-add-form
class="w-full flex flex-col justify-between"
id="form-new"
action="/bans"
method="POST"
>
<input type="hidden" name="csrf_token" value="{{csrf_token()}}" />
<input type="hidden" name="operation" value="ban" />
<input data-ban-add-inp type="hidden" name="data" value="" />
<!-- action button -->
<div class="w-full justify-center flex mt-6 mb-4">
<button
data-bans-modal-close
type="button"
class="close-btn mr-3 text-base"
>
Close
</button>
<button disabled data-bans-modal-submit type="submit" class="valid-btn">
Add
</button>
</div>
<!-- end action button-->
</form>
</div>
</div>
<!-- end modal -->

392
src/ui/templates/block_requests.html vendored Normal file
View file

@ -0,0 +1,392 @@
{% extends "base.html" %} {% block content %} {% set current_endpoint =
url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% set methods = [] %}
{% set codes = [] %}
{% set reasons = [] %}
{% for request in block_requests %}
{% if request["method"] not in methods %}
{% set methods = methods.append(request["method"]) %}
{% endif %}
{% if request["status"] not in codes %}
{% set codes = codes.append(request["status"]) %}
{% endif %}
{% if request["reason"] not in reasons %}
{% set reasons = reasons.append(request["reason"]) %}
{% endif %}
{% endfor %}
<!-- info-->
<div
class="col-span-12 md:col-span-4 3xl:col-span-3 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"
>
<h5 class="mb-2 font-bold dark:text-white/90">INFO</h5>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
BLOCK REQUESTS TOTAL
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{block_requests|length}}
</p>
</div>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
TOP REASON
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{top_reason}}
</p>
</div>
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
TOP STATUS CODE
</p>
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{top_code}}
</p>
</div>
</div>
<!-- end info -->
<!-- filter -->
<div
data-{{current_endpoint}}-filter
class="h-fit col-span-12 md:col-span-8 2xl:col-span-6 3xl:col-span-5 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- search inpt-->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Search
</h5>
<label for="keyword" class="sr-only">search</label>
<input
type="text"
id="keyword"
name="keyword"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="ip, url, date, data"
pattern="(.*?)"
required
/>
</div>
<!-- end search inpt-->
<!-- select method -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Method
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="method"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
aria-description="current filter state value"
id="{{current_endpoint}}-method"
data-name="{{current_endpoint}}-method"
data-{{current_endpoint}}-setting-select-text="method"
>all</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="method"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="method"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="method"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
>
all
</button>
{% for method in methods %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="method"
value="{{method}}"
class="{% if loop.last %}rounded-b{%endif%} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{method}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select method -->
<!-- select status -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Status code
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="status"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
aria-description="current filter state value"
id="{{current_endpoint}}-status"
data-name="{{current_endpoint}}-status"
data-{{current_endpoint}}-setting-select-text="status"
>all</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="status"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="status"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="status"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
>
all
</button>
{% for code in codes %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="status"
value="{{code}}"
class="{% if loop.last %}rounded-b{%endif%} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{code}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select status -->
<!-- select reason -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
Reason
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="reason"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
aria-description="current filter state value"
id="{{current_endpoint}}-reason"
data-name="{{current_endpoint}}-reason"
data-{{current_endpoint}}-setting-select-text="reason"
>all</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="reason"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="reason"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="reason"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
>
all
</button>
{% for reason in reasons %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="reason"
value="{{reason}}"
class="{% if loop.last %}rounded-b{%endif%} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{reason}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select reason -->
</div>
</div>
<!-- end filter -->
<div class="w-full 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">BLOCK REQUESTS</h5>
</div>
<div
class="col-span-12 overflow-y-auto overflow-x-auto"
> <!-- list container-->
<div class="min-w-[1024px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
IP
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Url
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Date
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Method
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Code
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Reason
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
>
Data
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
{% for request in block_requests %}
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-ip="{{request['ip']}}"
>
{{request['ip']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-url="{{request['url']}}"
>
{{request['url']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-date="{{request['date']}}"
>
{{request['date']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1 pl-1 "
data-{{current_endpoint}}-method="{{request['method']}}"
>
{{request["method"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1 pl-0.5 "
data-{{current_endpoint}}-status="{{request['status']}}"
>
{{request["status"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1 pl-0.5 "
data-{{current_endpoint}}-reason="{{request['reason']}}"
>
{{request["reason"]}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
data-{{current_endpoint}}-data="{{request['data']}}"
>
{{request["data"]}}
</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container--></div>
</div>
{% endblock %}

View file

@ -45,5 +45,12 @@
<script type="module" src="./js/jobs.js"></script>
{% elif current_endpoint == "account" %}
<script type="module" src="./js/account.js"></script>
{% elif current_endpoint == "block_requests" %}
<script type="module" src="./js/requests.js"></script>
{% elif current_endpoint == "bans" %}
<link rel="stylesheet" type="text/css" href="./css/flatpickr.css" />
<link rel="stylesheet" type="text/css" href="./css/flatpickr.dark.css" />
<script defer src="./js/utils/flatpickr.js"></script>
<script type="module" src="./js/bans.js"></script>
{% endif %}
</head>

View file

@ -35,7 +35,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- filter -->
<div
data-{{current_endpoint}}-filter
class="col-span-12 md:col-span-8 2xl:col-span-6 3xl:col-span-5 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
class="h-fit col-span-12 md:col-span-8 2xl:col-span-6 3xl:col-span-5 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
@ -197,215 +197,220 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
</div>
<!-- end filter -->
<div
class="w-full overflow-hidden overflow-y-auto overflow-x-auto max-h-100 sm:max-h-125 min-h-50-screen 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="w-full 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"
>
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">JOBS</h5>
<!-- list container-->
<div class="min-w-[900px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Last run
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Every
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Reload
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Success
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Files
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
{% for job_name, value in jobs.items() %}
<!-- job item-->
<li
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">JOBS LIST</h5>
</div>
<div
class="col-span-12 overflow-y-auto overflow-x-auto"
>
<!-- list container-->
<div class="min-w-[900px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
<p
Name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Last run
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Every
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Reload
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Success
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Files
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
{% for job_name, value in jobs.items() %}
<!-- job item-->
<li
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-name
class="items-center grid grid-cols-12 border-b border-gray-300 py-2.5"
>
{{job_name}}
</p>
<p
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-last_run
>
{{value['last_run']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-every
>
{{value["every"]}}
</p>
{% if value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-reload="true"
>
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-name
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</p>
{{job_name}}
</p>
<p
{%endif %} {% if not value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-reload="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-last_run
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %} {% if value["success"] %}
<p
{{value['last_run']}}
</p>
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-success="true"
>
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-every
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</p>
{% elif not value["success"] %}
<p
{{value["every"]}}
</p>
{% if value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-success="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-reload="true"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %}
<div
class="relative dark:text-gray-400 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-files
>
{% if value['cache']%}
<button
data-{{current_endpoint}}-setting-select="{{job_name}}"
class="py-1 text-sm disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left leading-6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="{{current_endpoint}}-{{job_name}}"
data-name="{{current_endpoint}}-{{job_name}}"
data-{{current_endpoint}}-setting-select-text="{{job_name}}"
>files</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="{{job_name}}"
class="transition-transform h-4 w-4 fill-gray-500"
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
data-{{current_endpoint}}-setting-select-dropdown="{{job_name}}"
class="hidden z-100 absolute h-full flex-col w-full translate-y-0.5"
>
{% for file in value['cache'] %}
<button
data-{{current_endpoint}}-download="{{job_name}}"
data-{{current_endpoint}}-file="{{file['file_name']}}"
data-{{current_endpoint}}-setting-select-dropdown-btn="{{job_name}}"
value="list"
class="{% if loop.index == loop.length %}rounded-b-lg {% endif %}{% if loop.first %}rounded-t-lg{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 bg-white dark:bg-slate-700 text-gray-700"
>
<span class="flex justify-start items-center">
<svg
class="h-5.5 w-5.5 stroke-sky-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</p>
<span
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-2"
>{{file['file_name']}}</span
>
</span>
{%endif %} {% if not value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-reload="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %} {% if value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-success="true"
>
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</p>
{% elif not value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
data-{{current_endpoint}}-success="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %}
<div
class="relative dark:text-gray-400 text-sm col-span-3 m-0 my-1"
data-{{current_endpoint}}-files
>
{% if value['cache']%}
<button
data-{{current_endpoint}}-setting-select="{{job_name}}"
class="py-1 text-sm disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left leading-6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="{{current_endpoint}}-{{job_name}}"
data-name="{{current_endpoint}}-{{job_name}}"
data-{{current_endpoint}}-setting-select-text="{{job_name}}"
>files</span
>
<!-- chevron -->
<svg
data-{{current_endpoint}}-setting-select="{{job_name}}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
{%endfor %}
<!-- end chevron -->
<!-- dropdown-->
<div
data-{{current_endpoint}}-setting-select-dropdown="{{job_name}}"
class="hidden z-100 absolute h-full flex-col w-full translate-y-0.5"
>
{% for file in value['cache'] %}
<button
data-{{current_endpoint}}-download="{{job_name}}"
data-{{current_endpoint}}-file="{{file['file_name']}}"
data-{{current_endpoint}}-setting-select-dropdown-btn="{{job_name}}"
value="list"
class="{% if loop.index == loop.length %}rounded-b-lg {% endif %}{% if loop.first %}rounded-t-lg{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 bg-white dark:bg-slate-700 text-gray-700"
>
<span class="flex justify-start items-center">
<svg
class="h-5.5 w-5.5 stroke-sky-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-2"
>{{file['file_name']}}</span
>
</span>
</button>
{%endfor %}
</div>
<!-- end dropdown-->
{%endif%}
</div>
<!-- end dropdown-->
{%endif%}
</div>
</li>
<!-- end job item-->
{% endfor %}
</ul>
<!-- end list-->
</li>
<!-- end job item-->
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
<!-- end list container-->
</div>
{% endblock %}

View file

@ -172,7 +172,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- filter -->
<div
data-{{current_endpoint}}-filter
class="col-span-12 md:col-span-6 lg:col-span-4 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
class="h-fit col-span-12 md:col-span-6 lg:col-span-4 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">FILTERS</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
@ -279,31 +279,42 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</div>
<!-- end filter -->
<div
class="min-h-50-screen col-span-12 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
<div class="w-full 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"
>
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">LOGS</h5>
<!-- list container-->
<div class="w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Type
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-9 m-0 pb-2 border-b border-gray-400"
>
Description
</p>
<!-- end header-->
<!-- list -->
<ul
class="col-span-12 w-full max-h-100 overflow-y-auto"
data-{{current_endpoint}}-list
></ul>
<!-- end list-->
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">LOGS</h5>
</div>
<div
class="col-span-12 overflow-y-auto overflow-x-auto"
>
<div data-{{current_endpoint}}-bans-list>
<!-- list container-->
<div class="overflow-hidden min-w-[800px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Type
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-9 m-0 pb-2 border-b border-gray-400"
>
Description
</p>
<!-- end header-->
<!-- list -->
<ul
class="col-span-12 w-full"
data-{{current_endpoint}}-list
></ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
<!-- end list container-->
</div>
{% endblock %}

View file

@ -133,20 +133,12 @@
<div
class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5"
>
<svg
class="stroke-red-500 h-5.5 w-5.5 relative"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.636 5.636a9 9 0 1012.728 0M12 3v9"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="stroke-stone-500 h-5.5 w-5.5 relative"
>
<path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
</svg>
</div>
<span class="ml-1 duration-300 opacity-100 pointer-events-none ease"
>Instances</span
@ -310,6 +302,56 @@
</li>
<!-- end item -->
<!-- item -->
<li class="mt-0.5 w-full">
<a
class="{% if current_endpoint == 'block_requests' %}font-semibold text-slate-700 dark:bg-primary/50 rounded-lg dark:hover:bg-primary/60 bg-primary/20 hover:bg-primary/30{% else %}dark:hover:bg-primary/20 hover:bg-primary/5 {% endif %} hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
href="{% if current_endpoint == 'block_requests' %}#{% else %}loading?next={{ url_for('block_requests') }}{% endif %}"
>
<div
class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="stroke-amber-500 dark:stroke-amber-500 h-6 w-6 relative"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M10.05 4.575a1.575 1.575 0 1 0-3.15 0v3m3.15-3v-1.5a1.575 1.575 0 0 1 3.15 0v1.5m-3.15 0 .075 5.925m3.075.75V4.575m0 0a1.575 1.575 0 0 1 3.15 0V15M6.9 7.575a1.575 1.575 0 1 0-3.15 0v8.175a6.75 6.75 0 0 0 6.75 6.75h2.018a5.25 5.25 0 0 0 3.712-1.538l1.732-1.732a5.25 5.25 0 0 0 1.538-3.712l.003-2.024a.668.668 0 0 1 .198-.471 1.575 1.575 0 1 0-2.228-2.228 3.818 3.818 0 0 0-1.12 2.687M6.9 7.575V12m6.27 4.318A4.49 4.49 0 0 1 16.35 15m.002 0h-.002" />
</svg>
</div>
<span class="ml-1 duration-300 opacity-100 pointer-events-none ease"
>
Block requests
</span
>
</a>
</li>
<!-- end item -->
<!-- item -->
<li class="mt-0.5 w-full">
<a
class="{% if current_endpoint == 'bans' %}font-semibold text-slate-700 dark:bg-primary/50 rounded-lg dark:hover:bg-primary/60 bg-primary/20 hover:bg-primary/30{% else %}dark:hover:bg-primary/20 hover:bg-primary/5 {% endif %} hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
href="{% if current_endpoint == 'bans' %}#{% else %}loading?next={{ url_for('bans') }}{% endif %}"
>
<div
class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="stroke-red-500 dark:stroke-red-500 h-6 w-6 relative"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
</div>
<span class="ml-1 duration-300 opacity-100 pointer-events-none ease"
>
Bans
</span
>
</a>
</li>
<!-- end item -->
<!-- item -->
<li class="mt-0.5 w-full">
<a
class="{% if current_endpoint == 'logs' %}font-semibold text-slate-700 dark:bg-primary/50 rounded-lg dark:hover:bg-primary/60 bg-primary/20 hover:bg-primary/30{% else %}dark:hover:bg-primary/20 hover:bg-primary/5 {% endif %} hover:rounded-lg dark:text-white dark:opacity-80 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
@ -334,7 +376,8 @@
</svg>
</div>
<span class="ml-1 duration-300 opacity-100 pointer-events-none ease"
>Logs</span
>Logs
</span
>
</a>
</li>

View file

@ -113,7 +113,7 @@ include "plugins_modal.html" %}
<!-- filter -->
<div
data-{{current_endpoint}}-filter
class="p-4 col-span-12 md:col-span-6 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
class="h-fit p-4 col-span-12 md:col-span-6 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">

View file

@ -7,15 +7,19 @@
data-services-action="new"
data-services-name="service"
type="button"
class="dark:bg-green-500/90 duration-300 dark:opacity-90 w-80 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
class="dark:bg-green-500/90 duration-300 dark:opacity-90 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
New SERVICE
<span class="mr-2">new service</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</button>
</div>
<!-- end actions -->
<!-- services container-->
<div
class="p-0 my-4 sm:mx-4 md:px-8 grid grid-cols-12 col-span-12 relative min-w-0 break-words rounded-2xl bg-clip-border"
class="p-0 my-4 sm:mx-4 md:px-4 grid grid-cols-12 col-span-12 lg:gap-x-4 relative min-w-0 break-words rounded-2xl bg-clip-border"
>
{% if services|length == 0 %}
<div class="col-span-12 sm:col-span-4 sm:col-start-5">
@ -29,7 +33,7 @@
services_batched %} {% set id_server_name =
service["SERVER_NAME"]['value'].replace(".", "-") %}
<div data-services-service
class="dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
class="my-2 dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div data-services-settings class="hidden" data-value="{{service['settings']}}"></div>
<div data-old-service-name class="hidden" data-value="{{service['SERVER_NAME']['full_value']}}"></div>
@ -355,6 +359,19 @@
</svg>
</a>
<button
data-services-action="clone"
aria-label="clone service settings"
data-services-name="{{service["SERVER_NAME"]['value']}}"
class="dark:brightness-90 z-20 mx-1 bg-emerald-500 hover:bg-emerald-500/80 focus:bg-emerald-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
</svg>
</button>
<button
data-services-action="edit"

View file

@ -7,6 +7,66 @@ from typing import List, Optional
from qrcode.main import QRCode
import math
def get_remain(remain_time):
# Convert s to ms
ms = int(str(remain_time) + "000")
seconds = math.floor(ms / 1000)
minutes = math.floor(seconds / 60)
hours = math.floor(minutes / 60)
days = math.floor(hours / 24)
months = math.floor(days / 30)
years = math.floor(days / 365)
seconds %= 60
minutes %= 60
hours %= 24
days %= 30
months %= 12
return f"{f'{years}y' if years else ''} {f'{months}m' if months else ''} {f'{days}d' if days else ''} {f'{hours}h' if hours else ''} {f'{minutes}min' if minutes else ''} {f'{seconds}s' if seconds else ''}"
def get_term_from_remain(remain):
# Data, need format <n>y <n>m <n>d <n>h <n>min <n>s
terms = remain.split(" ")
term = ""
formats = ["years", "months", "days", "hours", "minutes", "seconds"]
chars = ["y", "min", "m", "d", "h", "s"]
# Not handle
if remain == "unknown":
return remain
# start from seconds to years, stop when first 0 occurence
# The remain term is first 0 occurence - 1
for i in range(len(terms)):
# remove letter
num = terms[len(terms) - 1 - i]
for char in chars:
num = num.replace(char, "")
num = "0" if not num else num
num = int(num)
# Case seconds or less
if not num and i == 0:
term = formats[len(formats) - 1]
break
# Case last element
if num and i == (len(terms) - 1):
term = formats[len(formats) - 1 - i]
break
# Case between seconds and years
if not num:
term = formats[len(formats) - i]
break
return term
def path_to_dict(
path: str,

View file

@ -32,7 +32,7 @@ class KubernetesTest(Test):
"USE_PROXY_PROTOCOL": "yes",
"REAL_IP_FROM": "100.64.0.0/10 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8",
"REAL_IP_HEADER": "proxy_protocol",
"LOG_LEVEL": "info"
"LOG_LEVEL": "info",
}
replace_env = {"API_WHITELIST_IP": "127.0.0.1/8 100.64.0.0/10 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8"}
for yaml in data:

View file

@ -6,8 +6,9 @@ services:
environment:
PYTHONUNBUFFERED: "1"
USE_CUSTOM_SSL: "no"
CUSTOM_SSL_CERT: "/certs/certificate.pem"
extra_hosts:
- "www.example.com:192.168.0.2"
- "app1.example.com:192.168.0.2"
networks:
bw-services:
ipv4_address: 192.168.0.3

View file

@ -2,13 +2,17 @@ version: "3.5"
services:
bw:
image: bunkerity/bunkerweb:1.5.5
pull_policy: never
# image: bunkerity/bunkerweb:1.5.5
# pull_policy: never
build:
context: ../../..
dockerfile: src/bw/Dockerfile
labels:
- "bunkerweb.INSTANCE=yes"
volumes:
- ./index.html:/var/www/html/index.html
environment:
SERVER_NAME: "app1.example.com"
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24 192.168.0.3"
HTTP_PORT: "80"
HTTPS_PORT: "443"
@ -36,8 +40,11 @@ services:
ipv4_address: 192.168.0.2
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.5.5
pull_policy: never
# image: bunkerity/bunkerweb-scheduler:1.5.5
# pull_policy: never
build:
context: ../../..
dockerfile: src/scheduler/Dockerfile
depends_on:
- bw
- bw-docker

View file

@ -1,7 +1,7 @@
#!/bin/bash
echo " Generating certificate for www.example.com ..."
openssl req -nodes -x509 -newkey rsa:4096 -keyout /certs/privatekey.key -out /certs/certificate.pem -days 365 -subj /CN=www.example.com/
echo " Generating certificate for app1.example.com ..."
openssl req -nodes -x509 -newkey rsa:4096 -keyout /certs/privatekey.key -out /certs/certificate.pem -days 365 -subj /CN=app1.example.com/
chown -R root:101 /certs
chmod -R 777 /certs

View file

@ -1,5 +1,9 @@
from contextlib import suppress
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from os import getenv
from socket import create_connection
from ssl import CERT_NONE, DER_cert_to_PEM_cert, create_default_context
from requests import RequestException, get
from traceback import format_exc
from time import sleep
@ -9,7 +13,7 @@ try:
retries = 0
while not ready:
with suppress(RequestException):
resp = get("http://www.example.com/ready", headers={"Host": "www.example.com"}, verify=False, allow_redirects=True)
resp = get("http://app1.example.com/ready", headers={"Host": "app1.example.com"}, verify=False, allow_redirects=True)
status_code = resp.status_code
text = resp.text
@ -28,14 +32,16 @@ try:
sleep(5)
use_custom_ssl = getenv("USE_CUSTOM_SSL", "no") == "yes"
fallback = not bool(getenv("CUSTOM_SSL_CERT", False))
print(
" Sending a request to http://www.example.com ...",
" Sending a request to http://app1.example.com ...",
flush=True,
)
try:
get("http://www.example.com", headers={"Host": "www.example.com"})
req = get("http://app1.example.com", headers={"Host": "app1.example.com"})
req.raise_for_status()
except RequestException:
if not use_custom_ssl:
print(
@ -49,20 +55,48 @@ try:
exit(0)
print(
" Sending a request to https://www.example.com ...",
" Sending a request to https://app1.example.com ...",
flush=True,
)
try:
get("https://www.example.com", headers={"Host": "www.example.com"}, verify=False)
except RequestException:
req = get("https://app1.example.com", headers={"Host": "app1.example.com"}, verify=False)
req.raise_for_status()
except RequestException as e:
print(
"❌ The request failed even though the Custom Cert is activated, exiting ...",
f"❌ The request failed even though the Custom Cert is activated:\n{e}\n exiting ...",
flush=True,
)
exit(1)
print("✅ The Custom Cert is activated, as expected ...", flush=True)
sleep(1)
context = create_default_context()
context.check_hostname = False
context.verify_mode = CERT_NONE
with create_connection(("app1.example.com", 443)) as sock:
with context.wrap_socket(sock, server_hostname="app1.example.com") as ssock:
# Retrieve the SSL certificate
pem_data = DER_cert_to_PEM_cert(ssock.getpeercert(True))
# Parse the PEM certificate
certificate = x509.load_pem_x509_certificate(pem_data.encode(), default_backend())
common_name = certificate.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value
if fallback and common_name != "www.example.org":
print(
f"❌ The Custom Cert is activated and the Common Name (CN) is not www.example.org (fallback one) but {common_name}, exiting ...",
flush=True,
)
exit(1)
elif not fallback and common_name != "app1.example.com":
print(
f"❌ The Custom Cert is activated and the Common Name (CN) is not app1.example.com but {common_name}, exiting ...",
flush=True,
)
exit(1)
print("✅ The Custom Cert is activated and the Common Name (CN) is the right one, as expected ...", flush=True)
except SystemExit as e:
exit(e.code)
except:

View file

@ -1,2 +1,3 @@
cryptography==41.0.7
requests==2.31.0
selenium==4.16.0

View file

@ -16,6 +16,60 @@ certifi==2023.11.17 \
# via
# requests
# selenium
cffi==1.16.0 \
--hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \
--hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
--hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
--hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \
--hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
--hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
--hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
--hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \
--hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
--hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \
--hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \
--hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
--hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
--hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
--hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \
--hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
# via cryptography
charset-normalizer==3.3.2 \
--hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
--hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \
@ -108,6 +162,31 @@ charset-normalizer==3.3.2 \
--hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
--hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
# via requests
cryptography==41.0.7 \
--hash=sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960 \
--hash=sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a \
--hash=sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc \
--hash=sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a \
--hash=sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf \
--hash=sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1 \
--hash=sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39 \
--hash=sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406 \
--hash=sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a \
--hash=sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a \
--hash=sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c \
--hash=sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be \
--hash=sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15 \
--hash=sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2 \
--hash=sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d \
--hash=sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157 \
--hash=sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003 \
--hash=sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248 \
--hash=sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a \
--hash=sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec \
--hash=sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309 \
--hash=sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7 \
--hash=sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d
# via -r requirements.in
exceptiongroup==1.2.0 \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
@ -128,6 +207,10 @@ outcome==1.3.0.post0 \
--hash=sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8 \
--hash=sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b
# via trio
pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
# via cffi
pysocks==1.7.1 \
--hash=sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \

View file

@ -31,6 +31,8 @@ else
echo "USE_CUSTOM_SSL=no" | sudo tee -a /etc/bunkerweb/variables.env
echo "CUSTOM_SSL_CERT=/tmp/certificate.pem" | sudo tee -a /etc/bunkerweb/variables.env
echo "CUSTOM_SSL_KEY=/tmp/privatekey.key" | sudo tee -a /etc/bunkerweb/variables.env
export CUSTOM_SSL_CERT="/tmp/certificate.pem"
export CUSTOM_SSL_KEY="/tmp/privatekey.key"
sudo touch /var/www/html/index.html
sudo cp ready.conf /etc/bunkerweb/configs/server-http
fi
@ -43,12 +45,14 @@ cleanup_stack () {
if [ "$integration" == "docker" ] ; then
rm -rf init/certs
find . -type f -name 'docker-compose.*' -exec sed -i 's@USE_CUSTOM_SSL: "yes"@USE_CUSTOM_SSL: "no"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CUSTOM_SSL_CERT: ".*"@CUSTOM_SSL_CERT: "/certs/certificate.pem"@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CUSTOM_SSL_KEY: ".*"@CUSTOM_SSL_KEY: "/certs/privatekey.key"@' {} \;
else
sudo rm -f /tmp/certificate.pem /tmp/privatekey.key
sudo sed -i 's@USE_CUSTOM_SSL=.*$@USE_CUSTOM_SSL=no@' /etc/bunkerweb/variables.env
sudo sed -i 's@CUSTOM_SSL_CERT=.*$@CUSTOM_SSL_CERT=/tmp/certificate.pem@' /etc/bunkerweb/variables.env
sudo sed -i 's@CUSTOM_SSL_KEY=.*$@CUSTOM_SSL_KEY=/tmp/privatekey.key@' /etc/bunkerweb/variables.env
unset USE_CUSTOM_SSL
unset CUSTOM_SSL_CERT
unset CUSTOM_SSL_KEY
fi
if [[ $end -eq 1 && $exit_code = 0 ]] ; then
return
@ -103,7 +107,7 @@ else
sudo chmod 777 /tmp/privatekey.key /tmp/certificate.pem
fi
for test in "deactivated" "activated"
for test in "deactivated" "activated" "fallback"
do
if [ "$test" = "deactivated" ] ; then
echo "🔏 Running tests without the custom cert ..."
@ -115,6 +119,20 @@ do
sudo sed -i 's@USE_CUSTOM_SSL=.*$@USE_CUSTOM_SSL=yes@' /etc/bunkerweb/variables.env
export USE_CUSTOM_SSL="yes"
fi
elif [ "$test" = "fallback" ] ; then
echo "🔏 Running tests with the custom cert activated and fallback to default cert ..."
echo " Keeping the USE_CUSTOM_SSL variable to yes"
if [ "$integration" == "docker" ] ; then
find . -type f -name 'docker-compose.*' -exec sed -i 's@CUSTOM_SSL_CERT: ".*"@CUSTOM_SSL_CERT: ""@' {} \;
find . -type f -name 'docker-compose.*' -exec sed -i 's@CUSTOM_SSL_KEY: ".*"@CUSTOM_SSL_KEY: ""@' {} \;
else
sudo sed -i 's@USE_CUSTOM_SSL=.*$@USE_CUSTOM_SSL=yes@' /etc/bunkerweb/variables.env
sudo sed -i 's@CUSTOM_SSL_CERT=.*$@CUSTOM_SSL_CERT=@' /etc/bunkerweb/variables.env
sudo sed -i 's@CUSTOM_SSL_KEY=.*$@CUSTOM_SSL_KEY=@' /etc/bunkerweb/variables.env
unset CUSTOM_SSL_CERT
unset CUSTOM_SSL_KEY
export USE_CUSTOM_SSL="yes"
fi
fi
echo "🔏 Starting stack ..."