From 68212ac0eef3bc5edee7bd6d2ea0316e7e3aedad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Diot?= Date: Thu, 29 Aug 2024 16:54:11 +0200 Subject: [PATCH] Finished profile page + Start working on instances page in web UI --- src/ui/app/models/ui_database.py | 12 +- src/ui/app/routes/instances.py | 104 ++--- src/ui/app/routes/profile.py | 112 +++-- src/ui/app/routes/setup.py | 6 +- src/ui/app/routes/utils.py | 12 + src/ui/app/static/css/pages/profile.css | 21 + src/ui/app/static/js/main.js | 166 -------- src/ui/app/static/js/pages/instances.js | 29 ++ src/ui/app/static/js/pages/profile.js | 398 +++++++++++++----- src/ui/app/static/js/sidebar.js | 167 ++++++++ .../static/libs/datatables/datatables.min.css | 44 ++ .../static/libs/datatables/datatables.min.js | 219 ++++++++++ src/ui/app/templates/base.html | 13 + src/ui/app/templates/instances.html | 74 ++++ src/ui/app/templates/navbar.html | 2 +- src/ui/app/templates/profile.html | 279 ++++++------ src/ui/app/templates/totp.html | 2 +- src/ui/gunicorn.conf.py | 16 +- src/ui/main.py | 19 +- src/ui/requirements.in | 2 +- src/ui/requirements.txt | 151 +++++++ 21 files changed, 1305 insertions(+), 543 deletions(-) create mode 100644 src/ui/app/static/css/pages/profile.css create mode 100644 src/ui/app/static/js/pages/instances.js create mode 100644 src/ui/app/static/js/sidebar.js create mode 100644 src/ui/app/static/libs/datatables/datatables.min.css create mode 100644 src/ui/app/static/libs/datatables/datatables.min.js create mode 100644 src/ui/app/templates/instances.html diff --git a/src/ui/app/models/ui_database.py b/src/ui/app/models/ui_database.py index 8a0c92c23..6e1dca580 100644 --- a/src/ui/app/models/ui_database.py +++ b/src/ui/app/models/ui_database.py @@ -479,10 +479,18 @@ class UIDatabase(Database): return "" - def get_ui_user_sessions(self, username: str) -> List[UserSessions]: + def get_ui_user_sessions(self, username: str, current_session_id: Optional[str] = None) -> List[UserSessions]: """Get ui user sessions.""" with self._db_session() as session: - return session.query(UserSessions).filter_by(user_name=username).order_by(UserSessions.creation_date.desc()).all() + if current_session_id: + return ( + session.query(UserSessions) + .filter_by(user_name=username) + .order_by(UserSessions.id == current_session_id, UserSessions.creation_date.desc()) + .all() + ) + else: + return session.query(UserSessions).filter_by(user_name=username).order_by(UserSessions.creation_date.desc()).all() def delete_ui_user_old_sessions(self, username: str) -> str: """Delete ui user old sessions.""" diff --git a/src/ui/app/routes/instances.py b/src/ui/app/routes/instances.py index 0f8a488fc..924813e03 100644 --- a/src/ui/app/routes/instances.py +++ b/src/ui/app/routes/instances.py @@ -15,42 +15,12 @@ instances = Blueprint("instances", __name__) @instances.route("/instances", methods=["GET"]) @login_required def instances_page(): - instances = [] - instances_types = set() - instances_methods = set() - instances_healths = set() - - for instance in BW_INSTANCES_UTILS.get_instances(): - instances.append( - { - "hostname": instance.hostname, - "name": instance.name, - "method": instance.method, - "health": instance.status, - "type": instance.type, - "creation_date": instance.creation_date.strftime("%Y-%m-%d at %H:%M:%S %Z"), - "last_seen": instance.last_seen.strftime("%Y-%m-%d at %H:%M:%S %Z"), - } - ) - - instances_types.add(instance.type) - instances_methods.add(instance.method) - instances_healths.add(instance.status) - - # builder = instances_builder(instances, list(instances_types), list(instances_methods), list(instances_healths)) - # return render_template("instances.html", title="Instances", data_server_builder=b64encode(dumps(builder).encode("utf-8")).decode("ascii")) - return render_template("instances.html") # TODO + return render_template("instances.html", instances=BW_INSTANCES_UTILS.get_instances()) @instances.route("/instances/new", methods=["PUT"]) @login_required def instances_new(): - verify_data_in_form( - data={"csrf_token": None}, - err_message="Missing csrf_token parameter on /instances/new.", - redirect_url="instances", - next=True, - ) verify_data_in_form( data={"instance_hostname": None}, err_message="Missing instance hostname parameter on /instances/new.", @@ -85,57 +55,41 @@ def instances_new(): return redirect(url_for("loading", next=url_for("instances.instances_page"), message=f"Creating new instance {instance['hostname']}")) -@instances.route("/instances/", methods=["DELETE"]) +@instances.route("/instances//", methods=["POST"]) @login_required -def instances_delete(instance_hostname: str): - verify_data_in_form( - data={"csrf_token": None}, - err_message="Missing csrf_token parameter on /instances/delete.", - redirect_url="instances", - next=True, - ) +def instances_action(instance_hostname: str, action: Literal["ping", "reload", "stop", "delete"]): # TODO: see if we can support start and restart + if action == "delete": + delete_instance = None + for instance in BW_INSTANCES_UTILS.get_instances(): + if instance.hostname == instance_hostname: + delete_instance = instance + break - delete_instance = None - for instance in BW_INSTANCES_UTILS.get_instances(): - if instance.hostname == instance_hostname: - delete_instance = instance - break + if not delete_instance: + return handle_error(f"Instance {instance_hostname} not found.", "instances", True) + if delete_instance.method != "ui": + return handle_error(f"Instance {instance_hostname} is not a UI instance.", "instances", True) - if not delete_instance: - return handle_error(f"Instance {instance_hostname} not found.", "instances", True) - if delete_instance.method != "ui": - return handle_error(f"Instance {instance_hostname} is not a UI instance.", "instances", True) - - ret = DB.delete_instance(instance_hostname) - if ret: - return handle_error(f"Couldn't delete the instance in the database: {ret}", "instances", True) - - return redirect(url_for("loading", next=url_for("instances.instances_page"), message=f"Deleting instance {instance_hostname}")) - - -@instances.route("/instances/", methods=["POST"]) -@login_required -def instances_action(action: Literal["ping", "reload", "stop"]): # TODO: see if we can support start and restart - verify_data_in_form( - data={"instance_hostname": None, "csrf_token": None}, - err_message="Missing instance hostname parameter on /instances/reload.", - redirect_url="instances", - next=True, - ) - - DATA["RELOADING"] = True - DATA["LAST_RELOAD"] = time() - Thread( - target=manage_bunkerweb, - name=f"Reloading instance {request.form['instance_hostname']}", - args=("instances", request.form["instance_hostname"]), - kwargs={"operation": action, "threaded": True}, - ).start() + ret = DB.delete_instance(instance_hostname) + if ret: + return handle_error(f"Couldn't delete the instance in the database: {ret}", "instances", True) + else: + DATA["RELOADING"] = True + DATA["LAST_RELOAD"] = time() + Thread( + target=manage_bunkerweb, + args=("instances", instance_hostname), + kwargs={"operation": action, "threaded": True}, + ).start() return redirect( url_for( "loading", next=url_for("instances.instances_page"), - message=(f"{action.title()}ing" if action != "stop" else "Stopping") + " instance", + message=( + f"{action.title()}ing" + if action not in ("delete", "stop") + else ("Deleting" if action == "delete" else "Stopping") + f" instance {instance_hostname}" + ), ) ) diff --git a/src/ui/app/routes/profile.py b/src/ui/app/routes/profile.py index 6a220dd39..73499131d 100644 --- a/src/ui/app/routes/profile.py +++ b/src/ui/app/routes/profile.py @@ -1,13 +1,14 @@ -from flask import Blueprint, flash, redirect, render_template, request, url_for, session +from typing import Dict, Generator, Tuple, Union +from flask import Blueprint, Response, flash, jsonify, redirect, render_template, request, stream_with_context, url_for, session from flask_login import current_user, login_required, logout_user from user_agents import parse from app.models.totp import totp as TOTP -from app.dependencies import DB +from app.dependencies import DATA, DB from app.utils import USER_PASSWORD_RX, gen_password_hash -from app.routes.utils import handle_error, verify_data_in_form +from app.routes.utils import cors_required, handle_error, verify_data_in_form profile = Blueprint("profile", __name__) @@ -31,6 +32,46 @@ DEVICES = { } +def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Union[str, bool]], None, None], int]: + db_sessions = DB.get_ui_user_sessions(current_user.username, session.get("session_id")) + total_sessions = len(db_sessions) + + if total_sessions <= per_page: + per_page = total_sessions + page = 1 + elif total_sessions <= (page - 1) * per_page: + page = total_sessions // per_page + + def session_generator(): + for db_session in db_sessions[(page - 1) * per_page : page * per_page]: # noqa: E203 + ua_data = parse(db_session.user_agent) + last_session = { + "current": db_session.id == session.get("session_id"), + "browser": ua_data.get_browser(), + "os": ua_data.get_os(), + "device": ua_data.get_device(), + "ip": db_session.ip, + "creation_date": db_session.creation_date.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"), + "last_activity": db_session.last_activity.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"), + } + + for browser, icon in BROWSERS.items(): + if browser in last_session["browser"]: + last_session["browser"] = f" {last_session['browser']}" + break + + for os, icon in OS.items(): + if os in last_session["os"]: + last_session["os"] = f" {last_session['os']}" + break + + last_session["device"] = f" {last_session['device']}" + + yield last_session + + return session_generator(), total_sessions + + @profile.route("/profile", methods=["GET"]) @login_required def profile_page(): @@ -39,35 +80,7 @@ def profile_page(): session["tmp_totp_secret"] = TOTP.generate_totp_secret() totp_qr_image = TOTP.generate_qrcode(current_user.get_id(), session["tmp_totp_secret"]) - last_sessions = [] - for db_session in DB.get_ui_user_sessions(current_user.username): - ua_data = parse(db_session.user_agent) - last_session = { - "current": db_session.id == session.get("session_id"), - "browser": ua_data.get_browser(), - "os": ua_data.get_os(), - "device": ua_data.get_device(), - "ip": db_session.ip, - "creation_date": db_session.creation_date.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"), - "last_activity": db_session.last_activity.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"), - } - - for browser, icon in BROWSERS.items(): - if browser in last_session["browser"]: - last_session["browser"] = f" {last_session['browser']}" - break - - for os, icon in OS.items(): - if os in last_session["os"]: - last_session["os"] = f" {last_session['os']}" - break - - last_session["device"] = f" {last_session['device']}" - - if last_session["current"] and last_sessions: - last_sessions.insert(0, last_session) - continue - last_sessions.append(last_session) + last_sessions, total_sessions = get_last_sessions(1, 3) return render_template( "profile.html", @@ -77,9 +90,34 @@ def profile_page(): is_recovery_refreshed=session.pop("totp_refreshed", False), totp_secret=TOTP.get_totp_pretty_key(session.get("tmp_totp_secret", "")), last_sessions=last_sessions, + total_sessions=total_sessions, ) +@profile.route("/profile/sessions", methods=["GET"]) +@login_required +@cors_required +def get_sessions(): + page = request.args.get("page", 1, type=int) + + if page < 1: + return Response("Invalid page number", status=400) + + session_generator = get_last_sessions(page, 3)[0] + + def generate_stream(): + yield "[" + first = True + for session_data in session_generator: + if not first: + yield "," + first = False + yield jsonify(session_data).get_data(as_text=True) + yield "]" + + return Response(stream_with_context(generate_stream()), content_type="application/json") + + @profile.route("/profile/totp-refresh", methods=["POST"]) @login_required def totp_refresh(): @@ -246,20 +284,22 @@ def edit_profile(): return redirect(url_for("profile.profile_page")) -@profile.route("/profile/wipe-old-sessions", methods=["POST"]) +@profile.route("/profile/wipe-other-sessions", methods=["POST"]) @login_required def wipe_old_sessions(): if DB.readonly: return handle_error("Database is in read-only mode", "profile") - verify_data_in_form(data={"password": None}, err_message="Missing current password parameter on /profile/wipe-old-sessions.", redirect_url="profile") + verify_data_in_form(data={"password": None}, err_message="Missing current password parameter on /profile/wipe-other-sessions.", redirect_url="profile") if not current_user.check_password(request.form["password"]): return handle_error("The current password is incorrect.", "profile") + DATA["REVOKED_SESSIONS"] = [db_session.id for db_session in DB.get_ui_user_sessions(current_user.username) if db_session.id != session.get("session_id")] + ret = DB.delete_ui_user_old_sessions(current_user.username) if ret: - return handle_error(f"Couldn't wipe the old sessions in the database: {ret}", "profile") + return handle_error(f"Couldn't wipe the other sessions in the database: {ret}", "profile") - flash("The old sessions have been successfully wiped.") + flash("The other sessions have been successfully wiped.") return redirect(url_for("profile.profile_page") + "#sessions") diff --git a/src/ui/app/routes/setup.py b/src/ui/app/routes/setup.py index 7b816c8a7..782911fa7 100644 --- a/src/ui/app/routes/setup.py +++ b/src/ui/app/routes/setup.py @@ -77,14 +77,10 @@ def setup_page(): config = { "SERVER_NAME": request.form["server_name"], - "USE_UI": "yes", + "USE_TEMPLATE": "ui", "USE_REVERSE_PROXY": "yes", "REVERSE_PROXY_HOST": request.form["ui_host"], "REVERSE_PROXY_URL": request.form["ui_url"] or "/", - "INTERCEPTED_ERROR_CODES": "400 404 405 413 429 500 501 502 503 504", - "ALLOWED_METHODS": "GET|POST|PUT|DELETE", - "MAX_CLIENT_SIZE": "50m", - "KEEP_UPSTREAM_HEADERS": "Content-Security-Policy Strict-Transport-Security X-Frame-Options X-Content-Type-Options Referrer-Policy", } if request.form.get("auto_lets_encrypt", "no") == "yes": diff --git a/src/ui/app/routes/utils.py b/src/ui/app/routes/utils.py index 4c2746bd2..061aaf729 100644 --- a/src/ui/app/routes/utils.py +++ b/src/ui/app/routes/utils.py @@ -1,6 +1,7 @@ from base64 import b64encode from copy import deepcopy from datetime import datetime +from functools import wraps from io import BytesIO from threading import Thread from time import sleep, time @@ -368,3 +369,14 @@ def update_service(config, variables, format_configs, server_name, old_server_na message = f"Deleting {'draft ' if was_draft and is_draft else ''}service {request.form.get('SERVER_NAME', '').split(' ')[0]}" return message + + +def cors_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + fetch_mode = request.headers.get("Sec-Fetch-Mode") + if fetch_mode != "cors": + return Response("CORS request required", status=403) + return f(*args, **kwargs) + + return decorated_function diff --git a/src/ui/app/static/css/pages/profile.css b/src/ui/app/static/css/pages/profile.css new file mode 100644 index 000000000..e9bfbe335 --- /dev/null +++ b/src/ui/app/static/css/pages/profile.css @@ -0,0 +1,21 @@ +/* Initial state for existing cards */ +.card-transition { + opacity: 1; + transition: opacity 0.3s ease; +} + +/* State for fading out existing cards */ +.card-transition.fade-out { + opacity: 0; +} + +/* Initial state for placeholder cards (invisible) */ +.placeholder-transition { + opacity: 0; + transition: opacity 0.3s ease; /* Duration of the fade-in effect */ +} + +/* State for fading in placeholders */ +.placeholder-transition.fade-in { + opacity: 1; +} diff --git a/src/ui/app/static/js/main.js b/src/ui/app/static/js/main.js index 1926d4b8b..c0db5ec37 100644 --- a/src/ui/app/static/js/main.js +++ b/src/ui/app/static/js/main.js @@ -131,173 +131,7 @@ let menu, animate; * Custom */ -class News { - constructor() { - this.BASE_URL = "https://www.bunkerweb.io/"; - } - - init() { - window.addEventListener("load", () => { - if (sessionStorage.getItem("lastRefetch") !== null) { - const storeStamp = sessionStorage.getItem("lastRefetch"); - const nowStamp = Math.round(new Date().getTime() / 1000); - if (+nowStamp > storeStamp) { - sessionStorage.removeItem("lastRefetch"); - sessionStorage.removeItem("lastNews"); - } - } - - if (sessionStorage.getItem("lastNews") !== null) - return this.render(JSON.parse(sessionStorage.getItem("lastNews"))); - - fetch("https://www.bunkerweb.io/api/posts/0/2") - .then((res) => { - return res.json(); - }) - .then((res) => { - const reverseData = res.data.reverse(); - return this.render(reverseData); - }) - .catch((e) => {}); - }); - } - - render(lastNews) { - // store for next time if not the case - if ( - !sessionStorage.getItem("lastNews") && - !sessionStorage.getItem("lastRefetch") - ) { - sessionStorage.setItem( - "lastRefetch", - Math.round(new Date().getTime() / 1000) + 3600, - ); - sessionStorage.setItem("lastNews", JSON.stringify(lastNews)); - const newsNumber = lastNews.length; - document.querySelector("#news-pill").insertAdjacentHTML( - "beforeend", - DOMPurify.sanitize(`${newsNumber}`), - ); - document.querySelector("#news-button").insertAdjacentHTML( - "beforeend", - DOMPurify.sanitize(` - ${newsNumber} - unread news - `), - ); - } - - const newsContainer = document.querySelector("[data-news-container]"); - const lastItem = lastNews[0]; - //remove default message - newsContainer.textContent = ""; - document - .querySelector("[data-news-container]") - .insertAdjacentHTML( - "afterbegin", - `
`, - ); - //render last news - lastNews.forEach((news) => { - //create html card from infos - const cardHTML = this.template( - news.title, - news.slug, - news.photo.url, - news.excerpt, - news.tags, - news.date, - news === lastItem, - ); - const BASE_URL = this.BASE_URL; - let cleanHTML = DOMPurify.sanitize(cardHTML); - //add to DOM inside the created div - document - .querySelector("[data-news-row]") - .insertAdjacentHTML("afterbegin", cleanHTML); - document.querySelectorAll(`.blog-click-${news.slug}`).forEach((slug) => { - slug.addEventListener("click", function () { - window.open( - `${BASE_URL}blog/post/${news.slug}?utm_campaign=self&utm_source=ui`, - "_blank", - ); - }); - }); - document.querySelectorAll(".blog-click-tag").forEach((tag) => { - tag.target = "_blank"; - }); - }); - document - .querySelector("[data-news-container]") - .insertAdjacentHTML("beforeend", "
"); - } - - template(title, slug, img, excerpt, tags, date, last) { - //loop on tags to get list - let tagList = ""; - tags.forEach((tag) => { - tagList += ` - ${tag.name} - -`; - }); - const card = `
-
- News image -
-
- ${title} -
-

