Start migrating core plugins' pages to the new format

This commit is contained in:
Théophile Diot 2024-10-24 18:00:17 +02:00
parent 3058a629bf
commit 9c67ee143c
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
17 changed files with 557 additions and 796 deletions

View file

@ -1,24 +1,26 @@
from logging import getLogger
from traceback import format_exc
def pre_render(**kwargs):
logger = getLogger("UI")
ret = {
"counter_failed_challenges": {
"value": 0,
"title": "Challenges",
"subtitle": "Failed",
"subtitle_color": "danger",
"svg_color": "danger",
},
}
try:
data = kwargs["bw_instances_utils"].get_metrics("antibot")
return {
"counter_failed_challenges": {
"value": data.get("counter_failed_challenges", 0),
"title": "Challenge",
"subtitle": "Failed",
"subtitle_color": "error",
"svg_color": "red",
}
}
except BaseException:
print(format_exc(), flush=True)
return {
"counter_failed_challenges": {"value": "unknown", "title": "Challenge", "subtitle": "Failed", "subtitle_color": "error", "svg_color": "red"},
"error": format_exc(),
}
ret["counter_failed_challenges"]["value"] = kwargs["bw_instances_utils"].get_metrics("antibot").get("counter_failed_challenges", 0)
except BaseException as e:
logger.debug(format_exc())
logger.error(f"Failed to get antibot metrics: {e}")
ret["error"] = str(e)
return ret
def antibot(**kwargs):

View file

@ -1,140 +0,0 @@
{% extends "base.html" %}
{% set read_doc_text = 'You will find more information about the antibot plugin <a target="_blank" href="https://docs.bunkerweb.io/' + bw_version + '/security-tuning/#antibot" class="core-card-text-doc-link">in the documentation</a>.' %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %} <div class="core-layout-separator"></div>
<div class="my-2 flex justify-center col-span-12">
<div class="mr-1">
<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 stroke-red-500 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</div>
<p class="px-1 text-white break-words">(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}</p>
</div>
{% endif %}
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
{% for key, value in pre_render.get("data", {}).items() %}
{% if key.startswith("ping_") %}
<div class="core-card-status">
<div class="core-card-status-container">
<h5 class="core-card-status-title">{{ pre_render['data'][key].get('title', 'STATUS')}}</h5>
<svg data-status-svg
class="core-card-status-svg {{ 'fill-green-500' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'fill-red-500' }}"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p data-status-text class="core-card-text">{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}</p>
</div>
{% endif %}
{% if key.startswith("count_") or key.startswith("counter_") %}
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">{{pre_render['data'][key].get("title")}}</p>
<h5 data-count class="core-card-title">{{pre_render['data'][key].get("value")}}</h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content {{pre_render['data'][key].get("subtitle_color", "info")}}">{{pre_render['data'][key].get("subtitle")}}</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container {{pre_render['data'][key].get("svg_color")}}">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg"
>
<path
d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% endif %}
{% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %}
<div class="core-card-list">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">{{ key.replace('_', ' ').upper()}}</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap">
<!-- header-->
{% for val_key, val_value in pre_render['data'][key][0].items() %}
<p class="core-card-list-header {{'col-span-6' if pre_render['data'][key][0].keys()|length == 2 else "col-span-4" if pre_render['data'][key][0].keys()|length == 3 else "col-span-3" if pre_render['data'][key][0].keys()|length == 4}}">{{ val_key }}</p>
{% endfor%}
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in pre_render['data'][key] %}
<li class="core-card-list-item">
{% for top_key, top_value in item.items() %}
<p class="core-card-list-item-content {{'col-span-6' if item.keys()|length == 2 else "col-span-4" if item.keys()|length == 3 else "col-span-3" if item.keys()|length == 4}}">{{ top_value }}</p>
{% endfor %}
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-deactivated-title">Plugin deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p data-info class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,23 +1,47 @@
from datetime import datetime
from json import loads
from logging import debug
from logging import getLogger
from traceback import format_exc
def pre_render(*args, **kwargs):
def pre_render(**kwargs):
logger = getLogger("UI")
ret = {
"date_last_backup": {
"value": "N/A",
"title": "Last Backup",
"subtitle_color": "primary",
"svg_color": "primary",
},
"list_backup_files": {
"data": {},
"svg_color": "primary",
},
}
try:
backup_file = kwargs["db"].get_job_cache_file("backup-data", "backup.json")
debug(f"backup_file: {backup_file}")
logger.debug(f"backup_file: {backup_file}")
data = loads(backup_file or "{}")
if data.get("date", None):
data["date"] = datetime.fromisoformat(data["date"]).strftime("%Y-%m-%d %H:%M:%S %Z")
for backup_file in data.get("files", []):
if "file name" not in ret["list_backup_files"]["data"]:
ret["list_backup_files"]["data"]["file name"] = []
ret["list_backup_files"]["data"]["file name"].append(backup_file)
return data
except BaseException:
print(format_exc(), flush=True)
return {"date": None, "files": [], "error": format_exc()}
if ret["list_backup_files"]["data"]:
ret["date_last_backup"]["value"] = datetime.strptime(
"-".join(ret["list_backup_files"]["data"]["file name"][len(ret["list_backup_files"]["data"]["file name"]) - 1].split("-")[2:]).replace(
".zip", ""
),
"%Y-%m-%d_%H-%M-%S",
).isoformat()
except BaseException as e:
logger.debug(format_exc())
logger.error(f"Failed to get backup metrics: {e}")
ret["error"] = str(e)
return ret
def backup(**kwargs):

