Add the possibility to edit multisite settings in the global config page of the web UI + Lint html files with djlint

This commit is contained in:
Théophile Diot 2024-03-08 10:27:55 +00:00
parent 5a3d4814fe
commit ae3b8f1973
No known key found for this signature in database
GPG key ID: 248FEA4BAE400D06
13 changed files with 787 additions and 314 deletions

View file

@ -7,3 +7,4 @@ src/ui/templates/account.html:hashicorp-tf-password:417
src/ui/templates/account.html:hashicorp-tf-password:470
src/ui/templates/settings_plugins.html:hashicorp-tf-password:87
src/ui/templates/settings_plugins.html:hashicorp-tf-password:297
src/ui/templates/settings_plugins.html:hashicorp-tf-password:106

View file

@ -1,5 +1,4 @@
{% if USE_UI == "yes" +%}
SecRule REQUEST_FILENAME "@rx /services$" "id:7771,ctl:ruleRemoveByTag=attack-rce,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-generic,ctl:ruleRemoveByTag=attack-lfi,ctl:ruleRemoveByTag=attack-rfi,ctl:ruleRemoveByTag=attack-ssrf,nolog"
SecRule REQUEST_FILENAME "@rx /global_config$" "id:7772,ctl:ruleRemoveByTag=platform-pgsql,ctl:ruleRemoveByTag=attack-lfi,ctl:ruleRemoveByTag=attack-rfi,ctl:ruleRemoveByTag=attack-ssrf,nolog"
SecRule REQUEST_FILENAME "@rx /configs$" "id:7773,ctl:ruleRemoveByTag=language-shell,ctl:ruleRemoveByTag=attack-lfi,ctl:ruleRemoveByTag=attack-rfi,ctl:ruleRemoveByTag=attack-ssrf,nolog"
SecRule REQUEST_FILENAME "@rx /(global_config|services)$" "id:7771,ctl:ruleRemoveByTag=language-shell,ctl:ruleRemoveByTag=platform-pgsql,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-lfi,ctl:ruleRemoveByTag=attack-rfi,ctl:ruleRemoveByTag=attack-ssrf,nolog"
SecRule REQUEST_FILENAME "@rx /configs$" "id:7772,ctl:ruleRemoveByTag=language-shell,ctl:ruleRemoveByTag=attack-lfi,ctl:ruleRemoveByTag=attack-rfi,ctl:ruleRemoveByTag=attack-ssrf,nolog"
{% endif +%}

View file

@ -787,7 +787,7 @@ def services():
# Edit check fields and remove already existing ones
for variable, value in deepcopy(variables).items():
if variable.endswith("SCHEMA"):
if variable == "IS_DRAFT" or variable.endswith("SCHEMA"):
del variables[variable]
continue
@ -906,13 +906,14 @@ def services():
def global_config():
if request.method == "POST":
# Check variables
variables = deepcopy(request.form.to_dict())
variables = request.form.to_dict().copy()
del variables["csrf_token"]
# Edit check fields and remove already existing ones
config = app.config["CONFIG"].get_config(methods=False)
for variable, value in deepcopy(variables).items():
if variable.endswith("SCHEMA"):
config = app.config["CONFIG"].get_config(methods=False, with_drafts=True)
services = config["SERVER_NAME"].split(" ")
for variable, value in variables.copy().items():
if variable in ("AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE", "SERVER_NAME", "IS_LOADING", "IS_DRAFT") or variable.endswith("SCHEMA"):
del variables[variable]
continue
@ -921,13 +922,13 @@ def global_config():
elif value == "off":
value = "no"
if value == config.get(variable, None):
if value == config.get(variable, None) or any(variable.startswith(f"{service}_") for service in services):
del variables[variable]
if not variables:
return redirect_flash_error("The global configuration was not edited because no values were changed.", "global_config", True)
error = app.config["CONFIG"].check_variables(variables, True)
error = app.config["CONFIG"].check_variables(variables)
if error:
return redirect_flash_error("The global configuration variable checks returned error", "global_config", True)
@ -952,12 +953,13 @@ def global_config():
)
)
global_config = app.config["CONFIG"].get_config()
for service in global_config["SERVER_NAME"]["value"].split(" "):
for key in global_config.copy():
if key.startswith(f"{service}_"):
global_config.pop(key)
# Display global config
return render_template(
"global_config.html",
username=current_user.get_id(),
global_config=app.config["CONFIG"].get_config(),
)
return render_template("global_config.html", username=current_user.get_id(), global_config=global_config, dumped_global_config=dumps(global_config))
@app.route("/configs", methods=["GET", "POST"])

View file

@ -122,7 +122,7 @@ class Config:
"""
return self.__db.get_services_settings(methods=methods, with_drafts=with_drafts)
def check_variables(self, variables: dict, _global: bool = False) -> int:
def check_variables(self, variables: dict) -> int:
"""Testify that the variables passed are valid
Parameters
@ -141,11 +141,6 @@ class Config:
check = False
if k in plugins_settings:
if _global ^ (plugins_settings[k]["context"] == "global"):
error = 1
flash(f"Variable {k} is not valid.", "error")
continue
setting = k
else:
setting = k[0 : k.rfind("_")] # noqa: E203
@ -154,7 +149,7 @@ class Config:
flash(f"Variable {k} is not valid.", "error")
continue
if not (_global ^ (plugins_settings[setting]["context"] == "global")) and re_search(plugins_settings[setting]["regex"], v):
if re_search(plugins_settings[setting]["regex"], v):
check = True
if not check:

View file

@ -9,10 +9,451 @@ import {
class Multiple {
constructor(prefix) {
this.prefix = prefix;
this.container = document.querySelector("main");
this.init();
}
//hide multiples handler if no multiple setting on plugin
init() {
window.addEventListener("load", () => {
this.hiddenIfNoMultiples();
try {
//remove all multiples
this.removePrevMultiples();
//get multiple service values and parse as obj
const globalConfigSettings = document
.querySelector(`[data-${this.prefix}-settings]`)
.getAttribute("data-value");
const obj = JSON.parse(globalConfigSettings);
//keep only multiple settings value
const multipleSettings = this.getMultiplesOnly(obj);
const sortMultiples =
this.sortMultipleByContainerAndSuffixe(multipleSettings);
this.setMultipleToDOM(sortMultiples);
} catch (err) {}
});
document
.querySelector(`[data-${this.prefix}-form]`)
.addEventListener("click", (e) => {
//ADD BTN
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-multiple-add`)
) {
//get plugin from btn
const btn = e.target.closest("button");
const attName = btn.getAttribute(
`data-${this.prefix}-multiple-add`,
);
//get all multiple groups
const multipleEls = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple*="${attName}"]`,
);
//case no schema
if (multipleEls.length <= 0) return;
//get the next container number logic
//default is 0
let topNum = 0;
//loop on curr multiples, get the name suffix for each
//and keep the highest num
multipleEls.forEach((container) => {
const ctnrName = container.getAttribute(
`data-${this.prefix}-settings-multiple`,
);
const num = this.getSuffixNumOrFalse(ctnrName);
if (!isNaN(num) && num > topNum) topNum = num;
});
//the final number is num
//num is total - 1 because of hidden SCHEMA container
const currNum = `${multipleEls.length >= 2 ? topNum + 1 : topNum}`;
const setNum = +currNum === 0 ? `` : `_${currNum}`;
//the default (schema) group is the last group
const schema = document.querySelector(
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`,
);
//clone schema to create a group with new num
const schemaClone = schema.cloneNode(true);
//add special attribute for disabled logic
this.changeCloneSuffix(schemaClone, setNum);
//set disabled / enabled state
this.setDisabledMultNew(schemaClone);
this.showClone(schema, schemaClone);
//insert new group before first one
//show all groups
this.showMultByAtt(attName);
}
} catch (err) {}
//TOGGLE BTN
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-multiple-toggle`)
) {
const att = e.target
.closest("button")
.getAttribute(`data-${this.prefix}-multiple-toggle`);
this.toggleMultByAtt(att);
}
//remove last child
} catch (err) {}
//REMOVE BTN
try {
if (
e.target
.closest("button")
.hasAttribute(`data-${this.prefix}-multiple-delete`)
) {
const multContainer = e.target.closest(
`[data-${this.prefix}-settings-multiple]`,
);
multContainer.remove();
}
//remove last child
} catch (err) {}
});
}
sortMultipleByContainerAndSuffixe(obj) {
const sortMultiples = {};
for (const [name, value] of Object.entries(obj)) {
//split name and check if there is a suffixe
const splitName = name.split("_");
//suffixe start with number 1, if none give arbitrary 0 value to store on same group
const isSuffixe = !isNaN(splitName[splitName.length - 1]) ? true : false;
const suffixe = isSuffixe ? splitName[splitName.length - 1] : "0";
//remove suffix if exists and query related name_SCHEMA to get container info
const nameSuffixLess = isSuffixe
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
: name.trim();
const relateSetting = document.querySelector(
`[data-setting-container=${nameSuffixLess}_SCHEMA]`,
);
const relateCtnr = relateSetting.closest(
`[data-${this.prefix}-settings-multiple]`,
);
const relateCtnrName = relateCtnr.getAttribute(
`data-${this.prefix}-settings-multiple`,
);
//then we sort the setting on the right container name by suffixe number
if (!(relateCtnrName in sortMultiples)) {
sortMultiples[relateCtnrName] = {};
}
if (!(suffixe in sortMultiples[relateCtnrName])) {
sortMultiples[relateCtnrName][suffixe] = {};
}
sortMultiples[relateCtnrName][suffixe][name] = value;
}
return sortMultiples;
}
addOneMultGroup() {
const settings = document.querySelector(`[data-${this.prefix}-modal-form]`);
const multAddBtns = settings.querySelectorAll(
`[data-${this.prefix}-multiple-add]`,
);
multAddBtns.forEach((btn) => {
//check if already one (SCHEMA exclude so length >= 2)
const plugin = btn.closest("[data-plugin-item]");
if (
plugin.querySelectorAll(`[data-${this.prefix}-settings-multiple]`)
.length >= 2
)
return;
btn.click();
});
}
showMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
!container
.getAttribute(`data-${this.prefix}-settings-multiple`)
.includes("SCHEMA")
)
container.classList.remove("hidden");
});
}
toggleMultByAtt(att) {
const multContainers = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple^=${att}]`,
);
multContainers.forEach((container) => {
if (
!container
.getAttribute(`data-${this.prefix}-settings-multiple`)
.includes("SCHEMA")
)
container.classList.toggle("hidden");
});
}
getMultiplesOnly(settings) {
//get schema settings
const multiples = {};
const schemaSettings = document.querySelectorAll(
`[data-setting-container$="SCHEMA"]`,
);
// loop on every schema settings
schemaSettings.forEach((schema) => {
const schemaName = schema
.getAttribute("data-setting-container")
.replace("_SCHEMA", "")
.trim();
//check if match with service setting
for (const [key, data] of Object.entries(settings)) {
if (key.includes(schemaName)) {
multiples[key] = {
value: data["value"],
method: data["method"],
global: data["global"],
};
}
}
});
return multiples;
}
//put multiple on the right plugin, on schema container
setMultipleToDOM(sortMultObj) {
//we loop on each multiple that contains values to render to DOM
for (const [schemaCtnrName, multGroupBySuffix] of Object.entries(
sortMultObj,
)) {
//we need to access the DOM schema container
const schemaCtnr = document.querySelector(
`[data-${this.prefix}-settings-multiple="${schemaCtnrName}"]`,
);
//now we have to loop on each multiple settings group
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
//we have to clone schema container first
const schemaCtnrClone = schemaCtnr.cloneNode(true);
//remove id to avoid duplicate and for W3C
schemaCtnr.removeAttribute("id");
//now we replace _SCHEMA by current suffix everywhere we need
//unless it is 0 that means no suffix
const suffixFormat = +suffix === 0 ? `` : `_${suffix}`;
this.changeCloneSuffix(schemaCtnrClone, suffixFormat);
//then we have to loop on every settings of current group to change clone values by right ones
for (const [name, data] of Object.entries(settings)) {
//get setting container of clone container
const settingContainer = schemaCtnrClone.querySelector(
`[data-setting-container="${name}"]`,
);
//replace input info and disabled state
this.setSetting(
data["value"],
data["method"],
data["global"],
settingContainer,
);
}
//send schema clone to DOM and show it
this.showClone(schemaCtnr, schemaCtnrClone);
}
}
}
changeCloneSuffix(schemaCtnrClone, suffix) {
//rename multiple container
schemaCtnrClone.setAttribute(
`data-${this.prefix}-settings-multiple`,
schemaCtnrClone
.getAttribute(`data-${this.prefix}-settings-multiple`)
.replace("_SCHEMA", suffix),
);
//rename title
const titles = schemaCtnrClone.querySelectorAll("h5");
titles.forEach((title) => {
const text = title.textContent;
title.textContent = `${text} ${
suffix ? `#${suffix.replace("_", "")}` : ``
}`;
});
//rename setting container
const settingCtnrs = schemaCtnrClone.querySelectorAll(
"[data-setting-container]",
);
settingCtnrs.forEach((settingCtnr) => {
settingCtnr.setAttribute(
"data-setting-container",
settingCtnr
.getAttribute("data-setting-container")
.replace("_SCHEMA", suffix),
);
settingCtnr.setAttribute(
"id",
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix),
);
});
//rename input
try {
const inps = schemaCtnrClone.querySelectorAll("input");
this.renameLoop(inps, suffix);
} catch (err) {}
//rename select
try {
const selects = schemaCtnrClone.querySelectorAll("select");
this.renameLoop(selects, suffix);
} catch (err) {}
}
renameLoop(inps, suffix) {
inps.forEach((inp) => {
const newName = inp.getAttribute("name").replace("_SCHEMA", suffix);
inp.setAttribute("name", newName);
if (inp.hasAttribute("id")) inp.setAttribute("id", newName);
});
}
setSetting(value, method, global, settingContainer) {
//update input
try {
const inps = settingContainer.querySelectorAll("input");
inps.forEach((inp) => {
//form related values are excludes
const inpName = inp.getAttribute("name");
if (
inpName === "csrf_token" ||
inpName === "OLD_SERVER_NAME" ||
inpName === "is_draft" ||
inpName === "operation" ||
inpName === "settings-filter"
)
return;
//for settings input
if (inp.getAttribute("type") === "checkbox") {
try {
if (inp.hasAttribute("aria-checked")) {
value === "yes"
? inp.setAttribute("aria-checked", "true")
: inp.setAttribute("aria-checked", "false");
}
} catch (err) {}
try {
value === "yes"
? inp.setAttribute("data-checked", "true")
: inp.setAttribute("data-checked", "false");
} catch (err) {}
inp.setAttribute("value", value);
inp.setAttribute("data-method", method);
inp.checked = true;
}
if (inp.getAttribute("type") !== "checkbox") {
inp.setAttribute("value", value);
inp.value = value;
inp.setAttribute("data-method", method);
}
this.setDisabledMultServ(inp, method, global);
});
} catch (err) {}
//update select
try {
const select = settingContainer.querySelector("select");
select.setAttribute("data-method", method);
//click the custom select dropdown btn value to update select value
select.parentElement
.querySelector(
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
)
.click();
//set state to custom visible el
const btnCustom = document.querySelector(
`[data-setting-select=${select.getAttribute(
"data-setting-select-default",
)}]`,
);
this.setDisabledMultServ(btnCustom, method, global);
} catch (err) {}
}
showClone(schemaCtnr, schemaCtnrClone) {
schemaCtnr.insertAdjacentElement("afterend", schemaCtnrClone);
schemaCtnrClone.classList.remove("hidden");
schemaCtnrClone.classList.add("grid");
}
//global value isn't check at this point
setDisabledMultNew(container) {
const settings = container.querySelectorAll("[data-setting-container]");
settings.forEach((setting) => {
//replace input info
try {
const inps = setting.querySelectorAll("input");
inps.forEach((inp) => {
const method = inp.getAttribute("data-default-method");
if (method === "ui" || method === "default") {
inp.removeAttribute("disabled");
} else {
inp.setAttribute("disabled", "");
}
});
} catch (err) {}
//or select
try {
const selects = setting.querySelectorAll("select");
selects.forEach((select) => {
const method = select.getAttribute("data-default-method");
const name = select.getAttribute(
`data-${this.prefix}-setting-select-default`,
);
const selDOM = document.querySelector(
`button[data-${this.prefix}-setting-select='${name}']`,
);
if (method === "ui" || method === "default") {
selDOM.removeAttribute("disabled", "");
} else {
selDOM.setAttribute("disabled", "");
}
});
} catch (err) {}
});
}
//for already existing global config multiples
//global is check
setDisabledMultServ(inp, method, global) {
if (global) return inp.removeAttribute("disabled");
if (method === "ui" || method === "default") {
inp.removeAttribute("disabled");
} else {
inp.setAttribute("disabled", "");
}
}
//UTILS
getSuffixNumOrFalse(name) {
const num = !isNaN(Number(name.substring(name.lastIndexOf("_") + 1)))
? Number(name.substring(name.lastIndexOf("_") + 1))
: "";
return num;
}
hiddenIfNoMultiples() {
//hide multiple btn if no multiple exist on a plugin
const multiples = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`,
@ -24,6 +465,39 @@ class Multiple {
.classList.add("hidden");
});
}
removePrevMultiples() {
const multiPlugins = document.querySelectorAll(
`[data-${this.prefix}-settings-multiple]`,
);
multiPlugins.forEach((multiGrp) => {
if (
!multiGrp
.getAttribute(`data-${this.prefix}-settings-multiple`)
.includes("SCHEMA")
)
multiGrp.remove();
});
}
showMultiple(el) {
el.classList.add("grid");
el.classList.remove("hidden");
}
setNameIDloop(iterable, value) {
iterable.forEach((item) => {
const currID = item.getAttribute("id");
const currName = item.getAttribute("name");
item.setAttribute("id", `${currID}_${value}`);
item.setAttribute("name", `${currName}_${value}`);
});
}
setNameID(el, value) {
el.setAttribute("id", `${value}`);
el.setAttribute("name", `${value}`);
}
}
const setPopover = new Popover("main", "global-config");
@ -32,7 +506,6 @@ const setTabsSelect = new TabsSelect(
document.querySelector("[data-global-config-plugins-container]"),
);
const format = new FormatValue();
const setMultiple = new Multiple("global-config");
const setFilterGlobal = new FilterSettings(
"keyword",
@ -40,6 +513,8 @@ const setFilterGlobal = new FilterSettings(
document.querySelector("[data-global-config-plugins-container]"),
);
const setMultiple = new Multiple("global-config");
const checkServiceModalKeyword = new CheckNoMatchFilter(
document.querySelector("input#keyword"),
"input",

View file

@ -1,15 +1,14 @@
{% extends "base.html" %}
{% block content %}
<div data-global-config-tabs-select-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-select-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 mb-4">
<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 data-global-config-tabs-select-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-select-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 mb-4">
<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_select.html" %}
</div>
{% include "settings_tabs_select.html" %}
</div>
</div>
<!-- filter -->
{% set filters = [
{
@ -87,13 +86,15 @@ class="z-100 w-full grid grid-cols-12 h-fit max-h-100 sm:max-h-125 col-span-12
</div>
</div>
<!-- end filter -->
<div data-global-config-plugins-container
class="col-span-12 gap-y-4 grid grid-cols-12">
<div data-global-config-settings
class="hidden"
data-value="{{ dumped_global_config }}"></div>
<!-- form global conf -->
<form data-global-config-form
id="form-edit-global-config"
method="POST"
method="post"
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 -->
@ -121,5 +122,4 @@ class="z-100 w-full grid grid-cols-12 h-fit max-h-100 sm:max-h-125 col-span-12
</div>
</div>
</div>
</div>
{% endblock content %}

View file

@ -7,7 +7,8 @@
<h6 class="mb-0 text-lg font-bold text-white capitalize">{{ current_endpoint }}</h6>
<ul class="flex flex-wrap pt-1 mr-12 bg-transparent rounded-lg sm:mr-16">
<li class="text-sm leading-normal">
<a class="text-white opacity-50 dark:opacity-100 dark:text-gray-500" href="javascript:;">BunkerWeb</a>
<a class="text-white opacity-50 dark:opacity-100 dark:text-gray-500"
href="javascript:;">BunkerWeb</a>
</li>
<li class="hidden sm:inline text-sm pl-0 xs:pl-2 capitalize leading-normal text-white before:float-left before:pr-2 before:text-white before:content-['/']"
aria-current="page">{{ current_endpoint }}</li>

View file

@ -53,9 +53,7 @@
<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 text-sm sm:text-md font-bold m-0 dark:text-gray-200">
From date
</h5>
<h5 class="my-1 transition duration-300 ease-in-out text-sm sm:text-md font-bold m-0 dark:text-gray-200">From date</h5>
<div class="relative">
<input type="text"
id="from-date"
@ -124,7 +122,6 @@
</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 text-sm sm:text-md font-bold m-0 dark:text-gray-200">
@ -139,7 +136,6 @@
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"
@ -247,22 +243,21 @@
</div>
<!-- end filter -->
<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>
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>
<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 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

@ -201,7 +201,9 @@
<div>
<ul>
<li class="w-full mt-4">
<h6 class="pl-6 ml-2 text-xs font-bold leading-tight uppercase dark:text-gray-400 dark:opacity-100 opacity-60">PLUGINS PAGE</h6>
<h6 class="pl-6 ml-2 text-xs font-bold leading-tight uppercase dark:text-gray-400 dark:opacity-100 opacity-60">
PLUGINS PAGE
</h6>
</li>
{% for plugin in plugins %}
{% if plugin['page'] and plugin['type'] != "pro" %}
@ -221,9 +223,7 @@
{% endif %}
{% if plugin['page'] and plugin['type'] == "pro" %}
<li class="mt-0.5 w-full">
<a {% if not is_pro_version %}target="_blank" rel="noopener"{% endif %}
class="dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-gray-200 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition"
href="{% if not is_pro_version %}https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro{% else %}javascript:void(0){% endif %}"
<a {% if not is_pro_version %}target="_blank" rel="noopener"{% endif %} class="dark:hover:bg-primary/20 hover:bg-primary/5 hover:rounded-lg dark:text-gray-200 py-1 text-sm ease-nav-brand my-0 mx-2 flex items-center whitespace-nowrap px-4 transition" href="{% if not is_pro_version %}https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro{% else %}javascript:void(0){% endif %}"
<div class="mr-2 flex items-center justify-center rounded-lg bg-center stroke-0 text-center p-1 xl:p-1.5">
<svg class="h-5 w-5 dark:brightness-90"
viewBox="0 0 48 46"

View file

@ -42,12 +42,9 @@
</svg>
</button>
</div>
<div data-services-tabs-select-header
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_select.html" %}
</div>
<div data-services-tabs-select-header 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_select.html" %}</div>
<!-- search inpt-->
<div class="flex relative w-full max-w-[200px]">
<label class="sr-only" for="settings-filter">search</label>
@ -60,14 +57,11 @@
required />
</div>
<!-- end search inpt-->
</div>
</div>
<div class="w-full min-w-[300px] my-1 sm:my-0">
<hr class="separator" />
</div>
</div>
<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">
<div class="col-span-12 flex flex-col justify-center items-center h-fit">
@ -91,19 +85,18 @@
<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 flex-col items-center justify-center flex mt-10">
<div class="flex justify-center">
<button data-services-modal-close
type="button"
class="close-btn mb-4 mr-3 text-base">Close</button>
<button data-services-modal-submit type="submit" class="mb-4 valid-btn">Save</button>
type="button"
class="close-btn mb-4 mr-3 text-base">Close</button>
<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 font-bold 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>
</div>
</form>
<!-- end new and edit form -->

View file

@ -46,203 +46,199 @@
<p class="text-sm dark:text-gray-300 mb-1">{{ plugin['description'] }}</p>
</div>
</div>
{# get number of multiple groups for the plugin #}
{% set multList = [] %}
<!-- end title and desc -->
<div data-plugin-settings class="w-full grid grid-cols-12">
<!-- plugin settings not multiple -->
{% for setting, value in plugin["settings"].items() %}
{% if setting not in ["IS_LOADING", "IS_DRAFT"] and current_endpoint
== "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 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">
<h5 class="input-title">{{ value["label"] }}</h5>
<svg data-popover-btn="{{ value["label"] }}"
class="popover-settings-svg"
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 role="alert"
aria-description="show detail"
class="popover-settings-container hidden"
data-popover-content="{{ value["label"] }}">
<p class="popover-settings-text">{{ value['help'] }}</p>
</div>
<!-- end popover -->
</div>
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
<div class="relative flex items-center">
<label class="sr-only" for="{{ setting }}">{{ setting }}</label>
<input {% if setting == "SERVER_NAME" %}required{% endif %}
data-default-value="{{ global_config[setting]['value'] }}"
data-default-method="{{ global_config[setting]['method'] }}"
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %}disabled{% endif %}
id="{{ setting }}"
name="{{ setting }}"
class="regular-input"
value="{% if global_config[setting]['value'] %} {{ global_config[setting]['value'] }} {% else %} {{ value['default'] }} {% endif %}"
type="{{ value['type'] }}"
pattern="{{ value['regex']|safe }}" />
{% if value['type'] == "password" %}
<div data-setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button"
data-setting-password="visible"
class="h-5 w-5 flex items-center align-middle">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512">
<path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z" />
</svg>
</button>
<button type="button"
data-setting-password="invisible"
class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512">
<path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z" />
</svg>
</button>
</div>
{% endif %}
</div>
{% endif %}
<!-- end input -->
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select data-default-method="{{ global_config[setting]['method'] }}"
data-default-value="{{ value['default'] }}"
id="{{ setting }}"
name="{{ setting }}"
data-setting-select-default="{{ value['id'] }}"
data-type="form-select"
id="{{ setting }}"
name="{{ setting }}"
class="hidden">
{% for item in value['select'] %}
<option {% if not item %}label="empty"{% endif %}
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 data-select-container class="relative">
<button {% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %}disabled{% endif %}
data-setting-select="{{ value['id'] }}"
data-default-value="{{ global_config[setting]['value'] }}"
data-default-method="{{ global_config[setting]['method'] }}"
aria-controls="{{ value['id'] }}-dropdown"
type="button"
class="custom-select-btn">
{% for item in value['select'] %}
{% if global_config[setting]['value'] and
global_config[setting]['value'] == item %}
<span data-setting-select-text="{{ value['id'] }}"
data-value="{{ global_config[setting]['value'] }}">{{ global_config[setting]['value'] }}</span>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span aria-description="current value"
data-setting-select-text="{{ value['id'] }}"
data-value="{{ value['default'] }}">{{ value['default'] }}</span>
{% endif %}
{% endfor %}
<!-- chevron -->
<svg data-setting-select="{{ value['id'] }}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<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 id="{{ value['id'] }}-dropdown"
role="listbox"
data-setting-select-dropdown="{{ value['id'] }}"
class="hidden z-[20] absolute h-full flex-col w-full mt-2">
{% for item in value['select'] %}
{% if global_config[setting]['value'] and
global_config[setting]['value'] == item or not global_config[setting]['value']
and value['default'] == item %}
<button role="option"
value="{{ item }}"
data-setting-select-dropdown-btn="{{ value['id'] }}"
type="button"
class="active custom-dropdown-btn {% if loop.index == 1 %}border-t rounded-t{% endif %} {% if loop.index == loop.length %}rounded-b{% endif %} ">
{{ item }}
</button>
{% else %}
<button role="option"
value="{{ item }}"
data-setting-select-dropdown-btn="{{ value['id'] }}"
type="button"
class="custom-dropdown-btn {% if loop.index == 1 %}border-t rounded-t{% endif %} {% if loop.index == loop.length %}rounded-b{% endif %} ">
{{ item }}
</button>
{% endif %}
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end custom-->
{% endif %}
<!-- checkbox -->
{% if value["type"] == "check" %}
<div data-checkbox-handler="{{ value['id'] }}"
class="relative mb-7 md:mb-0 z-10 ">
<label class="sr-only" for="{{ setting }}">{{ setting }}</label>
<input id="{{ setting }}"
name="{{ setting }}"
data-default-method="{% if setting in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] %}mode{% else %}{{ global_config[setting]['method'] }}{% endif %}"
data-default-value="{{ global_config[setting]['value'] }}"
{% if setting in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] or global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %}
data-checked="{% if global_config[setting]['value'] == "yes" %}true{% else %}false{% endif %}"
checked
id="checkbox-{{ value['id'] }}"
class="checkbox"
type="checkbox"
data-pattern="{{ value['regex']|safe }}"
value="{{ global_config[setting]['value'] }}" />
<svg data-checkbox-handler="{{ value['id'] }}"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
{% if setting != "IS_DRAFT" and (current_endpoint == "global-config" and setting not in ["SERVER_NAME", "IS_LOADING"] or current_endpoint == "services" and value['context'] == "multisite") %}
{% if value['multiple'] and value['multiple'] not in multList %}
{% if multList.append(value['multiple']) %}{% endif %}
{% endif %}
{% if not value['multiple'] %}
<div data-setting-container 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">
<h5 class="input-title">{{ value["label"] }}</h5>
<svg data-popover-btn="{{ value["label"] }}"
class="popover-settings-svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path class="pointer-events-none" 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>
<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 role="alert"
aria-description="show detail"
class="popover-settings-container hidden"
data-popover-content="{{ value["label"] }}">
<p class="popover-settings-text">{{ value['help'] }}</p>
</div>
<!-- end popover -->
</div>
{% endif %}
<!-- end checkbox -->
<!-- invalid feedback -->
<div role="alert"
aria-label="show when invalid input"
class="hidden text-sm dark:text-red-500">
{{ value['label'] }} is invalid and must match this pattern:
{{ value['regex']|safe }}
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
<div class="relative flex items-center">
<label class="sr-only" for="{{ setting }}">{{ setting }}</label>
<input {% if setting == "SERVER_NAME" %}required{% endif %}
data-default-value="{{ global_config[setting]['value'] }}"
data-default-method="{{ global_config[setting]['method'] }}"
{% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %}disabled{% endif %}
id="{{ setting }}"
name="{{ setting }}"
class="regular-input"
value="{% if global_config[setting]['value'] %} {{ global_config[setting]['value'] }} {% else %} {{ value['default'] }} {% endif %}"
type="{{ value['type'] }}"
pattern="{{ value['regex']|safe }}" />
{% if value['type'] == "password" %}
<div data-setting-password-container class="absolute flex right-2 h-5 w-5">
<button type="button"
data-setting-password="visible"
class="h-5 w-5 flex items-center align-middle">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512">
<path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z" />
</svg>
</button>
<button type="button"
data-setting-password="invisible"
class="hidden -translate-y-0.2 scale-110 h-5 w-5 items-center align-middle">
<svg class="fill-primary pointer-events-none dark:fill-blue-500 hover:brightness-75 transition-all"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512">
<path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z" />
</svg>
</button>
</div>
{% endif %}
</div>
{% endif %}
<!-- end input -->
<!-- select -->
{% if value["type"] == "select" %}
<!-- default hidden-->
<select data-default-method="{{ global_config[setting]['method'] }}"
data-default-value="{{ value['default'] }}"
id="{{ setting }}"
name="{{ setting }}"
data-setting-select-default="{{ value['id'] }}"
data-type="form-select"
id="{{ setting }}"
name="{{ setting }}"
class="hidden">
{% for item in value['select'] %}
<option {% if not item %}label="empty"{% endif %}
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 data-select-container class="relative">
<button {% if global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %}disabled{% endif %}
data-setting-select="{{ value['id'] }}"
data-default-value="{{ global_config[setting]['value'] }}"
data-default-method="{{ global_config[setting]['method'] }}"
aria-controls="{{ value['id'] }}-dropdown"
type="button"
class="custom-select-btn">
{% for item in value['select'] %}
{% if global_config[setting]['value'] and
global_config[setting]['value'] == item %}
<span data-setting-select-text="{{ value['id'] }}"
data-value="{{ global_config[setting]['value'] }}">{{ global_config[setting]['value'] }}</span>
{% elif not global_config[setting]['value'] and value['default'] == item %}
<span aria-description="current value"
data-setting-select-text="{{ value['id'] }}"
data-value="{{ value['default'] }}">{{ value['default'] }}</span>
{% endif %}
{% endfor %}
<!-- chevron -->
<svg data-setting-select="{{ value['id'] }}"
class="transition-transform h-4 w-4 fill-gray-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<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 id="{{ value['id'] }}-dropdown"
role="listbox"
data-setting-select-dropdown="{{ value['id'] }}"
class="hidden z-[20] absolute h-full flex-col w-full mt-2">
{% for item in value['select'] %}
{% if global_config[setting]['value'] and
global_config[setting]['value'] == item or not global_config[setting]['value']
and value['default'] == item %}
<button role="option"
value="{{ item }}"
data-setting-select-dropdown-btn="{{ value['id'] }}"
type="button"
class="active custom-dropdown-btn {% if loop.index == 1 %}border-t rounded-t{% endif %} {% if loop.index == loop.length %}rounded-b{% endif %} ">
{{ item }}
</button>
{% else %}
<button role="option"
value="{{ item }}"
data-setting-select-dropdown-btn="{{ value['id'] }}"
type="button"
class="custom-dropdown-btn {% if loop.index == 1 %}border-t rounded-t{% endif %} {% if loop.index == loop.length %}rounded-b{% endif %} ">
{{ item }}
</button>
{% endif %}
{% endfor %}
</div>
<!-- end dropdown-->
</div>
<!-- end custom-->
{% endif %}
<!-- checkbox -->
{% if value["type"] == "check" %}
<div data-checkbox-handler="{{ value['id'] }}"
class="relative mb-7 md:mb-0 z-10 ">
<label class="sr-only" for="{{ setting }}">{{ setting }}</label>
<input id="{{ setting }}"
name="{{ setting }}"
data-default-method="{% if setting in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] %}mode{% else %}{{ global_config[setting]['method'] }}{% endif %}"
data-default-value="{{ global_config[setting]['value'] }}"
{% if setting in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] or global_config[setting]['method'] != 'ui' and global_config[setting]['method'] != 'default' %} disabled {% endif %}
data-checked="{% if global_config[setting]['value'] == "yes" %}true{% else %}false{% endif %}"
checked
id="checkbox-{{ value['id'] }}"
class="checkbox"
type="checkbox"
data-pattern="{{ value['regex']|safe }}"
value="{{ global_config[setting]['value'] }}" />
<svg data-checkbox-handler="{{ value['id'] }}"
class="pointer-events-none absolute fill-white dark:fill-gray-300 left-0 top-0 translate-x-1 translate-y-2 h-3 w-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path class="pointer-events-none" 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 role="alert"
aria-label="show when invalid input"
class="hidden text-sm dark:text-red-500">
{{ value['label'] }} is invalid and must match this pattern:
{{ value['regex']|safe }}
</div>
<!--end invalid feedback-->
</div>
<!--end invalid feedback-->
</div>
{% endif %}
{% endif %}
{% endfor %}
<!-- end plugin settings -->
</div>
<!-- end plugin settings not multiple -->
{# get number of multiple groups for the plugin #}
{% set multList = [] %}
{% for setting, value in plugin["settings"].items() %}
{% if current_endpoint
== "global-config" and value['context'] == "global" and value['multiple'] and not value['multiple'] in multList or current_endpoint ==
"services" and value['context'] == "multisite" and value['multiple'] and not value['multiple'] in multList %}
{% if multList.append(value['multiple']) %}{% endif %}
{% endif %}
{% endfor %}
{% if multList|length > 0 %}
<h5 class="transition duration-300 ease-in-out ml-2 font-bold text-[1.1rem] uppercase dark:text-white/90 mt-2 mb-0">
multiple settings
@ -265,9 +261,10 @@
<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 %}
{% if value['multiple'] == multiple and (
current_endpoint == "global-config"
or current_endpoint == "services" and value['context'] == "multisite"
) %}
<div data-setting-container="{{ setting }}_SCHEMA"
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">
@ -291,7 +288,7 @@
</div>
<!-- end title and info -->
<!-- input -->
{% if value["type"] != "select" and value["type"] != "check" %}
{% if value["type"] not in ["select", "check"] %}
<div class="relative flex items-center">
<label class="sr-only" for="{{ setting }}_SCHEMA">{{ setting }}</label>
<input data-default-value="{{ value['default'] }}"

View file

@ -9,7 +9,8 @@
{% if current_endpoint == "global-config" %}general{% endif %}
</span>
<!-- chevron -->
<svg data-tab-select-dropdown-arrow class="transition-transform h-4 w-4 fill-primary dark:fill-gray-300"
<svg data-tab-select-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" />
@ -24,14 +25,14 @@
{% 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") %}
<button role="option"
data-tab-select-handler="{{ plugin['id'] }}"
data-select="false"
id="edit-{{ current_endpoint }}-{{ plugin['id'] }}-tab"
class=" {% if loop.first %}
active first{% endif%} {% if loop.last%} last {% endif%} settings-tabs-select-dropdown-btn">{{ plugin['name'] }}</button>
and check_settings(plugin["settings"], "multisite") or current_endpoint == "global-config" %}
<button role="option"
data-tab-select-handler="{{ plugin['id'] }}"
data-select="false"
id="edit-{{ current_endpoint }}-{{ plugin['id'] }}-tab"
class=" {% if loop.first %}active first{% endif %} {% if loop.last %}last{% endif %} settings-tabs-select-dropdown-btn">
{{ plugin['name'] }}
</button>
{% endif %}
{% endfor %}
</div>

View file

@ -123,7 +123,9 @@
<h2 class="col-span-12 block text-left font-bold my-4 text-2xl">Account</h2>
<!-- username inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">Username<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Username<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_username">Username</label>
<input type="text"
id="admin_username"
@ -138,7 +140,9 @@
<!-- end username inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">Password<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password">Password</label>
<input type="password"
id="admin_password"
@ -153,7 +157,9 @@
<!-- end password inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">Confirm Password<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Confirm Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password_check">Confirm Password</label>
<input type="password"
id="admin_password_check"
@ -169,47 +175,51 @@
<!-- end password inpt-->
<!-- email inpt-->
<div class="flex flex-col relative col-span-12 mx-2 max-w-[400px] w-full">
<h5 class="text-base mb-1 transition duration-300 ease-in-out text-md font-bold m-0">Email</h5>
<label class="sr-only" for="newsletter-email">email</label>
<input type="email"
id="newsletter-email"
name="EMAIL"
class="col-span-12 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-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="example@gmail.com"
value=""/>
</div>
<!-- auto privacy policy-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0"> I've read and agree to the
<a class="privacy-link"
href="https://www.bunkerity.com/privacy-policy/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener">privacy policy</a></h5>
<label class="sr-only" for="newsletter-check">privacy policy</label>
<div data-checkbox-handler="newsletter-check"
class="relative mb-7 md:mb-0 z-10">
<input data-check
type="checkbox"
id="newsletter-check"
name="newsletter-check"
data-checked="false"
class="checkbox"
value="no" />
<svg data-checkbox-handler="newsletter-check"
class="pointer-events-none absolute fill-white 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 class="pointer-events-none" 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>
<h5 class="text-base mb-1 transition duration-300 ease-in-out text-md font-bold m-0">Email</h5>
<label class="sr-only" for="newsletter-email">email</label>
<input type="email"
id="newsletter-email"
name="EMAIL"
class="col-span-12 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-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="example@gmail.com"
value="" />
</div>
</div>
<!-- end auto privacy policy-->
<!-- end email inpt-->
<!-- auto privacy policy-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
I've read and agree to the
<a class="privacy-link"
href="https://www.bunkerity.com/privacy-policy/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener">privacy policy</a>
</h5>
<label class="sr-only" for="newsletter-check">privacy policy</label>
<div data-checkbox-handler="newsletter-check"
class="relative mb-7 md:mb-0 z-10">
<input data-check
type="checkbox"
id="newsletter-check"
name="newsletter-check"
data-checked="false"
class="checkbox"
value="no" />
<svg data-checkbox-handler="newsletter-check"
class="pointer-events-none absolute fill-white 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 class="pointer-events-none" 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 auto privacy policy-->
<!-- end email inpt-->
<h2 class="col-span-12 block text-left font-bold mb-4 mt-2 text-2xl">Settings</h2>
<!-- ui host-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">UI Host (REVERSE_PROXY_HOST)<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
UI Host (REVERSE_PROXY_HOST)<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="ui_host">ui host</label>
<input type="text"
id="ui_host"
@ -223,7 +233,9 @@
<!-- end ui host-->
<!-- ui url-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">UI URL (REVERSE_PROXY_URL)<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
UI URL (REVERSE_PROXY_URL)<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="ui_url">ui url</label>
<input type="text"
id="ui_url"
@ -237,7 +249,9 @@
<!-- end ui url-->
<!-- server name-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">Server name<strong class="required-mark">*</strong></h5>
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Server name<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="server_name">server name</label>
<input type="text"
id="server_name"