${excerpt}

-

${tagList}

-

- Posted on : ${date} -

-
-
-
-`; - return card; - } -} - -const setNews = new News(); - -DOMPurify.addHook("afterSanitizeAttributes", function (node) { - // set all elements owning target to target=_blank - if ("target" in node) { - node.setAttribute("target", "_blank"); - node.setAttribute("rel", "noopener"); - } -}); - document.addEventListener("DOMContentLoaded", function onContentLoaded() { - setNews.init(); - // Generic Copy to Clipboard with Tooltip $(".copy-to-clipboard").on("click", function () { const input = $(this).closest(".input-group").find("input")[0]; diff --git a/src/ui/app/static/js/pages/instances.js b/src/ui/app/static/js/pages/instances.js new file mode 100644 index 000000000..53acea360 --- /dev/null +++ b/src/ui/app/static/js/pages/instances.js @@ -0,0 +1,29 @@ +$(document).ready(function () { + new DataTable("#instances", { + autoFill: false, + }); + + $("#instance-form").on("submit", function (event) { + event.preventDefault(); // Prevent the default form submission + + const form = $(this); + const clickedButton = form.find('button[type="submit"]:focus'); // Find the button that triggered the submit + const action = clickedButton.data("action"); // Get the action from the button + const actionSplit = form.attr("action").split("/"); + const instanceHostname = actionSplit[actionSplit.length - 1]; + + if ( + action === "delete" && + $(`#method-${instanceHostname}`).val() !== "ui" + ) { + return; + } else if ($(`#status-${instanceHostname}`).val() !== "Up") { + return; + } + + form.attr("action", `${form.attr("action")}/${action}`); + + // Now, submit the form with the updated action + form.off("submit").submit(); + }); +}); diff --git a/src/ui/app/static/js/pages/profile.js b/src/ui/app/static/js/pages/profile.js index ba694d34b..5dec954ea 100644 --- a/src/ui/app/static/js/pages/profile.js +++ b/src/ui/app/static/js/pages/profile.js @@ -1,149 +1,323 @@ $(document).ready(function () { + // Password validation functions function validatePassword() { const password = $("#new_password").val(); let isValid = true; - // Validate length - if (password.length >= 8) { - $("#length-check i") - .removeClass("bx-x text-danger") - .addClass("bx-check text-success"); - } else { - isValid = false; - $("#length-check i") - .removeClass("bx-check text-success") - .addClass("bx-x text-danger"); - } - - // Validate uppercase letter - if (/[A-Z]/.test(password)) { - $("#uppercase-check i") - .removeClass("bx-x text-danger") - .addClass("bx-check text-success"); - } else { - isValid = false; - $("#uppercase-check i") - .removeClass("bx-check text-success") - .addClass("bx-x text-danger"); - } - - // Validate number - if (/\d/.test(password)) { - $("#number-check i") - .removeClass("bx-x text-danger") - .addClass("bx-check text-success"); - } else { - isValid = false; - $("#number-check i") - .removeClass("bx-check text-success") - .addClass("bx-x text-danger"); - } - - // Validate special character - if (/[ !"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(password)) { - $("#special-check i") - .removeClass("bx-x text-danger") - .addClass("bx-check text-success"); - } else { - isValid = false; - $("#special-check i") - .removeClass("bx-check text-success") - .addClass("bx-x text-danger"); - } + isValid = validateCondition( + password.length >= 8, + "#length-check i", + isValid, + ); + isValid = validateCondition( + /[A-Z]/.test(password), + "#uppercase-check i", + isValid, + ); + isValid = validateCondition( + /\d/.test(password), + "#number-check i", + isValid, + ); + isValid = validateCondition( + /[ !"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(password), + "#special-check i", + isValid, + ); return isValid; } - // Real-time validation as user types - $("#new_password").on("input", function () { - if (validatePassword()) { - $(this).removeClass("is-invalid"); - $(this).addClass("is-valid"); + function validateCondition(condition, selector, currentValidity) { + if (condition) { + $(selector) + .removeClass("bx-x text-danger") + .addClass("bx-check text-success"); + } else { + $(selector) + .removeClass("bx-check text-success") + .addClass("bx-x text-danger"); + return false; } - }); + return currentValidity; + } function matchPassword() { const newPassword = $("#new_password").val(); const confirmPassword = $("#new_password_confirm").val(); - - if (newPassword === confirmPassword) { - $("#new_password_confirm").removeClass("is-invalid"); - $("#new_password_confirm").addClass("is-valid"); - } else { - $("#new_password_confirm").removeClass("is-valid"); - $("#new_password_confirm").addClass("is-invalid"); - } + const match = newPassword === confirmPassword; + updateValidationState("#new_password_confirm", match); + return match; } - $("#new_password_confirm").on("input", function () { - if (matchPassword()) { - $(this).removeClass("is-invalid"); - $(this).addClass("is-valid"); - } + function updateValidationState(selector, isValid) { + $(selector) + .toggleClass("is-valid", isValid) + .toggleClass("is-invalid", !isValid); + } + + // Real-time validation as user types + $("#new_password").on("input", function () { + const isValid = validatePassword(); + updateValidationState(this, isValid); }); + $("#new_password_confirm").on("input", matchPassword); + // Form submission validation $("#formPasswordSettings").on("submit", function (e) { - const newPasswordInput = $("#new_password"); - const confirmPasswordInput = $("#new_password_confirm"); + const isValidPassword = validatePassword(); + const isMatchingPassword = matchPassword(); - let isValid = true; - - // Check if passwords match - if (newPasswordInput.val() !== confirmPasswordInput.val()) { - isValid = false; - confirmPasswordInput.addClass("is-invalid"); - } else { - confirmPasswordInput.removeClass("is-invalid"); - confirmPasswordInput.addClass("is-valid"); - } - - // Validate password using real-time checks - if (!validatePassword()) { - isValid = false; - newPasswordInput.addClass("is-invalid"); - } else { - newPasswordInput.removeClass("is-invalid"); - newPasswordInput.addClass("is-valid"); - } - - // Prevent form submission if validation fails - if (!isValid) { + if (!isValidPassword || !isMatchingPassword) { e.preventDefault(); } }); - // Listen for tab change + // Tab change handling $('button[data-bs-toggle="tab"]').on("shown.bs.tab", function (e) { - // Get the target tab's ID (data-bs-target without the '#') - var target = $(e.target) - .data("bs-target") - .substring(1) - .replace("navs-pills-", ""); - - if (target === "profile") { - if (window.location.hash) { - history.pushState( - "", - document.title, - window.location.pathname + window.location.search, - ); - } - return; - } - - // Update the URL fragment - window.location.hash = target; + handleTabChange($(e.target).data("bs-target")); }); + function handleTabChange(targetClass) { + const target = targetClass.substring(1).replace("navs-pills-", ""); + const isProfileTab = target === "profile"; + + if (isProfileTab) { + $("#navs-pills-sessions-pagination").removeClass("show active"); + } else { + setTimeout(() => { + $("#navs-pills-sessions-pagination").addClass("show active"); + }, 200); + } + + if (isProfileTab && window.location.hash) { + history.pushState( + "", + document.title, + window.location.pathname + window.location.search, + ); + } else { + window.location.hash = target; + } + } + // On page load, activate the tab based on the URL fragment - var hash = window.location.hash; + const hash = window.location.hash; if (hash) { - var targetTab = $( + const targetTab = $( `button[data-bs-target="#navs-pills-${hash.substring(1)}"]`, ); if (targetTab.length) { targetTab.tab("show"); } } + + // Pagination and session content handling + const totalPages = $(".page-item").length - 2; + const currentCardClasses = "border-primary border-1 position-relative"; + const currentCardHeaderClasses = "bg-primary text-white"; + const currentCardIcon = "bx-star text-warning"; + const currentItemsClasses = "text-primary"; + const otherCardClasses = "border-secondary"; + const otherCardHeaderClasses = "bg-secondary"; + const otherCardIcon = "bx-history text-white"; + const otherItemsClasses = "text-secondary"; + + let clickLock = false; + + $(".page-item").on("click", function () { + if (clickLock) return; + clickLock = true; + + const currentPage = parseInt($("#sessions-current-page").text().trim()); + let page = $(this).hasClass("prev") + ? currentPage - 1 + : $(this).hasClass("next") + ? currentPage + 1 + : parseInt($(this).text().trim()); + + if (page === currentPage || page < 1 || page > totalPages) { + clickLock = false; + return; + } + + updatePagination(page, currentPage); + setPlaceholders(3); + + setTimeout(() => { + fadeInPlaceholders(); + loadSessionData(page); + }, 50); + }); + + function updatePagination(newPage, oldPage) { + $("#sessions-current-page").text(newPage); + $(`.page-item[data-page=${oldPage}]`).removeClass("active"); + $(`.page-item[data-page=${newPage}]`).addClass("active"); + + $(".page-item.prev").toggleClass("disabled", newPage === 1); + $(".page-item.next").toggleClass("disabled", newPage === totalPages); + } + + function setPlaceholders(numPlaceholders) { + const placeholders = Array.from( + { length: numPlaceholders }, + (_, i) => ` +
+
+
+ +
Session
+
+ +
+
+
+ ${generatePlaceholderItems()} +
+
+
+ `, + ).join(""); + + $("#sessions-page-content").html(placeholders); + } + + function generatePlaceholderItems() { + const items = [ + "bx-window-alt", + "Browser", + "bx-layer", + "Operating System", + "bx-devices", + "Device", + "bx-network-chart", + "IP Address", + "bx-time", + "Creation date", + "bx-time", + "Last Activity", + ]; + + return items + .map((icon, i) => + i % 2 === 0 + ? ` +
+ ${ + items[i + 1] + }: +   +
+ ` + : "", + ) + .join(""); + } + + function fadeInPlaceholders() { + $(".placeholder-transition").addClass("fade-in"); + } + + function loadSessionData(page) { + const url = `${window.location.pathname}/sessions?page=${page}`; + + fetch(url) + .then((response) => { + // Check if the response is OK (status code 200-299) + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); // Parse the JSON data from the response + }) + .then((data) => { + updateSessionContent(data); // Update the session content with the data + removeExtraPlaceholders(data.length); // Remove any extra placeholders + clickLock = false; // Unlock the clickLock variable + }) + .catch((error) => { + handleError(); // Handle any errors that occurred during the fetch + clickLock = false; // Ensure clickLock is unlocked even in case of error + console.error("Fetch error:", error); // Log the error to the console + }); + } + + function updateSessionContent(sessions) { + sessions.forEach((session, index) => { + const content = generateSessionContent(session, index); + const placeholder = $(`#session-placeholder-${index}`); + + placeholder + .html(content) + .removeClass("placeholder-transition fade-in") + .addClass("card-transition") + .toggleClass(currentCardClasses, session.current) + .toggleClass(otherCardClasses, !session.current); + }); + } + + function generateSessionContent(session, index) { + const items = [ + ["bx-window-alt", "Browser", session.browser], + ["bx-layer", "Operating System", session.os], + ["bx-devices", "Device", session.device], + [ + "bx-network-chart", + "IP Address", + ``, + ], + ["bx-time", "Creation date", session.creation_date], + ["bx-time", "Last Activity", session.last_activity], + ]; + + return ` +
+
+ +
Session
+
+ ${ + session.current + ? ' Current Session' + : "" + } +
+
+
+ ${generateSessionItems(items, session)} +
+
+ `; + } + + function generateSessionItems(items, session) { + return items + .map( + ([icon, label, value]) => ` +
+ ${label}: +  ${value} +
+ `, + ) + .join(""); + } + + function removeExtraPlaceholders(sessionCount) { + $(`#sessions-page-content .card:gt(${sessionCount - 1})`).remove(); + } + + function handleError() { + $("#session-page-content").html( + "

