Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev

This commit is contained in:
florian 2023-04-26 16:35:14 +02:00
commit aa3ce13a81
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
45 changed files with 4689 additions and 878 deletions

View File

@ -279,6 +279,22 @@ You can use the following settings to set up whitelisting :
| `WHITELIST_USER_AGENT_URLS` | | List of URLs containing User-Agent to whitelist. |
| `WHITELIST_URI` | | List of requests URI to whitelist. |
| `WHITELIST_URI_URLS` | | List of URLs containing request(s) URI to whitelist. |
## ReverseScan
ReverseScan" is a feature designed to detect open ports by establishing TCP connections with clients' IP addresses.
Consider adding this feature if you want to detect possible open proxies or connections from servers.
We provide a list of suspicious ports by default, but it can be modified to fit your needs.Be mindful, Adding too many ports to the list can significantly slow down clients' connections due to the caching process.If a listed port is open, the client's access will be denied.
Please be aware, this feature is new and further improvements will be added soon.
Here is the list of settings related to ReverseScan:
| Setting | Default | Description |
| :----------: | :--------------------------------------------------------------------------: | :--------------------------------------------- |
| `USE_REVERSE_SCAN` | `no` | When set to `yes`, will enable ReverseScan. |
| `REVERSE_SCAN_PORTS` | `22 80 443 3128 8000 8080` | List of suspicious ports to scan. |
| `REVERSE_SCAN_TIMEOUT` | `500` | Specify the maximum timeout (in ms) when scanning a port. |
## BunkerNet

View File

@ -69,7 +69,7 @@ Because the web UI is a web application, the recommended installation procedure
-e bwadm.example.com_REVERSE_PROXY_URL=/changeme/ \
-e bwadm.example.com_REVERSE_PROXY_HOST=http://bw-ui:7000 \
-e "bwadm.example.com_REVERSE_PROXY_HEADERS=X-Script-Name /changeme" \
-e bwadm.example.com_INTERCEPTED_ERROR_CODES="400 401 405 413 429 500 501 502 503 504" \
-e bwadm.example.com_INTERCEPTED_ERROR_CODES="400 401 404 405 413 429 500 501 502 503 504" \
-l bunkerweb.INSTANCE \
bunkerity/bunkerweb:1.5.0-beta && \
docker network connect bw-universe bunkerweb
@ -645,7 +645,7 @@ Because the web UI is a web application, the recommended installation procedure
- bunkerweb.REVERSE_PROXY_URL=/changeme
- bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000
- bunkerweb.REVERSE_PROXY_HEADERS=X-Script-Name /changeme
- bunkerweb.INTERCEPTED_ERROR_CODES=400 401 405 413 429 500 501 502 503 504
- bunkerweb.INTERCEPTED_ERROR_CODES=400 401 404 405 413 429 500 501 502 503 504
volumes:
bw-data:

View File

