Refactor datatable filtering options in web UI

This commit is contained in:
Théophile Diot 2024-10-07 14:50:00 +02:00
parent 756af7becb
commit c73e9bf161
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
18 changed files with 304 additions and 28 deletions

View file

@ -457,6 +457,12 @@ def upload_plugin():
return {"status": "ok"}, 201
@plugins.route("/plugins/<string:plugin>", methods=["GET"])
@login_required
def custom_plugin_page(plugin: str):
return render_template("plugin_page.html")
# @plugins.route("/plugins/<plugin>", methods=["GET", "POST"])
# @login_required
# def custom_plugin(plugin: str):

View file

@ -1,11 +1,88 @@
from flask import Blueprint, render_template
from datetime import datetime
from threading import Thread
from time import time
from typing import Dict
from flask import Blueprint, redirect, render_template, request, url_for
from flask_login import login_required
from app.dependencies import BW_CONFIG, DATA, DB
from app.routes.utils import get_remain, handle_error, manage_bunkerweb, verify_data_in_form, wait_applying
from app.utils import LOGGER, flash
pro = Blueprint("pro", __name__)
@pro.route("/pro")
@pro.route("/pro", methods=["GET"])
@login_required
def pro_page():
return render_template("pro.html")
online_services = 0
draft_services = 0
for service in DB.get_services(with_drafts=True):
if service["is_draft"]:
draft_services += 1
continue
online_services += 1
metadata = DB.get_metadata()
current_day = datetime.now().astimezone().replace(hour=0, minute=0, second=0, microsecond=0)
pro_expires_in = "Unknown"
if metadata["pro_expire"]:
exp = (metadata["pro_expire"].astimezone() - current_day).total_seconds()
remain = ("Unknown", "Unknown") if exp <= 0 else get_remain(exp)
pro_expires_in = remain[0]
return render_template(
"pro.html",
online_services=online_services,
draft_services=draft_services,
pro_expires_in=pro_expires_in,
)
@pro.route("/pro/key", methods=["POST"])
@login_required
def pro_key():
if DB.readonly:
return handle_error("Database is in read-only mode", "pro")
verify_data_in_form(
data={"PRO_LICENSE_KEY": None},
err_message="Missing license key parameter on /pro/key.",
redirect_url="pro",
next=True,
)
license_key = request.form["PRO_LICENSE_KEY"]
if not license_key:
return handle_error("Invalid license key", "pro")
global_config = DB.get_config(global_only=True, methods=True, filtered_settings=("PRO_LICENSE_KEY"))
variables = BW_CONFIG.check_variables({"PRO_LICENSE_KEY": license_key}, global_config, global_config=True)
if not variables:
flash("The license key is the same as the current one.", "warning")
return redirect(url_for("pro.pro_page"))
DATA.load_from_file()
def update_license_key(license_key: str):
wait_applying()
manage_bunkerweb("global_config", {"PRO_LICENSE_KEY": license_key}, threaded=True)
DATA.update(
{
"RELOADING": True,
"LAST_RELOAD": time(),
"CONFIG_CHANGED": True,
"PRO_LOADING": True,
}
)
flash("Checking license key.")
Thread(target=update_license_key, args=(license_key,)).start()
return redirect(
url_for(
"loading",
next=url_for("pro.pro_page"),
message="Updating license key",
)
)

View file