View file

@ -1,89 +0,0 @@
{% extends "base.html" %}
{% set read_doc_text = 'You will find more information about the backup plugin <a target="_blank" href="https://docs.bunkerweb.io/' + bw_version + '/security-tuning/#backup-and-restore" class="core-card-text-doc-link">in the documentation</a>.' %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used %}
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %} <div class="core-layout-separator"></div>
<div class="my-2 flex justify-center col-span-12">
<div class="mr-1">
<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 stroke-red-500 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</div>
<p class="px-1 text-white break-words">(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}</p>
</div>
{% endif %}
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">LAST BACKUP</h5>
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{ pre_render["data"].get("date", "No backup found") }}</p>
</div>
</div>
{% if pre_render['data'].get("files", [])|length > 0 %}
<div class="core-layout-separator"></div>
<div class="core-card-list">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">BACKUP FILES</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap">
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in pre_render['data']["files"] %}
<li class="core-card-list-item">
<p class="core-card-list-item-content col-span-12">{{ item }}</p>
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
{% endif %}
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Plugin deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p data-info class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,18 +1,40 @@
from logging import getLogger
from operator import itemgetter
from traceback import format_exc
def pre_render(**kwargs):
logger = getLogger("UI")
ret = {
"top_bad_behavior": {
"data": {},
"order": {
"column": 1,
"dir": "desc",
},
"svg_color": "primary",
},
}
try:
# Here we will have a list { 'counter_403': X, 'counter_401': Y ... }
data = kwargs["bw_instances_utils"].get_metrics("badbehavior")
# Format to fit [{code: 403, count: X}, {code: 401, count: Y} ...]
format_data = [{"code": int(key.split("_")[1]), "count": int(value)} for key, value in data.items()]
format_data = [
{
"code": int(key.split("_")[1]),
"count": int(value),
}
for key, value in kwargs["bw_instances_utils"].get_metrics("badbehavior").items()
]
format_data.sort(key=itemgetter("count"), reverse=True)
return {"top_bad_behavior": format_data}
except BaseException:
print(format_exc(), flush=True)
return {"top_bad_behavior": "unknown", "error": format_exc()}
data = {"code": [], "count": []}
for item in format_data:
data["code"].append(item["code"])
data["count"].append(item["count"])
ret["top_bad_behavior"]["data"] = data
except BaseException as e:
logger.debug(format_exc())
logger.error(f"Failed to get badbehavior metrics: {e}")
ret["error"] = str(e)
return ret
def badbehavior(**kwargs):

View file

