Merge pull request #371 from TheophileDiot/dev

Road to the 1.5 🚀
This commit is contained in:
Théophile Diot 2022-11-25 15:43:57 +01:00 committed by GitHub
commit e988aacf38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2001 additions and 4026 deletions

View File

@ -3,7 +3,10 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/bunkerweb-1.4.3-blue" />
<img src="https://img.shields.io/github/license/bunkerity/bunkerweb?color=40bb6b" />
<img src="https://img.shields.io/github/release/bunkerity/bunkerweb?color=085577" />
<img src="https://img.shields.io/github/downloads/bunkerity/bunkerweb/total">
<img src="https://img.shields.io/docker/pulls/bunkerity/bunkerweb?color=085577">
<img src="https://img.shields.io/github/last-commit/bunkerity/bunkerweb" />
<img src="https://img.shields.io/github/workflow/status/bunkerity/bunkerweb/Automatic%20test%2C%20build%2C%20push%20and%20deploy%20%28DEV%29?label=CI%2FCD%20dev" />
<img src="https://img.shields.io/github/workflow/status/bunkerity/bunkerweb/Automatic%20test%2C%20build%2C%20push%20and%20deploy%20%28PROD%29?label=CI%2FCD%20prod" />
@ -262,12 +265,12 @@ BunkerWeb comes with a plugin system to make it possible to easily add new featu
Here is the list of "official" plugins that we maintain (see the [bunkerweb-plugins](https://github.com/bunkerity/bunkerweb-plugins) repository for more information) :
| Name | Version | Description | Link |
| :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------: |
| Name | Version | Description | Link |
| :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------: |
| **ClamAV** | 0.1 | Automatically scans uploaded files with the ClamAV antivirus engine and denies the request when a file is detected as malicious. | [bunkerweb-plugins/clamav](https://github.com/bunkerity/bunkerweb-plugins/tree/main/clamav) |
| **CrowdSec** | 0.1 | CrowdSec bouncer for BunkerWeb. | [bunkerweb-plugins/crowdsec](https://github.com/bunkerity/bunkerweb-plugins/tree/main/crowdsec) |
| **Discord** | 0.1 | Send security notifications to a Discord channel using a Webhook. | [bunkerweb-plugins/discord](https://github.com/bunkerity/bunkerweb-plugins/tree/main/discord) |
| **Slack** | 0.1 | Send security notifications to a Slack channel using a Webhook. | [bunkerweb-plugins/slack](https://github.com/bunkerity/bunkerweb-plugins/tree/main/slack) |
| **Discord** | 0.1 | Send security notifications to a Discord channel using a Webhook. | [bunkerweb-plugins/discord](https://github.com/bunkerity/bunkerweb-plugins/tree/main/discord) |
| **Slack** | 0.1 | Send security notifications to a Slack channel using a Webhook. | [bunkerweb-plugins/slack](https://github.com/bunkerity/bunkerweb-plugins/tree/main/slack) |
| **VirusTotal** | 0.1 | Automatically scans uploaded files with the VirusTotal API and denies the request when a file is detected as malicious. | [bunkerweb-plugins/virustotal](https://github.com/bunkerity/bunkerweb-plugins/tree/main/virustotal) |
You will find more information in the [plugins section](https://docs.bunkerweb.io/latest/plugins) of the documentation.

View File

@ -1,11 +1,12 @@
{% if USE_CUSTOM_HTTPS == "yes" +%}
{% set os_path = import("os.path") %}
{% if USE_CUSTOM_HTTPS == "yes" and os_path.isfile("/data/cache/customcert/{}".format(CUSTOM_HTTPS_CERT.replace("/", "_"))) and os_path.isfile("/data/cache/customcert/{}".format(CUSTOM_HTTPS_KEY.replace("/", "_"))) +%}
# listen on HTTPS PORT
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
# TLS config
ssl_certificate {{ CUSTOM_HTTPS_CERT }};
ssl_certificate_key {{ CUSTOM_HTTPS_KEY }};
ssl_certificate /data/cache/customcert/{{ CUSTOM_HTTPS_CERT.replace("/", "_") }};
ssl_certificate_key /data/cache/customcert/{{ CUSTOM_HTTPS_KEY.replace("/", "_") }};
ssl_protocols {{ HTTPS_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;

View File

@ -1,7 +1,8 @@
#!/usr/bin/python3
from os import getenv, makedirs
from os import environ, getenv, makedirs, remove
from os.path import isfile
from shutil import copy
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
@ -20,29 +21,86 @@ db = Database(
)
def check_cert(cert_path, first_server: str = None):
def check_cert(cert_path, key_path, first_server: str = None) -> bool:
try:
cache_path = (
if not cert_path or not key_path:
logger.warning(
"Both variables CUSTOM_HTTPS_CERT and CUSTOM_HTTPS_KEY have to be set to use custom certificates"
)
return False
elif not isfile(cert_path):
logger.warning(
f"Certificate file {cert_path} is not a valid file, ignoring the custom certificate"
)
return False
cert_cache_path = (
f"/var/cache/bunkerweb/customcert/{cert_path.replace('/', '_')}.hash"
)
current_hash = file_hash(cert_path)
if not isfile(cache_path):
with open(cache_path, "w") as f:
f.write(current_hash)
old_hash = file_hash(cache_path)
if old_hash == current_hash:
cert_hash = file_hash(cert_path)
if not isfile(cert_cache_path):
with open(cert_cache_path, "w") as f:
f.write(cert_hash)
old_hash = file_hash(cert_cache_path)
if old_hash == cert_hash:
return False
with open(cache_path, "w") as f:
f.write(current_hash)
err = db.update_job_cache(
"custom-cert",
first_server,
f"{cert_path.replace('/', '_')}.hash",
current_hash.encode("utf-8"),
checksum=current_hash,
with open(cert_cache_path, "w") as f:
f.write(cert_hash)
copy(cert_path, cert_cache_path.replace(".hash", ""))
if not isfile(key_path):
logger.warning(
f"Key file {key_path} is not a valid file, removing the custom certificate ..."
)
remove(cert_path)
remove(cert_cache_path)
return False
key_cache_path = (
f"/var/cache/bunkerweb/customcert/{key_path.replace('/', '_')}.hash"
)
key_hash = file_hash(key_path)
if not isfile(key_cache_path):
with open(key_cache_path, "w") as f:
f.write(key_hash)
old_hash = file_hash(key_cache_path)
if old_hash != key_hash:
copy(key_path, key_cache_path.replace(".hash", ""))
with open(key_path, "r") as f:
err = db.update_job_cache(
"custom-cert",
first_server,
key_cache_path.replace(".hash", "").split("/")[-1],
f.read().encode("utf-8"),
checksum=key_hash,
)
if err:
logger.warning(
f"Couldn't update db cache for {key_path.replace('/', '_')}.hash: {err}"
)
with open(cert_path, "r") as f:
err = db.update_job_cache(
"custom-cert",
first_server,
cert_cache_path.replace(".hash", "").split("/")[-1],
f.read().encode("utf-8"),
checksum=cert_hash,
)
if err:
logger.warning(f"Couldn't update db cache: {err}")
logger.warning(
f"Couldn't update db cache for {cert_path.replace('/', '_')}.hash: {err}"
)
return True
except:
logger.error(
@ -59,18 +117,25 @@ try:
# Multisite case
if getenv("MULTISITE") == "yes":
for first_server in getenv("SERVER_NAME").split(" "):
if (
getenv(first_server + "_USE_CUSTOM_HTTPS", getenv("USE_CUSTOM_HTTPS"))
if not first_server or (
getenv(
f"{first_server}_USE_CUSTOM_HTTPS", getenv("USE_CUSTOM_HTTPS", "no")
)
!= "yes"
):
continue
if first_server == "":
continue
cert_path = getenv(first_server + "_CUSTOM_HTTPS_CERT")
logger.info(
f"Checking if certificate {cert_path} changed ...",
cert_path = getenv(
f"{first_server}_CUSTOM_HTTPS_CERT", getenv("CUSTOM_HTTPS_CERT", "")
)
need_reload = check_cert(cert_path, first_server)
key_path = getenv(
f"{first_server}_CUSTOM_HTTPS_KEY", getenv("CUSTOM_HTTPS_KEY", "")
)
logger.info(
f"Checking certificate {cert_path} ...",
)
need_reload = check_cert(cert_path, key_path, first_server)
if need_reload:
logger.info(
f"Detected change for certificate {cert_path}",
@ -78,14 +143,15 @@ try:
status = 1
else:
logger.info(
"No change for certificate {cert_path}",
f"No change for certificate {cert_path}",
)
# Singlesite case
elif getenv("USE_CUSTOM_HTTPS") == "yes" and getenv("SERVER_NAME") != "":
cert_path = getenv("CUSTOM_HTTPS_CERT")
logger.info(f"Checking if certificate {cert_path} changed ...")
need_reload = check_cert(cert_path)
cert_path = getenv("CUSTOM_HTTPS_CERT", "")
key_path = getenv("CUSTOM_HTTPS_KEY", "")
logger.info(f"Checking certificate {cert_path} ...")
need_reload = check_cert(cert_path, key_path)
if need_reload:
logger.info(f"Detected change for certificate {cert_path}")
status = 1

View File

@ -12,7 +12,7 @@ import oracledb
from os import _exit, getenv, listdir, makedirs
from os.path import dirname, exists
from pymysql import install_as_MySQLdb
from re import search
from re import compile as re_compile
from sys import modules, path as sys_path
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import create_engine, inspect
@ -118,6 +118,7 @@ class Database:
self.__sql_session.configure(
bind=self.__sql_engine, autoflush=False, expire_on_commit=False
)
self.suffix_rx = re_compile(r"_\d+$")
def get_database_uri(self) -> str:
return self.database_uri
@ -351,7 +352,7 @@ class Database:
for key, value in deepcopy(config).items():
suffix = 0
if search(r"_\d+$", key):
if self.suffix_rx.search(key):
suffix = int(key.split("_")[-1])
key = key[: -len(str(suffix)) - 1]
@ -488,7 +489,7 @@ class Database:
for key, value in config.items():
suffix = 0
if search(r"_\d+$", key):
if self.suffix_rx.search(key):
suffix = int(key.split("_")[-1])
key = key[: -len(str(suffix)) - 1]
@ -637,7 +638,7 @@ class Database:
"""Get the config from the database"""
with self.__db_session() as session:
config = {}
db_services = session.query(Services).with_entities(Services.id).all()
multisite = []
for setting in (
session.query(Settings)
.with_entities(
@ -681,39 +682,35 @@ class Database:
}
)
if setting.context != "multisite":
continue
if setting.context == "multisite":
multisite.append(setting.id)
for service in db_services:
config[f"{service.id}_{setting.id}"] = (
config[setting.id]
if methods is False
else {
"value": config[setting.id]["value"],
"method": "default",
}
)
for service in session.query(Services).with_entities(Services.id).all():
for key, value in deepcopy(config).items():
original_key = key
if self.suffix_rx.search(key):
suffix = int(key.split("_")[-1])
key = key[: -len(str(suffix)) - 1]
service_settings = (
if key not in multisite:
continue
config[f"{service.id}_{original_key}"] = value
suffix = 0
service_setting = (
session.query(Services_settings)
.with_entities(
Services_settings.value,
Services_settings.suffix,
Services_settings.method,
)
.filter_by(service_id=service.id, setting_id=setting.id)
.all()
.filter_by(service_id=service.id, setting_id=key, suffix=suffix)
.first()
)
for service_setting in service_settings:
config[
f"{service.id}_{setting.id}"
+ (
f"_{service_setting.suffix}"
if service_setting.suffix > 0
else ""
)
] = (
if service_setting:
config[f"{service.id}_{original_key}"] = (
service_setting.value
if methods is False
else {

View File

@ -175,9 +175,9 @@ six==1.16.0 \
# google-auth
# kubernetes
# python-dateutil
urllib3==1.26.12 \
--hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
--hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
urllib3==1.26.13 \
--hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
--hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
# via
# docker
# kubernetes
@ -190,7 +190,7 @@ websocket-client==1.4.2 \
# kubernetes
# The following packages are considered to be unsafe in a requirements file:
setuptools==65.6.0 \
--hash=sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840 \
--hash=sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d
setuptools==65.6.3 \
--hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
--hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
# via kubernetes

View File

@ -1,4 +1,4 @@
FROM python:3.10-alpine
FROM python:3.11-alpine
# Copy python requirements
COPY src/scheduler/requirements.txt /tmp/req/requirements.txt

View File

@ -1,3 +1,3 @@
schedule==1.1.0
certbot==1.32.0
certbot==2.0.0
maxminddb==2.2.0

View File

@ -4,13 +4,13 @@
#
# pip-compile --allow-unsafe --generate-hashes
#
acme==1.32.0 \
--hash=sha256:c7917e044f4232585c6ce1d46655cf9495bdbe08b0bffac1e4b6f9fa03c9b940 \
--hash=sha256:ea2c5f12aca70c645c205ba0ff024f397a3d2e810bcff560fc0faa2b43b9f393
acme==2.0.0 \
--hash=sha256:17da5a3dc353e56104e9e2f5699c3fc6758e7939cc502f5d71aa045df6f0b244 \
--hash=sha256:7106d0bd04fab062eb657d7cf77f76d6c9babca9813603a7435be4382ea6dad8
# via certbot
certbot==1.32.0 \
--hash=sha256:1a86aed518a25af87a5ab43ed4b07375c9ac4db4e756c25d53af44f25dbdd2dd \
--hash=sha256:a7f35205953962dbf245d95a30b903c521ab20713a15b89218c72521ddea386e
certbot==2.0.0 \
--hash=sha256:05a63c25d4a622dfb63b8d346cb97cba86d38ca6e7d71d2bbbc9223995731c13 \
--hash=sha256:9e6f16be2a8e007b041099b9b6bc4d92566bc40f1066053ab00afbf4e4ca80de
# via -r requirements.in
certifi==2022.9.24 \
--hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \
@ -172,12 +172,6 @@ pytz==2022.6 \
requests==2.28.1 \
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
# via
# acme
# requests-toolbelt
requests-toolbelt==0.10.1 \
--hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \
--hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d
# via acme
schedule==1.1.0 \
--hash=sha256:617adce8b4bf38c360b781297d59918fbebfb2878f1671d189f4f4af5d0567a4 \
@ -187,106 +181,16 @@ six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via configobj
urllib3==1.26.12 \
--hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
--hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
urllib3==1.26.13 \
--hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
--hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
# via requests
zope-component==5.0.1 \
--hash=sha256:32cbe426ba8fa7b62ce5b211f80f0718a0c749cc7ff09e3f4b43a57f7ccdf5e5 \
--hash=sha256:e955eb9f1e55d30e2d8097c8baa9ee012c356887eef3b0d43e6bfcd4868221e5
# via certbot
zope-event==4.5.0 \
--hash=sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42 \
--hash=sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330
# via zope-component
zope-hookable==5.4 \
--hash=sha256:0054539ed839751b7f511193912cba393f0b8b5f7dfe9f3601c65b2d3b74e731 \
--hash=sha256:049ef54de127236e555d0864ad3b950b2b6e5048cdf1098cf340c6fb108104c7 \
--hash=sha256:06570ed57b22624c7673ff203801bbdece14d2d42dc5d9879c24ef5612c53456 \
--hash=sha256:0e9e5adc24954e157e084bee97362346470a06d0305cb095118367a8a776dce4 \
--hash=sha256:2e8fd79437c2007020d3faac41e13c49bcbaa6a0738e4142b996c656dcb5bb69 \
--hash=sha256:4313b3d282c1c26fcb69569b7988bc2de0b6dc59238ae7189b6b7b29503d47cb \
--hash=sha256:448ca90d78bd3aef75fe5d55d19f5d05a217193738b7a8d5fd9e93ecf2c02c84 \
--hash=sha256:4b2fd781571336b0b7655826d9a052379a06b62af138085409b2e3fef1e6fb3d \
--hash=sha256:5215355203b9583b7f2a8f06fa7df272562cc12bf5be1a960a45ea49c3294426 \
--hash=sha256:5cb0e4a23588435c6911bde300158d31e47c73c469fbf59d927e801e1cb457ef \
--hash=sha256:71bff8f7c2e223f92a218b0909ccc6f612c075cc3b5ed164cf152f1537cae2ca \
--hash=sha256:7241ab28df7288d9a8bf49339a0aabfbf035b93d6a2a843af13d13dfa735c46a \
--hash=sha256:7269a0fbcd7c5901e255679f8dac835b628eab58d5490c38cf2b15508f181e64 \
--hash=sha256:7401bd6138e58231aef751c63718726259a7aa6875d746d8a87bba70271b9cff \
--hash=sha256:761c9bf1b8df6e2b2d5ae87cda27b8e82c33e2f328750e039de4f6f7f35b73cd \
--hash=sha256:78c51f04aabd3b77ba8d3b2c2abaff8b7598376fea7bd1af9929e90549f6dd4c \
--hash=sha256:93cfda0663d4d3db6b1818619fbc14e3df2e703454983c841b3b95894d559f86 \
--hash=sha256:9af06ca83ff1ef9f94a98d08095dd8960fc5b71ffc7ed7db05988dc493e148a1 \
--hash=sha256:9cffa01d8ef1172492fd6df0113ff5432006129b9bd6e8265e1e4985362b973d \
--hash=sha256:9d398b1de407a5908c8e5f55fb7a26fa177916b1203e697ef0b4c3389dd28e14 \
--hash=sha256:9f447ecaf7741257333f4b1cc215de633daaf147dbc87133638142ed88492617 \
--hash=sha256:9f5d425eb57dee785e4d32703e45c5d6cf2b9fa7ad37c10214593b5f62daa60b \
--hash=sha256:9f7dd1b45cd13976f49ad21f48a8253628c74ad5eefe3f6e14d50f38cc45f613 \
--hash=sha256:9fd11381ec66a8569f999dbe11c94870ddf8aecd591300f203a927f18e938a24 \
--hash=sha256:acec917178af910959205f98f48bcd0a165bdcd6b4d8b3f4baf06fa393ac5ff5 \
--hash=sha256:b65e86a5cb8244d83eabd021f70968d4a80fac01edc99f6e35d29e5458a128bb \
--hash=sha256:bad033b8adfe71f650fef2d4fc33452b3310a0e53139a530dbffbcf9fe08c8c8 \
--hash=sha256:c39ffe1b1ef7543e8efafdc6472d7b9ece8ed1ebe20be261522346463aa2c8c0 \
--hash=sha256:c79da9673a7d704f6ea2a4bbef6e5e161adbba9d8371476de28a0e3416510cc1 \
--hash=sha256:d06da931ac88ebb4c02ac89d0b6fdb2e4fff130901edf9c6e7ea0338a2edf6bd \
--hash=sha256:d44229a0aa8d3587491f359d7326c55b5db6379f68656785dece792afbcfcbae \
--hash=sha256:d5e50bfbcde1afe32f9cf7fa5e8ea42e218090ecb989c31164d708d0491134b7 \
--hash=sha256:d822b7ec71ebb5c96df000e2180127e94ba49258335ae796dc4b6201259b2502 \
--hash=sha256:eeb4042f9b1771a1dd8377cb1cb307c4a4f5821d1491becbdc69bc9de66d3918 \
--hash=sha256:fb601f00ac87e5aa582a81315ed96768ce3513280729d3f51f79312e2b8b94ac \
--hash=sha256:fd49da3340339b8aeef31153ce898e93867ee5a7ffcf685e903ceae6717f0cc2
# via zope-component
zope-interface==5.5.2 \
--hash=sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32 \
--hash=sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0 \
--hash=sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c \
--hash=sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c \
--hash=sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d \
--hash=sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf \
--hash=sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b \
--hash=sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc \
--hash=sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f \
--hash=sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d \
--hash=sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e \
--hash=sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16 \
--hash=sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f \
--hash=sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9 \
--hash=sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296 \
--hash=sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a \
--hash=sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d \
--hash=sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d \
--hash=sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189 \
--hash=sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4 \
--hash=sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452 \
--hash=sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a \
--hash=sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0 \
--hash=sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5 \
--hash=sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671 \
--hash=sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e \
--hash=sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f \
--hash=sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396 \
--hash=sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7 \
--hash=sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b \
--hash=sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf \
--hash=sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f \
--hash=sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6 \
--hash=sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188 \
--hash=sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7 \
--hash=sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b
# via
# certbot
# zope-component
# The following packages are considered to be unsafe in a requirements file:
setuptools==65.6.0 \
--hash=sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840 \
--hash=sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d
setuptools==65.6.3 \
--hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
--hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
# via
# acme
# certbot
# josepy
# zope-component
# zope-event
# zope-hookable
# zope-interface

View File

@ -137,7 +137,7 @@ wtforms==3.0.1 \
# via flask-wtf
# The following packages are considered to be unsafe in a requirements file:
setuptools==65.6.0 \
--hash=sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840 \
--hash=sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d
setuptools==65.6.3 \
--hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
--hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
# via gunicorn

View File

@ -787,6 +787,10 @@ h6 {
z-index: 20;
}
.-z-10 {
z-index: -10;
}
.z-10 {
z-index: 10;
}
@ -807,8 +811,12 @@ h6 {
z-index: 50;
}
.-z-10 {
z-index: -10;
.z-\[1500\] {
z-index: 1500;
}
.z-\[10000\] {
z-index: 10000;
}
.order-2 {
@ -843,10 +851,6 @@ h6 {
grid-column: auto;
}
.col-span-6 {
grid-column: span 6 / span 6;
}
.float-right {
float: right;
}
@ -907,24 +911,14 @@ h6 {
margin-right: 0.25rem;
}
.my-1 {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.mx-2\.5 {
margin-left: 0.625rem;
margin-right: 0.625rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
.my-1 {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.my-3 {
@ -932,21 +926,26 @@ h6 {
margin-bottom: 0.75rem;
}
.mx-2\.5 {
margin-left: 0.625rem;
margin-right: 0.625rem;
}
.mx-3 {
margin-left: 0.75rem;
margin-right: 0.75rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.my-5 {
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}
.my-6 {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
.mb-1 {
margin-bottom: 0.25rem;
}
@ -1011,6 +1010,18 @@ h6 {
margin-left: 0.25rem;
}
.ml-6 {
margin-left: 1.5rem;
}
.ml-2 {
margin-left: 0.5rem;
}
.mb-8 {
margin-bottom: 2rem;
}
.mb-7 {
margin-bottom: 1.75rem;
}
@ -1019,14 +1030,6 @@ h6 {
margin-top: 0.25rem;
}
.ml-6 {
margin-left: 1.5rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-2 {
margin-top: 0.5rem;
}
@ -1039,14 +1042,14 @@ h6 {
margin-top: 0.125rem;
}
.ml-2 {
margin-left: 0.5rem;
}
.ml-0 {
margin-left: 0px;
}
.mb-4 {
margin-bottom: 1rem;
}
.ml-auto {
margin-left: auto;
}
@ -1075,8 +1078,8 @@ h6 {
margin-bottom: 1.25rem;
}
.mb-8 {
margin-bottom: 2rem;
.mr-4 {
margin-right: 1rem;
}
.box-content {
@ -1143,14 +1146,14 @@ h6 {
height: 3rem;
}
.h-3 {
height: 0.75rem;
}
.h-16 {
height: 4rem;
}
.h-3 {
height: 0.75rem;
}
.h-19 {
height: 4.75rem;
}
@ -1179,10 +1182,6 @@ h6 {
height: 25rem;
}
.h-7 {
height: 1.75rem;
}
.h-1\/3 {
height: 33.333333%;
}
@ -1191,16 +1190,16 @@ h6 {
max-height: 100vh;
}
.max-h-135 {
max-height: 33.75rem;
}
.max-h-100 {
max-height: 25rem;
}
.max-h-60 {
max-height: 15rem;
}
.max-h-16 {
max-height: 4rem;
.max-h-30 {
max-height: 7.5rem;
}
.max-h-80 {
@ -1215,14 +1214,6 @@ h6 {
max-height: 2rem;
}
.max-h-30 {
max-height: 7.5rem;
}
.max-h-135 {
max-height: 33.75rem;
}
.min-h-20 {
min-height: 5rem;
}
@ -1235,6 +1226,10 @@ h6 {
min-height: 50vh;
}
.min-h-screen {
min-height: 100vh;
}
.min-h-6 {
min-height: 1.5rem;
}
@ -1259,18 +1254,6 @@ h6 {
min-height: 85vh;
}
.min-h-screen {
min-height: 100vh;
}
.min-h-90 {
min-height: 22.5rem;
}
.min-h-80 {
min-height: 20rem;
}
.w-full {
width: 100%;
}
@ -1307,14 +1290,14 @@ h6 {
width: 1.25rem;
}
.w-3 {
width: 0.75rem;
}
.w-60 {
width: 15rem;
}
.w-3 {
width: 0.75rem;
}
.w-28 {
width: 7rem;
}
@ -1335,36 +1318,20 @@ h6 {
width: 20rem;
}
.w-7 {
width: 1.75rem;
.w-40 {
width: 10rem;
}
.min-w-0 {
min-width: 0px;
}
.min-w-\[900\] {
min-width: 900;
}
.min-w-\[900px\] {
min-width: 900px;
}
.min-w-\[800px\] {
min-width: 800px;
}
.min-w-\[700px\] {
min-width: 700px;
}
.min-w-\[750px\] {
min-width: 750px;
}
.max-w-screen-sm {
max-width: 576px;
.max-w-\[300px\] {
max-width: 300px;
}
.max-w-180 {
@ -1375,8 +1342,16 @@ h6 {
max-width: 10rem;
}
.max-w-120 {
max-width: 30rem;
.max-w-screen-sm {
max-width: 576px;
}
.max-w-\[400px\] {
max-width: 400px;
}
.max-w-60 {
max-width: 15rem;
}
.max-w-64 {
@ -1391,26 +1366,6 @@ h6 {
max-width: 32rem;
}
.max-w-\[300px\] {
max-width: 300px;
}
.max-w-60 {
max-width: 15rem;
}
.max-w-\[460px\] {
max-width: 460px;
}
.max-w-\[40px\] {
max-width: 40px;
}
.max-w-\[400px\] {
max-width: 400px;
}
.flex-auto {
flex: 1 1 auto;
}
@ -1482,11 +1437,6 @@ h6 {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-y-12 {
--tw-translate-y: 3rem;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.-translate-x-full {
--tw-translate-x: -100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -1563,10 +1513,6 @@ h6 {
resize: both;
}
.scroll-m-4 {
scroll-margin: 1rem;
}
.list-none {
list-style-type: none;
}
@ -1589,6 +1535,10 @@ h6 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.flex-col {
flex-direction: column;
}
@ -1654,10 +1604,6 @@ h6 {
overflow: hidden;
}
.overflow-scroll {
overflow: scroll;
}
.overflow-x-auto {
overflow-x: auto;
}
@ -1670,14 +1616,6 @@ h6 {
overflow-x: hidden;
}
.overflow-x-scroll {
overflow-x: scroll;
}
.overflow-y-scroll {
overflow-y: scroll;
}
.whitespace-nowrap {
white-space: nowrap;
}
@ -1744,6 +1682,16 @@ h6 {
border-top-right-radius: 0.25rem;
}
.rounded-b-lg {
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.rounded-t-lg {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
.rounded-b {
border-bottom-right-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
@ -1790,6 +1738,10 @@ h6 {
border-style: solid;
}
.border-dashed {
border-style: dashed;
}
.border-white {
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
@ -1878,6 +1830,11 @@ h6 {
background-color: rgb(94 114 228 / var(--tw-bg-opacity));
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(248 249 250 / var(--tw-bg-opacity));
}
.bg-primary\/20 {
background-color: rgb(8 85 119 / 0.2);
}
@ -1886,11 +1843,6 @@ h6 {
background-color: rgb(58 65 111 / 0.1);
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(248 249 250 / var(--tw-bg-opacity));
}
.bg-white\/80 {
background-color: rgb(255 255 255 / 0.8);
}
@ -1899,8 +1851,8 @@ h6 {
background-color: rgb(64 187 107 / 0.8);
}
.bg-opacity-0 {
--tw-bg-opacity: 0;
.bg-none {
background-image: none;
}
.bg-gradient-to-tl {
@ -1911,10 +1863,6 @@ h6 {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
.bg-none {
background-image: none;
}
.from-transparent {
--tw-gradient-from: transparent;
--tw-gradient-to: rgb(0 0 0 / 0);
@ -2079,10 +2027,6 @@ h6 {
padding: 1.5rem;
}
.p-12 {
padding: 3rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
@ -2133,6 +2077,21 @@ h6 {
padding-right: 0.75rem;
}
.py-2\.5 {
padding-top: 0.625rem;
padding-bottom: 0.625rem;
}
.px-12 {
padding-left: 3rem;
padding-right: 3rem;
}
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.px-8 {
padding-left: 2rem;
padding-right: 2rem;
@ -2153,6 +2112,11 @@ h6 {
padding-right: 0.125rem;
}
.py-1\.5 {
padding-top: 0.375rem;
padding-bottom: 0.375rem;
}
.py-1\.4 {
padding-top: 0.35rem;
padding-bottom: 0.35rem;
@ -2168,16 +2132,6 @@ h6 {
padding-bottom: 1.25rem;
}
.px-12 {
padding-left: 3rem;
padding-right: 3rem;
}
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.pb-0 {
padding-bottom: 0px;
}
@ -2347,6 +2301,10 @@ h6 {
line-height: 1;
}
.leading-6 {
line-height: 1.5rem;
}
.leading-tight {
line-height: 1.25;
}
@ -2374,16 +2332,16 @@ h6 {
color: rgb(245 57 57 / var(--tw-text-opacity));
}
.text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
}
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(73 80 87 / var(--tw-text-opacity));
}
.text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
}
.text-slate-700 {
--tw-text-opacity: 1;
color: rgb(52 71 103 / var(--tw-text-opacity));
@ -2451,6 +2409,10 @@ h6 {
opacity: 0.6;
}
.opacity-0 {
opacity: 0;
}
.shadow-md {
--tw-shadow: 0 4px 6px rgba(50,50,93,.1),0 1px 3px rgba(0,0,0,.08);
--tw-shadow-colored: 0 4px 6px var(--tw-shadow-color), 0 1px 3px var(--tw-shadow-color);
@ -2960,6 +2922,11 @@ h6 {
border-color: rgb(58 65 111 / var(--tw-border-opacity));
}
.dark .dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(73 80 87 / var(--tw-border-opacity));
}
.dark .dark\:bg-slate-900 {
--tw-bg-opacity: 1;
background-color: rgb(5 17 57 / var(--tw-bg-opacity));
@ -2997,6 +2964,11 @@ h6 {
background-color: rgb(17 28 68 / 0.8);
}
.dark .dark\:bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(37 47 64 / var(--tw-bg-opacity));
}
.dark .dark\:bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
@ -3028,16 +3000,16 @@ h6 {
fill: #adb5bd;
}
.dark .dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark .dark\:text-gray-300 {
--tw-text-opacity: 1;
color: rgb(210 214 218 / var(--tw-text-opacity));
}
.dark .dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark .dark\:text-gray-500 {
--tw-text-opacity: 1;
color: rgb(173 181 189 / var(--tw-text-opacity));
@ -3174,19 +3146,10 @@ h6 {
grid-column: span 2 / span 2;
}
.sm\:col-span-8 {
grid-column: span 8 / span 8;
}
.sm\:col-start-5 {
grid-column-start: 5;
}
.sm\:my-0 {
margin-top: 0px;
margin-bottom: 0px;
}
.sm\:mx-6 {
margin-left: 1.5rem;
margin-right: 1.5rem;
@ -3205,10 +3168,6 @@ h6 {
margin-right: 4rem;
}
.sm\:block {
display: block;
}
.sm\:hidden {
display: none;
}
@ -3225,22 +3184,22 @@ h6 {
height: 1.75rem;
}
.sm\:h-16 {
height: 4rem;
}
.sm\:h-20 {
height: 5rem;
}
.sm\:h-14 {
height: 3.5rem;
}
.sm\:max-h-28 {
max-height: 7rem;
}
.sm\:max-h-0 {
max-height: 0px;
}
.sm\:max-h-120 {
max-height: 30rem;
}
.sm\:max-h-135 {
max-height: 33.75rem;
}
.sm\:max-h-125 {
max-height: 31.25rem;
}
@ -3257,6 +3216,14 @@ h6 {
width: 1.75rem;
}
.sm\:w-60 {
width: 15rem;
}
.sm\:w-50 {
width: 12.5rem;
}
.sm\:flex-row {
flex-direction: row;
}
@ -3316,10 +3283,6 @@ h6 {
grid-column: span 7 / span 7;
}
.md\:col-span-1 {
grid-column: span 1 / span 1;
}
.md\:my-0 {
margin-top: 0px;
margin-bottom: 0px;
@ -3359,6 +3322,14 @@ h6 {
display: none;
}
.md\:h-25 {
height: 6.25rem;
}
.md\:h-16 {
height: 4rem;
}
.md\:max-h-160 {
max-height: 40rem;
}
@ -3367,21 +3338,20 @@ h6 {
min-height: 75vh;
}
.md\:min-h-50-screen {
min-height: 50vh;
}
.md\:w-1\/2 {
width: 50%;
}
.md\:justify-end {
justify-content: flex-end;
.md\:w-80 {
width: 20rem;
}
.md\:bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
.md\:w-60 {
width: 15rem;
}
.md\:justify-end {
justify-content: flex-end;
}
.md\:py-4 {
@ -3431,8 +3401,8 @@ h6 {
grid-column: span 6 / span 6;
}
.lg\:col-span-12 {
grid-column: span 12 / span 12;
.lg\:col-span-1 {
grid-column: span 1 / span 1;
}
.lg\:col-span-8 {
@ -3443,20 +3413,11 @@ h6 {
grid-column: span 3 / span 3;
}
.lg\:col-span-1 {
grid-column: span 1 / span 1;
}
.lg\:mx-8 {
margin-left: 2rem;
margin-right: 2rem;
}
.lg\:mx-0 {
margin-left: 0px;
margin-right: 0px;
}
.lg\:mt-0 {
margin-top: 0px;
}
@ -3485,6 +3446,14 @@ h6 {
height: 7.5rem;
}
.lg\:h-20 {
height: 5rem;
}
.lg\:h-24 {
height: 6rem;
}
.lg\:w-9 {
width: 2.25rem;
}
@ -3497,6 +3466,10 @@ h6 {
width: 50%;
}
.lg\:w-80 {
width: 20rem;
}
.lg\:flex-none {
flex: none;
}
@ -3525,16 +3498,6 @@ h6 {
gap: 1.5rem;
}
.lg\:bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.lg\:bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(235 239 244 / var(--tw-bg-opacity));
}
.lg\:bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(248 249 250 / var(--tw-bg-opacity));
@ -3558,6 +3521,10 @@ h6 {
top: 0.75rem;
}
.xl\:col-span-4 {
grid-column: span 4 / span 4;
}
.xl\:mx-4 {
margin-left: 1rem;
margin-right: 1rem;
@ -3618,6 +3585,10 @@ h6 {
grid-column: span 4 / span 4;
}
.\32xl\:col-span-6 {
grid-column: span 6 / span 6;
}
.\32xl\:my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@ -3643,6 +3614,10 @@ h6 {
grid-column: span 4 / span 4;
}
.\33xl\:col-span-6 {
grid-column: span 6 / span 6;
}
.\33xl\:col-span-2 {
grid-column: span 2 / span 2;
}

View File

@ -1,274 +0,0 @@
/*# sourceMappingURL=basic.css.map */
@keyframes passing-through {
0% {
opacity: 0;
transform: translateY(40px);
}
30%,
70% {
opacity: 1;
transform: translateY(0px);
}
100% {
opacity: 0;
transform: translateY(-40px);
}
}
@keyframes slide-in {
0% {
opacity: 0;
transform: translateY(40px);
}
30% {
opacity: 1;
transform: translateY(0px);
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
10% {
transform: scale(1.1);
}
20% {
transform: scale(1);
}
}
.dropzone,
.dropzone * {
box-sizing: border-box;
}
.dropzone {
min-height: 100px;
border: 1px solid rgba(0, 0, 0, 0.8);
border-radius: 5px;
padding: 0;
}
.dropzone.dz-clickable {
cursor: pointer;
}
.dropzone.dz-clickable * {
cursor: default;
}
.dropzone.dz-clickable .dz-message,
.dropzone.dz-clickable .dz-message * {
cursor: pointer;
}
.dropzone.dz-started .dz-message {
display: none;
}
.dropzone.dz-drag-hover {
border-style: solid;
}
.dropzone.dz-drag-hover .dz-message {
opacity: 0.5;
}
.dropzone .dz-message {
text-align: center;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
.dropzone .dz-message .dz-button {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
outline: inherit;
}
.dropzone .dz-preview {
position: relative;
display: inline-block;
vertical-align: top;
margin: 12px;
min-height: 100px;
}
.dropzone .dz-preview:hover {
z-index: 1000;
}
.dropzone .dz-preview:hover .dz-details {
opacity: 1;
}
.dropzone .dz-preview.dz-file-preview .dz-image {
border-radius: 20px;
background: #999;
background: linear-gradient(to bottom, #eee, #ddd);
}
.dropzone .dz-preview.dz-file-preview .dz-details {
opacity: 1;
}
.dropzone .dz-preview.dz-image-preview {
background: #fff;
}
.dropzone .dz-preview.dz-image-preview .dz-details {
transition: opacity 0.2s linear;
}
.dropzone .dz-preview .dz-remove {
font-size: 14px;
text-align: center;
display: block;
cursor: pointer;
border: none;
}
.dropzone .dz-preview .dz-remove:hover {
text-decoration: underline;
}
.dropzone .dz-preview:hover .dz-details {
opacity: 1;
}
.dropzone .dz-preview .dz-details {
z-index: 20;
position: absolute;
top: 0;
left: 0;
opacity: 0;
font-size: 13px;
min-width: 100%;
max-width: 100%;
padding: 2em 1em;
text-align: center;
color: rgba(0, 0, 0, 0.9);
line-height: 150%;
}
.dropzone .dz-preview .dz-details .dz-size {
margin-bottom: 1em;
font-size: 16px;
}
.dropzone .dz-preview .dz-details .dz-filename {
white-space: nowrap;
}
.dropzone .dz-preview .dz-details .dz-filename:hover span {
border: 1px solid rgba(200, 200, 200, 0.8);
background-color: rgba(255, 255, 255, 0.8);
}
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) {
overflow: hidden;
text-overflow: ellipsis;
}
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
border: 1px solid transparent;
}
.dropzone .dz-preview .dz-details .dz-filename span,
.dropzone .dz-preview .dz-details .dz-size span {
background-color: rgba(255, 255, 255, 0.4);
padding: 0 0.4em;
border-radius: 3px;
}
.dropzone .dz-preview:hover .dz-image img {
transform: scale(1.05, 1.05);
filter: blur(8px);
}
.dropzone .dz-preview .dz-image {
border-radius: 20px;
overflow: hidden;
width: 120px;
height: 120px;
position: relative;
display: block;
z-index: 10;
}
.dropzone .dz-preview .dz-image img {
display: block;
}
.dropzone .dz-preview.dz-success .dz-success-mark {
animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
}
.dropzone .dz-preview.dz-error .dz-error-mark {
opacity: 1;
animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
}
.dropzone .dz-preview .dz-success-mark,
.dropzone .dz-preview .dz-error-mark {
pointer-events: none;
opacity: 0;
z-index: 500;
position: absolute;
display: block;
top: 50%;
left: 50%;
margin-left: -27px;
margin-top: -27px;
background: rgba(0, 0, 0, 0.8);
border-radius: 50%;
}
.dropzone .dz-preview .dz-success-mark svg,
.dropzone .dz-preview .dz-error-mark svg {
display: block;
width: 54px;
height: 54px;
fill: #fff;
}
.dropzone .dz-preview.dz-processing .dz-progress {
opacity: 1;
transition: all 0.2s linear;
}
.dropzone .dz-preview.dz-complete .dz-progress {
opacity: 0;
transition: opacity 0.4s ease-in;
}
.dropzone .dz-preview:not(.dz-processing) .dz-progress {
animation: pulse 6s ease infinite;
}
.dropzone .dz-preview .dz-progress {
opacity: 1;
z-index: 1000;
pointer-events: none;
position: absolute;
height: 20px;
top: 50%;
margin-top: -10px;
left: 15%;
right: 15%;
border: 3px solid rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.8);
border-radius: 10px;
overflow: hidden;
}
.dropzone .dz-preview .dz-progress .dz-upload {
background: #fff;
display: block;
position: relative;
height: 100%;
width: 0;
transition: width 300ms ease-in-out;
border-radius: 17px;
}
.dropzone .dz-preview.dz-error .dz-error-message {
display: block;
}
.dropzone .dz-preview.dz-error:hover .dz-error-message {
opacity: 1;
pointer-events: auto;
}
.dropzone .dz-preview .dz-error-message {
pointer-events: none;
z-index: 1000;
position: absolute;
display: block;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 8px;
font-size: 13px;
top: 130px;
left: -10px;
width: 140px;
background: #b10606;
padding: 0.5em 1em;
color: #fff;
}
.dropzone .dz-preview .dz-error-message:after {
content: "";
position: absolute;
top: -6px;
left: 64px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #b10606;
} /*# sourceMappingURL=dropzone.css.map */

View File

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["../src/dropzone.scss"],"names":[],"mappings":"AAGA,2BACE,GACE,UACA,2BAGF,QACE,UACA,0BAGF,KACE,UACA,6BAKJ,oBACE,GACE,UACA,2BAEF,IACE,UACA,2BAMJ,iBACE,sBACA,yBACA,wBAKF,sBACE,sBAEF,UAmBE,iBACA,gCACA,kBACA,kBAhBA,uBACE,eAEA,yBACE,eAGA,wEACE,eAWJ,iCACE,aAIJ,wBACE,mBACA,oCACE,WAGJ,sBACE,kBACA,aAEA,iCACE,gBACA,cACA,YACA,UACA,aACA,eACA,gBAMJ,sBACE,kBACA,qBAEA,mBAEA,YACA,iBAEA,4BAEE,aACA,wCACE,UAMF,gDACE,cArEgB,KAsEhB,gBACA,kDAGF,kDACE,UAIJ,uCACE,gBACA,mDACE,8BAIJ,iCACE,eACA,kBACA,cACA,eACA,YACA,uCACE,0BAIJ,wCACE,UAEF,kCAGE,WAEA,kBACA,MACA,OAEA,UAEA,eACA,eACA,eACA,gBACA,kBACA,qBAIA,iBAEA,2CACE,kBACA,eAGF,+CAEE,mBAGE,0DACE,sCACA,sCAGJ,2DAIE,gBACA,uBAJA,gEACE,6BASJ,oGACE,sCACA,eACA,kBASF,0CACE,4BACA,iBAIN,gCACE,cAvKkB,KAwKlB,gBACA,MA3KS,MA4KT,OA5KS,MA6KT,kBACA,cACA,WAEA,oCACE,cAMF,kDACE,6DAIF,8CACE,UACA,sDASJ,4EAKE,oBAEA,UACA,YAEA,kBACA,cACA,QACA,SACA,kBACA,iBAEA,WApBiB,eAqBjB,kBAEA,oFACE,cACA,MAnBY,KAoBZ,OArBa,KAsBb,KA5BY,KAiChB,iDACE,UACA,0BAEF,+CACE,UACA,+BAIA,uDACE,iCAGJ,mCAIE,UACA,aAEA,oBACA,kBACA,YACA,QACA,iBACA,SACA,UAEA,gCACA,WA9DiB,eAgEjB,mBAEA,gBAEA,8CACE,WAtEY,KAwEZ,cACA,kBACA,YACA,QACA,mCAEA,mBAMF,iDACE,cAEF,uDACE,UACA,oBAIJ,wCAIE,oBACA,aACA,kBACA,cACA,aACA,UACA,4BACA,kBACA,eACA,UACA,WACA,MAdQ,MAeR,WAdQ,QAeR,iBACA,WAGA,8CACE,WACA,kBACA,SACA,UACA,QACA,SACA,kCACA,mCACA","file":"dropzone.css"}

View File

@ -1,4 +1,4 @@
import { Checkbox } from "./utils.js";
import { Checkbox, Loader } from "./utils.js";
class Menu {
constructor() {
@ -76,7 +76,30 @@ class darkMode {
}
}
class FlashMsg {
constructor() {
this.delayBeforeRemove = 5000;
this.init();
}
//remove flash message after this.delay if exist
init() {
window.addEventListener("DOMContentLoaded", () => {
try {
const flashEl = document.querySelector("[flash-message]");
setTimeout(() => {
try {
flashEl.remove();
} catch (err) {}
}, this.delayBeforeRemove);
} catch (err) {}
});
}
}
const setLoader = new Loader();
const setMenu = new Menu();
const setNews = new News();
const setDarkM = new darkMode();
const setCheckbox = new Checkbox("[sidebar-info]");
const setFlash = new FlashMsg();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

343
src/ui/static/js/jobs.js Normal file
View File

@ -0,0 +1,343 @@
class Dropdown {
constructor(prefix = "jobs") {
this.prefix = prefix;
this.container = document.querySelector("main");
this.lastDrop = "";
this.initDropdown();
}
initDropdown() {
this.container.addEventListener("click", (e) => {
//SELECT BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
//SELECT DROPDOWN BTN LOGIC
try {
if (
e.target
.closest("button")
.hasAttribute(`${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`
);
//stop if same value to avoid new fetching
const isSameVal = this.isSameValue(btnSetting, btnValue);
if (isSameVal) return this.hideDropdown(btnSetting);
//else, add new value to custom
this.setSelectNewValue(btnSetting, btnValue);
//close dropdown and change style
this.hideDropdown(btnSetting);
this.changeDropBtnStyle(btnSetting, btn);
//show / hide filter
if (btnSetting === "instances") {
this.hideFilterOnLocal(btn.getAttribute("_type"));
}
}
} catch (err) {}
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[${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`
)}"]`
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
);
const currVal = selectCustom.textContent;
return currVal === value ? true : false;
}
setSelectNewValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select="${btnSetting}"]`
);
selectCustom.querySelector(
`[${this.prefix}-setting-select-text]`
).textContent = value;
}
hideDropdown(btnSetting) {
//hide dropdown
const dropdownEl = document.querySelector(
`[${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}"]`
);
dropdownChevron.classList.remove("rotate-180");
}
changeDropBtnStyle(btnSetting, selectedBtn) {
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
);
//reset dropdown btns
const btnEls = dropdownEl.querySelectorAll("button");
btnEls.forEach((btn) => {
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");
});
//highlight clicked btn
selectedBtn.classList.remove(
"bg-white",
"dark:bg-slate-700",
"text-gray-700"
);
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
}
toggleSelectBtn(e) {
const attribut = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
//toggle dropdown
const dropdownEl = document.querySelector(
`[${this.prefix}-setting-select-dropdown="${attribut}"]`
);
const dropdownChevron = document.querySelector(
`svg[${this.prefix}-setting-select="${attribut}"]`
);
dropdownEl.classList.toggle("hidden");
dropdownEl.classList.toggle("flex");
dropdownChevron.classList.toggle("rotate-180");
}
//hide date filter on local
hideFilterOnLocal(type) {
console.log(type);
if (type === "local") {
this.hideInp(`input#from-date`);
this.hideInp(`input#to-date`);
}
if (type !== "local") {
this.showInp(`input#from-date`);
this.showInp(`input#to-date`);
}
}
showInp(selector) {
document.querySelector(selector).closest("div").classList.add("flex");
document.querySelector(selector).closest("div").classList.remove("hidden");
}
hideInp(selector) {
document.querySelector(selector).closest("div").classList.add("hidden");
document.querySelector(selector).closest("div").classList.remove("flex");
}
}
class Filter {
constructor(prefix = "jobs") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
this.keyInp = document.querySelector("input#keyword");
this.successValue = "all";
this.reloadValue = "all";
this.sortValue = "name";
this.initHandler();
}
initHandler() {
//SUCCESS HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
"success"
) {
setTimeout(() => {
const value = document
.querySelector(`[${this.prefix}-setting-select-text="success"]`)
.textContent.trim();
console.log(value);
this.successValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//RELOAD HANDLER
this.container.addEventListener("click", (e) => {
try {
if (
e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select-dropdown-btn`) ===
"reload"
) {
setTimeout(() => {
const value = document
.querySelector(`[${this.prefix}-setting-select-text="reload"]`)
.textContent.trim();
console.log(value);
this.reloadValue = value;
//run filter
this.filter();
}, 10);
}
} catch (err) {}
});
//KEYWORD HANDLER
this.keyInp.addEventListener("input", (e) => {
this.filter();
});
}
filter() {
const jobs = document.querySelector(`[${this.prefix}-list]`).children;
if (jobs.length === 0) return;
//reset
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
el.classList.remove("hidden");
}
//filter type
this.setFilterSuccess(jobs);
this.setFilterReload(jobs);
this.setFilterKeyword(jobs);
}
setFilterSuccess(jobs) {
if (this.successValue === "all") return;
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const type = el
.querySelector(`[${this.prefix}-success]`)
.getAttribute(`${this.prefix}-success`)
.trim();
if (type !== this.successValue) el.classList.add("hidden");
}
}
setFilterReload(jobs) {
if (this.reloadValue === "all") return;
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const type = el
.querySelector(`[${this.prefix}-reload]`)
.getAttribute(`${this.prefix}-reload`)
.trim();
if (type !== this.reloadValue) el.classList.add("hidden");
}
}
setFilterKeyword(jobs) {
const keyword = this.keyInp.value.trim().toLowerCase();
if (!keyword) return;
for (let i = 0; i < jobs.length; i++) {
const el = jobs[i];
const name = el
.querySelector(`[${this.prefix}-name`)
.textContent.trim()
.toLowerCase();
const date = el
.querySelector(`[${this.prefix}-last_run`)
.textContent.trim()
.toLowerCase();
const every = el
.querySelector(`[${this.prefix}-every`)
.textContent.trim()
.toLowerCase();
if (
!name.includes(keyword) &&
!date.includes(keyword) &&
!every.includes(keyword)
)
el.classList.add("hidden");
}
}
}
class Download {
constructor(prefix = "jobs") {
this.prefix = prefix;
this.listContainer = document.querySelector(`[${this.prefix}-list]`);
this.init();
}
init() {
this.listContainer.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-download`)
) {
const btnEl = e.target.closest("button");
const jobName = btnEl.getAttribute("jobs-download");
const fileName = btnEl.getAttribute("jobs-file");
this.sendFileToDL(jobName, fileName);
}
} catch (err) {}
});
}
async sendFileToDL(jobName, fileName) {
const response = await fetch(
`${location.href}/jobs/download?job_name=${jobName}&file_name=${fileName}`
);
if (response.status === 200) {
const res = await response.json();
//last update
return console.log(res);
} else {
console.log(`Error: ${response.status}`);
}
return null;
}
}
const setDropdown = new Dropdown("jobs");
const setFilter = new Filter("jobs");
const setDownload = new Download();

View File

@ -1,7 +1,7 @@
import { Checkbox } from "./utils.js";
import Datepicker from "./datepicker/datepicker.js";
class LogsDropdown {
class Dropdown {
constructor(prefix = "logs") {
this.prefix = prefix;
this.container = document.querySelector("main");
@ -18,6 +18,13 @@ class LogsDropdown {
.hasAttribute(`${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
@ -50,6 +57,23 @@ class LogsDropdown {
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[${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`
)}"]`
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
@ -319,7 +343,7 @@ class FetchLogs {
}
}
class FilterLogs {
class Filter {
constructor(prefix = "logs") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
@ -409,8 +433,8 @@ class LogsDate {
}
const setCheckbox = new Checkbox("[logs-settings]");
const dropdown = new LogsDropdown();
const dropdown = new Dropdown("logs");
const setLogs = new FetchLogs();
const setFilter = new FilterLogs();
const setFilter = new Filter("logs");
const fromDatepicker = new LogsDate(document.querySelector("input#from-date"));
const toDatepicker = new LogsDate(document.querySelector("input#to-date"));

View File

@ -1,29 +1,4 @@
class Upload {
constructor() {
this.dropEl = document.querySelector("#dropzone");
this.drop = new Dropzone(this.dropEl, {
paramName: "file",
method: "post",
maxFiles: 100,
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
url: "#",
});
this.submitBtn = this.dropEl.querySelector('button[type="submit"]');
this.init();
}
init() {
this.submitBtn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
drop.processQueue();
});
}
}
class PluginsDropdown {
class Dropdown {
constructor(prefix = "plugins") {
this.prefix = prefix;
this.container = document.querySelector("main");
@ -40,6 +15,14 @@ class PluginsDropdown {
.hasAttribute(`${this.prefix}-setting-select`) &&
!e.target.closest("button").hasAttribute(`disabled`)
) {
const btnName = e.target
.closest("button")
.getAttribute(`${this.prefix}-setting-select`);
if (this.lastDrop !== btnName) {
this.lastDrop = btnName;
this.closeAllDrop();
}
this.toggleSelectBtn(e);
}
} catch (err) {}
@ -72,6 +55,23 @@ class PluginsDropdown {
});
}
closeAllDrop() {
const drops = document.querySelectorAll(
`[${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`
)}"]`
)
.classList.remove("rotate-180");
});
}
isSameValue(btnSetting, value) {
const selectCustom = document.querySelector(
`[${this.prefix}-setting-select-text="${btnSetting}"]`
@ -170,7 +170,7 @@ class PluginsDropdown {
}
}
class FilterPlugins {
class Filter {
constructor(prefix = "plugins") {
this.prefix = prefix;
this.container = document.querySelector(`[${this.prefix}-filter]`);
@ -242,6 +242,166 @@ class FilterPlugins {
}
}
class Upload {
constructor() {
this.container = document.querySelector("[plugins-upload]");
this.form = document.querySelector("#dropzone-form");
this.dropZoneElement = document.querySelector(".drop-zone");
this.fileInput = document.querySelector(".file-input");
this.progressArea = document.querySelector(".progress-area");
this.uploadedArea = document.querySelector(".uploaded-area");
this.init();
}
init() {
//form click launch input file
this.form.addEventListener("click", () => {
this.fileInput.click();
});
//dropzone logic
this.dropZoneElement.addEventListener("dragover", (e) => {
e.preventDefault();
this.dropZoneElement.classList.add("bg-gray-100 dark:bg-gray-800");
});
["dragleave", "dragend"].forEach((type) => {
this.dropZoneElement.addEventListener(type, (e) => {
this.dropZoneElement.classList.remove("bg-gray-100 dark:bg-gray-800");
});
});
this.dropZoneElement.addEventListener("drop", (e) => {
e.preventDefault();
this.fileInput.files = e.dataTransfer.files;
this.fileInput.dispatchEvent(new Event("change"));
this.dropZoneElement.classList.remove("bg-gray-100 dark:bg-gray-800");
});
//when added file, set upload logic
this.fileInput.addEventListener("change", () => {
const timeout = 500;
for (let i = 0; i < this.fileInput.files.length; i++) {
setTimeout(() => this.uploadFile(this.fileInput.files[i]), timeout * i);
}
});
//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]");
message.remove();
}
} catch (err) {}
});
}
uploadFile(file) {
let name = file.name;
if (name.length >= 12) {
let splitName = name.split(".");
name = splitName[0].substring(0, 13) + "... ." + splitName[1];
}
let xhr = new XMLHttpRequest();
xhr.open("POST", "plugins/upload");
let fileSize;
xhr.upload.addEventListener("progress", ({ loaded, total }) => {
let fileLoaded = Math.floor((loaded / total) * 100);
let fileTotal = Math.floor(total / 1000);
fileTotal < 1024
? (fileSize = fileTotal + " KB")
: (fileSize = (loaded / (1024 * 1024)).toFixed(2) + " MB");
const progressHTML = this.fileLoad(name, fileSize);
this.uploadedArea.classList.add("onprogress");
this.progressArea.innerHTML = progressHTML;
});
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
this.progressArea.innerHTML = "";
let uploadedHTML =
xhr.status == 201
? this.fileSuccess(name, fileSize)
: this.fileFail(name, fileSize);
this.uploadedArea.insertAdjacentHTML("afterbegin", uploadedHTML);
}
});
let data = new FormData();
data.set("file", file);
data.set("csrf_token", document.querySelector("#csrf_token").value);
xhr.send(data);
}
fileLoad(name, fileSize) {
const str = `<div u 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>
<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="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-3 w-3 " 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 512z"/></svg>
</div>
</div>
</div>`;
return str;
}
fileSuccess(name, fileSize) {
const str = `<div 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"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</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>
<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>
</button>
</div>
</div>
</div>`;
return str;
}
fileFail(name, fileSize) {
const str = `<div 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"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</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>
<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>
</button>
</div>
</div>
</div>`;
return str;
}
}
const setDropdown = new Dropdown("plugins");
const setFilter = new Filter("plugins");
const setUpload = new Upload();
const setDropdown = new PluginsDropdown();
const setFilter = new FilterPlugins();

View File

@ -233,9 +233,291 @@ class ServiceModal {
}
}
class Multiple {
constructor(prefix) {
this.prefix = prefix;
this.container = document.querySelector("main");
this.modalForm = document.querySelector(`[${this.prefix}-modal-form]`);
this.init();
}
init() {
this.container.addEventListener("click", (e) => {
//edit button
try {
if (
e.target.closest("button").getAttribute("services-action") === "edit"
) {
//remove all multiples
this.removeMultiples();
//set multiple service values
const servicesSettings = e.target
.closest("[services-service]")
.querySelector("[services-settings]")
.getAttribute("value");
const obj = JSON.parse(servicesSettings);
this.updateModalMultiples(obj);
}
} catch (err) {}
});
this.modalForm.addEventListener("click", (e) => {
//ADD BTN
try {
if (
e.target.closest("button").hasAttribute(`${this.prefix}-multiple-add`)
) {
//get plugin from btn
const btn = e.target.closest("button");
const serviceName = btn.getAttribute(`${this.prefix}-multiple-add`);
//get all multiple groups
const multipleEls = document.querySelectorAll(
`[${this.prefix}-settings-multiple*="${serviceName}"]`
);
const count = Number(
multipleEls[1]
.getAttribute(`${this.prefix}-settings-multiple`)
.substring(
multipleEls[1].getAttribute(`${this.prefix}-settings-multiple`)
.length - 1
)
);
//the default (schema) group is the last group
const schema = document.querySelector(
`[${this.prefix}-settings-multiple="${serviceName}_SCHEMA"]`
);
//clone it and change name by total - 1 (schema is hidden)
const clone = schema.cloneNode(true);
console.log(clone.getAttribute("services-settings-multiple"));
const name = clone
.getAttribute("services-settings-multiple")
.replace(`SCHEMA`, `${count + 1}`);
clone.setAttribute("services-settings-multiple", name);
this.showMultiple(clone);
try {
const cloneContainer = clone.querySelectorAll(
"[setting-container]"
);
cloneContainer.forEach((ctnr) => {
const newName = ctnr
.getAttribute("setting-container")
.replace("SCHEMA", `${count + 1}`);
ctnr.setAttribute("setting-container", newName);
});
} catch (err) {}
try {
const cloneTitles = clone.querySelectorAll("h5");
cloneTitles.forEach((title) => {
title.textContent = `${title.textContent} #${count + 1}`;
});
} catch (err) {}
const setNameID = ["input", "select"];
setNameID.forEach((name) => {
try {
this.setNameIDloop(clone.querySelectorAll(name), count + 1);
} catch (err) {}
});
//insert new group before first one
schema.insertAdjacentElement("afterend", clone);
}
} catch (err) {}
//REMOVE BTN
try {
if (
e.target
.closest("button")
.hasAttribute(`${this.prefix}-multiple-delete`)
) {
const multContainer = e.target.closest(
"[services-settings-multiple]"
);
multContainer.remove();
}
//remove last child
} catch (err) {}
});
}
updateModalMultiples(settings) {
//keep only multiple settings value
const multipleSettings = this.filterMultiple(settings);
//put multiple on the right plugin, on schema container
this.setMultipleToDOM(multipleSettings);
//for each schema container, check if custom multiple (ending with _num)
//and sort them on containers by nums
//and check to keep default data (schema) or custom multiple value if exists
this.sortMultiplesByNum();
//remove custom multiple from schema to avoid them on add btn using schema container
this.removeCustomFromSchema();
}
//keep only multiple settings value
filterMultiple(settings) {
const multiple = {};
for (const [key, data] of Object.entries(settings)) {
if (!isNaN(key[key.length - 1]) && key[key.length - 2] === "_") {
multiple[key] = {
value: data["value"],
method: data["method"],
};
}
}
return multiple;
}
//put multiple on the right plugin, on schema container
setMultipleToDOM(multiples) {
//add them to the right plugin
for (const [key, data] of Object.entries(multiples)) {
const num = key[key.length - 1];
const getSchemaKey = key.substring(0, key.length - 2);
const getSchemaSetting = document.querySelector(
`[setting-container="${getSchemaKey}_SCHEMA"]`
);
const cloneSchemaSetting = getSchemaSetting.cloneNode(true);
//replace info
cloneSchemaSetting.setAttribute("setting-container", key);
const title = cloneSchemaSetting.querySelector("h5");
title.textContent = `${title.textContent} #${num}`;
//replace input info
try {
const inp = cloneSchemaSetting.querySelector("input");
this.setNameID(inp, key);
} catch (err) {}
//or select
try {
const select = cloneSchemaSetting.querySelector("select");
this.setNameID(select, key);
} catch (err) {}
getSchemaSetting.insertAdjacentElement("beforebegin", cloneSchemaSetting);
}
}
//for each schema container, check if custom multiple (ending with _num)
//and sort them on containers by nums
//and check to keep default data (schema) or custom multiple value if exists
sortMultiplesByNum() {
const multiPlugins = document.querySelectorAll(
`[${this.prefix}-settings-multiple*='SCHEMA']`
);
multiPlugins.forEach((defaultGrp) => {
//get group number for the multiples settings
const multipleEls = defaultGrp.querySelectorAll("[setting-container]");
const multNum = new Set();
multipleEls.forEach((setting) => {
const name = setting.getAttribute("setting-container");
if (!isNaN(name[name.length - 1])) multNum.add(name[name.length - 1]);
});
//create a different group for each number
multNum.forEach((num) => {
const newGroup = defaultGrp.cloneNode(true);
this.showMultiple(newGroup);
//change groupe name
const currName = newGroup.getAttribute(
`${this.prefix}-settings-multiple`
);
newGroup.setAttribute(
`${this.prefix}-settings-multiple`,
currName.replace("SCHEMA", num)
);
//remove elements that not fit num unless schema if no custom value
const newGroupSettings = newGroup.querySelectorAll(
"[setting-container]"
);
newGroupSettings.forEach((setting) => {
//remove logic
const settingName = setting.getAttribute("setting-container");
if (
(!settingName.endsWith(num) && !settingName.endsWith("SCHEMA")) ||
(settingName.endsWith("SCHEMA") &&
document.querySelector(`${settingName.replace("SCHEMA", num)}`))
) {
return setting.remove();
}
//else update info by num
setting.setAttribute(
"setting-container",
setting.getAttribute("setting-container").replace(`SCHEMA`, num)
);
const title = setting.querySelector("h5");
if (!title.textContent.includes(`#${num}`))
title.textContent = `${title.textContent} #${num}`;
//replace input info
const setNameID = ["input", "select"];
setNameID.forEach((name) => {
try {
this.setNameID(
setting.querySelector(name),
setting.getAttribute("setting-container")
);
} catch (err) {}
});
});
defaultGrp.insertAdjacentElement("afterend", newGroup);
});
});
}
removeCustomFromSchema() {
const multiPlugins = document.querySelectorAll(
`[${this.prefix}-settings-multiple*='SCHEMA']`
);
multiPlugins.forEach((defaultGrp) => {
const multipleEls = defaultGrp.querySelectorAll("[setting-container]");
multipleEls.forEach((setting) => {
const settingName = setting.getAttribute("setting-container");
if (!settingName.endsWith("SCHEMA")) setting.remove();
});
});
}
//UTILS
removeMultiples() {
const multiPlugins = document.querySelectorAll(
`[${this.prefix}-settings-multiple]`
);
multiPlugins.forEach((multiGrp) => {
if (
!multiGrp.getAttribute("services-settings-multiple").includes("SCHEMA")
)
multiGrp.remove();
});
}
showMultiple(el) {
el.classList.add("grid");
el.classList.remove("hidden");
}
setNameIDloop(iterable, value) {
iterable.forEach((item) => {
const currID = item.getAttribute("id");
const currName = item.getAttribute("name");
item.setAttribute("id", `${currID}_${value}`);
item.setAttribute("name", `${currName}_${value}`);
});
}
setNameID(el, value) {
el.setAttribute("id", `${value}`);
el.setAttribute("name", `${value}`);
}
}
const setCheckbox = new Checkbox("[services-modal-form]");
const setSelect = new Select("[services-modal-form]", "services");
const setPopover = new Popover("main", "services");
const setTabs = new Tabs("[services-tabs]", "services");
const setModal = new ServiceModal();
const format = new FormatValue();
const setMultiple = new Multiple("services");

View File

@ -907,11 +907,48 @@ class FormatValue {
init() {
this.inputs.forEach((inp) => {
console.log(inp);
inp.setAttribute("value", inp.getAttribute("value").trim());
});
}
}
class Loader {
constructor() {
this.menuContainer = document.querySelector("[menu-container]");
this.logoContainer = document.querySelector("[loader]");
this.logoEl = document.querySelector("[loader-img]");
this.isLoading = true;
this.init();
}
init() {
this.loading();
window.addEventListener("load", (e) => {
setTimeout(() => {
this.logoContainer.classList.add("opacity-0");
}, 350);
setTimeout(() => {
this.isLoading = false;
this.logoContainer.classList.add("hidden");
}, 650);
setTimeout(() => {
this.logoContainer.remove();
}, 800);
});
}
loading() {
if ((this.isLoading = true)) {
setTimeout(() => {
this.logoEl.classList.toggle("scale-105");
this.loading();
}, 300);
}
}
}
export {
Checkbox,
Popover,
@ -922,4 +959,5 @@ export {
FolderEditor,
FolderDropdown,
FormatValue,
Loader,
};

View File

@ -9,34 +9,46 @@
<noscript class="relative w-full p-4 text-white bg-yellow-500 rounded-lg"
>Your browser does not support JavaScript!</noscript
>
<!--fixed background-->
<div class="absolute w-full dark:hidden bg-primary"></div>
<!-- end fixed background-->
<div
loader
class="fixed z-[10000] transition duration-300 h-screen w-screen bg-primary flex justify-center align-middle items-center"
>
<img
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"
/>
</div>
{% include "menu.html" %} {% include "news.html" %}
<div
class="w-full relative h-full max-h-screen transition-all duration-200 ease-in-out xl:ml-68 rounded-xl"
>
{% include "header.html" %}
{% include "header.html" %} {% with messages =
get_flashed_messages(with_categories=true) %} {% if messages %}
<!-- flash message-->
{% with messages = get_flashed_messages(with_categories=true) %} {% if
messages %} {% for category, message in messages %}
{% for category, message in messages %}
<div
flash-message
class="hidden 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-screen-sm 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"
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"
>
{% if category == 'error' %}
<h5 class="text-lg mb-0 text-red-500">error</h5>
<p class="text-red-500 mb-0 text-sm">{{ message|safe }}</p>
{% endif %} {% if category == 'success' %}
<h5 class="text-lg mb-0 text-green-500">success</h5>
<p class="text-green-500 mb-0 text-sm">{{ message|safe }}</p>
<h5 class="text-lg mb-0 text-red-500">Error</h5>
<p class="text-gray-700 dark:text-gray-300 mb-0 text-sm">
{{ message|safe }}
</p>
{% else %}
<h5 class="text-lg mb-0 text-green-500">Success</h5>
<p class="text-gray-700 dark:text-gray-300 mb-0 text-sm">
{{ message|safe }}
</p>
{% endif %}
</div>
{% endfor %}
<!-- end flash message-->
{% endfor %} {% endif %} {% endwith %}
{% endif %} {% endwith %}
</div>
<!-- info -->
<main

View File

@ -34,24 +34,19 @@
{% elif current_endpoint == "services" %}
<script defer type="module" src="./js/services.js"></script>
{% elif current_endpoint == "plugins" %}
<script type="module" defer src="./js/dropzone/dropzone-min.js"></script>
<script
type="application"
defer
src="./js/dropzone/dropzone-min.js.map"
></script>
<script type="module" defer src="./js/plugins.js"></script>
<link rel="stylesheet" type="text/css" href="./css/dropzone/dropzone.css" />
{% elif current_endpoint == "cache" %}
<script defer type="module" src="./js/cache.js"></script>
{% elif current_endpoint == "logs" %}
<script type="module" defer src="./js/logs.js"></script>
<script type="module" defer 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" defer src="./js/logs.js"></script>
{% endif %}
</head>

View File

@ -47,7 +47,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- filter -->
<div
{{current_endpoint}}-filter
class="col-span-12 md:col-span-8 3xl:col-span-3 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"
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">
@ -63,82 +63,19 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
id="keyword"
name="keyword"
class="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="key words"
placeholder="keyword"
pattern="(.*?)"
required
/>
</div>
<!-- end search inpt-->
<!-- select sort -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<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"
>
Sort by
</h5>
<button
{{current_endpoint}}-setting-select="sort"
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}}-sort"
name="{{current_endpoint}}-sort"
{{current_endpoint}}-setting-select-text="sort"
>name</span
>
<!-- chevron -->
<svg
{{current_endpoint}}-setting-select="sort"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="sort"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="sort"
type="button"
value="name"
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"
>
name
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="sort"
type="button"
value="last run"
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"
>
last run
</button>
<button
{{current_endpoint}}-setting-select-dropdown-btn="sort"
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"
>
true
</button>
</div>
<!-- end dropdown-->
</div>
<!-- end select success -->
<!-- select success -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<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"
>
Select success
Success state
</h5>
<button
{{current_endpoint}}-setting-select="success"
@ -167,7 +104,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="success"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="success"
@ -202,7 +139,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<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"
>
Select reload
Reload state
</h5>
<button
{{current_endpoint}}-setting-select="reload"
@ -231,7 +168,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="reload"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="reload"
@ -270,7 +207,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
>
<h5 class="mb-2 font-bold dark:text-white">{{current_endpoint}}</h5>
<!-- list container-->
<div class="min-w-[750px] w-full grid grid-cols-12 rounded p-2">
<div class="min-w-[900px] w-full grid grid-cols-12 rounded p-2">
<!-- header-->
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
@ -278,120 +215,180 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
Name
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Last run
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Every
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Reload
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-2 m-0 pb-2 border-b border-gray-400"
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
>
Success
</p>
<p
class="dark:text-gray-300 h-8 text-sm font-bold col-span-1 m-0 pb-2 border-b border-gray-400"
class="dark:text-gray-300 h-8 text-sm font-bold col-span-3 m-0 pb-2 border-b border-gray-400"
>
Files
</p>
<!-- end header-->
<!-- list -->
<ul class="col-span-12 w-full" {{current_endpoint}}-list>
{% for job in jobs %} {{job['every']}}
{% for job_name, value in jobs.items() %}
<!-- job item-->
<li class="grid grid-cols-12 border-b border-gray-300 py-2">
<li class="grid grid-cols-12 border-b border-gray-300 py-2.5">
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0"
{{current_endpoint}}-name
>
{{job}}
{{job_name}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0"
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-3 m-0"
{{current_endpoint}}-last_run
>
{{job['last_run']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0"
{{current_endpoint}}-every
>
{{job["every"]}}
</p>
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0"
{{current_endpoint}}-reload
>
{% if job["reload"] %}
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
{%endif %} {% if not job["reload"] %}
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
{% endif %}
</p>
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-2 m-0"
{{current_endpoint}}-success
>
{% if job["success"] == True %}
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
{% elif not job["success"] %}
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
{% else %}
<svg
class="fill-sky-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM169.8 165.3c7.9-22.3 29.1-37.3 52.8-37.3h58.3c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24V250.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1H222.6c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM288 352c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32z"
/>
</svg>
{% endif %}
{{value['last_run']}}
</p>
<p
class="dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0"
{{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"
{{current_endpoint}}-reload="true"
>
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</p>
{%endif %} {% if not value["reload"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0"
{{current_endpoint}}-reload="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %} {% if value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0"
{{current_endpoint}}-success="true"
>
<svg
class="fill-green-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
/>
</svg>
</p>
{% elif not value["success"] %}
<p
class="ml-6 dark:text-gray-400 dark:opacity-80 text-sm col-span-1 m-0"
{{current_endpoint}}-success="false"
>
<svg
class="fill-red-500 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
/>
</svg>
</p>
{% endif %}
<div
class="relative dark:text-gray-400 text-sm col-span-3 m-0"
{{current_endpoint}}-files
></p>
>
{% if value['cache']%}
<button
{{current_endpoint}}-setting-select="{{job_name}}"
type="button"
class="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}}"
>list</span
>
<!-- chevron -->
<svg
{{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"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="{{job_name}}"
class="hidden z-100 absolute h-full flex-col w-full"
>
{% 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}}"
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">
<svg
class="h-6 w-6 fill-sky-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
d="M256 0C114.6 0 0 114.6 0 256S114.6 512 256 512s256-114.6 256-256S397.4 0 256 0zM244.7 395.3l-112-112c-4.6-4.6-5.9-11.5-3.5-17.4s8.3-9.9 14.8-9.9l64 0 0-96c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32l0 96 64 0c6.5 0 12.3 3.9 14.8 9.9s1.1 12.9-3.5 17.4l-112 112c-6.2 6.2-16.4 6.2-22.6 0z"
/>
</svg>
<span
class="transition duration-300 ease-in-out text-gray-700 dark:text-gray-300 dark:opacity-80 ml-2"
>{{file['file_name']}}</span
>
</div>
</button>
{%endfor %}
</div>
<!-- end dropdown-->
{%endif%}
</div>
</li>
<!-- end job item-->
{% endfor %}

View File

@ -9,19 +9,15 @@
href="data:image/svg+xml, %%3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='96.000000pt' height='96.000000pt' viewBox='0 0 96.000000 96.000000' preserveAspectRatio='xMidYMid meet'%%3E%%3Cg transform='translate(0.000000,96.000000) scale(0.100000,-0.100000)'%%0Afill='%%23085577' stroke='none'%%3E%%3Cpath d='M535 863 c-22 -2 -139 -17 -260 -34 -228 -31 -267 -43 -272 -85 -2%%0A-10 23 -181 55 -379 l57 -360 400 0 400 0 20 40 c16 31 20 59 19 125 -1 100%%0A-24 165 -73 199 -41 29 -46 57 -22 111 30 67 29 188 -3 256 -13 28 -37 60 -53%%0A72 -55 39 -169 62 -268 55z m-15 -348 c30 -16 60 -61 60 -90 0 -10 -8 -33 -17%%0A-52 -16 -34 -16 -41 0 -116 9 -44 15 -82 12 -85 -6 -7 -92 -21 -131 -21 l-31%%0A-1 -6 85 c-4 75 -8 89 -31 112 -20 20 -26 36 -26 70 0 38 5 50 34 79 39 39 86%%0A45 136 19z'/%%3E%%3C/g%%3E%%3C/svg%%3E"
type="image/svg+xml"
/>
<link
rel="stylesheet"
type="text/css"
href="http://127.0.0.1:5501/static/css/dashboard.css"
/>
<link rel="stylesheet" type="text/css" href="./css/dashboard.css" />
</head>
<body>
<div
class="h-screen w-screen bg-primary flex justify-center align-middle items-center"
class="fixed h-screen w-screen bg-primary flex justify-center align-middle items-center"
>
<img
src="images/logo-menu-2.png"
class="duration-300 w-60 h-16 sm:w-80 sm:h-24 lg:w-90 lg:h-30 inline transition-all"
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"
/>
</div>
@ -36,7 +32,7 @@
async function check_reloading() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const timeoutId = setTimeout(() => controller.abort(), 500);
const response = await fetch(
`${location.href.replace("/loading", "/check_reloading")}`,
{ signal: controller.signal }

View File

@ -16,23 +16,25 @@
<link rel="stylesheet" href="css/dashboard.css" />
</head>
<body>
{% if error %}
{% if error %} {% with messages = get_flashed_messages(with_categories=true)
%} {% if messages %}
<!-- flash message-->
{% with messages = get_flashed_messages(with_categories=true) %} {% if
messages %} {% for category, message in messages %}
{% for category, message in messages %}
<div
flash-message
class="hidden 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-screen-sm 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"
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-screen-sm 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"
>
{% if category == 'error' %}
<h5 class="text-lg mb-0 text-red-500">error</h5>
<h5 class="text-lg mb-0 text-red-500">Error</h5>
<p class="text-red-500 mb-0 text-sm">{{ message|safe }}</p>
{% endif %} {% if category == 'success' %}
<h5 class="text-lg mb-0 text-green-500">success</h5>
{% else %}
<h5 class="text-lg mb-0 text-green-500">Success</h5>
<p class="text-green-500 mb-0 text-sm">{{ message|safe }}</p>
{% endif %}
</div>
{% endfor %} {% endif %} {% endwith %} {%endif %}
{% endfor %}
<!-- end flash message-->
{% endif %} {% endwith %} {%endif %}
<!-- end flash message-->
<!--content -->
<main class="grid grid-cols-2 align-middle items-center min-h-screen">

View File

@ -10,7 +10,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<div class="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 lg:col-span-4 3xl:col-span-3"
class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3"
>
<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"
@ -44,7 +44,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="instances"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
{% for instance in instances %}
<button
@ -63,7 +63,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- end select instance -->
<!-- from date input -->
<div
class="flex flex-col relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3"
class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3"
>
<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"
@ -74,7 +74,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
type="text"
id="from-date"
name="from-date"
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 lg: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"
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="dd/mm/yyyy or yyyy/mm/dd"
pattern="(.*?)"
required
@ -83,7 +83,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- end from date input -->
<!-- to date input -->
<div
class="flex flex-col relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3"
class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3"
>
<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"
@ -94,46 +94,17 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
type="text"
id="to-date"
name="to-date"
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 lg: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"
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="dd/mm/yyyy or yyyy/mm/dd"
pattern="(.*?)"
required
/>
</div>
<!-- end to date input -->
<!-- refresh input -->
<div
class="flex flex-col relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3"
>
<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"
>
Update auto
</h5>
<div checkbox-handler="live-update" class="relative mb-7 md:mb-0">
<input
id="live-update"
class="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=""
value="no"
/>
<svg
checkbox-handler="live-update"
class="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"
>
<path
d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
></path>
</svg>
</div>
</div>
<!-- end refresh input -->
<!-- refresh delay input -->
<div
class="flex flex-col relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3"
class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3"
>
<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"
@ -144,13 +115,14 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
type="number"
id="update-delay"
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 lg: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"
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="(.*?)"
required
/>
</div>
<!-- end refresh delay input -->
<div class="col-span-12 w-full justify-center flex mt-2">
<button
type="button"
@ -223,7 +195,7 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="types"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"

View File

@ -0,0 +1,66 @@
<!-- modal -->
<div
plugins-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
plugins-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 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
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>
<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"
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"
/>
</svg>
</button>
</div>
<!-- delete form-->
<form
plugins-modal-form-delete
class="w-full h-full flex flex-col justify-between"
id="form-delete-server_name"
method="POST"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" value="server_name" name="SERVER_NAME" />
<input type="hidden" value="delete" name="operation" />
<div>
<p
plugins-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
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"
>
Close
</button>
<button
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-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Delete
</button>
</div>
<!-- end action button-->
</form>
<!-- end delete form-->
</div>
</div>
<!-- end modal -->

View File

@ -59,34 +59,41 @@ plugins = config["CONFIG"].get_plugins() %}
<!-- upload layout -->
<div
{{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 PLUGIN</h5>
<h5 class="col-span-12 mb-4 font-bold dark:text-white">UPLOAD / RELOAD</h5>
<div class="p-0 col-span-12 grid">
<form id="dropzone" class="col-span-12 dropzone max-h-16 overflow-y-auto">
<!-- Now setup your input fields -->
<div class="p-0 col-span-12 grid grid-cols-12">
<!-- dropzone -->
<form
id="dropzone-form"
action="#"
class="cursor-pointer col-span-12 border-2 rounded-lg p-2 border-dashed border-primary drop-zone"
>
<input
name="upload-csrf"
class="hidden"
type="hidden"
id="csrf_token"
name="csrf_token"
value="{{ csrf_token() }}"
/>
<button name="upload-submit" class="hidden" type="submit">
Submit data and files!
</button>
<input
class="file-input drop-zone__input"
type="file"
name="file"
multiple="multiple"
hidden
/>
<i class="fa-solid fa-cloud-upload-alt"></i>
<p class="text-sm text-center my-3">click or drag and drop</p>
</form>
<div
class="col-span-12 flex flex-col sm:flex-row justify-center items-center mt-3"
>
<button
type="button"
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-primary hover:bg-primary/80 focus:bg-primary/80 leading-normal text-sm ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
>
Upload
</button>
<section class="col-span-12 progress-area"></section>
<section class="col-span-12 uploaded-area"></section>
<!-- end dropzone -->
<div class="col-span-12 flex flex-col justify-center items-center">
<p
class="mx-4 my-3 sm:my-0 font-sans font-semibold leading-normal uppercase text-sm"
class="mx-4 my-3 font-sans font-semibold leading-normal uppercase text-sm"
>
or
</p>
@ -167,7 +174,7 @@ plugins = config["CONFIG"].get_plugins() %}
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="types"
class="hidden z-100 absolute h-full flex-col w-full translate-y-12"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
>
<button
{{current_endpoint}}-setting-select-dropdown-btn="types"
@ -208,35 +215,55 @@ plugins = config["CONFIG"].get_plugins() %}
<div {{current_endpoint}}-list class="grid grid-cols-12 gap-3">
{% for plugin in plugins %} {% if plugin['page'] %}
<a
<div
{{current_endpoint}}-external="{% if plugin['external'] %} external {%else%} internal {%endif%}"
href="/plugins?{{plugin['id']}}"
class="py-3 cursor-pointer min-h-12 relative col-span-12 sm:col-span-6 md:col-span-4 lg:col-span-3 3xl:col-span-2 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"
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
class="ml-3 mb-0 transition duration-300 ease-in-out dark:opacity-90 text-center text-sm md:text-base text-slate-700 dark:text-gray-300"
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-300"
>
{{plugin['name']}}
</p>
<svg
class="h-6 w-6 fill-sky-500 dark dark:brightness-90"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
d="M288 32c-17.7 0-32 14.3-32 32s14.3 32 32 32h50.7L169.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L384 141.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V64c0-17.7-14.3-32-32-32H288zM80 64C35.8 64 0 99.8 0 144V400c0 44.2 35.8 80 80 80H336c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v80c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h80c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"
></path>
</svg>
</a>
<div class="flex items-center">
<a class="hover:-translate-y-px" href="/plugins?{{plugin['id']}}">
<svg
class="h-6 w-6 fill-sky-500 dark dark:brightness-90"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
d="M288 32c-17.7 0-32 14.3-32 32s14.3 32 32 32h50.7L169.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L384 141.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V64c0-17.7-14.3-32-32-32H288zM80 64C35.8 64 0 99.8 0 144V400c0 44.2 35.8 80 80 80H336c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v80c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h80c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"
></path>
</svg>
</a>
<button
{{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"
>
<svg
class="h-5 w-5 fill-red-500 dark:brightness-90"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"
/>
</svg>
</button>
</div>
</div>
{% else %}
<div
{{current_endpoint}}-external="{% if plugin['external'] %} external {%else%} internal {%endif%}"
class="py-3 cursor-pointer min-h-12 relative col-span-12 sm:col-span-6 md:col-span-4 lg:col-span-3 3xl:col-span-2 p-1 flex justify-start items-center transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800"
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
class="ml-3 mb-0 transition duration-300 ease-in-out dark:opacity-90 text-center text-sm md:text-base text-slate-700 dark:text-gray-300"
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-300"
>
{{plugin['name']}}
</p>

View File

@ -37,10 +37,10 @@
<!-- detail list -->
<div
class="w-full grid grid-cols-2 justify-items-center sm:justify-items-start gap-2 mt-4 mb-6 ml-3 sm:ml-1"
class="w-full grid grid-cols-12 justify-items-center sm:justify-items-start gap-2 mt-4 mb-6 ml-3 sm:ml-1"
>
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -75,7 +75,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -109,7 +109,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -143,7 +143,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -178,7 +178,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -212,7 +212,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -246,7 +246,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="transition duration-300 ease-in-out dark:opacity-90 font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 "
>
@ -280,7 +280,7 @@
</div>
<!-- end detail -->
<!-- detail -->
<div class="flex items-center col-span-2 sm:col-span-1">
<div class="flex items-center col-span-12 sm:col-span-6 2xl:col-span-4">
<p
class="font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500"
>

View File

@ -4,155 +4,404 @@
config["CONFIG"].get_config() %}
{% set plugins = config["CONFIG"].get_plugins() %}
<!-- plugin item -->
{% for plugin in plugins %}
<div {{current_endpoint}}-item="{{plugin['id']}}" id="{{plugin['id']}}" class="hidden w-full">
{% for plugin in plugins %}
<div
{{current_endpoint}}-item="{{plugin['id']}}"
id="{{plugin['id']}}"
class="hidden w-full"
>
<!-- title and desc -->
<div class="col-span-12">
<h5 class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0">{{plugin['name']}} <span>{{plugin['version']}}</span></h5>
<div class="transition duration-300 ease-in-out dark:opacity-90 ml-2 text-sm mb-2 dark:text-gray-400">
{{plugin['description']}}
</div>
<h5
class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0"
>
{{plugin['name']}} <span>{{plugin['version']}}</span>
</h5>
<div
class="transition duration-300 ease-in-out dark:opacity-90 ml-2 text-sm mb-2 dark:text-gray-400"
>
{{plugin['description']}}
</div>
</div>
<!-- end title and desc -->
<!-- plugin settings -->
<div
class="w-full grid grid-cols-12"
>
{% for setting, value in plugin["settings"].items() %}{% if current_endpoint == "global-config" and
value['context'] == "global" or current_endpoint == "services" and value['context'] == "multisite" %}
<div
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">
<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
{{current_endpoint}}-info-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"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z"
/>
</svg>
<!-- popover -->
<div class="hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
{{current_endpoint}}-info-popover="{{ value["label"] }}"
>
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white m-0" >{{value['help']}}
</p>
<!-- plugin settings not multiple -->
<div {{current_endpoint}}-settings class="w-full grid grid-cols-12">
{% for setting, value in plugin["settings"].items() %}{% if 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
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">
<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
{{current_endpoint}}-info-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"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z"
/>
</svg>
<!-- popover -->
<div class="hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
{{current_endpoint}}-info-popover="{{ value["label"] }}"
>
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white m-0" >{{value['help']}}
</p>
</div>
<!-- end popover -->
</div>
<!-- end popover -->
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
<input
default-value="{{global_config[setting]['value']}}" 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="duration-300 ease-in-out dark:opacity-90 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-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}}" />
{% endif %}
<!-- end input -->
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select default-method="{{global_config[setting]['method']}}" default-value="{{value['default']}}"
id="{{setting}}" name="{{setting}}" {{current_endpoint}}-setting-select-default="{{value['id']}}" 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>
{% endfor %}
</select>
<!-- end default hidden-->
<!--custom-->
<div class="relative">
<button
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} {{current_endpoint}}-setting-select="{{value['id']}}"
default-value="{{global_config[setting]['value']}}"
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 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
{{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{global_config[setting]['value']}}"
>{{global_config[setting]['value']}}</span
>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span
{{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{value['default']}}"
>{{value['default']}}</span
>
{% endif %} {% endfor %}
<!-- chevron -->
<svg
{{current_endpoint}}-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"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
<!-- end chevron -->
</button>
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="{{value['id']}}"
class="hidden z-100 absolute h-full flex-col w-full mt-2"
>
{% for item in value['select'] %} {% if global_config[setting]['value'] and
global_config[setting]['value'] == item or not global_config[setting]['value']
and value['default'] == item %}
<button
type="button"
value="{{item}}"
{{current_endpoint}}-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"
>
{{item}}
</button>
{% else %}
<button
type="button"
value="{{item}}"
{{current_endpoint}}-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:bg-gray-100 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>
{% endif %} {% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end custom-->
{% endif %}
<!-- checkbox -->
{% if value["type"] == "check" %}
<div checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<input id="{{setting}}" name="{{setting}}"
default-method="{{global_config[setting]['method']}}"
default-value="{{global_config[setting]['value']}}" {% if
global_config[setting]['method'] != 'ui' or 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="relative {% if
global_config[setting]['method'] != 'ui' and global_config[setting]['method']
!= 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %}
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="{{value['regex']|safe}}"
value="{% if global_config[setting]['value'] %}
{{global_config[setting]['value']}} {% else %} {{value['default']}} {% endif
%}" />
<svg
checkbox-handler="{{value['id']}}"
class="{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %} 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"
>
<path
d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
></path>
</svg>
</div>
{% endif %}
<!-- end checkbox -->
<!-- invalid feedback -->
<div class="hidden text-sm dark:text-red-500">
{{value['label']}} is invalid and must match this pattern:
{{value['regex']|safe}}
</div>
<!--end invalid feedback-->
</div>
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
<input
default-value="{{global_config[setting]['value']}}" 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="duration-300 ease-in-out dark:opacity-90 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-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}}" />
{% endif %}
<!-- end input -->
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select default-method="{{global_config[setting]['method']}}" default-value="{{value['default']}}"
id="{{setting}}" name="{{setting}}" {{current_endpoint}}-setting-select-default="{{value['id']}}" 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>
{% endfor %}
</select>
<!-- end default hidden-->
<!--custom-->
<div class="relative">
<button default-value="{{global_config[setting]['value']}}"
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} {{current_endpoint}}-setting-select="{{value['id']}}"
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 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 {{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{global_config[setting]['value']}}">{{global_config[setting]['value']}}</span>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span {{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{value['default']}}">{{value['default']}}</span>
{% endif %}
{% endfor %}
<!-- chevron -->
<svg
{{current_endpoint}}-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"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
<!-- end chevron -->
</button>
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="{{value['id']}}"
class="hidden z-100 absolute h-full flex-col w-full mt-2 "
>
{% for item in value['select'] %}
{% if global_config[setting]['value'] and global_config[setting]['value'] == item or not global_config[setting]['value'] and value['default'] == item %}
<button type="button" value="{{item}}" {{current_endpoint}}-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"
>{{item}}</button>
{% else %}
<button type="button" value="{{item}}" {{current_endpoint}}-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:bg-gray-100 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>
{% endif %}
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end custom-->
{% endif %}
<!-- checkbox -->
{% if value["type"] == "check" %}
<div checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<input id="{{setting}}" name="{{setting}}"
default-method="{{global_config[setting]['method']}}" default-value="{{global_config[setting]['value']}}"
{% if global_config[setting]['method'] != 'ui' or 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="relative {% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %} 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="{{value['regex']|safe}}"
value="{% if global_config[setting]['value'] %} {{global_config[setting]['value']}} {% else %} {{value['default']}} {% endif %}"
/>
<svg checkbox-handler="{{value['id']}}" class="{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %} 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">
<path d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"></path>
</svg>
</div>
{% endif %}
<!-- end checkbox -->
<!-- invalid feedback -->
<div class="hidden text-sm dark:text-red-500">
{{value['label']}} is invalid and must match this pattern: {{value['regex']|safe}}
{% endif %} {% endfor %}
<!-- end plugin settings -->
</div>
<!--end invalid feedback -->
</div>
{% endif %}
{% endfor %}
<!-- end plugin settings -->
</div>
</div>
{% endfor %}
<!-- end plugin item -->
<!-- end plugin settings not multiple -->
<!-- plugin multiple -->
{% if not plugin['multiple'] %}
<div 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"
>
{{plugin['name']}}
</h5>
<button {{current_endpoint}}-multiple-add="{{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-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>
</div>
<div {{current_endpoint}}-settings-multiple="{{plugin['name']}}_SCHEMA" class="hidden w-full mb-8 grid-cols-12 border dark:border-gray-700">
{% for setting, value in plugin["settings"].items() %}
{% if current_endpoint
== "global-config" and value['context'] == "global" and value['multiple'] or current_endpoint ==
"services" and value['context'] == "multisite" and value['multiple'] %}
<div 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"] }}">
<!-- title and info -->
<div class="flex items-center my-1 relative">
<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
{{current_endpoint}}-info-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"
>
<path
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-144c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z"
/>
</svg>
<!-- popover -->
<div class="hidden transition z-50 rounded-md p-3 left-0 -translate-y-7 bottom-0 absolute bg-blue-500"
{{current_endpoint}}-info-popover="{{ value["label"] }}"
>
<p class="transition duration-300 ease-in-out dark:opacity-90 font-bold text-sm text-white m-0" >{{value['help']}}
</p>
</div>
<!-- end popover -->
</div>
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
<input
default-value="{{global_config[setting]['value']}}" 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="duration-300 ease-in-out dark:opacity-90 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-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}}" />
{% endif %}
<!-- end input -->
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select default-method="{{global_config[setting]['method']}}" default-value="{{value['default']}}"
id="{{setting}}" name="{{setting}}" {{current_endpoint}}-setting-select-default="{{value['id']}}" 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>
{% endfor %}
</select>
<!-- end default hidden-->
<!--custom-->
<div class="relative">
<button
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %} {{current_endpoint}}-setting-select="{{value['id']}}"
default-value="{{global_config[setting]['value']}}"
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 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
{{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{global_config[setting]['value']}}"
>{{global_config[setting]['value']}}</span
>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span
{{current_endpoint}}-setting-select-text="{{value['id']}}"
value="{{value['default']}}"
>{{value['default']}}</span
>
{% endif %} {% endfor %}
<!-- chevron -->
<svg
{{current_endpoint}}-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"
>
<path
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
/>
</svg>
<!-- end chevron -->
</button>
<!-- dropdown-->
<div
{{current_endpoint}}-setting-select-dropdown="{{value['id']}}"
class="hidden z-100 absolute h-full flex-col w-full mt-2"
>
{% for item in value['select'] %} {% if global_config[setting]['value'] and
global_config[setting]['value'] == item or not global_config[setting]['value']
and value['default'] == item %}
<button
type="button"
value="{{item}}"
{{current_endpoint}}-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"
>
{{item}}
</button>
{% else %}
<button
type="button"
value="{{item}}"
{{current_endpoint}}-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:bg-gray-100 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>
{% endif %} {% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end custom-->
{% endif %}
<!-- checkbox -->
{% if value["type"] == "check" %}
<div checkbox-handler="{{value['id']}}" class="relative mb-7 md:mb-0">
<input id="{{setting}}" name="{{setting}}"
default-method="{{global_config[setting]['method']}}"
default-value="{{global_config[setting]['value']}}" {% if
global_config[setting]['method'] != 'ui' or 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="relative {% if
global_config[setting]['method'] != 'ui' and global_config[setting]['method']
!= 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %}
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="{{value['regex']|safe}}"
value="{% if global_config[setting]['value'] %}
{{global_config[setting]['value']}} {% else %} {{value['default']}} {% endif
%}" />
<svg
checkbox-handler="{{value['id']}}"
class="{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} pointer-events-none {% else %} cursor-pointer {% endif %} 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"
>
<path
d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
></path>
</svg>
</div>
{% endif %}
<!-- end checkbox -->
<!-- invalid feedback -->
<div class="hidden text-sm dark:text-red-500">
{{value['label']}} is invalid and must match this pattern:
{{value['regex']|safe}}
</div>
<!--end invalid feedback-->
</div>
{% endif %} {% endfor %}
<div 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">
Remove
</button>
</div>
<!-- end plugin settings -->
</div>
{%endif %}
<!-- end plugin multiple -->
</div>
{% endfor %}
<!-- end plugin item -->