mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
update bans page
*add remain and remain period *create table with bans data *add ip ban number + top reason *add keyword, reason and period filter
This commit is contained in:
parent
7707aab9a8
commit
3edff50047
5 changed files with 546 additions and 51 deletions
|
|
@ -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_period_from_remain
|
||||
from Database import Database # type: ignore
|
||||
from logging import getLogger
|
||||
|
||||
|
|
@ -1605,26 +1605,22 @@ def block_requests():
|
|||
{"ip": "124.0.0.3", "url": "/test", "date": "12/51/9851", "reason": "antibot", "method": "GET", "status": 403, "data": "{fesfmk fesfsf sfesfes}"},
|
||||
]
|
||||
|
||||
# Get top reasons
|
||||
# Prepare data
|
||||
reasons = {}
|
||||
codes = {}
|
||||
for request in requests:
|
||||
# Get top reasons
|
||||
if not request["reason"] in reasons:
|
||||
reasons[request["reason"]] = 0
|
||||
reasons[request["reason"]] = reasons[request["reason"]] + 1
|
||||
|
||||
top_reason = [k for k, v in reasons.items() if v == max(reasons.values())][0]
|
||||
|
||||
# Get top status code
|
||||
codes = {}
|
||||
for request in requests:
|
||||
# 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]
|
||||
|
||||
# Get top reason and status
|
||||
|
||||
return render_template(
|
||||
"block_requests.html",
|
||||
block_requests=[
|
||||
|
|
@ -1643,9 +1639,34 @@ def block_requests():
|
|||
@login_required
|
||||
def bans():
|
||||
# TODO : Get bans list from database and send it
|
||||
bans = [
|
||||
{"ip": "124.0.0.1", "ban_start": 1705663430, "ban_end": 1705675421, "reason": "antibot"},
|
||||
{"ip": "124.0.0.2", "ban_start": 1705663430, "ban_end": 1705685421, "reason": "test"},
|
||||
{"ip": "124.0.0.3", "ban_start": 1705663430, "ban_end": 1705664748, "reason": "unknown"},
|
||||
]
|
||||
|
||||
# Prepare data
|
||||
reasons = {}
|
||||
now_stamp = int(time())
|
||||
|
||||
for ban in bans:
|
||||
# Add remain
|
||||
ban["remain"] = "unknown" if ban["ban_end"] - now_stamp < 0 else get_remain(ban["ban_end"] - now_stamp)
|
||||
ban["period"] = get_period_from_remain(ban["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=bans,
|
||||
top_reason=top_reason,
|
||||
username=current_user.get_id(),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,16 +1,302 @@
|
|||
function addReasonOption(endpoint, reasons) {
|
||||
let content = "";
|
||||
class Filter {
|
||||
constructor(prefix = "bans") {
|
||||
this.prefix = prefix;
|
||||
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
|
||||
this.keyInp = document.querySelector("input#keyword");
|
||||
this.periodValue = "all";
|
||||
this.reasonValue = "all";
|
||||
this.initHandler();
|
||||
}
|
||||
|
||||
reasons.forEach((reason, id) => {
|
||||
content += `<button
|
||||
role="option"
|
||||
data-${endpoint}-setting-select-dropdown-btn="reason"
|
||||
value="${reason}"
|
||||
class="${
|
||||
id === reasons.length - 1 ? "rounded-b" : ""
|
||||
} 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>`;
|
||||
});
|
||||
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) {}
|
||||
});
|
||||
// PERIOD HANDLER
|
||||
+this.container.addEventListener("click", (e) => {
|
||||
try {
|
||||
if (
|
||||
e.target
|
||||
.closest("button")
|
||||
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
|
||||
"period"
|
||||
) {
|
||||
setTimeout(() => {
|
||||
const value = document
|
||||
.querySelector(
|
||||
`[data-${this.prefix}-setting-select-text="period"]`
|
||||
)
|
||||
.textContent.trim();
|
||||
|
||||
this.periodValue = 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.setFilterPeriod(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");
|
||||
}
|
||||
}
|
||||
|
||||
setFilterPeriod(bans) {
|
||||
if (this.periodValue === "all") return;
|
||||
for (let i = 0; i < bans.length; i++) {
|
||||
const el = bans[i];
|
||||
const type = this.getElAttribut(el, "period");
|
||||
if (type !== this.periodValue) 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");
|
||||
}
|
||||
}
|
||||
|
||||
const setDropdown = new Dropdown();
|
||||
const setFilter = new Filter();
|
||||
|
|
|
|||
168
src/ui/templates/bans.html
vendored
168
src/ui/templates/bans.html
vendored
|
|
@ -1,5 +1,18 @@
|
|||
{% extends "base.html" %} {% block content %} {% set current_endpoint =
|
||||
url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
||||
|
||||
{% set reasons = [] %}
|
||||
{% set periods = [] %}
|
||||
|
||||
{% for ban in bans %}
|
||||
{% if ban["reason"] not in reasons %}
|
||||
{% set reasons = reasons.append(ban["reason"]) %}
|
||||
{% endif %}
|
||||
{% if ban["period"] not in periods %}
|
||||
{% set periods = periods.append(ban["period"]) %}
|
||||
{% 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"
|
||||
|
|
@ -17,6 +30,18 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
{{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 -->
|
||||
|
||||
|
|
@ -27,8 +52,8 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
>
|
||||
<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">
|
||||
<!-- 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"
|
||||
>
|
||||
|
|
@ -40,7 +65,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
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="keyword"
|
||||
placeholder="ip, ban start, ban end"
|
||||
pattern="(.*?)"
|
||||
required
|
||||
/>
|
||||
|
|
@ -94,10 +119,82 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
>
|
||||
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 success -->
|
||||
<!-- end select reason -->
|
||||
|
||||
<!-- select period -->
|
||||
<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"
|
||||
>
|
||||
Period
|
||||
</h5>
|
||||
<button
|
||||
aria-controls="filter-state"
|
||||
data-{{current_endpoint}}-setting-select="period"
|
||||
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}}-period"
|
||||
data-name="{{current_endpoint}}-period"
|
||||
data-{{current_endpoint}}-setting-select-text="period"
|
||||
>all</span
|
||||
>
|
||||
<!-- chevron -->
|
||||
<svg
|
||||
data-{{current_endpoint}}-setting-select="period"
|
||||
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="period"
|
||||
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
|
||||
>
|
||||
<button
|
||||
role="option"
|
||||
data-{{current_endpoint}}-setting-select-dropdown-btn="period"
|
||||
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 period in periods %}
|
||||
<button
|
||||
role="option"
|
||||
data-{{current_endpoint}}-setting-select-dropdown-btn="period"
|
||||
value="{{period}}"
|
||||
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"
|
||||
>
|
||||
{{period}}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- end dropdown-->
|
||||
</div>
|
||||
<!-- end select period -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- end filter -->
|
||||
|
|
@ -108,15 +205,15 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
<div data-{{current_endpoint}}-bans-list>
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">BANS LIST</h5>
|
||||
<!-- list container-->
|
||||
<div class="min-w-[900px] w-full grid grid-cols-12 rounded p-2">
|
||||
<div class="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-3 m-0 pb-2 border-b border-gray-400"
|
||||
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-3 m-0 pb-2 border-b border-gray-400"
|
||||
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>
|
||||
|
|
@ -126,58 +223,79 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
Reason
|
||||
</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"
|
||||
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-1 m-0 pb-2 border-b border-gray-400"
|
||||
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"
|
||||
>
|
||||
Period
|
||||
</p>
|
||||
<!-- end header-->
|
||||
<!-- list -->
|
||||
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
|
||||
{% for request, value in bans.items() %}
|
||||
{% for ban in bans %}
|
||||
<li
|
||||
data-{{current_endpoint}}-list-item="{{request}}"
|
||||
data-{{current_endpoint}}-list-item-value="{{value}}"
|
||||
data-{{current_endpoint}}-list-item="{{ban}}"
|
||||
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-3 m-0 my-1"
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1 pl-2"
|
||||
data-{{current_endpoint}}-select
|
||||
>
|
||||
<input type="checkbox" name="{{request}}" value="{{request}}" class="" />
|
||||
<input class="checkbox" type="checkbox" name="{{ban.__name__}}" value="{{ban.__name__}}" class="" />
|
||||
</p>
|
||||
<p
|
||||
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
|
||||
data-{{current_endpoint}}-ip
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0 my-1"
|
||||
data-{{current_endpoint}}-ip="{{ban["ip"]}}"
|
||||
>
|
||||
{{value['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
|
||||
data-{{current_endpoint}}-reason="{{ban["reason"]}}"
|
||||
>
|
||||
{{value["reason"]}}
|
||||
{{ban["reason"]}}
|
||||
</p>
|
||||
<p
|
||||
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
|
||||
data-{{current_endpoint}}-ban_start
|
||||
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"]}}"
|
||||
>
|
||||
{{value["ban_start"]}}
|
||||
{{ban["ban_start"]}}
|
||||
</p>
|
||||
<p
|
||||
|
||||
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
|
||||
data-{{current_endpoint}}-ban_end
|
||||
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"]}}"
|
||||
>
|
||||
{{value["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}}-period="{{ban["period"]}}"
|
||||
>
|
||||
{{ban["period"]}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,76 @@ from typing import List, Optional
|
|||
|
||||
from qrcode.main import QRCode
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def get_remain(stamp):
|
||||
# Convert to milliseconds if not
|
||||
time = str(stamp)
|
||||
length = len(time)
|
||||
|
||||
if length < 13:
|
||||
missing = 13 - length
|
||||
print(missing)
|
||||
for i in range(missing):
|
||||
time = time + "0"
|
||||
|
||||
# Get remain
|
||||
ms = int(time)
|
||||
|
||||
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"{years}y {months}m {days}d {hours}h {minutes}min {seconds}s"
|
||||
|
||||
|
||||
def get_period_from_remain(remain):
|
||||
# Data, need format <n>y <n>m <n>d <n>h <n>min <n>s
|
||||
periods = remain.split(" ")
|
||||
period = "unknown"
|
||||
formats = ["years", "months", "days", "hours", "minutes", "seconds"]
|
||||
chars = ["y", "min", "m", "d", "h", "s"]
|
||||
|
||||
# Case not right format
|
||||
if len(periods) != 6:
|
||||
return period
|
||||
|
||||
# start from seconds to years, stop when first 0 occurence
|
||||
# The remain period is first 0 occurence - 1
|
||||
for i in range(len(periods)):
|
||||
# remove letter
|
||||
num = periods[len(periods) - 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:
|
||||
period = formats[len(formats) - 1]
|
||||
break
|
||||
|
||||
# Case years period
|
||||
if num and i == (len(periods) - 1):
|
||||
period = formats[0]
|
||||
break
|
||||
|
||||
# Case between seconds and years
|
||||
if not num:
|
||||
period = formats[len(formats) - i]
|
||||
break
|
||||
|
||||
return period
|
||||
|
||||
|
||||
def path_to_dict(
|
||||
path: str,
|
||||
|
|
|
|||
Loading…
Reference in a new issue