@ -1,144 +0,0 @@
{% extends "base.html" %}
{% set read_doc_text = 'You will find more information about the bad behavior plugin <a target="_blank" href="https://docs.bunkerweb.io/' + bw_version + '/security-tuning/#bad-behavior" class="core-card-text-doc-link">in the documentation</a>.' %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %} <div class="core-layout-separator"></div>
<div class="my-2 flex justify-center col-span-12">
<div class="mr-1">
<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 stroke-red-500 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</div>
<p class="px-1 text-white break-words">(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}</p>
</div>
{% endif %}
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
{% for key, value in pre_render.get("data", {}).items() %}
{% if key.startswith("ping_") %}
<div class="core-card-status">
<div class="core-card-status-container">
<h5 class="core-card-status-title">{{ pre_render['data'][key].get('title', 'STATUS')}}</h5>
<svg data-status-svg
class="core-card-status-svg {{ 'fill-green-500' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'fill-red-500' }}"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p data-status-text class="core-card-text">{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}</p>
</div>
{% endif %}
{% if key.startswith("count_") or key.startswith("counter_") %}
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">{{pre_render['data'][key].get("title")}}</p>
<h5 data-count class="core-card-title">{{pre_render['data'][key].get("value")}}</h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content {{pre_render['data'][key].get("subtitle_color", "info")}}">{{pre_render['data'][key].get("subtitle")}}</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container {{pre_render['data'][key].get("svg_color")}}">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg"
>
<path
d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% endif %}
{% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %}
<div class="core-card-list">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">{{ key.replace('_', ' ').upper()}}</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap">
<!-- header-->
{% for val_key, val_value in pre_render['data'][key][0].items() %}
<p class="core-card-list-header {{'col-span-6' if pre_render['data'][key][0].keys()|length == 2 else "col-span-4" if pre_render['data'][key][0].keys()|length == 3 else "col-span-3" if pre_render['data'][key][0].keys()|length == 4}}">{{ val_key }}</p>
{% endfor%}
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in pre_render['data'][key] %}
<li class="core-card-list-item">
{% for top_key, top_value in item.items() %}
<p class="core-card-list-item-content {{'col-span-6' if item.keys()|length == 2 else "col-span-4" if item.keys()|length == 3 else "col-span-3" if item.keys()|length == 4}}">{{ top_value }}</p>
{% endfor %}
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Plugin deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p data-info class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,24 +1,58 @@
from logging import getLogger
from traceback import format_exc
def pre_render(**kwargs):
metrics = {
"counter_failed_url": {"value": "unknown", "title": "URL", "subtitle": "denied", "subtitle_color": "error", "svg_color": "red"},
"counter_failed_ip": {"value": "unknown", "title": "IP", "subtitle": "denied", "subtitle_color": "error", "svg_color": "orange"},
"counter_failed_rdns": {"value": "unknown", "title": "RDNS", "subtitle": "denied", "subtitle_color": "error", "svg_color": "amber"},
"counter_failed_asn": {"value": "unknown", "title": "ASN", "subtitle": "denied", "subtitle_color": "error", "svg_color": "emerald"},
"counter_failed_ua": {"value": "unknown", "title": "UA", "subtitle": "denied", "subtitle_color": "error", "svg_color": "pink"},
logger = getLogger("UI")
ret = {
"counter_failed_url": {
"value": 0,
"title": "URL",
"subtitle": "Denied",
"subtitle_color": "error",
"svg_color": "danger",
},
"counter_failed_ip": {
"value": 0,
"title": "IP",
"subtitle": "Denied",
"subtitle_color": "orange",
"svg_color": "orange",
},
"counter_failed_rdns": {
"value": 0,
"title": "RDNS",
"subtitle": "Denied",
"subtitle_color": "amber",
"svg_color": "amber",
},
"counter_failed_asn": {
"value": 0,
"title": "ASN",
"subtitle": "Denied",
"subtitle_color": "olive",
"svg_color": "olive",
},
"counter_failed_ua": {
"value": 0,
"title": "UA",
"subtitle": "Denied",
"subtitle_color": "purple",
"svg_color": "purple",
},
}
try:
data = kwargs["bw_instances_utils"].get_metrics("blacklist")
for key in metrics:
metrics[key]["value"] = data.get(key, 0)
return metrics
except BaseException:
print(format_exc(), flush=True)
metrics["error"] = format_exc()
return metrics
logger.debug(f"Blacklist metrics: {data}")
for key in data:
ret[key]["value"] = data.get(key, 0)
except BaseException as e:
logger.debug(format_exc())
logger.error(f"Failed to get blacklist metrics: {e}")
ret["error"] = str(e)
return ret
def blacklist(**kwargs):

View file

@ -1,144 +0,0 @@
{% extends "base.html" %}
{% set read_doc_text = 'You will find more information about the blacklist plugin <a target="_blank" href="https://docs.bunkerweb.io/' + bw_version + '/security-tuning/#blacklisting" class="core-card-text-doc-link">in the documentation</a>.' %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used and is_metrics %}
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %} <div class="core-layout-separator"></div>
<div class="my-2 flex justify-center col-span-12">
<div class="mr-1">
<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 stroke-red-500 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</div>
<p class="px-1 text-white break-words">(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}</p>
</div>
{% endif %}
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
<div class="core-layout-separator"></div>
{% for key, value in pre_render.get("data", {}).items() %}
{% if key.startswith("ping_") %}
<div class="core-card-status">
<div class="core-card-status-container">
<h5 class="core-card-status-title">{{ pre_render['data'][key].get('title', 'STATUS')}}</h5>
<svg data-status-svg
class="core-card-status-svg {{ 'fill-green-500' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'fill-red-500' }}"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p data-status-text class="core-card-text">{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}</p>
</div>
{% endif %}
{% if key.startswith("count_") or key.startswith("counter_") %}
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">{{pre_render['data'][key].get("title")}}</p>
<h5 data-count class="core-card-title">{{pre_render['data'][key].get("value")}}</h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content {{pre_render['data'][key].get("subtitle_color", "info")}}">{{pre_render['data'][key].get("subtitle")}}</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container {{pre_render['data'][key].get("svg_color")}}">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg"
>
<path
d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% endif %}
{% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %}
<div class="core-card-list">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">{{ key.replace('_', ' ').upper()}}</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap">
<!-- header-->
{% for val_key, val_value in pre_render['data'][key][0].items() %}
<p class="core-card-list-header {{'col-span-6' if pre_render['data'][key][0].keys()|length == 2 else "col-span-4" if pre_render['data'][key][0].keys()|length == 3 else "col-span-3" if pre_render['data'][key][0].keys()|length == 4}}">{{ val_key }}</p>
{% endfor%}
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in pre_render['data'][key] %}
<li class="core-card-list-item">
{% for top_key, top_value in item.items() %}
<p class="core-card-list-item-content {{'col-span-6' if item.keys()|length == 2 else "col-span-4" if item.keys()|length == 3 else "col-span-3" if item.keys()|length == 4}}">{{ top_value }}</p>
{% endfor %}
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Plugin deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p data-info class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -1,13 +1,53 @@
from logging import getLogger
from traceback import format_exc
def pre_render(**kwargs):
logger = getLogger("UI")
ret = {
"ping_status": {
"title": "BUNKERNET STATUS",
"value": "error",
"col-size": "col-12 col-md-4",
"card-classes": "h-100",
},
"info_instance_id": {
"value": "Unknown",
"title": "Instance ID",
"subtitle_color": "primary",
"svg_color": "primary",
"col-size": "col-12 col-md-8",
"card-classes": "h-100",
},
"list_bunkernet_ips": {
"data": {},
"types": {0: "ip-address"},
"svg_color": "primary",
"col-size": "col-12",
},
}
try:
ping_data = kwargs["bw_instances_utils"].get_ping("bunkernet")
return {"ping_status": {"title": "BUNKERNET STATUS", "value": ping_data["status"]}}
except BaseException:
print(format_exc(), flush=True)
return {"ping_status": {"title": "BUNKERNET STATUS", "value": "error"}, "error": format_exc()}
ret["ping_status"]["value"] = ping_data["status"]
instance_id = kwargs["db"].get_job_cache_file("bunkernet-register", "instance.id")
if instance_id:
ret["info_instance_id"]["value"] = instance_id.decode("utf-8")
ips_file = kwargs["db"].get_job_cache_file("bunkernet-data", "ip.list")
logger.debug(f"IPs file: {ips_file}")
if ips_file:
ips_file = ips_file.decode("utf-8")
for ip in ips_file.split("\n"):
if "ip" not in ret["list_bunkernet_ips"]["data"]:
ret["list_bunkernet_ips"]["data"]["ip"] = []
ret["list_bunkernet_ips"]["data"]["ip"].append(ip)
except BaseException as e:
logger.debug(format_exc())
logger.error(f"Failed to get bunkernet metrics: {e}")
ret["error"] = str(e)
return ret
def bunkernet(**kwargs):

