From 9c67ee143c9c58ca509c15f396acf4c7b31122fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Diot?= Date: Thu, 24 Oct 2024 18:00:17 +0200 Subject: [PATCH] Start migrating core plugins' pages to the new format --- src/common/core/antibot/ui/actions.py | 34 ++-- src/common/core/antibot/ui/template.html | 140 --------------- src/common/core/backup/ui/actions.py | 42 ++++- src/common/core/backup/ui/template.html | 89 --------- src/common/core/badbehavior/ui/actions.py | 38 +++- src/common/core/badbehavior/ui/template.html | 144 --------------- src/common/core/blacklist/ui/actions.py | 60 +++++-- src/common/core/blacklist/ui/template.html | 144 --------------- src/common/core/bunkernet/ui/actions.py | 48 ++++- src/common/core/bunkernet/ui/template.html | 144 --------------- src/ui/app/routes/plugins.py | 67 +++---- src/ui/app/static/css/overrides.css | 64 +++++++ src/ui/app/static/js/pages/plugin_page.js | 85 +++++++++ src/ui/app/templates/base.html | 3 + src/ui/app/templates/menu.html | 75 ++++---- src/ui/app/templates/plugin_page.html | 179 ++++++++++++++++--- src/ui/app/utils.py | 5 +- 17 files changed, 561 insertions(+), 800 deletions(-) delete mode 100644 src/common/core/antibot/ui/template.html delete mode 100644 src/common/core/backup/ui/template.html delete mode 100644 src/common/core/badbehavior/ui/template.html delete mode 100644 src/common/core/blacklist/ui/template.html delete mode 100644 src/common/core/bunkernet/ui/template.html create mode 100644 src/ui/app/static/js/pages/plugin_page.js diff --git a/src/common/core/antibot/ui/actions.py b/src/common/core/antibot/ui/actions.py index e66d8d771..1bba4dd33 100644 --- a/src/common/core/antibot/ui/actions.py +++ b/src/common/core/antibot/ui/actions.py @@ -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): diff --git a/src/common/core/antibot/ui/template.html b/src/common/core/antibot/ui/template.html deleted file mode 100644 index 89f45b555..000000000 --- a/src/common/core/antibot/ui/template.html +++ /dev/null @@ -1,140 +0,0 @@ -{% extends "base.html" %} - -{% set read_doc_text = 'You will find more information about the antibot plugin in the documentation.' %} - -{% block content %} - -
- {% if is_used and is_metrics %} -
-
INFO
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% 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 %}
-
-
- - - -
-

(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}

-
- {% 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_") %} -
-
-
{{ pre_render['data'][key].get('title', 'STATUS')}}
- - - -
-

{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}

-
- {% endif %} - - - {% if key.startswith("count_") or key.startswith("counter_") %} -
- -
-

{{pre_render['data'][key].get("title")}}

-
{{pre_render['data'][key].get("value")}}
-

- {{pre_render['data'][key].get("subtitle")}} -

-
- - - - -
- {% endif %} - - - {% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %} -
-
-
{{ key.replace('_', ' ').upper()}}
-
-
- -
- - {% for val_key, val_value in pre_render['data'][key][0].items() %} - - -

{{ val_key }}

- {% endfor%} - - -
    - {% for item in pre_render['data'][key] %} -
  • - {% for top_key, top_value in item.items() %} -

    {{ top_value }}

    - {% endfor %} -
  • - {% endfor %} -
- - -
- -
-
- {% endif %} - - - {% endfor %} - {% endif %} - {% else %} -
-
-
Plugin deactivated
- - - -
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% endif %} -
-{% endblock %} diff --git a/src/common/core/backup/ui/actions.py b/src/common/core/backup/ui/actions.py index 37f03fafb..698789acc 100644 --- a/src/common/core/backup/ui/actions.py +++ b/src/common/core/backup/ui/actions.py @@ -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): diff --git a/src/common/core/backup/ui/template.html b/src/common/core/backup/ui/template.html deleted file mode 100644 index ac2a7c2cd..000000000 --- a/src/common/core/backup/ui/template.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends "base.html" %} - -{% set read_doc_text = 'You will find more information about the backup plugin in the documentation.' %} - -{% block content %} - -
- {% if is_used %} -
-
INFO
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% 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 %}
-
-
- - - -
-

(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}

