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

This commit is contained in:
fl0ppy-d1sk 2024-02-29 17:33:59 +01:00
commit 6049068f53
No known key found for this signature in database
GPG key ID: 93EE47CC3D061500
33 changed files with 708 additions and 329 deletions

View file

@ -7,6 +7,7 @@
- [BUGFIX] Fix Bad behavior whitelist check in access phase
- [BUGFIX] Fix ModSecurity FP on antibot page
- [BUGFIX] Fix Whitelist core plugin missing a check for empty server_name in multisite mode
- [BUGFIX] Fix Templator missing some common configs
- [LINUX] Add logrotate support for the logs
- [UI] Add bans management page in the web UI
- [UI] Add blocked requests page in the web UI

View file

@ -53,6 +53,7 @@ try:
data["integration"] = data["integration"].lower()
data["database"] = f"{db.database_uri.split(':')[0].split('+')[0]}/{database_version}"
data["service_number"] = str(len(services))
data["draft_service_number"] = 0
data["python_version"] = version.split(" ")[0]
data["use_ui"] = "no"
@ -61,12 +62,25 @@ try:
for server in services:
if db_config.get(f"{server}_USE_UI", db_config.get("USE_UI", {"value": "no"}))["value"] == "yes":
data["use_ui"] = "yes"
break
if db_config.get(f"{server}_IS_DRAFT", db_config.get("IS_DRAFT", {"value": "no"}))["value"] == "yes":
data["draft_service_number"] += 1
# Singlesite case
elif db_config.get("USE_UI", {"value": "no"})["value"] == "yes":
data["use_ui"] = "yes"
else:
if db_config.get("USE_UI", {"value": "no"})["value"] == "yes":
data["use_ui"] = "yes"
if db_config.get("IS_DRAFT", {"value": "no"})["value"] == "yes":
data["draft_service_number"] = 1
data["draft_service_number"] = str(data["draft_service_number"])
data["external_plugins"] = []
data["pro_plugins"] = []
for plugin in db.get_plugins():
if plugin["type"] == "external":
data["external_plugins"].append(f"{plugin['id']}/{plugin['version']}")
elif plugin["type"] == "pro":
data["pro_plugins"].append(f"{plugin['id']}/{plugin['version']}")
data["external_plugins"] = [f"{plugin['id']}/{plugin['version']}" for plugin in db.get_plugins(_type="external")]
data["os"] = {
"name": "Linux",
"version": "Unknown",

View file

@ -34,7 +34,7 @@ from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from jobs import get_os_info, get_integration, get_version # type: ignore
API_ENDPOINT = "http://api:8080/pro"
API_ENDPOINT = "https://api.bunkerweb.io/pro"
TMP_DIR = Path(sep, "var", "tmp", "bunkerweb", "pro", "plugins")
PRO_PLUGINS_DIR = Path(sep, "etc", "bunkerweb", "pro", "plugins")
logger = setup_logger("Jobs.download-pro-plugins", getenv("LOG_LEVEL", "INFO"))
@ -102,6 +102,10 @@ try:
logger.info("🚀 Your BunkerWeb Pro license is valid, checking if there are new or updated pro plugins...")
db.set_is_pro(True)
db.set_pro_expire("")
db.set_pro_status("valid")
db.set_pro_overlapped(False)
db.set_pro_services("")
with BytesIO(resp.content) as plugin_content:
with ZipFile(plugin_content) as zf:
@ -113,6 +117,10 @@ try:
logger.warning(f"{message}, only checking if there are new or updated info about pro plugins...")
db.set_is_pro(False)
db.set_pro_expire("")
db.set_pro_status("invalid")
db.set_pro_overlapped(False)
db.set_pro_services("")
plugins = resp.json()
for plugin in plugins["data"]:

View file

@ -239,6 +239,102 @@ class Database:
return ""
def set_pro_expire(self, value: str) -> str:
"""Set the pro_expire value"""
with self.__db_session() as session:
try:
metadata = session.query(Metadata).get(1)
if not metadata:
return "The metadata are not set yet, try again"
metadata.pro_expire = value
session.commit()
except BaseException:
return format_exc()
return ""
def get_pro_expire(self) -> str:
with self.__db_session() as session:
try:
metadata = session.query(Metadata).with_entities(Metadata.pro_expire).filter_by(id=1).first()
return metadata.pro_expire
except (ProgrammingError, OperationalError):
return ""
def set_pro_services(self, value: str) -> str:
"""Set the pro_services value"""
with self.__db_session() as session:
try:
metadata = session.query(Metadata).get(1)
if not metadata:
return "The metadata are not set yet, try again"
metadata.pro_services = value
session.commit()
except BaseException:
return format_exc()
return ""
def get_pro_services(self) -> str:
with self.__db_session() as session:
try:
metadata = session.query(Metadata).with_entities(Metadata.pro_services).filter_by(id=1).first()
return metadata.pro_services
except (ProgrammingError, OperationalError):
return ""
def set_pro_overlapped(self, value: bool = False) -> str:
"""Set the pro_overlapped value"""
with self.__db_session() as session:
try:
metadata = session.query(Metadata).get(1)
if not metadata:
return "The metadata are not set yet, try again"
metadata.pro_overlapped = value
session.commit()
except BaseException:
return format_exc()
return ""
def get_pro_overlapped(self) -> str:
with self.__db_session() as session:
try:
metadata = session.query(Metadata).with_entities(Metadata.pro_overlapped).filter_by(id=1).first()
return metadata.pro_overlapped
except (ProgrammingError, OperationalError):
return ""
def set_pro_status(self, value: str) -> str:
"""Set the pro_status value"""
with self.__db_session() as session:
try:
metadata = session.query(Metadata).get(1)
if not metadata:
return "The metadata are not set yet, try again"
metadata.pro_status = value
session.commit()
except BaseException:
return format_exc()
return ""
def get_pro_status(self) -> str:
with self.__db_session() as session:
try:
metadata = session.query(Metadata).with_entities(Metadata.pro_status).filter_by(id=1).first()
return metadata.pro_status
except (ProgrammingError, OperationalError):
return ""
def is_scheduler_first_start(self) -> bool:
"""Check if it's the scheduler's first start"""
with self.__db_session() as session:
@ -290,14 +386,25 @@ class Database:
def get_metadata(self) -> Dict[str, str]:
"""Get the metadata from the database"""
data = {"version": "1.5.6", "integration": "unknown", "database_version": "Unknown", "is_pro": False}
data = {"version": "1.5.6", "integration": "unknown", "database_version": "Unknown", "is_pro": "no"}
database = self.database_uri.split(":")[0].split("+")[0]
with self.__db_session() as session:
with suppress(ProgrammingError, OperationalError):
data["database_version"] = (session.execute(text("SELECT sqlite_version()" if database == "sqlite" else "SELECT VERSION()")).first() or ["unknown"])[0]
metadata = session.query(Metadata).with_entities(Metadata.version, Metadata.integration, Metadata.is_pro).filter_by(id=1).first()
metadata = session.query(Metadata).with_entities(Metadata.version, Metadata.integration, Metadata.is_pro, Metadata.pro_expire, Metadata.pro_services, Metadata.pro_overlapped, Metadata.pro_status).filter_by(id=1).first()
if metadata:
data.update({"version": metadata.version, "integration": metadata.integration, "is_pro": metadata.is_pro})
print("METADATA :", metadata.is_pro, metadata.pro_expire, metadata.pro_services, metadata.pro_overlapped, metadata.pro_status, flush=True)
data.update(
{
"version": metadata.version,
"integration": metadata.integration,
"is_pro": "yes" if metadata.is_pro else "no",
"pro_expire": metadata.pro_expire,
"pro_services": metadata.pro_services,
"pro_overlapped": metadata.pro_overlapped,
"pro_status": metadata.pro_status,
}
)
return data
@ -992,6 +1099,9 @@ class Database:
if is_multisite:
for service in services:
config[f"{service.id}_IS_DRAFT"] = "yes" if service.is_draft else "no"
if methods:
config[f"{service.id}_IS_DRAFT"] = {"value": config[f"{service.id}_IS_DRAFT"], "global": False, "method": "default"}
checked_settings = []
for key, value in deepcopy(config).items():
original_key = key

View file

@ -270,6 +270,10 @@ class Metadata(Base):
id = Column(Integer, primary_key=True, default=1)
is_initialized = Column(Boolean, nullable=False)
is_pro = Column(Boolean, default=False, nullable=False)
pro_expire = Column(String(32), default="", nullable=False)
pro_status = Column(String(32), default="", nullable=False)
pro_services = Column(String(256), default="", nullable=False)
pro_overlapped = Column(Boolean, default=False, nullable=False)
first_config_saved = Column(Boolean, nullable=False)
autoconf_loaded = Column(Boolean, default=False, nullable=True)
scheduler_first_start = Column(Boolean, nullable=True)

View file

@ -18,6 +18,7 @@ from jinja2 import Environment, FileSystemLoader
class Templator:
def __init__(self, templates: str, core: str, plugins: str, pro_plugins: str, output: str, target: str, config: Dict[str, Any]):
self.__templates = templates
self.__global_templates = [basename(template) for template in glob(join(self.__templates, "*", "*.conf"))]
self.__core = core
self.__plugins = plugins
self.__pro_plugins = pro_plugins
@ -36,9 +37,9 @@ class Templator:
def __load_jinja_env(self) -> Environment:
searchpath = [self.__templates]
for subpath in glob(join(self.__core, "*")) + glob(join(self.__plugins, "*")) + glob(join(self.__pro_plugins, "*")):
for subpath in glob(join(self.__core, "*", "confs")) + glob(join(self.__plugins, "*", "confs")) + glob(join(self.__pro_plugins, "*", "confs")):
if Path(subpath).is_dir():
searchpath.append(join(subpath, "confs"))
searchpath.append(subpath)
return Environment(loader=FileSystemLoader(searchpath=searchpath), lstrip_blocks=True, trim_blocks=True)
def __find_templates(self, contexts) -> List[str]:
@ -87,18 +88,7 @@ class Templator:
if server_key not in self.__config:
config["SERVER_NAME"] = server
for root_conf in (
"server.conf",
"access-lua.conf",
"ssl-certificate-lua.conf",
"header-lua.conf",
"init-lua.conf",
"log-lua.conf",
"set-lua.conf",
"log-stream-lua.conf",
"preread-stream-lua.conf",
"server-stream.conf",
):
for root_conf in self.__global_templates:
if template.endswith(root_conf):
name = basename(template)
break

View file

@ -330,10 +330,15 @@ def generate_nonce():
@app.context_processor
def inject_variables():
# check that is value is in tuple
return dict(
dark_mode=app.config["DARK_MODE"],
script_nonce=app.config["SCRIPT_NONCE"],
is_pro_version=db.get_metadata()["is_pro"],
pro_status=db.get_metadata()["pro_status"],
pro_services=db.get_metadata()["pro_services"],
pro_expire=db.get_metadata()["pro_expire"],
pro_overlapped=db.get_metadata()["pro_overlapped"],
plugins=app.config["CONFIG"].get_plugins(),
)
@ -814,7 +819,7 @@ def services():
"full_value": service["SERVER_NAME"]["value"],
"method": service["SERVER_NAME"]["method"],
},
"IS_DRAFT": service.pop("IS_DRAFT", "no"),
"IS_DRAFT": service.pop("IS_DRAFT", {"value": "no"})["value"],
"USE_REVERSE_PROXY": service["USE_REVERSE_PROXY"],
"SERVE_FILES": service["SERVE_FILES"],
"REMOTE_PHP": service["REMOTE_PHP"],