View file

@ -1,144 +0,0 @@
{% extends "base.html" %}
{% set read_doc_text = 'You will find more information about the bunkernet plugin <a target="_blank" href="https://docs.bunkerweb.io/' + bw_version + '/security-tuning/#bunkernet" class="core-card-text-doc-link">in the documentation</a>.' %}
{% block content %}
<input type="csrf_token"
name="csrf_token"
value="{{ csrf_token }}"
class="hidden"
hidden />
<div class="core-layout">
{% if is_used %}
<div class="core-card">
<h5 class="core-card-title">INFO</h5>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %} <div class="core-layout-separator"></div>
<div class="my-2 flex justify-center col-span-12">
<div class="mr-1">
<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 stroke-red-500 fill-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</div>
<p class="px-1 text-white break-words">(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}</p>
</div>
{% endif %}
{% if pre_render.get("status", False) and pre_render.get("status", False) == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
{% for key, value in pre_render.get("data", {}).items() %}
{% if key.startswith("ping_") %}
<div class="core-card-status">
<div class="core-card-status-container">
<h5 class="core-card-status-title">{{ pre_render['data'][key].get('title', 'STATUS')}}</h5>
<svg data-status-svg
class="core-card-status-svg {{ 'fill-green-500' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'fill-red-500' }}"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
</div>
<p data-status-text class="core-card-text">{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}</p>
</div>
{% endif %}
{% if key.startswith("count_") or key.startswith("counter_") %}
<div class="core-card-metrics">
<!-- text -->
<div>
<p class="core-card-metrics-name">{{pre_render['data'][key].get("title")}}</p>
<h5 data-count class="core-card-title">{{pre_render['data'][key].get("value")}}</h5>
<p class="core-card-metrics-subtitle">
<span class="core-card-metrics-subtitle-content {{pre_render['data'][key].get("subtitle_color", "info")}}">{{pre_render['data'][key].get("subtitle")}}</span>
</p>
</div>
<!-- end text -->
<!-- icon -->
<div role="img" class="core-card-svg-container {{pre_render['data'][key].get("svg_color")}}">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-small core-card-metrics-svg"
>
<path
d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<!-- end icon -->
</div>
{% endif %}
{% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %}
<div class="core-card-list">
<div class="core-card-list-title-container">
<h5 class="core-card-list-title">{{ key.replace('_', ' ').upper()}}</h5>
</div>
<div class="core-card-list-container">
<!-- list container-->
<div class="core-card-list-wrap">
<!-- header-->
{% for val_key, val_value in pre_render['data'][key][0].items() %}
<p class="core-card-list-header {{'col-span-6' if pre_render['data'][key][0].keys()|length == 2 else "col-span-4" if pre_render['data'][key][0].keys()|length == 3 else "col-span-3" if pre_render['data'][key][0].keys()|length == 4}}">{{ val_key }}</p>
{% endfor%}
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full">
{% for item in pre_render['data'][key] %}
<li class="core-card-list-item">
{% for top_key, top_value in item.items() %}
<p class="core-card-list-item-content {{'col-span-6' if item.keys()|length == 2 else "col-span-4" if item.keys()|length == 3 else "col-span-3" if item.keys()|length == 4}}">{{ top_value }}</p>
{% endfor %}
</li>
{% endfor %}
</ul>
<!-- end list-->
</div>
<!-- end list container-->
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% else %}
<div class="core-card">
<div class="core-card-wrap">
<h5 class="core-card-title">Plugin deactivated</h5>
<!-- icon -->
<div role="img" class="core-card-svg-container">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="core-card-deactivated-svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</div>
<!-- end icon -->
</div>
<div class="core-card-text-container">
<p data-info class="core-card-text">{{plugin.get('description')}}</p>
</div>
<p data-info class="core-card-text-doc">{{ read_doc_text|safe }}</p>
</div>
<!-- end info -->
{% endif %}
</div>
{% endblock %}

View file