Error loading data. Please try again.

", + ); + } }); diff --git a/src/ui/app/static/js/sidebar.js b/src/ui/app/static/js/sidebar.js new file mode 100644 index 000000000..3b5df73bd --- /dev/null +++ b/src/ui/app/static/js/sidebar.js @@ -0,0 +1,167 @@ +class News { + constructor() { + this.BASE_URL = "https://www.bunkerweb.io/"; + } + + init() { + window.addEventListener("load", () => { + if (sessionStorage.getItem("lastRefetch") !== null) { + const storeStamp = sessionStorage.getItem("lastRefetch"); + const nowStamp = Math.round(new Date().getTime() / 1000); + if (+nowStamp > storeStamp) { + sessionStorage.removeItem("lastRefetch"); + sessionStorage.removeItem("lastNews"); + } + } + + if (sessionStorage.getItem("lastNews") !== null) + return this.render(JSON.parse(sessionStorage.getItem("lastNews"))); + + fetch("https://www.bunkerweb.io/api/posts/0/2") + .then((res) => { + return res.json(); + }) + .then((res) => { + const reverseData = res.data.reverse(); + return this.render(reverseData); + }) + .catch((e) => {}); + }); + } + + render(lastNews) { + // store for next time if not the case + if ( + !sessionStorage.getItem("lastNews") && + !sessionStorage.getItem("lastRefetch") + ) { + sessionStorage.setItem( + "lastRefetch", + Math.round(new Date().getTime() / 1000) + 3600, + ); + sessionStorage.setItem("lastNews", JSON.stringify(lastNews)); + const newsNumber = lastNews.length; + document.querySelector("#news-pill").insertAdjacentHTML( + "beforeend", + DOMPurify.sanitize(`${newsNumber}`), + ); + document.querySelector("#news-button").insertAdjacentHTML( + "beforeend", + DOMPurify.sanitize(` + ${newsNumber} + unread news + `), + ); + } + + const newsContainer = document.querySelector("[data-news-container]"); + const lastItem = lastNews[0]; + //remove default message + newsContainer.textContent = ""; + document + .querySelector("[data-news-container]") + .insertAdjacentHTML( + "afterbegin", + `
`, + ); + //render last news + lastNews.forEach((news) => { + //create html card from infos + const cardHTML = this.template( + news.title, + news.slug, + news.photo.url, + news.excerpt, + news.tags, + news.date, + news === lastItem, + ); + const BASE_URL = this.BASE_URL; + let cleanHTML = DOMPurify.sanitize(cardHTML); + //add to DOM inside the created div + document + .querySelector("[data-news-row]") + .insertAdjacentHTML("afterbegin", cleanHTML); + document.querySelectorAll(`.blog-click-${news.slug}`).forEach((slug) => { + slug.addEventListener("click", function () { + window.open( + `${BASE_URL}blog/post/${news.slug}?utm_campaign=self&utm_source=ui`, + "_blank", + ); + }); + }); + document.querySelectorAll(".blog-click-tag").forEach((tag) => { + tag.target = "_blank"; + }); + }); + document + .querySelector("[data-news-container]") + .insertAdjacentHTML("beforeend", "
"); + } + + template(title, slug, img, excerpt, tags, date, last) { + //loop on tags to get list + let tagList = ""; + tags.forEach((tag) => { + tagList += ` + ${tag.name} + +`; + }); + const card = `
+
+ News image +
+
+ ${title} +
+

${excerpt}

+

${tagList}

+

+ Posted on : ${date} +

