mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Start migrating core plugins' pages to the new format
This commit is contained in:
parent
3058a629bf
commit
9c67ee143c
17 changed files with 557 additions and 796 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
85
src/ui/app/static/js/pages/plugin_page.js
Normal file
85
src/ui/app/static/js/pages/plugin_page.js
Normal 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");
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue