mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev
This commit is contained in:
commit
20d3f48411
34 changed files with 2663 additions and 299 deletions
|
|
@ -2,4 +2,4 @@
|
|||
"dependencies": {
|
||||
"puppeteer": "^21.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
115
src/ui/main.py
115
src/ui/main.py
|
|
@ -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
|
|
@ -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
583
src/ui/static/js/bans.js
Normal 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();
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
338
src/ui/static/js/requests.js
Normal file
338
src/ui/static/js/requests.js
Normal 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();
|
||||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ class BackLogin {
|
|||
"href",
|
||||
window.location.href.replace(
|
||||
`/${this.currEndpoint}`,
|
||||
`/${this.backEndpoint}`
|
||||
)
|
||||
`/${this.backEndpoint}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
364
src/ui/templates/bans.html
vendored
Normal 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
195
src/ui/templates/bans_modal.html
vendored
Normal 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
392
src/ui/templates/block_requests.html
vendored
Normal 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 %}
|
||||
7
src/ui/templates/head.html
vendored
7
src/ui/templates/head.html
vendored
|
|
@ -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>
|
||||
|
|
|
|||
381
src/ui/templates/jobs.html
vendored
381
src/ui/templates/jobs.html
vendored
|
|
@ -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 %}
|
||||
|
|
|
|||
61
src/ui/templates/logs.html
vendored
61
src/ui/templates/logs.html
vendored
|
|
@ -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 %}
|
||||
|
|
|
|||
73
src/ui/templates/menu.html
vendored
73
src/ui/templates/menu.html
vendored
|
|
@ -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>
|
||||
|
|
|
|||
2
src/ui/templates/plugins.html
vendored
2
src/ui/templates/plugins.html
vendored
|
|
@ -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">
|
||||
|
|
|
|||
25
src/ui/templates/services.html
vendored
25
src/ui/templates/services.html
vendored
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
cryptography==41.0.7
|
||||
requests==2.31.0
|
||||
selenium==4.16.0
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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 ..."
|
||||
|
|
|
|||
Loading…
Reference in a new issue