File diff suppressed because one or more lines are too long

View file

@ -534,7 +534,7 @@ const setSelect = new Select();
const setPassword = new Password();
const setDisabledPop = new DisabledPop();
const setNews = new News();
// const setBanner = new Banner();
const setBanner = new Banner();
const setDarkM = new darkMode();
const setFlash = new FlashMsg();
const setLoader = new Loader();

View file

@ -27,20 +27,25 @@ class Multiple {
}
const setPopover = new Popover("main", "global-config");
const setTabs = new Tabs("[global-config-tabs]", "global-config");
const setTabs = new Tabs(
document.querySelector("[data-global-config-tabs-container]"),
document.querySelector("[data-global-config-plugins-container]"),
);
const format = new FormatValue();
const setMultiple = new Multiple("global-config");
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[data-service-content='settings']",
"keyword",
document.querySelector("[data-global-config-tabs-container]"),
document.querySelector("[data-global-config-plugins-container]"),
);
const checkServiceModalKeyword = new CheckNoMatchFilter(
document.querySelector("input#settings-filter"),
document.querySelector("input#keyword"),
"input",
document
.querySelector("[data-global-config-tabs]")
.querySelectorAll("[data-tab-handler]"),
.querySelector("[data-global-config-plugins-container]")
.querySelectorAll("[data-plugin-item]"),
document.querySelector("[data-global-config-form]"),
document.querySelector("[data-global-config-nomatch]"),
);

View file

