update bans

*change "period" by "term"
*start form post logic
*add unban logic
*update term and remain logic to keep only needed values
This commit is contained in:
Jordan Blasenhauer 2024-01-19 16:54:23 +01:00
parent d8b1cecf49
commit 13e546023e
4 changed files with 170 additions and 85 deletions

View file

@ -48,7 +48,7 @@ from src.Config import Config
from src.ReverseProxied import ReverseProxied
from src.User import AnonymousUser, User
from utils import check_settings, get_b64encoded_qr_image, path_to_dict, get_remain, get_period_from_remain
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
@ -1599,7 +1599,7 @@ def logs_container(container_id):
@login_required
def block_requests():
# TODO : Get block requests from database to send it
requests = [
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}"},
@ -1608,7 +1608,7 @@ def block_requests():
# Prepare data
reasons = {}
codes = {}
for request in requests:
for request in block_requests:
# Get top reasons
if not request["reason"] in reasons:
reasons[request["reason"]] = 0
@ -1623,11 +1623,7 @@ def block_requests():
return render_template(
"block_requests.html",
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}"},
],
block_requests=block_requests,
top_code=top_code,
top_reason=top_reason,
username=current_user.get_id(),
@ -1635,24 +1631,64 @@ def block_requests():
)
@app.route("/bans", methods=["GET"])
@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": 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"},
{"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())
now_stamp = int(time()) # in seconds
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"])
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"])

View file

@ -1,18 +1,16 @@
import { Checkbox } from "./utils/form";
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.termValue = "all";
this.reasonValue = "all";
this.initHandler();
}
initHandler() {
// REASON HANDLER
+this.container.addEventListener("click", (e) => {
this.container.addEventListener("click", (e) => {
try {
if (
e.target
@ -34,23 +32,21 @@ class Filter {
}
} catch (err) {}
});
// PERIOD HANDLER
+this.container.addEventListener("click", (e) => {
// TERM HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"period"
"term"
) {
setTimeout(() => {
const value = document
.querySelector(
`[data-${this.prefix}-setting-select-text="period"]`,
)
.querySelector(`[data-${this.prefix}-setting-select-text="term"]`)
.textContent.trim();
this.periodValue = value;
this.termValue = value;
//run filter
this.filter();
}, 10);
@ -74,7 +70,7 @@ class Filter {
//filter type
this.setFilterKeyword(bans);
this.setFilterReason(bans);
this.setFilterPeriod(bans);
this.setFilterTerm(bans);
}
setFilterKeyword(bans) {
@ -98,12 +94,12 @@ class Filter {
}
}
setFilterPeriod(bans) {
if (this.periodValue === "all") return;
setFilterTerm(bans) {
if (this.termValue === "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");
const type = this.getElAttribut(el, "term");
if (type !== this.termValue) el.classList.add("hidden");
}
}
@ -300,6 +296,66 @@ class Dropdown {
}
}
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();
});
}
}
const setDropdown = new Dropdown();
const setFilter = new Filter();
new Checkbox();
const setUnban = new Unban();

View file

@ -2,14 +2,14 @@
url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% set reasons = [] %}
{% set periods = [] %}
{% set terms = [] %}
{% 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"]) %}
{% if ban["term"] not in terms %}
{% set terms = terms.append(ban["term"]) %}
{% endif %}
{% endfor %}
@ -134,28 +134,28 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
</div>
<!-- end select reason -->
<!-- select period -->
<!-- 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"
>
Period
Term
</h5>
<button
aria-controls="filter-state"
data-{{current_endpoint}}-setting-select="period"
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}}-period"
data-name="{{current_endpoint}}-period"
data-{{current_endpoint}}-setting-select-text="period"
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="period"
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"
@ -170,31 +170,31 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<div
id="filter-state"
role="listbox"
data-{{current_endpoint}}-setting-select-dropdown="period"
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="period"
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 period in periods %}
{% for term in terms %}
<button
role="option"
data-{{current_endpoint}}-setting-select-dropdown-btn="period"
value="{{period}}"
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"
>
{{period}}
{{term}}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select period -->
<!-- end select term -->
</div>
</div>
<!-- end filter -->
@ -245,7 +245,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<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
Term
</p>
<!-- end header-->
<!-- list -->
@ -257,7 +257,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
>
<div
data-{{current_endpoint}}-select
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}}"
@ -317,9 +317,9 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
</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"]}}"
data-{{current_endpoint}}-term="{{ban["term"]}}"
>
{{ban["period"]}}
{{ban["term"]}}
</p>
</li>
{% endfor %}
@ -330,10 +330,13 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
</div>
</div>
<div class="w-full col-span-12 justify-center flex mt-6 mb-3">
<button disabled type="submit" class="valid-btn mr-3 text-base">
<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>
</div>
</div>
</form>
{% endblock %}

View file

@ -10,19 +10,9 @@ 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)
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)
@ -35,25 +25,25 @@ def get_remain(stamp):
hours %= 24
days %= 30
months %= 12
return f"{years}y {months}m {days}d {hours}h {minutes}min {seconds}s"
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_period_from_remain(remain):
def get_term_from_remain(remain):
# Data, need format <n>y <n>m <n>d <n>h <n>min <n>s
periods = remain.split(" ")
period = "unknown"
terms = remain.split(" ")
term = ""
formats = ["years", "months", "days", "hours", "minutes", "seconds"]
chars = ["y", "min", "m", "d", "h", "s"]
# Case not right format
if len(periods) != 6:
return period
# Not handle
if remain == "unknown":
return remain
# start from seconds to years, stop when first 0 occurence
# The remain period is first 0 occurence - 1
for i in range(len(periods)):
# The remain term is first 0 occurence - 1
for i in range(len(terms)):
# remove letter
num = periods[len(periods) - 1 - i]
num = terms[len(terms) - 1 - i]
for char in chars:
num = num.replace(char, "")
num = "0" if not num else num
@ -62,20 +52,20 @@ def get_period_from_remain(remain):
# Case seconds or less
if not num and i == 0:
period = formats[len(formats) - 1]
term = formats[len(formats) - 1]
break
# Case years period
if num and i == (len(periods) - 1):
period = formats[0]
# 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:
period = formats[len(formats) - i]
term = formats[len(formats) - i]
break
return period
return term
def path_to_dict(