@ -747,7 +747,8 @@ a.text-white-80:hover {
padding-bottom: 0.625rem !important;
}
.footer-container .footer-link:hover {
.footer-container .footer-link:hover,
a.courier-prime:hover {
font-style: italic;
text-decoration: underline;
}

View file

@ -41,7 +41,7 @@ $(document).ready(function () {
}
// Select the Flatpickr input elements
const flatpickrDatetime = $("[type='flapickr-datetime']");
const flatpickrDatetime = $("[type='flatpickr-datetime']");
// Get the current date and times
const currentDatetime = new Date();

View file

@ -0,0 +1,9 @@
$(document).ready(function () {
// Select the Flatpickr input elements
const $flatpickrDate = $("#flatpickr-date");
// Initialize Flatpickr with altInput and altFormat
$flatpickrDate.flatpickr({
inline: true,
});
});

View file

@ -118,7 +118,7 @@
required />
</div>
<div class="col-5 border-start">
<input type="flapickr-datetime"
<input type="flatpickr-datetime"
name="datetime"
class="form-control"
required />

View file

@ -57,7 +57,7 @@
href="{{ url_for('static', filename='libs/datatables/datatables.min.css') }}"
nonce="{{ style_nonce }}" />
{% endif %}
{% if current_endpoint == "bans" or current_endpoint != "plugins" and "plugins" in request.path %}
{% if current_endpoint in ("bans", "pro") or current_endpoint != "plugins" and "plugins" in request.path %}
<!-- Flatpickr -->
<link rel="stylesheet"
href="{{ url_for('static', filename='libs/flatpickr/flatpickr.min.css') }}"
@ -147,7 +147,7 @@
<script src="{{ url_for('static', filename='libs/leaflet/leaflet.min.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
{% if current_endpoint == "bans" or current_endpoint != "plugins" and "plugins" in request.path %}
{% if current_endpoint in ("bans", "pro") or current_endpoint != "plugins" and "plugins" in request.path %}
<script src="{{ url_for('static', filename='libs/flatpickr/flatpickr.min.js') }}"
nonce="{{ script_nonce }}"></script>
<script src="{{ url_for('static', filename='libs/flatpickr/plugins/minMaxTimePlugin.js') }}"
@ -227,6 +227,9 @@
{% elif current_endpoint == "logs" %}
<script src="{{ url_for('static', filename='js/pages/logs.js') }}"
nonce="{{ script_nonce }}"></script>
{% elif current_endpoint == "pro" %}
<script src="{{ url_for('static', filename='js/pages/pro.js') }}"
nonce="{{ script_nonce }}"></script>
{% endif %}
<script async defer src="{{ url_for('static', filename='js/buttons.js') }}"></script>
</body>

View file

@ -3,12 +3,14 @@
<!-- Content -->
<div class="card table-responsive text-nowrap p-4 pb-8 min-vh-70">
<input type="hidden" id="cache_number" value="{{ caches|length }}" />
<input type="hidden" id="services" value="{{ services }}" />
<input type="hidden"
id="services"
value="{{ services }}" />
<input type="hidden" id="cache_service_selection" value="{{ cache_service }}" />
id="cache_service_selection"
value="{{ cache_service }}" />
<input type="hidden" id="cache_plugin_selection" value="{{ cache_plugin }}" />
<input type="hidden" id="cache_job_name_selection" value="{{ cache_job_name }}" />
<input type="hidden"
id="cache_job_name_selection"
value="{{ cache_job_name }}" />
<input type="hidden"
id="csrf_token"
name="csrf_token"

View file

@ -3,13 +3,11 @@
<!-- Content -->
<div class="card table-responsive text-nowrap p-4 min-vh-70">
<input type="hidden" id="configs_number" value="{{ configs|length }}" />
<input type="hidden" id="services" value="{{ services }}" />
<input type="hidden" id="templates" value="{{ db_templates }}" />
<input type="hidden"
id="services"
value="{{ services }}" />
<input type="hidden"
id="templates"
value="{{ db_templates }}" />
<input type="hidden" id="configs_service_selection" value="{{ config_service }}" />
id="configs_service_selection"
value="{{ config_service }}" />
<input type="hidden" id="configs_type_selection" value="{{ config_type }}" />
<input type="hidden"
id="csrf_token"

View file

@ -179,7 +179,7 @@
width="18px"
height="15.5px">
</span>
Upgrade to Pro</a>
Upgrade to PRO</a>
</div>
{% endif %}
<div class="content-backdrop fade"></div>

View file

@ -2,7 +2,7 @@
class="toast-container position-fixed bottom-0 end-0 mb-3 me-3">
<!-- prettier-ignore -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if pro_overlapped %}
{% if pro_overlapped and current_endpoint != "pro" %}
{% if messages.append(('error', 'You have more services than allowed by your pro license. Upgrade your license or move some services to draft mode to unlock your pro license.')) %}
{% endif %}
{% endif %}

View file

@ -3,9 +3,16 @@
<!-- Content -->
<div class="row g-4">
<div class="col-sm-3 mb-2">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100{% if not is_pro_version %} bg-bw-green text-white{% endif %}">
<i class='bx bxs-rocket bx-sm position-absolute top-0 end-0 m-3'></i>
<p class="ps-4 fs-4 mb-2{% if is_pro_version %} text-primary shine{% else %} text-white{% endif %}">
<div class="card p-4 position-relative shadow-sm rounded-3 h-100 text-white {% if is_pro_version %}bg-primary{% else %}bg-bw-green{% endif %}">
{% if is_pro_version %}
<img class='position-absolute top-0 end-0 m-3'
src="{{ pro_diamond_url }}"
alt="Pro plugin"
width="24px">
{% else %}
<i class='bx bxs-rocket bx-sm position-absolute top-0 end-0 m-3'></i>
{% endif %}
<p class="ps-4 fs-4 mb-2 text-white">
Plan
<br />
<span class="fs-3 fw-bold don-jose">
@ -16,8 +23,13 @@
{% endif %}
</span>
</p>
<a class="ps-4 text-underline{% if not is_pro_version %} text-white-80{% endif %}"
href="{{ url_for('pro') }}">Upgrade?</a>
<a class="ps-4 text-underline text-white-80" href="{{ url_for('pro') }}">
{% if is_pro_version %}
More info
{% else %}
Upgrade?
{% endif %}
</a>
</div>
</div>
<div class="col-sm-3 mb-2">

View file

@ -79,7 +79,7 @@
<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 %}"
<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>

