Merge pull request #373 from TheophileDiot/dev
Advancements on the UI + some fixes
This commit is contained in:
commit
6bbbe70eea
|
@ -23,21 +23,7 @@ services:
|
|||
- bunkerweb.LIMIT_REQ_URL_1=/core/install.php
|
||||
- bunkerweb.LIMIT_REQ_RATE_1=5r/s
|
||||
|
||||
mydb:
|
||||
image: mariadb
|
||||
networks:
|
||||
- bw-services
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=drupaldb
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role==worker"
|
||||
# For the database, you can refer to the swarm example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -24,7 +24,7 @@ services:
|
|||
- bunkerweb.LIMIT_REQ_URL_2=/installation/index.php
|
||||
- bunkerweb.LIMIT_REQ_RATE_2=8r/s
|
||||
|
||||
# For the database, you can refer to the example of the autoconf including a database
|
||||
# For the database, you can refer to the autoconf integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -26,21 +26,7 @@ services:
|
|||
- bunkerweb.LIMIT_REQ_URL_2=/installation/index.php
|
||||
- bunkerweb.LIMIT_REQ_RATE_2=8r/s
|
||||
|
||||
mydb:
|
||||
image: mariadb
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- bw-services
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=joomla_db
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match JOOMLA_DB_PASSWORD)
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role==worker"
|
||||
# For the database, you can refer to the swarm integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -43,7 +43,7 @@ services:
|
|||
volumes:
|
||||
- ./elasticsearch-data:/bitnami/elasticsearch/data
|
||||
|
||||
# For the database, you can refer to the example of the autoconf including a database
|
||||
# For the database, you can refer to the autoconf integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -40,21 +40,7 @@ services:
|
|||
constraints:
|
||||
- "node.role==worker"
|
||||
|
||||
mydb:
|
||||
image: mariadb:10.2
|
||||
networks:
|
||||
- bw-services
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=magentodb
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match MAGENTO_DATABASE_PASSWORD)
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role==worker"
|
||||
# For the database, you can refer to the swarm integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -63,7 +63,7 @@ services:
|
|||
- bunkerweb.LIMIT_REQ_URL_3=^/static/
|
||||
- bunkerweb.LIMIT_REQ_RATE_3=10r/s
|
||||
|
||||
# For the postgres database, you can refer to the example of the autoconf including a postgres database
|
||||
# For the postgres database, you can refer to the autoconf integration example including a postgres database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -27,7 +27,7 @@ services:
|
|||
- bunkerweb.REVERSE_PROXY_URL=/
|
||||
- bunkerweb.REVERSE_PROXY_HOST=https://mymoodle:8443
|
||||
|
||||
# For the database, you can refer to the example of the autoconf including a database
|
||||
# For the database, you can refer to the autoconf integration example including a database
|
||||
# In this example, you will need to add the following lines to the mydb service:
|
||||
# - MARIADB_CHARACTER_SET=utf8mb4
|
||||
# - MARIADB_COLLATE=utf8mb4_unicode_ci
|
||||
|
|
|
@ -29,23 +29,10 @@ services:
|
|||
- bunkerweb.REVERSE_PROXY_URL=/
|
||||
- bunkerweb.REVERSE_PROXY_HOST=https://mymoodle:8443
|
||||
|
||||
mydb:
|
||||
image: mariadb:10.5
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- bw-services
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=moodle
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match MOODLE_DATABASE_PASSWORD)
|
||||
- MARIADB_CHARACTER_SET=utf8mb4
|
||||
- MARIADB_COLLATE=utf8mb4_unicode_ci
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role==worker"
|
||||
# For the database, you can refer to the swarm integration example including a database
|
||||
# In this example, you will need to add the following lines to the mydb service:
|
||||
# - MARIADB_CHARACTER_SET=utf8mb4
|
||||
# - MARIADB_COLLATE=utf8mb4_unicode_ci
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -56,7 +56,7 @@ services:
|
|||
bunkerweb.CUSTOM_CONF_MODSEC_nextcloud=
|
||||
SecRule REQUEST_FILENAME "@rx ^/remote.php/dav/files/" "id:1000,ctl:ruleRemoveByTag=attack-protocol,ctl:ruleRemoveByTag=attack-generic,nolog"
|
||||
|
||||
# For the database, you can refer to the example of the autoconf including a database
|
||||
# For the database, you can refer to the autoconf integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -37,7 +37,7 @@ services:
|
|||
- bunkerweb.LIMIT_REQ_URL_3=/core/preview
|
||||
- bunkerweb.LIMIT_REQ_RATE_3=5r/s
|
||||
|
||||
# For the database, you can refer to the example of the autoconf in swarm mode including a database
|
||||
# For the database, you can refer to the swarm integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -2,10 +2,24 @@ version: "3"
|
|||
|
||||
services:
|
||||
# you will need to add a user by hand
|
||||
# example : docker-compose exec mypassbolt su -m -c "bin/cake passbolt register_user -u your@email.com -f yourname -l surname -r admin" -s /bin/sh www-data
|
||||
# example : docker-compose exec mypassbolt su -m -c "/usr/share/php/passbolt/bin/cake passbolt register_user -u <your@email.com> -f <yourname> -l <surname> -r admin" -s /bin/sh www-data
|
||||
# more info at https://github.com/passbolt/passbolt_docker
|
||||
mypassbolt:
|
||||
image: passbolt/passbolt
|
||||
image: passbolt/passbolt:3.8.1-1-ce
|
||||
networks:
|
||||
bw-services:
|
||||
aliases:
|
||||
- mypassbolt
|
||||
environment:
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
- PASSBOLT_SSL_FORCE=false
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_DATABASE=${PASSBOLT_DATABASE:-passboltdb}
|
||||
- DATASOURCES_DEFAULT_USERNAME=${PASSBOLT_USER:-user}
|
||||
- DATASOURCES_DEFAULT_PASSWORD=${PASSBOLT_PASSWORD:-secret} # set a stronger password in a .env file (must match MYSQL_PASSWORD)
|
||||
volumes:
|
||||
- gpg_volume:/etc/passbolt/gpg
|
||||
- jwt_volume:/etc/passbolt/jwt
|
||||
command:
|
||||
[
|
||||
"/usr/bin/wait-for.sh",
|
||||
|
@ -15,36 +29,19 @@ services:
|
|||
"--",
|
||||
"/docker-entrypoint.sh",
|
||||
]
|
||||
networks:
|
||||
bw-services:
|
||||
aliases:
|
||||
- mypassbolt
|
||||
environment:
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_PASSWORD=db-user-pwd # replace with a stronger password (must match MYSQL_PASSWORD)
|
||||
- DATASOURCES_DEFAULT_USERNAME=user
|
||||
- DATASOURCES_DEFAULT_DATABASE=passbolt
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
labels:
|
||||
- bunkerweb.SERVER_NAME=www.example.com
|
||||
- bunkerweb.ALLOWED_METHODS=GET|POST|HEAD|PUT|DELETE
|
||||
- bunkerweb.COOKIE_FLAGS=* SameSite=Lax
|
||||
- bunkerweb.USE_REVERSE_PROXY=yes
|
||||
- bunkerweb.REVERSE_PROXY_URL=/
|
||||
- bunkerweb.REVERSE_PROXY_HOST=https://mypassbolt
|
||||
|
||||
mydb:
|
||||
image: mariadb
|
||||
volumes:
|
||||
- ./db-data:/var/lib/mysql
|
||||
networks:
|
||||
bw-services:
|
||||
aliases:
|
||||
- mydb
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=passbolt
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match DATASOURCES_DEFAULT_PASSWORD)
|
||||
# For the database, you can refer to the autoconf integration example including a database
|
||||
|
||||
volumes:
|
||||
gpg_volume:
|
||||
jwt_volume:
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
version: "3"
|
||||
|
||||
x-bunkerweb-env:
|
||||
&bunkerweb-env
|
||||
DATABASE_URI: "mariadb+pymysql://${PASSBOLT_USER:-user}:${PASSBOLT_PASSWORD:-secret}@mydb:3306/${BUNKERWEB_DATABASE:-bunkerweb}"
|
||||
|
||||
services:
|
||||
mybunker:
|
||||
image: bunkerity/bunkerweb:1.4.3
|
||||
image: bunkerity/bunkerweb:1.5.0
|
||||
ports:
|
||||
- 80:8080
|
||||
- 443:8443
|
||||
|
@ -13,24 +17,68 @@ services:
|
|||
# another example for existing folder : chown -R root:101 folder && chmod -R 770 folder
|
||||
# more info at https://docs.bunkerweb.io
|
||||
volumes:
|
||||
- bw_data:/data
|
||||
- bw-data:/data
|
||||
environment:
|
||||
- SERVER_NAME=www.example.com # replace with your domain
|
||||
- AUTO_LETS_ENCRYPT=yes
|
||||
- DISABLE_DEFAULT_SERVER=yes
|
||||
- ALLOWED_METHODS=GET|POST|HEAD|PUT|DELETE
|
||||
- SERVE_FILES=no
|
||||
- USE_CLIENT_CACHE=yes
|
||||
- USE_GZIP=yes
|
||||
- USE_REVERSE_PROXY=yes
|
||||
- REVERSE_PROXY_URL=/
|
||||
- REVERSE_PROXY_HOST=https://mypassbolt
|
||||
<<: *bunkerweb-env
|
||||
SERVER_NAME: "www.example.com" # replace with your domain
|
||||
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
|
||||
AUTO_LETS_ENCRYPT: "yes"
|
||||
COOKIE_FLAGS: "* SameSite=Lax"
|
||||
DISABLE_DEFAULT_SERVER: "yes"
|
||||
ALLOWED_METHODS: "GET|POST|HEAD|PUT|DELETE"
|
||||
SERVE_FILES: "no"
|
||||
USE_CLIENT_CACHE: "yes"
|
||||
USE_GZIP: "yes"
|
||||
USE_REVERSE_PROXY: "yes"
|
||||
REVERSE_PROXY_URL: "/"
|
||||
REVERSE_PROXY_HOST: "https://mypassbolt"
|
||||
labels:
|
||||
- "bunkerweb.INSTANCE" # required for the scheduler to recognize the container
|
||||
networks:
|
||||
- bw-universe
|
||||
- bw-services
|
||||
|
||||
bw-scheduler:
|
||||
image: bunkerity/bunkerweb-scheduler:1.5.0
|
||||
depends_on:
|
||||
- mybunker
|
||||
environment:
|
||||
<<: *bunkerweb-env
|
||||
DOCKER_HOST: "tcp://docker-proxy:2375"
|
||||
volumes:
|
||||
- bw-data:/data
|
||||
networks:
|
||||
- bw-universe
|
||||
- net-docker
|
||||
|
||||
docker-proxy:
|
||||
image: tecnativa/docker-socket-proxy
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- CONTAINERS=1
|
||||
networks:
|
||||
- net-docker
|
||||
|
||||
# you will need to add a user by hand
|
||||
# example : docker-compose exec mypassbolt su -m -c "bin/cake passbolt register_user -u your@email.com -f yourname -l surname -r admin" -s /bin/sh www-data
|
||||
# example : docker-compose exec mypassbolt su -m -c "/usr/share/php/passbolt/bin/cake passbolt register_user -u <your@email.com> -f <yourname> -l <surname> -r admin" -s /bin/sh www-data
|
||||
# more info at https://github.com/passbolt/passbolt_docker
|
||||
mypassbolt:
|
||||
image: passbolt/passbolt
|
||||
image: passbolt/passbolt:3.8.1-1-ce
|
||||
#Alternatively you can use rootless:
|
||||
# image: passbolt/passbolt:3.8.1-1-ce-non-root
|
||||
depends_on:
|
||||
- mydb
|
||||
environment:
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
- PASSBOLT_SSL_FORCE=false
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_DATABASE=${PASSBOLT_DATABASE:-passboltdb}
|
||||
- DATASOURCES_DEFAULT_USERNAME=${PASSBOLT_USER:-user}
|
||||
- DATASOURCES_DEFAULT_PASSWORD=${PASSBOLT_PASSWORD:-secret} # set a stronger password in a .env file (must match MYSQL_PASSWORD)
|
||||
volumes:
|
||||
- gpg_volume:/etc/passbolt/gpg
|
||||
- jwt_volume:/etc/passbolt/jwt
|
||||
command:
|
||||
[
|
||||
"/usr/bin/wait-for.sh",
|
||||
|
@ -38,24 +86,34 @@ services:
|
|||
"0",
|
||||
"mydb:3306",
|
||||
"--",
|
||||
"/docker-entrypoint.sh",
|
||||
"/docker-entrypoint.sh"
|
||||
]
|
||||
environment:
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_PASSWORD=db-user-pwd # replace with a stronger password (must match MYSQL_PASSWORD)
|
||||
- DATASOURCES_DEFAULT_USERNAME=user
|
||||
- DATASOURCES_DEFAULT_DATABASE=passbolt
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
networks:
|
||||
- bw-services
|
||||
|
||||
mydb:
|
||||
image: mariadb
|
||||
volumes:
|
||||
- ./db-data:/var/lib/mysql
|
||||
- db-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=passbolt
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match DATASOURCES_DEFAULT_PASSWORD)
|
||||
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
|
||||
entrypoint: sh -c "echo 'DROP USER IF EXISTS \"${PASSBOLT_USER:-user}\"; CREATE USER \"${PASSBOLT_USER:-user}\"@\"%\"; CREATE DATABASE IF NOT EXISTS ${PASSBOLT_DATABASE:-passboltdb}; CREATE DATABASE IF NOT EXISTS ${BUNKERWEB_DATABASE:-bunkerweb}; GRANT ALL PRIVILEGES ON ${PASSBOLT_DATABASE:-passboltdb}.* TO \"${PASSBOLT_USER:-user}\"@\"%\" IDENTIFIED BY \"${PASSBOLT_PASSWORD:-secret}\"; GRANT ALL PRIVILEGES ON ${BUNKERWEB_DATABASE:-bunkerweb}.* TO \"${PASSBOLT_USER:-user}\"@\"%\" IDENTIFIED BY \"${PASSBOLT_PASSWORD:-secret}\"; FLUSH PRIVILEGES;' > /docker-entrypoint-initdb.d/init.sql; /usr/local/bin/docker-entrypoint.sh --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
|
||||
networks:
|
||||
- bw-universe
|
||||
- bw-services
|
||||
|
||||
volumes:
|
||||
bw_data:
|
||||
gpg_volume:
|
||||
jwt_volume:
|
||||
db-data:
|
||||
bw-data:
|
||||
|
||||
|
||||
networks:
|
||||
bw-universe:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 10.20.30.0/24
|
||||
bw-services:
|
||||
net-docker:
|
||||
|
|
|
@ -2,10 +2,22 @@ version: "3"
|
|||
|
||||
services:
|
||||
# you will need to add a user by hand
|
||||
# example : docker-compose exec mypassbolt su -m -c "bin/cake passbolt register_user -u your@email.com -f yourname -l surname -r admin" -s /bin/sh www-data
|
||||
# example : docker-compose exec mypassbolt su -m -c "/usr/share/php/passbolt/bin/cake passbolt register_user -u <your@email.com> -f <yourname> -l <surname> -r admin" -s /bin/sh www-data
|
||||
# more info at https://github.com/passbolt/passbolt_docker
|
||||
mypassbolt:
|
||||
image: passbolt/passbolt
|
||||
image: passbolt/passbolt:3.8.1-1-ce
|
||||
networks:
|
||||
- bw-services
|
||||
environment:
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
- PASSBOLT_SSL_FORCE=false
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_DATABASE=${PASSBOLT_DATABASE:-passboltdb}
|
||||
- DATASOURCES_DEFAULT_USERNAME=${PASSBOLT_USER:-user}
|
||||
- DATASOURCES_DEFAULT_PASSWORD=${PASSBOLT_PASSWORD:-secret} # set a stronger password in a .env file (must match MYSQL_PASSWORD)
|
||||
volumes:
|
||||
- gpg_volume:/etc/passbolt/gpg
|
||||
- jwt_volume:/etc/passbolt/jwt
|
||||
command:
|
||||
[
|
||||
"/usr/bin/wait-for.sh",
|
||||
|
@ -15,14 +27,6 @@ services:
|
|||
"--",
|
||||
"/docker-entrypoint.sh",
|
||||
]
|
||||
networks:
|
||||
- bw-services
|
||||
environment:
|
||||
- DATASOURCES_DEFAULT_HOST=mydb
|
||||
- DATASOURCES_DEFAULT_PASSWORD=db-user-pwd # replace with a stronger password (must match MYSQL_PASSWORD)
|
||||
- DATASOURCES_DEFAULT_USERNAME=user
|
||||
- DATASOURCES_DEFAULT_DATABASE=passbolt
|
||||
- APP_FULL_BASE_URL=https://www.example.com # replace with your URL
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
|
@ -30,25 +34,12 @@ services:
|
|||
labels:
|
||||
- bunkerweb.SERVER_NAME=www.example.com
|
||||
- bunkerweb.ALLOWED_METHODS=GET|POST|HEAD|PUT|DELETE
|
||||
- bunkerweb.COOKIE_FLAGS=* SameSite=Lax
|
||||
- bunkerweb.USE_REVERSE_PROXY=yes
|
||||
- bunkerweb.REVERSE_PROXY_URL=/
|
||||
- bunkerweb.REVERSE_PROXY_HOST=https://mypassbolt
|
||||
|
||||
mydb:
|
||||
image: mariadb
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- bw-services
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=db-root-pwd # replace with a stronger password
|
||||
- MYSQL_DATABASE=passbolt
|
||||
- MYSQL_USER=user
|
||||
- MYSQL_PASSWORD=db-user-pwd # replace with a stronger password (must match DATASOURCES_DEFAULT_PASSWORD)
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role==worker"
|
||||
# For the database, you can refer to the swarm integration example including a database
|
||||
|
||||
networks:
|
||||
bw-services:
|
||||
|
|
|
@ -1224,14 +1224,18 @@ class Database:
|
|||
"every": job.every,
|
||||
"reload": job.reload,
|
||||
"success": job.success,
|
||||
"last_run": job.last_run.strftime("%Y/%m/%d, %I:%M:%S %p"),
|
||||
"last_run": job.last_run.strftime("%Y/%m/%d, %I:%M:%S %p")
|
||||
if job.last_run is not None
|
||||
else "Never",
|
||||
"cache": [
|
||||
{
|
||||
"service_id": cache.service_id,
|
||||
"file_name": cache.file_name,
|
||||
"last_update": cache.last_update.strftime(
|
||||
"%Y/%m/%d, %I:%M:%S %p"
|
||||
),
|
||||
)
|
||||
if cache.last_update is not None
|
||||
else "Never",
|
||||
}
|
||||
for cache in session.query(Jobs_cache)
|
||||
.with_entities(
|
||||
|
|
|
@ -105,6 +105,7 @@ class JobScheduler(ApiCaller):
|
|||
self.__logger.info(
|
||||
f"Executing job {name} from plugin {plugin} ...",
|
||||
)
|
||||
success = True
|
||||
try:
|
||||
proc = run(
|
||||
f"{path}/jobs/{file}",
|
||||
|
@ -115,6 +116,7 @@ class JobScheduler(ApiCaller):
|
|||
group=101,
|
||||
)
|
||||
except BaseException:
|
||||
success = False
|
||||
with self.__thread_lock:
|
||||
self.__logger.error(
|
||||
f"Exception while executing job {name} from plugin {plugin} :\n{format_exc()}",
|
||||
|
@ -122,23 +124,23 @@ class JobScheduler(ApiCaller):
|
|||
self.__job_success = False
|
||||
|
||||
if self.__job_success and proc.returncode >= 2:
|
||||
success = False
|
||||
with self.__thread_lock:
|
||||
self.__logger.error(
|
||||
f"Error while executing job {name} from plugin {plugin}",
|
||||
)
|
||||
self.__job_success = False
|
||||
|
||||
with self.__thread_lock:
|
||||
err = self.__db.update_job(plugin, name, self.__job_success)
|
||||
err = self.__db.update_job(plugin, name, success)
|
||||
|
||||
if not err:
|
||||
self.__logger.info(
|
||||
f"Successfully updated database for the job {name} from plugin {plugin}",
|
||||
)
|
||||
else:
|
||||
self.__logger.warning(
|
||||
f"Failed to update database for the job {name} from plugin {plugin}: {err}",
|
||||
)
|
||||
if not err:
|
||||
self.__logger.info(
|
||||
f"Successfully updated database for the job {name} from plugin {plugin}",
|
||||
)
|
||||
else:
|
||||
self.__logger.warning(
|
||||
f"Failed to update database for the job {name} from plugin {plugin}: {err}",
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
for plugin, jobs in self.__jobs.items():
|
||||
|
|
130
src/ui/main.py
130
src/ui/main.py
|
@ -22,6 +22,7 @@ from flask import (
|
|||
from flask_login import LoginManager, login_required, login_user, logout_user
|
||||
from flask_wtf.csrf import CSRFProtect, CSRFError, generate_csrf
|
||||
from json import JSONDecodeError, dumps, load as json_load
|
||||
from jinja2 import Template
|
||||
from kubernetes import client as kube_client
|
||||
from kubernetes.client.exceptions import ApiException as kube_ApiException
|
||||
from os import chmod, getenv, getpid, listdir, mkdir, walk
|
||||
|
@ -152,7 +153,7 @@ try:
|
|||
WTF_CSRF_SSL_STRICT=False,
|
||||
USER=user,
|
||||
SEND_FILE_MAX_AGE_DEFAULT=86400,
|
||||
PLUGIN_ARGS=None,
|
||||
PLUGIN_ARGS={},
|
||||
RELOADING=False,
|
||||
TO_FLASH=[],
|
||||
DARK_MODE=False,
|
||||
|
@ -274,44 +275,44 @@ def home():
|
|||
if r and r.status_code == 200:
|
||||
remote_version = r.text.strip()
|
||||
|
||||
headers = default_headers()
|
||||
headers.update({"User-Agent": "bunkerweb-ui"})
|
||||
# headers = default_headers()
|
||||
# headers.update({"User-Agent": "bunkerweb-ui"})
|
||||
|
||||
try:
|
||||
r = get(
|
||||
"https://www.bunkerity.com/wp-json/wp/v2/posts",
|
||||
headers=headers,
|
||||
)
|
||||
except BaseException:
|
||||
r = None
|
||||
# try:
|
||||
# r = get(
|
||||
# "https://www.bunkerity.com/wp-json/wp/v2/posts",
|
||||
# headers=headers,
|
||||
# )
|
||||
# except BaseException:
|
||||
# r = None
|
||||
|
||||
formatted_posts = None
|
||||
if r and r.status_code == 200:
|
||||
posts = r.json()
|
||||
formatted_posts = []
|
||||
# formatted_posts = None
|
||||
# if r and r.status_code == 200:
|
||||
# posts = r.json()
|
||||
# formatted_posts = []
|
||||
|
||||
for post in posts[:5]:
|
||||
formatted_posts.append(
|
||||
{
|
||||
"link": post["link"],
|
||||
"title": post["title"]["rendered"],
|
||||
"description": BeautifulSoup(
|
||||
post["content"]["rendered"][
|
||||
post["content"]["rendered"].index("<em>")
|
||||
+ 4 : post["content"]["rendered"].index("</em>")
|
||||
],
|
||||
features="html.parser",
|
||||
).get_text()[:256]
|
||||
+ ("..." if len(post["content"]["rendered"]) > 256 else ""),
|
||||
"date": dateutil_parse(post["date"]).strftime("%B %d, %Y"),
|
||||
"image_url": post["yoast_head_json"]["og_image"][0]["url"].replace(
|
||||
"wwwdev", "www"
|
||||
),
|
||||
"reading_time": post["yoast_head_json"]["twitter_misc"][
|
||||
"Est. reading time"
|
||||
],
|
||||
}
|
||||
)
|
||||
# for post in posts[:5]:
|
||||
# formatted_posts.append(
|
||||
# {
|
||||
# "link": post["link"],
|
||||
# "title": post["title"]["rendered"],
|
||||
# "description": BeautifulSoup(
|
||||
# post["content"]["rendered"][
|
||||
# post["content"]["rendered"].index("<em>")
|
||||
# + 4 : post["content"]["rendered"].index("</em>")
|
||||
# ],
|
||||
# features="html.parser",
|
||||
# ).get_text()[:256]
|
||||
# + ("..." if len(post["content"]["rendered"]) > 256 else ""),
|
||||
# "date": dateutil_parse(post["date"]).strftime("%B %d, %Y"),
|
||||
# "image_url": post["yoast_head_json"]["og_image"][0]["url"].replace(
|
||||
# "wwwdev", "www"
|
||||
# ),
|
||||
# "reading_time": post["yoast_head_json"]["twitter_misc"][
|
||||
# "Est. reading time"
|
||||
# ],
|
||||
# }
|
||||
# )
|
||||
|
||||
instances_number = len(app.config["INSTANCES"].get_instances())
|
||||
services_number = len(app.config["CONFIG"].get_services())
|
||||
|
@ -323,7 +324,7 @@ def home():
|
|||
version=bw_version,
|
||||
instances_number=instances_number,
|
||||
services_number=services_number,
|
||||
posts=formatted_posts,
|
||||
# posts=formatted_posts,
|
||||
plugins_errors=db.get_plugins_errors(),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
)
|
||||
|
@ -958,12 +959,14 @@ def plugins():
|
|||
# Fix permissions for plugins folders
|
||||
for root, dirs, files in walk("/etc/bunkerweb/plugins", topdown=False):
|
||||
for name in files + dirs:
|
||||
chown(join(root, name), "nginx", "nginx")
|
||||
chown(join(root, name), 101, 101)
|
||||
chmod(join(root, name), 0o770)
|
||||
|
||||
if operation:
|
||||
flash(operation)
|
||||
|
||||
app.config["CONFIG"].reload_plugins()
|
||||
|
||||
# Reload instances
|
||||
app.config["RELOADING"] = True
|
||||
Thread(
|
||||
|
@ -979,7 +982,6 @@ def plugins():
|
|||
except OSError:
|
||||
pass
|
||||
|
||||
app.config["CONFIG"].reload_plugins()
|
||||
return redirect(
|
||||
url_for("loading", next=url_for("plugins"), message="Reloading plugins")
|
||||
)
|
||||
|
@ -999,8 +1001,10 @@ def plugins():
|
|||
flash(f"Plugin {plugin_id} not found", "error")
|
||||
|
||||
if page_path:
|
||||
return render_template(
|
||||
page_path,
|
||||
with open(page_path, "r") as f:
|
||||
template = Template(f.read())
|
||||
|
||||
return template.render(
|
||||
csrf_token=generate_csrf,
|
||||
url_for=url_for,
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
|
@ -1011,8 +1015,23 @@ def plugins():
|
|||
),
|
||||
)
|
||||
|
||||
app.config["CONFIG"].reload_plugins()
|
||||
plugins = app.config["CONFIG"].get_plugins()
|
||||
plugins_internal = 0
|
||||
plugins_external = 0
|
||||
|
||||
for plugin in plugins:
|
||||
if plugin["external"] is True:
|
||||
plugins_external += 1
|
||||
else:
|
||||
plugins_internal += 1
|
||||
|
||||
return render_template(
|
||||
"plugins.html",
|
||||
plugins=plugins,
|
||||
plugins_internal=plugins_internal,
|
||||
plugins_external=plugins_external,
|
||||
plugins_errors=db.get_plugins_errors(),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
)
|
||||
|
||||
|
@ -1047,7 +1066,7 @@ def custom_plugin(plugin):
|
|||
f"Invalid plugin id, <b>{plugin}</b> (must be between 1 and 64 characters, only letters, numbers, underscores and hyphens)",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
|
||||
if not exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") and not exists(
|
||||
f"/usr/share/bunkerweb/core/{plugin}/ui/actions.py"
|
||||
|
@ -1056,7 +1075,7 @@ def custom_plugin(plugin):
|
|||
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
|
||||
# Add the custom plugin to sys.path
|
||||
sys_path.append(
|
||||
|
@ -1075,7 +1094,7 @@ def custom_plugin(plugin):
|
|||
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
|
||||
"error",
|
||||
)
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
|
||||
error = False
|
||||
res = None
|
||||
|
@ -1090,7 +1109,7 @@ def custom_plugin(plugin):
|
|||
"error",
|
||||
)
|
||||
error = True
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
except:
|
||||
flash(
|
||||
f"An error occurred while executing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
|
||||
|
@ -1109,12 +1128,14 @@ def custom_plugin(plugin):
|
|||
or res is None
|
||||
or isinstance(res, dict) is False
|
||||
):
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(
|
||||
url_for("loading", next=url_for("plugins", plugin_id=plugin))
|
||||
)
|
||||
|
||||
app.config["PLUGIN_ARGS"] = {"plugin": plugin, "args": res}
|
||||
|
||||
flash(f"Your action <b>{plugin}</b> has been executed")
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
|
||||
|
||||
|
||||
@app.route("/cache", methods=["GET"])
|
||||
|
@ -1383,6 +1404,7 @@ def jobs():
|
|||
return render_template(
|
||||
"jobs.html",
|
||||
jobs=db.get_jobs(),
|
||||
jobs_errors=db.get_plugins_errors(),
|
||||
dark_mode=app.config["DARK_MODE"],
|
||||
)
|
||||
|
||||
|
@ -1454,13 +1476,15 @@ def login():
|
|||
@app.route("/darkmode", methods=["POST"])
|
||||
@login_required
|
||||
def darkmode():
|
||||
if "darkmode" in request.form:
|
||||
if request.form["darkmode"] == "true":
|
||||
app.config["DARK_MODE"] = True
|
||||
else:
|
||||
app.config["DARK_MODE"] = False
|
||||
if not request.is_json:
|
||||
return jsonify({"status": "ko", "message": "invalid request"}), 400
|
||||
|
||||
return jsonify({"status": "ok"})
|
||||
if "darkmode" in request.json:
|
||||
app.config["DARK_MODE"] = request.json["darkmode"] == "true"
|
||||
else:
|
||||
return jsonify({"status": "ko", "message": "darkmode is required"}), 422
|
||||
|
||||
return jsonify({"status": "ok"}), 200
|
||||
|
||||
|
||||
@app.route("/check_reloading")
|
||||
|
|
|
@ -38,6 +38,7 @@ class Config:
|
|||
|
||||
def reload_plugins(self) -> None:
|
||||
self.__plugins = []
|
||||
external_plugins = []
|
||||
|
||||
for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + list(
|
||||
iglob("/usr/share/bunkerweb/core/*")
|
||||
|
@ -55,6 +56,13 @@ class Config:
|
|||
"external": foldername.startswith("/etc/bunkerweb/plugins"),
|
||||
}
|
||||
)
|
||||
|
||||
if plugin["external"] is True:
|
||||
external_plugin = deepcopy(plugin)
|
||||
del external_plugin["external"]
|
||||
del external_plugin["page"]
|
||||
external_plugins.append(external_plugin)
|
||||
|
||||
if "ui" in content:
|
||||
if "template.html" in listdir(f"{foldername}/ui"):
|
||||
plugin["page"] = True
|
||||
|
@ -67,6 +75,13 @@ class Config:
|
|||
**self.__settings,
|
||||
}
|
||||
|
||||
if external_plugins:
|
||||
err = self.__db.update_external_plugins(external_plugins)
|
||||
if err:
|
||||
self.__logger.error(
|
||||
f"Couldn't update external plugins to database: {err}",
|
||||
)
|
||||
|
||||
def __env_to_dict(self, filename: str) -> dict:
|
||||
"""Converts the content of an env file into a dict
|
||||
|
||||
|
|
|
@ -775,6 +775,10 @@ h6 {
|
|||
z-index: 100;
|
||||
}
|
||||
|
||||
.z-\[10000\] {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.z-\[1001\] {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
@ -791,10 +795,6 @@ h6 {
|
|||
z-index: -10;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-990 {
|
||||
z-index: 990;
|
||||
}
|
||||
|
@ -807,16 +807,16 @@ h6 {
|
|||
z-index: 1020;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-\[1500\] {
|
||||
z-index: 1500;
|
||||
}
|
||||
|
||||
.z-\[10000\] {
|
||||
z-index: 10000;
|
||||
.z-0 {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.order-2 {
|
||||
|
@ -1022,14 +1022,6 @@ h6 {
|
|||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.mb-7 {
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
@ -1058,6 +1050,14 @@ h6 {
|
|||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-7 {
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.mr-6 {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
@ -1114,6 +1114,14 @@ h6 {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
@ -1134,26 +1142,10 @@ h6 {
|
|||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.h-3 {
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.h-19 {
|
||||
height: 4.75rem;
|
||||
}
|
||||
|
@ -1174,6 +1166,10 @@ h6 {
|
|||
height: 7.5rem;
|
||||
}
|
||||
|
||||
.h-3 {
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.h-\[90vh\] {
|
||||
height: 90vh;
|
||||
}
|
||||
|
@ -1258,6 +1254,14 @@ h6 {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.w-screen {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.w-40 {
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.w-4 {
|
||||
width: 1rem;
|
||||
}
|
||||
|
@ -1278,10 +1282,6 @@ h6 {
|
|||
width: 12rem;
|
||||
}
|
||||
|
||||
.w-screen {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.w-12 {
|
||||
width: 3rem;
|
||||
}
|
||||
|
@ -1290,14 +1290,6 @@ h6 {
|
|||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.w-60 {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.w-3 {
|
||||
width: 0.75rem;
|
||||
}
|
||||
|
||||
.w-28 {
|
||||
width: 7rem;
|
||||
}
|
||||
|
@ -1314,12 +1306,12 @@ h6 {
|
|||
width: 22.5rem;
|
||||
}
|
||||
|
||||
.w-80 {
|
||||
width: 20rem;
|
||||
.w-3 {
|
||||
width: 0.75rem;
|
||||
}
|
||||
|
||||
.w-40 {
|
||||
width: 10rem;
|
||||
.w-80 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
|
@ -1432,11 +1424,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-x-1 {
|
||||
--tw-translate-x: 0.25rem;
|
||||
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));
|
||||
|
@ -1452,6 +1439,11 @@ 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-x-1 {
|
||||
--tw-translate-x: 0.25rem;
|
||||
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-7 {
|
||||
--tw-translate-y: -1.75rem;
|
||||
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));
|
||||
|
@ -1535,10 +1527,6 @@ 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;
|
||||
}
|
||||
|
@ -1652,6 +1640,10 @@ h6 {
|
|||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.rounded-10 {
|
||||
border-radius: 2.5rem;
|
||||
}
|
||||
|
||||
.rounded-1\.4 {
|
||||
border-radius: 0.35rem;
|
||||
}
|
||||
|
@ -1660,10 +1652,6 @@ h6 {
|
|||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.rounded-10 {
|
||||
border-radius: 2.5rem;
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
@ -2465,6 +2453,11 @@ h6 {
|
|||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.brightness-75 {
|
||||
--tw-brightness: brightness(.75);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.filter {
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
@ -2481,12 +2474,6 @@ h6 {
|
|||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
|
@ -2495,20 +2482,26 @@ h6 {
|
|||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition-transform {
|
||||
transition-property: transform;
|
||||
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.duration-300 {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.duration-250 {
|
||||
transition-duration: 250ms;
|
||||
}
|
||||
|
@ -3138,14 +3131,6 @@ h6 {
|
|||
grid-column: span 4 / span 4;
|
||||
}
|
||||
|
||||
.sm\:col-span-1 {
|
||||
grid-column: span 1 / span 1;
|
||||
}
|
||||
|
||||
.sm\:col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
|
||||
.sm\:col-start-5 {
|
||||
grid-column-start: 5;
|
||||
}
|
||||
|
@ -3172,8 +3157,8 @@ h6 {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.sm\:h-24 {
|
||||
height: 6rem;
|
||||
.sm\:h-14 {
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.sm\:h-10 {
|
||||
|
@ -3184,18 +3169,6 @@ 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;
|
||||
}
|
||||
|
@ -3204,8 +3177,8 @@ h6 {
|
|||
max-height: 31.25rem;
|
||||
}
|
||||
|
||||
.sm\:w-80 {
|
||||
width: 20rem;
|
||||
.sm\:w-50 {
|
||||
width: 12.5rem;
|
||||
}
|
||||
|
||||
.sm\:w-36 {
|
||||
|
@ -3216,14 +3189,6 @@ h6 {
|
|||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.sm\:w-60 {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.sm\:w-50 {
|
||||
width: 12.5rem;
|
||||
}
|
||||
|
||||
.sm\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@ -3322,10 +3287,6 @@ h6 {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.md\:h-25 {
|
||||
height: 6.25rem;
|
||||
}
|
||||
|
||||
.md\:h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
@ -3338,18 +3299,14 @@ h6 {
|
|||
min-height: 75vh;
|
||||
}
|
||||
|
||||
.md\:w-1\/2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.md\:w-80 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.md\:w-60 {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.md\:w-1\/2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.md\:justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
@ -3438,38 +3395,26 @@ h6 {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.lg\:h-24 {
|
||||
height: 6rem;
|
||||
}
|
||||
|
||||
.lg\:h-9 {
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.lg\:h-30 {
|
||||
height: 7.5rem;
|
||||
}
|
||||
|
||||
.lg\:h-20 {
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.lg\:h-24 {
|
||||
height: 6rem;
|
||||
.lg\:w-80 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.lg\:w-9 {
|
||||
width: 2.25rem;
|
||||
}
|
||||
|
||||
.lg\:w-90 {
|
||||
width: 22.5rem;
|
||||
}
|
||||
|
||||
.lg\:w-1\/2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.lg\:w-80 {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.lg\:flex-none {
|
||||
flex: none;
|
||||
}
|
||||
|
@ -3521,10 +3466,6 @@ h6 {
|
|||
top: 0.75rem;
|
||||
}
|
||||
|
||||
.xl\:col-span-4 {
|
||||
grid-column: span 4 / span 4;
|
||||
}
|
||||
|
||||
.xl\:mx-4 {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
|
@ -3613,14 +3554,6 @@ h6 {
|
|||
.\33xl\:col-span-4 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.\[\&\>\*\]\:bg-primary>* {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
FolderEditor,
|
||||
FolderModal,
|
||||
FolderDropdown,
|
||||
} from "./utils.js";
|
||||
} from "./utils/file.manager.js";
|
||||
|
||||
const setModal = new FolderModal("cache");
|
||||
const setEditor = new FolderEditor();
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
FolderEditor,
|
||||
FolderModal,
|
||||
FolderDropdown,
|
||||
} from "./utils.js";
|
||||
} from "./utils/file.manager.js";
|
||||
|
||||
const setModal = new FolderModal("configs");
|
||||
const setEditor = new FolderEditor();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Checkbox, Loader } from "./utils.js";
|
||||
import { Checkbox } from "./utils/form.js";
|
||||
|
||||
class Menu {
|
||||
constructor() {
|
||||
|
@ -69,10 +69,11 @@ class darkMode {
|
|||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": this.csrf.value,
|
||||
},
|
||||
body: JSON.stringify({ darkmode: isDark, csrf_token: this.csrf.value }),
|
||||
body: JSON.stringify({ darkmode: isDark }),
|
||||
};
|
||||
const send = await fetch(`${location.href}/darkmode}`, data);
|
||||
const send = await fetch(`${location.href.split("/").slice(0, -1).join("/")}/darkmode`, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,6 +98,43 @@ class FlashMsg {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setLoader = new Loader();
|
||||
const setMenu = new Menu();
|
||||
const setNews = new News();
|
|
@ -1,7 +1,114 @@
|
|||
import { Checkbox, Popover, Select, Tabs, FormatValue } from "./utils.js";
|
||||
import { Checkbox, Select } from "./utils/form.js";
|
||||
import { Popover, Tabs, FormatValue } from "./utils/settings.js";
|
||||
|
||||
class FilterSettings {
|
||||
constructor(prefix) {
|
||||
this.prefix = prefix;
|
||||
this.input = document.querySelector("input#settings-filter");
|
||||
//DESKTOP
|
||||
this.deskTabs = document.querySelectorAll(`[${this.prefix}-item-handler]`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.input.addEventListener("input", () => {
|
||||
this.resetFilter();
|
||||
//get inp format
|
||||
const inpValue = this.input.value.trim().toLowerCase();
|
||||
//loop all tabs
|
||||
this.deskTabs.forEach((tab) => {
|
||||
//get settings of tabs except multiples
|
||||
const settings = this.getSettingsFromTab(tab);
|
||||
|
||||
//compare total count to currCount to determine
|
||||
//if tabs need to be hidden
|
||||
const settingCount = settings.length;
|
||||
let hiddenCount = 0;
|
||||
settings.forEach((setting) => {
|
||||
console.log(setting);
|
||||
try {
|
||||
const title = setting
|
||||
.querySelector("h5")
|
||||
.textContent.trim()
|
||||
.toLowerCase();
|
||||
if (!title.includes(inpValue)) {
|
||||
setting.classList.add("hidden");
|
||||
hiddenCount++;
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
//case no setting match, hidden tab and content
|
||||
if (settingCount === hiddenCount) {
|
||||
const tabName = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
//hide mobile and desk tabs
|
||||
tab.classList.add("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-mobile-item-handler="${tabName}"]`)
|
||||
.classList.add("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-item=${tabName}]`)
|
||||
.querySelector("[setting-header]")
|
||||
|
||||
.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter() {
|
||||
this.deskTabs.forEach((tab) => {
|
||||
const tabName = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
//hide mobile and desk tabs
|
||||
tab.classList.remove("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-mobile-item-handler="${tabName}"]`)
|
||||
.classList.remove("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-item=${tabName}]`)
|
||||
.querySelector("[setting-header]")
|
||||
.classList.remove("hidden");
|
||||
const settings = this.getSettingsFromTab(tab);
|
||||
settings.forEach((setting) => {
|
||||
setting.classList.remove("hidden");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSettingsFromTab(tabEl) {
|
||||
const tabName = tabEl.getAttribute(`${this.prefix}-item-handler`);
|
||||
const settingContainer = document
|
||||
.querySelector(`[${this.prefix}-item="${tabName}"]`)
|
||||
.querySelector(`[${this.prefix}-settings]`);
|
||||
const settings = settingContainer.querySelectorAll("[setting-container]");
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
class Multiple {
|
||||
constructor(prefix) {
|
||||
this.prefix = prefix;
|
||||
this.init();
|
||||
}
|
||||
//hide multiples handler if no multiple setting on plugin
|
||||
init() {
|
||||
//hide multiple btn if no multiple exist on a plugin
|
||||
const multiples = document.querySelectorAll(
|
||||
`[${this.prefix}-settings-multiple]`
|
||||
);
|
||||
multiples.forEach((container) => {
|
||||
console.log(container.querySelectorAll(`[setting-container]`));
|
||||
if (container.querySelectorAll(`[setting-container]`).length <= 0)
|
||||
container.parentElement
|
||||
.querySelector("[multiple-handler]")
|
||||
.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const setCheckbox = new Checkbox("[global-config-form]");
|
||||
const setSelect = new Select("[global-config-form]", "global-config");
|
||||
const setPopover = new Popover("main", "global-config");
|
||||
const setTabs = new Tabs("[global-config-tabs]", "global-config");
|
||||
const format = new FormatValue();
|
||||
const setMultiple = new Multiple("global-config");
|
||||
const setFilter = new FilterSettings("global-config");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Checkbox } from "./utils.js";
|
||||
import { Checkbox } from "./utils/form.js";
|
||||
import Datepicker from "./datepicker/datepicker.js";
|
||||
|
||||
class Dropdown {
|
||||
|
@ -427,7 +427,6 @@ class Filter {
|
|||
class LogsDate {
|
||||
constructor(el, options = {}) {
|
||||
this.datepicker = new Datepicker(el, options);
|
||||
this.init();
|
||||
this.container = document.querySelector("[logs-settings]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Checkbox, Popover, Select, Tabs, FormatValue } from "./utils.js";
|
||||
import { Checkbox, Select } from "./utils/form.js";
|
||||
import { Popover, Tabs, FormatValue } from "./utils/settings.js";
|
||||
|
||||
class ServiceModal {
|
||||
constructor() {
|
||||
|
@ -15,6 +16,8 @@ class ServiceModal {
|
|||
//general inputs
|
||||
this.inputs = this.modal.querySelectorAll("input[default-value]");
|
||||
this.selects = this.modal.querySelectorAll("select[default-value]");
|
||||
this.lastGroup = "";
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
@ -40,7 +43,7 @@ class ServiceModal {
|
|||
) {
|
||||
this.setDeleteForm(
|
||||
"delete",
|
||||
e.target.closest("button").getAttribute("service-name")
|
||||
e.target.closest("button").getAttribute("services-name")
|
||||
);
|
||||
this.openModal();
|
||||
}
|
||||
|
@ -60,10 +63,15 @@ class ServiceModal {
|
|||
if (
|
||||
e.target.closest("button").getAttribute("services-action") === "edit"
|
||||
) {
|
||||
this.setNewEditForm(
|
||||
"edit",
|
||||
e.target.closest("button").getAttribute("service-name")
|
||||
);
|
||||
//no reupdate if same service
|
||||
const serviceName = e.target
|
||||
.closest("button")
|
||||
.getAttribute("services-name");
|
||||
|
||||
if (this.lastGroup === serviceName) return this.openModal();
|
||||
//else
|
||||
this.lastGroup = serviceName;
|
||||
this.setNewEditForm("edit", serviceName);
|
||||
//change this to hidden config on service card later
|
||||
const servicesSettings = e.target
|
||||
.closest("[services-service]")
|
||||
|
@ -238,16 +246,28 @@ class Multiple {
|
|||
this.prefix = prefix;
|
||||
this.container = document.querySelector("main");
|
||||
this.modalForm = document.querySelector(`[${this.prefix}-modal-form]`);
|
||||
this.lastGroup = "";
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
window.addEventListener("load", () => {
|
||||
this.hiddenIfNoMultiples();
|
||||
});
|
||||
|
||||
this.container.addEventListener("click", (e) => {
|
||||
//edit button
|
||||
try {
|
||||
if (
|
||||
e.target.closest("button").getAttribute("services-action") === "edit"
|
||||
) {
|
||||
//avoid reupdate if same service
|
||||
const serviceName = e.target
|
||||
.closest("button")
|
||||
.getAttribute("services-name");
|
||||
if (this.lastGroup === serviceName) return;
|
||||
//else
|
||||
this.lastGroup = serviceName;
|
||||
//remove all multiples
|
||||
this.removeMultiples();
|
||||
//set multiple service values
|
||||
|
@ -274,14 +294,25 @@ class Multiple {
|
|||
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
|
||||
)
|
||||
);
|
||||
let count;
|
||||
//case no schema
|
||||
if (multipleEls.length <= 0) return;
|
||||
//case only schema
|
||||
if (multipleEls.length === 1) {
|
||||
count = 0;
|
||||
}
|
||||
//case schema and custom configs with num
|
||||
if (multipleEls.length > 1) {
|
||||
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"]`
|
||||
|
@ -482,6 +513,20 @@ class Multiple {
|
|||
|
||||
//UTILS
|
||||
|
||||
hiddenIfNoMultiples() {
|
||||
//hide multiple btn if no multiple exist on a plugin
|
||||
const multiples = document.querySelectorAll(
|
||||
`[${this.prefix}-settings-multiple]`
|
||||
);
|
||||
multiples.forEach((container) => {
|
||||
console.log(container.querySelectorAll(`[setting-container]`));
|
||||
if (container.querySelectorAll(`[setting-container]`).length <= 0)
|
||||
container.parentElement
|
||||
.querySelector("[multiple-handler]")
|
||||
.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
removeMultiples() {
|
||||
const multiPlugins = document.querySelectorAll(
|
||||
`[${this.prefix}-settings-multiple]`
|
||||
|
@ -514,6 +559,89 @@ class Multiple {
|
|||
}
|
||||
}
|
||||
|
||||
class FilterSettings {
|
||||
constructor(prefix) {
|
||||
this.prefix = prefix;
|
||||
this.input = document.querySelector("input#settings-filter");
|
||||
//DESKTOP
|
||||
this.deskTabs = document.querySelectorAll(`[${this.prefix}-item-handler]`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.input.addEventListener("input", () => {
|
||||
this.resetFilter();
|
||||
//get inp format
|
||||
const inpValue = this.input.value.trim().toLowerCase();
|
||||
//loop all tabs
|
||||
this.deskTabs.forEach((tab) => {
|
||||
//get settings of tabs except multiples
|
||||
const settings = this.getSettingsFromTab(tab);
|
||||
|
||||
//compare total count to currCount to determine
|
||||
//if tabs need to be hidden
|
||||
const settingCount = settings.length;
|
||||
let hiddenCount = 0;
|
||||
settings.forEach((setting) => {
|
||||
console.log(setting);
|
||||
try {
|
||||
const title = setting
|
||||
.querySelector("h5")
|
||||
.textContent.trim()
|
||||
.toLowerCase();
|
||||
if (!title.includes(inpValue)) {
|
||||
setting.classList.add("hidden");
|
||||
hiddenCount++;
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
//case no setting match, hidden tab and content
|
||||
if (settingCount === hiddenCount) {
|
||||
const tabName = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
//hide mobile and desk tabs
|
||||
tab.classList.add("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-mobile-item-handler="${tabName}"]`)
|
||||
.classList.add("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-item=${tabName}]`)
|
||||
.querySelector("[setting-header]")
|
||||
|
||||
.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter() {
|
||||
this.deskTabs.forEach((tab) => {
|
||||
const tabName = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
//hide mobile and desk tabs
|
||||
tab.classList.remove("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-mobile-item-handler="${tabName}"]`)
|
||||
.classList.remove("hidden");
|
||||
document
|
||||
.querySelector(`[${this.prefix}-item=${tabName}]`)
|
||||
.querySelector("[setting-header]")
|
||||
.classList.remove("hidden");
|
||||
const settings = this.getSettingsFromTab(tab);
|
||||
settings.forEach((setting) => {
|
||||
setting.classList.remove("hidden");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSettingsFromTab(tabEl) {
|
||||
const tabName = tabEl.getAttribute(`${this.prefix}-item-handler`);
|
||||
const settingContainer = document
|
||||
.querySelector(`[${this.prefix}-item="${tabName}"]`)
|
||||
.querySelector(`[${this.prefix}-settings]`);
|
||||
const settings = settingContainer.querySelectorAll("[setting-container]");
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
const setCheckbox = new Checkbox("[services-modal-form]");
|
||||
const setSelect = new Select("[services-modal-form]", "services");
|
||||
const setPopover = new Popover("main", "services");
|
||||
|
@ -521,3 +649,4 @@ const setTabs = new Tabs("[services-tabs]", "services");
|
|||
const setModal = new ServiceModal();
|
||||
const format = new FormatValue();
|
||||
const setMultiple = new Multiple("services");
|
||||
const setFilter = new FilterSettings("services");
|
||||
|
|
|
@ -1,338 +1,3 @@
|
|||
class Checkbox {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.checkContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.checkContainer.addEventListener("click", (e) => {
|
||||
//checkbox click
|
||||
try {
|
||||
if (
|
||||
e.target.closest("div").hasAttribute("checkbox-handler") &&
|
||||
!e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.hasAttribute("disabled")
|
||||
) {
|
||||
//change DOM
|
||||
const checkboxEl = e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]');
|
||||
checkboxEl.checked
|
||||
? checkboxEl.setAttribute("value", "yes")
|
||||
: checkboxEl.setAttribute("value", "no");
|
||||
}
|
||||
} catch (err) {}
|
||||
//nested elements click
|
||||
try {
|
||||
if (
|
||||
e.target.closest("svg").hasAttribute("checkbox-handler") &&
|
||||
!e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.hasAttribute("disabled")
|
||||
) {
|
||||
e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.click();
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Select {
|
||||
constructor(container, prefixAtt) {
|
||||
this.prefix = prefixAtt;
|
||||
this.container = container;
|
||||
this.SelectContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.SelectContainer.addEventListener("click", (e) => {
|
||||
//SELECT BTN LOGIC
|
||||
try {
|
||||
if (
|
||||
e.target
|
||||
.closest("button")
|
||||
.hasAttribute(`${this.prefix}-setting-select`) &&
|
||||
!e.target.closest("button").hasAttribute(`disabled`)
|
||||
) {
|
||||
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`
|
||||
);
|
||||
//add new value to custom
|
||||
const selectCustom = document.querySelector(
|
||||
`[${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
selectCustom.querySelector(
|
||||
`[${this.prefix}-setting-select-text]`
|
||||
).textContent = btnValue;
|
||||
//add selected to new value
|
||||
|
||||
//change style
|
||||
const dropdownEl = document.querySelector(
|
||||
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
||||
);
|
||||
dropdownEl.classList.add("hidden");
|
||||
dropdownEl.classList.remove("flex");
|
||||
|
||||
//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
|
||||
btn.classList.remove(
|
||||
"bg-white",
|
||||
"dark:bg-slate-700",
|
||||
"text-gray-700"
|
||||
);
|
||||
btn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
||||
|
||||
//close dropdown
|
||||
const dropdownChevron = document.querySelector(
|
||||
`svg[${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
dropdownChevron.classList.remove("rotate-180");
|
||||
|
||||
//update real select element
|
||||
this.updateSelected(
|
||||
document.querySelector(
|
||||
`[${this.prefix}-setting-select-default="${btnSetting}"]`
|
||||
),
|
||||
btnValue
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
updateSelected(selectEl, selectedValue) {
|
||||
const options = selectEl.querySelectorAll("option");
|
||||
//remove selected to all
|
||||
options.forEach((option) => {
|
||||
option.removeAttribute("selected");
|
||||
option.selected = false;
|
||||
});
|
||||
//select new one
|
||||
const newOption = selectEl.querySelector(
|
||||
`option[value="${selectedValue}"]`
|
||||
);
|
||||
newOption.selected = true;
|
||||
newOption.setAttribute("selected", "");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
class Popover {
|
||||
constructor(container, prefix) {
|
||||
this.prefix = prefix;
|
||||
this.container = container;
|
||||
this.popoverContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
let popoverCount = 0; //for auto hide
|
||||
let btnPopoverAtt = ""; //to manage info btn clicked
|
||||
|
||||
this.popoverContainer.addEventListener("click", (e) => {
|
||||
//POPOVER LOGIC
|
||||
try {
|
||||
if (e.target.closest("svg").hasAttribute(`${this.prefix}-info-btn`)) {
|
||||
const btnPop = e.target.closest("svg");
|
||||
//toggle curr popover
|
||||
const popover = btnPop.parentElement.querySelector(
|
||||
`[${this.prefix}-info-popover]`
|
||||
);
|
||||
popover.classList.toggle("hidden");
|
||||
|
||||
//get a btn att if none
|
||||
if (btnPopoverAtt === "")
|
||||
btnPopoverAtt = btnPop.getAttribute(`${this.prefix}-info-btn`);
|
||||
|
||||
//compare prev btn and curr
|
||||
//hide prev popover if not the same
|
||||
if (
|
||||
btnPopoverAtt !== "" &&
|
||||
btnPopoverAtt !== btnPop.getAttribute(`${this.prefix}-info-btn`)
|
||||
) {
|
||||
const prevPopover = document.querySelector(
|
||||
`[${this.prefix}-info-popover="${btnPopoverAtt}"]`
|
||||
);
|
||||
prevPopover.classList.add("hidden");
|
||||
btnPopoverAtt = btnPop.getAttribute(`${this.prefix}-info-btn`);
|
||||
}
|
||||
|
||||
//hide popover after an amount of time
|
||||
popoverCount++;
|
||||
const currCount = popoverCount;
|
||||
setTimeout(() => {
|
||||
//if another click on same infoBtn, restart hidden
|
||||
if (currCount === popoverCount) popover.classList.add("hidden");
|
||||
}, 3000);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Tabs {
|
||||
constructor(container, prefix) {
|
||||
this.prefix = prefix;
|
||||
this.container = container;
|
||||
this.tabsContainer = document.querySelector(`${this.container}`);
|
||||
|
||||
this.mobileBtn = document.querySelector(`[${this.prefix}-mobile-select]`);
|
||||
this.mobileBtnTxt = this.mobileBtn.querySelector(`span`);
|
||||
this.mobileBtnSVG = document.querySelector(
|
||||
`[${this.prefix}-mobile-chevron]`
|
||||
);
|
||||
this.mobileDropdown = document.querySelector(
|
||||
`[${this.prefix}-mobile-dropdown]`
|
||||
);
|
||||
this.mobileDropdownEls = this.mobileDropdown.querySelectorAll(`button`);
|
||||
this.mobileBtn.addEventListener(`click`, this.toggleDropdown.bind(this));
|
||||
//FORM
|
||||
this.settingContainers = document.querySelectorAll(`[${this.prefix}-item]`);
|
||||
this.generalSettings = document.querySelector(
|
||||
`[${this.prefix}-item='general']`
|
||||
);
|
||||
this.initTabs();
|
||||
this.initDisplay();
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
this.tabsContainer.addEventListener("click", (e) => {
|
||||
//MOBILE TABS LOGIC
|
||||
try {
|
||||
if (
|
||||
!e.target.hasAttribute(`${this.prefix}-mobile-info-btn`) &&
|
||||
e.target.hasAttribute(`${this.prefix}-mobile-item-handler`)
|
||||
) {
|
||||
//change text to select btn
|
||||
const tab = e.target.closest("button");
|
||||
const tabAtt = tab.getAttribute(`${this.prefix}-mobile-item-handler`);
|
||||
this.mobileBtnTxt.textContent = tab.childNodes[0].textContent;
|
||||
//reset all tabs style
|
||||
this.mobileDropdownEls.forEach((item) => {
|
||||
item.classList.add(
|
||||
"bg-white",
|
||||
"dark:bg-slate-700",
|
||||
"text-gray-700"
|
||||
);
|
||||
item.classList.remove(
|
||||
"dark:bg-primary",
|
||||
"bg-primary",
|
||||
"bg-primary",
|
||||
"text-gray-300",
|
||||
"text-gray-300"
|
||||
);
|
||||
});
|
||||
//highlight chosen one
|
||||
tab.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
||||
tab.classList.remove(
|
||||
"bg-white",
|
||||
"dark:bg-slate-700",
|
||||
"text-gray-700"
|
||||
);
|
||||
//show settings
|
||||
this.showRightSetting(tabAtt);
|
||||
//close dropdown
|
||||
this.toggleDropdown();
|
||||
}
|
||||
} catch (err) {}
|
||||
//DESKTOP TABS LOGIC
|
||||
try {
|
||||
if (
|
||||
!e.target.hasAttribute(`${this.prefix}-info-btn`) &&
|
||||
e.target.closest("button").hasAttribute(`${this.prefix}-item-handler`)
|
||||
) {
|
||||
const tab = e.target.closest("button");
|
||||
const tabAtt = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
this.showRightSetting(tabAtt);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
initDisplay() {
|
||||
//show general setting or
|
||||
//first setting list if doesn't exist (like in services)
|
||||
//on mobile and desktop
|
||||
if (this.generalSettings === null) {
|
||||
//desktop
|
||||
document
|
||||
.querySelector(
|
||||
`[${this.prefix}-tabs-desktop] [${this.prefix}-item-handler]`
|
||||
)
|
||||
.click();
|
||||
//mobile
|
||||
document
|
||||
.querySelector(
|
||||
`[${this.prefix}-tabs-mobile] [${this.prefix}-mobile-item-handler]`
|
||||
)
|
||||
.click();
|
||||
this.toggleDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
showRightSetting(tabAtt) {
|
||||
this.settingContainers.forEach((container) => {
|
||||
if (container.getAttribute(`${this.prefix}-item`) === tabAtt)
|
||||
container.classList.remove("hidden");
|
||||
if (container.getAttribute(`${this.prefix}-item`) !== tabAtt)
|
||||
container.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
toggleDropdown() {
|
||||
this.mobileDropdown.classList.toggle("hidden");
|
||||
this.mobileDropdown.classList.toggle("flex");
|
||||
this.mobileBtnSVG.classList.toggle("rotate-180");
|
||||
}
|
||||
}
|
||||
|
||||
class FolderNav {
|
||||
constructor(prefix) {
|
||||
this.prefix = prefix;
|
||||
|
@ -899,65 +564,4 @@ class FolderModal {
|
|||
}
|
||||
}
|
||||
|
||||
class FormatValue {
|
||||
constructor() {
|
||||
this.inputs = document.querySelectorAll("[value]");
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.inputs.forEach((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,
|
||||
Select,
|
||||
Tabs,
|
||||
FolderNav,
|
||||
FolderModal,
|
||||
FolderEditor,
|
||||
FolderDropdown,
|
||||
FormatValue,
|
||||
Loader,
|
||||
};
|
||||
export { FolderNav, FolderModal, FolderEditor, FolderDropdown };
|
|
@ -0,0 +1,167 @@
|
|||
class Checkbox {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.checkContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.checkContainer.addEventListener("click", (e) => {
|
||||
//checkbox click
|
||||
try {
|
||||
if (
|
||||
e.target.closest("div").hasAttribute("checkbox-handler") &&
|
||||
!e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.hasAttribute("disabled")
|
||||
) {
|
||||
//change DOM
|
||||
const checkboxEl = e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]');
|
||||
checkboxEl.checked
|
||||
? checkboxEl.setAttribute("value", "yes")
|
||||
: checkboxEl.setAttribute("value", "no");
|
||||
}
|
||||
} catch (err) {}
|
||||
//nested elements click
|
||||
try {
|
||||
if (
|
||||
e.target.closest("svg").hasAttribute("checkbox-handler") &&
|
||||
!e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.hasAttribute("disabled")
|
||||
) {
|
||||
e.target
|
||||
.closest("div")
|
||||
.querySelector('input[type="checkbox"]')
|
||||
.click();
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Select {
|
||||
constructor(container, prefixAtt) {
|
||||
this.prefix = prefixAtt;
|
||||
this.container = container;
|
||||
this.SelectContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.SelectContainer.addEventListener("click", (e) => {
|
||||
//SELECT BTN LOGIC
|
||||
try {
|
||||
if (
|
||||
e.target
|
||||
.closest("button")
|
||||
.hasAttribute(`${this.prefix}-setting-select`) &&
|
||||
!e.target.closest("button").hasAttribute(`disabled`)
|
||||
) {
|
||||
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`
|
||||
);
|
||||
//add new value to custom
|
||||
const selectCustom = document.querySelector(
|
||||
`[${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
selectCustom.querySelector(
|
||||
`[${this.prefix}-setting-select-text]`
|
||||
).textContent = btnValue;
|
||||
//add selected to new value
|
||||
|
||||
//change style
|
||||
const dropdownEl = document.querySelector(
|
||||
`[${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
||||
);
|
||||
dropdownEl.classList.add("hidden");
|
||||
dropdownEl.classList.remove("flex");
|
||||
|
||||
//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
|
||||
btn.classList.remove(
|
||||
"bg-white",
|
||||
"dark:bg-slate-700",
|
||||
"text-gray-700"
|
||||
);
|
||||
btn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
||||
|
||||
//close dropdown
|
||||
const dropdownChevron = document.querySelector(
|
||||
`svg[${this.prefix}-setting-select="${btnSetting}"]`
|
||||
);
|
||||
dropdownChevron.classList.remove("rotate-180");
|
||||
|
||||
//update real select element
|
||||
this.updateSelected(
|
||||
document.querySelector(
|
||||
`[${this.prefix}-setting-select-default="${btnSetting}"]`
|
||||
),
|
||||
btnValue
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
updateSelected(selectEl, selectedValue) {
|
||||
const options = selectEl.querySelectorAll("option");
|
||||
//remove selected to all
|
||||
options.forEach((option) => {
|
||||
option.removeAttribute("selected");
|
||||
option.selected = false;
|
||||
});
|
||||
//select new one
|
||||
const newOption = selectEl.querySelector(
|
||||
`option[value="${selectedValue}"]`
|
||||
);
|
||||
newOption.selected = true;
|
||||
newOption.setAttribute("selected", "");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
export { Checkbox, Select };
|
|
@ -0,0 +1,198 @@
|
|||
class Popover {
|
||||
constructor(container, prefix) {
|
||||
this.prefix = prefix;
|
||||
this.container = container;
|
||||
this.popoverContainer = document.querySelector(`${this.container}`);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
let popoverCount = 0; //for auto hide
|
||||
let btnPopoverAtt = ""; //to manage info btn clicked
|
||||
|
||||
this.popoverContainer.addEventListener("click", (e) => {
|
||||
//POPOVER LOGIC
|
||||
try {
|
||||
if (e.target.closest("svg").hasAttribute(`${this.prefix}-info-btn`)) {
|
||||
const btnPop = e.target.closest("svg");
|
||||
//toggle curr popover
|
||||
const popover = btnPop.parentElement.querySelector(
|
||||
`[${this.prefix}-info-popover]`
|
||||
);
|
||||
popover.classList.toggle("hidden");
|
||||
|
||||
//get a btn att if none
|
||||
if (btnPopoverAtt === "")
|
||||
btnPopoverAtt = btnPop.getAttribute(`${this.prefix}-info-btn`);
|
||||
|
||||
//compare prev btn and curr
|
||||
//hide prev popover if not the same
|
||||
if (
|
||||
btnPopoverAtt !== "" &&
|
||||
btnPopoverAtt !== btnPop.getAttribute(`${this.prefix}-info-btn`)
|
||||
) {
|
||||
const prevPopover = document.querySelector(
|
||||
`[${this.prefix}-info-popover="${btnPopoverAtt}"]`
|
||||
);
|
||||
prevPopover.classList.add("hidden");
|
||||
btnPopoverAtt = btnPop.getAttribute(`${this.prefix}-info-btn`);
|
||||
}
|
||||
|
||||
//hide popover after an amount of time
|
||||
popoverCount++;
|
||||
const currCount = popoverCount;
|
||||
setTimeout(() => {
|
||||
//if another click on same infoBtn, restart hidden
|
||||
if (currCount === popoverCount) popover.classList.add("hidden");
|
||||
}, 3000);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Tabs {
|
||||
constructor(container, prefix) {
|
||||
this.prefix = prefix;
|
||||
this.container = container;
|
||||
this.tabsContainer = document.querySelector(`${this.container}`);
|
||||
//DESKTOP
|
||||
this.desktopBtns = document.querySelectorAll(
|
||||
`[${this.prefix}-tabs-desktop] button`
|
||||
);
|
||||
console.log(this.desktopBtns);
|
||||
//MOBILE
|
||||
this.mobileBtn = document.querySelector(`[${this.prefix}-mobile-select]`);
|
||||
this.mobileBtnTxt = this.mobileBtn.querySelector(`span`);
|
||||
this.mobileBtnSVG = document.querySelector(
|
||||
`[${this.prefix}-mobile-chevron]`
|
||||
);
|
||||
this.mobileDropdown = document.querySelector(
|
||||
`[${this.prefix}-mobile-dropdown]`
|
||||
);
|
||||
this.mobileDropdownEls = this.mobileDropdown.querySelectorAll(`button`);
|
||||
this.mobileBtn.addEventListener(`click`, this.toggleDropdown.bind(this));
|
||||
//FORM
|
||||
this.settingContainers = document.querySelectorAll(`[${this.prefix}-item]`);
|
||||
this.generalSettings = document.querySelector(
|
||||
`[${this.prefix}-item='general']`
|
||||
);
|
||||
this.initTabs();
|
||||
this.initDisplay();
|
||||
}
|
||||
|
||||
initTabs() {
|
||||
this.tabsContainer.addEventListener("click", (e) => {
|
||||
//MOBILE TABS LOGIC
|
||||
try {
|
||||
if (
|
||||
!e.target.hasAttribute(`${this.prefix}-mobile-info-btn`) &&
|
||||
e.target.hasAttribute(`${this.prefix}-mobile-item-handler`)
|
||||
) {
|
||||
//change text to select btn
|
||||
const tab = e.target.closest("button");
|
||||
const tabAtt = tab.getAttribute(`${this.prefix}-mobile-item-handler`);
|
||||
this.mobileBtnTxt.textContent = tab.childNodes[0].textContent;
|
||||
//reset all tabs style
|
||||
this.resetMobTabStyle();
|
||||
//highlight chosen one
|
||||
this.highlightMobClicked(tab);
|
||||
//show settings
|
||||
this.showRightSetting(tabAtt);
|
||||
//close dropdown
|
||||
this.toggleDropdown();
|
||||
}
|
||||
} catch (err) {}
|
||||
//DESKTOP TABS LOGIC
|
||||
try {
|
||||
if (
|
||||
!e.target.hasAttribute(`${this.prefix}-info-btn`) &&
|
||||
e.target.closest("button").hasAttribute(`${this.prefix}-item-handler`)
|
||||
) {
|
||||
const tab = e.target.closest("button");
|
||||
const tabAtt = tab.getAttribute(`${this.prefix}-item-handler`);
|
||||
//style
|
||||
this.resetDeskStyle();
|
||||
tab.classList.add("brightness-75");
|
||||
//show content
|
||||
this.showRightSetting(tabAtt);
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
resetDeskStyle() {
|
||||
this.desktopBtns.forEach((tab) => {
|
||||
tab.classList.remove("brightness-75");
|
||||
});
|
||||
}
|
||||
|
||||
resetMobTabStyle() {
|
||||
this.mobileDropdownEls.forEach((tab) => {
|
||||
tab.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
|
||||
tab.classList.remove(
|
||||
"dark:bg-primary",
|
||||
"bg-primary",
|
||||
"bg-primary",
|
||||
"text-gray-300",
|
||||
"text-gray-300"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
highlightMobClicked(tabEl) {
|
||||
tabEl.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
||||
tabEl.classList.remove("bg-white", "dark:bg-slate-700", "text-gray-700");
|
||||
}
|
||||
|
||||
initDisplay() {
|
||||
//show general setting or
|
||||
//first setting list if doesn't exist (like in services)
|
||||
//on mobile and desktop
|
||||
if (this.generalSettings === null) {
|
||||
//desktop
|
||||
document
|
||||
.querySelector(
|
||||
`[${this.prefix}-tabs-desktop] [${this.prefix}-item-handler]`
|
||||
)
|
||||
.click();
|
||||
//mobile
|
||||
document
|
||||
.querySelector(
|
||||
`[${this.prefix}-tabs-mobile] [${this.prefix}-mobile-item-handler]`
|
||||
)
|
||||
.click();
|
||||
this.toggleDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
showRightSetting(tabAtt) {
|
||||
this.settingContainers.forEach((container) => {
|
||||
if (container.getAttribute(`${this.prefix}-item`) === tabAtt)
|
||||
container.classList.remove("hidden");
|
||||
if (container.getAttribute(`${this.prefix}-item`) !== tabAtt)
|
||||
container.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
toggleDropdown() {
|
||||
this.mobileDropdown.classList.toggle("hidden");
|
||||
this.mobileDropdown.classList.toggle("flex");
|
||||
this.mobileBtnSVG.classList.toggle("rotate-180");
|
||||
}
|
||||
}
|
||||
|
||||
class FormatValue {
|
||||
constructor() {
|
||||
this.inputs = document.querySelectorAll("[value]");
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.inputs.forEach((inp) => {
|
||||
inp.setAttribute("value", inp.getAttribute("value").trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { Popover, Tabs, FormatValue };
|
|
@ -1,126 +1,388 @@
|
|||
<ul class="col-span-6 w-full flex justify-end items-center mb-3">
|
||||
<li
|
||||
conf-add-folder
|
||||
class="min-h-20 hidden flex-col items-center mx-4 sm:mx-6 lg:mx-0 p-4 relative cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<button type="button">
|
||||
<svg class="h-7 w-7 fill-primary" viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M512 416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96C0 60.7 28.7 32 64 32H181.5c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7H448c35.3 0 64 28.7 64 64V416zM232 376c0 13.3 10.7 24 24 24s24-10.7 24-24V312h64c13.3 0 24-10.7 24-24s-10.7-24-24-24H280V200c0-13.3-10.7-24-24-24s-24 10.7-24 24v64H168c-13.3 0-24 10.7-24 24s10.7 24 24 24h64v64z"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="pt-1 mb-0 font-sans font-semibold leading-normal uppercase text-sm"
|
||||
>
|
||||
ADD SERVICE
|
||||
</p>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li
|
||||
conf-add-file
|
||||
class="min-h-20 hidden flex-col items-center mx-4 sm:mx-6 lg:mx-0 p-4 relative cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<button type="button">
|
||||
<svg
|
||||
class="h-7 w-7"
|
||||
width="384"
|
||||
height="512"
|
||||
viewBox="0 0 384 512"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_19_2)">
|
||||
<path
|
||||
class="fill-primary"
|
||||
d="M0 64C0 28.7 28.7 0 64 0H224V128C224 145.7 238.3 160 256 160H384V448C384 483.3 355.3 512 320 512H64C28.7 512 0 483.3 0 448V64ZM384 128H256V0L384 128Z"
|
||||
fill="black"
|
||||
/>
|
||||
<rect
|
||||
class="bg-opacity-0"
|
||||
x="173"
|
||||
y="197"
|
||||
width="38"
|
||||
height="250"
|
||||
rx="19"
|
||||
fill="#D9D9D9"
|
||||
/>
|
||||
<rect
|
||||
class="bg-opacity-0"
|
||||
x="67"
|
||||
y="341"
|
||||
width="38"
|
||||
height="250"
|
||||
rx="19"
|
||||
transform="rotate(-90 67 341)"
|
||||
fill="#D9D9D9"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_19_2">
|
||||
<rect width="384" height="512" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<p
|
||||
class="pt-1 mb-0 font-sans font-semibold leading-normal uppercase text-sm"
|
||||
>
|
||||
ADD FILE
|
||||
</p>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- dropdown actions -->
|
||||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
%}
|
||||
{% set global_config =
|
||||
config["CONFIG"].get_config() %}
|
||||
{% set plugins = config["CONFIG"].get_plugins() %}
|
||||
<!-- plugin item -->
|
||||
{% for plugin in plugins %}
|
||||
<div
|
||||
action-dropdown="{{child['name']}}"
|
||||
class="z-100 relative h-full flex-col mt-2"
|
||||
{{current_endpoint}}-item="{{plugin['id']}}"
|
||||
id="{{plugin['id']}}"
|
||||
class="hidden w-full"
|
||||
>
|
||||
{% if child['type'] == "file" %}
|
||||
<button
|
||||
type="button"
|
||||
value="read"
|
||||
action-dropdown-btn="{{child['name']}}"
|
||||
class="border-t rounded-t 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"
|
||||
>
|
||||
read
|
||||
</button>
|
||||
{% endif %} {% if child['type'] == "file" and child['can_edit'] == True %}
|
||||
<button
|
||||
type="button"
|
||||
value="edit"
|
||||
action-dropdown-btn="{{child['name']}}"
|
||||
class="border-t rounded-t 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"
|
||||
>
|
||||
edit
|
||||
</button>
|
||||
{% endif %} {% if child['type'] == "file" and child['can_download'] == True %}
|
||||
<button
|
||||
type="button"
|
||||
value="download"
|
||||
action-dropdown-btn="{{child['name']}}"
|
||||
class="border-t rounded-t 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"
|
||||
>
|
||||
download
|
||||
</button>
|
||||
{% endif %} {% if child['type'] == "folder" and child['can_edit'] == True %}
|
||||
<button
|
||||
type="button"
|
||||
value="edit"
|
||||
action-dropdown-btn="{{child['name']}}"
|
||||
class="border-t rounded-t 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"
|
||||
>
|
||||
edit
|
||||
</button>
|
||||
{% endif %} {% if child['type'] == "folder" and child['can_delete'] == True %}
|
||||
<button
|
||||
type="button"
|
||||
value="delete"
|
||||
action-dropdown-btn="{{child['name']}}"
|
||||
class="border-t rounded-t 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"
|
||||
>
|
||||
delete
|
||||
</button>
|
||||
{% endif %}
|
||||
<!-- 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>
|
||||
</div>
|
||||
<!-- end title and desc -->
|
||||
<!-- plugin unless multiple -->
|
||||
<div {{current_endpoint}}-settings-multiple 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=" {%if value['multiple'] %}hidden{% endif %}
|
||||
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 %}
|
||||
<!-- end plugin settings -->
|
||||
</div>
|
||||
<!-- end plugin unless multiple -->
|
||||
<!-- plugin 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 value['multiple'] or current_endpoint ==
|
||||
"services" and value['context'] == "multisite" and 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 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 %}
|
||||
<!-- end plugin settings -->
|
||||
</div>
|
||||
<!-- end plugin multiple-->
|
||||
</div>
|
||||
<!-- end dropdown actions -->
|
||||
{% endfor %}
|
||||
<!-- end plugin item -->
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-')
|
||||
%}
|
||||
{% set global_config =
|
||||
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">
|
||||
<!-- 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>
|
||||
</div>
|
||||
<!-- end title and desc -->
|
||||
<!-- 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" or current_endpoint == "services" and value['context'] == "multisite" %}
|
||||
<div
|
||||
{%if value['multiple'] %}multiple="{{value['multiple']}}"{% endif %}
|
||||
class=" {%if value['multiple'] %}hidden{% endif %}
|
||||
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 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}}
|
||||
</div>
|
||||
<!--end invalid feedback -->
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<!-- end plugin settings -->
|
||||
</div>
|
||||
<!-- end plugin settings not multiple -->
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- end plugin item -->
|
||||
|
||||
|
||||
|
|
@ -1,36 +1,59 @@
|
|||
{% extends "base.html" %} {% block content %} {% set global_config =
|
||||
config["CONFIG"].get_config() %}
|
||||
<!-- tabs -->
|
||||
{% include "settings_tabs.html" %}
|
||||
<!-- end tabs-->
|
||||
<!-- form global conf -->
|
||||
<form
|
||||
global-config-form
|
||||
id="form-edit-global-configs"
|
||||
method="POST"
|
||||
class="flex flex-col justify-between overflow-hidden overflow-y-auto max-h-135 md:max-h-160 dark:brightness-110 col-span-12 break-words bg-white shadow-xl p-4 dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
|
||||
<div class="p-4 col-span-12 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="flex justify-start items-center gap-x-4 gap-y-2 mb-2">
|
||||
<h5 class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0">CONFIGS</h5>
|
||||
<!-- search inpt-->
|
||||
<div class="flex relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3">
|
||||
|
||||
<!-- general container -->
|
||||
{% include "settings_general.html" %}
|
||||
<!-- end general container -->
|
||||
|
||||
<!-- plugin item -->
|
||||
{% include "settings_plugins.html" %}
|
||||
<!-- end plugin item -->
|
||||
|
||||
<!-- submit -->
|
||||
<div class="flex w-full justify-center mt-8 mb-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="tracking-wide 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-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
|
||||
>
|
||||
SAVE
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
id="settings-filter"
|
||||
name="settings-filter"
|
||||
class="col-span-12 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"
|
||||
pattern="(.*?)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<!-- end search inpt-->
|
||||
</div>
|
||||
<!-- end submit -->
|
||||
</form>
|
||||
<!--end form global conf -->
|
||||
<!-- tabs -->
|
||||
{% include "settings_tabs.html" %}
|
||||
<!-- end tabs-->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
<!-- form global conf -->
|
||||
<form
|
||||
global-config-form
|
||||
id="form-edit-global-configs"
|
||||
method="POST"
|
||||
class="flex flex-col justify-between overflow-hidden overflow-y-auto max-h-135 md:max-h-160 dark:brightness-110 col-span-12 break-words bg-white shadow-xl p-4 dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
<!-- general container -->
|
||||
{% include "settings_general.html" %}
|
||||
<!-- end general container -->
|
||||
|
||||
<!-- plugin item -->
|
||||
{% include "settings_plugins.html" %}
|
||||
<!-- end plugin item -->
|
||||
|
||||
<!-- submit -->
|
||||
<div class="flex w-full justify-center mt-8 mb-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="tracking-wide 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-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
|
||||
>
|
||||
SAVE
|
||||
</button>
|
||||
</div>
|
||||
<!-- end submit -->
|
||||
</form>
|
||||
<!--end form global conf -->
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<!-- tailwind style -->
|
||||
<link rel="stylesheet" type="text/css" href="./css/dashboard.css" />
|
||||
|
||||
<script type="module" defer src="./js/dashboard.js"></script>
|
||||
<script type="module" defer src="./js/global.js"></script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
|
|
@ -14,14 +14,14 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{jobs_total}}
|
||||
{{jobs|length}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
JOBS ERROS
|
||||
JOBS ERRORS
|
||||
</p>
|
||||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
|
@ -29,18 +29,6 @@ url_for(request.endpoint)[1:].split("/")[-1].strip() %}
|
|||
{{jobs_errors}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
AUTO UPDATE
|
||||
</p>
|
||||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
2 mins
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end info -->
|
||||
|
||||
|
|
|
@ -122,6 +122,39 @@ url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
|
|||
/>
|
||||
</div>
|
||||
<!-- end refresh delay input -->
|
||||
<!-- refresh inp -->
|
||||
<div
|
||||
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"
|
||||
>
|
||||
Update Delay
|
||||
</h5>
|
||||
<div checkbox-handler="live-update" class="relative mb-7 md:mb-0">
|
||||
<input
|
||||
id="live-update"
|
||||
name="live-update"
|
||||
default-method="default"
|
||||
default-value="no"
|
||||
class="z-0 relative cursor-pointer dark:border-slate-600 dark:bg-slate-700 z-10 checked:z-0 w-5 h-5 ease text-base rounded-1.4 checked:bg-primary checked:border-primary dark:checked:bg-primary dark:checked:border-primary duration-250 float-left mt-1 appearance-none border border-gray-300 bg-white bg-contain bg-center bg-no-repeat align-top transition-all disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 disabled:text-gray-700 dark:disabled:text-gray-300"
|
||||
type="checkbox"
|
||||
pattern="^(yes|no)$"
|
||||
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 inp-->
|
||||
|
||||
<div class="col-span-12 w-full justify-center flex mt-2">
|
||||
<button
|
||||
|
|
|
@ -301,8 +301,7 @@
|
|||
<!-- end default anchor -->
|
||||
|
||||
<!-- plugin list -->
|
||||
{% set plugins = config["CONFIG"].get_plugins() %} {% if
|
||||
plugins_pages_count != 0 %}
|
||||
{% set plugins = config["CONFIG"].get_plugins() %}
|
||||
<div>
|
||||
<ul>
|
||||
<li class="w-full mt-4">
|
||||
|
@ -342,7 +341,6 @@
|
|||
</ul>
|
||||
<!-- end plugin list -->
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end list items -->
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{% extends "base.html" %} {% block content %}{% set current_endpoint =
|
||||
url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %} {% set
|
||||
plugins = config["CONFIG"].get_plugins() %}
|
||||
url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
|
||||
<!-- info -->
|
||||
<div
|
||||
class="p-4 col-span-12 md:col-span-5 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
|
@ -15,7 +14,7 @@ plugins = config["CONFIG"].get_plugins() %}
|
|||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_total}}
|
||||
{{plugins|length}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center my-4">
|
||||
|
|
|
@ -26,6 +26,28 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-start items-center gap-x-4 gap-y-2 mb-2">
|
||||
<h5
|
||||
class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0"
|
||||
>
|
||||
CONFIGS
|
||||
</h5>
|
||||
<!-- search inpt-->
|
||||
<div
|
||||
class="flex relative col-span-12 sm:col-span-6 lg:col-span-4 3xl:col-span-3"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="settings-filter"
|
||||
name="settings-filter"
|
||||
class="col-span-12 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"
|
||||
pattern="(.*?)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<!-- end search inpt-->
|
||||
</div>
|
||||
{% include "settings_tabs.html" %}
|
||||
<!-- new and edit form -->
|
||||
<form
|
||||
|
|
|
@ -336,7 +336,7 @@
|
|||
<button
|
||||
services-action="edit"
|
||||
type="button"
|
||||
service-name="{{service["SERVER_NAME"]['value']}}"
|
||||
services-name="{{service["SERVER_NAME"]['value']}}"
|
||||
|
||||
class="dark:brightness-90 z-20 mx-1 bg-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
|
||||
>
|
||||
|
@ -354,7 +354,7 @@
|
|||
<button
|
||||
services-action="delete"
|
||||
type="button"
|
||||
service-name="{{service["SERVER_NAME"]['value']}}"
|
||||
services-name="{{service["SERVER_NAME"]['value']}}"
|
||||
class="dark:brightness-90 z-20 mx-1 bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md"
|
||||
>
|
||||
<svg
|
||||
|
|
|
@ -6,20 +6,21 @@
|
|||
<!-- general container -->
|
||||
<div {{current_endpoint}}-item="general" class="w-full">
|
||||
<!-- general conf -->
|
||||
<div class="col-span-12">
|
||||
<div class="col-span-12" setting-header>
|
||||
<h5 class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0">GENERAL</h5>
|
||||
<div class="transition duration-300 ease-in-out dark:opacity-90 ml-2 text-sm mb-2 dark:text-gray-400">
|
||||
General config : HTTP, DNS, LOG, API...
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
{{current_endpoint}}-settings
|
||||
class="w-full grid grid-cols-12"
|
||||
id="edit-{{current_endpoint}}-general"
|
||||
>
|
||||
{% for
|
||||
setting, value in config["CONFIG"].get_settings().items() %} {%
|
||||
if current_endpoint == "global-config" and value["context"] == "global" and "label" in value %}
|
||||
<div
|
||||
<div setting-container
|
||||
class="mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:col-span-4 xl:mx-4 2xl:my-2 3xl:col-span-3"
|
||||
id="form-edit-{{current_endpoint}}-{{ value["id"] }}"
|
||||
>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
class="hidden w-full"
|
||||
>
|
||||
<!-- title and desc -->
|
||||
<div class="col-span-12">
|
||||
<div class="col-span-12" setting-header>
|
||||
<h5
|
||||
class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0"
|
||||
>
|
||||
|
@ -30,7 +30,7 @@
|
|||
{% 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
|
||||
<div setting-container
|
||||
class="
|
||||
mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
|
||||
id="form-edit-{{current_endpoint}}-{{ value["id"] }}">
|
||||
|
@ -208,7 +208,7 @@
|
|||
|
||||
<!-- 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 ">
|
||||
<div multiple-handler class="flex items-center mx-0 sm:mx-4 md:mx-6 md:my-3 my-2 2xl:mx-6 2xl:my-3 col-span-12 ">
|
||||
<h5
|
||||
class="transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300"
|
||||
>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<button
|
||||
{{current_endpoint}}-item-handler="general"
|
||||
type="button"
|
||||
class=" border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 border my-1 relative inline-block px-3 py-3 font-bold text-center uppercase align-middle transition-all rounded-none cursor-pointer bg-white hover:bg-gray-100 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
|
||||
class=" brightness-75 border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 border my-1 relative inline-block px-3 py-3 font-bold text-center uppercase align-middle transition-all rounded-none cursor-pointer bg-white hover:bg-gray-100 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
|
||||
>
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<!-- text and icon -->
|
||||
|
@ -47,7 +47,7 @@
|
|||
<button
|
||||
{{current_endpoint}}-item-handler="{{ plugin['id'] }}"
|
||||
type="button"
|
||||
class=" border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 border my-1 relative inline-block px-3 py-3 font-bold text-center uppercase align-middle transition-all rounded-none cursor-pointer bg-white hover:bg-gray-100 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
|
||||
class="{% if current_endpoint == 'service' and loop.first %} brightness-75 {% endif %} border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 border my-1 relative inline-block px-3 py-3 font-bold text-center uppercase align-middle transition-all rounded-none cursor-pointer bg-white hover:bg-gray-100 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md"
|
||||
>
|
||||
<div clas="w-full flex justify-between items-center">
|
||||
<!-- text and icon -->
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from datetime import datetime
|
||||
from typing import List
|
||||
from bs4 import Tag
|
||||
import magic
|
||||
import os
|
||||
|
||||
|
@ -91,6 +90,8 @@ def path_to_dict(
|
|||
):
|
||||
with open(path, "rb") as f:
|
||||
d["content"] = f.read().decode("utf-8")
|
||||
else:
|
||||
d["content"] = "Download file to view content"
|
||||
else:
|
||||
config_types = [
|
||||
"http",
|
||||
|
|
Loading…
Reference in New Issue