-
- {% 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", {}) %} -
-
-
LAST BACKUP
-
-
-

{{ pre_render["data"].get("date", "No backup found") }}

-
-
- {% if pre_render['data'].get("files", [])|length > 0 %} -
-
-
-
BACKUP FILES
-
-
- -
- -
    - {% for item in pre_render['data']["files"] %} -
  • -

    {{ item }}

    -
  • - {% endfor %} -
- -
- -
-
- {% endif %} - {% endif %} - {% else %} -
-
-
Plugin deactivated
- - - -
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% endif %} -
-{% endblock %} diff --git a/src/common/core/badbehavior/ui/actions.py b/src/common/core/badbehavior/ui/actions.py index aa27c5eb6..bf64cf8fa 100644 --- a/src/common/core/badbehavior/ui/actions.py +++ b/src/common/core/badbehavior/ui/actions.py @@ -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): diff --git a/src/common/core/badbehavior/ui/template.html b/src/common/core/badbehavior/ui/template.html deleted file mode 100644 index 7d9c6dfe0..000000000 --- a/src/common/core/badbehavior/ui/template.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends "base.html" %} - -{% set read_doc_text = 'You will find more information about the bad behavior plugin in the documentation.' %} - -{% block content %} - -
- {% if is_used and is_metrics %} -
-
INFO
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - - {% 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 %}
-
-
- - - -
-

(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}

-
- {% 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_") %} -
-
-
{{ pre_render['data'][key].get('title', 'STATUS')}}
- - - -
-

{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}

-
- {% endif %} - - - {% if key.startswith("count_") or key.startswith("counter_") %} -
- -
-

{{pre_render['data'][key].get("title")}}

-
{{pre_render['data'][key].get("value")}}
-

- {{pre_render['data'][key].get("subtitle")}} -

-
- - - - -
- {% endif %} - - - {% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %} -
-
-
{{ key.replace('_', ' ').upper()}}
-
-
- -
- - {% for val_key, val_value in pre_render['data'][key][0].items() %} - - -

{{ val_key }}

- {% endfor%} - - -
    - {% for item in pre_render['data'][key] %} -
  • - {% for top_key, top_value in item.items() %} -

    {{ top_value }}

    - {% endfor %} -
  • - {% endfor %} -
- - -
- -
-
- {% endif %} - - - {% endfor %} - {% endif %} - {% else %} -
-
-
Plugin deactivated
- - - -
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% endif %} -
-{% endblock %} diff --git a/src/common/core/blacklist/ui/actions.py b/src/common/core/blacklist/ui/actions.py index 11857a380..6a4109d52 100644 --- a/src/common/core/blacklist/ui/actions.py +++ b/src/common/core/blacklist/ui/actions.py @@ -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): diff --git a/src/common/core/blacklist/ui/template.html b/src/common/core/blacklist/ui/template.html deleted file mode 100644 index 9da4445c2..000000000 --- a/src/common/core/blacklist/ui/template.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends "base.html" %} - -{% set read_doc_text = 'You will find more information about the blacklist plugin in the documentation.' %} - -{% block content %} - -
- {% if is_used and is_metrics %} -
-
INFO
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - - {% 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 %}
-
-
- - - -
-

(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}

-
- {% 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_") %} -
-
-
{{ pre_render['data'][key].get('title', 'STATUS')}}
- - - -
-

{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}

-
- {% endif %} - - - {% if key.startswith("count_") or key.startswith("counter_") %} -
- -
-

{{pre_render['data'][key].get("title")}}

-
{{pre_render['data'][key].get("value")}}
-

- {{pre_render['data'][key].get("subtitle")}} -

-
- - - - -
- {% endif %} - - - {% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %} -
-
-
{{ key.replace('_', ' ').upper()}}
-
-
- -
- - {% for val_key, val_value in pre_render['data'][key][0].items() %} - - -

{{ val_key }}

- {% endfor%} - - -
    - {% for item in pre_render['data'][key] %} -
  • - {% for top_key, top_value in item.items() %} -

    {{ top_value }}

    - {% endfor %} -
  • - {% endfor %} -
- - -
- -
-
- {% endif %} - - - {% endfor %} - {% endif %} - {% else %} -
-
-
Plugin deactivated
- - - -
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% endif %} -
-{% endblock %} diff --git a/src/common/core/bunkernet/ui/actions.py b/src/common/core/bunkernet/ui/actions.py index a956b83d7..13983ef03 100644 --- a/src/common/core/bunkernet/ui/actions.py +++ b/src/common/core/bunkernet/ui/actions.py @@ -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): diff --git a/src/common/core/bunkernet/ui/template.html b/src/common/core/bunkernet/ui/template.html deleted file mode 100644 index efc73407c..000000000 --- a/src/common/core/bunkernet/ui/template.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends "base.html" %} - -{% set read_doc_text = 'You will find more information about the bunkernet plugin in the documentation.' %} - -{% block content %} - -
- {% if is_used %} -
-
INFO
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - - {% 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 %}
-
-
- - - -
-