View file

@ -29,6 +29,8 @@
{% elif current_endpoint == 'logs' %}
{% set documentation_endpoint = "/web-ui" %}
{% set documentation_fragment = "#accessing-logs" %}
{% elif current_endpoint == 'pro' %}
{% set documentation_endpoint = "/professional-services" %}
{% endif %}
<div class="navbar-nav align-items-center">
<div class="nav-item d-flex align-items-center">

View file

@ -0,0 +1,13 @@
{% extends "dashboard.html" %}
{% block content %}
<!-- Content -->
<div class="card text-nowrap p-4 min-vh-70">
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center text-primary">
<p id="config-waiting"
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Coming soon...</p>
</div>
</div>
</div>
<!--/ Content -->
{% endblock %}

View file

@ -71,7 +71,7 @@
{% endif %}
>
{% if plugin_data["type"] == "pro" %}
<a href="{{ url_for("pro") }}" target="_blank" rel="noopener" {% else %}
<a href="{{ url_for('pro') }}" target="_blank" rel="noopener" {% else %}
<div {% endif %}
class="{{ plugin_types[plugin_data['type']].get('text-class', '') }}">
{{ plugin_types[plugin_data["type"]].get('icon', '<img src="' + pro_diamond_url + '"

View file

@ -1,5 +1,158 @@
{% extends "dashboard.html" %}
{% block content %}
<!-- Content -->
{% set is_overlapped = pro_status == "active" and pro_overlapped %}
{% set is_expired = pro_status == "expired" %}
{% set is_suspended = pro_status == "suspended" %}
<div class="row g-3">
<div class="col-12 col-md-6 col-xl-4">
<div class="card p-4 position-relative h-100">
<img class='position-absolute top-0 end-0 m-3'
src="{{ pro_diamond_url if is_pro_version else url_for('static', filename='img/diamond-blue.svg') }}"
alt="Pro plugin"
width="24px">
<div class="card-header p-2">
<div class="card-title">
<h5 class="me-2 text-primary shine don-jose mb-0">Pro status</h5>
</div>
</div>
<div class="card-body position-relative">
<div class="d-flex align-items-center justify-content-center h-100">
<div class="text-center text-primary">
<h4 class="mt-0 mb-2 fw-semibold{% if is_pro_version %} text-primary shine courier-prime{% endif %}">
{% if is_pro_version %}
PRO version
{% else %}
{% if is_overlapped %}
PRO version / <span class="text-danger fw-bold">OVERLAPPED</span>
{% elif pro_status == "expired" %}
PRO version / <span class="text-warning fw-bold">EXPIRED</span>
{% elif pro_status == "suspended" %}
PRO version / <span class="text-danger fw-bold">SUSPENDED</span>
{% else %}
FREE version
{% endif %}
{% endif %}
</h4>
<h5 class="mt-2 mb-2 courier-prime fw-medium{% if is_overlapped %} text-decoration-line-through{% endif %}">
{{ online_services }} Online service{{ 's' if online_services > 1 else '' }} / {{ pro_services }} PRO service{{ 's' if pro_services > 1 else '' }}
</h5>
{% if is_overlapped %}
<div class="mt-2 mb-2 alert alert-danger text-center" role="alert">
You have more services than your license allows !
<br />
Delete or draft some services to reach the number of {{ pro_services }} services.
</div>
{% endif %}
<a href="https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro"
class="fs-6 mt-2 p-2 courier-prime fst-italic text-primary shine">
<i class="bx bx-link-external"></i>
{% if is_pro_version %}
All features available
{% else %}
{% if is_overlapped %}
Awaiting compliance...
{% elif pro_status == "expired" %}
Renew license
{% elif pro_status == "suspended" %}
Suspended license
{% else %}
Upgrade to PRO
{% endif %}
{% endif %}
</a>
</div>
</div>
<small class="position-absolute bottom-0 end-0 text-muted">
{{ draft_services }} Draft service{{ 's' if draft_services > 1 else '' }}
</small>
</div>
</div>
</div>
<div class="col-12 col-xl-4">
<div class="card p-4 position-relative h-100">
<i class='bx bx-calendar bx-sm position-absolute top-0 end-0 m-3 {% if is_expired %}text-warning{% else %}text-primary{% endif %}'></i>
<div class="card-header p-2">
<div class="card-title">
<h5 class="me-2 text-primary shine don-jose mb-0">Pro expiration date</h5>
</div>
</div>
<div class="card-body position-relative">
<div class="d-flex align-items-center justify-content-center h-100 pb-2">
<input id="expiration-date" type="hidden" value="{{ pro_expire }}" />
<input id="flatpickr-date"
type="hidden"
name="date"
class="visually-hidden"
value="{{ pro_expire }}" />
</div>
<small class="position-absolute bottom-0 end-0 text-primary">
{% if pro_expires_in != "Unknown" %}
Expires in: {{ pro_expires_in }}
{% else %}
Unknown expiration date
{% endif %}
</small>
</div>
</div>
</div>
<div class="col-12 row g-3 m-0 p-0">
<div class="col-12 col-md-6 col-xl-4">
<div class="card p-4 position-relative">
<i class='bx bx-key bx-sm position-absolute top-0 end-0 m-3 {% if is_expired %}text-warning{% else %}text-primary{% endif %}'></i>
<div class="card-header p-2">
<div class="card-title">
<h5 class="me-2 text-primary shine don-jose mb-0">
{% if is_pro_version %}
Update
{% else %}
Activate
{% endif %}
key
</h5>
</div>
</div>
<div class="card-body position-relative">
<form method="POST" action="{{ url_for("pro") }}/key">
<input type="hidden"
id="csrf_token"
name="csrf_token"
value="{{ csrf_token() }}" />
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
class="form-label fw-semibold text-truncate">License key</label>
<div class="d-flex align-items-center">
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Your BunkerWeb PRO license key">
<span class="bx bx-question-mark bx-xs"></span>
</span>
</div>
</div>
{% set setting = "PRO_LICENSE_KEY" %}
{% set setting_data = {"type": "password", "id": "license_key", "regex": "^.+$"} %}
{% set required = true %}
{% if is_readonly %}
{% set disabled = true %}
{% set setting_method = "readonly" %}
{% endif %}
{% include "models/input_setting.html" %}
<button type="submit"
class="btn btn-primary w-100 text-uppercase don-jose mt-3">
{% if is_pro_version %}
Update
{% else %}
Activate
{% endif %}
key
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!--/ Content -->
{% endblock %}

View file

@ -170,7 +170,7 @@ def inject_variables():
is_pro_version=metadata["is_pro"],
pro_status=metadata["pro_status"],
pro_services=metadata["pro_services"],
pro_expire=metadata["pro_expire"].strftime("%d-%m-%Y") if metadata["pro_expire"] else "Unknown",
pro_expire=metadata["pro_expire"].strftime("%Y/%m/%d") if metadata["pro_expire"] else "Unknown",
pro_overlapped=metadata["pro_overlapped"],
plugins=BW_CONFIG.get_plugins(),
flash_messages=session.get("flash_messages", []),