@ -186,7 +186,11 @@ if __name__ == "__main__":
retries += 1
sleep(5)
proc = run(["nginx", "-s", "reload"], stdin=DEVNULL, stderr=STDOUT)
proc = run(
["sudo", "/usr/sbin/nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=STDOUT,
)
if proc.returncode != 0:
status = 1
logger.error("Error while reloading nginx")

View File

@ -1,4 +1,3 @@
from datetime import datetime
from logging import (
CRITICAL,
DEBUG,
@ -31,10 +30,6 @@ class BWLogger(Logger):
stack_info=False,
stacklevel=1,
):
if self.name == "UI":
with open("/var/log/nginx/ui.log", "a") as f:
f.write(f"[{datetime.now().replace(microsecond=0)}] {msg}\n")
return super(BWLogger, self)._log(
level, msg, args, exc_info, extra, stack_info, stacklevel
)

View File

@ -5,7 +5,7 @@ After=bunkerweb.service
[Service]
Restart=no
User=root
User=nginx
PIDFile=/var/tmp/bunkerweb/ui.pid
ExecStart=/usr/share/bunkerweb/scripts/bunkerweb-ui.sh start
ExecStop=/usr/share/bunkerweb/scripts/bunkerweb-ui.sh stop

View File

@ -1,13 +1,13 @@
#!/bin/bash
# Set the PYTHONPATH
export PYTHONPATH=/usr/share/bunkerweb/deps/python
export PYTHONPATH=/usr/share/bunkerweb/deps/python:/usr/share/bunkerweb/ui
# Create the ui.env file if it doesn't exist
if [ ! -f /etc/bunkerweb/ui.env ]; then
echo "ADMIN_USERNAME=admin" > /etc/bunkerweb/ui.env
echo "ADMIN_PASSWORD=changeme" >> /etc/bunkerweb/ui.env
echo "ABSOLUTE_URI=http://mydomain.ext/mypath/" >> /etc/bunkerweb/ui.env
echo "ABSOLUTE_URI=http://bwadm.example.com/changeme/" >> /etc/bunkerweb/ui.env
fi
# Function to start the UI
@ -18,7 +18,7 @@ start() {
fi
source /etc/bunkerweb/ui.env
export $(cat /etc/bunkerweb/ui.env)
python3 -m gunicorn --graceful-timeout=0 --bind=127.0.0.1:7000 --chdir /usr/share/bunkerweb/ui/ --workers=1 --threads=4 main:app &
python3 -m gunicorn main:app --worker-class gevent --bind 127.0.0.1:7000 --graceful-timeout 0 --access-logfile - --error-logfile - &
echo $! > /var/tmp/bunkerweb/ui.pid
}

View File

@ -80,9 +80,12 @@ function start() {
log "SYSTEMCTL" "" "Starting BunkerWeb service ..."
echo "nginx ALL=(ALL) NOPASSWD: /usr/sbin/nginx" > /etc/sudoers.d/bunkerweb
chown -R nginx:nginx /etc/nginx
# Create dummy variables.env
if [ ! -f /etc/bunkerweb/variables.env ]; then
echo -ne "# remove IS_LOADING=yes when your config is ready\nIS_LOADING=yes\nHTTP_PORT=80\nHTTPS_PORT=443\nAPI_LISTEN_IP=127.0.0.1\nSERVER_NAME=\n" > /etc/bunkerweb/variables.env
sudo -E -u nginx -g nginx /bin/bash -c "echo -ne '\# remove IS_LOADING=yes when your config is ready\nIS_LOADING=yes\nHTTP_PORT=80\nHTTPS_PORT=443\nAPI_LISTEN_IP=127.0.0.1\nSERVER_NAME=\n' > /etc/bunkerweb/variables.env"
log "SYSTEMCTL" "" "Created dummy variables.env file"
fi
@ -101,8 +104,8 @@ function start() {
if [ "$HTTPS_PORT" = "" ] ; then
HTTPS_PORT="8443"
fi
echo -ne "IS_LOADING=yes\nHTTP_PORT=${HTTP_PORT}\nHTTPS_PORT=${HTTPS_PORT}\nAPI_LISTEN_IP=127.0.0.1\nSERVER_NAME=\n" > /var/tmp/bunkerweb/tmp.env
/usr/share/bunkerweb/gen/main.py --variables /var/tmp/bunkerweb/tmp.env --no-linux-reload
sudo -E -u nginx -g nginx /bin/bash -c "echo -ne 'IS_LOADING=yes\nHTTP_PORT=${HTTP_PORT}\nHTTPS_PORT=${HTTPS_PORT}\nAPI_LISTEN_IP=127.0.0.1\nSERVER_NAME=\n' > /var/tmp/bunkerweb/tmp.env"
sudo -E -u nginx -g nginx /bin/bash -c "/usr/share/bunkerweb/gen/main.py --variables /var/tmp/bunkerweb/tmp.env --no-linux-reload"
if [ $? -ne 0 ] ; then
log "SYSTEMCTL" "❌" "Error while generating config from /var/tmp/bunkerweb/tmp.env"
exit 1
@ -134,9 +137,9 @@ function start() {
# Update database
log "SYSTEMCTL" "" "Updating database ..."
if [ ! -f /var/lib/bunkerweb/db.sqlite3 ]; then
/usr/share/bunkerweb/gen/save_config.py --variables /etc/bunkerweb/variables.env --init
else
/usr/share/bunkerweb/gen/save_config.py --variables /etc/bunkerweb/variables.env
sudo -E -u nginx -g nginx /bin/bash -c "/usr/share/bunkerweb/gen/save_config.py --variables /etc/bunkerweb/variables.env --init"
else
sudo -E -u nginx -g nginx /bin/bash -c "/usr/share/bunkerweb/gen/save_config.py --variables /etc/bunkerweb/variables.env"
fi
if [ $? -ne 0 ] ; then
log "SYSTEMCTL" "❌" "save_config failed"
@ -146,7 +149,7 @@ function start() {
# Execute scheduler
log "SYSTEMCTL" " " "Executing scheduler ..."
/usr/share/bunkerweb/scheduler/main.py --variables /etc/bunkerweb/variables.env
sudo -E -u nginx -g nginx /bin/bash -c "/usr/share/bunkerweb/scheduler/main.py --variables /etc/bunkerweb/variables.env"
if [ "$?" -ne 0 ] ; then
log "SYSTEMCTL" "❌" "Scheduler failed"
exit 1

View File

@ -79,7 +79,10 @@ class JobScheduler(ApiCaller):
if self.__integration not in ("Autoconf", "Swarm", "Kubernetes", "Docker"):
self.__logger.info("Reloading nginx ...")
proc = run(
["nginx", "-s", "reload"], stdin=DEVNULL, stderr=PIPE, env=self.__env
["sudo", "/usr/sbin/nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=PIPE,
env=self.__env,
)
reload = proc.returncode == 0
if reload:

View File

@ -315,9 +315,6 @@ if __name__ == "__main__":
"Looks like BunkerWeb configuration is already generated, will not generate it again ..."
)
if Path("/var/lib/bunkerweb/db.sqlite3").exists():
chmod("/var/lib/bunkerweb/db.sqlite3", 0o760)
first_run = True
while True:
# Instantiate scheduler
@ -386,7 +383,7 @@ if __name__ == "__main__":
# Stop temp nginx
logger.info("Stopping temp nginx ...")
proc = subprocess_run(
["/usr/sbin/nginx", "-s", "stop"],
["sudo", "/usr/sbin/nginx", "-s", "stop"],
stdin=DEVNULL,
stderr=STDOUT,
env=deepcopy(env),
@ -408,7 +405,7 @@ if __name__ == "__main__":
# Start nginx
logger.info("Starting nginx ...")
proc = subprocess_run(
["/usr/sbin/nginx"],
["sudo", "/usr/sbin/nginx"],
stdin=DEVNULL,
stderr=STDOUT,
env=deepcopy(env),
@ -475,7 +472,7 @@ if __name__ == "__main__":
# Reloading the nginx server.
proc = subprocess_run(
# Reload nginx
["/usr/sbin/nginx", "-s", "reload"],
["sudo", "/usr/sbin/nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=STDOUT,
env=deepcopy(env),

View File

@ -55,13 +55,13 @@ from kubernetes.client.exceptions import ApiException as kube_ApiException
from os import _exit, getenv, getpid, listdir
from re import match as re_match
from requests import get
from shutil import move, rmtree, copytree
from shutil import move, rmtree
from signal import SIGINT, signal, SIGTERM
from subprocess import PIPE, Popen, call
from tarfile import CompressionError, HeaderError, ReadError, TarError, open as tar_open
from threading import Thread
from tempfile import NamedTemporaryFile
from time import time
from time import sleep, time
from traceback import format_exc
from typing import Optional
from zipfile import BadZipFile, ZipFile
@ -80,10 +80,6 @@ from utils import (
from logger import setup_logger
from Database import Database
if not Path("/var/log/nginx/ui.log").exists():
Path("/var/log/nginx").mkdir(parents=True, exist_ok=True)
Path("/var/log/nginx/ui.log").touch()
logger = setup_logger("UI", getenv("LOG_LEVEL", "INFO"))
@ -113,8 +109,8 @@ def handle_stop(signum, frame):
signal(SIGINT, handle_stop)
signal(SIGTERM, handle_stop)
Path("/var/tmp/bunkerweb/ui.pid").write_text(str(getpid()))
if not Path("/var/tmp/bunkerweb/ui.pid").is_file():
Path("/var/tmp/bunkerweb/ui.pid").write_text(str(getpid()))
# Flask app
app = Flask(
@ -187,6 +183,24 @@ elif integration == "Kubernetes":
kubernetes_client = kube_client.CoreV1Api()
db = Database(logger)
while not db.is_initialized():
logger.warning(
"Database is not initialized, retrying in 5s ...",
)
sleep(5)
env = db.get_config()
while not db.is_first_config_saved() or not env:
logger.warning(
"Database doesn't have any config saved yet, retrying in 5s ...",
)
sleep(5)
env = db.get_config()
logger.info("Database is ready")
Path("/var/tmp/bunkerweb/ui.healthy").write_text("ok")
with open("/usr/share/bunkerweb/VERSION", "r") as f:
bw_version = f.read().strip()
@ -196,7 +210,7 @@ try:
SECRET_KEY=vars["FLASK_SECRET"],
ABSOLUTE_URI=vars["ABSOLUTE_URI"],
INSTANCES=Instances(docker_client, kubernetes_client, integration),
CONFIG=Config(logger, db),
CONFIG=Config(db),
CONFIGFILES=ConfigFiles(logger, db),
SESSION_COOKIE_DOMAIN=vars["ABSOLUTE_URI"]
.replace("http://", "")
@ -449,7 +463,7 @@ def services():
del variables["OLD_SERVER_NAME"]
# Edit check fields and remove already existing ones
config = app.config["CONFIG"].get_config(methods=True)
config = app.config["CONFIG"].get_config(methods=False)
for variable, value in deepcopy(variables).items():
if variable.endswith("SCHEMA"):
del variables[variable]
@ -460,19 +474,15 @@ def services():
elif value == "off":
value = "no"
config_setting = config.get(
f"{variables['SERVER_NAME'].split(' ')[0]}_{variable}", None
)
if variable in variables and (
request.form["operation"] == "edit"
and variable != "SERVER_NAME"
and config_setting is not None
and value == config_setting["value"]
variable != "SERVER_NAME"
and value == config.get(variable, None)
or not value.strip()
):
del variables[variable]
print(variables, flush=True)
if len(variables) <= 1:
flash(
f"{variables['SERVER_NAME'].split(' ')[0]} was not edited because no values were changed."
@ -630,6 +640,8 @@ def configs():
operation = app.config["CONFIGFILES"].check_path(variables["path"])
print(variables, flush=True)
if operation:
flash(operation, "error")
return redirect(url_for("loading", next=url_for("configs"))), 500
@ -736,6 +748,7 @@ def plugins():
f"Couldn't update external plugins to database: {err}",
"error",
)
flash(f"Deleted plugin {variables['name']} successfully")
else:
if not Path("/var/tmp/bunkerweb/ui").exists() or not listdir(
"/var/tmp/bunkerweb/ui"
@ -1231,9 +1244,7 @@ def custom_plugin(plugin):
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
try:
# Try to import the custom plugin
@ -1248,9 +1259,7 @@ def custom_plugin(plugin):
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
error = False
res = None
@ -1542,6 +1551,9 @@ def logs_container(container_id):
log = " ".join(splitted[1:])
log_lower = log.lower()
if "[48;2" in log or not log.strip():
continue
logs.append(
{
"content": log,

View File

@ -10,36 +10,17 @@ from pathlib import Path
from re import search as re_search
from subprocess import run, DEVNULL, STDOUT
from tarfile import open as tar_open
from time import sleep
from typing import List, Tuple
from uuid import uuid4
class Config:
def __init__(self, logger, db) -> None:
def __init__(self, db) -> None:
with open("/usr/share/bunkerweb/settings.json", "r") as f:
self.__settings: dict = json_load(f)
self.__logger = logger
self.__db = db
while not self.__db.is_initialized():
self.__logger.warning(
"Database is not initialized, retrying in 5s ...",
)
sleep(5)
env = self.__db.get_config()
while not self.__db.is_first_config_saved() or not env:
self.__logger.warning(
"Database doesn't have any config saved yet, retrying in 5s ...",
)
sleep(5)
env = self.__db.get_config()
self.__logger.info("Database is ready")
Path("/var/tmp/bunkerweb/ui.healthy").write_text("ok")
def __env_to_dict(self, filename: str) -> dict:
"""Converts the content of an env file into a dict

View File

@ -1,13 +1,29 @@
from glob import glob
from os import listdir, replace, walk
from os.path import dirname, join
from pathlib import Path
from re import compile as re_compile
from shutil import rmtree, move as shutil_move
from typing import Tuple
from typing import Any, Dict, List, Tuple
from utils import path_to_dict
def generate_custom_configs(
custom_configs: List[Dict[str, Any]],
*,
original_path: str = "/data/configs",
):
Path(original_path).mkdir(parents=True, exist_ok=True)
for custom_config in custom_configs:
tmp_path = f"{original_path}/{custom_config['type'].replace('_', '-')}"
if custom_config["service_id"]:
tmp_path += f"/{custom_config['service_id']}"
tmp_path += f"/{custom_config['name']}.conf"
Path(dirname(tmp_path)).mkdir(parents=True, exist_ok=True)
Path(tmp_path).write_bytes(custom_config["data"])
class ConfigFiles:
def __init__(self, logger, db):
self.__name_regex = re_compile(r"^[a-zA-Z0-9_\-.]{1,64}$")
@ -19,6 +35,21 @@ class ConfigFiles:
self.__logger = logger
self.__db = db
if not Path("/usr/sbin/nginx").is_file():
custom_configs = self.__db.get_custom_configs()
if custom_configs:
self.__logger.info("Refreshing custom configs ...")
# Remove old custom configs files
for file in glob("/data/configs/*"):
if Path(file).is_symlink() or Path(file).is_file():
Path(file).unlink()
elif Path(file).is_dir():
rmtree(file, ignore_errors=False)
generate_custom_configs(custom_configs)
self.__logger.info("Custom configs refreshed successfully")
def save_configs(self) -> str:
custom_configs = []
root_dirs = listdir("/etc/bunkerweb/configs")
@ -109,8 +140,8 @@ class ConfigFiles:
return f"The file {file_path} was successfully created", 0
def edit_folder(self, path: str, name: str, old_name: str) -> Tuple[str, int]:
new_folder_path = dirname(join(path, name))
old_folder_path = dirname(join(path, old_name))
new_folder_path = join(dirname(path), name)
old_folder_path = join(dirname(path), old_name)
if old_folder_path == new_folder_path:
return (
@ -131,8 +162,8 @@ class ConfigFiles:
def edit_file(
self, path: str, name: str, old_name: str, content: str
) -> Tuple[str, int]:
new_path = dirname(join(path, name))
old_path = dirname(join(path, old_name))
new_path = join(dirname(path), name)
old_path = join(dirname(path), old_name)
try:
file_content = Path(old_path).read_text()

View File

@ -1,5 +1,5 @@
from pathlib import Path
from subprocess import run
from subprocess import DEVNULL, STDOUT, run
from sys import path as sys_path
from typing import Any, Optional, Union
@ -56,8 +56,9 @@ class Instance:
if self._type == "local":
return (
run(
["sudo", "systemctl", "reload", "bunkerweb"],
capture_output=True,
["sudo", "/usr/sbin/nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=STDOUT,
).returncode
== 0
)
@ -68,8 +69,9 @@ class Instance:
if self._type == "local":
return (
run(
["sudo", "systemctl", "start", "bunkerweb"],
capture_output=True,
["sudo", "/usr/sbin/nginx"],
stdin=DEVNULL,
stderr=STDOUT,
).returncode
== 0
)
@ -80,8 +82,9 @@ class Instance:
if self._type == "local":
return (
run(
["sudo", "systemctl", "stop", "bunkerweb"],
capture_output=True,
["sudo", "/usr/sbin/nginx", "-s", "stop"],
stdin=DEVNULL,
stderr=STDOUT,
).returncode
== 0
)
@ -92,8 +95,9 @@ class Instance:
if self._type == "local":
return (
run(
["sudo", "systemctl", "restart", "bunkerweb"],
capture_output=True,
["sudo", "/usr/sbin/nginx", "-s", "restart"],
stdin=DEVNULL,
stderr=STDOUT,
).returncode
== 0
)
@ -159,27 +163,24 @@ class Instances:
status = "up"
apis = []
for instance in self.__docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
api_http_port = None
api_server_name = None
api_http_port = None
api_server_name = None
for var in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][
"Env"
]:
if var.startswith("API_HTTP_PORT="):
api_http_port = var.replace("API_HTTP_PORT=", "", 1)
elif var.startswith("API_SERVER_NAME="):
api_server_name = var.replace("API_SERVER_NAME=", "", 1)
for var in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][
"Env"
]:
if var.startswith("API_HTTP_PORT="):
api_http_port = var.replace("API_HTTP_PORT=", "", 1)
elif var.startswith("API_SERVER_NAME="):
api_server_name = var.replace("API_SERVER_NAME=", "", 1)
for task in instance.tasks():
apis.append(
API(
f"http://{instance.name}.{task['NodeID']}.{task['ID']}:{api_http_port or '5000'}",
host=api_server_name or "bwapi",
)
for task in instance.tasks():
apis.append(
API(
f"http://{instance.name}.{task['NodeID']}.{task['ID']}:{api_http_port or '5000'}",
host=api_server_name or "bwapi",
)
)
apiCaller = ApiCaller(apis=apis)
instances.append(
@ -202,33 +203,17 @@ class Instances:
and "bunkerweb.io/INSTANCE" in pod.metadata.annotations
):
env_variables = {
e.name: e.value for e in pod.spec.containers[0].env
env.name: env.value or "" for env in pod.spec.containers[0].env
}
apis = []
config.load_incluster_config()
corev1 = self.__kubernetes_client.CoreV1Api()
for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if (
pod.metadata.annotations != None
and "bunkerweb.io/INSTANCE" in pod.metadata.annotations
):
api_http_port = None
api_server_name = None
for pod_env in pod.spec.containers[0].env:
if pod_env.name == "API_HTTP_PORT":
api_http_port = pod_env.value or "5000"
elif pod_env.name == "API_SERVER_NAME":
api_server_name = pod_env.value or "bwapi"
apis.append(
API(
f"http://{pod.status.pod_ip}:{api_http_port or '5000'}",
host=api_server_name or "bwapi",
)
apiCaller = ApiCaller(
apis=[
API(
f"http://{pod.status.pod_ip}:{env_variables.get('API_HTTP_PORT', '5000')}",
host=env_variables.get("API_SERVER_NAME", "bwapi"),
)
apiCaller = ApiCaller(apis=apis)
]
)
status = "up"
if pod.status.conditions is not None:

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@ import {
class Download {
constructor(prefix = "cache") {
this.prefix = prefix;
this.listContainer = document.querySelector(`[cache-container]`);
this.listContainer = document.querySelector(`[data-cache-container]`);
this.init();
}
@ -16,11 +16,11 @@ class Download {
this.listContainer.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-download`)
e.target.closest("button").hasAttribute(`data-${this.prefix}-download`)
) {
const btnEl = e.target.closest("button");
const jobName = btnEl.getAttribute("cache-download");
const fileName = btnEl.getAttribute("cache-file");
const jobName = btnEl.getAttribute("data-cache-download");
const fileName = btnEl.getAttribute("data-cache-file");
this.sendFileToDL(jobName, fileName);
}
} catch (err) {}

View File

@ -2,9 +2,9 @@ import { Checkbox } from "./utils/form.js";
class Menu {
constructor() {
this.sidebarEl = document.querySelector("[sidebar-menu]");
this.toggleBtn = document.querySelector("[sidebar-menu-toggle]");
this.closeBtn = document.querySelector("[sidebar-menu-close]");
this.sidebarEl = document.querySelector("[data-sidebar-menu]");
this.toggleBtn = document.querySelector("[data-sidebar-menu-toggle]");
this.closeBtn = document.querySelector("[data-sidebar-menu-close]");
this.toggleBtn.addEventListener("click", this.toggle.bind(this));
this.closeBtn.addEventListener("click", this.close.bind(this));
@ -15,7 +15,7 @@ class Menu {
window.addEventListener("click", (e) => {
try {
if (
e.target.closest("aside").hasAttribute("sidebar-menu") &&
e.target.closest("aside").hasAttribute("data-sidebar-menu") &&
e.target.closest("button").getAttribute("role") === "tab"
) {
this.close();
@ -53,7 +53,7 @@ class News {
}
render(lastNews) {
const newsContainer = document.querySelector("[news-container]");
const newsContainer = document.querySelector("[data-news-container]");
//remove default message
newsContainer.textContent = "";
//render last news
@ -76,7 +76,7 @@ class News {
);
//add to DOM
document
.querySelector("[news-container]")
.querySelector("[data-news-container]")
.insertAdjacentHTML("afterbegin", cardHTML);
});
}
@ -157,8 +157,8 @@ class Sidebar {
class darkMode {
constructor() {
this.htmlEl = document.querySelector("html");
this.darkToggleEl = document.querySelector("[dark-toggle]");
this.darkToggleLabel = document.querySelector("[dark-toggle-label]");
this.darkToggleEl = document.querySelector("[data-dark-toggle]");
this.darkToggleLabel = document.querySelector("[data-dark-toggle-label]");
this.csrf = document.querySelector("input#csrf_token");
this.init();
}
@ -197,8 +197,8 @@ class darkMode {
class FlashMsg {
constructor() {
this.openBtn = document.querySelector("[flash-sidebar-open]");
this.flashCount = document.querySelector("[flash-count]");
this.openBtn = document.querySelector("[data-flash-sidebar-open]");
this.flashCount = document.querySelector("[data-flash-count]");
this.isMsgCheck = false;
this.init();
}
@ -211,7 +211,7 @@ class FlashMsg {
//stop animate if clicked once
this.openBtn.addEventListener("click", (e) => {
try {
if (e.target.closest("button").hasAttribute("flash-sidebar-open")) {
if (e.target.closest("button").hasAttribute("data-flash-sidebar-open")) {
this.isMsgCheck = true;
}
} catch (err) {}
@ -219,14 +219,14 @@ class FlashMsg {
//remove flash message and change count
window.addEventListener("click", (e) => {
try {
if (e.target.closest("button").hasAttribute("close-flash-message")) {
if (e.target.closest("button").hasAttribute("data-close-flash-message")) {
//remove logic
const closeBtn = e.target.closest("button");
const flashEl = closeBtn.closest("[flash-message]");
const flashEl = closeBtn.closest("[data-flash-message]");
flashEl.remove();
//update count
this.flashCount.textContent =
document.querySelectorAll("[flash-message]").length;
document.querySelectorAll("[data-flash-message]").length;
}
} catch (err) {}
});
@ -254,9 +254,9 @@ class FlashMsg {
class Loader {
constructor() {
this.menuContainer = document.querySelector("[menu-container]");
this.logoContainer = document.querySelector("[loader]");
this.logoEl = document.querySelector("[loader-img]");
this.menuContainer = document.querySelector("[data-menu-container]");
this.logoContainer = document.querySelector("[data-loader]");
this.logoEl = document.querySelector("[data-loader-img]");
this.isLoading = true;
this.init();
}
@ -292,14 +292,14 @@ class Loader {
const setLoader = new Loader();
const setMenu = new Menu();
const setNewsSidebar = new Sidebar(
"[sidebar-info]",
"[sidebar-info-open]",
"[sidebar-info-close]"
"[data-sidebar-info]",
"[data-sidebar-info-open]",
"[data-sidebar-info-close]"
);
const setFlashSidebar = new Sidebar(
"[flash-sidebar]",
"[flash-sidebar-open]",
"[flash-sidebar-close]"
"[data-flash-sidebar]",
"[data-flash-sidebar-open]",
"[data-flash-sidebar-close]"
);
const setNews = new News();
const setDarkM = new darkMode();

View File

@ -1,4 +1,4 @@
import { Checkbox, Select, Password } from "./utils/form.js";
import { Checkbox, Select, Password, DisabledPop } from "./utils/form.js";
import {
Popover,
Tabs,
@ -15,12 +15,12 @@ class Multiple {
init() {
//hide multiple btn if no multiple exist on a plugin
const multiples = document.querySelectorAll(
`[${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`
);
multiples.forEach((container) => {
if (container.querySelectorAll(`[setting-container]`).length <= 0)
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
container.parentElement
.querySelector("[multiple-handler]")
.querySelector("[data-multiple-handler]")
.classList.add("hidden");
});
}
@ -29,6 +29,7 @@ class Multiple {
const setCheckbox = new Checkbox();
const setSelect = new Select();
const setPassword = new Password();
const setDisabledPop = new DisabledPop();
const setPopover = new Popover("main", "global-config");
const setTabs = new Tabs("[global-config-tabs]", "global-config");
@ -36,5 +37,5 @@ const format = new FormatValue();
const setMultiple = new Multiple("global-config");
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[service-content='settings']"
"[data-service-content='settings']"
);

View File

@ -13,12 +13,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select`) &&
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
@ -32,12 +32,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select-dropdown-btn`)
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`${this.prefix}-setting-select-dropdown-btn`
`data-${this.prefix}-setting-select-dropdown-btn`
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
@ -46,10 +46,13 @@ class Dropdown {
this.setSelectNewValue(btnSetting, btnValue);
//close dropdown and change style
this.hideDropdown(btnSetting);
this.changeDropBtnStyle(btnSetting, btn);
if (!e.target.closest("button").hasAttribute(`data-${prefix}-file`)) {
this.changeDropBtnStyle(btnSetting, btn);
}
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("_type"));
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
@ -58,15 +61,15 @@ class Dropdown {
closeAllDrop() {
const drops = document.querySelectorAll(
`[${this.prefix}-setting-select-dropdown]`
`[data-${this.prefix}-setting-select-dropdown]`
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[${this.prefix}-setting-select="${drop.getAttribute(
`${this.prefix}-setting-select-dropdown`
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`
)}"]`
)
.classList.remove("rotate-180");
@ -75,7 +78,7 @@ class Dropdown {
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
@ -83,39 +86,38 @@ class Dropdown {
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select="${btnSetting}"]`
`[data-${this.prefix}-setting-select="${btnSetting}"]`
);
selectCustom.querySelector(
`[${this.prefix}-setting-select-text]`
`[data-${this.prefix}-setting-select-text]`
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${btnSetting}"]`
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"bg-primary",
"dark:bg-primary",
"bg-primary",
"bg-primary",
"text-gray-300",
"text-gray-300"
);
@ -133,13 +135,13 @@ class Dropdown {
toggleSelectBtn(e) {
const attribut = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${attribut}"]`
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`
);
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${attribut}"]`
`svg[data-${this.prefix}-setting-select="${attribut}"]`
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
@ -173,7 +175,7 @@ class Dropdown {
class Filter {
constructor(prefix = "jobs") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.successValue = "all";
this.reloadValue = "all";
@ -189,12 +191,14 @@ class Filter {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"success"
) {
setTimeout(() => {
const value = document
.querySelector(`[${this.prefix}-setting-select-text="success"]`)
.querySelector(
`[data-${this.prefix}-setting-select-text="success"]`
)
.textContent.trim();
this.successValue = value;
@ -210,12 +214,14 @@ class Filter {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"reload"
) {
setTimeout(() => {
const value = document
.querySelector(`[${this.prefix}-setting-select-text="reload"]`)
.querySelector(
`[data-${this.prefix}-setting-select-text="reload"]`
)
.textContent.trim();
this.reloadValue = value;
@ -232,7 +238,7 @@ class Filter {
}
filter() {
const jobs = document.querySelector(`[${this.prefix}-list]`).children;
const jobs = document.querySelector(`[data-${this.prefix}-list]`).children;
if (jobs.length === 0) return;
//reset
for (let i = 0; i < jobs.length; i++) {
@ -250,8 +256,8 @@ class Filter {
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const type = el
.querySelector(`[${this.prefix}-success]`)
.getAttribute(`${this.prefix}-success`)
.querySelector(`[data-${this.prefix}-success]`)
.getAttribute(`data-${this.prefix}-success`)
.trim();
if (type !== this.successValue) el.classList.add("hidden");
}
@ -262,8 +268,8 @@ class Filter {
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const type = el
.querySelector(`[${this.prefix}-reload]`)
.getAttribute(`${this.prefix}-reload`)
.querySelector(`[data-${this.prefix}-reload]`)
.getAttribute(`data-${this.prefix}-reload`)
.trim();
if (type !== this.reloadValue) el.classList.add("hidden");
}
@ -275,15 +281,15 @@ class Filter {
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const name = el
.querySelector(`[${this.prefix}-name`)
.querySelector(`[data-${this.prefix}-name]`)
.textContent.trim()
.toLowerCase();
const date = el
.querySelector(`[${this.prefix}-last_run`)
.querySelector(`[data-${this.prefix}-last_run]`)
.textContent.trim()
.toLowerCase();
const every = el
.querySelector(`[${this.prefix}-every`)
.querySelector(`[data-${this.prefix}-every]`)
.textContent.trim()
.toLowerCase();
@ -300,7 +306,7 @@ class Filter {
class Download {
constructor(prefix = "jobs") {
this.prefix = prefix;
this.listContainer = document.querySelector(`[${this.prefix}-list]`);
this.listContainer = document.querySelector(`[data-${this.prefix}-list]`);
this.init();
}
@ -308,11 +314,13 @@ class Download {
this.listContainer.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-download`)
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-download`)
) {
const btnEl = e.target.closest("button");
const jobName = btnEl.getAttribute("jobs-download");
const fileName = btnEl.getAttribute("jobs-file");
const jobName = btnEl.getAttribute("data-jobs-download");
const fileName = btnEl.getAttribute("data-jobs-file");
this.sendFileToDL(jobName, fileName);
}
} catch (err) {}

View File

@ -1,4 +1,4 @@
import { Checkbox } from "./utils/form.js";
import { Checkbox, Select } from "./utils/form.js";
class Dropdown {
constructor(prefix = "logs") {
@ -14,12 +14,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select`) &&
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
@ -32,12 +32,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select-dropdown-btn`)
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`${this.prefix}-setting-select-dropdown-btn`
`data-${this.prefix}-setting-select-dropdown-btn`
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
@ -49,7 +49,7 @@ class Dropdown {
this.changeDropBtnStyle(btnSetting, btn);
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("_type"));
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
@ -58,15 +58,15 @@ class Dropdown {
closeAllDrop() {
const drops = document.querySelectorAll(
`[${this.prefix}-setting-select-dropdown]`
`[data-${this.prefix}-setting-select-dropdown]`
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[${this.prefix}-setting-select="${drop.getAttribute(
`${this.prefix}-setting-select-dropdown`
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`
)}"]`
)
.classList.remove("rotate-180");
@ -75,7 +75,7 @@ class Dropdown {
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
@ -83,39 +83,38 @@ class Dropdown {
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select="${btnSetting}"]`
`[data-${this.prefix}-setting-select="${btnSetting}"]`
);
selectCustom.querySelector(
`[${this.prefix}-setting-select-text]`
`[data-${this.prefix}-setting-select-text]`
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${btnSetting}"]`
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
btn.classList.remove(
"bg-primary",
"dark:bg-primary",
"bg-primary",
"bg-primary",
"text-gray-300",
"text-gray-300"
);
@ -133,13 +132,13 @@ class Dropdown {
toggleSelectBtn(e) {
const attribut = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${attribut}"]`
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`
);
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${attribut}"]`
`svg[data-${this.prefix}-setting-select="${attribut}"]`
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
@ -174,7 +173,7 @@ class FetchLogs {
constructor(prefix = "logs") {
this.prefix = prefix;
this.instance = document.querySelector(
`[${this.prefix}-setting-select-text="instances"]`
`[data-${this.prefix}-setting-select-text="instances"]`
);
this.instanceName = "";
this.updateInp = document.querySelector("input#update-date");
@ -184,17 +183,67 @@ class FetchLogs {
this.toDateInp = document.querySelector("input#to-date");
this.fromDate = "";
this.toDate = "";
this.isLiveUpdate = false;
this.isLiveON = false;
this.updateDelay = 2000;
this.lastUpdate = Date.now() - 86400000;
this.container = document.querySelector(`[${this.prefix}-settings]`);
this.logListContainer = document.querySelector(`[${this.prefix}-list]`);
this.submitSettings = document.querySelector("button#submit-settings");
this.container = document.querySelector(`[data-${this.prefix}-settings]`);
this.logListContainer = document.querySelector(
`[data-${this.prefix}-list]`
);
this.submitDate = document.querySelector("button[data-submit-date]");
this.submitLive = document.querySelector("button[data-submit-live]");
this.init();
}
init() {
this.submitSettings.addEventListener("click", (e) => {
//change submit btn text
this.liveUpdateInp.addEventListener("change", (e) => {
const currValue = this.liveUpdateInp.getAttribute("value");
if (currValue === "yes") {
this.submitDate.classList.add("hidden");
this.submitLive.classList.remove("hidden");
}
if (currValue === "no") {
this.submitDate.classList.remove("hidden");
this.submitLive.classList.add("hidden");
}
});
this.submitLive.addEventListener("click", (e) => {
//change state
this.submitLive.setAttribute(
"data-submit-live",
this.submitLive.getAttribute("data-submit-live") === "yes"
? "no"
: "yes"
);
if (this.submitLive.getAttribute("data-submit-live") === "yes") {
this.submitLive.textContent = "Stop live";
//check if min setting to fetch
const isSettings = this.isSettings();
if (!isSettings) return;
//if can fetch, remove previous logs
this.logListContainer.textContent = "";
//get new settings data
this.setSettings();
//two cases
//live update is checked, get data since last update or all if undefined
return this.getLogsSinceLastUpdate();
}
if (this.submitLive.getAttribute("data-submit-live") === "no") {
this.submitLive.textContent = "Go live";
}
});
//when submit btn click
this.submitDate.addEventListener("click", (e) => {
//logic for the current state
//check if min setting to fetch
const isSettings = this.isSettings();
if (!isSettings) return;
@ -203,13 +252,9 @@ class FetchLogs {
//get new settings data
this.setSettings();
//two cases
if (this.isLiveUpdate) {
//live update is checked, get data since last update or all if undefined
return this.getLogsSinceLastUpdate();
} else {
//get data from the range from/to (defined or default)
return this.getLogsFromToDate();
}
//get data from the range from/to (defined or default)
return this.getLogsFromToDate();
});
//to date is disabled if live update is set
@ -262,16 +307,14 @@ class FetchLogs {
//set update delay
this.updateDelay =
this.updateDelayInp.value * 1000 ? this.updateDelayInp.value : 2000;
//look if live update
this.isLiveUpdate = this.liveUpdateInp.checked;
}
goBottomList() {
document
.querySelector(`[${this.prefix}-list]`)
.querySelector(`[data-${this.prefix}-list]`)
.scrollTo(
0,
document.querySelector(`[${this.prefix}-list]`).scrollHeight
document.querySelector(`[data-${this.prefix}-list]`).scrollHeight
);
}
@ -283,7 +326,7 @@ class FetchLogs {
`${location.href}/${this.instanceName}?from_date=${this.fromDate}&to_date=${this.toDate}`
);
const data = await res.json();
return await this.showLogs(data);
return await this.showLogsDate(data);
}
//case from date and to date defined
if (!this.toDate) {
@ -291,7 +334,7 @@ class FetchLogs {
`${location.href}/${this.instanceName}?from_date=${this.fromDate}`
);
const data = await res.json();
return await this.showLogs(data);
return await this.showLogsDate(data);
}
}
@ -301,10 +344,10 @@ class FetchLogs {
(this.lastUpdate ? `?last_update=${this.lastUpdate}` : "")
);
const data = await response.json();
return await this.showLogs(data);
return await this.showLogsLive(data);
}
async showLogs(res) {
async showLogsDate(res, type) {
//get last update timestamp
this.lastUpdate = res.last_update;
//render logs
@ -318,21 +361,53 @@ class FetchLogs {
typeEl.className =
"dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0";
typeEl.textContent = log.type;
typeEl.setAttribute("logs-type", "");
typeEl.setAttribute("data-logs-type", "");
logContainer.appendChild(typeEl);
//content
const contentEl = document.createElement("p");
contentEl.className =
"dark:text-gray-400 dark:opacity-80 text-sm col-span-9 m-0";
contentEl.textContent = log.content;
contentEl.setAttribute("logs-content", "");
contentEl.setAttribute("data-logs-content", "");
logContainer.appendChild(contentEl);
//show on DOM
this.logListContainer.appendChild(logContainer);
});
//force scroll when no live update
const logListEl = document.querySelector(`[data-${this.prefix}-list]`);
logListEl.scrollTop = logListEl.scrollHeight;
}
async showLogsLive(res, type) {
//get last update timestamp
this.lastUpdate = res.last_update;
//render logs
res.logs.forEach((log) => {
//container
const logContainer = document.createElement("li");
logContainer.className =
"grid grid-cols-12 border-b border-gray-300 py-2";
//type
const typeEl = document.createElement("p");
typeEl.className =
"dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0";
typeEl.textContent = log.type;
typeEl.setAttribute("data-logs-type", "");
logContainer.appendChild(typeEl);
//content
const contentEl = document.createElement("p");
contentEl.className =
"dark:text-gray-400 dark:opacity-80 text-sm col-span-9 m-0";
contentEl.textContent = log.content;
contentEl.setAttribute("data-logs-content", "");
logContainer.appendChild(contentEl);
//show on DOM
this.logListContainer.appendChild(logContainer);
});
//if live update, refetch to last update every defined delay
if (this.isLiveUpdate) {
if (this.submitLive.getAttribute("data-submit-live") === "yes") {
setTimeout(() => {
this.getLogsSinceLastUpdate();
}, this.updateDelay);
@ -343,7 +418,7 @@ class FetchLogs {
class Filter {
constructor(prefix = "logs") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.lastType = "all";
this.initHandler();
@ -356,13 +431,13 @@ class Filter {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"types"
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`${this.prefix}-setting-select-dropdown-btn`
`data-${this.prefix}-setting-select-dropdown-btn`
);
this.lastType = btnValue;
@ -378,7 +453,7 @@ class Filter {
}
filter() {
const logs = document.querySelector(`[${this.prefix}-list]`).children;
const logs = document.querySelector(`[data-${this.prefix}-list]`).children;
if (logs.length === 0) return;
//reset
for (let i = 0; i < logs.length; i++) {
@ -395,7 +470,7 @@ class Filter {
for (let i = 0; i < logs.length; i++) {
const el = logs[i];
const typeEl = el.querySelector("[logs-type]");
const typeEl = el.querySelector("[data-logs-type]");
if (this.lastType !== "misc" && typeEl.textContent !== this.lastType)
el.classList.add("hidden");
if (
@ -413,7 +488,7 @@ class Filter {
for (let i = 0; i < logs.length; i++) {
const el = logs[i];
const content = el
.querySelector("[logs-content]")
.querySelector("[data-logs-content]")
.textContent.trim()
.toLowerCase();
if (!content.includes(keyword)) el.classList.add("hidden");
@ -422,6 +497,7 @@ class Filter {
}
const setCheckbox = new Checkbox();
const setSelect = new Select();
const dropdown = new Dropdown("logs");
const setLogs = new FetchLogs();
const setFilter = new Filter("logs");

View File

@ -12,12 +12,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select`) &&
.hasAttribute(`data-${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
@ -31,12 +31,12 @@ class Dropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select-dropdown-btn`)
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
) {
const btn = e.target.closest("button");
const btnValue = btn.getAttribute("value");
const btnSetting = btn.getAttribute(
`${this.prefix}-setting-select-dropdown-btn`
`data-${this.prefix}-setting-select-dropdown-btn`
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
@ -48,7 +48,7 @@ class Dropdown {
this.changeDropBtnStyle(btnSetting, btn);
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("_type"));
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
}
}
} catch (err) {}
@ -57,15 +57,15 @@ class Dropdown {
closeAllDrop() {
const drops = document.querySelectorAll(
`[${this.prefix}-setting-select-dropdown]`
`[data-${this.prefix}-setting-select-dropdown]`
);
drops.forEach((drop) => {
drop.classList.add("hidden");
drop.classList.remove("flex");
document
.querySelector(
`svg[${this.prefix}-setting-select="${drop.getAttribute(
`${this.prefix}-setting-select-dropdown`
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
`data-${this.prefix}-setting-select-dropdown`
)}"]`
)
.classList.remove("rotate-180");
@ -74,7 +74,7 @@ class Dropdown {
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
@ -82,30 +82,30 @@ class Dropdown {
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select="${btnSetting}"]`
`[data-${this.prefix}-setting-select="${btnSetting}"]`
);
selectCustom.querySelector(
`[${this.prefix}-setting-select-text]`
`[data-${this.prefix}-setting-select-text]`
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
//svg effect
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${btnSetting}"]`
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
@ -132,13 +132,13 @@ class Dropdown {
toggleSelectBtn(e) {
const attribut = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
.getAttribute(`data-${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${attribut}"]`
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`
);
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${attribut}"]`
`svg[data-${this.prefix}-setting-select="${attribut}"]`
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
@ -172,7 +172,7 @@ class Dropdown {
class Filter {
constructor(prefix = "plugins") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.lastType = "all";
this.initHandler();
@ -185,7 +185,7 @@ class Filter {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
"types"
) {
const btn = e.target.closest("button");
@ -204,7 +204,7 @@ class Filter {
}
filter() {
const logs = document.querySelector(`[${this.prefix}-list]`).children;
const logs = document.querySelector(`[data-${this.prefix}-list]`).children;
if (logs.length === 0) return;
//reset
for (let i = 0; i < logs.length; i++) {
@ -220,7 +220,7 @@ class Filter {
if (this.lastType === "all") return;
for (let i = 0; i < logs.length; i++) {
const el = logs[i];
const type = el.getAttribute(`${this.prefix}-external`).trim();
const type = el.getAttribute(`data-${this.prefix}-external`).trim();
if (type !== this.lastType) el.classList.add("hidden");
}
}
@ -231,7 +231,7 @@ class Filter {
for (let i = 0; i < logs.length; i++) {
const el = logs[i];
const content = el
.querySelector(`[${this.prefix}-content]`)
.querySelector(`[data-${this.prefix}-content]`)
.textContent.trim()
.toLowerCase();
@ -242,7 +242,7 @@ class Filter {
class Upload {
constructor() {
this.container = document.querySelector("[plugins-upload]");
this.container = document.querySelector("[data-plugins-upload]");
this.form = document.querySelector("#dropzone-form");
this.dropZoneElement = document.querySelector(".drop-zone");
this.fileInput = document.querySelector(".file-input");
@ -286,8 +286,8 @@ class Upload {
//close fail/success log
this.container.addEventListener("click", (e) => {
try {
if (e.target.closest("button").hasAttribute("upload-message-delete")) {
const message = e.target.closest("div[upload-message]");
if (e.target.closest("button").hasAttribute("data-upload-message-delete")) {
const message = e.target.closest("div[data-upload-message]");
message.remove();
}
} catch (err) {}
@ -362,18 +362,21 @@ class Upload {
}
allowReload() {
const reloadBtn = document.querySelector("[plugin-reload-btn]");
const reloadBtn = document.querySelector("[data-plugin-reload-btn]");
reloadBtn.removeAttribute("disabled");
}
fileLoad(name, fileSize) {
const str = `<div u class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
const str = `<div class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
<div class="flex items-center justify-between">
<svg class="fill-sky-500 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM385 215c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-71-71V392c0 13.3-10.7 24-24 24s-24-10.7-24-24V177.9l-71 71c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9L239 103c9.4-9.4 24.6-9.4 33.9 0L385 215z"/></svg>
<svg class="fill-sky-500 stroke-sky-500 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
<svg class=" fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-3 w-3 " xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
<svg class=" fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-3 w-3 " viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50"/>
</svg>
</div>
</div>
@ -382,7 +385,7 @@ class Upload {
}
fileSuccess(name, fileSize) {
const str = `<div upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
<div class="flex items-center justify-between">
<svg
class="fill-green-500 h-5 w-5"
@ -395,10 +398,10 @@ class Upload {
</svg>
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
<button type="button" upload-message-delete>
<button type="button" data-upload-message-delete>
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
</svg>
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
</svg>
</button>
</div>
</div>
@ -407,7 +410,7 @@ class Upload {
}
fileFail(name, fileSize) {
const str = `<div upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
<div class="flex items-center justify-between">
<svg
class="fill-red-500 h-5 w-5 mr-4"
@ -420,7 +423,7 @@ class Upload {
</svg>
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
<button type="button" upload-message-delete>
<button type="button" data-upload-message-delete>
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
@ -437,13 +440,13 @@ class Upload {
class Modal {
constructor(prefix = "plugins") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-list]`);
this.modal = document.querySelector(`[${this.prefix}-modal]`);
this.container = document.querySelector(`[data-${this.prefix}-list]`);
this.modal = document.querySelector(`[data-${this.prefix}-modal]`);
this.modalNameInp = this.modal.querySelector("input#name");
this.modalExtInp = this.modal.querySelector("input#external");
this.modalTitle = this.modal.querySelector(`[${this.prefix}-modal-title]`);
this.modalTxt = this.modal.querySelector(`[${this.prefix}-modal-text]`);
this.modalTitle = this.modal.querySelector(`[data-${this.prefix}-modal-title]`);
this.modalTxt = this.modal.querySelector(`[data-${this.prefix}-modal-text]`);
this.init();
}
@ -452,7 +455,7 @@ class Modal {
//DELETE HANDLER
try {
if (
e.target.closest("button").getAttribute(`${this.prefix}-action`) ===
e.target.closest("button").getAttribute(`data-${this.prefix}-action`) ===
"delete"
) {
const btnEl = e.target.closest("button");
@ -466,7 +469,7 @@ class Modal {
//CLOSE MODAL HANDLER
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-modal-close`)
e.target.closest("button").hasAttribute(`data-${this.prefix}-modal-close`)
) {
this.hideModal();
}
@ -482,8 +485,8 @@ class Modal {
this.modalTxt.textContent = `Are you sure you want to delete ${elName} ?`;
//external
const isExternal = el
.closest("[plugins-external]")
.getAttribute("plugins-external")
.closest("[data-plugins-external]")
.getAttribute("data-plugins-external")
.trim()
.includes("external")
? "True"

View File

@ -1,4 +1,4 @@
import { Checkbox, Select, Password } from "./utils/form.js";
import { Checkbox, Select, Password, DisabledPop } from "./utils/form.js";
import {
Popover,
Tabs,
@ -9,15 +9,19 @@ import {
class ServiceModal {
constructor() {
//modal elements
this.modal = document.querySelector("[services-modal]");
this.modalTitle = this.modal.querySelector("[services-modal-title]");
this.modalTabs = this.modal.querySelector(["[services-tabs]"]);
this.modalTabsHeader = this.modal.querySelector(["[services-tabs-header]"]);
this.modal = document.querySelector("[data-services-modal]");
this.modalTitle = this.modal.querySelector("[data-services-modal-title]");
this.modalTabs = this.modal.querySelector(["[data-services-tabs]"]);
this.modalTabsHeader = this.modal.querySelector([
"[data-services-tabs-header]",
]);
this.modalCard = this.modal.querySelector("[services-modal-card]");
this.modalCard = this.modal.querySelector("[data-services-modal-card]");
//modal forms
this.formNewEdit = this.modal.querySelector("[services-modal-form]");
this.formDelete = this.modal.querySelector("[services-modal-form-delete]");
this.formNewEdit = this.modal.querySelector("[data-services-modal-form]");
this.formDelete = this.modal.querySelector(
"[data-services-modal-form-delete]"
);
//container
this.container = document.querySelector("main");
this.init();
@ -27,8 +31,12 @@ class ServiceModal {
//to update modal data on new button click
getActionAndServName(target) {
const action = target.closest("button").getAttribute("services-action");
const serviceName = target.closest("button").getAttribute("services-name");
const action = target
.closest("button")
.getAttribute("data-services-action");
const serviceName = target
.closest("button")
.getAttribute("data-services-name");
return [action, serviceName];
}
@ -37,7 +45,9 @@ class ServiceModal {
this.modal.addEventListener("click", (e) => {
//close
try {
if (e.target.closest("button").hasAttribute("services-modal-close")) {
if (
e.target.closest("button").hasAttribute("data-services-modal-close")
) {
this.closeModal();
}
} catch (err) {}
@ -47,7 +57,8 @@ class ServiceModal {
//edit action
try {
if (
e.target.closest("button").getAttribute("services-action") === "edit"
e.target.closest("button").getAttribute("data-services-action") ===
"edit"
) {
//set form info and right form
const [action, serviceName] = this.getActionAndServName(e.target);
@ -55,19 +66,21 @@ class ServiceModal {
//get service data and parse it
//multiple type logic is launch at same time on relate class
const servicesSettings = e.target
.closest("[services-service]")
.querySelector("[services-settings]")
.getAttribute("value");
.closest("[data-services-service]")
.querySelector("[data-services-settings]")
.getAttribute("data-value");
const obj = JSON.parse(servicesSettings);
this.updateModalData(obj);
//show modal
this.changeSubmitBtnName("EDIT");
this.openModal();
}
} catch (err) {}
//new action
try {
if (
e.target.closest("button").getAttribute("services-action") === "new"
e.target.closest("button").getAttribute("data-services-action") ===
"new"
) {
//set form info and right form
const [action, serviceName] = this.getActionAndServName(e.target);
@ -77,16 +90,18 @@ class ServiceModal {
//server name is unset
const inpServName = document.querySelector("input#SERVER_NAME");
inpServName.getAttribute("value", "");
inpServName.removeAttribute("disabled", "");
inpServName.value = "";
//show modal
this.changeSubmitBtnName("CREATE");
this.openModal();
}
} catch (err) {}
//delete action
try {
if (
e.target.closest("button").getAttribute("services-action") ===
e.target.closest("button").getAttribute("data-services-action") ===
"delete"
) {
//set form info and right form
@ -99,6 +114,13 @@ class ServiceModal {
});
}
changeSubmitBtnName(text) {
const submitBtn = document.querySelector(
"button[data-services-modal-submit]"
);
submitBtn.textContent = text;
}
setSettingsDefault() {
const inps = this.modal.querySelectorAll("input");
inps.forEach((inp) => {
@ -112,20 +134,20 @@ class ServiceModal {
return;
//for all other settings values
const defaultMethod = "default";
const defaultVal = inp.getAttribute("default-value") || "";
const defaultMethod = inp.getAttribute("data-default-method");
const defaultVal = inp.getAttribute("data-default-value") || "";
//SET VALUE
if (inp.getAttribute("type") === "checkbox") {
inp.checked = defaultVal === "yes" ? true : false;
inp.setAttribute("value", defaultVal);
inp.value = defaultVal;
inp.setAttribute("method", defaultMethod);
inp.setAttribute("data-method", defaultMethod);
}
if (inp.getAttribute("type") !== "checkbox") {
inp.setAttribute("value", defaultVal);
inp.setAttribute("method", defaultMethod);
inp.setAttribute("data-method", defaultMethod);
}
//SET METHOD
@ -135,17 +157,19 @@ class ServiceModal {
const selects = this.modal.querySelectorAll("select");
selects.forEach((select) => {
const defaultMethod = "default";
const defaultVal = select.getAttribute("default-value") || "";
const defaultVal = select.getAttribute("data-default-value") || "";
document
.querySelector(
`[setting-select=${select.getAttribute("setting-select-default")}]`
`[data-setting-select=${select.getAttribute(
"data-setting-select-default"
)}]`
)
.removeAttribute("disabled");
select.parentElement
.querySelector(
`button[setting-select-dropdown-btn][value='${defaultVal}']`
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
)
.click();
@ -178,7 +202,7 @@ class ServiceModal {
if (action === "delete") {
this.showDeleteForm();
formEl.querySelector(
`[services-modal-text]`
`[data-services-modal-text]`
).textContent = `Are you sure you want to delete ${serviceName} ?`;
const nameInp = formEl.querySelector(`input[name="SERVER_NAME"]`);
nameInp.setAttribute("value", serviceName);
@ -258,7 +282,7 @@ class ServiceModal {
) {
inp.setAttribute("value", value);
inp.value = value;
inp.setAttribute("method", method);
inp.setAttribute("data-method", method);
}
//for checkbox
if (
@ -267,24 +291,24 @@ class ServiceModal {
) {
inp.checked = value === "yes" ? true : false;
inp.setAttribute("value", value);
inp.setAttribute("method", method);
inp.setAttribute("data-method", method);
if (inp.hasAttribute("disabled")) {
const hidden_inp = inp
.closest("div[checkbox-handler]")
.closest("div[data-checkbox-handler]")
.querySelector("input[type='hidden']");
hidden_inp.setAttribute("value", value);
hidden_inp.setAttribute("method", method);
hidden_inp.setAttribute("data-method", method);
}
}
//for select
if (inp.tagName === "SELECT") {
inp.parentElement
.querySelector(
`button[setting-select-dropdown-btn][value='${value}']`
`button[data-setting-select-dropdown-btn][value='${value}']`
)
.click();
inp.setAttribute("method", method);
inp.setAttribute("data-method", method);
}
//check disabled/enabled after setting values and methods
@ -307,7 +331,7 @@ class ServiceModal {
openModal() {
//switch to first setting
document.querySelector("button[tab-handler]").click();
document.querySelector("button[data-tab-handler]").click();
//show modal el
this.modal.classList.add("flex");
this.modal.classList.remove("hidden");
@ -318,7 +342,7 @@ class Multiple {
constructor(prefix) {
this.prefix = prefix;
this.container = document.querySelector("main");
this.modalForm = document.querySelector(`[${this.prefix}-modal-form]`);
this.modalForm = document.querySelector(`[data-${this.prefix}-modal-form]`);
this.init();
}
@ -331,15 +355,16 @@ class Multiple {
//edit button
try {
if (
e.target.closest("button").getAttribute("services-action") === "edit"
e.target.closest("button").getAttribute("data-services-action") ===
"edit"
) {
//remove all multiples
this.removePrevMultiples();
//get multiple service values and parse as obj
const servicesSettings = e.target
.closest("[services-service]")
.querySelector("[services-settings]")
.getAttribute("value");
.closest("[data-services-service]")
.querySelector("[data-services-settings]")
.getAttribute("data-value");
const obj = JSON.parse(servicesSettings);
//keep only multiple settings value
const multipleSettings = this.getMultiplesOnly(obj);
@ -351,7 +376,8 @@ class Multiple {
//new button
try {
if (
e.target.closest("button").getAttribute("services-action") === "new"
e.target.closest("button").getAttribute("data-services-action") ===
"new"
) {
this.removePrevMultiples();
this.addOneMultGroup();
@ -363,14 +389,16 @@ class Multiple {
//ADD BTN
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-multiple-add`)
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-multiple-add`)
) {
//get plugin from btn
const btn = e.target.closest("button");
const attName = btn.getAttribute(`${this.prefix}-multiple-add`);
const attName = btn.getAttribute(`data-${this.prefix}-multiple-add`);
//get all multiple groups
const multipleEls = document.querySelectorAll(
`[${this.prefix}-settings-multiple*="${attName}"]`
`[data-${this.prefix}-settings-multiple*="${attName}"]`
);
//case no schema
if (multipleEls.length <= 0) return;
@ -382,7 +410,7 @@ class Multiple {
//and keep the highest num
multipleEls.forEach((container) => {
const ctnrName = container.getAttribute(
"services-settings-multiple"
"data-services-settings-multiple"
);
const num = this.getSuffixNumOrFalse(ctnrName);
if (!isNaN(num) && num > topNum) topNum = num;
@ -393,7 +421,7 @@ class Multiple {
const setNum = +currNum === 0 ? `` : `_${currNum}`;
//the default (schema) group is the last group
const schema = document.querySelector(
`[${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
);
//clone schema to create a group with new num
const schemaClone = schema.cloneNode(true);
@ -411,11 +439,11 @@ class Multiple {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-multiple-toggle`)
.hasAttribute(`data-${this.prefix}-multiple-toggle`)
) {
const att = e.target
.closest("button")
.getAttribute(`${this.prefix}-multiple-toggle`);
.getAttribute(`data-${this.prefix}-multiple-toggle`);
this.toggleMultByAtt(att);
}
//remove last child
@ -426,10 +454,10 @@ class Multiple {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-multiple-delete`)
.hasAttribute(`data-${this.prefix}-multiple-delete`)
) {
const multContainer = e.target.closest(
"[services-settings-multiple]"
"[data-services-settings-multiple]"
);
multContainer.remove();
}
@ -451,11 +479,13 @@ class Multiple {
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
: name.trim();
const relateSetting = document.querySelector(
`[setting-container=${nameSuffixLess}_SCHEMA]`
`[data-setting-container=${nameSuffixLess}_SCHEMA]`
);
const relateCtnr = relateSetting.closest(
"[data-services-settings-multiple]"
);
const relateCtnr = relateSetting.closest("[services-settings-multiple]");
const relateCtnrName = relateCtnr.getAttribute(
"services-settings-multiple"
"data-services-settings-multiple"
);
//then we sort the setting on the right container name by suffixe number
if (!(relateCtnrName in sortMultiples)) {
@ -471,12 +501,16 @@ class Multiple {
}
addOneMultGroup() {
const settings = document.querySelector("[services-modal-form]");
const multAddBtns = settings.querySelectorAll("[services-multiple-add]");
const settings = document.querySelector("[data-services-modal-form]");
const multAddBtns = settings.querySelectorAll(
"[data-services-multiple-add]"
);
multAddBtns.forEach((btn) => {
//check if already one (SCHEMA exclude so length >= 2)
const plugin = btn.closest("[plugin-item]");
if (plugin.querySelectorAll("[services-settings-multiple]").length >= 2)
const plugin = btn.closest("[data-plugin-item]");
if (
plugin.querySelectorAll("[data-services-settings-multiple]").length >= 2
)
return;
btn.click();
});
@ -484,11 +518,13 @@ class Multiple {
showMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`
);
multContainers.forEach((container) => {
if (
!container.getAttribute("services-settings-multiple").includes("SCHEMA")
!container
.getAttribute("data-services-settings-multiple")
.includes("SCHEMA")
)
container.classList.remove("hidden");
});
@ -496,11 +532,13 @@ class Multiple {
toggleMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[services-settings-multiple^=${att}]`
`[data-services-settings-multiple^=${att}]`
);
multContainers.forEach((container) => {
if (
!container.getAttribute("services-settings-multiple").includes("SCHEMA")
!container
.getAttribute("data-services-settings-multiple")
.includes("SCHEMA")
)
container.classList.toggle("hidden");
});
@ -510,12 +548,12 @@ class Multiple {
//get schema settings
const multiples = {};
const schemaSettings = document.querySelectorAll(
`[setting-container$="SCHEMA"]`
`[data-setting-container$="SCHEMA"]`
);
// loop on every schema settings
schemaSettings.forEach((schema) => {
const schemaName = schema
.getAttribute("setting-container")
.getAttribute("data-setting-container")
.replace("_SCHEMA", "")
.trim();
//check if match with service setting
@ -539,12 +577,14 @@ class Multiple {
)) {
//we need to access the DOM schema container
const schemaCtnr = document.querySelector(
`[services-settings-multiple="${schemaCtnrName}"]`
`[data-services-settings-multiple="${schemaCtnrName}"]`
);
//now we have to loop on each multiple settings group
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
//we have to clone schema container first
const schemaCtnrClone = schemaCtnr.cloneNode(true);
//remove id to avoid duplicate and for W3C
schemaCtnr.removeAttribute("id");
//now we replace _SCHEMA by current suffix everywhere we need
//unless it is 0 that means no suffix
const suffixFormat = +suffix === 0 ? `` : `_${suffix}`;
@ -553,7 +593,7 @@ class Multiple {
for (const [name, data] of Object.entries(settings)) {
//get setting container of clone container
const settingContainer = schemaCtnrClone.querySelector(
`[setting-container="${name}"]`
`[data-setting-container="${name}"]`
);
//replace input info
this.setSetting(data["value"], data["method"], settingContainer);
@ -570,9 +610,9 @@ class Multiple {
changeCloneSuffix(schemaCtnrClone, suffix) {
//rename multiple container
schemaCtnrClone.setAttribute(
"services-settings-multiple",
"data-services-settings-multiple",
schemaCtnrClone
.getAttribute("services-settings-multiple")
.getAttribute("data-services-settings-multiple")
.replace("_SCHEMA", suffix)
);
@ -587,12 +627,18 @@ class Multiple {
//rename setting container
const settingCtnrs = schemaCtnrClone.querySelectorAll(
"[setting-container]"
"[data-setting-container]"
);
settingCtnrs.forEach((settingCtnr) => {
settingCtnr.setAttribute(
"setting-container",
settingCtnr.getAttribute("setting-container").replace("_SCHEMA", suffix)
"data-setting-container",
settingCtnr
.getAttribute("data-setting-container")
.replace("_SCHEMA", suffix)
);
settingCtnr.setAttribute(
"id",
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix)
);
});
@ -636,22 +682,22 @@ class Multiple {
inp.checked = value === "yes" ? true : false;
if (inp.hasAttribute("disabled")) {
const hidden_inp = inp
.closest("div[checkbox-handler]")
.closest("div[data-checkbox-handler]")
.querySelector("input[type='hidden']");
hidden_inp.setAttribute("value", value);
}
}
inp.value = value;
inp.setAttribute("method", method);
inp.setAttribute("data-method", method);
});
} catch (err) {}
//update select
try {
const select = settingContainer.querySelector("select");
const name = select.getAttribute("services-setting-select-default");
const name = select.getAttribute("data-services-setting-select-default");
const selTxt = document.querySelector(
`[services-setting-select-text='${name}']`
`[data-services-setting-select-text='${name}']`
);
selTxt.textContent = value;
@ -664,7 +710,7 @@ class Multiple {
? option.setAttribute("selected")
: option.removeAttribute("selected");
}
select.setAttribute("method", method);
select.setAttribute("data-method", method);
} catch (err) {}
}
@ -676,17 +722,17 @@ class Multiple {
setDisabled() {
const multipleCtnr = document.querySelectorAll(
"[services-settings-multiple]"
"[data-services-settings-multiple]"
);
multipleCtnr.forEach((container) => {
const settings = container.querySelectorAll("[setting-container]");
const settings = container.querySelectorAll("[data-setting-container]");
settings.forEach((setting) => {
//replace input info
try {
const inps = setting.querySelectorAll("input");
inps.forEach((inp) => {
const method = inp.getAttribute("method") || "default";
const method = inp.getAttribute("data-method") || "default";
if (method === "ui" || method === "default") {
inp.removeAttribute("disabled");
} else {
@ -698,10 +744,12 @@ class Multiple {
try {
const selects = setting.querySelectorAll("select");
selects.forEach((select) => {
const method = select.getAttribute("method") || "default";
const name = select.getAttribute("services-setting-select-default");
const method = select.getAttribute("data-method") || "default";
const name = select.getAttribute(
"data-services-setting-select-default"
);
const selDOM = document.querySelector(
`button[services-setting-select='${name}']`
`button[data-services-setting-select='${name}']`
);
if (method === "ui" || method === "default") {
selDOM.removeAttribute("disabled", "");
@ -726,23 +774,25 @@ class Multiple {
hiddenIfNoMultiples() {
//hide multiple btn if no multiple exist on a plugin
const multiples = document.querySelectorAll(
`[${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`
);
multiples.forEach((container) => {
if (container.querySelectorAll(`[setting-container]`).length <= 0)
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
container.parentElement
.querySelector("[multiple-handler]")
.querySelector("[data-multiple-handler]")
.classList.add("hidden");
});
}
removePrevMultiples() {
const multiPlugins = document.querySelectorAll(
`[${this.prefix}-settings-multiple]`
`[data-${this.prefix}-settings-multiple]`
);
multiPlugins.forEach((multiGrp) => {
if (
!multiGrp.getAttribute("services-settings-multiple").includes("SCHEMA")
!multiGrp
.getAttribute("data-services-settings-multiple")
.includes("SCHEMA")
)
multiGrp.remove();
});
@ -771,6 +821,7 @@ class Multiple {
const setCheckbox = new Checkbox();
const setSelect = new Select();
const setPassword = new Password();
const setDisabledPop = new DisabledPop();
const setPopover = new Popover();
const setTabs = new Tabs();
@ -778,7 +829,7 @@ const setModal = new ServiceModal();
const format = new FormatValue();
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[service-content='settings']"
"[data-service-content='settings']"
);
const setMultiple = new Multiple("services");

View File

@ -1,15 +1,15 @@
class FolderNav {
constructor(prefix) {
this.prefix = prefix;
this.breadContainer = document.querySelector(`[${this.prefix}-breadcrumb]`);
this.container = document.querySelector(`[${this.prefix}-container]`);
this.listContainer = document.querySelector(`[${this.prefix}-folders]`);
this.els = document.querySelectorAll(`div[${this.prefix}-element]`);
this.breadContainer = document.querySelector(`[data-${this.prefix}-breadcrumb]`);
this.container = document.querySelector(`[data-${this.prefix}-container]`);
this.listContainer = document.querySelector(`[data-${this.prefix}-folders]`);
this.els = document.querySelectorAll(`div[data-${this.prefix}-element]`);
this.files = document.querySelectorAll(
`div[${this.prefix}-element][_type='file']`
`div[data-${this.prefix}-element][data-_type='file']`
);
this.addFileEl = document.querySelector(`[${this.prefix}-add-file]`);
this.addFolderEl = document.querySelector(`[${this.prefix}-add-folder]`);
this.addFileEl = document.querySelector(`[data-${this.prefix}-add-file]`);
this.addFolderEl = document.querySelector(`[data-${this.prefix}-add-folder]`);
this.initSorted();
this.initNav();
}
@ -25,9 +25,9 @@ class FolderNav {
this.container.addEventListener("click", (e) => {
//GO ON NESTED FOLDER
try {
if (e.target.closest("div").getAttribute("_type") === "folder") {
if (e.target.closest("div").getAttribute("data-_type") === "folder") {
//avoid logic on action btn click
const folder = e.target.closest("div[_type='folder']");
const folder = e.target.closest("div[data-_type='folder']");
this.updatedNested(folder);
}
} catch (err) {}
@ -36,8 +36,8 @@ class FolderNav {
if (
e.target
.closest("li")
.hasAttribute(`${this.prefix}-breadcrumb-item`) &&
!e.target.closest("li").hasAttribute(`${this.prefix}-back`) &&
.hasAttribute(`data-${this.prefix}-breadcrumb-item`) &&
!e.target.closest("li").hasAttribute(`data-${this.prefix}-back`) &&
e.target.closest("li").nextSibling !== null
) {
const breadItem = e.target.closest("li");
@ -47,8 +47,8 @@ class FolderNav {
//BREADCRUMB BACK LOGIC
try {
if (
e.target.closest("li").hasAttribute(`${this.prefix}-back`) &&
+this.breadContainer.lastElementChild.getAttribute("level") !== 0
e.target.closest("li").hasAttribute(`data-${this.prefix}-back`) &&
+this.breadContainer.lastElementChild.getAttribute("data-level") !== 0
) {
//back is like clicking on last prev element
const prevItem =
@ -81,7 +81,7 @@ class FolderNav {
//remove useless bread
this.removeBreadElByLvl(+prevLvl);
const folder = document.querySelector(
`div[${this.prefix}-element][path='${item.getAttribute("path")}']`
`div[data-${this.prefix}-element][data-path='${item.getAttribute("data-path")}']`
);
this.updateActions(folder);
}
@ -91,8 +91,8 @@ class FolderNav {
//by default
this.hideAddConf();
//check if folder allow add file/folder
const isAddFile = folder.getAttribute("can-create-file");
const isAddFolder = folder.getAttribute("can-create-folder");
const isAddFile = folder.getAttribute("data-can-create-file");
const isAddFolder = folder.getAttribute("data-can-create-folder");
isAddFile === "True" ? this.addFileEl.classList.remove("hidden") : "";
isAddFolder === "True" ? this.addFolderEl.classList.remove("hidden") : "";
}
@ -104,11 +104,11 @@ class FolderNav {
showCurrentFolderEls(path, lvl) {
const nestedEl = document.querySelectorAll(
`div[path^="${path}/"][level="${+lvl + 1}"]`
`div[data-path^="${path}/"][data-level="${+lvl + 1}"]`
);
for (let i = 0; i < nestedEl.length; i++) {
const el = nestedEl[i];
el.setAttribute("current-el", "");
el.setAttribute("data-current-el", "");
el.classList.remove("hidden");
}
}
@ -117,28 +117,28 @@ class FolderNav {
//the clicked bread item
removeBreadElByLvl(lvl) {
const breadcrumbItem = this.breadContainer.querySelectorAll(
`[${this.prefix}-breadcrumb-item]`
`[data-${this.prefix}-breadcrumb-item]`
);
breadcrumbItem.forEach((item) => {
if (item.hasAttribute("level") && +item.getAttribute("level") > lvl)
if (item.hasAttribute("data-level") && +item.getAttribute("data-level") > lvl)
item.remove();
});
}
//retrieve path, level and text
getElAtt(el) {
const newPath = el.getAttribute("path");
const newLvl = el.getAttribute("level");
const newTxt = el.getAttribute("name");
const newPath = el.getAttribute("data-path");
const newLvl = el.getAttribute("data-level");
const newTxt = el.getAttribute("data-name");
return [newPath, newLvl, newTxt];
}
//hidden all folders
hiddenConfEls() {
this.els = document.querySelectorAll(`div[${this.prefix}-element]`);
this.els = document.querySelectorAll(`div[data-${this.prefix}-element]`);
this.els.forEach((el) => {
el.classList.add("hidden");
el.removeAttribute("current-el");
el.removeAttribute("data-current-el");
});
}
@ -149,10 +149,10 @@ class FolderNav {
itemEl.className = "leading-normal text-sm";
//set item atts
const itemAtt = [
["path", path],
[`${this.prefix}-breadcrumb-item`, ""],
["level", level],
["name", name],
["data-path", path],
[`data-${this.prefix}-breadcrumb-item`, ""],
["data-level", level],
["data-name", name],
];
for (let i = 0; i < itemAtt.length; i++) {
itemEl.setAttribute(`${itemAtt[i][0]}`, `${itemAtt[i][1]}`);
@ -171,9 +171,9 @@ class FolderNav {
class FolderDropdown {
constructor(prefix) {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-container]`);
this.container = document.querySelector(`[data-${this.prefix}-container]`);
this.dropEls = document.querySelectorAll(
`[${this.prefix}-action-dropdown]`
`[data-${this.prefix}-action-dropdown]`
);
this.init();
}
@ -184,7 +184,7 @@ class FolderDropdown {
//remove when none click
try {
if (
!e.target.closest("div").hasAttribute(`${this.prefix}-action-button`)
!e.target.closest("div").hasAttribute(`data-${this.prefix}-action-button`)
) {
this.hideDropEls();
}
@ -192,11 +192,11 @@ class FolderDropdown {
//show dropdown actions for folders
try {
if (
e.target.closest("div").hasAttribute(`${this.prefix}-action-button`)
e.target.closest("div").hasAttribute(`data-${this.prefix}-action-button`)
) {
const dropEl = e.target
.closest(`div[${this.prefix}-element]`)
.querySelector(`[${this.prefix}-action-dropdown]`);
.closest(`div[data-${this.prefix}-element]`)
.querySelector(`[data-${this.prefix}-action-dropdown]`);
//avoid multiple dropdown
if (prevActionBtn === "") prevActionBtn = dropEl;
if (prevActionBtn !== dropEl) this.hideDropEls();
@ -209,13 +209,13 @@ class FolderDropdown {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-action-dropdown-btn`)
.hasAttribute(`data-${this.prefix}-action-dropdown-btn`)
) {
const att = e.target
.closest("button")
.getAttribute(`${this.prefix}-action-dropdown-btn`);
.getAttribute(`data-${this.prefix}-action-dropdown-btn`);
const dropEl = document.querySelector(
`[${this.prefix}-action-dropdown="${att}"]`
`[data-${this.prefix}-action-dropdown="${att}"]`
);
this.hideDrop(dropEl);
}
@ -245,7 +245,7 @@ class FolderDropdown {
class FolderEditor {
constructor() {
this.editor = ace.edit("editor");
this.darkMode = document.querySelector("[dark-toggle]");
this.darkMode = document.querySelector("[data-dark-toggle]");
this.initEditor();
this.listenDarkToggle();
}
@ -287,32 +287,32 @@ class FolderModal {
constructor(prefix) {
this.prefix = prefix;
//container
this.container = document.querySelector(`[${this.prefix}-container]`);
this.container = document.querySelector(`[data-${this.prefix}-container]`);
//add service/file elements
this.breadContainer = document.querySelector(`[${this.prefix}-breadcrumb]`);
this.breadContainer = document.querySelector(`[data-${this.prefix}-breadcrumb]`);
this.addConfContainer = document.querySelector(
`[${this.prefix}-add-container]`
`[data-${this.prefix}-add-container]`
);
//modal DOM elements
this.form = document.querySelector(`[${this.prefix}-modal-form]`);
this.modalEl = document.querySelector(`[${this.prefix}-modal]`);
this.form = document.querySelector(`[data-${this.prefix}-modal-form]`);
this.modalEl = document.querySelector(`[data-${this.prefix}-modal]`);
this.modalTitle = this.modalEl.querySelector(
`[${this.prefix}-modal-title]`
`[data-${this.prefix}-modal-title]`
);
this.modalPath = this.modalEl.querySelector(`[${this.prefix}-modal-path]`);
this.modalPath = this.modalEl.querySelector(`[data-${this.prefix}-modal-path]`);
this.modalEditor = this.modalEl.querySelector(
`[${this.prefix}-modal-editor]`
`[data-${this.prefix}-modal-editor]`
);
this.modalPathPrev = this.modalPath.querySelector(
`p[${this.prefix}-modal-path-prefix]`
`p[data-${this.prefix}-modal-path-prefix]`
);
this.modalPathName = this.modalPath.querySelector("input");
this.modalPathSuffix = this.modalPath.querySelector(
`p[${this.prefix}-modal-path-suffix]`
`p[data-${this.prefix}-modal-path-suffix]`
);
this.modalSubmit = this.modalEl.querySelector(
`[${this.prefix}-modal-submit]`
`[data-${this.prefix}-modal-submit]`
);
//hidden input for backend
this.modalInpPath = this.modalEl.querySelector("#path");
@ -336,7 +336,7 @@ class FolderModal {
this.addConfContainer.addEventListener("click", (e) => {
//add folder
try {
if (e.target.closest("li").hasAttribute(`${this.prefix}-add-folder`)) {
if (e.target.closest("li").hasAttribute(`data-${this.prefix}-add-folder`)) {
this.setModal(
"new",
this.getPathFromBread(),
@ -349,7 +349,7 @@ class FolderModal {
} catch (err) {}
//add file
try {
if (e.target.closest("li").hasAttribute(`${this.prefix}-add-file`)) {
if (e.target.closest("li").hasAttribute(`data-${this.prefix}-add-file`)) {
this.setModal(
"new",
this.getPathFromBread(),
@ -367,10 +367,10 @@ class FolderModal {
this.container.addEventListener("click", (e) => {
//click on file logic
try {
if (e.target.closest("div").getAttribute("_type") == "file") {
if (e.target.closest("div").getAttribute("data-_type") == "file") {
const btnEl = e.target
.closest("div")
.querySelector('button[value="view"]');
.querySelector('button[value]');
const [action, path, type, content, name, level] =
this.getInfoFromActionBtn(btnEl);
this.setModal(action, path, type, content, name, level);
@ -382,7 +382,7 @@ class FolderModal {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-action-dropdown-btn`) &&
.hasAttribute(`data-${this.prefix}-action-dropdown-btn`) &&
e.target.closest("button").getAttribute("value") !== "download"
) {
const btnEl = e.target.closest("button");
@ -397,7 +397,7 @@ class FolderModal {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-action-dropdown-btn`) &&
.hasAttribute(`data-${this.prefix}-action-dropdown-btn`) &&
e.target.closest("button").getAttribute("value") === "download"
) {
const btnEl = e.target.closest("button");
@ -414,7 +414,7 @@ class FolderModal {
//close modal logic
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-modal-close`)
e.target.closest("button").hasAttribute(`data-${this.prefix}-modal-close`)
) {
this.closeModal();
}
@ -467,12 +467,12 @@ class FolderModal {
//for add file/folder btn
//get path of last bread element
getPathFromBread() {
const path = this.breadContainer.lastElementChild.getAttribute("path");
const path = this.breadContainer.lastElementChild.getAttribute("data-path");
return `${path}/`;
}
getLevelFromBread() {
const level = this.breadContainer.lastElementChild.getAttribute("level");
const level = this.breadContainer.lastElementChild.getAttribute("data-level");
return level;
}
//set all needed data from btn action and folder info
@ -562,16 +562,16 @@ class FolderModal {
//get all needed info when clicking on action btn
getInfoFromActionBtn(btnEl) {
const action = btnEl.getAttribute("value");
const name = btnEl.getAttribute(`${this.prefix}-action-dropdown-btn`);
const folder = btnEl.closest(`[${this.prefix}-element]`);
const level = folder.getAttribute("level");
const path = folder.getAttribute("path");
const type = folder.getAttribute("_type");
const name = btnEl.getAttribute(`data-${this.prefix}-action-dropdown-btn`);
const folder = btnEl.closest(`[data-${this.prefix}-element]`);
const level = folder.getAttribute("data-level");
const path = folder.getAttribute("data-path");
const type = folder.getAttribute("data-_type");
let content;
try {
content = folder
.querySelector(`[${this.prefix}-content]`)
.getAttribute("value");
.querySelector(`[data-${this.prefix}-content]`)
.getAttribute("data-value");
} catch (err) {
content = "";
}

View File

@ -9,7 +9,7 @@ class Checkbox {
try {
//case a related checkbox element is clicked and checkbox is enabled
if (
e.target.closest("div").hasAttribute("checkbox-handler") &&
e.target.closest("div").hasAttribute("data-checkbox-handler") &&
!e.target
.closest("div")
.querySelector('input[type="checkbox"]')
@ -41,15 +41,19 @@ class Select {
try {
if (!e.target.closest("button")) {
const selectEls = document.querySelectorAll(
"div[setting-select-dropdown]"
"div[data-setting-select-dropdown]"
);
selectEls.forEach((select) => {
select.classList.add("hidden");
select.classList.remove("flex");
});
const btnEls = document.querySelectorAll("button[setting-select]");
const btnEls = document.querySelectorAll(
"button[data-setting-select]"
);
btnEls.forEach((btn) => {
const dropdownChevron = btn.querySelector(`svg[setting-select]`);
const dropdownChevron = btn.querySelector(
`svg[data-setting-select]`
);
dropdownChevron.classList.remove("rotate-180");
});
}
@ -57,7 +61,7 @@ class Select {
//SELECT BTN LOGIC
try {
if (
e.target.closest("button").hasAttribute(`setting-select`) &&
e.target.closest("button").hasAttribute(`data-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnEl = e.target.closest("button");
@ -67,22 +71,26 @@ class Select {
//SELECT DROPDOWN BTN LOGIC
try {
if (
e.target.closest("button").hasAttribute(`setting-select-dropdown-btn`)
e.target
.closest("button")
.hasAttribute(`data-setting-select-dropdown-btn`)
) {
const btn = e.target.closest(`button[setting-select-dropdown-btn]`);
const btn = e.target.closest(
`button[data-setting-select-dropdown-btn]`
);
const btnValue = btn.getAttribute("value");
//add new value to custom
const selectCustom = btn
.closest("div[select-container]")
.querySelector(`button[setting-select]`);
.closest("div[data-select-container]")
.querySelector(`button[data-setting-select]`);
selectCustom.querySelector(`[setting-select-text]`).textContent =
selectCustom.querySelector(`[data-setting-select-text]`).textContent =
btnValue;
//add selected to new value
//change style
const dropdownEl = btn.closest(`div[setting-select-dropdown]`);
const dropdownEl = btn.closest(`div[data-setting-select-dropdown]`);
dropdownEl.classList.add("hidden");
dropdownEl.classList.remove("flex");
@ -93,8 +101,6 @@ class Select {
btn.classList.remove(
"dark:bg-primary",
"bg-primary",
"bg-primary",
"text-gray-300",
"text-gray-300"
);
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
@ -108,13 +114,14 @@ class Select {
btn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
//close dropdown
const dropdownChevron =
selectCustom.querySelector(`svg[setting-select]`);
const dropdownChevron = selectCustom.querySelector(
`svg[data-setting-select]`
);
dropdownChevron.classList.remove("rotate-180");
//update real select element
const realSel = btn
.closest("div[setting-container]")
.closest("div[data-setting-container]")
.querySelector("select");
this.updateSelected(realSel, btnValue);
}
@ -141,8 +148,8 @@ class Select {
//toggle dropdown
const dropdownEl = btn
.closest("div")
.querySelector(`[setting-select-dropdown]`);
const dropdownChevron = btn.querySelector(`svg[setting-select]`);
.querySelector(`[data-setting-select-dropdown]`);
const dropdownChevron = btn.querySelector(`svg[data-setting-select]`);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
dropdownChevron.classList.toggle("rotate-180");
@ -157,10 +164,12 @@ class Password {
init() {
window.addEventListener("click", (e) => {
try {
if (e.target.closest("button").hasAttribute("setting-password")) {
if (e.target.closest("button").hasAttribute("data-setting-password")) {
const btn = e.target.closest("button");
const action = btn.getAttribute("setting-password");
const inp = btn.closest("[setting-container]").querySelector("input");
const action = btn.getAttribute("data-setting-password");
const inp = btn
.closest("[data-setting-container]")
.querySelector("input");
this.setValDisplay(action, inp);
this.hiddenBtns(btn);
this.showOppositeBtn(btn, action);
@ -174,7 +183,7 @@ class Password {
const opposite = action === "visible" ? "invisible" : "visible";
btnEls.forEach((btn) => {
const action = btn.getAttribute("setting-password");
const action = btn.getAttribute("data-setting-password");
if (action === opposite) {
btn.classList.add("flex");
@ -199,9 +208,54 @@ class Password {
getBtns(btnEl) {
return btnEl
.closest("[setting-container]")
.querySelectorAll("button[setting-password]");
.closest("[data-setting-container]")
.querySelectorAll("button[data-setting-password]");
}
}
export { Checkbox, Select, Password };
class DisabledPop {
constructor() {
this.init();
}
init() {
window.addEventListener("pointerover", (e) => {
//for checkbox and regular inputs
if (e.target.tagName === "INPUT") {
const el = e.target;
this.showPopup(el, "input");
}
//for select custom
if (
e.target.tagName === "BUTTON" &&
e.target.hasAttribute("data-setting-select")
) {
const el = e.target;
this.showPopup(el, "select");
}
});
window.addEventListener("pointerout", (e) => {
try {
const popupEl = e.target
.closest("div")
.querySelector("div[data-disabled-info]");
popupEl.remove();
} catch (err) {}
});
}
showPopup(el, type = "input") {
if (!el.hasAttribute("disabled")) return;
const method = el.getAttribute("data-default-method");
const popupHTML = `
<div data-disabled-info class="${
type === "select" ? "translate-y-2" : ""
} bg-blue-500 absolute right-2 rounded-lg px-2 py-1 z-20 dark:brightness-90">
<p class="m-0 text-xs text-white dark:text-gray-100">disabled by ${method}</p>
</div>`;
el.insertAdjacentHTML("beforebegin", popupHTML);
}
}
export { Checkbox, Select, Password, DisabledPop };

View File

@ -7,7 +7,7 @@ class Popover {
window.addEventListener("pointerover", (e) => {
//POPOVER LOGIC
try {
if (e.target.closest("svg").hasAttribute(`popover-btn`)) {
if (e.target.closest("svg").hasAttribute(`data-popover-btn`)) {
this.showPopover(e.target);
}
} catch (err) {}
@ -16,7 +16,7 @@ class Popover {
window.addEventListener("pointerout", (e) => {
//POPOVER LOGIC
try {
if (e.target.closest("svg").hasAttribute(`popover-btn`)) {
if (e.target.closest("svg").hasAttribute(`data-popover-btn`)) {
this.hidePopover(e.target);
}
} catch (err) {}
@ -26,14 +26,14 @@ class Popover {
showPopover(el) {
const btn = el.closest("svg");
//toggle curr popover
const popover = btn.parentElement.querySelector(`[popover-content]`);
const popover = btn.parentElement.querySelector(`[data-popover-content]`);
popover.classList.remove("hidden");
}
hidePopover(el) {
const btn = el.closest("svg");
//toggle curr popover
const popover = btn.parentElement.querySelector(`[popover-content]`);
const popover = btn.parentElement.querySelector(`[data-popover-content]`);
popover.classList.add("hidden");
}
}
@ -47,15 +47,15 @@ class Tabs {
window.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute("tab-handler") ||
e.target.closest("button").hasAttribute("tab-handler-mobile")
e.target.closest("button").hasAttribute("data-tab-handler") ||
e.target.closest("button").hasAttribute("data-tab-handler-mobile")
) {
//get needed data
const tab = e.target.closest("button");
const tabAtt =
tab.getAttribute("tab-handler") ||
tab.getAttribute("tab-handler-mobile");
const container = tab.closest("div[service-content]");
tab.getAttribute("data-tab-handler") ||
tab.getAttribute("data-tab-handler-mobile");
const container = tab.closest("div[data-service-content]");
// change style
this.resetTabsStyle(container);
this.highlightClicked(container, tabAtt);
@ -69,9 +69,9 @@ class Tabs {
} catch (err) {}
try {
if (e.target.closest("button").hasAttribute("tab-dropdown-btn")) {
if (e.target.closest("button").hasAttribute("data-tab-dropdown-btn")) {
const dropBtn = e.target.closest("button");
const container = dropBtn.closest("div[service-content]");
const container = dropBtn.closest("div[data-service-content]");
this.toggleDropdown(container);
}
} catch (err) {}
@ -80,13 +80,13 @@ class Tabs {
resetTabsStyle(container) {
//reset desktop style
const tabsDesktop = container.querySelectorAll("button[tab-handler]");
const tabsDesktop = container.querySelectorAll("button[data-tab-handler]");
tabsDesktop.forEach((tab) => {
tab.classList.remove("brightness-90", "z-[1001]");
tab.classList.add("z-1000");
});
//reset mobile style
const tabsMobile = container.querySelectorAll("button[tab-handler-mobile]");
const tabsMobile = container.querySelectorAll("button[data-tab-handler-mobile]");
tabsMobile.forEach((tab) => {
tab.classList.add(
"bg-white",
@ -107,13 +107,13 @@ class Tabs {
highlightClicked(container, tabAtt) {
//desktop case
const tabDesktop = container.querySelector(
`button[tab-handler='${tabAtt}']`
`button[data-tab-handler='${tabAtt}']`
);
tabDesktop.classList.add("brightness-90", "z-[1001]");
//mobile case
const tabMobile = container.querySelector(
`button[tab-handler-mobile='${tabAtt}']`
`button[data-tab-handler-mobile='${tabAtt}']`
);
tabMobile.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
tabMobile.classList.remove(
@ -124,30 +124,30 @@ class Tabs {
}
hideAllSettings(container) {
const plugins = container.querySelectorAll("[plugin-item]");
const plugins = container.querySelectorAll("[data-plugin-item]");
plugins.forEach((plugin) => {
plugin.classList.add("hidden");
});
}
showSettingClicked(container, tabAtt) {
const plugin = container.querySelector(`[plugin-item='${tabAtt}']`);
const plugin = container.querySelector(`[data-plugin-item='${tabAtt}']`);
plugin.classList.remove("hidden");
}
setDropBtnText(container, tabAtt) {
const dropBtn = container.querySelector("[tab-dropdown-btn]");
const dropBtn = container.querySelector("[data-tab-dropdown-btn]");
dropBtn.querySelector("span").textContent = tabAtt;
}
closeDropdown(container) {
const dropdown = container.querySelector("[tab-dropdown]");
const dropdown = container.querySelector("[data-tab-dropdown]");
dropdown.classList.add("hidden");
dropdown.classList.remove("flex");
}
toggleDropdown(container) {
const dropdown = container.querySelector("[tab-dropdown]");
const dropdown = container.querySelector("[data-tab-dropdown]");
dropdown.classList.toggle("hidden");
dropdown.classList.toggle("flex");
}
@ -174,7 +174,7 @@ class FilterSettings {
this.input = document.querySelector(`input#${inputID}`);
//DESKTOP
this.container = document.querySelector(container);
this.deskTabs = this.container.querySelectorAll(`[tab-handler]`);
this.deskTabs = this.container.querySelectorAll(`[data-tab-handler]`);
this.init();
}
@ -206,15 +206,15 @@ class FilterSettings {
});
//case no setting match, hidden tab and content
if (settingCount === hiddenCount) {
const tabName = tab.getAttribute(`tab-handler`);
const tabName = tab.getAttribute(`data-tab-handler`);
//hide mobile and desk tabs
tab.classList.add("hidden");
this.container
.querySelector(`[tab-handler-mobile="${tabName}"]`)
.querySelector(`[data-tab-handler-mobile="${tabName}"]`)
.classList.add("hidden");
this.container
.querySelector(`[plugin-item=${tabName}]`)
.querySelector("[setting-header]")
.querySelector(`[data-plugin-item=${tabName}]`)
.querySelector("[data-setting-header]")
.classList.add("hidden");
}
@ -224,15 +224,15 @@ class FilterSettings {
resetFilter() {
this.deskTabs.forEach((tab) => {
const tabName = tab.getAttribute(`tab-handler`);
const tabName = tab.getAttribute(`data-tab-handler`);
//hide mobile and desk tabs
tab.classList.remove("hidden");
this.container
.querySelector(`[tab-handler-mobile="${tabName}"]`)
.querySelector(`[data-tab-handler-mobile="${tabName}"]`)
.classList.remove("hidden");
this.container
.querySelector(`[plugin-item=${tabName}]`)
.querySelector("[setting-header]")
.querySelector(`[data-plugin-item=${tabName}]`)
.querySelector("[data-setting-header]")
.classList.remove("hidden");
const settings = this.getSettingsFromTab(tab);
settings.forEach((setting) => {
@ -242,11 +242,11 @@ class FilterSettings {
}
getSettingsFromTab(tabEl) {
const tabName = tabEl.getAttribute(`tab-handler`);
const tabName = tabEl.getAttribute(`data-tab-handler`);
const settingContainer = this.container
.querySelector(`[plugin-item="${tabName}"]`)
.querySelector(`[plugin-settings]`);
const settings = settingContainer.querySelectorAll("[setting-container]");
.querySelector(`[data-plugin-item="${tabName}"]`)
.querySelector(`[data-plugin-settings]`);
const settings = settingContainer.querySelectorAll("[data-setting-container]");
return settings;
}
}

View File

@ -10,11 +10,11 @@
>Your browser does not support JavaScript!</noscript
>
<div
loader
data-loader
class="fixed z-[10000] transition duration-300 h-screen w-screen bg-primary flex justify-center align-middle items-center"
>
<img
loader-img
data-loader-img
src="images/logo-menu-2.png"
class="duration-300 w-40 h-12 sm:w-50 sm:h-14 md:w-60 md:h-16 lg:w-80 lg:h-24 inline transition-all"
alt="main logo"

View File

@ -1,2 +1,2 @@
{% extends "base.html" %} {% block content %} {% include "file-manager.html" %}
{% extends "base.html" %} {% block content %} {% include "file_manager.html" %}
{% endblock %}

View File

@ -1,2 +1,2 @@
{% extends "base.html" %} {% block content %} {% include "file-manager.html" %}
{% extends "base.html" %} {% block content %} {% include "file_manager.html" %}
{% endblock %}

View File

@ -2,7 +2,7 @@
url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- main container -->
<div
{{current_endpoint}}-container
data-{{current_endpoint}}-container
class="dark:brightness-110 md:min-h-75-screen col-span-12 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="w-full grid-cols-12 grid">
@ -13,12 +13,12 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</h5>
<!--breadcrumb -->
<ul
{{current_endpoint}}-breadcrumb
data-{{current_endpoint}}-breadcrumb
class="flex flex-wrap bg-transparent rounded-lg md:mb-8"
>
<li
{{current_endpoint}}-breadcrumb-item
{{current_endpoint}}-back
data-{{current_endpoint}}-breadcrumb-item
data-{{current_endpoint}}-back
class="mr-2 cursor-pointer text-sm capitalize leading-normal text-gray-700"
aria-current="page"
>
@ -39,10 +39,10 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</svg>
</li>
<li
{{current_endpoint}}-breadcrumb-item
level="0"
path="{% if current_endpoint == "cache" %}/var/cache/bunkerweb{% elif current_endpoint == "configs" %}/etc/bunkerweb/{{current_endpoint}}{% endif %}"
name="{{current_endpoint}}"
data-{{current_endpoint}}-breadcrumb-item
data-level="0"
data-path="{% if current_endpoint == "cache" %}/var/cache/bunkerweb{% elif current_endpoint == "configs" %}/etc/bunkerweb/{{current_endpoint}}{% endif %}"
data-name="{{current_endpoint}}"
class="leading-normal text-sm"
>
<button
@ -57,11 +57,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</div>
<!-- actions -->
<ul
{{current_endpoint}}-add-container
data-{{current_endpoint}}-add-container
class="col-span-12 md:col-span-4 my-2 md:my-0 w-full flex justify-center md:justify-end items-center mb-3"
>
<li
{{current_endpoint}}-add-folder
data-{{current_endpoint}}-add-folder
class="rounded transition hidden flex-col items-center mx-2 p-2 md:py-4 md:px-6 relative cursor-pointer hover:bg-gray-100 dark:hover:bg-slate-800"
>
<button type="button">
@ -80,16 +80,15 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
<p
<span
class="dark:text-gray-500 pt-1 mb-0 font-sans font-semibold leading-normal uppercase text-sm lg:text-md"
>
ADD SERVICE
</p>
</span>
</button>
</li>
<li
{{current_endpoint}}-add-file
data-{{current_endpoint}}-add-file
class="rounded transition hidden flex-col items-center mx-2 p-2 md:py-4 md:px-6 relative cursor-pointer hover:bg-gray-100 dark:hover:bg-slate-800"
>
<button type="button">
@ -109,11 +108,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
/>
</svg>
<p
<span
class="dark:text-gray-500 pt-1 mb-0 font-sans font-semibold leading-normal uppercase text-sm lg:text-md"
>
ADD FILE
</p>
</span>
</button>
</li>
</ul>
@ -121,48 +120,42 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</div>
<!-- folders-->
<div {{current_endpoint}}-folders
<div data-{{current_endpoint}}-folders
class="grid grid-cols-12 gap-3">
{% for folder in folders %} {% for child in folder['children'] recursive %}
{{loop(child['children'])}}
<!-- folder -->
<div
{{current_endpoint}}-element="{{child['name']}}"
name="{{child['name']}}"
can-create-folder="{{child['can_create_folders']}}"
can-create-file="{{child['can_create_files']}}"
path="{{child['path']}}"
level="{{loop.depth}}"
_type="{{child['type']}}"
data-{{current_endpoint}}-element="{{child['name']}}"
data-name="{{child['name']}}"
data-can-create-folder="{{child['can_create_folders']}}"
data-can-create-file="{{child['can_create_files']}}"
data-path="{{child['path']}}"
data-level="{{loop.depth}}"
data-_type="{{child['type']}}"
class="cursor-pointer {% if loop.depth != 1%} hidden {% endif %} relative min-h-20 col-span-12 md:col-span-6 2xl:col-span-4 3xl:col-span-3 p-3 flex justify-center items-center h-full transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800"
>
{% if child['content'] %}
<span
value="{{child['content']}}"
{{current_endpoint}}-content
data-value="{{child['content']}}"
data-{{current_endpoint}}-content
class="hidden"
></span>
{% endif %}
<span>
<div class="pointer-events-none">
<!-- service root-->
{% if child['type'] == "folder" and current_endpoint == "configs" and loop.depth == 1 %}
<svg
class="absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150"
class=" absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="{1.5}"
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
@ -171,42 +164,41 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- services folder -->
{% if child['type'] == "folder" and current_endpoint == "configs" and loop.depth != 1 %}
<svg class="absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3m16.5 0h.008v.008h-.008v-.008zm-3 0h.008v.008h-.008v-.008z" /></svg> {% endif %}
<svg class=" absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3m16.5 0h.008v.008h-.008v-.008zm-3 0h.008v.008h-.008v-.008z" /></svg> {% endif %}
<!-- end services folder-->
<!-- services files -->
{% if child['type'] == "file" and current_endpoint == "configs" and loop.depth != 1 %}
<svg class="absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
<svg class=" absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
{% endif %}
<!-- end services files-->
<!-- cache folder-->
{% if child['type'] == "folder" and current_endpoint == "cache" and loop.depth == 1 %}
<svg class="absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<svg class=" absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
</svg>
{% endif %}
<!-- end cache folder -->
<!-- cache file -->
{% if child['type'] == "file" and current_endpoint == "cache" %}
<svg class="absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150"
<svg class=" absolute left-3 top-5 h-10 w-10 fill-primary stroke-gray-100 dark:stroke-gray-600 dark:brightness-150"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 13.5H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
</svg>
{% endif %}
<!-- end cache file -->
<p
{{current_endpoint}}-element-text
class="transition duration-300 ease-in-out dark:opacity-90 text-center ml-12 mr-7 mb-0 text-sm md:text-base text-slate-700 dark:text-gray-300"
<span
data-{{current_endpoint}}-element-text
class="pointer-events-none transition duration-300 ease-in-out dark:opacity-90 text-center ml-12 mr-7 mb-0 text-sm md:text-base text-slate-700 dark:text-gray-300"
>
{{child['name']}}
</p>
</span>
</div>
<div>
<!-- action button -->
<div
{{current_endpoint}}-action-button="{{child['name']}}"
type="button"
data-{{current_endpoint}}-action-button="{{child['name']}}"
class="dark:brightness-125 dark:hover:brightness-100 flex justify-center items-center absolute h-full w-10 bg-primary fill-white first-letter:absolute top-0 -right-1 font-bold text-center text-white uppercase transition-all rounded-none rounded-r-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem bg-150 bg-x-25 active:opacity-85"
>
<svg
@ -226,20 +218,21 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</svg>
</div>
<!-- end action button -->
</span>
</div>
<!-- dropdown actions -->
<div
{{current_endpoint}}-action-dropdown="{{child['name']}}"
<div role="tablist"
data-{{current_endpoint}}-action-dropdown="{{child['name']}}"
class="absolute hidden flex-col z-110 w-48 right-0 top-0 translate-y-16"
>
<!-- view button-->
<button
role="tab"
type="button"
value="view"
{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
data-{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
class="duration-300 border-gray-300 hover:brightness-90 bg-white text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 w-full border-t rounded-t border-b border-l border-r hover:bg-gray-100"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg
class="h-6 w-6 stroke-green-700 dark:brightness-125"
xmlns="http://www.w3.org/2000/svg"
@ -264,7 +257,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase"
>view</span
>
</div>
</span>
</button>
<!-- end view button-->
@ -272,12 +265,13 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
{% if child['type'] == "file" and child['can_edit'] == True or
child['type'] == "folder" and child['can_edit'] == True %}
<button
role="tab"
type="button"
value="edit"
{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
data-{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
class="duration-300 border-gray-300 hover:brightness-90 bg-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 w-full border-b border-l border-r hover:bg-gray-100"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg class="h-6 w-6 stroke-orange-500"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
@ -287,7 +281,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase"
>edit</span
>
</div>
</span>
</button>
{% endif %}
<!-- end edit button -->
@ -296,14 +290,15 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
{% if child['type'] == "file" and child['can_download'] == True %}
{% if current_endpoint == "cache" %}
<button
role="tab"
type="button"
value="download"
{{current_endpoint}}-download="{{child['name'].split('/')[0]}}"
{{current_endpoint}}-file="{{child['name'].split('/')[1]}}"
{{current_endpoint}}-setting-select-dropdown-btn="{{child['name'].split('/')[0]}}"
data-{{current_endpoint}}-download="{{child['name'].split('/')[0]}}"
data-{{current_endpoint}}-file="{{child['name'].split('/')[1]}}"
data-{{current_endpoint}}-setting-select-dropdown-btn="{{child['name'].split('/')[0]}}"
class="duration-300 border-gray-300 hover:brightness-90 bg-white text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 w-full border-b border-l border-r hover:bg-gray-100"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg class="h-6 w-6 stroke-sky-500"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@ -314,16 +309,17 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase"
>download</span
>
</div>
</span>
</button>
{%else%}
<button
role="tab"
type="button"
value="download"
{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
data-{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
class="duration-300 border-gray-300 hover:brightness-90 bg-white text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 w-full border-b border-l border-r hover:bg-gray-100"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg class="h-6 w-6 stroke-sky-500"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@ -334,7 +330,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase"
>download</span
>
</div>
</span>
</button>
{%endif %}
{% endif %}
@ -343,12 +339,13 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- delete button -->
{% if child['can_delete'] == True %}
<button
role="tab"
type="button"
value="delete"
{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
data-{{current_endpoint}}-action-dropdown-btn="{{child['name']}}"
class="bg-white duration-300 border-gray-300 hover:brightness-90 text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 w-full border-b border-l border-r hover:bg-gray-100"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg class="h-6 w-6 stroke-red-500"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
@ -357,7 +354,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-4 font-bold uppercase"
>delete</span
>
</div>
</span>
</button>
{% endif %}
<!-- end delete button -->
@ -375,7 +372,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- modal -->
<div
{{current_endpoint}}-modal
data-{{current_endpoint}}-modal
class="hidden w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 justify-center items-center"
>
<div
@ -383,24 +380,24 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
>
<div class="w-full flex justify-between">
<p
{{current_endpoint}}-modal-title
data-{{current_endpoint}}-modal-title
class="dark:text-white mb-0 font-sans font-semibold leading-normal uppercase text-sm"
>
TITLE
</p>
</div>
<form
{{current_endpoint}}-modal-form
data-{{current_endpoint}}-modal-form
class="w-full"
id="form-services"
method="POST"
>
<div
class="mb-2 flex flex-col sm:flex-row justify-start align-middle items-start sm:items-center"
{{current_endpoint}}-modal-path
data-{{current_endpoint}}-modal-path
>
<p
{{current_endpoint}}-modal-path-prefix
data-{{current_endpoint}}-modal-path-prefix
class="mb-0 dark:text-white dark:opacity-75 text-gray-700 opacity-50 text-sm"
></p>
<input
@ -412,7 +409,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
required
/>
<p
{{current_endpoint}}-modal-path-suffix
data-{{current_endpoint}}-modal-path-suffix
class="mb-0 dark:text-white dark:opacity-75 text-gray-700 opacity-50 text-sm"
>
suffix
@ -423,10 +420,10 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<input type="hidden" id="path" value="" name="path" />
<input type="hidden" id="old_name" value="" name="old_name" />
<input type="hidden" id="_type" value="file" name="type" />
<textarea class="hidden" id="content" name="content" value=""></textarea>
<textarea class="hidden" id="content" name="content"></textarea>
<!-- editor-->
<div
{{current_endpoint}}-modal-editor
data-{{current_endpoint}}-modal-editor
id="editor"
class="text-base w-full h-48 overflow-hidden overflow-y-auto my-2 border border-gray-300 dark:border-slate-800"
></div>
@ -434,14 +431,14 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<div class="mt-4 w-full justify-end flex">
<button
{{current_endpoint}}-modal-close
data-{{current_endpoint}}-modal-close
type="button"
class="dark:brightness-90 mr-3 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Close
</button>
<button
{{current_endpoint}}-modal-submit
data-{{current_endpoint}}-modal-submit
type="submit"
class="dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>

View File

@ -1,37 +1,39 @@
<!-- float button-->
{% with messages = get_flashed_messages(with_categories=true) %}
<button
type="button"
flash-sidebar-open
class="transition scale-90 sm:scale-100 dark:brightness-95 dark:hover:brightness-105 hover:brightness-75 fixed p-3 text-xl bg-white shadow-sm cursor-pointer top-2 sm:top-3 right-19 sm:right-24 xl:right-24 z-990 rounded-circle text-slate-700"
>
<svg
class="fill-yellow-500 -translate-y-0.4 h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
<div class="fixed top-2 sm:top-3 right-19 sm:right-24 xl:right-24 z-990">
<button
type="button"
data-flash-sidebar-open
class="transition scale-90 sm:scale-100 dark:brightness-95 dark:hover:brightness-105 hover:brightness-75 p-3 text-xl bg-white shadow-sm cursor-pointer rounded-circle text-slate-700"
>
<path
d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"
/>
</svg>
<svg
class="fill-yellow-500 -translate-y-0.4 h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"
/>
</svg>
</button>
<div
class="px-2 translate-x-2 bottom-0 right-0 absolute rounded-full bg-white"
>
<p flash-count class="mb-0 text-sm text-bold text-red-500">
<p data-flash-count class="mb-0 text-sm text-bold text-red-500">
{%if messages %} {{messages|length}}{%else%} 0 {%endif%}
</p>
</div>
</button>
</div>
<!-- end float button-->
<!-- right sidebar -->
<aside
flash-sidebar
data-flash-sidebar
class="translate-x-90 -right-0 transition z-sticky dark:bg-slate-850 dark:brightness-110 shadow-3xl max-w-full w-90 ease fixed top-0 left-auto flex h-full min-w-0 flex-col break-words rounded-none border-0 bg-white bg-clip-border px-0.5"
>
<!-- close btn-->
<svg
flash-sidebar-close
data-flash-sidebar-close
class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 absolute h-8 w-8 top-4 right-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
@ -51,7 +53,7 @@
<!-- close button -->
<div class="float-right mt-6">
<button
flash-sidebar-close
data-flash-sidebar-close
class="inline-block p-0 mb-4 text-sm font-bold leading-normal text-center uppercase align-middle transition-all ease-in bg-transparent border-0 rounded-lg shadow-none cursor-pointer hover:-translate-y-px tracking-tight-rem bg-150 bg-x-25 active:opacity-85 dark:text-white text-slate-700"
>
<i class="fa fa-close"></i>
@ -69,7 +71,7 @@
<!-- flash message-->
{% for category, message in messages %}
<div
flash-message
data-flash-message
class="{% if category == 'error' %}bg-red-500 {%else%} bg-green-500 {%endif %} my-2 border relative p-4 w-11/12 min-h-20 rounded-lg hover:scale-102 transition shadow-md break-words dark:brightness-90"
>
<div class="flex justify-between align-top items-start">
@ -79,8 +81,7 @@
<h5 class="text-lg mb-0 text-white">Success</h5>
{%endif%}
<button
close-flash-message
role="close alert message"
data-close-flash-message
type="button"
class="absolute right-8 top-2"
>

View File

@ -1,9 +1,9 @@
{% extends "base.html" %} {% block content %}
<div service-content="settings" class="col-span-12 gap-y-4 grid grid-cols-12">
<div data-service-content="settings" class="col-span-12 gap-y-4 grid grid-cols-12">
<div class="p-4 col-span-12 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div {{current_endpoint}}-tabs-header class="flex justify-start items-center gap-x-4 gap-y-2 my-3">
<div data-{{current_endpoint}}-tabs-header class="flex justify-start items-center gap-x-4 gap-y-2 my-3">
<h5 class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0">CONFIGS</h5>
<!-- search inpt-->
<div class="flex relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3">
@ -27,7 +27,7 @@
<!-- form global conf -->
<form
global-config-form
data-global-config-form
id="form-edit-global-configs"
method="POST"
class="flex flex-col justify-between overflow-hidden overflow-y-auto max-h-135 md:max-h-160 dark:brightness-110 col-span-12 break-words bg-white shadow-xl p-4 dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"

View File

@ -2,8 +2,8 @@
%}
<head>
<base href="{{ config["ABSOLUTE_URI"] }}">
<meta charset="utf-8" />
<base href="{{ config["ABSOLUTE_URI"] }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
@ -21,41 +21,37 @@
<!-- tailwind style -->
<link rel="stylesheet" type="text/css" href="./css/dashboard.css" />
<script type="module" defer src="./js/global.js"></script>
<script type="module" src="./js/global.js"></script>
<script
type="text/javascript"
charset="utf-8"
defer
src="./js/editor/ace.js"
></script>
{% if current_endpoint == "global_config" %}
<script defer type="module" src="./js/global_config.js"></script>
<script type="module" src="./js/global_config.js"></script>
{% elif current_endpoint == "configs" %}
<script defer type="module" src="./js/configs.js"></script>
<script type="module" src="./js/configs.js"></script>
{% elif current_endpoint == "services" %}
<script defer type="module" src="./js/services.js"></script>
<script type="module" src="./js/services.js"></script>
{% elif current_endpoint == "plugins" %}
<script type="module" defer src="./js/plugins.js"></script>
<script type="module" src="./js/plugins.js"></script>
{% elif current_endpoint == "cache" %}
<script defer type="module" src="./js/cache.js"></script>
<script type="module" src="./js/cache.js"></script>
{% elif current_endpoint == "logs" %}
<link rel="stylesheet" type="text/css" href="./css/flatpickr.css" />
<link rel="stylesheet" type="text/css" href="./css/flatpickr.dark.css" />
<script type="module" defer src="./js/utils/flatpickr.js"></script>
<script type="module" defer src="./js/utils/fr.js"></script>
<script type="module" src="./js/utils/flatpickr.js"></script>
<script type="module" src="./js/utils/fr.js"></script>
<script type="module" defer src="./js/logs.js"></script>
<script type="module" defer src="./js/datepicker/datepicker.js"></script>
<script type="module" src="./js/logs.js"></script>
<script type="module" src="./js/datepicker/datepicker.js"></script>
<link
rel="stylesheet"
type="text/css"
href="./css/datepicker-foundation.css"
/>
{% elif current_endpoint == "jobs" %}
<script defer type="module" src="./js/jobs.js"></script>
<script type="module" src="./js/jobs.js"></script>
{% endif %}
</head>

View File

@ -11,7 +11,7 @@
<nav>
<!-- breadcrumb -->
<h6 class="mb-0 text-lg font-bold text-white capitalize">
{{ current_endpoint}}
{{current_endpoint}}
</h6>
<ol class="flex flex-wrap pt-1 mr-12 bg-transparent rounded-lg sm:mr-16">
<li class="text-sm leading-normal">
@ -23,7 +23,7 @@
class="text-sm pl-2 capitalize leading-normal text-white before:float-left before:pr-2 before:text-white before:content-['/']"
aria-current="page"
>
{{ current_endpoint}}
{{current_endpoint}}
</li>
</ol>
</nav>

View File

@ -13,7 +13,7 @@
Version
</p>
<!-- version of user -->
<h5 class="mb-1 font-bold dark:text-white">{{ version }}</h5>
<h5 class="mb-1 font-bold dark:text-gray-400">{{ version }}</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<!-- case no remote fetch -->
@ -64,7 +64,7 @@
>
Instances
</p>
<h5 class="mb-1 font-bold dark:text-white">{{ instances_number }}</h5>
<h5 class="mb-1 font-bold dark:text-gray-400">{{ instances_number }}</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-emerald-500 mx-0.5"
>{{instance_health_count}} / {{ instances_number }}</span
@ -103,7 +103,7 @@
>
Services
</p>
<h5 class="mb-1 font-bold dark:text-white">{{ services_number }}</h5>
<h5 class="mb-1 font-bold dark:text-gray-400">{{ services_number }}</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-sky-600 mx-0.5"
>{{services_ui_count}}</span
@ -150,8 +150,9 @@
>
Plugins
</p>
<!-- add {{ plugins_number }} && {{ plugins_error}}-->
<h5 class="mb-1 font-bold dark:text-white">1</h5>
<h5 class="mb-1 font-bold dark:text-gray-400">
{{ config["CONFIG"].get_plugins()|length }}
</h5>
<p class="mb-0 dark:text-white dark:opacity-60">
<span class="font-bold leading-normal text-sm text-red-500 mx-0.5"
>{{plugins_errors}}</span

View File

@ -10,7 +10,6 @@
instances_batched %}
<!-- instance card -->
<div
href="https://github.com/bunkerity/bunkerweb"
class="overflow-hidden max-h-none sm:max-h- hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:brightness-110 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<form class="w-full" id="form-instance-{{ instance._id }}" method="POST">
@ -43,7 +42,7 @@ instances_batched %}
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
>
{{ instance._type}}
{{ instance._type }}
</p>
</div>
<!-- end detail -->
@ -57,7 +56,7 @@ instances_batched %}
<p
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-90"
>
{{ instance.hostname}}
{{ instance.hostname }}
</p>
</div>
<!-- end detail -->

View File

@ -5,7 +5,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
class="col-span-12 md:col-span-4 3xl:col-span-3 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white">INFO</h5>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -17,7 +17,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{{jobs|length}}
</p>
</div>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -34,11 +34,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- filter -->
<div
{{current_endpoint}}-filter
data-{{current_endpoint}}-filter
class="col-span-12 md:col-span-8 2xl:col-span-6 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white">FILTER</h5>
<div class="grid grid-cols-12 gap-x-4 gap-y-2">
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- search inpt-->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<h5
@ -66,19 +66,19 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
Success state
</h5>
<button
{{current_endpoint}}-setting-select="success"
data-{{current_endpoint}}-setting-select="success"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="{{current_endpoint}}-success"
name="{{current_endpoint}}-success"
{{current_endpoint}}-setting-select-text="success"
data-name="{{current_endpoint}}-success"
data-{{current_endpoint}}-setting-select-text="success"
>all</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="success"
data-{{current_endpoint}}-setting-select="success"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -91,11 +91,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="success"
data-{{current_endpoint}}-setting-select-dropdown="success"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="success"
data-{{current_endpoint}}-setting-select-dropdown-btn="success"
type="button"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
@ -103,7 +103,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
all
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="success"
data-{{current_endpoint}}-setting-select-dropdown-btn="success"
type="button"
value="false"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -111,10 +111,10 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
false
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="success"
data-{{current_endpoint}}-setting-select-dropdown-btn="success"
type="button"
value="true"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="rounded-b border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
true
</button>
@ -130,19 +130,19 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
Reload state
</h5>
<button
{{current_endpoint}}-setting-select="reload"
data-{{current_endpoint}}-setting-select="reload"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="{{current_endpoint}}-reload"
name="{{current_endpoint}}-reload"
{{current_endpoint}}-setting-select-text="reload"
data-name="{{current_endpoint}}-reload"
data-{{current_endpoint}}-setting-select-text="reload"
>all</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="reload"
data-{{current_endpoint}}-setting-select="reload"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -155,11 +155,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="reload"
data-{{current_endpoint}}-setting-select-dropdown="reload"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="reload"
data-{{current_endpoint}}-setting-select-dropdown-btn="reload"
type="button"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
@ -167,7 +167,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
all
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="reload"
data-{{current_endpoint}}-setting-select-dropdown-btn="reload"
type="button"
value="false"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -175,10 +175,10 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
false
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="reload"
data-{{current_endpoint}}-setting-select-dropdown-btn="reload"
type="button"
value="true"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="rounded-b border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
true
</button>
@ -229,7 +229,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" {{current_endpoint}}-list>
<ul class="col-span-12 w-full" data-{{current_endpoint}}-list>
{% for job_name, value in jobs.items() %}
<!-- job item-->
<li
@ -237,26 +237,26 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
{{current_endpoint}}-name
data-{{current_endpoint}}-name
>
{{job_name}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0 my-1"
{{current_endpoint}}-last_run
data-{{current_endpoint}}-last_run
>
{{value['last_run']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
{{current_endpoint}}-every
data-{{current_endpoint}}-every
>
{{value["every"]}}
</p>
{% if value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
{{current_endpoint}}-reload="true"
data-{{current_endpoint}}-reload="true"
>
<svg
class="fill-green-500 h-5 w-5"
@ -272,7 +272,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{%endif %} {% if not value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
{{current_endpoint}}-reload="false"
data-{{current_endpoint}}-reload="false"
>
<svg
class="fill-red-500 h-5 w-5"
@ -287,7 +287,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% endif %} {% if value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
{{current_endpoint}}-success="true"
data-{{current_endpoint}}-success="true"
>
<svg
class="fill-green-500 h-5 w-5"
@ -302,7 +302,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% elif not value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0 my-1"
{{current_endpoint}}-success="false"
data-{{current_endpoint}}-success="false"
>
<svg
class="fill-red-500 h-5 w-5"
@ -317,23 +317,23 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
{% endif %}
<div
class="relative dark:text-gray-400 text-sm col-span-3 m-0 my-1"
{{current_endpoint}}-files
data-{{current_endpoint}}-files
>
{% if value['cache']%}
<button
{{current_endpoint}}-setting-select="{{job_name}}"
data-{{current_endpoint}}-setting-select="{{job_name}}"
type="button"
class="py-1 text-sm disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left leading-6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="{{current_endpoint}}-{{job_name}}"
name="{{current_endpoint}}-{{job_name}}"
{{current_endpoint}}-setting-select-text="{{job_name}}"
data-name="{{current_endpoint}}-{{job_name}}"
data-{{current_endpoint}}-setting-select-text="{{job_name}}"
>files</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="{{job_name}}"
data-{{current_endpoint}}-setting-select="{{job_name}}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -346,19 +346,19 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="{{job_name}}"
data-{{current_endpoint}}-setting-select-dropdown="{{job_name}}"
class="hidden z-100 absolute h-full flex-col w-full translate-y-0.5"
>
{% for file in value['cache'] %}
<button
{{current_endpoint}}-download="{{job_name}}"
{{current_endpoint}}-file="{{file['file_name']}}"
{{current_endpoint}}-setting-select-dropdown-btn="{{job_name}}"
data-{{current_endpoint}}-download="{{job_name}}"
data-{{current_endpoint}}-file="{{file['file_name']}}"
data-{{current_endpoint}}-setting-select-dropdown-btn="{{job_name}}"
type="button"
value="list"
class="{% if loop.index == loop.length %}rounded-b-lg {% endif %}{% if loop.first %}rounded-t-lg{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 bg-white dark:bg-slate-700 text-gray-700"
>
<div class="flex justify-start items-center">
<span class="flex justify-start items-center">
<svg
class="h-6 w-6 fill-sky-500"
xmlns="http://www.w3.org/2000/svg"
@ -372,7 +372,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-2"
>{{file['file_name']}}</span
>
</div>
</span>
</button>
{%endfor %}
</div>

View File

@ -17,11 +17,11 @@
</head>
<body>
<div
loader
data-loader
class="fixed z-[10000] transition duration-300 h-screen w-screen bg-primary flex justify-center align-middle items-center"
>
<img
loader-img
data-loader-img
src="images/logo-menu-2.png"
class="duration-300 w-40 h-12 sm:w-50 sm:h-14 md:w-60 md:h-16 lg:w-80 lg:h-24 inline transition-all"
alt="main logo"
@ -32,11 +32,11 @@
<!-- flash message-->
{% for category, message in messages %}
<div
flash-message
data-flash-message
class="p-4 mb-1 md:mb-3 md:mr-3 z-[1001] flex flex-col fixed bottom-0 right-0 w-full md:w-1/2 max-w-[300px] min-h-20 bg-white rounded-lg dark:brightness-110 hover:scale-102 transition shadow-md break-words dark:bg-slate-850 dark:shadow-dark-xl bg-clip-border"
>
<button
close-flash-message
data-close-flash-message
role="close alert message"
type="button"
class="absolute right-7 top-1.5"
@ -164,9 +164,9 @@
<script>
class Loader {
constructor() {
this.menuContainer = document.querySelector("[menu-container]");
this.logoContainer = document.querySelector("[loader]");
this.logoEl = document.querySelector("[loader-img]");
this.menuContainer = document.querySelector("[data-menu-container]");
this.logoContainer = document.querySelector("[data-loader]");
this.logoEl = document.querySelector("[data-loader-img]");
this.isLoading = true;
this.init();
}
@ -209,7 +209,7 @@
init() {
window.addEventListener("DOMContentLoaded", () => {
try {
const flashEl = document.querySelector("[flash-message]");
const flashEl = document.querySelector("[data-flash-message]");
setTimeout(() => {
try {
flashEl.remove();
@ -221,10 +221,10 @@
window.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute("close-flash-message")
e.target.closest("button").hasAttribute("data-close-flash-message")
) {
const closeBtn = e.target.closest("button");
const flashEl = closeBtn.closest("[flash-message]");
const flashEl = closeBtn.closest("[data-flash-message]");
flashEl.remove();
}
} catch (err) {}

View File

@ -3,11 +3,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- settings -->
<div
{{current_endpoint}}-settings
data-{{current_endpoint}}-settings
class="col-span-12 lg:col-span-8 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white">SETTINGS</h5>
<div class="grid grid-cols-12 gap-x-4 gap-y-2">
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- select instance -->
<div
class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3"
@ -18,14 +18,14 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
Select instance
</h5>
<button
{{current_endpoint}}-setting-select="instances"
data-{{current_endpoint}}-setting-select="instances"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="logs-instance"
name="logs-instance"
{{current_endpoint}}-setting-select-text="instances"
data-name="logs-instance"
data-{{current_endpoint}}-setting-select-text="instances"
>
{% for instance in instances %} {% if loop.first %} {% if
instance.name %} {{instance.name}} {%else%} no instance {%endif%}
@ -33,7 +33,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
</span>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="instances"
data-{{current_endpoint}}-setting-select="instances"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -46,15 +46,15 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="instances"
data-{{current_endpoint}}-setting-select-dropdown="instances"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
{% for instance in instances %}
<button
{{current_endpoint}}-setting-select-dropdown-btn="instances"
data-{{current_endpoint}}-setting-select-dropdown-btn="instances"
type="button"
value="{{instance.name}}"
_type="{{instance._type}}"
data-_type="{{instance._type}}"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{instance.name}}
@ -91,7 +91,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<h5
class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
To date
To date (default today)
</h5>
<input
type="text"
@ -120,7 +120,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
name="update-delay"
class="disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700 col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="2"
pattern="(.*?)"
data-pattern="(.*?)"
required
/>
</div>
@ -134,26 +134,26 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
>
Live update
</h5>
<div checkbox-handler="live-update" class="relative mb-7 md:mb-0">
<div data-checkbox-handler="live-update" class="relative mb-7 md:mb-0">
<input
id="live-update"
name="live-update"
default-method="default"
default-value="no"
data-default-method="default"
data-default-value="no"
class="relative cursor-pointer disabled:cursor-default disabled:pointer-events-none dark:border-slate-600 dark:bg-slate-700 z-10 checked:z-0 w-5 h-5 ease text-base rounded-1.4 checked:bg-primary checked:border-primary dark:checked:bg-primary dark:checked:border-primary duration-250 float-left mt-1 appearance-none border border-gray-300 bg-white bg-contain bg-center bg-no-repeat align-top transition-all disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 disabled:text-gray-700 dark:disabled:text-gray-300"
type="checkbox"
pattern="^(yes|no)$"
data-pattern="^(yes|no)$"
value="no"
/>
<input
type="hidden"
name="live-update"
default-method="default"
default-value="no"
data-default-method="default"
data-default-value="no"
value="no"
/>
<svg
checkbox-handler="live-update"
data-checkbox-handler="live-update"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -168,11 +168,20 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<div class="col-span-12 w-full justify-center flex mt-2">
<button
data-submit-date
type="button"
id="submit-settings"
class="tracking-wide dark:brightness-125 hover:brightness-75 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-gradient-to-tl bg-primary leading-normal text-xs ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Submit
Submit Date
</button>
<button
data-submit-live="no"
type="button"
id="submit-settings"
class="hidden tracking-wide dark:brightness-125 hover:brightness-75 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-gradient-to-tl bg-primary leading-normal text-xs ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Go Live
</button>
</div>
</div>
@ -181,11 +190,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- filter -->
<div
{{current_endpoint}}-filter
data-{{current_endpoint}}-filter
class="col-span-12 lg:col-span-4 p-4 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white">FILTERS</h5>
<div class="grid grid-cols-12 gap-x-4 gap-y-2">
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- search inpt-->
<div class="flex flex-col relative col-span-12">
<h5
@ -212,19 +221,19 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
Select types
</h5>
<button
{{current_endpoint}}-setting-select="types"
data-{{current_endpoint}}-setting-select="types"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="logs-types"
name="logs-types"
{{current_endpoint}}-setting-select-text="types"
data-name="logs-types"
data-{{current_endpoint}}-setting-select-text="types"
>all</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="types"
data-{{current_endpoint}}-setting-select="types"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -237,11 +246,11 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="types"
data-{{current_endpoint}}-setting-select-dropdown="types"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
@ -249,7 +258,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
all
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="message"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -257,7 +266,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
message
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="error"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -265,7 +274,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
error
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="warn"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -273,7 +282,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
warn
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="info"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -281,7 +290,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
info
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="misc"
class="rounded-b border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -317,7 +326,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- list -->
<ul
class="col-span-12 w-full max-h-100 overflow-y-auto"
{{current_endpoint}}-list
data-{{current_endpoint}}-list
></ul>
<!-- end list-->
</div>

View File

@ -4,7 +4,7 @@
<!-- float button-->
<button
type="button"
sidebar-menu-toggle
data-sidebar-menu-toggle
class="scale-90 sm:scale-100 dark:brightness-95 dark:hover:brightness-105 hover:brightness-75 xl:hidden fixed p-3 text-xl bg-white shadow-sm cursor-pointer top-2 sm:top-3 right-5 sm:right-6 z-990 rounded-circle text-slate-700"
>
<svg
@ -22,13 +22,13 @@
<!-- left sidebar -->
<aside
sidebar-menu
data-sidebar-menu
class="fixed flex inset-y-0 flex-wrap justify-between w-full p-0 my-4 overflow-y-auto antialiased transition-transform duration-200 -translate-x-full bg-white border-0 shadow-xl dark:shadow-none dark:bg-slate-850 dark:brightness-110 max-w-64 z-[1000] xl:ml-6 rounded-2xl xl:left-0 xl:translate-x-0"
aria-expanded="false"
>
<!-- close btn-->
<svg
sidebar-menu-close
data-sidebar-menu-close
class="sm:hidden cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 absolute h-6 w-6 top-4 right-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
@ -435,7 +435,7 @@
value="{{ csrf_token() }}"
/>
<input {% if dark_mode == True %}checked{% endif %} id="darkMode"
dark-toggle class="dark:brightness-125 hover:brightness-75 rounded-10
data-dark-toggle class="dark:brightness-125 hover:brightness-75 rounded-10
duration-300 ease-in-out after:rounded-circle after:shadow-2xl
after:duration-300 checked:after:translate-x-5.3 h-5 mt-0.5 relative
float-left w-10 cursor-pointer appearance-none border border-solid
@ -446,7 +446,7 @@
checked:bg-right" type="checkbox" />
<label
for="darkMode"
dark-toggle-label
data-dark-toggle-label
class="dark:text-white dark:opacity-80 transition inline-block pl-3 mb-0 ml-0 font-normal cursor-pointer select-none text-sm text-slate-700"
>
{% if dark_mode == True %}dark mode{% else %} light mode{% endif %}

View File

@ -1,7 +1,7 @@
<!-- float button-->
<button
type="button"
sidebar-info-open
data-sidebar-info-open
class="scale-90 sm:scale-100 dark:brightness-95 dark:hover:brightness-105 hover:brightness-75 fixed p-3 text-xl bg-white shadow-sm cursor-pointer top-16 sm:top-3 right-5 sm:right-40 xl:right-6 z-990 rounded-circle text-slate-700"
>
<svg
@ -18,12 +18,12 @@
<!-- right sidebar -->
<aside
sidebar-info
data-sidebar-info
class="translate-x-90 -right-0 transition z-sticky dark:bg-slate-850 dark:brightness-110 shadow-3xl max-w-full w-90 ease fixed top-0 left-auto flex h-full min-w-0 flex-col break-words rounded-none border-0 bg-white bg-clip-border px-0.5"
>
<!-- close btn-->
<svg
sidebar-info-close
data-sidebar-info-close
class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 absolute h-8 w-8 top-4 right-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
@ -43,7 +43,7 @@
<!-- close button -->
<div class="float-right mt-6">
<button
sidebar-info-close
data-sidebar-info-close
class="inline-block p-0 mb-4 text-sm font-bold leading-normal text-center uppercase align-middle transition-all ease-in bg-transparent border-0 rounded-lg shadow-none cursor-pointer hover:-translate-y-px tracking-tight-rem bg-150 bg-x-25 active:opacity-85 dark:text-white text-slate-700"
>
<i class="fa fa-close"></i>
@ -56,7 +56,7 @@
/>
<!-- end header -->
<!-- news-->
<div news-container class="flex-auto overflow-auto">
<div data-news-container class="flex-auto overflow-auto">
<p
class="text-center col-span-12 relative w-full p-4 text-blue-500 rounded-lg"
>
@ -90,16 +90,16 @@
</div>
<div class="block mt-2 mb-4">
<div class="relative">
<div checkbox-handler="newsletter-check" class="relative mb-7 md:mb-0">
<div data-checkbox-handler="newsletter-check" class="relative mb-7 md:mb-0">
<input
id="newsletter-check"
class="mr-2 relative cursor-pointer dark:border-slate-600 dark:bg-slate-700 z-10 checked:z-0 w-5 h-5 ease text-base rounded-1.4 checked:bg-primary checked:border-primary dark:checked:bg-primary dark:checked:border-primary duration-250 float-left mt-1 appearance-none border border-gray-300 bg-white bg-contain bg-center bg-no-repeat align-top transition-all disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 disabled:text-gray-700 dark:disabled:text-gray-300"
type="checkbox"
pattern="^(yes|no)$"
data-pattern="^(yes|no)$"
value="no"
/>
<svg
checkbox-handler="newsletter-check"
data-checkbox-handler="newsletter-check"
class="pointer-events-none cursor-pointer absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -115,7 +115,7 @@
<a
class="italic"
href="https://www.bunkerity.com/privacy-policy/"
_target="_blank"
target="_blank"
>privacy policy</a
>
</label>

View File

@ -1,13 +1,13 @@
{% extends "base.html" %} {% block content %}{% set current_endpoint =
url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %} {%
include "plugins-modal.html" %}
include "plugins_modal.html" %}
<!-- info -->
<div
class="p-4 col-span-12 md:col-span-5 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="col-span-12 mb-4 font-bold dark:text-white">INFO</h5>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -19,7 +19,7 @@ include "plugins-modal.html" %}
{{plugins|length}}
</p>
</div>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -31,7 +31,7 @@ include "plugins-modal.html" %}
{{plugins_internal}}
</p>
</div>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -43,7 +43,7 @@ include "plugins-modal.html" %}
{{plugins_external}}
</p>
</div>
<div class="flex items-center my-4">
<div class="mx-1 flex items-center my-4">
<p
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
>
@ -60,24 +60,19 @@ include "plugins-modal.html" %}
<!-- upload layout -->
<div
{{current_endpoint}}-upload
data-{{current_endpoint}}-upload
class="p-4 col-span-12 md:col-span-7 2xl:col-span-4 grid grid-cols-12 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="col-span-12 mb-4 font-bold dark:text-white">UPLOAD / RELOAD</h5>
<div class="p-0 col-span-12 grid grid-cols-12">
<div class="mx-2 p-0 col-span-12 grid grid-cols-12">
<!-- dropzone -->
<form
id="dropzone-form"
action="#"
class="hover:bg-gray-100 dark:hover:bg-slate-700/50 cursor-pointer col-span-12 border-2 rounded-lg p-2 border-dashed border-primary dark:brightness-125 drop-zone"
>
<input
type="hidden"
id="csrf_token"
name="csrf_token"
value="{{ csrf_token() }}"
/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input
class="file-input drop-zone__input"
type="file"
@ -90,8 +85,8 @@ include "plugins-modal.html" %}
click or drag and drop
</p>
</form>
<section class="col-span-12 progress-area"></section>
<section class="col-span-12 uploaded-area"></section>
<div class="col-span-12 progress-area"></div>
<div class="col-span-12 uploaded-area"></div>
<!-- end dropzone -->
<div class="col-span-12 flex flex-col justify-center items-center mt-2">
@ -102,7 +97,7 @@ include "plugins-modal.html" %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button
plugin-reload-btn
data-plugin-reload-btn
disabled
type="submit"
class="disabled:hover:translate-y-0 disabled:cursor-not-allowed disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 tracking-wide dark:brightness-125 hover:brightness-75 w-full inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-gradient-to-tl bg-primary leading-normal text-xs ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
@ -117,11 +112,11 @@ include "plugins-modal.html" %}
<!-- filter -->
<div
{{current_endpoint}}-filter
data-{{current_endpoint}}-filter
class="p-4 col-span-12 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<h5 class="mb-2 font-bold dark:text-white">FILTER</h5>
<div class="grid grid-cols-12 gap-x-4 gap-y-2">
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
<!-- search inpt-->
<div class="flex flex-col relative col-span-12">
<h5
@ -148,19 +143,19 @@ include "plugins-modal.html" %}
Select types
</h5>
<button
{{current_endpoint}}-setting-select="types"
data-{{current_endpoint}}-setting-select="types"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
<span
id="types"
name="types"
{{current_endpoint}}-setting-select-text="types"
data-name="types"
data-{{current_endpoint}}-setting-select-text="types"
>all</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="types"
data-{{current_endpoint}}-setting-select="types"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -173,11 +168,11 @@ include "plugins-modal.html" %}
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="types"
data-{{current_endpoint}}-setting-select-dropdown="types"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="all"
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
@ -185,7 +180,7 @@ include "plugins-modal.html" %}
all
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="internal"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -193,7 +188,7 @@ include "plugins-modal.html" %}
internal
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
type="button"
value="external"
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
@ -213,14 +208,14 @@ include "plugins-modal.html" %}
>
<h5 class="mb-4 mt-2 font-bold dark:text-white mx-2">LIST</h5>
<div {{current_endpoint}}-list class="grid grid-cols-12 gap-3">
<div data-{{current_endpoint}}-list class="grid grid-cols-12 gap-3">
{% for plugin in plugins %} {% if plugin['external'] %}
<div
{{current_endpoint}}-external="{% if plugin['external'] %} external {% else %} internal {% endif %}"
data-{{current_endpoint}}-external="{% if plugin['external'] %} external {% else %} internal {% endif %}"
class="py-3 min-h-12 relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3 p-1 flex justify-between items-center transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800"
>
<p
{{current_endpoint}}-content
data-{{current_endpoint}}-content
class="ml-3 mr-2 mb-0 transition duration-300 ease-in-out dark:opacity-90 text-left text-sm md:text-base text-slate-700 dark:text-gray-200"
>
{{plugin['name']}}
@ -243,7 +238,7 @@ include "plugins-modal.html" %}
</a>
{%endif%}
<button
{{current_endpoint}}-action="delete"
data-{{current_endpoint}}-action="delete"
name="{{plugin['id']}}"
type="button"
class="z-20 mx-2 inline-block font-bold text-left text-white uppercase align-middle transition-all cursor-pointer text-xs ease-in tracking-tight-rem hover:-translate-y-px"
@ -262,11 +257,11 @@ include "plugins-modal.html" %}
</div>
{% else %}
<div
{{current_endpoint}}-external="{% if plugin['external'] %} external {%else%} internal {%endif%}"
data-{{current_endpoint}}-external="{% if plugin['external'] %} external {%else%} internal {%endif%}"
class="py-3 min-h-12 relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3 p-1 flex justify-between items-center transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800"
>
<p
{{current_endpoint}}-content
data-{{current_endpoint}}-content
class="ml-3 mb-0 transition duration-300 ease-in-out dark:opacity-90 text-left text-sm md:text-base text-slate-700 dark:text-gray-200"
>
{{plugin['name']}}

View File

@ -1,20 +1,20 @@
<!-- modal -->
<div
plugins-modal
data-plugins-modal
class="dark:brightness-110 w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 hidden justify-center items-center"
>
<div
plugins-modal-card
data-plugins-modal-card
class="min-w-[500px ]overflow-y-auto mx-3 ml-2 mr-6 sm:mx-6 lg:mx-8 my-3 px-4 pt-4 pb-8 w-full max-w-[400px] flex flex-col break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="w-full flex justify-between mb-2">
<p
plugins-modal-title
data-plugins-modal-title
class="transition duration-300 ease-in-out dark:opacity-90 dark:text-gray-300 mb-2 font-sans font-semibold leading-normal uppercase text-md"
>
DELETE PLUGINS
</p>
<button class="-translate-y-1" type="button" plugins-modal-close>
<button class="-translate-y-1" type="button" data-plugins-modal-close>
<svg
class="transition duration-300 ease-in-out dark:opacity-90 h-6 w-6 sm:h-7 sm:w-7 fill-slate-800 dark:fill-gray-300"
xmlns="http://www.w3.org/2000/svg"
@ -28,7 +28,7 @@
</div>
<!-- delete form-->
<form
plugins-modal-form-delete
data-plugins-modal-form-delete
class="w-full h-full flex flex-col justify-between"
id="form-delete-plugin"
method="POST"
@ -39,14 +39,14 @@
<input type="hidden" value="delete" name="operation" id="operation" />
<div>
<p
plugins-modal-text
data-plugins-modal-text
class="text-center mx-2 mb-2 mt-8 font-semibold font-sans leading-normal uppercase text-sm"
></p>
</div>
<!-- action button -->
<div class="w-full justify-center flex mt-10">
<button
plugins-modal-close
data-plugins-modal-close
type="button"
class="dark:brightness-90 mr-3 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>

View File

@ -4,8 +4,8 @@
class="col-span-12 relative flex justify-center min-w-0 break-words rounded-2xl bg-clip-border"
>
<button
services-action="new"
services-name="service"
data-services-action="new"
data-services-name="service"
type="button"
class="dark:bg-green-500/90 duration-300 dark:opacity-90 w-80 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
@ -15,7 +15,7 @@
<!-- end actions -->
<!-- services container-->
<div
class="gap-3 p-4 grid grid-cols-12 col-span-12 relative min-w-0 break-words rounded-2xl bg-clip-border"
class="gap-8 p-4 grid grid-cols-12 col-span-12 relative min-w-0 break-words rounded-2xl bg-clip-border"
>
{% if services|length == 0 %}
<div class="col-span-12 sm:col-span-4 sm:col-start-5">
@ -28,10 +28,10 @@
{% else %}{% for services_batched in services|batch(3) %} {% for service in
services_batched %} {% set id_server_name =
service["SERVER_NAME"]['value'].replace(".", "-") %}
<div services-service
<div data-services-service
class="dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div services-settings class="hidden" value="{{service['settings']}}"></div>
<div data-services-settings class="hidden" data-value="{{service['settings']}}"></div>
<h5 class="transition duration-300 ease-in-out dark:opacity-90 text-center sm:text-left mb-1 font-bold dark:text-white">
{{ service["SERVER_NAME"]['value'] }}
</h5>
@ -339,9 +339,9 @@
<button
services-action="edit"
data-services-action="edit"
type="button"
services-name="{{service["SERVER_NAME"]['value']}}"
data-services-name="{{service["SERVER_NAME"]['value']}}"
class="dark:brightness-90 z-20 mx-1 bg-blue-500 hover:bg-blue-500/80 focus:bg-yellow-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md"
>
@ -354,9 +354,9 @@
{% if service["SERVER_NAME"]['method'] == "ui" %}
<button
services-action="delete"
data-services-action="delete"
type="button"
services-name="{{service["SERVER_NAME"]['value']}}"
data-services-name="{{service["SERVER_NAME"]['value']}}"
class="dark:brightness-90 z-20 mx-1 bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md"
>
<svg
@ -379,5 +379,5 @@
<!-- end services container-->
<!-- modal -->
{% include "services-modal.html" %}
{% include "services_modal.html" %}
{% endblock %}

View File

@ -1,21 +1,21 @@
<!-- modal -->
<div
service-content="settings"
services-modal
data-service-content="settings"
data-services-modal
class="dark:brightness-110 hidden w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 justify-center items-center"
>
<div
services-modal-card
data-services-modal-card
class="overflow-y-auto mx-3 ml-2 mr-6 sm:mx-6 lg:mx-8 my-3 px-4 pt-4 pb-8 w-full min-w-[500px] h-[90vh] flex flex-col break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
>
<div class="w-full flex justify-between mb-2">
<p
services-modal-title
data-services-modal-title
class="transition duration-300 ease-in-out dark:opacity-90 dark:text-gray-200 mb-2 font-sans font-semibold leading-normal uppercase text-md"
>
SERVICE MODAL
</p>
<button class="-translate-y-1" type="button" services-modal-close>
<button class="-translate-y-1" type="button" data-services-modal-close>
<svg
class="transition duration-300 ease-in-out dark:opacity-90 h-6 w-6 sm:h-7 sm:w-7 fill-slate-800 dark:fill-gray-300"
xmlns="http://www.w3.org/2000/svg"
@ -28,7 +28,7 @@
</button>
</div>
<div
services-tabs-header
data-services-tabs-header
class="flex justify-start items-center gap-x-4 gap-y-2 my-3"
>
<h5
@ -55,7 +55,7 @@
{% include "settings_tabs.html" %}
<!-- new and edit form -->
<form
services-modal-form
data-services-modal-form
class="w-full h-full flex flex-col justify-between"
id="form-new"
method="POST"
@ -69,14 +69,14 @@
<!-- action button -->
<div class="w-full justify-center flex mt-10">
<button
services-modal-close
data-services-modal-close
type="button"
class="dark:brightness-90 mb-4 mr-3 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Close
</button>
<button
services-modal-submit
data-services-modal-submit
type="submit"
class="dark:brightness-90 mb-4 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
@ -88,7 +88,7 @@
<!-- end new and edit form -->
<!-- delete form-->
<form
services-modal-form-delete
data-services-modal-form-delete
class="w-full h-full flex flex-col justify-between"
id="form-delete-server_name"
method="POST"
@ -99,14 +99,14 @@
<div class="flex justify-center">
<p
services-modal-text
data-services-modal-text
class="mx-2 mb-2 mt-8 font-semibold font-sans leading-normal uppercase text-sm"
></p>
</div>
<!-- action button -->
<div class="w-full justify-center flex mt-10">
<button
services-modal-close
data-services-modal-close
type="button"
class="dark:brightness-90 mr-3 inline-block px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>

View File

@ -6,12 +6,12 @@
{% for plugin in plugins %}
<div
plugin-item="{{plugin['id']}}"
data-plugin-item="{{plugin['id']}}"
id="{{plugin['id']}}"
class="{% if loop.index != 1 %}hidden{%endif%} w-full"
>
<!-- title and desc -->
<div class="col-span-12" setting-header>
<div class="col-span-12" data-setting-header>
<h5
class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0"
>
@ -24,24 +24,23 @@
</div>
</div>
<!-- end title and desc -->
<div plugin-settings class="w-full grid grid-cols-12">
<div data-plugin-settings class="w-full grid grid-cols-12">
<!-- plugin settings not multiple -->
{% for setting, value in plugin["settings"].items() %}{% if setting != "IS_LOADING" and current_endpoint
== "global-config" and value['context'] == "global" and not value['multiple'] or current_endpoint ==
"services" and value['context'] == "multisite" and not value['multiple'] %}
<div setting-container
class="
mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
<div data-setting-container
class="mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
id="form-edit-{{current_endpoint}}-{{ value["id"] }}">
<!-- title and info -->
<div class="flex items-center my-1 relative">
<div class="flex items-center my-1 relative z-10">
<h5
class="transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
{{value["label"]}}
</h5>
<svg
popover-btn="{{ value["label"] }}"
data-popover-btn="{{ value["label"] }}"
class="cursor-pointer fill-blue-500 h-5 w-5 ml-2 hover:brightness-75"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -51,10 +50,10 @@
/>
</svg>
<!-- popover -->
<div class="hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
popover-content="{{ value["label"] }}"
<div class="dark:brightness-90 hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
data-popover-content="{{ value["label"] }}"
>
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white m-0" >{{value['help']}}
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white dark:text-gray-100 m-0" >{{value['help']}}
</p>
</div>
<!-- end popover -->
@ -66,17 +65,17 @@
<div class="relative flex items-center">
<input
{% if setting == "SERVER_NAME" %}required{%endif%}
default-value="{{global_config[setting]['value']}}" default-method="{{global_config[setting]['method']}}"
data-default-value="{{global_config[setting]['value']}}" data-default-method="{{global_config[setting]['method']}}"
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} id="{{setting}}" name="{{setting}}"
class="outline-none dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:border-gray-300/0 focus:ring-1 focus:valid:ring-green-500 focus:invalid:ring-red-500 text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 md:py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500 disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700"
value="{% if global_config[setting]['value'] %} {{global_config[setting]['value']}} {% else %} {{value['default']}} {% endif %}" type="{{value['type']}}" pattern="{{value['regex']|safe}}" />
{% if value['type'] == "password" %}
<div setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button" setting-password="visible" class="h-5 w-5 flex items-center align-middle" type="button">
<div data-setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button" data-setting-password="visible" class="h-5 w-5 flex items-center align-middle" type="button">
<svg class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg>
</button>
<button type="button" setting-password="invisible" class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<button type="button" data-setting-password="invisible" class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<svg class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg>
</button>
</div>
@ -89,8 +88,8 @@
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select default-method="{{global_config[setting]['method']}}" default-value="{{value['default']}}"
id="{{setting}}" name="{{setting}}" setting-select-default="{{value['id']}}" type="form-select" id="{{setting}}" name="{{setting}}"
<select data-default-method="{{global_config[setting]['method']}}" data-default-value="{{value['default']}}"
id="{{setting}}" name="{{setting}}" data-setting-select-default="{{value['id']}}" data-type="form-select" id="{{setting}}" name="{{setting}}"
class="hidden">
{% for item in value['select'] %}
<option value="{{item}}" {% if global_config[setting]['value'] and global_config[setting]['value'] == item or not global_config[setting]['value'] and value['default'] == item %} selected{% endif %}>{{item}}</option>
@ -99,30 +98,31 @@
<!-- end default hidden-->
<!--custom-->
<div select-container class="relative">
<div data-select-container class="relative">
<button
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} setting-select="{{value['id']}}"
default-value="{{global_config[setting]['value']}}"
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} data-setting-select="{{value['id']}}"
data-default-value="{{global_config[setting]['value']}}"
data-default-method="{{global_config[setting]['method']}}"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-primary flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 md:py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
{% for item in value['select'] %} {% if global_config[setting]['value'] and
global_config[setting]['value'] == item %}
<span
setting-select-text="{{value['id']}}"
value="{{global_config[setting]['value']}}"
data-setting-select-text="{{value['id']}}"
data-value="{{global_config[setting]['value']}}"
>{{global_config[setting]['value']}}</span
>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span
setting-select-text="{{value['id']}}"
value="{{value['default']}}"
data-setting-select-text="{{value['id']}}"
data-value="{{value['default']}}"
>{{value['default']}}</span
>
{% endif %} {% endfor %}
<!-- chevron -->
<svg
setting-select="{{value['id']}}"
data-setting-select="{{value['id']}}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -135,7 +135,7 @@
</button>
<!-- dropdown-->
<div
setting-select-dropdown="{{value['id']}}"
data-setting-select-dropdown="{{value['id']}}"
class="hidden z-[20] absolute h-full flex-col w-full mt-2"
>
{% for item in value['select'] %} {% if global_config[setting]['value'] and
@ -144,9 +144,9 @@
<button
type="button"
value="{{item}}"
setting-select-dropdown-btn="{{value['id']}}"
data-setting-select-dropdown-btn="{{value['id']}}"
type="button"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-primary text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="min-h-[38px] {% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-primary text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{item}}
</button>
@ -154,9 +154,9 @@
<button
type="button"
value="{{item}}"
setting-select-dropdown-btn="{{value['id']}}"
data-setting-select-dropdown-btn="{{value['id']}}"
type="button"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="min-h-[38px] {% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{item}}
</button>
@ -169,15 +169,15 @@
<!-- checkbox -->
{% if value["type"] == "check" %}
<div checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<div data-checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0 z-10">
<input id="{{setting}}" name="{{setting}}"
default-method="{{global_config[setting]['method']}}"
default-value="{{global_config[setting]['value']}}" {% if
data-default-method="{{global_config[setting]['method']}}"
data-default-value="{{global_config[setting]['value']}}" {% if
setting in ["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"] or global_config[setting]['method'] != 'ui' and global_config[setting]['method']
!= 'default' %} disabled {% endif %} {% if global_config[setting]['value'] and
global_config[setting]['value'] == 'yes' or not
global_config[setting]['value'] and value['default'] == 'yes' %} checked {%
endif %} id="checkbox-{{value['id']}}" class="cursor-pointer disabled:cursor-default disabled:pointer-events-none
endif %} id="checkbox-{{value['id']}}" class="cursor-pointer disabled:cursor-default
relative dark:border-slate-600 dark:bg-slate-700 z-10 checked:z-0 w-5 h-5 ease
text-base rounded-1.4 checked:bg-primary checked:border-primary
dark:checked:bg-primary dark:checked:border-primary duration-250 float-left
@ -185,17 +185,17 @@
bg-no-repeat align-top transition-all disabled:bg-gray-400
disabled:border-gray-400 dark:disabled:bg-gray-800
dark:disabled:border-gray-800 disabled:text-gray-700
dark:disabled:text-gray-300" type="checkbox" pattern="{{value['regex']|safe}}"
dark:disabled:text-gray-300" type="checkbox" data-pattern="{{value['regex']|safe}}"
value="{% if global_config[setting]['value'] %}
{{global_config[setting]['value']}} {% else %} {{value['default']}} {% endif
%}" />
<input type="hidden" name="{{setting}}" value="{% if
setting in ["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"] or global_config[setting]['method'] != 'ui' and global_config[setting]['method']
!= 'default' %}{{global_config[setting]['value']}}{% else %}no{% endif %}" default-value="{% if
!= 'default' %}{{global_config[setting]['value']}}{% else %}no{% endif %}" data-default-value="{% if
setting in ["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"] or global_config[setting]['method'] != 'ui' and global_config[setting]['method']
!= 'default' %}{{global_config[setting]['value']}}{% else %}no{% endif %}" default-method="default" />
!= 'default' %}{{global_config[setting]['value']}}{% else %}no{% endif %}" data-default-method="default" />
<svg
checkbox-handler="{{value['id']}}"
data-checkbox-handler="{{value['id']}}"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -232,41 +232,41 @@
{%for multiple in multList %}
<!-- plugin multiple handler -->
<div multiple-handler class="flex items-center mx-0 sm:mx-4 md:mx-6 md:my-3 my-2 2xl:mx-6 2xl:my-3 col-span-12 ">
<div data-multiple-handler class="flex items-center mx-0 sm:mx-4 md:mx-6 md:my-3 my-2 2xl:mx-6 2xl:my-3 col-span-12 ">
<h5
class="transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
{{multiple}}
</h5>
<button {{current_endpoint}}-multiple-add="{{multiple}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
<button data-{{current_endpoint}}-multiple-add="{{multiple}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
Add
</button>
<button {{current_endpoint}}-multiple-toggle="{{multiple}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
<button data-{{current_endpoint}}-multiple-toggle="{{multiple}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
SHOW / HIDE
</button>
</div>
<!-- end plugin multiple handler-->
<!-- multiple settings -->
<div {{current_endpoint}}-settings-multiple="{{multiple}}_SCHEMA" class="bg-gray-50 dark:bg-slate-900/30 hidden w-full mb-8 grid-cols-12 border dark:border-gray-700 rounded">
<div data-{{current_endpoint}}-settings-multiple="{{multiple}}_SCHEMA" class="bg-gray-50 dark:bg-slate-900/30 hidden w-full mb-8 grid-cols-12 border dark:border-gray-700 rounded">
{% for setting, value in plugin["settings"].items() %}
{# render only setting that match the multiple id and context #}
{% if current_endpoint
== "global-config" and value['context'] == "global" and value['multiple'] == multiple or current_endpoint ==
"services" and value['context'] == "multisite" and value['multiple'] == multiple %}
<div setting-container="{{setting}}_SCHEMA"
<div data-setting-container="{{setting}}_SCHEMA"
class="
mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
id="form-edit-{{current_endpoint}}-{{ value["id"] }}">
id="form-edit-{{current_endpoint}}-{{ value["id"] }}_SCHEMA">
<!-- title and info -->
<div class="flex items-center my-1 relative">
<div class="flex items-center my-1 relative z-10">
<h5
class="transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
>
{{value["label"]}}
</h5>
<svg
popover-btn="{{ value["label"] }}"
data-popover-btn="{{ value["label"] }}"
class="cursor-pointer fill-blue-500 h-5 w-5 ml-2 hover:brightness-75"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -276,10 +276,10 @@
/>
</svg>
<!-- popover -->
<div class="hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
popover-content="{{ value["label"] }}"
<div class="dark:brightness-80 hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
data-popover-content="{{ value["label"] }}"
>
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white m-0" >{{value['help']}}
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white dark:text-gray-100 m-0" >{{value['help']}}
</p>
</div>
<!-- end popover -->
@ -290,16 +290,16 @@
{% if value["type"] != "select" and value["type"] != "check" %}
<div class="relative flex items-center">
<input
default-value="{{value['default']}}" default-method="default" id="{{setting}}_SCHEMA" name="{{setting}}_SCHEMA"
data-default-value="{{value['default']}}" data-default-method="default" id="{{setting}}_SCHEMA" name="{{setting}}_SCHEMA"
class="outline-none dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:border-gray-300/0 focus:ring-1 focus:valid:ring-green-500 focus:invalid:ring-red-500 text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 md:py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500 disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700"
value="{{value['default']}}" type="{{value['type']}}" pattern="{{value['regex']|safe}}" />
{% if value['type'] == "password" %}
<div setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button" setting-password="visible" class="h-5 w-5 flex items-center align-middle" type="button">
<div data-setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button"data- setting-password="visible" class="h-5 w-5 flex items-center align-middle" type="button">
<svg class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg>
</button>
<button type="button" setting-password="invisible" class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<button type="button" data-setting-password="invisible" class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<svg class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg>
</button>
</div>
@ -311,8 +311,8 @@
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select default-method="default" default-value="{{value['default']}}"
id="{{setting}}_SCHEMA" name="{{setting}}_SCHEMA" select-default="{{value['id']}}" type="form-select" id="{{setting}}" name="{{setting}}"
<select data-default-method="default" data-default-value="{{value['default']}}"
id="{{setting}}_SCHEMA" name="{{setting}}_SCHEMA" data-select-default="{{value['id']}}" data-type="form-select" id="{{setting}}" name="{{setting}}"
class="hidden">
{% for item in value['select'] %}
<option value="{{item}}" {% if value['default'] == item %} selected {% endif %}>{{item}}</option>
@ -321,23 +321,23 @@
<!-- end default hidden-->
<!--custom-->
<div select-container class="relative">
<div data-select-container class="relative">
<button
setting-select="{{value['id']}}"
default-value="{{value['default']}}"
data-setting-select="{{value['id']}}"
data-default-value="{{value['default']}}"
type="button"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-primary flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 md:py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
>
{% for item in value['select'] %} {% if value['default'] == item %}
<span
setting-select-text="{{value['id']}}"
value="{{value['default']}}"
data-setting-select-text="{{value['id']}}"
data-value="{{value['default']}}"
>{{value['default']}}</span
>
{% endif %} {% endfor %}
<!-- chevron -->
<svg
setting-select="{{value['id']}}"
data-setting-select="{{value['id']}}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -350,16 +350,16 @@
</button>
<!-- dropdown-->
<div
setting-select-dropdown="{{value['id']}}"
data-setting-select-dropdown="{{value['id']}}"
class="hidden z-[20] absolute h-full flex-col w-full mt-2"
>
{% for item in value['select'] %} {% if value['default'] == item %}
<button
type="button"
value="{{item}}"
setting-select-dropdown-btn="{{value['id']}}"
data-setting-select-dropdown-btn="{{value['id']}}"
type="button"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-primary text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="min-h-[38px] {% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-primary text-white my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{item}}
</button>
@ -367,9 +367,9 @@
<button
type="button"
value="{{item}}"
setting-select-dropdown-btn="{{value['id']}}"
data-setting-select-dropdown-btn="{{value['id']}}"
type="button"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
class="min-h-[38px] {% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b {% endif %} border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
>
{{item}}
</button>
@ -382,11 +382,11 @@
<!-- checkbox -->
{% if value["type"] == "check" %}
<div checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<div data-checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<input id="{{setting}}_SCHEMA" name="{{setting}}_SCHEMA"
default-method="default"
default-value="{{value['default']}}" {% if value['default'] == 'yes' %} checked {%
endif %} id="checkbox-{{value['id']}}" class="relative cursor-pointer disabled:cursor-default disabled:pointer-events-none
data-default-method="default"
data-default-value="{{value['default']}}" {% if value['default'] == 'yes' %} checked {%
endif %} id="checkbox-{{value['id']}}" class="relative cursor-pointer disabled:cursor-default
dark:border-slate-600 dark:bg-slate-700 z-10 checked:z-0 w-5 h-5 ease
text-base rounded-1.4 checked:bg-primary checked:border-primary
dark:checked:bg-primary dark:checked:border-primary duration-250 float-left
@ -394,11 +394,11 @@
bg-no-repeat align-top transition-all disabled:bg-gray-400
disabled:border-gray-400 dark:disabled:bg-gray-800
dark:disabled:border-gray-800 disabled:text-gray-700
dark:disabled:text-gray-300" type="checkbox" pattern="{{value['regex']|safe}}"
dark:disabled:text-gray-300" type="checkbox" data-pattern="{{value['regex']|safe}}"
value="{{value['default']}}" />
<input type="hidden" name="{{setting}}_SCHEMA" value="no" default-value="no" default-method="default" />
<input type="hidden" name="{{setting}}_SCHEMA" value="no" data-default-value="no" data-default-method="default" />
<svg
checkbox-handler="{{value['id']}}"
data-checkbox-handler="{{value['id']}}"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -419,8 +419,8 @@
<!--end invalid feedback-->
</div>
{% endif %} {% endfor %}
<div multiple-delete-container class="col-span-12 flex justify-center my-4">
<button {{current_endpoint}}-multiple-delete="{{plugin['name']}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
<div data-multiple-delete-container class="col-span-12 flex justify-center my-4">
<button data-{{current_endpoint}}-multiple-delete="{{plugin['name']}}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
Remove
</button>
</div>

View File

@ -2,23 +2,23 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
%}
{% set plugins = config["CONFIG"].get_plugins() %}
<div {{current_endpoint}}-tabs class="col-span-12 grid grid-cols-12 {% if current_endpoint == 'services' %}mb-4{% endif %}">
<div data-{{current_endpoint}}-tabs class="col-span-12 grid grid-cols-12 {% if current_endpoint == 'services' %}mb-4{% endif %}">
<!-- desktop tabs -->
<div {{current_endpoint}}-tabs-desktop class="hidden md:block col-span-12">
<div role="tablist" data-{{current_endpoint}}-tabs-desktop class="hidden md:block col-span-12">
<!-- tabs -->
{% for plugin in plugins %} {% if current_endpoint == "services" and plugin["settings"]
and check_settings(plugin["settings"], "multisite") or current_endpoint == "global-config" and plugin["settings"]
and check_settings(plugin["settings"], "global") %}
<button
tab-handler="{{ plugin['id'] }}"
<button role="tab"
data-tab-handler="{{ plugin['id'] }}"
type="button"
class="{% if loop.first %} brightness-90 z-[1001]{%else %} {% endif %} border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 border my-1 relative inline-block px-3 py-3 font-bold text-center uppercase align-middle transition-all rounded-none cursor-pointer bg-white hover:bg-gray-100 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
>
<div class="w-full flex justify-between items-center">
<span class="w-full flex justify-between items-center">
<!-- text and icon -->
<span class="text-primary transition duration-300 ease-in-out dark:opacity-90 pl-3 pr-2 dark:text-gray-300"> {{ plugin["name"] }} </span>
<svg
popover-btn="{{ plugin["name"] }}"
data-popover-btn="{{ plugin["name"] }}"
class=" fill-blue-500 h-5 w-5 mr-2 hover:brightness-95"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
@ -29,14 +29,14 @@
</svg>
<!-- end text and icon -->
<!-- popover -->
<div
popover-content="{{ plugin["name"] }}"
<span
data-popover-content="{{ plugin["name"] }}"
class="top-[60px] min-w-[150px] dark:brightness-90 bg-blue-500 hidden transition z-50 rounded-md p-3 left-0 absolute"
>
<p class="font-bold text-sm text-white m-0">{{ plugin['description'] }}</p>
</div>
<span class="font-bold text-sm text-white m-0">{{ plugin['description'] }}</span>
</span>
<!-- end popover -->
</div>
</span>
</button>
{% endif %} {% endfor %}
<!--end tabs-->
@ -45,7 +45,7 @@
<!-- mobile tabs -->
<div class="md:hidden relative col-span-12 h-full">
<button
tab-dropdown-btn
data-tab-dropdown-btn
type="button"
class="dark:hover:brightness-95 dark:border-slate-600 dark:bg-slate-700 border-primary border w-full flex items-center justify-between rounded-lg hover:-translate-y-px my-1 px-6 py-3 font-bold text-center uppercase align-middle transition-all cursor-pointer bg-white hover:bg-gray-50 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
>
@ -65,7 +65,7 @@
</button>
<!-- dropdown-->
<div
tab-dropdown
data-tab-dropdown
class="hidden z-100 absolute flex-col w-full overflow-hidden overflow-y-auto max-h-90"
>
{% set first_el = "True" %}
@ -75,7 +75,7 @@
{% if loop.first %}
<button
tab-handler-mobile="{{ plugin['id'] }}"
data-tab-handler-mobile="{{ plugin['id'] }}"
type="button"
data-select="false"
id="edit-{{current_endpoint}}-{{ plugin['id'] }}-tab"
@ -84,7 +84,7 @@
</button>
{% else %}
<button
tab-handler-mobile="{{ plugin['id'] }}"
data-tab-handler-mobile="{{ plugin['id'] }}"
type="button"
data-select="false"
id="edit-{{current_endpoint}}-{{ plugin['id'] }}-tab"