@ -188,6 +188,8 @@ class FetchLogs {
this.logListContainer = document.querySelector(
`[data-${this.prefix}-list]`,
);
this.noRunLogEl = document.querySelector("[data-logs-no-run]");
this.logsCard = document.querySelector("[data-logs-card]");
this.submitDate = document.querySelector("button[data-submit-date]");
this.submitLive = document.querySelector("button[data-submit-live]");
@ -372,6 +374,8 @@ class FetchLogs {
this.logListContainer.appendChild(logContainer);
});
this.showLogRunEls();
//force scroll when no live update
const logListEl = document.querySelector(`[data-${this.prefix}-list]`);
logListEl.scrollTop = logListEl.scrollHeight;
@ -404,6 +408,8 @@ class FetchLogs {
this.logListContainer.appendChild(logContainer);
});
this.showLogRunEls();
//if live update, refetch to last update every defined delay
if (this.submitLive.getAttribute("data-submit-live") === "yes") {
setTimeout(() => {
@ -411,6 +417,11 @@ class FetchLogs {
}, this.updateDelay);
}
}
showLogRunEls() {
this.noRunLogEl.classList.add("hidden");
this.logsCard.classList.remove("hidden");
}
}
class Filter {

View file

@ -1406,14 +1406,19 @@ class Filter {
const setDropdown = new Dropdown();
const setFilter = new Filter();
const setTabs = new Tabs(
document.querySelector("[data-services-tabs]"),
document.querySelector("[data-services-modal-form]"),
);
const setPopover = new Popover();
const setTabs = new Tabs();
const setModal = new ServiceModal();
const format = new FormatValue();
const setFilterGlobal = new FilterSettings(
"settings-filter",
"[data-service-content='settings']",
document.querySelector("[data-services-tabs]"),
document.querySelector("[data-services-modal-form]"),
);
const setMultiple = new Multiple("services");
@ -1422,8 +1427,8 @@ const checkServiceModalKeyword = new CheckNoMatchFilter(
document.querySelector("input#settings-filter"),
"input",
document
.querySelector("[data-services-tabs-desktop]")
.querySelectorAll("[data-tab-handler]"),
.querySelector("[data-services-modal-form]")
.querySelectorAll("[data-plugin-item]"),
document.querySelector("[data-services-modal-form]"),
document.querySelector("[data-services-nomatch]"),
);

View file

@ -44,101 +44,105 @@ class Popover {
}
class Tabs {
constructor() {
constructor(tabContainer, contentContainer) {
this.tabContainer = tabContainer;
this.contentContainer = contentContainer;
this.tabArrow = tabContainer
.querySelector("[data-tab-dropdown-btn]")
.querySelector("[data-tab-dropdown-arrow]");
this.init();
}
init() {
window.addEventListener("click", (e) => {
try {
if (
e.target.closest("button").hasAttribute("data-tab-handler") ||
e.target.closest("button").hasAttribute("data-tab-handler-mobile")
) {
if (e.target.closest("button").hasAttribute("data-tab-handler")) {
//get needed data
const tab = e.target.closest("button");
const tabAtt =
tab.getAttribute("data-tab-handler") ||
tab.getAttribute("data-tab-handler-mobile");
const container = tab.closest("div[data-service-content]");
const tabAtt = tab.getAttribute("data-tab-handler");
// change style
this.resetTabsStyle(container);
this.highlightClicked(container, tabAtt);
this.resetTabsStyle();
this.highlightClicked(tabAtt);
//show content
this.hideAllSettings(container);
this.showSettingClicked(container, tabAtt);
this.hideAllSettings();
this.showSettingClicked(tabAtt);
//close dropdown and change btn textcontent on mobile
this.setDropBtnText(container, tabAtt);
this.closeDropdown(container);
this.setDropBtnText(tabAtt);
this.closeDropdown();
}
} catch (err) {}
} catch (e) {}
try {
if (e.target.closest("button").hasAttribute("data-tab-dropdown-btn")) {
const dropBtn = e.target.closest("button");
const container = dropBtn.closest("div[data-service-content]");
this.toggleDropdown(container);
this.toggleDropdown();
}
} catch (err) {}
});
}
resetTabsStyle(container) {
//reset desktop style
const tabsDesktop = container.querySelectorAll("button[data-tab-handler]");
tabsDesktop.forEach((tab) => {
tab.classList.remove("active");
});
//reset mobile style
const tabsMobile = container.querySelectorAll(
"button[data-tab-handler-mobile]",
resetTabsStyle() {
const tabsMobile = this.tabContainer.querySelectorAll(
"button[data-tab-handler]",
);
tabsMobile.forEach((tab) => {
tab.classList.remove("active");
});
}
highlightClicked(container, tabAtt) {
//desktop case
const tabDesktop = container.querySelector(
highlightClicked(tabAtt) {
const tabMobile = this.tabContainer.querySelector(
`button[data-tab-handler='${tabAtt}']`,
);
tabDesktop.classList.add("active");
//mobile case
const tabMobile = container.querySelector(
`button[data-tab-handler-mobile='${tabAtt}']`,
);
tabMobile.classList.add("active");
}
hideAllSettings(container) {
const plugins = container.querySelectorAll("[data-plugin-item]");
hideAllSettings() {
const plugins =
this.contentContainer.querySelectorAll("[data-plugin-item]");
plugins.forEach((plugin) => {
plugin.classList.add("hidden");
});
}
showSettingClicked(container, tabAtt) {
const plugin = container.querySelector(`[data-plugin-item='${tabAtt}']`);
showSettingClicked(tabAtt) {
const plugin = this.contentContainer.querySelector(
`[data-plugin-item='${tabAtt}']`,
);
plugin.classList.remove("hidden");
}
setDropBtnText(container, tabAtt) {
const dropBtn = container.querySelector("[data-tab-dropdown-btn]");
setDropBtnText(tabAtt) {
const dropBtn = this.tabContainer.querySelector("[data-tab-dropdown-btn]");
dropBtn.querySelector("span").textContent = tabAtt;
}
closeDropdown(container) {
const dropdown = container.querySelector("[data-tab-dropdown]");
closeDropdown() {
const dropdown = this.tabContainer.querySelector("[data-tab-dropdown]");
dropdown.classList.add("hidden");
dropdown.classList.remove("flex");
this.updateTabArrow();
}
toggleDropdown(container) {
const dropdown = container.querySelector("[data-tab-dropdown]");
toggleDropdown() {
const dropdown = this.tabContainer.querySelector("[data-tab-dropdown]");
dropdown.classList.toggle("hidden");
dropdown.classList.toggle("flex");
this.updateTabArrow();
}
updateTabArrow() {
const dropdown = this.tabContainer.querySelector("[data-tab-dropdown]");
if (dropdown.classList.contains("hidden")) {
this.tabArrow.classList.remove("rotate-180");
}
if (dropdown.classList.contains("flex")) {
this.tabArrow.classList.add("rotate-180");
}
}
}
@ -159,11 +163,11 @@ class FormatValue {
}
class FilterSettings {
constructor(inputID, container) {
constructor(inputID, tabContainer, contentContainer) {
this.input = document.querySelector(`input#${inputID}`);
//DESKTOP
this.container = document.querySelector(container);
this.deskTabs = this.container.querySelectorAll(`[data-tab-handler]`);
this.tabContainer = tabContainer;
this.contentContainer = contentContainer;
this.tabsEls = this.tabContainer.querySelectorAll(`[data-tab-handler]`);
this.init();
}
@ -172,8 +176,9 @@ class FilterSettings {
this.resetFilter();
//get inp format
const inpValue = this.input.value.trim().toLowerCase();
//loop all tabs
this.deskTabs.forEach((tab) => {
this.tabsEls.forEach((tab) => {
//get settings of tabs except multiples
const settings = this.getSettingsFromTab(tab);
@ -196,31 +201,65 @@ class FilterSettings {
//case no setting match, hidden tab and content
if (settingCount === hiddenCount) {
const tabName = tab.getAttribute(`data-tab-handler`);
//hide mobile and desk tabs
tab.classList.add("hidden");
this.container
.querySelector(`[data-tab-handler-mobile="${tabName}"]`)
.classList.add("hidden");
this.container
tab.classList.add("!hidden");
this.contentContainer
.querySelector(`[data-plugin-item=${tabName}]`)
.querySelector("[data-setting-header]")
.classList.add("hidden");
}
});
// check current tabs states
let isAllHidden = true;
let firstNotHiddenEl = null;
for (let i = 0; i < this.tabsEls.length; i++) {
const tab = this.tabsEls[i];
if (!tab.classList.contains("!hidden")) {
isAllHidden = false;
firstNotHiddenEl = tab;
break;
}
}
// case no tab match
if (isAllHidden) {
return (this.tabContainer.querySelector(
"[data-tab-dropdown-btn] span",
).textContent = "No match");
}
// click first not hidden tab
const currTabEl = this.tabContainer.querySelector(
"[data-tab-dropdown-btn] span",
);
const currTabName = currTabEl.textContent.toLowerCase().trim();
// case previously no match
if (currTabName.toLowerCase() === "no match") {
return firstNotHiddenEl.click();
}
const currTabBtn = this.tabContainer.querySelector(
`[data-tab-handler='${currTabName}']`,
);
if (!currTabBtn) return;
if (!currTabBtn.classList.contains("!hidden")) {
return currTabBtn.click();
}
if (currTabBtn.classList.contains("!hidden")) {
return firstNotHiddenEl.click();
}
});
}
resetFilter() {
this.deskTabs.forEach((tab) => {
this.tabsEls.forEach((tab) => {
const tabName = tab.getAttribute(`data-tab-handler`);
//hide mobile and desk tabs
tab.classList.remove("hidden");
this.container
.querySelector(`[data-tab-handler-mobile="${tabName}"]`)
.classList.remove("hidden");
this.container
tab.classList.remove("!hidden");
this.contentContainer
.querySelector(`[data-plugin-item=${tabName}]`)
.querySelector("[data-setting-header]")
.classList.remove("hidden");
const settings = this.getSettingsFromTab(tab);
settings.forEach((setting) => {
@ -231,7 +270,7 @@ class FilterSettings {
getSettingsFromTab(tabEl) {
const tabName = tabEl.getAttribute(`data-tab-handler`);
const settingContainer = this.container
const settingContainer = this.contentContainer
.querySelector(`[data-plugin-item="${tabName}"]`)
.querySelector(`[data-plugin-settings]`);
const settings = settingContainer.querySelectorAll(
@ -242,12 +281,20 @@ class FilterSettings {
}
class CheckNoMatchFilter {
constructor(input, type, elsToCheck, elContainer, noMatchEl) {
constructor(
input,
type,
elsToCheck,
elContainer,
noMatchEl,
classToCheck = "hidden",
) {
this.input = input;
this.type = type;
this.elsToCheck = elsToCheck;
this.elContainer = elContainer;
this.noMatchEl = noMatchEl;
this.classToCheck = classToCheck;
this.init();
}
@ -276,20 +323,24 @@ class CheckNoMatchFilter {
let isAllHidden = true;
for (let i = 0; i < this.elsToCheck.length; i++) {
const el = this.elsToCheck[i];
if (!el.classList.contains("hidden")) {
if (!el.classList.contains(this.classToCheck)) {
isAllHidden = false;
break;
}
}
if (isAllHidden) {
this.noMatchEl.classList.remove("hidden");
this.elContainer ? this.elContainer.classList.add("hidden") : false;
this.noMatchEl.classList.remove(this.classToCheck);
this.elContainer
? this.elContainer.classList.add(this.classToCheck)
: false;
}
if (!isAllHidden) {
this.elContainer ? this.elContainer.classList.remove("hidden") : false;
this.noMatchEl.classList.add("hidden");
this.elContainer
? this.elContainer.classList.remove(this.classToCheck)
: false;
this.noMatchEl.classList.add(this.classToCheck);
}
}, 20);
}

View file

@ -23,24 +23,28 @@
@apply hidden;
}
.separator {
@apply h-px mx-0 mt-3 mb-2 bg-transparent bg-gradient-to-r from-transparent via-black/40 to-transparent dark:bg-gradient-to-r dark:from-transparent dark:via-white dark:to-transparent;
}
.close-btn {
@apply dark:brightness-90 inline-block px-6 py-3 font-bold text-center text-red-500 border border-red-500 uppercase align-middle transition-all rounded-lg cursor-pointer dark:bg-gray-200 dark:hover:brightness-75 bg-white hover:bg-white/80 focus:bg-white/80 leading-normal ease-in tracking-tight-rem shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
@apply dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-red-500 border border-red-500 uppercase align-middle transition-all rounded-lg cursor-pointer dark:bg-gray-200 dark:hover:brightness-75 bg-white hover:bg-white/80 focus:bg-white/80 leading-normal ease-in tracking-tight-rem shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.valid-btn {
@apply 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 ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
@apply tracking-wide dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.delete-btn {
@apply 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-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
@apply tracking-wide dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.edit-btn {
@apply 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-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
@apply tracking-wide dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.info-btn {
@apply 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-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
@apply tracking-wide dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
/*----------------------------------------------*/
@ -102,43 +106,27 @@
/*---------------SETTINGS_TABS-----------------*/
/*---------------------------------------------*/
.active.settings-tabs-tab-btn {
@apply border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 border my-1 relative 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 brightness-90 z-10;
.settings-tabs-btn {
@apply dark:hover:brightness-95 dark:border-slate-600 dark:bg-slate-700 border-primary border w-full flex items-center justify-between rounded-lg hover:-translate-y-px my-1 px-4 py-2 md:px-6 md:py-3 font-bold text-center uppercase align-middle transition-all cursor-pointer bg-white hover:bg-gray-50 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md;
}
.settings-tabs-tab-btn {
@apply border-primary dark:hover:bg-slate-800 dark:border-slate-600 dark:bg-slate-700 border my-1 relative 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;
}
.settings-tabs-name {
@apply text-primary transition duration-300 ease-in-out dark:opacity-90 pl-3 pr-2 dark:text-gray-300;
}
.settings-tabs-popover-container {
@apply top-[60px] min-w-[150px] dark:brightness-90 bg-blue-500 transition z-50 rounded-md p-3 left-0 absolute;
}
.settings-tabs-popover-text {
@apply font-bold text-sm text-white m-0;
}
.settings-tabs-mobile-btn {
@apply dark:hover:brightness-95 dark:border-slate-600 dark:bg-slate-700 border-primary border w-full flex items-center justify-between rounded-lg hover:-translate-y-px my-1 px-6 py-3 font-bold text-center uppercase align-middle transition-all cursor-pointer bg-white hover:bg-gray-50 leading-normal text-sm ease-in tracking-tight-rem shadow-xs hover:shadow-md;
}
.settings-tabs-mobile-btn-text {
.settings-tabs-btn-text {
@apply transition duration-300 ease-in-out dark:opacity-90 dark:text-gray-300 text-primary;
}
.active.settings-tabs-mobile-dropdown-btn {
.active.settings-tabs-dropdown-btn {
@apply border-gray-300 dark:hover:brightness-90 hover:brightness-90 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:text-gray-300 z-[1000] dark:bg-primary bg-primary text-gray-300;
}
.first.settings-tabs-mobile-dropdown-btn {
.first.settings-tabs-dropdown-btn {
@apply border-t rounded-t border-b border-l border-r;
}
.settings-tabs-mobile-dropdown-btn {
.last.settings-tabs-dropdown-btn {
@apply rounded-b;
}
.settings-tabs-dropdown-btn {
@apply flex justify-between border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300;
}

View file

@ -88,6 +88,12 @@
<!-- end dropdown-->
</div>
<!-- end mobile tabs -->
{% set global_info = {
"message" : "You are using pro version" if is_pro_version and pro_status else "Pro version is expired" if is_pro_version and not pro_status else "You are using free version",
"link_message" : "Upgrade to pro" if not is_pro_version else "Renew my license" if is_pro_version and not pro_status else "",
"icon" : "pro" if is_pro_version and pro_status else "free"
}
%}
<div data-plugin-item="global"
class="grid grid-cols-12 w-full justify-items-center">
<div class="col-span-12">
@ -96,18 +102,12 @@
</h5>
<div class="flex justify-center items-center">
<p class="mb-0 mr-2 dark:text-gray-300">
You are using
{% if is_pro_version %}
pro
{% else %}
free
{% endif %}
version
{{global_info['message']}}
</p>
<div role="img"
aria-label="version"
class="dark:brightness-90 inline-block w-8 h-8 text-center rounded-circle bg-yellow-500">
{% if is_pro_version %}
{% if global_info['icon'] == 'pro' %}
<svg class="leading-none text-lg relative scale-[0.6]"
viewBox="0 0 48 46"
fill="none"
@ -127,12 +127,35 @@
{% endif %}
</div>
</div>
{% if not is_pro_version %}
{% if global_info['link_message'] %}
<div class="flex justify-center mt-2">
<a class="text-center font-semibold text-yellow-500 underline"
href="https://panel.bunkerweb.io/">Upgrade to pro</a>
href="https://panel.bunkerweb.io/">{{ global_info['link_message'] }}</a>
</div>
{% endif %}
{% if is_pro_version and pro_status %}
<div class="mt-2 flex flex-col justify-center items-center">
{% if pro_expire %}
<p class="my-2 mr-2 dark:text-gray-300 text-center">
Your license is valid until {{ pro_expire }}
</p>
{% endif %}
{% if pro_services %}
<p class="my-2 mr-2 dark:text-gray-300 text-center">
You can handle {{ pro_services }} services
</p>
{% endif %}
{% if pro_overlapped %}
<p class="my-2 mr-2 text-red-500 font-bold dark:brightness-95 text-center">
You have more services than allowed by your pro license. Upgrade your license or move some services to draft mode before your license lockout.
</p>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div data-plugin-item="username"

View file

@ -1,5 +1,9 @@
<!-- float button-->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if is_pro_version and pro_status and pro_overlapped %}
{% set messages = messages.append(('error', 'You have more services than allowed by your pro license. Upgrade your license or move some services to draft mode before your license lockout.'))%}
{% endif %}
<div data-flash-group
class="transition-all group group-hover hover:brightness-75 dark:hover:brightness-105 fixed top-[4.5rem] right-20 sm:right-24 xl:right-24 z-990">
<button aria-controls="sidebar-flash"

View file

@ -1,32 +1,99 @@
{% extends "base.html" %}
{% block content %}
<div data-service-content="settings"
class="col-span-12 gap-y-4 grid grid-cols-12">
<div class="p-4 col-span-12 relative flex flex-col min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div data-{{ current_endpoint }}-tabs-header class="flex flex-col xs:flex-row xs:justify-start xs:items-center gap-x-4 gap-y-2 my-3">
<h5 class="transition duration-300 ease-in-out 0 ml-2 font-bold text-md uppercase dark:text-white/90 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">
<label class="sr-only" for="settings-filter">filter settings</label>
<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>
<!-- tabs -->
{% include "settings_tabs.html" %}
<!-- end tabs-->
<div data-global-config-tabs-container
class="z-100 w-full grid grid-cols-12 h-fit max-h-100 sm:max-h-125 col-span-12 md:col-span-6 lg:col-span-4 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div data-{{ current_endpoint }}-tabs-header class="col-span-12">
<div class="flex flex-col xs:flex-row xs:justify-start xs:items-center gap-x-4 gap-y-2 my-3">
<h5 class="transition duration-300 ease-in-out 0 ml-2 font-bold text-md uppercase dark:text-white/90 mb-0">PLUGINS</h5>
</div>
{% include "settings_tabs.html" %}
</div>
</div>
<!-- filter -->
{% set filters = [
{
"type": "input",
"name": "Search",
"label": "search",
"id": "keyword",
"placeholder": "keyword",
"pattern": "(.*?)"
}
] %}
<div data-global-config-filter
class="h-fit p-4 col-span-12 md:col-span-6 lg:col-span-5 xl:col-span-4 2xl:col-span-3 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
<div class="mx-2 grid grid-cols-12 gap-x-4 gap-y-2">
{% for filter in filters %}
{% if filter['type'] == 'input' %}
<!-- search inpt-->
<div class="flex flex-col relative col-span-12">
<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">
{{ filter['name'] }}
</h5>
<label for="{{ filter['id'] }}" class="sr-only">{{ filter['label'] }}</label>
<input type="text"
id="{{ filter['id'] }}"
name="{{ filter['id'] }}"
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="{{ filter['placeholder'] }}"
pattern="{{ filter['pattern'] }}"
required />
</div>
<!-- end search inpt-->
{% endif %}
{% if filter['type'] == 'select' %}
<!-- select -->
<div class="flex flex-col relative col-span-12">
<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">
{{ filter['name'] }}
</h5>
<button aria-controls="filter-{{ filter['id'] }}"
data-global-config-setting-select="{{ filter['id'] }}"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500">
<span aria-description="current filter state value"
id="global-config-{{ filter['id'] }}"
data-name="global-config-{{ filter['id'] }}"
data-global-config-setting-select-text="{{ filter['id'] }}">{{ filter['value'] }}</span>
<!-- chevron -->
<svg data-global-config-setting-select="{{ filter['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>
</button>
<!-- end chevron -->
<!-- dropdown-->
<div id="filter-{{ filter['id'] }}"
role="listbox"
data-global-config-setting-select-dropdown="{{ filter['id'] }}"
class="hidden z-100 absolute h-full flex-col w-full translate-y-16">
{% for value in filter['values'] %}
<button role="option"
data-global-config-setting-select-dropdown-btn="{{ filter['id'] }}"
value="{{ value }}"
class="{% if loop.first %}dark:bg-primary bg-primary text-gray-300 border-t rounded-t {% else %} bg-white {% endif %} {% if loop.last %}rounded-b{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300">
{{ value }}
</button>
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end select -->
{% endif %}
{% endfor %}
</div>
</div>
<!-- end filter -->
<div data-global-config-plugins-container
class="col-span-12 gap-y-4 grid grid-cols-12">
<!-- form global conf -->
<form data-global-config-form
id="form-edit-global-configs"
method="POST"
class="flex flex-col justify-between overflow-hidden overflow-y-auto max-h-135 md:max-h-160 dark:brightness-110 col-span-12 break-words bg-white shadow-xl p-4 dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
class="flex flex-col justify-between overflow-hidden overflow-y-auto 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() }}" />
<!-- plugin item -->
{% include "settings_plugins.html" %}

View file

@ -32,7 +32,7 @@
<link rel="stylesheet" type="text/css" href="./css/flatpickr.css" />
<link rel="stylesheet" type="text/css" href="./css/flatpickr.dark.css" />
<script defer src="./js/utils/flatpickr.js" nonce="{{ script_nonce }}"></script>
<script defer src="./js/logs.js" nonce="{{ script_nonce }}"></script>
<script type="module" src="./js/logs.js" nonce="{{ script_nonce }}"></script>
<link rel="stylesheet"
type="text/css"
href="./css/datepicker-foundation.css" />

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block content %}
{% set cards = [
{'name' : 'Version', 'title' : 'PRO' if is_pro_version else 'FREE', 'link' : '#' if is_pro_version else 'https://github.com/bunkerity/bunkerweb', 'subtitle' : 'all features available' if is_pro_version else 'upgrade to pro', 'subtitle_color' : 'success' if is_pro_version else 'warning' },
{'name' : 'Version', 'title' : 'PRO' if is_pro_version and pro_status else 'EXPIRED' if is_pro_version and not pro_status else 'FREE', 'link' : '#' if is_pro_version and pro_status else 'https://github.com/bunkerity/bunkerweb', 'subtitle' : 'all features available' if is_pro_version and pro_status else 'renew your license' if is_pro_version and not pro_status else 'upgrade to pro', 'subtitle_color' : 'success' if is_pro_version and pro_status else 'warning' },
{'name' : 'Version number', 'title' : version, 'link' : 'https://github.com/bunkerity/bunkerweb', 'subtitle' : "couldn'd fint remote" if not remote_version else "latest version" if remote_version and check_version else 'Update to ' + remote_version , 'subtitle_color' : "error" if not remote_version else "success" if remote_version and check_version else 'warning'},
{'name' : 'Instances', 'title' : instances_number, 'link' : 'loading?next=' + url_for('instances') , 'subtitle' : instance_health_count|string + ' / ' + instances_number|string + ' is working' , 'subtitle_color' : "info",},
{'name' : 'Services', 'title' : services_number, 'link' : 'loading?next=' + url_for('services') , 'subtitle' : services_ui_count|string + ' ui, ' + services_scheduler_count|string + ' scheduler, ' + services_autoconf_count|string + ' autoconf ' , 'subtitle_color' : "info"},
@ -23,7 +23,7 @@
<div role="img"
aria-label="version"
class="home-card-svg-container {{ card['name'].replace(' ', '-') |lower }}">
{% if card['name'] == "Version" and is_pro_version %}
{% if card['name'] == "Version" and is_pro_version and pro_status %}
<svg class="leading-none text-lg relative scale-[0.6]"
viewBox="0 0 48 46"
fill="none"
@ -32,7 +32,7 @@
<path class="fill-white" d="M21.2803 45.5H26.7198C33.8098 45.5 37.3545 45.5 39.7198 43.383C40.7523 42.4588 41.4057 40.793 41.8775 38.625H6.1224C6.59413 40.793 7.24783 42.4588 8.2802 43.383C10.6454 45.5 14.1903 45.5 21.2803 45.5Z" fill="#1C274C" />
</svg>
{% endif %}
{% if card['name'] == "Version" and not is_pro_version %}
{% if card['name'] == "Version" and not is_pro_version or card['name'] == "Version" and is_pro_version and not pro_status %}
<svg class="leading-none fill-white text-yellow-500 text-lg relative scale-[0.6]"
xmlns="http://www.w3.org/2000/svg"
fill="none"

View file

@ -11,7 +11,7 @@
Select instance
</h5>
<button data-logs-setting-select="instances"
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500">
class="max-w-[300px] disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500">
<span id="logs-instance"
data-name="logs-instance"
data-logs-setting-select-text="instances">
@ -42,7 +42,7 @@
<button data-logs-setting-select-dropdown-btn="instances"
value="{{ instance.name }}"
data-_type="{{ instance._type }}"
class="{% if loop.index == 1 %} border-t rounded-t {% endif %} {% if loop.index == loop.length %}rounded-b{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300">
class="{% if loop.first %}dark:bg-primary bg-primary text-gray-300 border-t rounded-t {% else %} bg-white {% endif %} {% if loop.last %}rounded-b{% endif %} border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300">
{{ instance.name }}
</button>
{% endfor %}
@ -50,6 +50,7 @@
<!-- end dropdown-->
</div>
<!-- end select instance -->
<h5 class="col-span-12 mb-1 mt-2 text-[1.1rem] font-bold dark:text-white/90">Date options</h5>
<!-- from date input -->
<div class="flex flex-col relative col-span-12 sm:col-span-6">
<h5 class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300">
@ -98,24 +99,11 @@
</div>
</div>
<!-- end to date input -->
<!-- refresh delay input -->
<div class="flex flex-col relative col-span-12 sm:col-span-6">
<h5 class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300">
Update delay (in seconds)
</h5>
<input type="number"
id="update-delay"
name="update-delay"
class="disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700 col-span-12 sm:col-span-6 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-3 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="2"
data-pattern="(.*?)"
required />
</div>
<!-- end refresh delay input -->
<h5 class="col-span-12 mb-1 mt-2 text-[1.1rem] font-bold dark:text-white/90">Live options</h5>
<!-- refresh inp -->
<div class="flex flex-col relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3">
<div class="flex flex-col relative col-span-12 sm:col-span-6 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">
Live update
Listen live
</h5>
<div data-checkbox-handler="live-update" class="relative mb-7 md:mb-0">
<input id="live-update"
@ -136,6 +124,22 @@
</div>
</div>
<!-- end refresh inp-->
<!-- refresh delay input -->
<div class="flex flex-col relative col-span-12 sm:col-span-6">
<h5 class="my-1 transition duration-300 ease-in-out dark:opacity-90 text-sm sm:text-md font-bold m-0 dark:text-gray-300">
Update delay (in seconds)
</h5>
<input type="number"
id="update-delay"
name="update-delay"
class="disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700 col-span-12 sm:col-span-6 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="default 2"
data-pattern="(.*?)"
required />
</div>
<!-- end refresh delay input -->
<div class="col-span-12 w-full justify-center flex mt-2">
<button data-submit-date
id="submit-data"
@ -183,7 +187,7 @@
{% for filter in filters %}
{% if filter['type'] == 'input' %}
<!-- search inpt-->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<div class="flex flex-col relative col-span-12">
<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">
{{ filter['name'] }}
</h5>
@ -200,7 +204,7 @@
{% endif %}
{% if filter['type'] == 'select' %}
<!-- select -->
<div class="flex flex-col relative col-span-12 md:col-span-6">
<div class="flex flex-col relative col-span-12">
<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">
{{ filter['name'] }}
</h5>
@ -242,7 +246,23 @@
</div>
</div>
<!-- end filter -->
<div class="w-full overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div data-logs-no-run
class="w-full overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words">
<div class="col-span-12 flex flex-col justify-center items-center h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="mb-2 w-8 h-8 stroke-white">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607ZM10.5 7.5v6m3-3h-6" />
</svg>
<h5 class="font-bold dark:text-white/90 mx-2 text-white">No logs to show</h5>
</div>
</div>
<div data-logs-card class="hidden w-full overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="col-span-12">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">LOGS</h5>
</div>

View file

@ -21,6 +21,11 @@
</p>
</div>
{% endfor %}
{% if (plugins_count_pro > 0 and not is_pro_version) or (plugins_count_pro > 0 and is_pro_version and not pro_status) %}
<p class="ml-2 mt-0 mb-2 mr-2 text-red-500 font-bold dark:brightness-95 text-sm">
You have pro plugins without pro version.
</p>
{% endif %}
</div>
<!-- end info -->
<!-- upload layout -->
@ -151,6 +156,7 @@
<div data-plugins-list-container
class="min-h-[55vh] max-h-80 overflow-hidden overflow-y-auto p-4 col-span-12 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">LIST</h5>
<div data-plugins-list class="grid grid-cols-12 gap-3">
{% for plugin in plugins %}
<div data-plugins-type="{{ plugin['type'] }}"
@ -186,7 +192,7 @@
{% endif %}
{% if plugin['type'] == "pro" %}
<a {% if not is_pro_version %}target="_blank" rel="noopener"{% endif %}
aria-label="plugin page link"
aria-label="pro plugin"
class="hover:-translate-y-px mx-1 -translate-y-0.5"
href="{% if not is_pro_version %}https://panel.bunkerweb.io/{% else %}javascript:void(0){% endif %}">
<svg class="h-6 w-6 dark:brightness-90"

View file

@ -228,7 +228,7 @@
{% endif %}
</div>
<h6 data-services-method="{{ service["SERVER_NAME"]['method'] }}"
class="text-center sm:text-left mb-2 font-semibold text-gray-600 dark:text-white/80">
class="text-left sm:mb-2 font-semibold text-gray-600 dark:text-white/80">
{{ service["SERVER_NAME"]['method'] }}
</h6>
{% set details = [

View file

@ -1,17 +1,17 @@
<!-- modal -->
<div data-service-content="settings"
<div data-services-plugins-container
data-services-modal
class="dark:brightness-110 hidden w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 justify-center items-center">
<div data-services-modal-card
class="overflow-y-auto mx-0 sm:mx-6 lg:mx-8 my-3 px-4 pt-4 pb-8 w-full sm:min-w-[500px] h-[90vh] flex flex-col break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="w-full flex justify-between mb-2">
<div class="flex justify-start items-center">
class="overflow-y-auto mx-0 sm:mx-6 lg:mx-8 my-3 px-3 sm:px-4 pt-4 pb-8 w-full h-fit sm:min-w-[500px] max-w-[1000px] max-h-[95vh] md:max-h-[90vh] flex flex-col break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="w-full flex justify-between items-start mb-2">
<div class="flex flex-col sm:flex-row justify-start items-start sm:items-center">
<p data-services-modal-title
class="transition duration-300 ease-in-out dark:opacity-90 dark:text-gray-200 mb-1 font-sans font-semibold leading-normal uppercase text-md">
SERVICE MODAL
</p>
<button data-toggle-draft-btn
class="transition hover:brightness-75 dark:hover:brightness-110 ml-4 flex items-center border border-gray-700 dark:border-gray-300 rounded py-1 px-2">
class="transition hover:brightness-75 dark:hover:brightness-110 sm:ml-4 flex items-center border border-gray-700 dark:border-gray-300 rounded py-1 px-2">
<p data-toggle-draft="true"
class="hidden dark:text-gray-300 mb-0 mr-2 pointer-events-none">Draft</p>
<p data-toggle-draft="false"
@ -43,26 +43,33 @@
</button>
</div>
<div data-services-tabs-header
class="flex flex-col sm:flex-row justify-start items-start sm:items-center gap-x-4 gap-y-2 my-3">
<h5 class="transition duration-300 ease-in-out dark:opacity-90 ml-2 font-bold text-md uppercase dark:text-white mb-0">
CONFIGS
</h5>
<!-- search inpt-->
<div class="flex relative">
<label class="sr-only" for="settings-filter">search</label>
<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 />
class="flex flex-col">
<div class="flex flex-col sm:flex-row justify-start w-full items-start sm:items-center gap-y-3 gap-x-4">
<div class="w-full sm:min-w-[250px] max-w-[300px]">
{% include "settings_tabs.html" %}
</div>
<!-- search inpt-->
<div class="flex relative w-full max-w-[200px]">
<label class="sr-only" for="settings-filter">search</label>
<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="search by keyword"
pattern="(.*?)"
required />
</div>
<!-- end search inpt-->
</div>
<div class="w-full min-w-[300px] my-1 sm:my-0">
<hr class="separator" />
</div>
<!-- end search inpt-->
</div>
{% include "settings_tabs.html" %}
<div data-services-nomatch
class="hidden w-full overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words">
class="hidden w-full overflow-hidden grid grid-cols-12 max-h-100 sm:max-h-125 col-span-12 p-4 relative break-words">
<div class="col-span-12 flex flex-col justify-center items-center h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -77,13 +84,14 @@
</div>
<!-- new and edit form -->
<form data-services-modal-form
class="w-full h-full flex flex-col justify-between"
class="w-full h-[90vh] overflow-auto flex flex-col justify-between"
id="form-new"
method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" id="operation" value="new" name="operation" />
<input type="hidden" value="new" name="OLD_SERVER_NAME" />
<input type="hidden" value="no" name="is_draft" />
{% include "settings_plugins.html" %}
<!-- action button -->
<div class="w-full justify-center flex mt-10">
@ -93,7 +101,7 @@
<button data-services-modal-submit type="submit" class="mb-4 valid-btn">Save</button>
</div>
<!-- end action button-->
<p data-services-modal-error-msg class="hidden text-red-500 dark:opacity-80 mb-0 text-center"></p>
<p data-services-modal-error-msg class="hidden text-red-500 font-bold dark:opacity-80 mb-0 text-center"></p>
</form>
<!-- end new and edit form -->
<!-- delete form-->

View file

@ -3,9 +3,14 @@
{% for plugin in plugins %}
<div data-plugin-item="{{ plugin['id'] }}"
id="{{ plugin['id'] }}"
class="{% if loop.index != 1 %}hidden{% endif %} w-full">
class="{% if loop.index != 1 %}hidden{% endif %} w-full px-1">
<!-- title and desc -->
<div class="col-span-12" data-setting-header>
{% if (plugin['type'] == "pro" and not is_pro_version) or (plugin['type'] == "pro" and is_pro_version and not pro_status) %}
<p class="ml-2 mt-0 mb-2 mr-2 text-red-500 font-bold dark:brightness-95 text-sm">
You need a valid pro license to use this plugin
</p>
{% endif %}
<div class="flex justify-start items-center">
<h5 class="transition duration-300 ease-in-out ml-2 font-bold text-md uppercase dark:text-white/90 mb-0">
{{ plugin['name'] }} <span>{{ plugin['version'] }}</span>
@ -22,10 +27,26 @@
</svg>
</a>
{% endif %}
{% if plugin['type'] == "pro" %}
<a {% if not is_pro_version %}target="_blank" rel="noopener"{% endif %}
aria-label="pro plugin"
class="hover:-translate-y-px mx-1 -translate-y-0.5 ml-1"
href="{% if not is_pro_version %}https://panel.bunkerweb.io/{% else %}javascript:void(0){% endif %}">
<svg class="h-6 w-6 dark:brightness-90"
viewBox="0 0 48 46"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path class="fill-yellow-500" d="M43.218 28.2327L43.6765 23.971C43.921 21.6973 44.0825 20.1957 43.9557 19.2497L44 19.25C46.071 19.25 47.75 17.5711 47.75 15.5C47.75 13.4289 46.071 11.75 44 11.75C41.929 11.75 40.25 13.4289 40.25 15.5C40.25 16.4366 40.5935 17.2931 41.1613 17.9503C40.346 18.4535 39.2805 19.515 37.6763 21.1128C36.4405 22.3438 35.8225 22.9593 35.1333 23.0548C34.7513 23.1075 34.3622 23.0532 34.0095 22.898C33.373 22.6175 32.9485 21.8567 32.0997 20.335L27.6262 12.3135C27.1025 11.3747 26.6642 10.5889 26.2692 9.95662C27.89 9.12967 29 7.44445 29 5.5C29 2.73857 26.7615 0.5 24 0.5C21.2385 0.5 19 2.73857 19 5.5C19 7.44445 20.11 9.12967 21.7308 9.95662C21.3358 10.589 20.8975 11.3746 20.3738 12.3135L15.9002 20.335C15.0514 21.8567 14.627 22.6175 13.9905 22.898C13.6379 23.0532 13.2487 23.1075 12.8668 23.0548C12.1774 22.9593 11.5595 22.3438 10.3238 21.1128C8.71968 19.515 7.6539 18.4535 6.83882 17.9503C7.4066 17.2931 7.75 16.4366 7.75 15.5C7.75 13.4289 6.07107 11.75 4 11.75C1.92893 11.75 0.25 13.4289 0.25 15.5C0.25 17.5711 1.92893 19.25 4 19.25L4.04428 19.2497C3.91755 20.1957 4.07905 21.6973 4.32362 23.971L4.782 28.2327C5.03645 30.5982 5.24802 32.849 5.50717 34.875H42.4928C42.752 32.849 42.9635 30.5982 43.218 28.2327Z" fill="#1C274C" />
<path class="fill-yellow-500" d="M21.2803 45.5H26.7198C33.8098 45.5 37.3545 45.5 39.7198 43.383C40.7523 42.4588 41.4057 40.793 41.8775 38.625H6.1224C6.59413 40.793 7.24783 42.4588 8.2802 43.383C10.6454 45.5 14.1903 45.5 21.2803 45.5Z" fill="#1C274C" />
</svg>
</a>
{% endif %}
</div>
<div class="transition duration-300 ease-in-out dark:opacity-90 ml-2 text-sm mb-2 dark:text-gray-400">
{{ plugin['description'] }}
<div class="transition duration-300 ease-in-out dark:opacity-90 ml-2 ">
<p class="text-sm dark:text-gray-400 mb-1">{{ plugin['description'] }}</p>
</div>
</div>
<!-- end title and desc -->
<div data-plugin-settings class="w-full grid grid-cols-12">
@ -35,7 +56,8 @@
== "global-config" and value['context'] == "global" and not value['multiple'] or setting != "IS_DRAFT" and current_endpoint ==
"services" and value['context'] == "multisite" and not value['multiple'] %}
<div data-setting-container
class="mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
data-{{current_endpoint}}-context="{{ value['context'] }}"
class="mx-0 sm:mx-2 my-2 col-span-12 md:my-3 md:col-span-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 z-10">
@ -229,8 +251,8 @@
{% for multiple in multList %}
<!-- plugin multiple handler -->
<div data-multiple-handler
class="flex items-center mx-0 sm:mx-4 md:mx-6 md:my-3 my-2 2xl:mx-6 2xl:my-3 col-span-12 ">
<h5 class="input-title">{{ multiple }}</h5>
class="flex items-center mx-0 sm:mx-4 md:mx-6 mb-2 mt-5 2xl:mx-6 col-span-12 ">
<h5 class="input-title max-w-[150px] sm:max-w-[350px]">{{ multiple }}</h5>
<button data-{{ current_endpoint }}-multiple-add="{{ multiple }}" type="button" class="ml-3 dark:brightness-90 inline-block px-3 py-1.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-md ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
Add
</button>
@ -240,14 +262,14 @@
</div>
<!-- end plugin multiple handler-->
<!-- multiple settings -->
<div data-{{ current_endpoint }}-settings-multiple="{{ multiple }}_SCHEMA" class="bg-gray-50 dark:bg-slate-900/30 hidden w-full mb-8 grid-cols-12 border dark:border-gray-700 rounded">
<div data-{{ current_endpoint }}-settings-multiple="{{ multiple }}_SCHEMA" class="bg-gray-50 dark:bg-slate-900/30 hidden w-full mb-4 mt-2 grid-cols-12 border dark:border-gray-700 rounded">
{% for setting, value in plugin["settings"].items() %}
{# render only setting that match the multiple id and context #}
{% if current_endpoint
== "global-config" and value['context'] == "global" and value['multiple'] == multiple or current_endpoint ==
"services" and value['context'] == "multisite" and value['multiple'] == multiple %}
<div data-setting-container="{{ setting }}_SCHEMA"
class="mx-0 sm:mx-4 my-2 col-span-12 md:mx-6 md:my-3 md:col-span-6 2xl:mx-6 2xl:my-3 2xl:col-span-4"
class="mx-2 md:mx-3 my-2 md:my-3 col-span-12 md:col-span-6 2xl:col-span-4"
id="form-edit-{{ current_endpoint }}-{{ value["id"] }}_SCHEMA">
<!-- title and info -->
<div class="flex items-center my-1 relative z-10">

View file

@ -1,48 +1,14 @@
{% set current_endpoint = current_endpoint or url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<div data-{{ current_endpoint }}-tabs class="col-span-12 grid grid-cols-12 {% if current_endpoint == 'services' %}mb-4{% endif %}">
<!-- desktop tabs -->
<div role="tablist" data-{{ current_endpoint }}-tabs-desktop class="hidden md:block col-span-12">
<!-- tabs -->
{% for plugin in plugins %}
{% if current_endpoint == "services" and plugin["settings"]
and check_settings(plugin["settings"], "multisite") or current_endpoint == "global-config" and plugin["settings"]
and check_settings(plugin["settings"], "global") %}
<button role="tab"
data-tab-handler="{{ plugin['id'] }}"
class="{% if loop.first %}active{% endif %} settings-tabs-tab-btn">
<span class="w-full flex justify-between items-center">
<!-- text and icon -->
<span class="settings-tabs-name">{{ plugin['name'] }}</span>
<svg data-popover-btn="{{ plugin['name'] }}"
class=" fill-blue-500 h-5 w-5 mr-2 hover:brightness-95"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<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>
<!-- end text and icon -->
<!-- popover -->
<span data-popover-content="{{ plugin['name'] }}"
class="settings-tabs-popover-container hidden">
<span class="settings-tabs-popover-text">{{ plugin['description'] }}</span>
</span>
<!-- end popover -->
</span>
</button>
{% endif %}
{% endfor %}
<!--end tabs-->
</div>
<!-- end desktop tabs -->
<!-- mobile tabs -->
<div class="md:hidden relative col-span-12 h-full">
<div data-{{ current_endpoint }}-tabs class="col-span-12 grid grid-cols-12">
<div class="relative col-span-12 h-full">
<button data-tab-dropdown-btn
aria-controls="tab-dropdown-mobile"
class="settings-tabs-mobile-btn">
<span aria-description="current tab" class="settings-tabs-mobile-btn-text">
aria-controls="tab-dropdown"
class="settings-tabs-btn">
<span aria-description="current tab" class="settings-tabs-btn-text">
{% if current_endpoint == "global-config" %}general{% endif %}
</span>
<!-- chevron -->
<svg class="transition-transform h-4 w-4 fill-primary dark:fill-gray-300"
<svg data-tab-dropdown-arrow class="transition-transform h-4 w-4 fill-primary dark:fill-gray-300"
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" />
@ -50,34 +16,24 @@
<!-- end chevron -->
</button>
<!-- dropdown-->
<div id="tab-dropdown-mobile"
<div id="tab-dropdown"
role="listbox"
data-tab-dropdown
class="hidden z-100 absolute flex-col w-full overflow-hidden overflow-y-auto max-h-90">
class="hidden z-100 absolute flex-col w-full overflow-hidden overflow-y-auto h-90 max-h-[50vh]">
{% set first_el = "True" %}
{% for plugin in plugins %}
{% if current_endpoint == "services" and plugin["settings"]
and check_settings(plugin["settings"], "multisite") or current_endpoint == "global-config" and plugin["settings"]
and check_settings(plugin["settings"], "global") %}
{% if loop.first %}
<button role="option"
data-tab-handler-mobile="{{ plugin['id'] }}"
data-tab-handler="{{ plugin['id'] }}"
data-select="false"
id="edit-{{ current_endpoint }}-{{ plugin['id'] }}-tab"
class="active first settings-tabs-mobile-dropdown-btn">{{ plugin['name'] }}</button>
{% else %}
<button role="option"
data-tab-handler-mobile="{{ plugin['id'] }}"
data-select="false"
id="edit-{{ current_endpoint }}-{{ plugin['id'] }}-tab"
class="settings-tabs-mobile-dropdown-btn {% if loop.index == loop.length %}rounded-b{% endif %}">
{{ plugin['name'] }}
</button>
{% endif %}
class=" {% if loop.first %}
active first{% endif%} {% if loop.last%} last {% endif%} settings-tabs-dropdown-btn">{{ plugin['name'] }}</button>
{% endif %}
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end mobile tabs -->
</div>

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,7 @@ from selenium.webdriver.remote.webelement import WebElement
from selenium.common.exceptions import TimeoutException
from wizard import DRIVER
from utils import access_page, assert_button_click, safe_get_element, verify_select_filters
from utils import access_page, assert_button_click, safe_get_element
exit_code = 0
@ -88,9 +88,10 @@ try:
key_word_filter_input.send_keys("")
# Test select filters
select_filters = [{"name": "reason", "id": "reason", "value": "all", "update_value": "123456"}, {"name": "range", "id": "term", "value": "all", "update_value": "123456"}]
select_filters = [{"name": "reason", "id": "reason", "value": "all"}, {"name": "range", "id": "term", "value": "all"}]
verify_select_filters(DRIVER, "bans", select_filters)
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-bans-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
log_info("Bans found, trying to delete them ...")

View file

@ -7,7 +7,7 @@ from selenium.webdriver.remote.webelement import WebElement
from selenium.common.exceptions import TimeoutException
from wizard import DRIVER, UI_URL
from utils import access_page, assert_button_click, safe_get_element, verify_select_filters
from utils import access_page, assert_button_click, safe_get_element
exit_code = 0
@ -39,12 +39,13 @@ try:
# Test select filters
select_filters = [
{"name": "Success state", "id": "success", "value": "all", "update_value": "123456"},
{"name": "Reload state", "id": "reload", "value": "all", "update_value": "123456"},
{"name": "Run time", "id": "every", "value": "all", "update_value": "123456"},
{"name": "Success state", "id": "success", "value": "all"},
{"name": "Reload state", "id": "reload", "value": "all"},
{"name": "Run time", "id": "every", "value": "all"},
]
verify_select_filters(DRIVER, "jobs", select_filters)
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-jobs-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
log_info("Keyword filter is working, trying to filter by success state ...")

View file

@ -15,6 +15,23 @@ try:
log_info("Navigating to the logs page ...")
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[11]/a", "logs")
log_info("Trying filters ...")
key_word_filter_input = safe_get_element(DRIVER, "js", 'document.querySelector("input#keyword")')
assert isinstance(key_word_filter_input, WebElement), "Key word filter input is not a WebElement"
key_word_filter_input.send_keys("Antibot")
# Reset
key_word_filter_input.send_keys("")
# Test select filters
select_filters = [
{"name": "Types", "id": "types", "value": "all"},
]
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-logs-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
log_info("Selecting correct instance ...")
assert_button_click(DRIVER, "//button[@data-logs-setting-select='instances']")

View file

@ -10,7 +10,7 @@ from selenium.common.exceptions import TimeoutException
from wizard import DRIVER, UI_URL
from base import TEST_TYPE
from utils import access_page, assert_button_click, safe_get_element, wait_for_service, verify_select_filters
from utils import access_page, assert_button_click, safe_get_element, wait_for_service
exit_code = 0
@ -53,10 +53,11 @@ try:
# Test select filters
select_filters = [
{"name": "Types", "id": "types", "value": "all", "update_value": "123456"},
{"name": "Types", "id": "types", "value": "all"},
]
verify_select_filters(DRIVER, "plugins", select_filters)
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-plugins-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
log_info("The filter is working, trying to add a bad plugin ...")

View file

@ -8,7 +8,7 @@ from selenium.webdriver.remote.webelement import WebElement
from selenium.common.exceptions import TimeoutException
from wizard import DRIVER
from utils import access_page, safe_get_element, verify_select_filters
from utils import access_page, safe_get_element
exit_code = 0
@ -41,13 +41,14 @@ try:
# Test select filters
select_filters = [
{"name": "Country", "id": "country", "value": "all", "update_value": "123456"},
{"name": "Method", "id": "method", "value": "all", "update_value": "123456"},
{"name": "Status code", "id": "status", "value": "all", "update_value": "123456"},
{"name": "Reason", "id": "reason", "value": "all", "update_value": "123456"},
{"name": "Country", "id": "country", "value": "all"},
{"name": "Method", "id": "method", "value": "all"},
{"name": "Status code", "id": "status", "value": "all"},
{"name": "Reason", "id": "reason", "value": "all"},
]
verify_select_filters(DRIVER, "reports", select_filters)
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-reports-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
filter_input = safe_get_element(DRIVER, By.ID, "keyword")
assert isinstance(filter_input, WebElement), "Keyword filter input is not a WebElement"

View file

@ -9,7 +9,7 @@ from selenium.common.exceptions import TimeoutException
from wizard import DRIVER
from base import TEST_TYPE
from utils import access_page, assert_alert_message, assert_button_click, safe_get_element, wait_for_service, verify_select_filters
from utils import access_page, assert_alert_message, assert_button_click, safe_get_element, wait_for_service
exit_code = 0
@ -304,11 +304,12 @@ try:
# Test select filters
select_filters = [
{"name": "Method", "id": "method", "value": "all", "update_value": "123456"},
{"name": "State", "id": "state", "value": "all", "update_value": "123456"},
{"name": "Method", "id": "method", "value": "all"},
{"name": "State", "id": "state", "value": "all"},
]
verify_select_filters(DRIVER, "services", select_filters)
for item in select_filters:
DRIVER.execute_script(f"""document.querySelector('[data-services-setting-select-dropdown-btn="{item["id"]}"][value="{item["value"]}"]').click()""")
log_info("Filters working as expected, trying to delete app2.example.com ...")
@ -323,6 +324,8 @@ try:
assert_button_click(DRIVER, delete_button)
sleep(0.1)
access_page(DRIVER, "//form[@data-services-modal-form-delete='']//button[@type='submit']", "services", False)
if TEST_TYPE == "linux":

View file

@ -61,6 +61,7 @@ def assert_button_click(driver, button: Union[str, WebElement], by: str = "xpath
sleep(0.5)
button.click()
clicked = True
return clicked
@ -161,20 +162,3 @@ def wait_for_service(service: str = "www.example.com"):
retries += 1
log_warning(f"Waiting for {service} to be ready, retrying in 5s ...")
sleep(5)
# We replace value by non existing one and click on button
# If elements are hidden, it means script is working
# Example filter_items: [ {"name" : "Success state", "id" : "success", "value" : "all", "update_value" : "123456"}]
def verify_select_filters(driver, page_name: str, filter_items: list):
for item in filter_items:
# Get a select filter, change value and click to get no match
# Verify that elements are all hidden
# If not return false, else reset and send true
check_result = driver.execute_script(f"""const select{item['id']} = document.querySelector("[data-{page_name}-setting-select-dropdown-btn='{item["id"]}'][value='{item["value"]}']'); if(!select{item['id']}) return false; select{item['id']}.setAttribute('value', '{item["update_value"]}');select{item['id']}.click(); const select{item['id']}Match = document.querySelectorAll("[data-{page_name}-list-item][class*='hidden']");if (select{item['id']}Match.length === 0) return false;select{item['id']}.setAttribute('value', '{item["value"]}');select{item['id']}.click();return true;""")
if not check_result:
log_error(f"The {item['name']} filter is not working, exiting ...")
exit(1)
sleep(0.1)