+
+
+
+`; + return card; + } +} + +const setNews = new News(); + +DOMPurify.addHook("afterSanitizeAttributes", function (node) { + // set all elements owning target to target=_blank + if ("target" in node) { + node.setAttribute("target", "_blank"); + node.setAttribute("rel", "noopener"); + } +}); + +document.addEventListener("DOMContentLoaded", function onContentLoaded() { + setNews.init(); +}); diff --git a/src/ui/app/static/libs/datatables/datatables.min.css b/src/ui/app/static/libs/datatables/datatables.min.css new file mode 100644 index 000000000..ce0534a52 --- /dev/null +++ b/src/ui/app/static/libs/datatables/datatables.min.css @@ -0,0 +1,44 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#bs5/dt-2.1.4/b-3.1.1/date-1.5.3/fh-4.0.1/r-3.0.2/rg-1.5.0/sc-2.4.3/sb-1.8.0/sp-2.3.2/sl-2.0.5 + * + * Included libraries: + * DataTables 2.1.4, Buttons 3.1.1, DateTime 1.5.3, FixedHeader 4.0.1, Responsive 3.0.2, RowGroup 1.5.0, Scroller 2.4.3, SearchBuilder 1.8.0, SearchPanes 2.3.2, Select 2.0.5 + */ + +:root{--dt-row-selected: 13, 110, 253;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11;--dt-row-stripe: 0, 0, 0;--dt-row-hover: 0, 0, 0;--dt-column-ordering: 0, 0, 0;--dt-html-background: white}:root.dark{--dt-html-background: rgb(33, 37, 41)}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{display:inline-block;box-sizing:border-box;content:"";border-top:5px solid transparent;border-left:10px solid rgba(0, 0, 0, 0.5);border-bottom:5px solid transparent;border-right:0px solid transparent}table.dataTable tr.dt-hasChild td.dt-control:before{border-top:10px solid rgba(0, 0, 0, 0.5);border-left:5px solid transparent;border-bottom:0px solid transparent;border-right:5px solid transparent}html.dark table.dataTable td.dt-control:before,:root[data-bs-theme=dark] table.dataTable td.dt-control:before,:root[data-theme=dark] table.dataTable td.dt-control:before{border-left-color:rgba(255, 255, 255, 0.5)}html.dark table.dataTable tr.dt-hasChild td.dt-control:before,:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before,:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before{border-top-color:rgba(255, 255, 255, 0.5);border-left-color:transparent}div.dt-scroll{width:100%}div.dt-scroll-body thead tr,div.dt-scroll-body tfoot tr{height:0}div.dt-scroll-body thead tr th,div.dt-scroll-body thead tr td,div.dt-scroll-body tfoot tr th,div.dt-scroll-body tfoot tr td{height:0 !important;padding-top:0px !important;padding-bottom:0px !important;border-top-width:0px !important;border-bottom-width:0px !important}div.dt-scroll-body thead tr th div.dt-scroll-sizing,div.dt-scroll-body thead tr td div.dt-scroll-sizing,div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,div.dt-scroll-body tfoot tr td div.dt-scroll-sizing{height:0 !important;overflow:hidden !important}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before{position:absolute;display:block;bottom:50%;content:"▲";content:"▲"/""}table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{position:absolute;display:block;top:50%;content:"▼";content:"▼"/""}table.dataTable thead>tr>th.dt-orderable-asc,table.dataTable thead>tr>th.dt-orderable-desc,table.dataTable thead>tr>th.dt-ordering-asc,table.dataTable thead>tr>th.dt-ordering-desc,table.dataTable thead>tr>td.dt-orderable-asc,table.dataTable thead>tr>td.dt-orderable-desc,table.dataTable thead>tr>td.dt-ordering-asc,table.dataTable thead>tr>td.dt-ordering-desc{position:relative;padding-right:30px}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order{position:absolute;right:12px;top:0;bottom:0;width:12px}table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-orderable-asc span.dt-column-order:after,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:before,table.dataTable thead>tr>th.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:after,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-asc span.dt-column-order:after,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:before,table.dataTable thead>tr>td.dt-orderable-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{left:0;opacity:.125;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.dt-orderable-asc,table.dataTable thead>tr>th.dt-orderable-desc,table.dataTable thead>tr>td.dt-orderable-asc,table.dataTable thead>tr>td.dt-orderable-desc{cursor:pointer}table.dataTable thead>tr>th.dt-orderable-asc:hover,table.dataTable thead>tr>th.dt-orderable-desc:hover,table.dataTable thead>tr>td.dt-orderable-asc:hover,table.dataTable thead>tr>td.dt-orderable-desc:hover{outline:2px solid rgba(0, 0, 0, 0.05);outline-offset:-2px}table.dataTable thead>tr>th.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>th.dt-ordering-desc span.dt-column-order:after,table.dataTable thead>tr>td.dt-ordering-asc span.dt-column-order:before,table.dataTable thead>tr>td.dt-ordering-desc span.dt-column-order:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled span.dt-column-order:after,table.dataTable thead>tr>th.sorting_asc_disabled span.dt-column-order:before,table.dataTable thead>tr>td.sorting_desc_disabled span.dt-column-order:after,table.dataTable thead>tr>td.sorting_asc_disabled span.dt-column-order:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dt-scroll-body>table.dataTable>thead>tr>th,div.dt-scroll-body>table.dataTable>thead>tr>td{overflow:hidden}:root.dark table.dataTable thead>tr>th.dt-orderable-asc:hover,:root.dark table.dataTable thead>tr>th.dt-orderable-desc:hover,:root.dark table.dataTable thead>tr>td.dt-orderable-asc:hover,:root.dark table.dataTable thead>tr>td.dt-orderable-desc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>th.dt-orderable-asc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>th.dt-orderable-desc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>td.dt-orderable-asc:hover,:root[data-bs-theme=dark] table.dataTable thead>tr>td.dt-orderable-desc:hover{outline:2px solid rgba(255, 255, 255, 0.05)}div.dt-processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-22px;text-align:center;padding:2px;z-index:10}div.dt-processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dt-processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(13, 110, 253);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dt-processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dt-processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dt-processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dt-processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th,table.dataTable td{box-sizing:border-box}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable th.dt-empty,table.dataTable td.dt-empty{text-align:center;vertical-align:top}table.dataTable th.dt-type-numeric,table.dataTable th.dt-type-date,table.dataTable td.dt-type-numeric,table.dataTable td.dt-type-date{text-align:right}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}/*! Bootstrap 5 integration for DataTables + * + * ©2020 SpryMedia Ltd, all rights reserved. + * License: MIT datatables.net/license/mit + */table.table.dataTable{clear:both;margin-bottom:0;max-width:none;border-spacing:0}table.table.dataTable.table-striped>tbody>tr:nth-of-type(2n+1)>*{box-shadow:none}table.table.dataTable>:not(caption)>*>*{background-color:var(--bs-table-bg)}table.table.dataTable>tbody>tr{background-color:transparent}table.table.dataTable>tbody>tr.selected>*{box-shadow:inset 0 0 0 9999px rgb(13, 110, 253);box-shadow:inset 0 0 0 9999px rgb(var(--dt-row-selected));color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.table.dataTable>tbody>tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.table.dataTable.table-striped>tbody>tr:nth-of-type(2n+1)>*{box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05)}table.table.dataTable.table-striped>tbody>tr:nth-of-type(2n+1).selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.95);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95)}table.table.dataTable.table-hover>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075)}table.table.dataTable.table-hover>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.975);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975)}div.dt-container div.dt-layout-start>*:not(:last-child){margin-right:1em}div.dt-container div.dt-layout-end>*:not(:first-child){margin-left:1em}div.dt-container div.dt-layout-full{width:100%}div.dt-container div.dt-layout-full>*:only-child{margin-left:auto;margin-right:auto}div.dt-container div.dt-layout-table>div{display:block !important}@media screen and (max-width: 767px){div.dt-container div.dt-layout-start>*:not(:last-child){margin-right:0}div.dt-container div.dt-layout-end>*:not(:first-child){margin-left:0}}div.dt-container div.dt-length label{font-weight:normal;text-align:left;white-space:nowrap}div.dt-container div.dt-length select{width:auto;display:inline-block;margin-right:.5em}div.dt-container div.dt-search{text-align:right}div.dt-container div.dt-search label{font-weight:normal;white-space:nowrap;text-align:left}div.dt-container div.dt-search input{margin-left:.5em;display:inline-block;width:auto}div.dt-container div.dt-paging{margin:0}div.dt-container div.dt-paging ul.pagination{margin:2px 0;flex-wrap:wrap}div.dt-container div.dt-row{position:relative}div.dt-scroll-head table.dataTable{margin-bottom:0 !important}div.dt-scroll-body{border-bottom-color:var(--bs-border-color);border-bottom-width:var(--bs-border-width);border-bottom-style:solid}div.dt-scroll-body>table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dt-scroll-body>table>tbody>tr:first-child{border-top-width:0}div.dt-scroll-body>table>thead>tr{border-width:0 !important}div.dt-scroll-body>table>tbody>tr:last-child>*{border-bottom:none}div.dt-scroll-foot>.dt-scroll-footInner{box-sizing:content-box}div.dt-scroll-foot>.dt-scroll-footInner>table{margin-top:0 !important;border-top:none}div.dt-scroll-foot>.dt-scroll-footInner>table>tfoot>tr:first-child{border-top-width:0 !important}@media screen and (max-width: 767px){div.dt-container div.dt-length,div.dt-container div.dt-search,div.dt-container div.dt-info,div.dt-container div.dt-paging{text-align:center}div.dt-container .row{--bs-gutter-y: 0.5rem}div.dt-container div.dt-paging ul.pagination{justify-content:center !important}}table.dataTable.table-sm>thead>tr th.dt-orderable-asc,table.dataTable.table-sm>thead>tr th.dt-orderable-desc,table.dataTable.table-sm>thead>tr th.dt-ordering-asc,table.dataTable.table-sm>thead>tr th.dt-ordering-desc,table.dataTable.table-sm>thead>tr td.dt-orderable-asc,table.dataTable.table-sm>thead>tr td.dt-orderable-desc,table.dataTable.table-sm>thead>tr td.dt-ordering-asc,table.dataTable.table-sm>thead>tr td.dt-ordering-desc{padding-right:20px}table.dataTable.table-sm>thead>tr th.dt-orderable-asc span.dt-column-order,table.dataTable.table-sm>thead>tr th.dt-orderable-desc span.dt-column-order,table.dataTable.table-sm>thead>tr th.dt-ordering-asc span.dt-column-order,table.dataTable.table-sm>thead>tr th.dt-ordering-desc span.dt-column-order,table.dataTable.table-sm>thead>tr td.dt-orderable-asc span.dt-column-order,table.dataTable.table-sm>thead>tr td.dt-orderable-desc span.dt-column-order,table.dataTable.table-sm>thead>tr td.dt-ordering-asc span.dt-column-order,table.dataTable.table-sm>thead>tr td.dt-ordering-desc span.dt-column-order{right:5px}div.dt-scroll-head table.table-bordered{border-bottom-width:0}div.table-responsive>div.dt-container>div.row{margin:0}div.table-responsive>div.dt-container>div.row>div[class^=col-]:first-child{padding-left:0}div.table-responsive>div.dt-container>div.row>div[class^=col-]:last-child{padding-right:0}:root[data-bs-theme=dark]{--dt-row-hover: 255, 255, 255;--dt-row-stripe: 255, 255, 255;--dt-column-ordering: 255, 255, 255} + + +@keyframes dtb-spinner{100%{transform:rotate(360deg)}}@-o-keyframes dtb-spinner{100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes dtb-spinner{100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dtb-spinner{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes dtb-spinner{100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}div.dataTables_wrapper{position:relative}div.dt-buttons{position:initial}div.dt-buttons .dt-button{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border-radius:.75em;box-shadow:3px 4px 10px 1px rgba(0, 0, 0, 0.8);text-align:center;z-index:2003;overflow:hidden}div.dt-button-info h2{padding:2rem 2rem 1rem 2rem;margin:0;font-weight:normal}div.dt-button-info>div{padding:1em 2em 2em 2em}div.dtb-popover-close{position:absolute;top:6px;right:6px;width:22px;height:22px;text-align:center;border-radius:3px;cursor:pointer;z-index:2003}button.dtb-hide-drop{display:none !important}div.dt-button-collection-title{text-align:center;padding:.3em .5em .5em;margin-left:.5em;margin-right:.5em;font-size:.9em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}div.dt-button-collection-title:empty{display:none}span.dt-button-spacer{display:inline-block;margin:.5em;white-space:nowrap}span.dt-button-spacer.bar{border-left:1px solid rgba(0, 0, 0, 0.3);vertical-align:middle;padding-left:.5em}span.dt-button-spacer.bar:empty{height:1em;width:1px;padding-left:0}div.dt-button-collection .dt-button-active{padding-right:3em}div.dt-button-collection .dt-button-active:after{position:absolute;top:50%;margin-top:-10px;right:1em;display:inline-block;content:"✓";color:inherit}div.dt-button-collection .dt-button-active.dt-button-split{padding-right:0}div.dt-button-collection .dt-button-active.dt-button-split:after{display:none}div.dt-button-collection .dt-button-active.dt-button-split>*:first-child{padding-right:3em}div.dt-button-collection .dt-button-active.dt-button-split>*:first-child:after{position:absolute;top:50%;margin-top:-10px;right:1em;display:inline-block;content:"✓";color:inherit}div.dt-button-collection .dt-button-active-a a{padding-right:3em}div.dt-button-collection .dt-button-active-a a:after{position:absolute;right:1em;display:inline-block;content:"✓";color:inherit}div.dt-button-collection span.dt-button-spacer{width:100%;font-size:.9em;text-align:center;margin:.5em 0}div.dt-button-collection span.dt-button-spacer:empty{height:0;width:100%}div.dt-button-collection span.dt-button-spacer.bar{border-left:none;border-bottom:1px solid rgba(0, 0, 0, 0.1);padding-left:0}@media print{table.dataTable tr>*{box-shadow:none !important}}html.dark div.dt-button-info{background-color:var(--dt-html-background);border:1px solid rgba(255, 255, 255, 0.15)}div.dt-buttons div.btn-group{position:initial}div.dt-buttons div.dropdown-menu{margin-top:4px;width:200px}div.dt-buttons div.dropdown-menu .dt-button{position:relative}div.dt-buttons div.dropdown-menu div.dt-button-split{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:stretch}div.dt-buttons div.dropdown-menu div.dt-button-split a:first-child{min-width:auto;flex:1 0 50px;padding-right:0}div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child{min-width:33px;flex:0;background:transparent;border:none;line-height:1rem;color:var(--bs-dropdown-link-color);padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);overflow:visible}div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:after{position:relative;left:-3px}div.dt-buttons div.dropdown-menu.fixed{position:fixed;display:block;top:50%;left:50%;margin-left:-75px;border-radius:5px;background-color:white;padding:.5em}div.dt-buttons div.dropdown-menu.fixed.two-column{margin-left:-200px}div.dt-buttons div.dropdown-menu.fixed.three-column{margin-left:-225px}div.dt-buttons div.dropdown-menu.fixed.four-column{margin-left:-300px}div.dt-buttons div.dropdown-menu.fixed.columns{margin-left:-409px}@media screen and (max-width: 1024px){div.dt-buttons div.dropdown-menu.fixed.columns{margin-left:-308px}}@media screen and (max-width: 640px){div.dt-buttons div.dropdown-menu.fixed.columns{margin-left:-203px}}@media screen and (max-width: 460px){div.dt-buttons div.dropdown-menu.fixed.columns{margin-left:-100px}}div.dt-buttons div.dropdown-menu.fixed>:last-child{max-height:100vh;overflow:auto}div.dt-buttons div.dropdown-menu.two-column>:last-child,div.dt-buttons div.dropdown-menu.three-column>:last-child,div.dt-buttons div.dropdown-menu.four-column>:last-child{display:block !important;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}div.dt-buttons div.dropdown-menu.two-column>:last-child>*,div.dt-buttons div.dropdown-menu.three-column>:last-child>*,div.dt-buttons div.dropdown-menu.four-column>:last-child>*{-webkit-column-break-inside:avoid;break-inside:avoid}div.dt-buttons div.dropdown-menu.two-column{width:400px}div.dt-buttons div.dropdown-menu.two-column>:last-child{padding-bottom:1px;column-count:2}div.dt-buttons div.dropdown-menu.three-column{width:450px}div.dt-buttons div.dropdown-menu.three-column>:last-child{padding-bottom:1px;column-count:3}div.dt-buttons div.dropdown-menu.four-column{width:600px}div.dt-buttons div.dropdown-menu.four-column>:last-child{padding-bottom:1px;column-count:4}div.dt-buttons div.dropdown-menu .dt-button{border-radius:0}div.dt-buttons div.dropdown-menu.columns{width:auto}div.dt-buttons div.dropdown-menu.columns>:last-child{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:6px;width:818px;padding-bottom:1px}div.dt-buttons div.dropdown-menu.columns>:last-child .dt-button{min-width:200px;flex:0 1;margin:0}div.dt-buttons div.dropdown-menu.columns.dtb-b3>:last-child,div.dt-buttons div.dropdown-menu.columns.dtb-b2>:last-child,div.dt-buttons div.dropdown-menu.columns.dtb-b1>:last-child{justify-content:space-between}div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button{flex:1 1 32%}div.dt-buttons div.dropdown-menu.columns.dtb-b2 .dt-button{flex:1 1 48%}div.dt-buttons div.dropdown-menu.columns.dtb-b1 .dt-button{flex:1 1 100%}@media screen and (max-width: 1024px){div.dt-buttons div.dropdown-menu.columns>:last-child{width:612px}}@media screen and (max-width: 640px){div.dt-buttons div.dropdown-menu.columns>:last-child{width:406px}div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button{flex:0 1 32%}}@media screen and (max-width: 460px){div.dt-buttons div.dropdown-menu.columns>:last-child{width:200px}}div.dt-buttons span.dt-button-spacer.empty{margin:1px}div.dt-buttons span.dt-button-spacer.bar:empty{height:inherit}div.dt-buttons .btn.processing{color:rgba(0, 0, 0, 0.2)}div.dt-buttons .btn.processing:after{position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;box-sizing:border-box;display:block;content:" ";border:2px solid rgb(40, 40, 40);border-radius:50%;border-left-color:transparent;border-right-color:transparent;animation:dtb-spinner 1500ms infinite linear;-o-animation:dtb-spinner 1500ms infinite linear;-ms-animation:dtb-spinner 1500ms infinite linear;-webkit-animation:dtb-spinner 1500ms infinite linear;-moz-animation:dtb-spinner 1500ms infinite linear}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;z-index:999}@media screen and (max-width: 767px){div.dt-buttons{float:none;width:100%;text-align:center;margin-bottom:.5em}div.dt-buttons a.btn{float:none}}:root[data-bs-theme=dark] div.dropdown-menu.dt-button-collection.fixed{background-color:rgb(33, 37, 41);border:1px solid rgba(255, 255, 255, 0.15);border-radius:8px} + + +div.dt-datetime{position:absolute;background-color:white;z-index:2050;border:1px solid #ccc;box-shadow:0 5px 15px -5px rgba(0, 0, 0, 0.5);padding:6px 20px;width:275px;border-radius:5px}div.dt-datetime.inline{position:relative;box-shadow:none}div.dt-datetime div.dt-datetime-title{text-align:center;padding:5px 0px 3px}div.dt-datetime div.dt-datetime-buttons{text-align:center}div.dt-datetime div.dt-datetime-buttons a{display:inline-block;padding:0 .5em .5em .5em;margin:0;font-size:.9em}div.dt-datetime div.dt-datetime-buttons a:hover{text-decoration:underline}div.dt-datetime table{border-spacing:0;margin:12px 0;width:100%}div.dt-datetime table.dt-datetime-table-nospace{margin-top:-12px}div.dt-datetime table th{font-size:.8em;color:#777;font-weight:normal;width:14.285714286%;padding:0 0 4px 0;text-align:center}div.dt-datetime table td{font-size:.9em;color:#444;padding:0}div.dt-datetime table td.selectable{text-align:center;background:#f5f5f5}div.dt-datetime table td.selectable.disabled{color:#aaa;background:white}div.dt-datetime table td.selectable.disabled button:hover{color:#aaa;background:white}div.dt-datetime table td.selectable.now{background-color:#ddd}div.dt-datetime table td.selectable.now button{font-weight:bold}div.dt-datetime table td.selectable.selected button{background:#4e6ca3;color:white;border-radius:2px}div.dt-datetime table td.selectable button:hover{background:#ff8000;color:white;border-radius:2px}div.dt-datetime table td.dt-datetime-week{font-size:.7em}div.dt-datetime table button{width:100%;box-sizing:border-box;border:none;background:transparent;font-size:inherit;color:inherit;text-align:center;padding:4px 0;cursor:pointer;margin:0}div.dt-datetime table button span{display:inline-block;min-width:14px;text-align:right}div.dt-datetime table.weekNumber th{width:12.5%}div.dt-datetime div.dt-datetime-calendar table{margin-top:0}div.dt-datetime div.dt-datetime-label{position:relative;display:inline-block;height:30px;padding:5px 6px;border:1px solid transparent;box-sizing:border-box;cursor:pointer}div.dt-datetime div.dt-datetime-label:hover{border:1px solid #ddd;border-radius:2px;background-color:#f5f5f5}div.dt-datetime div.dt-datetime-label select{position:absolute;top:6px;left:0;cursor:pointer;opacity:0}div.dt-datetime.horizontal{width:550px}div.dt-datetime.horizontal div.dt-datetime-date,div.dt-datetime.horizontal div.dt-datetime-time{width:48%}div.dt-datetime.horizontal div.dt-datetime-time{margin-left:4%}div.dt-datetime div.dt-datetime-date{position:relative;float:left;width:100%}div.dt-datetime div.dt-datetime-time{position:relative;float:left;width:100%;text-align:center}div.dt-datetime div.dt-datetime-time>span{vertical-align:middle}div.dt-datetime div.dt-datetime-time th{text-align:left}div.dt-datetime div.dt-datetime-time div.dt-datetime-timeblock{display:inline-block;vertical-align:middle}div.dt-datetime div.dt-datetime-iconLeft,div.dt-datetime div.dt-datetime-iconRight{width:30px;height:30px;background-position:center;background-repeat:no-repeat;opacity:.3;overflow:hidden;box-sizing:border-box;border:1px solid transparent}div.dt-datetime div.dt-datetime-iconLeft:hover,div.dt-datetime div.dt-datetime-iconRight:hover{border:1px solid #ccc;border-radius:2px;background-color:#f0f0f0;opacity:.6}div.dt-datetime div.dt-datetime-iconLeft button,div.dt-datetime div.dt-datetime-iconRight button{border:none;background:transparent;text-indent:30px;height:100%;width:100%;cursor:pointer}div.dt-datetime div.dt-datetime-iconLeft{position:absolute;top:5px;left:5px}div.dt-datetime div.dt-datetime-iconLeft button{position:relative;z-index:1}div.dt-datetime div.dt-datetime-iconLeft:after{position:absolute;top:7px;left:10px;display:block;content:"";border-top:7px solid transparent;border-right:7px solid black;border-bottom:7px solid transparent}div.dt-datetime div.dt-datetime-iconRight{position:absolute;top:5px;right:5px}div.dt-datetime div.dt-datetime-iconRight button{position:relative;z-index:1}div.dt-datetime div.dt-datetime-iconRight:after{position:absolute;top:7px;left:12px;display:block;content:"";border-top:7px solid transparent;border-left:7px solid black;border-bottom:7px solid transparent}div.dt-datetime-error{clear:both;padding:0 1em;max-width:240px;font-size:11px;line-height:1.25em;text-align:center;color:#b11f1f}html.dark input.dt-datetime{color-scheme:dark}html.dark div.dt-datetime{border:1px solid #595b5e;background-color:#212529;box-shadow:3px 4px 10px 1px rgba(0, 0, 0, 0.8)}html.dark div.dt-datetime table th{color:#ccc}html.dark div.dt-datetime table td{color:#eee}html.dark div.dt-datetime table td.selectable{background:#373c41}html.dark div.dt-datetime table td.selectable.disabled{color:#aaa;background:#171b1f}html.dark div.dt-datetime table td.selectable.disabled button:hover{color:#aaa;background:#171b1f}html.dark div.dt-datetime table td.selectable.now{background:#4b5055}html.dark div.dt-datetime table td.selectable.selected button{background:#6ea8fe;color:black}html.dark div.dt-datetime table td.selectable button:hover{background:#ff8000;color:black}html.dark div.dt-datetime div.dt-datetime-label:hover{border:1px solid transparent;background-color:rgba(255, 255, 255, 0.1)}html.dark div.dt-datetime div.dt-datetime-iconLeft:hover,html.dark div.dt-datetime div.dt-datetime-iconRight:hover,html.dark div.dt-datetime div.dt-datetime-iconUp:hover,html.dark div.dt-datetime div.dt-datetime-iconDown:hover{border:1px solid transparent;background-color:rgba(255, 255, 255, 0.1)}html.dark div.dt-datetime div.dt-datetime-iconLeft:after{border-right-color:white}html.dark div.dt-datetime div.dt-datetime-iconRight:after{border-left-color:white}html.dark div.dt-datetime select{color-scheme:dark}html.dark div.dt-datetime-error{color:#b11f1f} + + +table.dataTable.fixedHeader-floating,table.dataTable.fixedHeader-locked{position:relative !important;background-color:var(--bs-body-bg);margin-top:0 !important;margin-bottom:0 !important}div.dtfh-floatingparent-foot table{border-top-color:var(--bs-border-color);border-top-width:var(--bs-border-width);border-top-style:solid}@media print{table.fixedHeader-floating{display:none}} + + +table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty{cursor:default !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty:before{display:none !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control{cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before{margin-right:.5em;display:inline-block;box-sizing:border-box;content:"";border-top:5px solid transparent;border-left:10px solid rgba(0, 0, 0, 0.5);border-bottom:5px solid transparent;border-right:0px solid transparent}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control.arrow-right::before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control.arrow-right::before{border-top:5px solid transparent;border-left:0px solid transparent;border-bottom:5px solid transparent;border-right:10px solid rgba(0, 0, 0, 0.5)}table.dataTable.dtr-inline.collapsed>tbody>tr.dtr-expanded>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr.dtr-expanded>th.dtr-control:before{border-top:10px solid rgba(0, 0, 0, 0.5);border-left:5px solid transparent;border-bottom:0px solid transparent;border-right:5px solid transparent}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control{padding-left:.333em}table.dataTable.dtr-column>tbody>tr>td.dtr-control,table.dataTable.dtr-column>tbody>tr>th.dtr-control,table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.dtr-control:before,table.dataTable.dtr-column>tbody>tr>th.dtr-control:before,table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{display:inline-block;box-sizing:border-box;content:"";border-top:5px solid transparent;border-left:10px solid rgba(0, 0, 0, 0.5);border-bottom:5px solid transparent;border-right:0px solid transparent}table.dataTable.dtr-column>tbody>tr>td.dtr-control.arrow-right::before,table.dataTable.dtr-column>tbody>tr>th.dtr-control.arrow-right::before,table.dataTable.dtr-column>tbody>tr>td.control.arrow-right::before,table.dataTable.dtr-column>tbody>tr>th.control.arrow-right::before{border-top:5px solid transparent;border-left:0px solid transparent;border-bottom:5px solid transparent;border-right:10px solid rgba(0, 0, 0, 0.5)}table.dataTable.dtr-column>tbody>tr.dtr-expanded td.dtr-control:before,table.dataTable.dtr-column>tbody>tr.dtr-expanded th.dtr-control:before,table.dataTable.dtr-column>tbody>tr.dtr-expanded td.control:before,table.dataTable.dtr-column>tbody>tr.dtr-expanded th.control:before{border-top:10px solid rgba(0, 0, 0, 0.5);border-left:5px solid transparent;border-bottom:0px solid transparent;border-right:5px solid transparent}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul.dtr-details{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul.dtr-details>li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul.dtr-details>li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul.dtr-details>li:last-child{padding-bottom:0;border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}div.dtr-modal{position:fixed;box-sizing:border-box;top:0;left:0;height:100%;width:100%;z-index:100;padding:10em 1em}div.dtr-modal div.dtr-modal-display{position:absolute;top:0;left:0;bottom:0;right:0;width:50%;height:fit-content;max-height:75%;overflow:auto;margin:auto;z-index:102;overflow:auto;background-color:#f5f5f7;border:1px solid black;border-radius:.5em;box-shadow:0 12px 30px rgba(0, 0, 0, 0.6)}div.dtr-modal div.dtr-modal-content{position:relative;padding:2.5em}div.dtr-modal div.dtr-modal-content h2{margin-top:0}div.dtr-modal div.dtr-modal-close{position:absolute;top:6px;right:6px;width:22px;height:22px;text-align:center;border-radius:3px;cursor:pointer;z-index:12}div.dtr-modal div.dtr-modal-background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:101;background:rgba(0, 0, 0, 0.6)}@media screen and (max-width: 767px){div.dtr-modal div.dtr-modal-display{width:95%}}html.dark table.dataTable>tbody>tr>td.dtr-control:before,html[data-bs-theme=dark] table.dataTable>tbody>tr>td.dtr-control:before{border-left-color:rgba(255, 255, 255, 0.5) !important}html.dark table.dataTable>tbody>tr>td.dtr-control.arrow-right::before,html[data-bs-theme=dark] table.dataTable>tbody>tr>td.dtr-control.arrow-right::before{border-right-color:rgba(255, 255, 255, 0.5) !important}html.dark table.dataTable>tbody>tr.dtr-expanded>td.dtr-control:before,html.dark table.dataTable>tbody>tr.dtr-expanded>th.dtr-control:before,html[data-bs-theme=dark] table.dataTable>tbody>tr.dtr-expanded>td.dtr-control:before,html[data-bs-theme=dark] table.dataTable>tbody>tr.dtr-expanded>th.dtr-control:before{border-top-color:rgba(255, 255, 255, 0.5) !important;border-left-color:transparent !important;border-right-color:transparent !important}html.dark table.dataTable>tbody>tr.child ul.dtr-details>li,html[data-bs-theme=dark] table.dataTable>tbody>tr.child ul.dtr-details>li{border-bottom-color:rgb(64, 67, 70)}html.dark div.dtr-modal div.dtr-modal-display,html[data-bs-theme=dark] div.dtr-modal div.dtr-modal-display{background-color:rgb(33, 37, 41);border:1px solid rgba(255, 255, 255, 0.15)}div.dtr-bs-modal table.table tr:first-child td{border-top:none}table.dataTable.table-bordered th.dtr-control.dtr-hidden+*,table.dataTable.table-bordered td.dtr-control.dtr-hidden+*{border-left-width:1px} + + +table.dataTable tr.dtrg-group th{background-color:rgba(0, 0, 0, 0.1);text-align:left}table.dataTable tr.dtrg-group.dtrg-level-0 th{font-weight:bold}table.dataTable tr.dtrg-group.dtrg-level-1 th,table.dataTable tr.dtrg-group.dtrg-level-2 th,table.dataTable tr.dtrg-group.dtrg-level-3 th,table.dataTable tr.dtrg-group.dtrg-level-4 th,table.dataTable tr.dtrg-group.dtrg-level-5 th{background-color:rgba(0, 0, 0, 0.05);padding-top:.25em;padding-bottom:.25em;padding-left:2em;font-size:.9em}table.dataTable tr.dtrg-group.dtrg-level-2 th{background-color:rgba(0, 0, 0, 0.01);padding-left:2.5em}table.dataTable tr.dtrg-group.dtrg-level-3 th{background-color:rgba(0, 0, 0, 0.01);padding-left:3em}table.dataTable tr.dtrg-group.dtrg-level-4 th{background-color:rgba(0, 0, 0, 0.01);padding-left:3.5em}table.dataTable tr.dtrg-group.dtrg-level-5 th{background-color:rgba(0, 0, 0, 0.01);padding-left:4em}html.dark table.dataTable tr.dtrg-group th{background-color:rgba(255, 255, 255, 0.1)}html.dark table.dataTable tr.dtrg-group.dtrg-level-1 th{background-color:rgba(255, 255, 255, 0.05)}html.dark table.dataTable tr.dtrg-group.dtrg-level-2 th,html.dark table.dataTable tr.dtrg-group.dtrg-level-3 th,html.dark table.dataTable tr.dtrg-group.dtrg-level-4 th,html.dark table.dataTable tr.dtrg-group.dtrg-level-5 th{background-color:rgba(255, 255, 255, 0.01)}table.dataTable.table-striped tr.dtrg-level-0{background-color:rgba(0, 0, 0, 0.1)}table.dataTable.table-striped tr.dtrg-level-1{background-color:rgba(0, 0, 0, 0.05)}table.dataTable.table-striped tr.dtrg-level-2,table.dataTable.table-striped tr.dtrg-level-3,table.dataTable.table-striped tr.dtrg-level-4,table.dataTable.table-striped tr.dtrg-level-5{background-color:rgba(0, 0, 0, 0.01)}table.dataTable.table-striped tr.dtrg-level-1 tr.dtrg-level-2 th,table.dataTable.table-striped tr.dtrg-level-3 th,table.dataTable.table-striped tr.dtrg-level-4 th,table.dataTable.table-striped tr.dtrg-level-5 th{background-color:transparent} + + +div.dts{display:block !important}div.dts tbody th,div.dts tbody td{white-space:nowrap}div.dts div.dts_loading{z-index:1}div.dts div.dts_label{position:absolute;right:20px;background:rgba(0, 0, 0, 0.8);color:white;box-shadow:3px 3px 10px rgba(0, 0, 0, 0.5);text-align:right;border-radius:3px;padding:.4em;z-index:2;display:none}div.dts div.dt-scroll-body,div.dts div.dataTables_scrollBody{background:repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.025), rgba(0, 0, 0, 0.025) 10px, rgba(0, 0, 0, 0) 10px, rgba(0, 0, 0, 0) 20px)}div.dts div.dt-scroll-body table,div.dts div.dataTables_scrollBody table{background-color:white;z-index:2}div.dts div.dt-length,div.dts div.dt-paging,div.dts div.dataTables_paginate,div.dts div.dataTables_length{display:none}html.dark div.dts div.dts_label{background:rgba(255, 255, 255, 0.8);color:black}html.dark div.dts div.dt-scroll-body,html.dark div.dts div.dataTables_scrollBody{background:repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0.025) 10px, rgba(255, 255, 255, 0) 10px, rgba(255, 255, 255, 0) 20px)}html.dark div.dts div.dt-scroll-body table,html.dark div.dts div.dataTables_scrollBody table{background-color:var(--dt-html-background);z-index:2}div.DTS div.dataTables_scrollBody table{background-color:white}html[data-bs-theme=dark] div.DTS div.dataTables_scrollBody table{background-color:var(--bs-body-bg)} + + +div.dt-button-collection{overflow:visible !important;z-index:2002 !important}div.dt-button-collection div.dtsb-searchBuilder{padding-left:1em !important;padding-right:1em !important}div.dt-button-collection.dtb-collection-closeable div.dtsb-titleRow{padding-right:40px}.dtsb-greyscale{border:1px solid #cecece !important}div.dtsb-logicContainer .dtsb-greyscale{border:none !important}div.dtsb-searchBuilder{justify-content:space-evenly;cursor:default;margin-bottom:1em;text-align:left;width:100%}div.dtsb-searchBuilder button.dtsb-button,div.dtsb-searchBuilder select{font-size:1em}div.dtsb-searchBuilder div.dtsb-titleRow{justify-content:space-evenly;margin-bottom:.5em}div.dtsb-searchBuilder div.dtsb-titleRow div.dtsb-title{display:inline-block;padding-top:14px}div.dtsb-searchBuilder div.dtsb-titleRow div.dtsb-title:empty{display:inline}div.dtsb-searchBuilder div.dtsb-titleRow button.dtsb-clearAll{float:right;margin-bottom:.8em}div.dtsb-searchBuilder div.dtsb-vertical .dtsb-value,div.dtsb-searchBuilder div.dtsb-vertical .dtsb-data,div.dtsb-searchBuilder div.dtsb-vertical .dtsb-condition{display:block}div.dtsb-searchBuilder div.dtsb-group{position:relative;clear:both;margin-bottom:.8em}div.dtsb-searchBuilder div.dtsb-group button.dtsb-search{float:right}div.dtsb-searchBuilder div.dtsb-group button.dtsb-clearGroup{margin:2px;text-align:center;padding:0}div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);position:absolute;margin-top:.8em;margin-right:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria{margin-bottom:.8em;display:flex;justify-content:start;flex-flow:row wrap}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input{padding:.4em;margin-right:.8em;min-width:5em;max-width:20em;color:inherit;font-size:1em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown option.dtsb-notItalic,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input option.dtsb-notItalic{font-style:normal}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-italic{font-style:italic}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont{flex:1;white-space:nowrap}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont span.dtsb-joiner{margin-right:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont input.dtsb-value{width:33%}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont select,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont input{height:100%;box-sizing:border-box}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer{margin-left:auto;display:inline-block}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-delete,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-right,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-left{margin-right:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-delete:last-child,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-right:last-child,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button.dtsb-left:last-child{margin-right:0}@media screen and (max-width: 550px){div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria{display:flex;flex-flow:none;flex-direction:column;justify-content:start;padding-right:calc(35px + .8em);margin-bottom:0px}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:first-child),div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:nth-child(2)),div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:not(:last-child){padding-top:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:first-child,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:nth-child(2),div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria:last-child{padding-top:0em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-dropDown,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-input{max-width:none;width:100%;margin-bottom:.8em;margin-right:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-inputCont{margin-right:.8em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer{position:absolute;width:35px;display:flex;flex-wrap:wrap-reverse;right:0}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria div.dtsb-buttonContainer button{margin-right:0px !important}}div.dtsb-searchBuilder div.dtsb-titleRow{height:40px}div.dtsb-searchBuilder div.dtsb-titleRow div.dtsb-title{padding-top:10px}div.dtsb-searchBuilder div.dtsb-group button.dtsb-clearGroup{margin-right:8px}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria .form-select{width:auto;display:inline-block;padding-right:30px !important}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-condition{border-color:#28a745}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-data{border-color:#dc3545}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria select.dtsb-value,div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria input.dtsb-value{border-color:#007bff}div.dtsb-searchBuilder div.dtsb-group div.dtsb-criteria .form-control{display:inline-block;font-size:1em}div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer{border-radius:4px;display:flex;flex-direction:row;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;margin-top:10px;overflow:hidden}div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer button.dtsb-logic{border:none;border-radius:0px;flex-grow:1;flex-shrink:0;flex-basis:3em;margin:0px;padding:.375rem .7rem}div.dtsb-searchBuilder div.dtsb-group div.dtsb-logicContainer button.dtsb-clearGroup{border:none;border-radius:0px;width:2em;margin:0px}div.dt-button-collection div.dtsb-searchBuilder{padding-left:10px;padding-right:10px} + + +div.dtsp-topRow{display:flex;flex-direction:row;flex-wrap:nowrap;border:2px solid rgba(0, 0, 0, 0);border-radius:3px;justify-content:space-around;align-content:flex-start;align-items:flex-start}div.dtsp-topRow input.dtsp-search{text-overflow:ellipsis;min-width:50px;flex-basis:90px;max-width:none}div.dtsp-topRow input.dtsp-search::placeholder{color:inherit}div.dtsp-topRow div.dtsp-subRow1{display:flex;flex-direction:row;flex-wrap:nowrap;flex:1 1 auto}div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont{position:relative;width:100%}div.dtsp-topRow div.dtsp-subRow1 input{padding-right:2em;width:100% !important;box-sizing:border-box;font-size:1em}div.dtsp-topRow div.dtsp-subRow1 input[disabled=disabled]{background-color:transparent;border:none;cursor:initial;box-shadow:none;padding-bottom:0;padding-top:0;min-height:1em;height:fit-content;box-sizing:content-box}div.dtsp-topRow div.dtsp-subRow1 input[disabled=disabled]::placeholder{color:initial;opacity:1}div.dtsp-topRow div.dtsp-subRow1 button.dtsp-searchIcon{position:absolute;top:0;right:0;bottom:0}div.dtsp-topRow div.dtsp-subRow1 button.dtsp-searchIcon span{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAABbmlDQ1BpY2MAACiRdZE7SwNBFIU/EyWikRRaiFhsoWKhEBREO42FTZAQFYza7G5eQhKX3QQJtoKNhWAh2vgq/AfaCrYKgqAIIhb+Al+NhPVOEkiQZJbZ+3FmzmXmDHjCGTPrtAYhm8vb0bmQthxb0Xzv+PHRxRSabjrWTCQSpun4eaRF1YdR1av5voajM55wTGhpF54wLTsvPC0c3sxbineFe8y0Hhc+ER6x5YDCt0o3KvymOFXhL8X2YnQWPKqnlqpjo47NtJ0VHhYeyGYKZvU86ib+RG5pQWqfzH4coswRQsOgwDoZ8oxKzUlmjX3Bsm+eDfGY8rcoYosjRVq8I6IWpGtCalL0hHwZiir3/3k6yfGxSnd/CNpeXfdzEHz7UNpz3d9T1y2dgfcFrnM1/4bkNPkt+l5NGziGwDZc3tQ04wCudqD32dJtvSx5ZXqSSfi4gK4YdN9Dx2olq+o650+wuCVPdAeHRzAk+wNrfw8JaBFXEnV+AAAACXBIWXMAAA9hAAAPYQGoP6dpAAABMUlEQVQoU6XRr0vDQRjH8akoM4iIjqGoOIZ5oIjB5XWxajaYDGLSIhhNYjcPRDSJwbQNw+L+BNGgYYo/5pT5/shz8vDlBgMPXux7z3N3z+25VOofYyCyd4ZYCavI4gXPsRp9LqiDdrEMH+8wv8Vh8gBfWclFPOEUN3hAHjlMoRa7wTzBS5xgKLFglPkZLjDic6HyDsEMNvGR2Nxifoci3tEI+X770JU0XmPXIlax+LTPh83fFox1X6kxyzdjm9UcdXi9S+Vti6svfyNULhNR9TVsYNhW6Ff9KKCNR7/Zv6eeaQ+6+qcdpu9BqGlp1HFgud+FYdzzUcUExu0Q/cdzHGEFetIlXKPjK/sbqYoOftMiS+j9jzEJPd1Wt+5+kdR/9EM9ucIC5jCbyPc01Q32kfsBppYz3hYFcCwAAAAASUVORK5CYII=") !important;background-repeat:no-repeat;background-position:center;background-size:12px}div.dtsp-topRow div.dtsp-subRow2{white-space:nowrap;flex:0 0 auto}div.dtsp-topRow button>span{display:inline-block;height:100%;width:100%}div.dtsp-topRow button.dtsp-nameButton span{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAK2SURBVFgJ7ZY9j41BFICvryCExrJBQ6HyEYVEIREaUZDQIRoR2ViJKCioxV+gkVXYTVZEQiEUhG2EQnxUCh0FKolY4ut5XnM2cyfva3Pt5m7EPcmzZ2bemTNnzjkzd1utnvQi0IvAfxiBy5z5FoxO89kPY+8mbMjtzs47RXs5/WVpbAG6bWExt5PuIibvhVkwmC+ck3eK9ln6/fAddFojYzBVuYSBpcnIEvRaqOw2RcaN18FPuJH0JvRUxbT3wWf4ltiKPgfVidWlbGZgPozDFfgAC+EA/K2EI4cwcAJ+gPaeQ+VQU2SOMMGcPgPl/m/V2p50rrbRsRgt9Iv5h6xtpP22Bz7Ce1C+gFFxfKzOmShcU+Qmyh2w3w8rIJfddHTck66EukL/xPhj+JM8rHNmFys0Pg4v0up3aFNlwR9NYyodd3OL/C64zpsymcTFcf6ElM4YzjAWKYrJkaq8kE/yUYNP4BoYvS1QRo+hNtF5xfkTUjoTheukSFFMjlTFm6PjceOca/SMpKfeCR1L6Uzk/y2WIkVhNFJlJAZhP+hYns7b9D3IPuhY5mYrIv8OrQJvR5NYyNaW4jsU8pSGNySiVx4o5tXq3JkoXE/mg5R/M8dGJCJpKhaDcjBRdbI/Rm8g69c122om33BHmj2CHoV5qa9jUXBraJ+G1fAVjIBO1klc87ro1K4JZ/K35SWW3TwcyDd6TecqnAEd8cGq2+w84xvBm1n3vS0izKkkwh5XNC/GmFPqqAtPF89AOScKuemaNzoTV1SD5dtSbmLf1/RV+tC0WTgcj6R7HEtrVGWaqu/lYDZ/2pvxQ/kIyw/gFByHC9AHw910hv1aUUumyd8yy0QfhmEkfiNod0Xusct68J1qc8Tdux0Z97Q+hsDb+AYGYEbF/4Guw2Q/qDPqZG/zXgT+3Qj8AtKnfWhFwmuAAAAAAElFTkSuQmCC") !important;background-repeat:no-repeat;background-position:center;background-size:23px;vertical-align:bottom}div.dtsp-topRow button.dtsp-countButton span{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABcGlDQ1BpY2MAACiRdZHNSwJBGMYftTDS8FCHkA57sOigIAXRMQzyYh3UIKvL7rirwe66zK6IdA26dBA6RF36OvQf1DXoWhAERRAR9B/0dQnZ3nEFJXSG2ffHs/O8zDwD+DM6M+yBJGCYDs+mU9JaYV0KviNMM4QoEjKzreXcUh59x88jfKI+JESv/vt6jlBRtRngGyKeYxZ3iBeIMzXHErxHPMbKcpH4hDjO6YDEt0JXPH4TXPL4SzDPZxcBv+gplbpY6WJW5gbxNHHM0KusfR5xk7BqruaoRmlNwEYWaaQgQUEVW9DhIEHVpMx6+5It3woq5GH0tVAHJ0cJZfLGSa1SV5WqRrpKU0dd5P4/T1ubnfG6h1PA4Kvrfk4CwX2g2XDd31PXbZ4BgRfg2uz4K5TT/DfpjY4WOwYiO8DlTUdTDoCrXWD82ZK53JICtPyaBnxcACMFYPQeGN7wsmr/x/kTkN+mJ7oDDo+AKdof2fwDCBRoDkL8UccAAAAJcEhZcwAAD2EAAA9hAag/p2kAAAG5SURBVEgN3VU9LwVBFF0fiYhofUSlEQkKhU7z/oBCQkIiGr9BgUbhVzy9BAnhFyjV/AYFiU5ICM7ZN+c5Zud5dm3lJmfmzrkz9+7cu3c3y/6jjOBSF8CxXS7FmTkbwqIJjDpJvTcmsJ4K3KPZUpyZsx0sxoB9J6mnAkyC7wGuuCFIipNtEcpcWExgXpOBc78vgj6N+QO4NVsjwdFM59tUIDxDrHMBOeIQ34C5ZDregXuAQm4YcI68nN9B3wr2PcwPAIPkN2EqtJH6b+QZm1ajjTx7BqwAr26Lb+C2Kvpbt0Mb2HAJ7NrGFGfmXO3DeA4UshDfQAVmH0gaUFg852TTTDvlxwBlCtxy9zXyBhQFaq0wMmIdRebrfgosA3zb2hKnqG0oqchp4QbuR8X0TjzABhbdOT8jnQ/atcgqpnfwOA7yqZyTU587ZkIGdesLTt2EkynOnbreMUUKMI/dA4B/QVOcO13CQh+5wWCgDwo/75u59odB/wjmfhbgvACcAOyZPHihMWAoIwxyCLgf1oxfgjzVbgBXSTzIN+f0pg6s5DkcesLMRpsBrgE2XO3CN64JFP7JtUeKHX4CKtRRXFZ+7dEAAAAASUVORK5CYII=") !important;background-repeat:no-repeat;background-position:center;background-size:18px;vertical-align:bottom}div.dtsp-topRow button.dtsp-collapseButton span.dtsp-caret{position:relative;top:9px;display:inline-block}div.dtsp-topRow button.dtsp-collapseButton.dtsp-rotated{transform:rotate(180deg)}div.dtsp-searchPane table thead th,div.dtsp-searchPane table thead td{width:100% !important}div.dt-button-collection{z-index:2002}div.dt-button-collection.dtb-collection-closeable div.dtsp-titleRow{padding-right:25px}div.dtsp-columns-1{max-width:100%;min-width:100%;margin:0px !important}div.dtsp-columns-2{max-width:49%;min-width:49%;margin:0px !important}div.dtsp-columns-3{max-width:32%;min-width:32%;margin:0px !important}div.dtsp-columns-4{max-width:24%;min-width:24%;margin:0px !important}div.dtsp-columns-5{max-width:19%;min-width:19%;margin:0px !important}div.dtsp-columns-6{max-width:16%;min-width:16%;margin:0px !important}div.dtsp-columns-7{max-width:14%;min-width:14%;margin:0px !important}div.dtsp-columns-8{max-width:12%;min-width:12%;margin:0px !important}div.dtsp-columns-9{max-width:10.5%;min-width:10.5%;margin:0px !important}div.dtsp-narrow{flex-direction:column !important}div.dtsp-narrow div.dtsp-subRow1,div.dtsp-narrow div.dtsp-subRow2{width:100%}div.dtsp-narrow div.dtsp-subRow2 button{margin:0 !important;width:25% !important}div.dt-button-collection div.dtsp-panesContainer{padding-left:1em;padding-right:1em;margin-bottom:0}div.dtsp-panesContainer{margin-bottom:1em}div.dtsp-searchPane div.dt-container,div.dtsp-searchPane div.dataTables_wrapper{width:100%}div.dtsp-searchPane div.dt-container div.dataTables_layout_cell,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_layout_cell{padding:0}div.dtsp-searchPane div.dt-container div.dt-scroll-head,div.dtsp-searchPane div.dt-container div.dataTables_scrollHead,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-head,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollHead{display:none !important}div.dtsp-searchPane div.dt-container div.dt-scroll-body,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody{background:white !important;border:none}div.dtsp-searchPane div.dt-container div.dt-scroll-body thead,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody thead,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body thead,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody thead{display:none}div.dtsp-searchPane div.dt-container div.dt-scroll-body table tr>th,div.dtsp-searchPane div.dt-container div.dt-scroll-body table tr>td,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody table tr>th,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody table tr>td,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body table tr>th,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body table tr>td,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody table tr>th,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody table tr>td{padding:5px 10px}div.dtsp-searchPane div.dt-container div.dt-scroll-body td.dtsp-nameColumn,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody td.dtsp-nameColumn,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body td.dtsp-nameColumn,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody td.dtsp-nameColumn{width:100% !important}div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont{width:100%;display:flex;flex-direction:row;justify-content:flex-start;align-content:flex-start;align-items:flex-start}div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill{cursor:default}div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-name,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-name{text-overflow:ellipsis;overflow:hidden;display:inline-block;vertical-align:middle;white-space:nowrap;flex-grow:1;text-align:left}div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill{display:inline-block;background-color:#cfcfcf;text-align:center;border-radius:10px;width:auto;min-width:30px;color:black;font-size:.9em;padding:0 4px}div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill:empty,div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill:empty,div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill:empty,div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill:empty{display:none}div.dtsp-panesContainer{clear:both;padding-left:0;padding-right:0;text-align:center}div.dtsp-panesContainer div.dtsp-searchPanes{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-content:flex-start;align-items:stretch;clear:both;text-align:left}div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane{flex-grow:1;flex-shrink:0;font-size:.9em;margin-top:15px !important}div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dt-container,div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper{flex:1;box-sizing:border-box}div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dt-container div.dt-search,div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dt-container div.dataTables_filter,div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper div.dt-search,div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_wrapper div.dataTables_filter{display:none}div.dtsp-panesContainer div.dtsp-title{float:left;padding:10px 0}div.dtsp-panesContainer button.dtsp-clearAll,div.dtsp-panesContainer button.dtsp-collapseAll,div.dtsp-panesContainer button.dtsp-showAll{float:right}div.dtsp-hidden{display:none !important}html.dark div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont input[disabled=disabled]::placeholder,html[data-bs-theme=dark] div.dtsp-topRow div.dtsp-subRow1 div.dtsp-searchCont input[disabled=disabled]::placeholder{color:white}div.dtsp-panesContainer button.btn-subtle{background-color:#f8f9fa;border:1px solid #ced4da}div.dtsp-panesContainer button.btn-subtle.disabled{opacity:.5}div.dtsp-panesContainer button.btn-subtle:hover{background-color:#cbd3da}div.dtsp-panesContainer button.dtsp-clearAll,div.dtsp-panesContainer button.dtsp-showAll{margin-left:3px}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow{margin:.5em 0}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow div.dtsp-subRow2{margin-left:.5em}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button{width:35px;line-height:20px}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button.dtsp-searchIcon,div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button.dtsp-nameButton,div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button.dtsp-countButton{padding:0}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow div.dtsp-subRow1 button{border-right:none;margin-right:1px}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow div.dtsp-subRow1 input{padding-right:3em}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow span.dtsp-caret{top:3px}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button.dtsp-rotated{transform:none}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow button.dtsp-rotated span{transform:rotate(180deg);top:-2px}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow.dtsp-bordered:hover button.disabled{cursor:pointer !important;pointer-events:none}div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow.dtsp-bordered:hover input.dtsp-paneInputButton{pointer-events:none}div.dtsp-panesContainer div.dtsp-searchPane div.dt-container,div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper{border:2px #f0f0f0 solid;border-radius:4px}div.dtsp-panesContainer div.dtsp-searchPane div.dt-container:hover,div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper:hover{border:2px solid #cfcfcf !important}div.dtsp-panesContainer div.dtsp-searchPane div.dt-container div.dtsp-nameCont span.badge,div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper div.dtsp-nameCont span.badge{min-width:30px;line-height:1.25em;margin-top:3.5px}div.dtsp-panesContainer div.dtsp-searchPane div.dt-container>div.row.mt-2,div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper>div.row.mt-2{margin:0 !important}div.dtsp-panesContainer div.dtsp-searchPane div.dt-container>div.row.mt-2>*,div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper>div.row.mt-2>*{padding:0}div.dtsp-panesContainer button.disabled{cursor:not-allowed}div.dt-button-collection div.dtsp-panesContainer{padding:9px 1rem}html[data-bs-theme=dark] div.dtsp-topRow button.dtsp-searchIcon span{filter:invert(1)}html[data-bs-theme=dark] div.dtsp-topRow button.dtsp-nameButton span{filter:invert(1)}html[data-bs-theme=dark] div.dtsp-topRow button.dtsp-countButton span{filter:invert(1)}html[data-bs-theme=dark] div.dtsp-topRow input.dtsp-paneInputButton,html[data-bs-theme=dark] div.dtsp-topRow button{color:inherit}html[data-bs-theme=dark] div.dtsp-panesContainer button.btn-subtle{background-color:rgb(33, 37, 41);border:var(--bs-border-width) solid var(--bs-border-color)}html[data-bs-theme=dark] div.dtsp-panesContainer button.btn-subtle:hover{background-color:rgba(255, 255, 255, 0.1)}html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-clearAll,html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-collapseAll,html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-showAll{color:inherit}html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-clearAll:hover,html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-collapseAll:hover,html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-showAll:hover{background-color:rgb(64, 69, 73)}html[data-bs-theme=dark] div.dtsp-panesContainer button.dtsp-disabledButton{color:rgb(124, 124, 124)}html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper{border:1px solid rgba(255, 255, 255, 0.2)}html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container:hover,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper:hover{border:1px solid rgba(255, 255, 255, 0.3) !important}html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container div.dt-scroll-body,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container div.dataTables_scrollBody,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody{background:var(--bs-table-bg) !important}html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dt-container div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper div.dt-scroll-body div.dtsp-nameCont span.dtsp-pill,html[data-bs-theme=dark] div.dtsp-panesContainer div.dtsp-searchPane div.dataTables_wrapper div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill{background-color:rgb(33, 37, 41);color:inherit} + + +table.dataTable>tbody>tr>.selected{background-color:rgb(13, 110, 253);color:white}table.dataTable>tbody>tr>.dt-select{text-align:center;vertical-align:middle}table.dataTable>thead>tr>.dt-select{text-align:center}table.dataTable input.dt-select-checkbox{appearance:none;position:relative;display:inline-block;width:12px;height:12px;border:1px solid;border-radius:3px;vertical-align:middle;margin-top:1px;color:inherit;font-size:20px;line-height:1em}table.dataTable input.dt-select-checkbox:checked:after{display:block;content:"✓";margin-top:-8px}table.dataTable input.dt-select-checkbox:indeterminate:after{display:block;position:absolute;content:" ";top:3px;left:3px;height:4px;width:4px;background-color:black;border-radius:2px}table.dataTable>tbody>tr.selected input.dt-select-checkbox:checked{border:1px solid}table.dataTable>tbody>tr>td.select-checkbox,table.dataTable>tbody>tr>th.select-checkbox{position:relative}table.dataTable>tbody>tr>td.select-checkbox:before,table.dataTable>tbody>tr>th.select-checkbox:before{display:block;position:absolute;top:50%;left:50%;width:12px;height:12px;box-sizing:border-box;content:" ";margin-top:-6px;margin-left:-6px;border:1px solid;border-radius:3px}table.dataTable>tbody>tr.selected>td.select-checkbox:before,table.dataTable>tbody>tr.selected>th.select-checkbox:before{border:1px solid;content:"✓";font-size:20px;line-height:4px;text-align:center}table.dataTable.compact>tbody>tr>td.select-checkbox:before,table.dataTable.compact>tbody>tr>th.select-checkbox:before{margin-top:-12px}table.dataTable.compact>tbody>tr.selected>td.select-checkbox:after,table.dataTable.compact>tbody>tr.selected>th.select-checkbox:after{margin-top:-16px}div.dt-container span.select-info,div.dt-container span.select-item{margin-left:.5em}html.dark table.dataTable input.dt-select-checkbox:indeterminate:after,html[data-bs-theme=dark] table.dataTable input.dt-select-checkbox:indeterminate:after{background-color:white}@media screen and (max-width: 640px){div.dt-container span.select-info,div.dt-container span.select-item{margin-left:0;display:block}}table.dataTable.table-sm tbody td.select-checkbox::before{margin-top:-9px} diff --git a/src/ui/app/static/libs/datatables/datatables.min.js b/src/ui/app/static/libs/datatables/datatables.min.js new file mode 100644 index 000000000..c50feece0 --- /dev/null +++ b/src/ui/app/static/libs/datatables/datatables.min.js @@ -0,0 +1,219 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#bs5/dt-2.1.4/b-3.1.1/date-1.5.3/fh-4.0.1/r-3.0.2/rg-1.5.0/sc-2.4.3/sb-1.8.0/sp-2.3.2/sl-2.0.5 + * + * Included libraries: + * DataTables 2.1.4, Buttons 3.1.1, DateTime 1.5.3, FixedHeader 4.0.1, Responsive 3.0.2, RowGroup 1.5.0, Scroller 2.4.3, SearchBuilder 1.8.0, SearchPanes 2.3.2, Select 2.0.5 + */ + +/*! DataTables 2.1.4 + * © SpryMedia Ltd - datatables.net/license + */ +!function(n){"use strict";var a;"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e,window,document)}):"object"==typeof exports?(a=require("jquery"),"undefined"==typeof window?module.exports=function(e,t){return e=e||window,t=t||a(e),n(t,e,e.document)}:module.exports=n(a,window,window.document)):window.DataTable=n(jQuery,window,document)}(function(H,W,_){"use strict";function f(e){var t=parseInt(e,10);return!isNaN(t)&&isFinite(e)?t:null}function s(e,t,n,a){var r=typeof e,o="string"==r;return"number"==r||"bigint"==r||!(!a||!T(e))||(t&&o&&(e=E(e,t)),n&&o&&(e=e.replace(P,"")),!isNaN(parseFloat(e))&&isFinite(e))}function c(e,t,n,a){var r;return!(!a||!T(e))||("string"!=typeof e||!e.match(/<(input|select)/i))&&(T(r=e)||"string"==typeof r)&&!!s(L(e),t,n,a)||null}function b(e,t,n,a){var r=[],o=0,i=t.length;if(void 0!==a)for(;o").prependTo(this),fastData:function(e,t,n){return B(c,e,t,n)}}),n=(c.nTable=this,c.oInit=e,o.push(c),c.api=new X(c),c.oInstance=1===E.length?E:r.dataTable(),Q(e),e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=Array.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:H.isPlainObject(e.aLengthMenu[0])?e.aLengthMenu[0].value:e.aLengthMenu[0]),e=et(H.extend(!0,{},a),e),z(c.oFeatures,e,["bPaginate","bLengthChange","bFilter","bSort","bSortMulti","bInfo","bProcessing","bAutoWidth","bSortClasses","bServerSide","bDeferRender"]),z(c,e,["ajax","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","iStateDuration","bSortCellsTop","iTabIndex","sDom","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId","caption","layout","orderDescReverse",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]),z(c.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]),z(c.oLanguage,e,"fnInfoCallback"),Y(c,"aoDrawCallback",e.fnDrawCallback),Y(c,"aoStateSaveParams",e.fnStateSaveParams),Y(c,"aoStateLoadParams",e.fnStateLoadParams),Y(c,"aoStateLoaded",e.fnStateLoaded),Y(c,"aoRowCallback",e.fnRowCallback),Y(c,"aoRowCreatedCallback",e.fnCreatedRow),Y(c,"aoHeaderCallback",e.fnHeaderCallback),Y(c,"aoFooterCallback",e.fnFooterCallback),Y(c,"aoInitComplete",e.fnInitComplete),Y(c,"aoPreDrawCallback",e.fnPreDrawCallback),c.rowIdFn=U(e.rowId),c),d=(V.__browser||(f={},V.__browser=f,p=H("
").css({position:"fixed",top:0,left:-1*W.pageXOffset,height:1,width:1,overflow:"hidden"}).append(H("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(H("
").css({width:"100%",height:10}))).appendTo("body"),d=p.children(),u=d.children(),f.barWidth=d[0].offsetWidth-d[0].clientWidth,f.bScrollbarLeft=1!==Math.round(u.offset().left),p.remove()),H.extend(n.oBrowser,V.__browser),n.oScroll.iBarWidth=V.__browser.barWidth,c.oClasses),f=(H.extend(d,V.ext.classes,e.oClasses),r.addClass(d.table),c.oFeatures.bPaginate||(e.iDisplayStart=0),void 0===c.iInitDisplayStart&&(c.iInitDisplayStart=e.iDisplayStart,c._iDisplayStart=e.iDisplayStart),e.iDeferLoading),h=(null!==f&&(c.deferLoading=!0,u=Array.isArray(f),c._iRecordsDisplay=u?f[0]:f,c._iRecordsTotal=u?f[1]:f),[]),p=this.getElementsByTagName("thead"),n=Ae(c,p[0]);if(e.aoColumns)h=e.aoColumns;else if(n.length)for(R=n[t=0].length;t").appendTo(r):n).html(c.caption),n.length&&(n[0]._captionSide=n.css("caption-side"),c.captionNode=n[0]),0===p.length&&(p=H("").appendTo(r)),c.nTHead=p[0],H("tr",p).addClass(d.thead.row),r.children("tbody")),n=(0===n.length&&(n=H("").insertAfter(p)),c.nTBody=n[0],r.children("tfoot")),O=(0===n.length&&(n=H("").appendTo(r)),c.nTFoot=n[0],H("tr",n).addClass(d.tfoot.row),c.aiDisplay=c.aiDisplayMaster.slice(),c.bInitialised=!0,c.oLanguage);H.extend(!0,O,e.oLanguage),O.sUrl?H.ajax({dataType:"json",url:O.sUrl,success:function(e){q(a.oLanguage,e),H.extend(!0,O,e,c.oInit.oLanguage),G(c,null,"i18n",[c],!0),Me(c)},error:function(){$(c,0,"i18n file loading error",21),Me(c)}}):(G(c,null,"i18n",[c]),Me(c))}}),E=null,this)},g=(V.ext=C={buttons:{},classes:{},builder:"bs5/dt-2.1.4/b-3.1.1/date-1.5.3/fh-4.0.1/r-3.0.2/rg-1.5.0/sc-2.4.3/sb-1.8.0/sp-2.3.2/sl-2.0.5",errMode:"alert",feature:[],features:{},search:[],selector:{cell:[],column:[],row:[]},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{className:{},detect:[],render:{},search:{},order:{}},_unique:0,fnVersionCheck:V.fnVersionCheck,iApiIndex:0,sVersion:V.version},H.extend(C,{afnFiltering:C.search,aTypes:C.type.detect,ofnSearch:C.type.search,oSort:C.type.order,afnSortData:C.order,aoFeatures:C.feature,oStdClasses:C.classes,oPagination:C.pager}),H.extend(V.ext.classes,{container:"dt-container",empty:{row:"dt-empty"},info:{container:"dt-info"},layout:{row:"dt-layout-row",cell:"dt-layout-cell",tableRow:"dt-layout-table",tableCell:"",start:"dt-layout-start",end:"dt-layout-end",full:"dt-layout-full"},length:{container:"dt-length",select:"dt-input"},order:{canAsc:"dt-orderable-asc",canDesc:"dt-orderable-desc",isAsc:"dt-ordering-asc",isDesc:"dt-ordering-desc",none:"dt-orderable-none",position:"sorting_"},processing:{container:"dt-processing"},scrolling:{body:"dt-scroll-body",container:"dt-scroll",footer:{self:"dt-scroll-foot",inner:"dt-scroll-footInner"},header:{self:"dt-scroll-head",inner:"dt-scroll-headInner"}},search:{container:"dt-search",input:"dt-input"},table:"dataTable",tbody:{cell:"",row:""},thead:{cell:"",row:""},tfoot:{cell:"",row:""},paging:{active:"current",button:"dt-paging-button",container:"dt-paging",disabled:"disabled"}}),{}),F=/[\r\n\u2028]/g,N=/<([^>]*>)/g,j=Math.pow(2,28),R=/^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/,O=new RegExp("(\\"+["/",".","*","+","?","|","(",")","[","]","{","}","\\","$","^","-"].join("|\\")+")","g"),P=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,T=function(e){return!e||!0===e||"-"===e},E=function(e,t){return g[t]||(g[t]=new RegExp(Pe(t),"g")),"string"==typeof e&&"."!==t?e.replace(/\./g,"").replace(g[t],"."):e},m=function(e,t,n){var a=[],r=0,o=e.length;if(void 0!==n)for(;rj)throw new Error("Exceeded max str len");var t;for(e=e.replace(N,"");(e=(t=e).replace(/ @@ -64,15 +71,21 @@ + + {% if current_endpoint != "login" and current_endpoint != "totp" %} + + {% endif %} {% if current_endpoint == "profile" %} + {% elif current_endpoint == "instances" %} + {% endif %} diff --git a/src/ui/app/templates/instances.html b/src/ui/app/templates/instances.html new file mode 100644 index 000000000..33982408a --- /dev/null +++ b/src/ui/app/templates/instances.html @@ -0,0 +1,74 @@ +{% extends "dashboard.html" %} +{% block content %} + +
+ + + + + + + + + + + + + + + {% for instance in instances %} + + + + + + + + + + + {% endfor %} + +
HostnameNameMethodHealthTypeCreation dateLast seenActions
{{ instance.hostname }}{{ instance.name }}{{ instance.method }} + {% if instance.status == "up" %} + Up + {% elif instance.status == "down" %} + Down + {% else %} + Loading + {% endif %} + {{ instance.type }}{{ instance.creation_date.astimezone().strftime("%Y-%m-%d at %H:%M:%S %Z") }}{{ instance.last_seen.astimezone().strftime("%Y-%m-%d at %H:%M:%S %Z") }} +
+ + {% if instance.status != "up" %} + {% set disabled = "disabled" %} + {% else %} + {% set disabled = "" %} + {% endif %} + {% if instance.method == "ui" %} + {% set can_delete = "" %} + {% else %} + {% set can_delete = "disabled" %} + {% endif %} + + + + +
+
+
+ +{% endblock %} diff --git a/src/ui/app/templates/navbar.html b/src/ui/app/templates/navbar.html index 596cd9cbf..513025d1a 100644 --- a/src/ui/app/templates/navbar.html +++ b/src/ui/app/templates/navbar.html @@ -113,7 +113,7 @@
  • + href="{{ url_for('logout') }}"> Log Out
  • diff --git a/src/ui/app/templates/profile.html b/src/ui/app/templates/profile.html index 759103b26..68f6afa2e 100644 --- a/src/ui/app/templates/profile.html +++ b/src/ui/app/templates/profile.html @@ -1,35 +1,70 @@ {% extends "dashboard.html" %} {% block content %} {% set profile_url = url_for('profile') %} + {% set total_pages = (total_sessions // 3) + (1 if total_sessions % 3 > 0 else 0) %} -