(Pre rendering error) {{ pre_render.get("data", { "error" : "No log to show" }).get("error", "No log to show") }}

-
- {% 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_") %} -
-
-
{{ pre_render['data'][key].get('title', 'STATUS')}}
- - - -
-

{{ 'Active' if pre_render['data'][key].get('value') in ('up', 'yes', 'success', 'true') else 'Inactive' }}

-
- {% endif %} - - - {% if key.startswith("count_") or key.startswith("counter_") %} -
- -
-

{{pre_render['data'][key].get("title")}}

-
{{pre_render['data'][key].get("value")}}
-

- {{pre_render['data'][key].get("subtitle")}} -

-
- - - - -
- {% endif %} - - - {% if (key.startswith("top_") and pre_render['data'][key]|length > 0) or (key.startswith("list_") and pre_render['data'][key]|length > 0) %} -
-
-
{{ key.replace('_', ' ').upper()}}
-
-
- -
- - {% for val_key, val_value in pre_render['data'][key][0].items() %} - - -

{{ val_key }}

- {% endfor%} - - -
    - {% for item in pre_render['data'][key] %} -
  • - {% for top_key, top_value in item.items() %} -

    {{ top_value }}

    - {% endfor %} -
  • - {% endfor %} -
- - -
- -
-
- {% endif %} - - - {% endfor %} - {% endif %} - {% else %} -
-
-
Plugin deactivated
- - - -
-
-

{{plugin.get('description')}}

-
-

{{ read_doc_text|safe }}

-
- - {% endif %} -
-{% endblock %} diff --git a/src/ui/app/routes/plugins.py b/src/ui/app/routes/plugins.py index 3054f3909..92cb484c9 100644 --- a/src/ui/app/routes/plugins.py +++ b/src/ui/app/routes/plugins.py @@ -2,7 +2,7 @@ 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 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 plugin_used(prefix: str = "") -> bool: 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 plugin_used(prefix: str = "") -> bool: 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 - - # 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) - - # tmp_page_dir = tmp_page_dir.joinpath("ui") - - # 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'' + 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) + + 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") + + LOGGER.debug(f"Plugin {plugin} page extracted successfully") + + 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'' return render_template( "plugin_page.html", @@ -568,6 +570,7 @@ def plugin_used(prefix: str = "") -> bool: plugin=plugin_data, is_used=is_used, is_metrics=is_metrics_on, + pre_render=pre_render, ) diff --git a/src/ui/app/static/css/overrides.css b/src/ui/app/static/css/overrides.css index a36112af3..aeea4ce61 100644 --- a/src/ui/app/static/css/overrides.css +++ b/src/ui/app/static/css/overrides.css @@ -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; +} diff --git a/src/ui/app/static/js/pages/plugin_page.js b/src/ui/app/static/js/pages/plugin_page.js new file mode 100644 index 000000000..92a74af66 --- /dev/null +++ b/src/ui/app/static/js/pages/plugin_page.js @@ -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"); +}); diff --git a/src/ui/app/templates/base.html b/src/ui/app/templates/base.html index dc770896d..2bd93c652 100644 --- a/src/ui/app/templates/base.html +++ b/src/ui/app/templates/base.html @@ -230,6 +230,9 @@ {% elif current_endpoint == "pro" %} + {% elif current_endpoint != "plugins" and "plugins" in request.path %} + {% endif %} diff --git a/src/ui/app/templates/menu.html b/src/ui/app/templates/menu.html index 5204a86b8..673f452aa 100644 --- a/src/ui/app/templates/menu.html +++ b/src/ui/app/templates/menu.html @@ -31,9 +31,9 @@ } %}