@ -2,7 +2,7 @@ from importlib.machinery import SourceFileLoader
from io import BytesIO
from json import JSONDecodeError, loads as json_loads
from os import listdir
from os.path import basename, dirname, isabs
from os.path import basename, dirname, isabs, join, sep
from pathlib import Path
from shutil import move, rmtree
from sys import path as sys_path
@ -15,6 +15,7 @@ from zipfile import BadZipFile, ZipFile
from flask import Blueprint, Response, current_app, flash, jsonify, redirect, render_template, request, url_for
from flask_login import login_required
from jinja2 import Environment, FileSystemLoader, select_autoescape
from werkzeug.utils import secure_filename
from common_utils import bytes_hash # type: ignore
@ -520,7 +521,7 @@ def custom_plugin_page(plugin: str):
is_metrics_on = db_config.get("USE_METRICS", "yes") != "no"
is_used = plugin in ALWAYS_USED_PLUGINS or plugin_used()
if not is_metrics_on and not is_used:
if is_metrics_on and not is_used:
# Check if at least one service is using metrics and/or the plugin
for service in db_config.get("SERVER_NAME", "").split(" "):
if not is_metrics_on and db_config.get(f"{service}_USE_METRICS", "yes") != "no":
@ -530,37 +531,38 @@ def custom_plugin_page(plugin: str):
if is_metrics_on and is_used:
break
pre_render = {}
plugin_page = ""
# TODO: uncomment this when the plugin pages are ready
# if is_used and is_metrics_on:
# page = DB.get_plugin_page(plugin)
# if not page:
# return error_message("The plugin does not have a page"), 404
if is_used and is_metrics_on:
page = DB.get_plugin_page(plugin)
if not page:
return error_message("The plugin does not have a page"), 404
# tmp_page_dir = TMP_DIR.joinpath("ui", "page", str(uuid4()))
# tmp_page_dir.mkdir(parents=True, exist_ok=True)
tmp_page_dir = TMP_DIR.joinpath("ui", "page", str(uuid4()))
tmp_page_dir.mkdir(parents=True, exist_ok=True)
# with tar_open(fileobj=BytesIO(page), mode="r:gz") as tar_file:
# tar_file.extractall(tmp_page_dir)
with tar_open(fileobj=BytesIO(page), mode="r:gz") as tar_file:
tar_file.extractall(tmp_page_dir)
# tmp_page_dir = tmp_page_dir.joinpath("ui")
tmp_page_dir = tmp_page_dir.joinpath("ui")
# LOGGER.debug(f"Plugin {plugin} page extracted successfully")
LOGGER.debug(f"Plugin {plugin} page extracted successfully")
# pre_render = run_action(plugin, "pre_render", tmp_dir=tmp_page_dir)
# try:
# plugin_page = (
# # deepcode ignore Ssti: We trust the plugin template
# Environment(
# loader=FileSystemLoader((tmp_page_dir.as_posix() + "/", join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")),
# autoescape=select_autoescape(["html"]),
# )
# .from_string(tmp_page_dir.joinpath("template.html").read_text(encoding="utf-8"))
# .render(pre_render=pre_render, **current_app.jinja_env.globals)
# )
# except BaseException as e:
# LOGGER.exception(f"An error occurred while rendering the plugin page")
# plugin_page = f'<div class="mt-2 mb-2 alert alert-danger text-center" role="alert">An error occurred while rendering the plugin page: {e}<br/>See logs for more details</div>'
pre_render = run_action(plugin, "pre_render", tmp_dir=tmp_page_dir)
if tmp_page_dir.joinpath("template.html").is_file():
try:
plugin_page = (
# deepcode ignore Ssti: We trust the plugin template
Environment(
loader=FileSystemLoader((tmp_page_dir.as_posix() + "/", join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")),
autoescape=select_autoescape(["html"]),
)
.from_string(tmp_page_dir.joinpath("template.html").read_text(encoding="utf-8"))
.render(pre_render=pre_render, **current_app.jinja_env.globals)
)
except BaseException as e:
LOGGER.exception("An error occurred while rendering the plugin page")
plugin_page = f'<div class="mt-2 mb-2 alert alert-danger text-center" role="alert">An error occurred while rendering the plugin page: {e}<br/>See logs for more details</div>'
return render_template(
"plugin_page.html",
@ -568,6 +570,7 @@ def custom_plugin_page(plugin: str):
plugin=plugin_data,
is_used=is_used,
is_metrics=is_metrics_on,
pre_render=pre_render,
)

View file

@ -782,3 +782,67 @@ a.courier-prime:hover {
width: 100vw;
height: 100vh;
}
.text-orange {
color: #ff7f50;
}
.text-amber {
color: #ffbf00;
}
.text-emerald {
color: #50c878;
}
.text-pink {
color: #ff69b4;
}
.text-purple {
color: #800080;
}
.text-sky {
color: #87ceeb;
}
.text-turquoise {
color: #40e0d0;
}
.text-violet {
color: #ee82ee;
}
.text-rose {
color: #ff007f;
}
.text-olive {
color: #808000;
}
.text-lime {
color: #00ff00;
}
.text-gold {
color: #ffd700;
}
.text-cyan {
color: #00ffff;
}
.text-aqua {
color: #00ffff;
}
.text-indigo {
color: #4b0082;
}
.text-maroon {
color: #800000;
}

View file

@ -0,0 +1,85 @@
$(document).ready(function () {
$(".date-field").each(function () {
const isoDateStr = $(this).text().trim();
if (isoDateStr == "N/A") return;
// Parse the ISO format date string
const date = new Date(isoDateStr);
// Check if the date is valid
if (!isNaN(date)) {
// Convert to local date and time string
const localDateStr = date.toLocaleString();
// Update the text content with the local date string
$(this).text(localDateStr);
} else {
// Handle invalid date
console.error(`Invalid date string: ${isoDateStr}`);
}
});
$(".table").each(function () {
tableLength = parseInt($(`#${this.id}-length`).val().trim());
var tableOrder;
const $tableOrder = $(`#${this.id}-order`);
if ($tableOrder.length) {
tableOrder = JSON.parse($tableOrder.text().trim());
} else {
tableOrder = { column: 0, dir: "desc" };
}
var tableTypes;
const $tableTypes = $(`#${this.id}-types`);
if ($tableTypes.length) {
tableTypes = JSON.parse($tableTypes.text().trim());
}
const layout = {
topStart: {},
bottomEnd: {},
};
if (tableLength > 10) {
const menu = [10];
if (tableLength > 25) {
menu.push(25);
}
if (tableLength > 50) {
menu.push(50);
}
if (tableLength > 100) {
menu.push(100);
}
menu.push({ label: "All", value: -1 });
layout.topStart.pageLength = {
menu: menu,
};
layout.bottomEnd.paging = true;
}
const columnDefs = [];
if (tableTypes) {
Object.entries(tableTypes).forEach(([column, type]) => {
columnDefs.push({
type: type,
targets: parseInt(column),
});
});
}
new DataTable(this, {
columnDefs: columnDefs,
autoFill: false,
responsive: true,
layout: layout,
order: [[parseInt(tableOrder.column), tableOrder.dir]],
});
$(this).removeClass("d-none");
});
$(".dt-type-numeric").removeClass("dt-type-numeric");
});

View file

@ -230,6 +230,9 @@
{% elif current_endpoint == "pro" %}
<script src="{{ url_for('static', filename='js/pages/pro.js') }}"
nonce="{{ script_nonce }}"></script>
{% elif current_endpoint != "plugins" and "plugins" in request.path %}
<script src="{{ url_for('static', filename='js/pages/plugin_page.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
<script async defer src="{{ url_for('static', filename='js/buttons.js') }}"></script>
</body>

View file

@ -31,9 +31,9 @@
} %}
<ul class="menu-inner py-1">
{% for endpoint, item in menu_items.items() %}
<li class="menu-item {% if endpoint in request.path.split('/')[1] %}active{% endif %} {% if item.get('sub') and item.get('open', True) %}open{% endif %}">
<li class="menu-item{% if endpoint == current_endpoint or endpoint != 'plugins' and endpoint in request.path.split('/')[1] %} active{% endif %}{% if item.get('sub') and item.get('open', True) %} open{% endif %}">
<a href="{{ item['url'] }}"
class="menu-link {% if item.get('sub') %}menu-toggle{% endif %}">
class="menu-link{% if item.get('sub') %} menu-toggle{% endif %}">
<i class="menu-icon tf-icons bx {{ item['icon'] }}"></i>
<div class="text-truncate"
data-i18n="{{ endpoint.replace('-', ' ') |title }}">
@ -43,7 +43,7 @@
{% if item.get('sub') %}
<ul class="menu-sub">
{% for sub in item['sub'][:item.get('max', 5)] %}
<li class="menu-item {% if current_endpoint == sub %}active{% endif %}">
<li class="menu-item{% if current_endpoint == sub %} active{% endif %}">
<a href="{{ item['url'] }}/{{ sub }}" class="menu-link">
<div class="text-truncate" data-i18n="Without menu">{{ sub }}</div>
</a>
@ -61,7 +61,7 @@
</li>
{% endfor %}
<!-- Plugins Pages -->
<li class="menu-header text-uppercase align-items-center">
<li class="menu-header text-uppercase align-items-center mb-0">
<span class="menu-header-text">Plugins Pages</span>
<button class="btn btn-link menu-header-text p-0"
type="button"
@ -72,36 +72,45 @@
<i class="bx bx-chevron-right chevron-icon chevron-rotate"></i>
</button>
</li>
<div class="collapse w-100 show" id="pluginsCollapse">
{% for plugin, plugin_data in plugins.items() %}
{% with not_pro_pro_plugin = not is_pro_version and plugin_data['type'] == "pro" %}
{% if not_pro_pro_plugin or plugin_data['page'] %}
<li class="menu-item{% if current_endpoint == plugin %} active{% endif %}"{% if not_pro_pro_plugin %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" data-bs-original-title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
{% endif %}
>
<a href="{% if not_pro_pro_plugin %}{{ url_for('pro') }}{% else %}{{ url_for("plugins") }}/{{ plugin }}{% endif %}"
class="menu-link"
{% if not_pro_pro_plugin %}target="_blank" rel="noopener"{% endif %}>
<i class="menu-icon tf-icons bx bx-puzzle"></i>
<div class="text-truncate{% if plugin_data['type'] == 'pro' %} text-primary shine shine-sm{% endif %} pe-2"
data-i18n="{{ plugin_data['name'] }}">{{ plugin_data['name'] }}</div>
{% if plugin_data['type'] != "pro" %}
<div class="badge rounded-pill bg-label-{% if plugin_data['type'] == 'core' %}secondary{% else %}primary{% endif %} text-uppercase fs-tiny ms-auto">
{{ plugin_data['type']|upper }}
</div>
{% else %}
<div class="badge badge-center rounded-pill text-uppercase fs-tiny ms-auto">
<img src="{{ pro_diamond_url }}"
alt="Pro plugin"
width="18px"
height="15.5px">
</div>
{% set menu_plugin_types = {
"core": {
"icon": "<i class=\"menu-icon tf-icons bx bx-shield me-0\"></i>"
},
"external": {
"icon": "<i class=\"menu-icon tf-icons bx bx-plug me-0\"></i>"
},
"ui": {
"icon": "<i class=\"menu-icon tf-icons bx bx-cloud-upload me-0\"></i>"
}
} %}
<div class="collapse py-1 show" id="pluginsCollapse">
<div class="menu-inner">
{% for plugin, plugin_data in plugins.items() %}
{% with not_pro_pro_plugin = not is_pro_version and plugin_data['type'] == "pro" %}
{% if not_pro_pro_plugin or plugin_data['page'] %}
<li class="menu-item{% if current_endpoint == plugin|lower %} active{% endif %}"{% if not_pro_pro_plugin %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" data-bs-original-title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
{% endif %}
</a>
</li>
{% endif %}
{% endwith %}
{% endfor %}
>
<a href="{% if not_pro_pro_plugin %}{{ url_for('pro') }}{% else %}{{ url_for("plugins") }}/{{ plugin }}{% endif %}"
class="menu-link"
{% if not_pro_pro_plugin %}target="_blank" rel="noopener"{% endif %}>
{{ menu_plugin_types.get(plugin_data["type"], {}).get('icon', '<img src="' + pro_diamond_url + '"
alt="Pro plugin"
width="20px"
height="17.2px">') |safe }}
<div class="text-truncate pe-2{% if plugin_data['type'] == 'pro' %} text-primary shine shine-sm ps-3{% else %} ps-2{% endif %}"
data-i18n="{{ plugin_data['name'] }}">{{ plugin_data['name'] }}</div>
{% if plugin_data['type'] != "pro" %}
<div class="badge rounded-pill bg-label-{% if plugin_data['type'] == 'core' %}secondary{% else %}primary{% endif %} text-uppercase fs-tiny ms-auto">
{{ plugin_data['type']|upper }}
</div>
{% endif %}
</a>
</li>
{% endif %}
{% endwith %}
{% endfor %}
</div>
</div>
<!-- / Plugins Pages -->
<!-- Misc -->

View file

@ -1,7 +1,7 @@
{% extends "dashboard.html" %}
{% block content %}
<!-- Content -->
<div class="card text-nowrap p-4 min-vh-70">
<div class="card p-1 mb-4 sticky-card text-nowrap">
<div class="card-header d-flex justify-content-between align-items-center mw-100">
<div class="pt-1 flex-grow-1 me-2" style="min-width: 0;">
<h5 class="card-title d-inline don-jose{{ plugin_types[plugin['type']].get('text-class', '') }}{{ plugin_types[plugin['type']].get('title-class', '') }}">
@ -21,29 +21,164 @@
</a>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center text-primary">
{% if not is_used %}
<p id="config-waiting"
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Plugin is deactivated, therefore no information is available.
</p>
{% elif not is_metrics %}
<p id="config-waiting"
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Metrics plugin isn't activated, therefore no information is available.
</p>
{% else %}
<!-- TODO: remove this when plugin pages are available -->
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Plugin pages are not yet available during the beta phase.
</p>
{{ plugin_page|safe }}
{% endif %}
</div>
</div>
{% if not is_used %}
<div class="d-flex align-items-center justify-content-center">
<div class="text-center text-primary">
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Plugin is deactivated, therefore no information is available.
</p>
</div>
</div>
</div>
{% elif not is_metrics %}
<div class="d-flex align-items-center justify-content-center">
<div class="text-center text-primary">
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Metrics plugin isn't activated, therefore no information is available.
</p>
</div>
</div>
{% elif pre_render.get("status", False) and pre_render.get("status", False) == "ko" or "error" in pre_render.get("data", {}) or pre_render.get("data") is not mapping %}
<div class="d-flex align-items-center justify-content-center">
<div class="text-center text-primary">
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
(Pre rendering error) {{ pre_render.get('data', {'error': 'No log to show'}).get("error", "No log to show") }}
</p>
</div>
</div>
{% else %}
{% if plugin_page %}
{{ plugin_page|safe }}
{% elif pre_render.get("status", "ko") == "ok" and pre_render.get("data") is mapping and "error" not in pre_render.get("data", {}) %}
<div class="row g-4">
{% for key, value in pre_render.get("data", {}).items() %}
{% if key.startswith("ping_") %}
{% set up = value.get("value") in ('up', 'yes', 'success', 'true') %}
<div class="{{ value.get('col-size', 'col-12 col-md-4') }}">
<div class="card p-4 position-relative shadow-md rounded-3 {{ value.get('card-classes', '') }}">
<i class='bx bx-{% if up %}up{% else %}down{% endif %}-arrow-circle bx-sm position-absolute top-0 end-0 m-3 text-{% if up %}success{% else %}danger{% endif %}'></i>
<div class="card-header p-1">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">{{ value.get('title', 'STATUS') }}</h5>
</div>
</div>
<div class="card-body position-relative pb-0">
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center mt-2">
<p class="g-3 p-2 text-{% if up %}success{% else %}danger{% endif %} rounded-lg fw-bold">
{{ 'Active' if up else 'Inactive' }}
</p>
</div>
</div>
</div>
</div>
</div>
{% elif key.startswith(("info_", "date_", "count_", "counter_")) %}
<div class="{{ value.get('col-size', 'col-12 col-md-4') }}">
<div class="card p-4 position-relative shadow-md rounded-3 {{ value.get('card-classes', '') }}">
{% set svg_color = value.get("svg_color") %}
<i class='bx bx-{% if key.startswith("info_") %}info-circle{% elif key.startswith("date_") %}calendar{% else %}slider-alt{% endif %} bx-sm position-absolute top-0 end-0 m-3{% if svg_color %} text-{{ svg_color }}{% endif %}'></i>
<div class="card-header p-1">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">{{ value.get("title") }}</h5>
</div>
</div>
<div class="card-body position-relative">
{% if key.startswith("info_") %}
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center mt-2">
<p class="card-text mb-1">{{ value.get("value") }}</p>
{% set description = value.get("description") %}
{% if description %}<p class="card-text text-muted mb-0">{{ value.get("description") }}</p>{% endif %}
</div>
</div>
{% elif key.startswith("date_") %}
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center mt-2">
<p class="g-3 p-2 text-primary rounded-lg fw-bold date-field">{{ value.get("value") }}</p>
</div>
</div>
{% else %}
<p class="card-text mb-0">{{ human_readable_number(value.get("value") ) }}</p>
{% endif %}
{% set subtitle = value.get("subtitle") %}
{% if subtitle %}
<small class="position-absolute bottom-0 end-0 text-{{ value.get('subtitle_color', 'muted') }}">{{ subtitle }}</small>
{% endif %}
</div>
{% if key.startswith("date_") %}
<span class="position-absolute bottom-0 start-50 translate-middle badge rounded-pill bg-secondary">
TZ: <script nonce="{{ script_nonce }}">document.write(Intl.DateTimeFormat().resolvedOptions().timeZone);</script>
</span>
{% endif %}
</div>
</div>
{% elif key.startswith(("top_", "list_")) %}
<div class="{{ value.get('col-size', 'col-12 col-md-8') }}">
<div class="card p-4 position-relative shadow-md rounded-3 h-100 {{ value.get('card-classes', '') }}">
{% set svg_color = value.get("svg_color") %}
<i class='bx bx-list-{% if key.startswith("top_") %}ol{% else %}ul{% endif %} bx-sm position-absolute top-0 end-0 m-3{% if svg_color %} text-{{ svg_color }}{% endif %}'></i>
<div class="card-header p-1">
<div class="card-title mb-0">
<h5 class="mb-1 me-2 don-jose">{{ key.replace('_', ' ') }}</h5>
</div>
</div>
<div class="card-body p-0">
{% if value.get("data") %}
{% if key.startswith("top_") %}
{% set top_order = value.get("order", {"column": "0", "dir": "desc"}) %}
<div id="table-{{ key.replace('_', '-') }}-order" class="visually-hidden">{{ top_order|tojson }}</div>
{% endif %}
<div id="table-{{ key.replace('_', '-') }}-types" class="visually-hidden">{{ value.get('types', {}) |tojson }}</div>
<div class="card table-responsive text-nowrap">
<table id="table-{{ key.replace('_', '-') }}" class="table w-100 d-none">
{% set ns_list = namespace(size=0) %}
<thead>
<tr>
{% for val_key in value['data'] %}
<th>{{ val_key|trim }}</th>
{% set ns_list.size = value['data'][val_key]|length if value['data'][val_key]|length > ns_list.size else ns_list.size %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for n in range(ns_list.size) %}
<tr>
{% for val_key, items in value['data'].items() %}
<td>
{{ items[n]|trim if n < items|length else '' }}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<input type="hidden"
id="table-{{ key.replace('_', '-') }}-length"
value="{{ ns_list.size }}">
{% else %}
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center mt-2">
<p class="g-3 p-2 text-primary rounded-lg fw-bold">No data to show</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% else %}
<div class="d-flex align-items-center justify-content-center">
<div class="text-center text-primary">
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">
Plugin page not yet available during the beta phase.
</p>
</div>
</div>
{% endif %}
{% endif %}
<!--/ Content -->
{% endblock %}

View file

@ -5,7 +5,7 @@ from os import _exit, getenv
from os.path import join, sep
from pathlib import Path
from subprocess import PIPE, Popen, call
from typing import Dict, List, Optional, Set
from typing import Dict, List, Optional, Set, Union
from bcrypt import checkpw, gensalt, hashpw
from flask import flash as flask_flash, session
@ -281,7 +281,8 @@ def flash(message: str, category: str = "success", *, save: bool = True) -> None
session["flash_messages"].append((message, category, datetime.now().astimezone().isoformat()))
def human_readable_number(value):
def human_readable_number(value: Union[str, int]) -> str:
value = int(value)
if value >= 1_000_000:
return f"{value/1_000_000:.1f}M"
elif value >= 1_000: