mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
update settings editor, mode and template
* add template for editor container to used to generate a new editor config in the same way of multiples * render editors looking custom configs in settings (instanciate ace, update attribute, add to template, update value and input logic, ...) * case no instance on an editor container, create default empty config editor * handle custom config on simple mode filterSettings * now remove conflicts settings on filterSettings and merge remaining settings on both main and compare settings * force simple mode on new service and advanced mode on edit service
This commit is contained in:
parent
fe9ceab961
commit
94e234ef58
4 changed files with 233 additions and 33 deletions
|
|
@ -129,7 +129,12 @@ class SettingsService {
|
|||
setMethodUI,
|
||||
emptyServerName,
|
||||
);
|
||||
const modeBtn = document.querySelector(
|
||||
"button[data-toggle-settings-mode-btn]",
|
||||
);
|
||||
const mode = modeBtn.getAttribute("data-toggle-settings-mode-btn");
|
||||
if (action === "new") {
|
||||
mode !== "simple" ? modeBtn.click() : null;
|
||||
document
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn="security-level"][value="standard"]`,
|
||||
|
|
@ -141,6 +146,7 @@ class SettingsService {
|
|||
)
|
||||
.setAttribute("disabled", "true");
|
||||
} else {
|
||||
mode !== "advanced" ? modeBtn.click() : null;
|
||||
document
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn="security-level"][value="custom"]`,
|
||||
|
|
@ -154,7 +160,6 @@ class SettingsService {
|
|||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
// security level
|
||||
try {
|
||||
|
|
@ -241,7 +246,8 @@ class SettingsService {
|
|||
true,
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -317,7 +323,6 @@ class ServiceModal {
|
|||
this.setFormModal(e.target);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1032,6 +1032,7 @@ class Settings {
|
|||
inpName === "is_draft" ||
|
||||
inpName === "operation" ||
|
||||
inpName === "settings-filter" ||
|
||||
inpName === "CONFIG_NAME" ||
|
||||
inp.hasAttribute("data-combobox")
|
||||
)
|
||||
return true;
|
||||
|
|
@ -1732,32 +1733,168 @@ class SettingsEditor extends SettingsMultiple {
|
|||
}
|
||||
|
||||
initEditors() {
|
||||
window.addEventListener("load", () => {
|
||||
this.instanciateEditors();
|
||||
});
|
||||
|
||||
this.darkMode.addEventListener("click", (e) => {
|
||||
this.isDarkMode = e.target.checked;
|
||||
this.updateEditorMode();
|
||||
});
|
||||
}
|
||||
|
||||
instanciateEditors() {
|
||||
const editors = this.container.querySelectorAll("[data-editor]");
|
||||
editors.forEach((editorEl) => {
|
||||
const editor = ace.edit(editorEl.getAttribute("id"));
|
||||
// Handle
|
||||
if (this.isDarkMode) {
|
||||
editor.setTheme("ace/theme/dracula");
|
||||
} else {
|
||||
editor.setTheme("ace/theme/dawn");
|
||||
setupEditorsInstance() {
|
||||
this.editorEls.forEach((editor) => {
|
||||
const editorEl = editor.container;
|
||||
// we want to link editor to inp when sending form
|
||||
const linkInp = editorEl
|
||||
.closest("[data-editor-container]")
|
||||
.querySelector(`textarea[data-editor-input]`);
|
||||
// format name to get format TYPE_CONFIG_NAME
|
||||
linkInp.addEventListener("change", () => {
|
||||
const filename = linkInp?.getAttribute("data-filename")
|
||||
? linkInp?.getAttribute("data-filename")
|
||||
: linkInp?.getAttribute("data-default-filename");
|
||||
const type = linkInp?.getAttribute("data-config-type");
|
||||
const action = linkInp?.getAttribute("data-action");
|
||||
linkInp.setAttribute("name", `${type}_${filename}_${action}`);
|
||||
});
|
||||
|
||||
editor.on("change", () => {
|
||||
linkInp.value = editor.getValue();
|
||||
});
|
||||
|
||||
// we can link inp to input file name to update it if exists
|
||||
const inpFileName = editorEl
|
||||
.closest("[data-editor-container]")
|
||||
.querySelector(`input[data-editor-filename]`);
|
||||
|
||||
if (inpFileName) {
|
||||
inpFileName.addEventListener("input", () => {
|
||||
linkInp.setAttribute("data-filename", inpFileName.value);
|
||||
// dispatch event to inp to ensure it is updated
|
||||
const event = new Event("change");
|
||||
linkInp.dispatchEvent(event);
|
||||
});
|
||||
inpFileName.addEventListener("change", () => {
|
||||
linkInp.setAttribute("data-filename", inpFileName.value);
|
||||
// dispatch event to inp to ensure it is updated
|
||||
const event = new Event("change");
|
||||
linkInp.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
//editor options
|
||||
editor.setShowPrintMargin(false);
|
||||
this.editorEls.push(editor);
|
||||
});
|
||||
}
|
||||
|
||||
setEditorSettings() {
|
||||
this.resetEditorsInstAndDOM();
|
||||
this.addDefaultEditorIfNone();
|
||||
this.setupEditorsInstance();
|
||||
this.updateEditorMode();
|
||||
}
|
||||
|
||||
addDefaultEditorIfNone() {
|
||||
// get containers with _SCHEMA
|
||||
const editorContainers = this.container.querySelectorAll(
|
||||
"[data-editor-container$='_SCHEMA']",
|
||||
);
|
||||
editorContainers.forEach((editorContainer) => {
|
||||
// Check if others editor exists with same base name
|
||||
const editorName = editorContainer
|
||||
.getAttribute("data-editor-container")
|
||||
.replace("_SCHEMA", "");
|
||||
const otherEditors = this.container.querySelectorAll(
|
||||
`[data-editor-container*='${editorName}']`,
|
||||
);
|
||||
if (otherEditors.length > 1) return;
|
||||
// Add default editor
|
||||
const defaultType = editorContainer.getAttribute("data-default-type");
|
||||
const defaultName = editorContainer.getAttribute("data-default-name");
|
||||
this.addOneEditor(editorContainer, defaultType, defaultName, 1, "");
|
||||
});
|
||||
}
|
||||
|
||||
resetEditorsInstAndDOM() {
|
||||
// reset previous editors
|
||||
this.editorEls.forEach((editor) => {
|
||||
const editorContainer = editor.container.closest(
|
||||
"[data-editor-container]",
|
||||
);
|
||||
editorContainer.remove();
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
this.editorEls = [];
|
||||
// get only container ending with _SCHEMA
|
||||
const editorContainers = this.container.querySelectorAll(
|
||||
"[data-editor-container$='_SCHEMA']",
|
||||
);
|
||||
const configsSettings = this.getEditorSettings();
|
||||
// Create instances on the right containers
|
||||
editorContainers.forEach((editorContainer) => {
|
||||
const contName = editorContainer
|
||||
.getAttribute("data-editor-container")
|
||||
.replace("_SCHEMA", "");
|
||||
// Loop on each custom config settings that match same prefix as key
|
||||
// And create instance
|
||||
for (const [key, data] of Object.entries(configsSettings)) {
|
||||
if (!key.startsWith(contName)) continue;
|
||||
const editorName = data["name"];
|
||||
const editorType = data["type"];
|
||||
const editorValue = data["value"];
|
||||
const [num, isNum, name] = this.getSuffixData(key);
|
||||
this.addOneEditor(
|
||||
editorContainer,
|
||||
editorType,
|
||||
editorName,
|
||||
num,
|
||||
editorValue,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addOneEditor(container, type, name, num, value) {
|
||||
const contName = container
|
||||
.getAttribute("data-editor-container")
|
||||
.replace("_SCHEMA", "");
|
||||
const containerClone = container.cloneNode(true);
|
||||
// update attributs
|
||||
containerClone.setAttribute("data-editor-container", `${contName}_${num}`);
|
||||
const editor = containerClone.querySelector(`[data-editor]`);
|
||||
if (editor) {
|
||||
editor.setAttribute("id", `${contName}_${num}`);
|
||||
editor.setAttribute("name", `${contName}_${num}`);
|
||||
}
|
||||
const filenameInp = containerClone.querySelector(
|
||||
`input[data-editor-filename]`,
|
||||
);
|
||||
if (filenameInp) filenameInp.value = name;
|
||||
const hiddenInp = containerClone.querySelector(
|
||||
`textarea[data-editor-input]`,
|
||||
);
|
||||
if (hiddenInp) {
|
||||
hiddenInp.setAttribute("data-config-type", type);
|
||||
hiddenInp.setAttribute("data-filename", name);
|
||||
hiddenInp.setAttribute("data-action", this.currAction);
|
||||
hiddenInp.setAttribute("name", `${type}_${name}_${this.currAction}`);
|
||||
}
|
||||
// append to DOM and show as sibling of the original container
|
||||
container.insertAdjacentElement("afterend", containerClone);
|
||||
containerClone.classList.remove("hidden");
|
||||
// instantiate editor
|
||||
const editorInst = ace.edit(editor);
|
||||
editorInst.setValue(value);
|
||||
this.editorEls.push(editorInst);
|
||||
}
|
||||
|
||||
getEditorSettings() {
|
||||
const settings = JSON.parse(JSON.stringify(this.currSettings));
|
||||
const configsSettings = {};
|
||||
for (const [key, data] of Object.entries(settings)) {
|
||||
if (key.startsWith("CUSTOM_CONFIG")) {
|
||||
configsSettings[key] = data;
|
||||
}
|
||||
}
|
||||
return configsSettings;
|
||||
}
|
||||
|
||||
updateEditorMode() {
|
||||
this.editorEls.forEach((editor) => {
|
||||
if (this.isDarkMode) {
|
||||
|
|
@ -1927,6 +2064,7 @@ class SettingsSimple extends SettingsEditor {
|
|||
compareSettings && Object.keys(compareSettings).length > 0
|
||||
? this.filterSettings(mainSettings, compareSettings)
|
||||
: mainSettings;
|
||||
|
||||
this.updateData(
|
||||
action,
|
||||
oldServName,
|
||||
|
|
@ -1937,6 +2075,7 @@ class SettingsSimple extends SettingsEditor {
|
|||
emptyServerName,
|
||||
);
|
||||
this.setSettingsSimple();
|
||||
this.setEditorSettings();
|
||||
this.resetServerName();
|
||||
if (resetSteps) this.resetSimpleMode();
|
||||
this.checkVisibleInpsValidity();
|
||||
|
|
@ -1944,12 +2083,35 @@ class SettingsSimple extends SettingsEditor {
|
|||
|
||||
filterSettings(mainSettings, compareSettings) {
|
||||
const mergeSettings = {};
|
||||
// handle custom configs, we only keep security level config on new, else we get only service configs
|
||||
const configsToGetFrom =
|
||||
this.currAction === "new" ? compareSettings : mainSettings;
|
||||
const customConfSettings = []; // Allow to delete custom configs
|
||||
for (const [key, value] of Object.entries(configsToGetFrom)) {
|
||||
if (key.startsWith("CUSTOM_CONFIG")) {
|
||||
mergeSettings[key] = value;
|
||||
customConfSettings.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete merged custom configs
|
||||
for (let i = 0; i < customConfSettings.length; i++) {
|
||||
try {
|
||||
delete mainSettings[customConfSettings[i]];
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
delete compareSettings[customConfSettings[i]];
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// get the highest suffix number in mainSettings
|
||||
let highestMainSuffix = 0;
|
||||
let highestCompareSuffix = 0;
|
||||
// This will allow
|
||||
const settingsConflicts = [];
|
||||
for (const [key, value] of Object.entries(mainSettings)) {
|
||||
const [mainSuffix, mainIsSuffixe, mainName] = this.getSuffixData(key);
|
||||
|
||||
// Case same key (same setting) and not a multiple
|
||||
// Keep the one with a method != than ui or default if exists
|
||||
// Else keep the one from compareSettings that is the securityLevel
|
||||
|
|
@ -1957,17 +2119,18 @@ class SettingsSimple extends SettingsEditor {
|
|||
const method = mainSettings[key]["method"];
|
||||
if (method !== "ui" && method !== "default") {
|
||||
mergeSettings[key] = value;
|
||||
continue;
|
||||
} else {
|
||||
highestMainSuffix = mergeSettings[key] = compareSettings[key];
|
||||
continue;
|
||||
}
|
||||
settingsConflicts.push(key);
|
||||
continue;
|
||||
}
|
||||
// Need to check if is a multiple from a list because we can have custom configs with suffixe too
|
||||
if (this.multSettingsName.includes(mainName)) {
|
||||
highestMainSuffix = mainIsSuffixe
|
||||
? Math.max(highestMainSuffix, suffixeNum)
|
||||
: highestMainSuffix;
|
||||
settingsConflicts.push(key);
|
||||
}
|
||||
const [compareSuffix, compareIsSuffixe, compareName] =
|
||||
this.getSuffixData(key);
|
||||
|
|
@ -1977,6 +2140,7 @@ class SettingsSimple extends SettingsEditor {
|
|||
highestCompareSuffix = compareIsSuffixe
|
||||
? Math.max(highestCompareSuffix, suffixeNum)
|
||||
: highestCompareSuffix;
|
||||
settingsConflicts.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2013,7 +2177,24 @@ class SettingsSimple extends SettingsEditor {
|
|||
mergeSettings[key] = value;
|
||||
}
|
||||
}
|
||||
return mergeSettings;
|
||||
|
||||
// Delete conflicts settings in order to merge the rest
|
||||
for (let i = 0; i < settingsConflicts.length; i++) {
|
||||
try {
|
||||
delete mainSettings[settingsConflicts[i]];
|
||||
} catch (e) {}
|
||||
try {
|
||||
delete compareSettings[settingsConflicts[i]];
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Merge the rest of the settings
|
||||
const mergeAllSettings = {
|
||||
...mergeSettings,
|
||||
...mainSettings,
|
||||
...compareSettings,
|
||||
};
|
||||
return mergeAllSettings;
|
||||
}
|
||||
|
||||
setSettingsSimple() {
|
||||
|
|
@ -2218,7 +2399,7 @@ class SettingsSwitch {
|
|||
this.prefix = prefix;
|
||||
this.modes = modes;
|
||||
this.switchModeBtn = switchBtn;
|
||||
// dict wth mode as key and form element as value
|
||||
// dict with mode as key and form element as value
|
||||
this.container = container;
|
||||
this.init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,15 @@
|
|||
},
|
||||
"CUSTOM_CONFIG_MODSEC_1" : {
|
||||
"value": "test",
|
||||
"method": "default"
|
||||
"method": "default",
|
||||
"type" : "modsec",
|
||||
"name" : "modsec 1"
|
||||
},
|
||||
"CUSTOM_CONFIG_MODSEC_2" : {
|
||||
"value": "test",
|
||||
"method": "default"
|
||||
"value": "test 2",
|
||||
"method": "default",
|
||||
"type" : "modsec",
|
||||
"name" : "modsec 2"
|
||||
},
|
||||
}%}
|
||||
|
||||
|
|
@ -70,8 +74,7 @@
|
|||
{"plugin_id" : "antibot", "setting_id": "USE_ANTIBOT", "title" : "Define the type of your antibot", "subtitle" : "Javascript, Captcha or Cookie don't need additionnal settings to be fill. Recaptcha, Hcaptcha and Turnstile need secret key and site key delivered from providers." },
|
||||
],
|
||||
"configs": [
|
||||
{"id" : "CUSTOM_CONFIG_MODSEC_1", "name": "my_config_1", "type": "modsec", "subtitle" : "This will determine the modsecurity rules to apply to this service."},
|
||||
{"id" : "CUSTOM_CONFIG_MODSEC_2", "name": "my_config_2", "type": "server-http", "subtitle" : "This will determine the server http..."}
|
||||
{"id" : "CUSTOM_CONFIG_MODSEC", "type" : "modsec", "name" : "modsecrules", "subtitle" : "This will determine the modsecurity rules to apply to this service."},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
|
|||
19
src/ui/templates/settings_simple.html
vendored
19
src/ui/templates/settings_simple.html
vendored
|
|
@ -77,15 +77,26 @@
|
|||
<div class="col-span-12" data-setting-header>
|
||||
<div class="flex flex-col justify-start items-start">
|
||||
<h5 class="sm:pl-3 sm:pr-2 mt-2 transition duration-300 ease-in-out font-bold text-base uppercase dark:text-white/90 mb-0">
|
||||
Custom config for {{ config['type'] }}
|
||||
Custom config {{ config['type'] }} for your service.
|
||||
</h5>
|
||||
<p class="max-w-[550px] sm:pl-3 sm:pr-2 text-sm dark:text-gray-300 mb-0">{{ config['subtitle'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div data-editor-container class="w-full col-span-12 px-4">
|
||||
<textarea data-editor-input class="hidden" name="{{config['type']}}_SERVER_NAME_{{config['name']}}"></textarea>
|
||||
<div data-editor-container="{{config['id']}}_SCHEMA" data-default-type="{{config['type']}}" data-default-name="{{config['name']}}" class="hidden w-full col-span-12 px-4">
|
||||
<div class="mb-2 flex flex-wrap justify-start items-center" >
|
||||
<input disabled type="text"
|
||||
name="CONFIG_NAME"
|
||||
data-editor-filename
|
||||
class="dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 sm:ml-1 max-w-40 focus:valid:border-green-500 focus:file:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 font-normal text-gray-700 transition-all placeholder:text-gray-500 disabled:bg-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 dark:disabled:text-gray-300 disabled:text-gray-700"
|
||||
placeholder="{{config['name']}}"
|
||||
required />
|
||||
<p class="ml-1 mb-0 dark:text-white/80 text-gray-700/80 text-sm">
|
||||
.conf
|
||||
</p>
|
||||
</div>
|
||||
<textarea data-editor-input class="hidden"></textarea>
|
||||
<!-- editor-->
|
||||
<div data-editor id="{{config['id']}}" class="text-base w-full h-50 overflow-hidden overflow-y-auto my-2 border border-gray-300 dark:border-slate-800">
|
||||
<div data-editor class="text-base w-full h-50 overflow-hidden overflow-y-auto my-2 border border-gray-300 dark:border-slate-800">
|
||||
</div>
|
||||
<!-- editor-->
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue