mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Enhance QOL in web UI in general + made advancements in easy mode for services
This commit is contained in:
parent
398a33ccfe
commit
f73632f8c7
18 changed files with 821 additions and 257 deletions
|
|
@ -58,23 +58,18 @@ class Config:
|
|||
|
||||
def get_plugins_settings(self) -> dict:
|
||||
return {
|
||||
**{k: v for x in self.get_plugins() for k, v in x["settings"].items()},
|
||||
**{k: v for x in self.get_plugins().values() for k, v in x["settings"].items()},
|
||||
**self.__settings,
|
||||
}
|
||||
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "ui", "pro"] = "all", with_data: bool = False) -> List[dict]:
|
||||
plugins = self.__db.get_plugins(_type=_type, with_data=with_data)
|
||||
plugins.sort(key=itemgetter("name"))
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "ui", "pro"] = "all", with_data: bool = False) -> dict:
|
||||
db_plugins = self.__db.get_plugins(_type=_type, with_data=with_data)
|
||||
db_plugins.sort(key=itemgetter("name"))
|
||||
|
||||
general_plugin = None
|
||||
for plugin in plugins.copy():
|
||||
if plugin["id"] == "general":
|
||||
general_plugin = plugin
|
||||
plugins.remove(plugin)
|
||||
break
|
||||
plugins = {"general": {}}
|
||||
|
||||
if general_plugin:
|
||||
plugins.insert(0, general_plugin)
|
||||
for plugin in db_plugins.copy():
|
||||
plugins[plugin.pop("id")] = plugin
|
||||
|
||||
return plugins
|
||||
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ def plugins_page():
|
|||
def update_plugins(threaded: bool = False):
|
||||
wait_applying()
|
||||
|
||||
plugins = BW_CONFIG.get_plugins(_type="ui", with_data=True)
|
||||
plugins = BW_CONFIG.get_plugins(_type="ui", with_data=True) # TODO: remember that this returns a dict now
|
||||
for plugin in deepcopy(plugins):
|
||||
if plugin["id"] in new_plugins_ids:
|
||||
flash(f"Plugin {plugin['id']} already exists", "error")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from re import match
|
||||
from threading import Thread
|
||||
from time import time
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
from flask import Blueprint, Response, redirect, render_template, request, url_for
|
||||
from flask_login import login_required
|
||||
|
||||
|
|
@ -18,6 +18,86 @@ def services_page():
|
|||
return render_template("services.html", services=DB.get_services(with_drafts=True))
|
||||
|
||||
|
||||
@services.route("/services/convert", methods=["POST"])
|
||||
@login_required
|
||||
def services_convert():
|
||||
verify_data_in_form(
|
||||
data={"services": None},
|
||||
err_message="Missing services parameter on /services/convert.",
|
||||
redirect_url="services",
|
||||
next=True,
|
||||
)
|
||||
verify_data_in_form(
|
||||
data={"convert_to": None},
|
||||
err_message="Missing convert_to parameter on /services/convert.",
|
||||
redirect_url="services",
|
||||
next=True,
|
||||
)
|
||||
|
||||
services = request.form["services"].split(",")
|
||||
if not services:
|
||||
return handle_error("No services selected.", "services", True)
|
||||
|
||||
convert_to = request.form["convert_to"]
|
||||
if convert_to not in ("online", "draft"):
|
||||
return handle_error("Invalid convert_to parameter.", "services", True)
|
||||
DATA.load_from_file()
|
||||
|
||||
def convert_services(services: List[str], convert_to: str):
|
||||
wait_applying()
|
||||
|
||||
db_services = DB.get_services(with_drafts=True)
|
||||
services_to_convert = set()
|
||||
non_ui_services = set()
|
||||
non_convertible_services = set()
|
||||
|
||||
for db_service in db_services:
|
||||
if db_service["id"] in services:
|
||||
if db_service["method"] != "ui":
|
||||
non_ui_services.add(db_service["id"])
|
||||
continue
|
||||
if db_service["is_draft"] == (convert_to == "draft"):
|
||||
non_convertible_services.add(db_service["id"])
|
||||
continue
|
||||
services_to_convert.add(db_service["id"])
|
||||
|
||||
for non_ui_service in non_ui_services:
|
||||
DATA["TO_FLASH"].append({"content": f"Service {non_ui_service} is not a UI service and will not be converted.", "type": "error"})
|
||||
|
||||
for non_convertible_service in non_convertible_services:
|
||||
DATA["TO_FLASH"].append(
|
||||
{"content": f"Service {non_convertible_service} is already a {convert_to} service and will not be converted.", "type": "error"}
|
||||
)
|
||||
|
||||
if not services_to_convert:
|
||||
DATA["TO_FLASH"].append({"content": "All selected services could not be found, are not UI services or are already converted.", "type": "error"})
|
||||
DATA.update({"RELOADING": False, "CONFIG_CHANGED": False})
|
||||
return
|
||||
|
||||
db_config = DB.get_config(with_drafts=True)
|
||||
for service in services_to_convert:
|
||||
db_config[f"{service}_IS_DRAFT"] = "yes" if convert_to == "draft" else "no"
|
||||
|
||||
ret = DB.save_config(db_config, "ui", changed=True)
|
||||
if isinstance(ret, str):
|
||||
DATA["TO_FLASH"].append({"content": ret, "type": "error"})
|
||||
DATA.update({"RELOADING": False, "CONFIG_CHANGED": False})
|
||||
return
|
||||
DATA["TO_FLASH"].append({"content": f"Converted services: {', '.join(services_to_convert)}", "type": "success"})
|
||||
DATA["RELOADING"] = False
|
||||
|
||||
DATA.update({"RELOADING": True, "LAST_RELOAD": time(), "CONFIG_CHANGED": True})
|
||||
Thread(target=convert_services, args=(services, convert_to)).start()
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"loading",
|
||||
next=url_for("services.services_page"),
|
||||
message=f"Converting service{'s' if len(services) > 1 else ''} {', '.join(services)} to {convert_to}",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@services.route("/services/delete", methods=["POST"])
|
||||
@login_required
|
||||
def services_delete():
|
||||
|
|
@ -32,10 +112,10 @@ def services_delete():
|
|||
return handle_error("No services selected.", "services", True)
|
||||
DATA.load_from_file()
|
||||
|
||||
def delete_services(services):
|
||||
def delete_services(services: List[str]):
|
||||
wait_applying()
|
||||
|
||||
db_config = BW_CONFIG.get_config(methods=False)
|
||||
db_config = BW_CONFIG.get_config(methods=False, with_drafts=True)
|
||||
db_services = DB.get_services(with_drafts=True)
|
||||
all_drafts = True
|
||||
services_to_delete = set()
|
||||
|
|
@ -71,7 +151,7 @@ def services_delete():
|
|||
DATA["TO_FLASH"].append({"content": ret, "type": "error"})
|
||||
DATA.update({"RELOADING": False, "CONFIG_CHANGED": False})
|
||||
return
|
||||
DATA["TO_FLASH"].append({"content": f"Deleted services {', '.join(services_to_delete)}", "type": "success"})
|
||||
DATA["TO_FLASH"].append({"content": f"Deleted services: {', '.join(services_to_delete)}", "type": "success"})
|
||||
DATA["RELOADING"] = False
|
||||
|
||||
DATA.update({"RELOADING": True, "LAST_RELOAD": time(), "CONFIG_CHANGED": True})
|
||||
|
|
@ -81,7 +161,7 @@ def services_delete():
|
|||
url_for(
|
||||
"loading",
|
||||
next=url_for("services.services_page"),
|
||||
message=f"Deleting services {', '.join(services)}",
|
||||
message=f"Deleting service{'s' if len(services) > 1 else ''} {', '.join(services)}",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -107,7 +187,7 @@ def services_service_page(service: str):
|
|||
mode = request.args.get("mode", "easy")
|
||||
is_draft = variables.pop("IS_DRAFT", "no") == "yes"
|
||||
|
||||
def update_service(service: str, variables: Dict[str, str], is_draft: bool, mode: str): # TODO: handle easy and raw modes
|
||||
def update_service(service: str, variables: Dict[str, str], is_draft: bool, mode: str): # TODO: handle easy mode
|
||||
wait_applying()
|
||||
|
||||
# Edit check fields and remove already existing ones
|
||||
|
|
@ -121,15 +201,16 @@ def services_service_page(service: str):
|
|||
ignored_multiples = set()
|
||||
|
||||
# Edit check fields and remove already existing ones
|
||||
for variable, value in variables.copy().items():
|
||||
if (mode == "raw" or variable != "SERVER_NAME") and value == config.get(variable, {"value": None})["value"]:
|
||||
if match(r"^.+_\d+$", variable):
|
||||
ignored_multiples.add(variable)
|
||||
del variables[variable]
|
||||
if mode != "easy":
|
||||
for variable, value in variables.copy().items():
|
||||
if (mode == "raw" or variable != "SERVER_NAME") and value == config.get(variable, {"value": None})["value"]:
|
||||
if match(r"^.+_\d+$", variable):
|
||||
ignored_multiples.add(variable)
|
||||
del variables[variable]
|
||||
|
||||
variables = BW_CONFIG.check_variables(variables, config, ignored_multiples=ignored_multiples, new=service == "new", threaded=True)
|
||||
|
||||
if was_draft == is_draft and not variables:
|
||||
if service != "new" and was_draft == is_draft and not variables:
|
||||
content = f"The service {service} was not edited because no values were changed."
|
||||
DATA["TO_FLASH"].append({"content": content, "type": "warning"})
|
||||
DATA.update({"RELOADING": False, "CONFIG_CHANGED": False})
|
||||
|
|
@ -181,6 +262,8 @@ def services_service_page(service: str):
|
|||
|
||||
mode = request.args.get("mode", "easy")
|
||||
search_type = request.args.get("type", "all")
|
||||
template = request.args.get("template", "high")
|
||||
db_templates = DB.get_templates()
|
||||
if service == "new":
|
||||
clone = request.args.get("clone", "")
|
||||
if clone:
|
||||
|
|
@ -189,24 +272,30 @@ def services_service_page(service: str):
|
|||
return render_template(
|
||||
"service_settings.html",
|
||||
config=db_config,
|
||||
templates=db_templates,
|
||||
mode=mode,
|
||||
type=search_type,
|
||||
current_template=template,
|
||||
)
|
||||
|
||||
db_config = DB.get_config(global_only=True, methods=True)
|
||||
return render_template(
|
||||
"service_settings.html",
|
||||
config=db_config,
|
||||
templates=db_templates,
|
||||
mode=mode,
|
||||
type=search_type,
|
||||
current_template=template,
|
||||
)
|
||||
|
||||
db_config = DB.get_config(methods=True, with_drafts=True, service=service)
|
||||
return render_template(
|
||||
"service_settings.html",
|
||||
config=db_config,
|
||||
templates=db_templates,
|
||||
mode=mode,
|
||||
type=search_type,
|
||||
current_template=template,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -453,3 +453,11 @@ a.badge:hover {
|
|||
background-color: transparent;
|
||||
opacity: 1; /* You can set this to 0 if you want it to fade out */
|
||||
}
|
||||
|
||||
.template-steps-container {
|
||||
--bs-breadcrumb-divider: url("../img/bxs-chevron-right.svg");
|
||||
}
|
||||
|
||||
.template-steps-container .breadcrumb-item + .breadcrumb-item::before {
|
||||
padding: 0 1rem 0 1rem;
|
||||
}
|
||||
|
|
|
|||
1
src/ui/app/static/img/bxs-chevron-right.svg
Normal file
1
src/ui/app/static/img/bxs-chevron-right.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10.061 19.061 17.121 12l-7.06-7.061-2.122 2.122L12.879 12l-4.94 4.939z"/></svg>
|
||||
|
After Width: | Height: | Size: 172 B |
|
|
@ -309,7 +309,6 @@ $(document).ready(function () {
|
|||
],
|
||||
order: [[7, "desc"]],
|
||||
autoFill: false,
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
select: {
|
||||
style: "multi+shift",
|
||||
|
|
|
|||
|
|
@ -293,7 +293,6 @@ $(document).ready(function () {
|
|||
],
|
||||
order: [[5, "desc"]],
|
||||
autoFill: false,
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
select: {
|
||||
style: "multi+shift",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
$(document).ready(() => {
|
||||
var toastNum = 0;
|
||||
let currentPlugin = "general";
|
||||
let usedTemplate = $("#used-template").val();
|
||||
let currentTemplate = $("#selected-template").val();
|
||||
let currentMode = $("#selected-mode").val();
|
||||
let currentType = $("#selected-type").val();
|
||||
|
||||
|
|
@ -9,7 +11,10 @@ $(document).ready(() => {
|
|||
const $pluginTypeSelect = $("#plugin-type-select");
|
||||
const $pluginKeywordSearch = $("#plugin-keyword-search");
|
||||
const $pluginDropdownMenu = $("#plugins-dropdown-menu");
|
||||
const pluginDropdownItems = $("#plugins-dropdown-menu li.nav-item");
|
||||
const $pluginDropdownItems = $("#plugins-dropdown-menu li.nav-item");
|
||||
const $templateSearch = $("#template-search");
|
||||
const $templateDropdownMenu = $("#templates-dropdown-menu");
|
||||
const $templateDropdownItems = $("#templates-dropdown-menu li.nav-item");
|
||||
|
||||
const updateUrlParams = (params, removeHash = false) => {
|
||||
const newUrl = new URL(window.location.href);
|
||||
|
|
@ -36,37 +41,64 @@ $(document).ready(() => {
|
|||
|
||||
// Prepare params for the URL update
|
||||
const params = {};
|
||||
if (currentType !== "all") params.type = currentType;
|
||||
params.mode = currentMode;
|
||||
if (currentMode === "advanced" && currentType !== "all")
|
||||
params.type = currentType;
|
||||
if (currentMode === "easy" && currentTemplate !== "high")
|
||||
params.template = currentTemplate;
|
||||
|
||||
// If "easy" is selected, remove the "mode" parameter
|
||||
if (currentMode === "easy") {
|
||||
params.mode = null; // Set mode to null to remove it from the URL
|
||||
updateUrlParams(params); // Call the function without the hash (keep it intact)
|
||||
params.type = null; // Remove the type parameter
|
||||
updateUrlParams(params, true); // Call the function without the hash (keep it intact)
|
||||
} else {
|
||||
// If another mode is selected, update the "mode" parameter
|
||||
params.mode = currentMode;
|
||||
updateUrlParams(params); // Keep the mode in the URL
|
||||
params.template = null; // Remove the template parameter
|
||||
if (currentMode === "advanced" && currentPlugin !== "general") {
|
||||
// Update the URL hash to the current plugin (e.g., #plugin-name)
|
||||
window.location.hash = currentPlugin;
|
||||
} else if (currentMode === "raw") {
|
||||
params.type = null; // Remove the type parameter
|
||||
}
|
||||
updateUrlParams(params, currentMode === "raw"); // Keep the mode in the URL
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (targetClass) => {
|
||||
currentPlugin = targetClass.substring(1).replace("navs-plugins-", "");
|
||||
|
||||
// Prepare the params for URL (parameters to be updated in the URL)
|
||||
const params = {};
|
||||
if (currentType !== "all") params.type = currentType;
|
||||
if (currentMode !== "easy") params.mode = currentMode;
|
||||
if (currentType !== "all") params.type = currentType;
|
||||
|
||||
// If "general" is selected and a hash exists, remove the hash but keep the parameters
|
||||
if (currentPlugin === "general" && window.location.hash) {
|
||||
// Call updateUrlParams with `removeHash = true` to remove the hash fragment
|
||||
updateUrlParams(params, true);
|
||||
} else {
|
||||
// Update the URL hash to the current plugin (e.g., #plugin-name)
|
||||
window.location.hash = currentPlugin;
|
||||
if (targetClass.includes("navs-plugins-")) {
|
||||
currentPlugin = targetClass.substring(1).replace("navs-plugins-", "");
|
||||
params.template = null; // Remove the template parameter
|
||||
|
||||
// Also update the URL parameters (if any exist) while preserving the hash
|
||||
updateUrlParams(params);
|
||||
// If "general" is selected and a hash exists, remove the hash but keep the parameters
|
||||
if (currentPlugin === "general" && window.location.hash) {
|
||||
// Call updateUrlParams with `removeHash = true` to remove the hash fragment
|
||||
updateUrlParams(params, true);
|
||||
} else {
|
||||
// Update the URL hash to the current plugin (e.g., #plugin-name)
|
||||
window.location.hash = currentPlugin;
|
||||
|
||||
// Also update the URL parameters (if any exist) while preserving the hash
|
||||
updateUrlParams(params);
|
||||
}
|
||||
} else if (targetClass.includes("navs-templates-")) {
|
||||
currentTemplate = targetClass.substring(1).replace("navs-templates-", "");
|
||||
params.type = null; // Remove the type parameter
|
||||
|
||||
// If "high" is selected, remove the "template" parameter
|
||||
if (currentTemplate === "high") {
|
||||
params.template = null; // Set template to null to remove it from the URL
|
||||
updateUrlParams(params); // Call the function without the hash (keep it intact)
|
||||
} else {
|
||||
// If another template is selected, update the "template" parameter
|
||||
params.template = currentTemplate;
|
||||
updateUrlParams(params); // Keep the template in the URL
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -109,7 +141,70 @@ $(document).ready(() => {
|
|||
}
|
||||
};
|
||||
|
||||
const getFormFromSettings = () => {
|
||||
// Function to validate inputs and display error messages
|
||||
const validateCurrentStepInputs = (currentStepContainer) => {
|
||||
let isStepValid = true;
|
||||
|
||||
currentStepContainer.find(".plugin-setting").each(function () {
|
||||
const $input = $(this);
|
||||
const value = $input.val().trim();
|
||||
const isRequired = $input.prop("required");
|
||||
const pattern = $input.attr("pattern");
|
||||
const fieldName =
|
||||
$input.data("field-name") || $input.attr("name") || "This field";
|
||||
|
||||
let errorMessage = "";
|
||||
let isValid = true;
|
||||
|
||||
// Custom error messages
|
||||
const requiredMessage =
|
||||
$input.data("required-message") || `${fieldName} is required.`;
|
||||
const patternMessage =
|
||||
$input.data("pattern-message") || `Please enter a valid ${fieldName}.`;
|
||||
|
||||
// Check if the field is required and not empty
|
||||
if (isRequired && value === "") {
|
||||
errorMessage = requiredMessage;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Validate based on pattern if the input is not empty
|
||||
if (isValid && pattern && value !== "") {
|
||||
const regex = new RegExp(pattern);
|
||||
if (!regex.test(value)) {
|
||||
errorMessage = patternMessage;
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle valid/invalid classes
|
||||
$input.toggleClass("is-invalid", !isValid);
|
||||
|
||||
// Manage the invalid-feedback element
|
||||
let $feedback = $input.next(".invalid-feedback");
|
||||
if (!$feedback.length) {
|
||||
$feedback = $('<div class="invalid-feedback"></div>').insertAfter(
|
||||
$input,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
$feedback.text(errorMessage);
|
||||
isStepValid = false;
|
||||
} else {
|
||||
$feedback.text("");
|
||||
}
|
||||
});
|
||||
|
||||
if (!isStepValid) {
|
||||
// Focus the first invalid input
|
||||
currentStepContainer.find(".is-invalid").first().focus();
|
||||
}
|
||||
|
||||
return isStepValid;
|
||||
};
|
||||
|
||||
const getFormFromSettings = (elem) => {
|
||||
const form = $("<form>", {
|
||||
method: "POST",
|
||||
action: window.location.href,
|
||||
|
|
@ -127,31 +222,48 @@ $(document).ready(() => {
|
|||
);
|
||||
};
|
||||
|
||||
const addChildrenToForm = (form, elem, isEasy = false) => {
|
||||
elem.find("input, select").each(function () {
|
||||
const $this = $(this);
|
||||
const settingName = $this.attr("name");
|
||||
const settingType = $this.attr("type");
|
||||
const originalValue = $this.data("original");
|
||||
let settingValue = $this.val();
|
||||
|
||||
if ($this.is("select")) {
|
||||
settingValue = $this.val();
|
||||
} else if (settingType === "checkbox") {
|
||||
settingValue = $this.is(":checked") ? "yes" : "no";
|
||||
}
|
||||
|
||||
if (!isEasy && settingValue == originalValue) return;
|
||||
|
||||
appendHiddenInput(form, settingName, settingValue);
|
||||
});
|
||||
};
|
||||
|
||||
// Handle missing CSRF token gracefully
|
||||
const csrfToken = $("#csrf_token").val() || "";
|
||||
appendHiddenInput(form, "csrf_token", csrfToken);
|
||||
|
||||
// TODO: support easy mode
|
||||
if (currentMode === undefined || currentMode === "advanced") {
|
||||
$("div[id^='navs-plugins-']")
|
||||
.find("input, select")
|
||||
.each(function () {
|
||||
const $this = $(this);
|
||||
const settingName = $this.attr("name");
|
||||
const settingType = $this.attr("type");
|
||||
const originalValue = $this.data("original");
|
||||
let settingValue = $this.val();
|
||||
if (currentMode === "easy") {
|
||||
const template = elem.data("template");
|
||||
appendHiddenInput(form, "USE_TEMPLATE", template);
|
||||
addChildrenToForm(form, $(`#navs-templates-${template}`), true);
|
||||
|
||||
if ($this.is("select")) {
|
||||
settingValue = $this.val();
|
||||
} else if (settingType === "checkbox") {
|
||||
settingValue = $this.is(":checked") ? "yes" : "no";
|
||||
}
|
||||
// Append 'IS_DRAFT' if it exists
|
||||
const $draftInput = $("#is-draft");
|
||||
if ($draftInput.length) {
|
||||
appendHiddenInput(form, "IS_DRAFT", $draftInput.val());
|
||||
}
|
||||
|
||||
if (settingValue == originalValue) return;
|
||||
|
||||
appendHiddenInput(form, settingName, settingValue);
|
||||
});
|
||||
// Append 'OLD_SERVER_NAME' if it exists
|
||||
const $oldServerName = $("#old-server-name");
|
||||
if ($oldServerName.length) {
|
||||
appendHiddenInput(form, "OLD_SERVER_NAME", $oldServerName.val());
|
||||
}
|
||||
} else if (currentMode === undefined || currentMode === "advanced") {
|
||||
addChildrenToForm(form, $("div[id^='navs-plugins-']"));
|
||||
|
||||
const $draftInput = $("#is-draft");
|
||||
if ($draftInput.length) {
|
||||
|
|
@ -243,7 +355,7 @@ $(document).ready(() => {
|
|||
const inputValue = e.target.value.toLowerCase();
|
||||
let visibleItems = 0;
|
||||
|
||||
pluginDropdownItems.each(function () {
|
||||
$pluginDropdownItems.each(function () {
|
||||
const item = $(this);
|
||||
const matches =
|
||||
(currentType === "all" || item.data("type") === currentType) &&
|
||||
|
|
@ -258,21 +370,54 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
if (visibleItems === 0) {
|
||||
if ($pluginDropdownMenu.find(".no-items").length === 0) {
|
||||
if ($pluginDropdownMenu.find(".no-plugin-items").length === 0) {
|
||||
$pluginDropdownMenu.append(
|
||||
'<li class="no-items dropdown-item text-muted">No Item</li>',
|
||||
'<li class="no-plugin-items dropdown-item text-muted">No Item</li>',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$pluginDropdownMenu.find(".no-items").remove();
|
||||
$pluginDropdownMenu.find(".no-plugin-items").remove();
|
||||
}
|
||||
}, 50),
|
||||
);
|
||||
|
||||
// Clear search and "No Item" message when the dropdown is closed
|
||||
$("#select-plugin").on("hidden.bs.dropdown", () => {
|
||||
$("#select-template").on("click", () => $templateSearch.focus());
|
||||
|
||||
$("#template-search").on(
|
||||
"input",
|
||||
debounce((e) => {
|
||||
const inputValue = e.target.value.toLowerCase();
|
||||
let visibleItems = 0;
|
||||
|
||||
$templateDropdownItems.each(function () {
|
||||
const item = $(this);
|
||||
const matches = item.text().toLowerCase().includes(inputValue);
|
||||
|
||||
item.toggle(matches);
|
||||
|
||||
if (matches) {
|
||||
visibleItems++; // Increment when an item is shown
|
||||
}
|
||||
});
|
||||
|
||||
if (visibleItems === 0) {
|
||||
if ($templateDropdownMenu.find(".no-template-items").length === 0) {
|
||||
$templateDropdownMenu.append(
|
||||
'<li class="no-template-items dropdown-item text-muted">No Item</li>',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$templateDropdownMenu.find(".no-template-items").remove();
|
||||
}
|
||||
}, 50),
|
||||
);
|
||||
|
||||
$(document).on("hidden.bs.dropdown", "#select-plugin", function () {
|
||||
$("#plugin-search").val("").trigger("input");
|
||||
$(".no-items").remove();
|
||||
});
|
||||
|
||||
$(document).on("hidden.bs.dropdown", "#select-template", function () {
|
||||
$("#template-search").val("").trigger("input");
|
||||
});
|
||||
|
||||
// Attach event listener to handle mode changes when tabs are switched
|
||||
|
|
@ -290,13 +435,22 @@ $(document).ready(() => {
|
|||
},
|
||||
);
|
||||
|
||||
$('#templates-dropdown-menu button[data-bs-toggle="tab"]').on(
|
||||
"shown.bs.tab",
|
||||
(e) => {
|
||||
handleTabChange($(e.target).data("bs-target"));
|
||||
},
|
||||
);
|
||||
|
||||
$(document).on("input", ".plugin-setting", function () {
|
||||
const isValid = $(this).data("pattern")
|
||||
? new RegExp($(this).data("pattern")).test($(this).val())
|
||||
: true;
|
||||
$(this)
|
||||
.toggleClass("is-valid", isValid)
|
||||
.toggleClass("is-invalid", !isValid);
|
||||
debounce(() => {
|
||||
const isValid = $(this).attr("pattern")
|
||||
? new RegExp($(this).attr("pattern")).test($(this).val())
|
||||
: true;
|
||||
$(this)
|
||||
.toggleClass("is-valid", isValid)
|
||||
.toggleClass("is-invalid", !isValid);
|
||||
}, 100)();
|
||||
});
|
||||
|
||||
$(document).on("focusout", ".plugin-setting", function () {
|
||||
|
|
@ -310,7 +464,7 @@ $(document).ready(() => {
|
|||
|
||||
updateUrlParams(params);
|
||||
|
||||
pluginDropdownItems.each(function () {
|
||||
$pluginDropdownItems.each(function () {
|
||||
const typeMatches =
|
||||
currentType === "all" || $(this).data("type") === currentType;
|
||||
$(this).toggle(typeMatches);
|
||||
|
|
@ -325,54 +479,44 @@ $(document).ready(() => {
|
|||
}
|
||||
});
|
||||
|
||||
const findMatchingSettings = (keyword) => {
|
||||
let matchedPlugin = null;
|
||||
let matchedSettings = $();
|
||||
|
||||
$("div[id^='navs-plugins-']").each(function () {
|
||||
const $plugin = $(this);
|
||||
const pluginId = $plugin.attr("id").replace("navs-plugins-", "");
|
||||
const pluginType = $plugin.data("type"); // Get the type of the plugin (core, external, pro)
|
||||
|
||||
// If the currentType filter is not "all" and the plugin's type doesn't match the currentType, skip this plugin
|
||||
if (currentType !== "all" && pluginType !== currentType) {
|
||||
return; // Skip this plugin
|
||||
}
|
||||
|
||||
// Find settings that match the keyword based on label text or input/select name
|
||||
const matchingSettings = $plugin.find(".form-label").filter(function () {
|
||||
const $label = $(this);
|
||||
const settingName = $label.attr("for") || "";
|
||||
const labelText = $label.text().toLowerCase();
|
||||
|
||||
// Find the associated input/select element using the "for" attribute
|
||||
const $inputElement = $("#" + settingName);
|
||||
const inputName = $inputElement.attr("name") || "";
|
||||
|
||||
// Match either the label text or the input/select name
|
||||
return (
|
||||
labelText.includes(keyword) ||
|
||||
inputName.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (matchingSettings.length > 0) {
|
||||
matchedPlugin = pluginId;
|
||||
matchedSettings = matchingSettings.closest(".col-12");
|
||||
return false; // Stop searching after finding a plugin with matching settings
|
||||
}
|
||||
});
|
||||
|
||||
return { matchedPlugin, matchedSettings };
|
||||
};
|
||||
|
||||
$pluginKeywordSearch.on(
|
||||
"input",
|
||||
debounce((e) => {
|
||||
const keyword = e.target.value.toLowerCase().trim();
|
||||
if (!keyword) return;
|
||||
|
||||
const { matchedPlugin, matchedSettings } = findMatchingSettings(keyword);
|
||||
let matchedPlugin = null;
|
||||
let matchedSettings = $();
|
||||
|
||||
$("div[id^='navs-plugins-']").each(function () {
|
||||
const $plugin = $(this);
|
||||
const pluginId = $plugin.attr("id").replace("navs-plugins-", "");
|
||||
const pluginType = $plugin.data("type"); // Get the type of the plugin (core, external, pro)
|
||||
|
||||
// If the currentType filter is not "all" and the plugin's type doesn't match the currentType, skip this plugin
|
||||
if (currentType !== "all" && pluginType !== currentType) {
|
||||
return; // Skip this plugin
|
||||
}
|
||||
|
||||
// Find settings that match the keyword based on label text or input/select name
|
||||
const matchingSettings = $plugin
|
||||
.find("input, select")
|
||||
.filter(function () {
|
||||
const $input = $(this);
|
||||
const settingName = ($input.attr("name") || "").toLowerCase();
|
||||
const label = $input.next("label");
|
||||
const labelText = (label.text() || "").toLowerCase();
|
||||
|
||||
// Match either the label text or the input/select name
|
||||
return labelText.includes(keyword) || settingName.includes(keyword);
|
||||
});
|
||||
|
||||
if (matchingSettings.length > 0) {
|
||||
matchedPlugin = pluginId;
|
||||
matchedSettings = matchingSettings.closest(".col-12");
|
||||
return false; // Stop searching after finding a plugin with matching settings
|
||||
}
|
||||
});
|
||||
|
||||
if (matchedPlugin) {
|
||||
// Automatically switch to the plugin tab
|
||||
|
|
@ -586,31 +730,39 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
$(".save-settings").on("click", function () {
|
||||
const form = getFormFromSettings();
|
||||
// TODO: support easy mode
|
||||
let minSettings = 4;
|
||||
if (!form.find("input[name='IS_DRAFT']").length) minSettings = 2;
|
||||
const form = getFormFromSettings($(this));
|
||||
if (currentMode === "easy") {
|
||||
const currentStep = parseInt($(this).data("current-step"));
|
||||
const template = $(this).data("template");
|
||||
const currentStepId = `navs-steps-${template}-${currentStep}`;
|
||||
const currentStepContainer = $(`#${currentStepId}`);
|
||||
const isStepValid = validateCurrentStepInputs(currentStepContainer);
|
||||
if (!isStepValid) return;
|
||||
} else {
|
||||
let minSettings = 4;
|
||||
if (!form.find("input[name='IS_DRAFT']").length) minSettings = 2;
|
||||
|
||||
const draftInput = $("#is-draft");
|
||||
const wasDraft = draftInput.data("original") === "yes";
|
||||
let isDraft = draftInput.val() === "yes";
|
||||
if (currentMode === "raw")
|
||||
isDraft = form.find("input[name='IS_DRAFT']").val() === "yes";
|
||||
const draftInput = $("#is-draft");
|
||||
const wasDraft = draftInput.data("original") === "yes";
|
||||
let isDraft = draftInput.val() === "yes";
|
||||
if (currentMode === "raw")
|
||||
isDraft = form.find("input[name='IS_DRAFT']").val() === "yes";
|
||||
|
||||
if (form.children().length < minSettings && isDraft === wasDraft) {
|
||||
alert("No changes detected.");
|
||||
return;
|
||||
if (form.children().length < minSettings && isDraft === wasDraft) {
|
||||
alert("No changes detected.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$(window).off("beforeunload");
|
||||
form.appendTo("body").submit();
|
||||
});
|
||||
|
||||
$("#toggle-draft").on("click", function () {
|
||||
$(".toggle-draft").on("click", function () {
|
||||
const draftInput = $("#is-draft");
|
||||
const isDraft = draftInput.val() === "yes";
|
||||
|
||||
draftInput.val(isDraft ? "no" : "yes");
|
||||
$(this).html(
|
||||
$(".toggle-draft").html(
|
||||
`<i class="bx bx-sm bx-${
|
||||
isDraft ? "globe" : "file-blank"
|
||||
} bx-sm"></i> ${isDraft ? "Online" : "Draft"}`,
|
||||
|
|
@ -638,6 +790,54 @@ $(document).ready(() => {
|
|||
});
|
||||
});
|
||||
|
||||
$(document).on("click", ".next-step, .previous-step", function () {
|
||||
const template = $(this).data("template");
|
||||
let currentStep = parseInt($(this).data("current-step"));
|
||||
const isNext = $(this).hasClass("next-step");
|
||||
|
||||
// Determine the new step
|
||||
const newStep = isNext ? currentStep + 1 : currentStep - 1;
|
||||
const currentStepId = `navs-steps-${template}-${currentStep}`;
|
||||
const newStepId = `navs-steps-${template}-${newStep}`;
|
||||
|
||||
const currentStepContainer = $(`#${currentStepId}`);
|
||||
const newTabTrigger = $(`button[data-bs-target="#${newStepId}"]`);
|
||||
const currentTabTrigger = $(`button[data-bs-target="#${currentStepId}"]`);
|
||||
|
||||
if (newTabTrigger.length) {
|
||||
if (isNext) {
|
||||
const isStepValid = validateCurrentStepInputs(currentStepContainer);
|
||||
|
||||
if (!isStepValid) {
|
||||
// Prevent proceeding to the next step
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentTabTrigger
|
||||
.parent()
|
||||
.find("div.text-primary")
|
||||
.removeClass("text-primary")
|
||||
.addClass("text-muted");
|
||||
currentTabTrigger.addClass("disabled");
|
||||
|
||||
// Activate the new tab
|
||||
const newTab = new bootstrap.Tab(newTabTrigger[0]);
|
||||
newTab.show();
|
||||
newTabTrigger
|
||||
.parent()
|
||||
.find("div.text-muted")
|
||||
.removeClass("text-muted")
|
||||
.addClass("text-primary");
|
||||
newTabTrigger.removeClass("disabled");
|
||||
newTabTrigger[0].scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('div[id^="multiple-"]')
|
||||
.filter(function () {
|
||||
return /^multiple-.*-\d+$/.test($(this).attr("id"));
|
||||
|
|
@ -696,9 +896,24 @@ $(document).ready(() => {
|
|||
}
|
||||
});
|
||||
|
||||
if (
|
||||
(usedTemplate === "" || usedTemplate === "ui") &&
|
||||
currentMode === "easy"
|
||||
) {
|
||||
$(`button[data-bs-target="#navs-modes-advanced"]`).tab("show");
|
||||
} else if (usedTemplate !== "high" && currentMode === "easy") {
|
||||
$(`button[data-bs-target="#navs-templates-${usedTemplate}"]`).tab("show");
|
||||
}
|
||||
|
||||
if (currentMode === "easy" && currentTemplate !== "high") {
|
||||
$(`button[data-bs-target="#navs-templates-${currentTemplate}"]`).tab(
|
||||
"show",
|
||||
);
|
||||
}
|
||||
|
||||
var hasExternalPlugins = false;
|
||||
var hasProPlugins = false;
|
||||
pluginDropdownItems.each(function () {
|
||||
$pluginDropdownItems.each(function () {
|
||||
const type = $(this).data("type");
|
||||
if (type === "external") {
|
||||
hasExternalPlugins = true;
|
||||
|
|
@ -723,11 +938,13 @@ $(document).ready(() => {
|
|||
if (targetTab.length) targetTab.tab("show");
|
||||
}
|
||||
|
||||
$pluginTypeSelect.trigger("change");
|
||||
if (currentType !== "all") {
|
||||
$pluginTypeSelect.trigger("change");
|
||||
}
|
||||
|
||||
if (currentMode === "advanced") {
|
||||
const serverNameSetting = $("#setting-general-server-name");
|
||||
if (!serverNameSetting.val()) {
|
||||
if (serverNameSetting.val() === "") {
|
||||
if (currentType !== "all") {
|
||||
currentType = "all";
|
||||
$pluginTypeSelect.val("all");
|
||||
|
|
@ -766,18 +983,19 @@ $(document).ready(() => {
|
|||
}
|
||||
|
||||
$(window).on("beforeunload", function (e) {
|
||||
const form = getFormFromSettings();
|
||||
// TODO: support easy mode
|
||||
let minSettings = 4;
|
||||
if (!form.find("input[name='IS_DRAFT']").length) minSettings = 2;
|
||||
const form = getFormFromSettings($(this));
|
||||
if (currentMode !== "easy") {
|
||||
let minSettings = 4;
|
||||
if (!form.find("input[name='IS_DRAFT']").length) minSettings = 2;
|
||||
|
||||
const draftInput = $("#is-draft");
|
||||
const wasDraft = draftInput.data("original") === "yes";
|
||||
let isDraft = draftInput.val() === "yes";
|
||||
if (currentMode === "raw")
|
||||
isDraft = form.find("input[name='IS_DRAFT']").val() === "yes";
|
||||
const draftInput = $("#is-draft");
|
||||
const wasDraft = draftInput.data("original") === "yes";
|
||||
let isDraft = draftInput.val() === "yes";
|
||||
if (currentMode === "raw")
|
||||
isDraft = form.find("input[name='IS_DRAFT']").val() === "yes";
|
||||
|
||||
if (form.children().length < minSettings && isDraft === wasDraft) return;
|
||||
if (form.children().length < minSettings && isDraft === wasDraft) return;
|
||||
}
|
||||
|
||||
// Cross-browser compatibility (for older browsers)
|
||||
var message =
|
||||
|
|
|
|||
|
|
@ -3,6 +3,21 @@
|
|||
<!-- Content -->
|
||||
{% set blacklisted_settings = get_blacklisted_settings(true) %}
|
||||
{% set service_method = "ui" %}
|
||||
{% set plugin_types = {
|
||||
"core": {
|
||||
"icon": "<i class=\"bx bx-cube\"></i>",
|
||||
"title-class": " border-dark"
|
||||
},
|
||||
"external": {
|
||||
"icon": "<i class=\"bx bx-plug\"></i>",
|
||||
"title-class": " border-secondary",
|
||||
"text-class": " text-secondary fw-bold"
|
||||
},
|
||||
"pro": {
|
||||
"title-class": " border-primary",
|
||||
"text-class": " text-primary fw-bold shine"
|
||||
}
|
||||
} %}
|
||||
{% include "models/plugins_settings.html" %}
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -73,21 +73,21 @@
|
|||
</button>
|
||||
</li>
|
||||
<div class="collapse w-100 show" id="pluginsCollapse">
|
||||
{% for plugin in plugins %}
|
||||
{% with not_pro_pro_plugin = not is_pro_version and plugin['type'] == "pro" %}
|
||||
{% if not_pro_pro_plugin or plugin['page'] %}
|
||||
<li class="menu-item{% if current_endpoint == plugin['id'] %} active{% endif %}"{% if not_pro_pro_plugin %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
|
||||
{% for plugin, plugin_data in plugins.items() %}
|
||||
{% with not_pro_pro_plugin = not is_pro_version and plugin_data['type'] == "pro" %}
|
||||
{% if not_pro_pro_plugin or plugin_data['page'] %}
|
||||
<li class="menu-item{% if current_endpoint == plugin %} active{% endif %}"{% if not_pro_pro_plugin %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="<i class='bx bx-diamond bx-xs'></i><span>Pro feature</span>"
|
||||
{% endif %}
|
||||
>
|
||||
<a href="{% if not_pro_pro_plugin %}https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro{% else %}{{ url_for("plugins") }}/{{ plugin['id'] }}{% endif %}"
|
||||
<a href="{% if not_pro_pro_plugin %}https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui#pro{% else %}{{ url_for("plugins") }}/{{ plugin }}{% endif %}"
|
||||
class="menu-link"
|
||||
{% if not_pro_pro_plugin %}target="_blank" rel="noopener"{% endif %}>
|
||||
<i class="menu-icon tf-icons bx bx-puzzle"></i>
|
||||
<div class="text-truncate{% if plugin['type'] == 'pro' %} text-primary shine shine-sm{% endif %} pe-2"
|
||||
data-i18n="{{ plugin['name'] }}">{{ plugin['name'] }}</div>
|
||||
{% if plugin['type'] != "pro" %}
|
||||
<div class="text-truncate{% if plugin_data['type'] == 'pro' %} text-primary shine shine-sm{% endif %} pe-2"
|
||||
data-i18n="{{ plugin_data['name'] }}">{{ plugin_data['name'] }}</div>
|
||||
{% if plugin_data['type'] != "pro" %}
|
||||
<div class="badge rounded-pill bg-label-{% if plugin['type'] == 'core' %}secondary{% else %}primary{% endif %} text-uppercase fs-tiny ms-auto">
|
||||
{{ plugin['type'].title() }}
|
||||
{{ plugin_data['type'].title() }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="badge badge-center rounded-pill text-uppercase fs-tiny ms-auto">
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
<div class="form-check form-switch mt-1">
|
||||
<input id="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
<input id="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
name="{{ setting }}"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-labelledby="{{ setting_id_prefix }}label-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
aria-labelledby="label-{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
data-original="{% if current_endpoint != 'new' %}{{ setting_value }}{% else %}{{ setting_default }}{% endif %}"
|
||||
data-default="{{ setting_default }}"
|
||||
{% if setting_value == "yes" %}checked{% endif %}
|
||||
{% if disabled %}disabled{% endif %}>
|
||||
{% if disabled %}disabled{% endif %}
|
||||
{% if required %}required{% endif %}>
|
||||
<label class="form-check setting-checkbox-label d-flex align-items-center ps-0"
|
||||
for="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}">
|
||||
{{ setting }}
|
||||
</label>
|
||||
for="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}">{{ setting }}</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
<div class="form-floating mt-1{% if setting_data['type'] == 'password' %} input-group input-group-merge form-password-toggle{% endif %}">
|
||||
<input id="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
<input id="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
name="{{ setting }}"
|
||||
type="{{ setting_data['type'] }}"
|
||||
class="form-control plugin-setting"
|
||||
aria-labelledby="{{ setting_id_prefix }}label-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
aria-labelledby="label-{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
pattern="{{ setting_data['regex'] }}"
|
||||
value="{{ setting_value }}"
|
||||
data-original="{% if current_endpoint != 'new' %}{{ setting_value }}{% else %}{{ setting_default }}{% endif %}"
|
||||
data-default="{{ setting_default }}"
|
||||
{% if disabled %}disabled{% endif %}>
|
||||
<label for="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}">
|
||||
{{ setting }}
|
||||
</label>
|
||||
{% if disabled %}disabled{% endif %}
|
||||
{% if required %}required{% endif %}>
|
||||
<label for="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}">{{ setting }}</label>
|
||||
{% if setting_data['type'] == 'password' %}
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -29,15 +29,17 @@
|
|||
placeholder="Search..."
|
||||
aria-label="Search...">
|
||||
</div>
|
||||
{% for plugin in plugins if get_filtered_settings(plugin["settings"], current_endpoint == "global-config") %}
|
||||
<li class="nav-item" data-type="{{ plugin['type'] }}">
|
||||
{% for plugin, plugin_data in plugins.items() if get_filtered_settings(plugin_data["settings"], current_endpoint == "global-config") %}
|
||||
<li class="nav-item" data-type="{{ plugin_data['type'] }}">
|
||||
<button type="button"
|
||||
class="dropdown-item{% if loop.index == 1 %} active{% endif %}"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-plugins-{{ plugin['id'] }}"
|
||||
aria-controls="navs-plugins-{{ plugin['id'] }}"
|
||||
{% if loop.index == 1 %}aria-selected="true"{% endif %}>{{ plugin["name"] }}</button>
|
||||
data-bs-target="#navs-plugins-{{ plugin }}"
|
||||
aria-controls="navs-plugins-{{ plugin }}"
|
||||
{% if loop.index == 1 %}aria-selected="true"{% endif %}>
|
||||
{{ plugin_data["name"] }}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -65,15 +67,8 @@
|
|||
{% if current_endpoint != "global-config" %}
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
{% set is_draft = config.get('IS_DRAFT', {}).get('value', 'no') %}
|
||||
<input type="hidden"
|
||||
id="is-draft"
|
||||
name="IS_DRAFT"
|
||||
value="{{ is_draft }}"
|
||||
data-original="{% if current_endpoint != 'new' %}{{ is_draft }}{% else %}no{% endif %}"
|
||||
data-default="no">
|
||||
<button id="toggle-draft"
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary me-3 {% if current_endpoint != 'new' and service_method != 'ui' %}disabled{% endif %}">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary toggle-draft me-3 {% if current_endpoint != 'new' and service_method != 'ui' %}disabled{% endif %}">
|
||||
<i class="bx bx-sm bx-{% if is_draft == 'yes' %}file-blank{% else %}globe{% endif %}"></i>
|
||||
<span class="d-none d-md-inline">
|
||||
{% if is_draft == 'yes' %}
|
||||
|
|
@ -95,56 +90,43 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% set plugin_types = {
|
||||
"core": {
|
||||
"icon": "<i class=\"bx bx-cube\"></i>",
|
||||
"title-class": " border-dark"
|
||||
},
|
||||
"external": {
|
||||
"icon": "<i class=\"bx bx-plug\"></i>",
|
||||
"title-class": " border-secondary text-secondary fw-bold"
|
||||
},
|
||||
"pro": {
|
||||
"title-class": " border-primary text-primary fw-bold shine"
|
||||
}
|
||||
} %}
|
||||
<div class="card tab-content m-1 p-2 position-relative">
|
||||
{% for plugin in plugins %}
|
||||
{% set filtered_settings = get_filtered_settings(plugin["settings"], current_endpoint == "global-config") %}
|
||||
{% for plugin, plugin_data in plugins.items() %}
|
||||
{% set filtered_settings = get_filtered_settings(plugin_data["settings"], current_endpoint == "global-config") %}
|
||||
{% if filtered_settings %}
|
||||
<div id="navs-plugins-{{ plugin['id'] }}"
|
||||
<div id="navs-plugins-{{ plugin }}"
|
||||
class="tab-pane fade{% if loop.index == 1 %} show active{% endif %}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="navs-plugins-{{ plugin['id'] }}-tab"
|
||||
data-type="{{ plugin['type'] }}">
|
||||
aria-labelledby="navs-plugins-{{ plugin }}-tab"
|
||||
data-type="{{ plugin_data['type'] }}">
|
||||
<div class="card-header d-flex justify-content-between align-items-center mw-100">
|
||||
<div class="pt-1 flex-grow-1 me-2" style="min-width: 0;">
|
||||
<h5 class="card-title d-inline border p-2{{ plugin_types[plugin['type']].get('title-class', '') }}">
|
||||
{{ plugin["name"] }} - v{{ plugin["version"] }} - {{ plugin_types[plugin["type"]].get('icon', '<img src="' + pro_diamond_url + '"
|
||||
<h5 class="card-title d-inline border p-2{{ plugin_types[plugin_data['type']].get('text-class', '') }}{{ plugin_types[plugin_data['type']].get('title-class', '') }}">
|
||||
{{ plugin_data["name"] }} - v{{ plugin_data["version"] }} - {{ plugin_types[plugin_data["type"]].get('icon', '<img src="' + pro_diamond_url + '"
|
||||
alt="Pro plugin"
|
||||
width="18px"
|
||||
height="15.5px">') |safe }}
|
||||
</h5>
|
||||
<p class="card-subtitle text-muted text-truncate mt-2">{{ plugin["description"] }}</p>
|
||||
<p class="card-subtitle text-muted text-truncate mt-2">{{ plugin_data["description"] }}</p>
|
||||
</div>
|
||||
<div class="d-flex flex-grow-0 flex-shrink-0 justify-content-end align-items-center">
|
||||
<a href="https://docs.bunkerweb.io/latest/quickstart-guide/#protect-udptcp-applications"
|
||||
class="btn btn-sm btn-{% if plugin['stream'] == 'yes' %}bw-green{% elif plugin['stream'] == 'partial' %}warning{% else %}danger{% endif %} rounded-pill"
|
||||
class="btn btn-sm btn-{% if plugin_data['stream'] == 'yes' %}bw-green{% elif plugin_data['stream'] == 'partial' %}warning{% else %}danger{% endif %} rounded-pill"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% if plugin['stream'] != 'no' %}Supports{% else %}Doesn't support{% endif %} STREAM mode{% if plugin['stream'] == 'partial' %} partially{% endif %}">
|
||||
<i class="bx bx-{% if plugin['stream'] == 'yes' %}badge-check{% elif plugin['stream'] == 'partial' %}message-square-detail{% else %}no-entry{% endif %}"></i> STREAM
|
||||
title="{% if plugin_data['stream'] != 'no' %}Supports{% else %}Doesn't support{% endif %} STREAM mode{% if plugin_data['stream'] == 'partial' %} partially{% endif %}">
|
||||
<i class="bx bx-{% if plugin_data['stream'] == 'yes' %}badge-check{% elif plugin_data['stream'] == 'partial' %}message-square-detail{% else %}no-entry{% endif %}"></i> STREAM
|
||||
</a>
|
||||
<a href="{% if plugin['type'] == 'core' %}https://docs.bunkerweb.io/latest/settings/?utm_campaign=self&utm_source=ui#{% if plugin['id'] == 'general' %}global-settings{% else %}{{ plugin['id'] }}{% endif %}{% else %}https://docs.bunkerweb.io/latest/plugins/?utm_campaign=self&utm_source=ui{% endif %}"
|
||||
<a href="{% if plugin_data['type'] == 'core' %}https://docs.bunkerweb.io/latest/settings/?utm_campaign=self&utm_source=ui#{% if plugin == 'general' %}global-settings{% else %}{{ plugin }}{% endif %}{% else %}https://docs.bunkerweb.io/latest/plugins/?utm_campaign=self&utm_source=ui{% endif %}"
|
||||
class="btn btn-sm btn-primary rounded-pill ms-2"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<i class="bx bx-link"></i> More info
|
||||
</a>
|
||||
{% if plugin["page"] %}
|
||||
<a href="{{ url_for("plugins") }}/{{ plugin['id'] }}"
|
||||
{% if plugin_data["page"] %}
|
||||
<a href="{{ url_for("plugins") }}/{{ plugin }}"
|
||||
class="btn btn-sm btn-primary rounded-pill ms-2">
|
||||
<i class="bx bxs-file-html"></i> Custom page
|
||||
</a>
|
||||
|
|
@ -153,12 +135,13 @@
|
|||
</div>
|
||||
<div class="card-body row pb-0">
|
||||
{% for setting, setting_data in filtered_settings.items() if not setting_data.get('multiple', false) and setting not in blacklisted_settings and (not service_endpoint or setting_data['context'] == "multisite") %}
|
||||
{% set setting_id_prefix = "setting-" + plugin + "-" %}
|
||||
{% set setting_config = config.get(setting, {}) %}
|
||||
{% set setting_default = setting_data.get("default", "") %}
|
||||
{% set setting_value = setting_config.get("value", setting_default) %}
|
||||
{% set setting_method = setting_config.get("method", "default") %}
|
||||
{% set setting_template = setting_config.get("template", "") %}
|
||||
{% set disabled = setting_method not in ('ui', 'default') %}
|
||||
{% set disabled = setting_method not in ('ui', 'default') and (current_endpoint == "global-config" or not setting_config.get("global")) %}
|
||||
{% if current_endpoint == "new" %}
|
||||
{% set disabled = false %}
|
||||
{% if setting == "SERVER_NAME" %}
|
||||
|
|
@ -172,11 +155,9 @@
|
|||
<div class="col-12 col-sm-6 col-lg-4 pb-3"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="label-{{ plugin['id'] }}-{{ setting_data['id'] }}"
|
||||
for="setting-{{ plugin['id'] }}-{{ setting_data['id'] }}"
|
||||
class="form-label fw-semibold text-truncate">
|
||||
{{ setting_data["label"]|capitalize }}
|
||||
</label>
|
||||
<label id="label-setting-{{ plugin }}-{{ setting_data['id'] }}"
|
||||
for="setting-{{ plugin }}-{{ setting_data['id'] }}"
|
||||
class="form-label fw-semibold text-truncate">{{ setting_data["label"] }}</label>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if current_endpoint == "global-config" and setting_data["context"] == "multisite" %}
|
||||
<a role="badge"
|
||||
|
|
@ -198,10 +179,18 @@
|
|||
<span class="bx bx-spreadsheet bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if current_endpoint != "global-config" and setting_config.get("global") and setting_value != setting_default %}
|
||||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help']|capitalize }}">
|
||||
title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -224,18 +213,18 @@
|
|||
</div>
|
||||
{% set plugin_multiples = get_multiples(filtered_settings, config) %}
|
||||
{% if plugin_multiples %}
|
||||
{% set setting_id_prefix = "multiple-" %}
|
||||
{% set setting_id_prefix = "multiple-setting-" + plugin +"-" %}
|
||||
{% set multiple_plugin_multiples = plugin_multiples|length > 1 %}
|
||||
<div class="card-header pb-2 mt-6">
|
||||
<h5 class="card-title d-inline border p-2{{ plugin_types[plugin['type']].get('title-class', '') }}">
|
||||
<h5 class="card-title d-inline border p-2{{ plugin_types[plugin_data['type']].get('text-class', '') }}{{ plugin_types[plugin_data['type']].get('title-class', '') }}">
|
||||
Multiple settings
|
||||
</h5>
|
||||
<p class="card-subtitle text-muted mt-2">This is where you can configure multiple settings for this plugin.</p>
|
||||
</div>
|
||||
<div class="card-body row card-body pt-0">
|
||||
<div class="card-body row pt-0">
|
||||
{% for multiple, multiples in plugin_multiples.items() %}
|
||||
{% set multiple_settings = settings|length > 1 %}
|
||||
<div id="multiple-{{ plugin['id'] }}-{{ multiple }}"
|
||||
<div id="multiple-{{ plugin }}-{{ multiple }}"
|
||||
class="col-12{% if multiple_plugin_multiples %} col-md-6{% endif %}">
|
||||
<!-- TODO: Handle if multiple_plugin_multiples|length > 2 and not multiple_settings -> col-lg-4 via JS -->
|
||||
{% for setting_suffix, settings in multiples.items() %}
|
||||
|
|
@ -248,32 +237,32 @@
|
|||
</h6>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if setting_suffix == "0" %}
|
||||
<button id="add-multiple-{{ plugin['id'] }}-{{ multiple }}"
|
||||
<button id="add-multiple-{{ plugin }}-{{ multiple }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-bw-green rounded-pill add-multiple p-0 pe-2">
|
||||
<i class="bx bx-plus-circle bx-sm"></i> ADD
|
||||
</button>
|
||||
{% else %}
|
||||
<div>
|
||||
<button id="remove-multiple-{{ plugin['id'] }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
<button id="remove-multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-danger rounded-pill remove-multiple p-0 pe-2">
|
||||
<i class="bx bx-trash bx-sm"></i> REMOVE
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button id="show-multiple-{{ plugin['id'] }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
<button id="show-multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-secondary rounded-pill show-multiple p-0"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#multiple-{{ plugin['id'] }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
data-bs-target="#multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
aria-expanded="true"
|
||||
aria-controls="multiple-{{ plugin['id'] }}-{{ multiple }}-{{ setting_suffix }}">
|
||||
aria-controls="multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}">
|
||||
<i class="bx bx-show-alt bx-sm"></i> HIDE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="multiple-{{ plugin['id'] }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
<div id="multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
class="collapse show multiple-collapse pt-0">
|
||||
<div class="row mt-2 pt-2">
|
||||
{% for setting, setting_data in settings.items() if setting not in blacklisted_settings %}
|
||||
|
|
@ -282,7 +271,10 @@
|
|||
{% set setting_value = setting_config.get("value", setting_default) %}
|
||||
{% set setting_method = setting_config.get("method", "default") %}
|
||||
{% set setting_template = setting_config.get("template", "") %}
|
||||
{% set disabled = current_endpoint != "new" and setting_method not in ('ui', 'default') %}
|
||||
{% set disabled = setting_method not in ('ui', 'default') and (current_endpoint == "global-config" or not setting_config.get("global")) %}
|
||||
{% if current_endpoint == "new" %}
|
||||
{% set disabled = false %}
|
||||
{% endif %}
|
||||
{% if service_method == "autoconf" %}
|
||||
{% set setting_method = "autoconf" %}
|
||||
{% set disabled = true %}
|
||||
|
|
@ -290,10 +282,10 @@
|
|||
<div class="col-12{% if multiple_settings %} col-md-6{% endif %}{% if settings|length > 2 and not multiple_multiples %} col-lg-4{% endif %} pb-2"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="multiple-label-{{ plugin['id'] }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
for="multiple-setting-{{ plugin['id'] }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
<label id="label-multiple-setting-{{ plugin }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
for="multiple-setting-{{ plugin }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
class="form-label fw-semibold text-truncate">
|
||||
{{ setting_data["label"]|capitalize }}
|
||||
{{ setting_data["label"] }}
|
||||
</label>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if current_endpoint == "global-config" and setting_data["context"] == "multisite" %}
|
||||
|
|
@ -316,10 +308,18 @@
|
|||
<span class="bx bx-spreadsheet bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if current_endpoint != "global-config" and setting_config.get("global") and setting_value != setting_default %}
|
||||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help']|capitalize }}">
|
||||
title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
{% set plugins_settings = get_plugins_settings() %}
|
||||
<div class="card p-1 mb-4 sticky-card">
|
||||
<div class="d-flex flex-wrap justify-content-around align-items-center">
|
||||
<div class="dropdown btn-group">
|
||||
<button id="select-template"
|
||||
type="button"
|
||||
class="btn btn-outline-primary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="bx bx-notepad"></i>
|
||||
<span class="d-none d-md-inline"> Templates</span>
|
||||
</button>
|
||||
<ul id="templates-dropdown-menu"
|
||||
class="dropdown-menu nav-pills max-vh-60 overflow-auto pt-0"
|
||||
role="tablist">
|
||||
<div class="input-group input-group-merge mb-2">
|
||||
<span class="input-group-text p-2 border-0 border-primary border-bottom shadow-none"><i class="bx fs-6 bx-search"></i></span>
|
||||
<input id="template-search"
|
||||
type="text"
|
||||
class="form-control border-0 border-primary border-bottom shadow-none"
|
||||
placeholder="Search..."
|
||||
aria-label="Search...">
|
||||
</div>
|
||||
{% for template in templates if template != "ui" %}
|
||||
<li class="nav-item">
|
||||
<button type="button"
|
||||
class="dropdown-item{% if loop.index == 1 %} active{% endif %}"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-templates-{{ template }}"
|
||||
aria-controls="navs-templates-{{ template }}"
|
||||
{% if loop.index == 1 %}aria-selected="true"{% endif %}>
|
||||
{{ template|capitalize }}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{% if current_endpoint != "global-config" %}
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
{% set is_draft = config.get('IS_DRAFT', {}).get('value', 'no') %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary toggle-draft me-3 {% if current_endpoint != 'new' and service_method != 'ui' %}disabled{% endif %}">
|
||||
<i class="bx bx-sm bx-{% if is_draft == 'yes' %}file-blank{% else %}globe{% endif %}"></i>
|
||||
<span class="d-none d-md-inline">
|
||||
{% if is_draft == 'yes' %}
|
||||
Draft
|
||||
{% else %}
|
||||
Online
|
||||
{% endif %}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card tab-content m-1 p-2 position-relative">
|
||||
{% for template, template_data in templates.items() if template != "ui" %}
|
||||
{% set template_plugin = plugins[template_data['plugin_id']] %}
|
||||
<div id="navs-templates-{{ template }}"
|
||||
class="tab-pane fade{% if loop.index == 1 %} show active{% endif %}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="navs-templates-{{ template }}-tab">
|
||||
<div class="card-header d-flex align-items-center mw-100">
|
||||
<div class="pt-1">
|
||||
<h5 class="card-title d-inline border p-2{{ plugin_types[template_plugin['type']].get('text-class', '') }}{{ plugin_types[template_plugin['type']].get('title-class', '') }}">
|
||||
{{ template|capitalize }} - {{ plugin_types[template_plugin["type"]].get('icon', '<img src="' + pro_diamond_url + '"
|
||||
alt="Pro plugin"
|
||||
width="18px"
|
||||
height="15.5px">') |safe }}
|
||||
</h5>
|
||||
<p class="card-subtitle text-muted text-truncate mt-2">{{ template_data["name"] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<nav class="p-3 template-steps-container align-items-center mw-100 border rounded-top{{ plugin_types[template_plugin['type']].get('title-class', '') }}"
|
||||
aria-label="breadcrumb">
|
||||
<ol class="breadcrumb nav nav-scroller mb-0 flex-nowrap overflow-hidden{% if loop.index == 1 %} active{% endif %}"
|
||||
role="tablist">
|
||||
{% for step in template_data["steps"] %}
|
||||
<li class="breadcrumb-item nav-link d-flex align-items-center pe-0">
|
||||
<button class="btn btn-primary pt-3 pb-3 me-3{% if loop.index == 1 %} active{% else %} disabled{% endif %}"
|
||||
role="tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-steps-{{ template }}-{{ loop.index }}"
|
||||
aria-controls="navs-steps-{{ template }}-{{ loop.index }}"
|
||||
{% if loop.index == 1 %}aria-selected="true"{% endif %}>
|
||||
{{ loop.index }}
|
||||
</button>
|
||||
<div class="text-nowrap">
|
||||
<div class="fw-bold{% if loop.index == 1 %} text-primary{% else %} text-muted{% endif %}">{{ step["title"] }}</div>
|
||||
<small class="text-muted">{{ step["subtitle"] }}</small>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="tab-content p-3 align-items-center mw-100 border border-top-0 rounded-bottom{{ plugin_types[template_plugin['type']].get('title-class', '') }}">
|
||||
{% for step in template_data["steps"] %}
|
||||
<div id="navs-steps-{{ template }}-{{ loop.index }}"
|
||||
class="ps-2 pe-2 tab-pane fade{% if loop.index == 1 %} show active{% endif %}"
|
||||
role="tabpanel"
|
||||
data-step="{{ loop.index }}"
|
||||
aria-labelledby="navs-steps-{{ template }}-{{ loop.index }}-tab">
|
||||
<div class="pt-1 pb-4">
|
||||
<h5 class="mb-1 fw-bold{{ plugin_types[template_plugin['type']].get('text-class', '') }}">{{ step["title"] }}</h5>
|
||||
<p class="card-subtitle text-muted">{{ step["subtitle"] }}</p>
|
||||
</div>
|
||||
<div class="row pb-0">
|
||||
{% for setting in step["settings"] %}
|
||||
{% set setting_id_prefix = template + "-setting-" + template_data['plugin_id'] +"-" %}
|
||||
{% set setting_config = config.get(setting, {}) %}
|
||||
{% set setting_data = plugins_settings.get(setting, {}) %}
|
||||
{% set setting_default = setting_data.get("default", "") %}
|
||||
{% set setting_value = setting_config.get("value", setting_default) %}
|
||||
{% set setting_method = setting_config.get("method", "default") %}
|
||||
{% set setting_template = setting_config.get("template", "") %}
|
||||
{% set disabled = setting_method not in ('ui', 'default') and (current_endpoint == "global-config" or not setting_config.get("global")) %}
|
||||
{% set required = setting == "SERVER_NAME" %}
|
||||
{% if current_endpoint == "new" %}
|
||||
{% set disabled = false %}
|
||||
{% set setting_value = template_data["settings"].get(setting, setting_default) %}
|
||||
{% endif %}
|
||||
{% if service_method == "autoconf" %}
|
||||
{% set setting_method = "autoconf" %}
|
||||
{% set disabled = true %}
|
||||
{% endif %}
|
||||
<div class="col-12 col-sm-6 col-lg-4 pb-3"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="label-{{ template }}-setting-{{ template_data['plugin_id'] }}-{{ setting_data['id'] }}"
|
||||
for="{{ template }}-setting-{{ template_data['plugin_id'] }}-{{ setting_data['id'] }}"
|
||||
class="form-label fw-semibold text-truncate">
|
||||
{{ setting_data["label"] }}
|
||||
</label>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if current_endpoint == "global-config" and setting_data["context"] == "multisite" %}
|
||||
<a role="badge"
|
||||
href='https://docs.bunkerweb.io/latest/concepts/?utm_campaign=self&utm_source=ui#multisite-mode'
|
||||
class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Multisite setting"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<span class="bx bx-server bx-xs"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current_endpoint not in ("new", "global-config") and setting_config.get("global") and setting_value != setting_default %}
|
||||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if setting_data["type"] == "select" %}
|
||||
{% include "models/select_setting.html" %}
|
||||
{% elif setting_data["type"] == "check" %}
|
||||
{% include "models/checkbox_setting.html" %}
|
||||
{% else %}
|
||||
{% include "models/input_setting.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="col-12 d-flex justify-content-between">
|
||||
{% if loop.index > 1 %}
|
||||
<button class="btn btn-primary btn-prev previous-step"
|
||||
data-template="{{ template }}"
|
||||
data-current-step="{{ loop.index }}">
|
||||
<i class="bx bx-chevron-left bx-sm ms-sm-n2"></i>
|
||||
<span class="align-middle d-sm-inline-block d-none">Previous</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<div></div>
|
||||
{% endif %}
|
||||
{% if loop.index == template_data["steps"]|length %}
|
||||
<div {% if service_method == "autoconf" %}data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-outline-bw-green save-settings{% if service_method == "autoconf" %} disabled{% endif %}"
|
||||
data-template="{{ template }}"
|
||||
data-current-step="{{ loop.index }}">
|
||||
<i class="bx bx-save bx-sm ms-sm-n2"></i>
|
||||
<span class="align-middle d-sm-inline-block d-none ms-sm-1">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-next next-step"
|
||||
data-template="{{ template }}"
|
||||
data-current-step="{{ loop.index }}">
|
||||
<span class="align-middle d-sm-inline-block d-none me-sm-1">Next</span>
|
||||
<i class="bx bx-chevron-right bx-sm me-sm-n2"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
@ -2,17 +2,18 @@
|
|||
id="csrf_token"
|
||||
name="csrf_token"
|
||||
value="{{ csrf_token() }}">
|
||||
<div class="position-absolute top-0 end-0 m-3"
|
||||
style="z-index: 1000">
|
||||
<div class="position-absolute top-0 end-0 m-3" style="z-index: 1000">
|
||||
<div class="d-flex flex-wrap justify-content-center align-items-center">
|
||||
<div class="card p-1 me-2">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary copy-settings">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
<span class="d-none d-md-inline"> Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card p-1"{% if service_method == "autoconf" %} data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
{% if request.is_secure %}
|
||||
<div class="card p-1 me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary copy-settings">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
<span class="d-none d-md-inline"> Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card p-1"
|
||||
{% if service_method == "autoconf" %} data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
<!-- Save button container -->
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-bw-green save-settings {% if service_method == "autoconf" %}disabled{% endif %}">
|
||||
|
|
@ -26,8 +27,8 @@
|
|||
{% if service_method == "autoconf" %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ service_method }}"{% endif %}>
|
||||
{% set config_lines = ["IS_DRAFT=" + config.get('IS_DRAFT', {}).get('value', 'no')] %}
|
||||
{% set default_settings = ["IS_DRAFT=no"] %}
|
||||
{% for plugin in plugins %}
|
||||
{% set filtered_settings = get_filtered_settings(plugin["settings"], current_endpoint == "global-config") %}
|
||||
{% for plugin_data in plugins.values() %}
|
||||
{% set filtered_settings = get_filtered_settings(plugin_data["settings"], current_endpoint == "global-config") %}
|
||||
{% if filtered_settings %}
|
||||
{% for setting, setting_data in filtered_settings.items() if setting not in blacklisted_settings %}
|
||||
{% set setting_config = config.get(setting, {}) %}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
<div class="form-floating mt-1">
|
||||
<select id="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
<select id="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
name="{{ setting }}"
|
||||
class="form-select"
|
||||
aria-labelledby="{{ setting_id_prefix }}label-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
aria-labelledby="label-{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}"
|
||||
data-original="{% if current_endpoint != 'new' %}{{ setting_value }}{% else %}{{ setting_default }}{% endif %}"
|
||||
data-default="{{ setting_default }}"
|
||||
{% if disabled %}disabled{% endif %}>
|
||||
{% if disabled %}disabled{% endif %}
|
||||
{% if required %}required{% endif %}>
|
||||
{% for option in setting_data["select"] %}
|
||||
<option value="{{ option }}"
|
||||
{% if setting_value == option %}selected{% endif %}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="{{ setting_id_prefix }}setting-{{ plugin['id'] }}-{{ setting_data['id'] }}{{ setting_id_suffix }}">
|
||||
{{ setting }}
|
||||
</label>
|
||||
<label for="{{ setting_id_prefix }}{{ setting_data['id'] }}{{ setting_id_suffix }}">{{ setting }}</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,21 +3,45 @@
|
|||
<!-- Content -->
|
||||
{% set blacklisted_settings = get_blacklisted_settings() %}
|
||||
{% set service_method = "ui" %}
|
||||
{% if current_endpoint != "global-config" %}
|
||||
{% set service_method = config.get("SERVER_NAME", {"method": "ui"})["method"] %}
|
||||
<input type="hidden"
|
||||
id="selected-mode"
|
||||
name="selected_mode"
|
||||
value="{{ mode }}">
|
||||
{% endif %}
|
||||
{% set plugin_types = {
|
||||
"core": {
|
||||
"icon": "<i class=\"bx bx-cube\"></i>",
|
||||
"title-class": " border-dark"
|
||||
},
|
||||
"external": {
|
||||
"icon": "<i class=\"bx bx-plug\"></i>",
|
||||
"title-class": " border-secondary",
|
||||
"text-class": " text-secondary fw-bold"
|
||||
},
|
||||
"pro": {
|
||||
"title-class": " border-primary",
|
||||
"text-class": " text-primary fw-bold shine"
|
||||
}
|
||||
} %}
|
||||
{% set service_method = config.get("SERVER_NAME", {"method": "ui"})["method"] %}
|
||||
<input type="hidden"
|
||||
id="selected-mode"
|
||||
name="selected_mode"
|
||||
value="{{ mode }}">
|
||||
<input type="hidden" id="used-template" name="used_template" value="{% if current_endpoint == "new" %}high{% else %}{{ config.get('USE_TEMPLATE', {'value': ''})['value'] }}{% endif %}">
|
||||
<input type="hidden"
|
||||
id="selected-template"
|
||||
name="selected_template"
|
||||
value="{{ current_template }}">
|
||||
<input type="hidden"
|
||||
id="service-method"
|
||||
name="service_method"
|
||||
value="{{ service_method }}">
|
||||
<input type="hidden"
|
||||
id="is-draft"
|
||||
name="IS_DRAFT"
|
||||
value="{{ is_draft }}"
|
||||
data-original="{% if current_endpoint != 'new' %}{{ is_draft }}{% else %}no{% endif %}"
|
||||
data-default="no">
|
||||
<div class="tab-content p-0 position-relative">
|
||||
<div class="tab-pane fade{% if mode == 'easy' %} show active{% endif %}"
|
||||
id="navs-modes-easy"
|
||||
role="tabpanel">TODO: Add Easy mode</div>
|
||||
role="tabpanel">{% include "models/plugins_settings_easy.html" %}</div>
|
||||
<div class="tab-pane fade{% if mode == 'advanced' %} show active{% endif %}"
|
||||
id="navs-modes-advanced"
|
||||
role="tabpanel">{% include "models/plugins_settings.html" %}</div>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ with app.app_context():
|
|||
get_multiples=get_multiples,
|
||||
get_filtered_settings=get_filtered_settings,
|
||||
get_blacklisted_settings=get_blacklisted_settings,
|
||||
get_plugins_settings=BW_CONFIG.get_plugins_settings,
|
||||
url_for=custom_url_for,
|
||||
)
|
||||
|
||||
|
|
@ -121,6 +122,9 @@ with app.app_context():
|
|||
|
||||
@app.context_processor
|
||||
def inject_variables():
|
||||
if request.path.startswith(("/setup", "/loading", "/login", "/totp")):
|
||||
return dict(script_nonce=app.config["SCRIPT_NONCE"])
|
||||
|
||||
DATA.load_from_file()
|
||||
metadata = DB.get_metadata()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue