mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
add clone service + precommit
*fix a middle screen service style margin issue *add possibility to clone existing service *add precommit
This commit is contained in:
parent
96a4c6853c
commit
ceb81603dd
9 changed files with 109 additions and 57 deletions
|
|
@ -2,4 +2,4 @@
|
|||
"dependencies": {
|
||||
"puppeteer": "^21.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -550,4 +550,3 @@ Allow access based on internal and external IP/network/rDNS/ASN whitelists.
|
|||
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|
||||
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|
||||
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |
|
||||
|
||||
|
|
|
|||
|
|
@ -465,4 +465,4 @@ In case you lost your UI credentials or have 2FA issues, you can connect to the
|
|||
1|<username>|<password_hash>|0||(manual or ui)
|
||||
```
|
||||
|
||||
You should now be able to log into the web UI only using your username and password.
|
||||
You should now be able to log into the web UI only using your username and password.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -45,7 +45,7 @@ class SubmitAccount {
|
|||
"focus:valid:!ring-red-500",
|
||||
"active:!border-red-500",
|
||||
"active:valid:!border-red-500",
|
||||
"valid:!border-red-500"
|
||||
"valid:!border-red-500",
|
||||
);
|
||||
this.pwAlertEl.classList.add("opacity-0");
|
||||
this.pwAlertEl.setAttribute("aria-hidden", "true");
|
||||
|
|
@ -59,7 +59,7 @@ class SubmitAccount {
|
|||
"focus:valid:!ring-red-500",
|
||||
"active:!border-red-500",
|
||||
"active:valid:!border-red-500",
|
||||
"valid:!border-red-500"
|
||||
"valid:!border-red-500",
|
||||
);
|
||||
this.pwAlertEl.classList.remove("opacity-0");
|
||||
this.pwAlertEl.setAttribute("aria-hidden", "false");
|
||||
|
|
@ -77,14 +77,14 @@ class PwBtn {
|
|||
const passwordContainer = e.target.closest("[data-input-group]");
|
||||
const inpEl = passwordContainer.querySelector("input");
|
||||
const invBtn = passwordContainer.querySelector(
|
||||
'[data-setting-password="invisible"]'
|
||||
'[data-setting-password="invisible"]',
|
||||
);
|
||||
const visBtn = passwordContainer.querySelector(
|
||||
'[data-setting-password="visible"]'
|
||||
'[data-setting-password="visible"]',
|
||||
);
|
||||
inpEl.setAttribute(
|
||||
"type",
|
||||
inpEl.getAttribute("type") === "password" ? "text" : "password"
|
||||
inpEl.getAttribute("type") === "password" ? "text" : "password",
|
||||
);
|
||||
|
||||
if (inpEl.getAttribute("type") === "password") {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ class ServiceModal {
|
|||
//modal forms
|
||||
this.formNewEdit = this.modal.querySelector("[data-services-modal-form]");
|
||||
this.formDelete = this.modal.querySelector(
|
||||
"[data-services-modal-form-delete]",
|
||||
"[data-services-modal-form-delete]"
|
||||
);
|
||||
this.submitBtn = document.querySelector(
|
||||
"button[data-services-modal-submit]",
|
||||
"button[data-services-modal-submit]"
|
||||
);
|
||||
//container
|
||||
this.container = document.querySelector("main");
|
||||
|
|
@ -83,6 +83,37 @@ class ServiceModal {
|
|||
this.openModal();
|
||||
}
|
||||
} catch (err) {}
|
||||
// clone action
|
||||
try {
|
||||
if (
|
||||
e.target.closest("button").getAttribute("data-services-action") ===
|
||||
"clone"
|
||||
) {
|
||||
//set form info and right form
|
||||
const [action, serviceName] = this.getActionAndServName(e.target);
|
||||
this.setForm(action, serviceName, serviceName, this.formNewEdit);
|
||||
//set default value with method default
|
||||
//get service data and parse it
|
||||
//multiple type logic is launch at same time on relate class
|
||||
const servicesSettings = e.target
|
||||
.closest("[data-services-service]")
|
||||
.querySelector("[data-services-settings]")
|
||||
.getAttribute("data-value");
|
||||
const obj = JSON.parse(servicesSettings);
|
||||
this.updateModalData(obj, true);
|
||||
// server name is unset
|
||||
const inpServName = document.querySelector("input#SERVER_NAME");
|
||||
inpServName.getAttribute("value", "");
|
||||
inpServName.removeAttribute("disabled", "");
|
||||
inpServName.value = "";
|
||||
// clone is UI creation, so no setting should be disabled
|
||||
|
||||
//show modal
|
||||
this.resetFilterInp();
|
||||
this.changeSubmitBtn("CREATE", "valid-btn");
|
||||
this.openModal(); //server name is unset
|
||||
}
|
||||
} catch (err) {}
|
||||
//new action
|
||||
try {
|
||||
if (
|
||||
|
|
@ -134,7 +165,7 @@ class ServiceModal {
|
|||
"delete-btn",
|
||||
"valid-btn",
|
||||
"edit-btn",
|
||||
"info-btn",
|
||||
"info-btn"
|
||||
);
|
||||
this.submitBtn.classList.add(btnType);
|
||||
}
|
||||
|
|
@ -194,15 +225,15 @@ class ServiceModal {
|
|||
//click the custom select dropdown to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
|
||||
`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",
|
||||
)}]`,
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
);
|
||||
|
||||
this.setDisabledDefault(btnCustom, defaultMethod);
|
||||
|
|
@ -219,10 +250,11 @@ class ServiceModal {
|
|||
|
||||
setForm(action, serviceName, oldServName, formEl) {
|
||||
this.modalTitle.textContent = `${action} ${serviceName}`;
|
||||
formEl.setAttribute("id", `form-${action}-${serviceName}`);
|
||||
const operation = action === "clone" ? "new" : action;
|
||||
formEl.setAttribute("id", `form-${operation}-${serviceName}`);
|
||||
const opeInp = formEl.querySelector(`input[name="operation"]`);
|
||||
opeInp.setAttribute("value", action);
|
||||
opeInp.value = action;
|
||||
opeInp.setAttribute("value", operation);
|
||||
opeInp.value = operation;
|
||||
|
||||
if (action === "edit" || action === "new") {
|
||||
this.showNewEditForm();
|
||||
|
|
@ -231,10 +263,18 @@ class ServiceModal {
|
|||
oldNameInp.value = oldServName;
|
||||
}
|
||||
|
||||
if (action === "clone") {
|
||||
this.showNewEditForm();
|
||||
const oldNameInp = formEl.querySelector(`input[name="OLD_SERVER_NAME"]`);
|
||||
oldNameInp.setAttribute("value", "");
|
||||
oldNameInp.value = "";
|
||||
}
|
||||
|
||||
if (action === "delete") {
|
||||
this.showDeleteForm();
|
||||
formEl.querySelector(`[data-services-modal-text]`).textContent =
|
||||
`Are you sure you want to delete ${serviceName} ?`;
|
||||
formEl.querySelector(
|
||||
`[data-services-modal-text]`
|
||||
).textContent = `Are you sure you want to delete ${serviceName} ?`;
|
||||
const nameInp = formEl.querySelector(`input[name="SERVER_NAME"]`);
|
||||
nameInp.setAttribute("value", serviceName);
|
||||
nameInp.value = serviceName;
|
||||
|
|
@ -286,7 +326,7 @@ class ServiceModal {
|
|||
this.modalTabsHeader.classList.remove("hidden");
|
||||
}
|
||||
|
||||
updateModalData(settings) {
|
||||
updateModalData(settings, forceEnabled = false) {
|
||||
//use this to select inputEl and change value
|
||||
for (const [key, data] of Object.entries(settings)) {
|
||||
//change format to match id
|
||||
|
|
@ -344,20 +384,20 @@ class ServiceModal {
|
|||
if (inp.tagName === "SELECT") {
|
||||
inp.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${value}']`,
|
||||
`button[data-setting-select-dropdown-btn][value='${value}']`
|
||||
)
|
||||
.click();
|
||||
inp.setAttribute("data-method", method);
|
||||
}
|
||||
|
||||
//check disabled/enabled after setting values and methods
|
||||
this.setDisabledServ(inp, method, global);
|
||||
if (!forceEnabled) this.setDisabledState(inp, method, global);
|
||||
if (forceEnabled) inp.removeAttribute("disabled");
|
||||
});
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
|
||||
setDisabledServ(inp, method, global) {
|
||||
setDisabledState(inp, method, global) {
|
||||
if (global) return inp.removeAttribute("disabled");
|
||||
|
||||
if (method === "ui" || method === "default") {
|
||||
|
|
@ -447,7 +487,7 @@ class Multiple {
|
|||
const attName = btn.getAttribute(`data-${this.prefix}-multiple-add`);
|
||||
//get all multiple groups
|
||||
const multipleEls = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple*="${attName}"]`,
|
||||
`[data-${this.prefix}-settings-multiple*="${attName}"]`
|
||||
);
|
||||
//case no schema
|
||||
if (multipleEls.length <= 0) return;
|
||||
|
|
@ -459,7 +499,7 @@ class Multiple {
|
|||
//and keep the highest num
|
||||
multipleEls.forEach((container) => {
|
||||
const ctnrName = container.getAttribute(
|
||||
"data-services-settings-multiple",
|
||||
"data-services-settings-multiple"
|
||||
);
|
||||
const num = this.getSuffixNumOrFalse(ctnrName);
|
||||
if (!isNaN(num) && num > topNum) topNum = num;
|
||||
|
|
@ -470,7 +510,7 @@ class Multiple {
|
|||
const setNum = +currNum === 0 ? `` : `_${currNum}`;
|
||||
//the default (schema) group is the last group
|
||||
const schema = document.querySelector(
|
||||
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`,
|
||||
`[data-${this.prefix}-settings-multiple="${attName}_SCHEMA"]`
|
||||
);
|
||||
//clone schema to create a group with new num
|
||||
const schemaClone = schema.cloneNode(true);
|
||||
|
|
@ -508,7 +548,7 @@ class Multiple {
|
|||
.hasAttribute(`data-${this.prefix}-multiple-delete`)
|
||||
) {
|
||||
const multContainer = e.target.closest(
|
||||
"[data-services-settings-multiple]",
|
||||
"[data-services-settings-multiple]"
|
||||
);
|
||||
multContainer.remove();
|
||||
}
|
||||
|
|
@ -530,13 +570,13 @@ class Multiple {
|
|||
? name.replace(`_${splitName[splitName.length - 1]}`, "").trim()
|
||||
: name.trim();
|
||||
const relateSetting = document.querySelector(
|
||||
`[data-setting-container=${nameSuffixLess}_SCHEMA]`,
|
||||
`[data-setting-container=${nameSuffixLess}_SCHEMA]`
|
||||
);
|
||||
const relateCtnr = relateSetting.closest(
|
||||
"[data-services-settings-multiple]",
|
||||
"[data-services-settings-multiple]"
|
||||
);
|
||||
const relateCtnrName = relateCtnr.getAttribute(
|
||||
"data-services-settings-multiple",
|
||||
"data-services-settings-multiple"
|
||||
);
|
||||
//then we sort the setting on the right container name by suffixe number
|
||||
if (!(relateCtnrName in sortMultiples)) {
|
||||
|
|
@ -554,7 +594,7 @@ class Multiple {
|
|||
addOneMultGroup() {
|
||||
const settings = document.querySelector("[data-services-modal-form]");
|
||||
const multAddBtns = settings.querySelectorAll(
|
||||
"[data-services-multiple-add]",
|
||||
"[data-services-multiple-add]"
|
||||
);
|
||||
multAddBtns.forEach((btn) => {
|
||||
//check if already one (SCHEMA exclude so length >= 2)
|
||||
|
|
@ -569,7 +609,7 @@ class Multiple {
|
|||
|
||||
showMultByAtt(att) {
|
||||
const multContainers = document.querySelectorAll(
|
||||
`[data-services-settings-multiple^=${att}]`,
|
||||
`[data-services-settings-multiple^=${att}]`
|
||||
);
|
||||
multContainers.forEach((container) => {
|
||||
if (
|
||||
|
|
@ -583,7 +623,7 @@ class Multiple {
|
|||
|
||||
toggleMultByAtt(att) {
|
||||
const multContainers = document.querySelectorAll(
|
||||
`[data-services-settings-multiple^=${att}]`,
|
||||
`[data-services-settings-multiple^=${att}]`
|
||||
);
|
||||
multContainers.forEach((container) => {
|
||||
if (
|
||||
|
|
@ -599,7 +639,7 @@ class Multiple {
|
|||
//get schema settings
|
||||
const multiples = {};
|
||||
const schemaSettings = document.querySelectorAll(
|
||||
`[data-setting-container$="SCHEMA"]`,
|
||||
`[data-setting-container$="SCHEMA"]`
|
||||
);
|
||||
// loop on every schema settings
|
||||
schemaSettings.forEach((schema) => {
|
||||
|
|
@ -625,11 +665,11 @@ class Multiple {
|
|||
setMultipleToDOM(sortMultObj) {
|
||||
//we loop on each multiple that contains values to render to DOM
|
||||
for (const [schemaCtnrName, multGroupBySuffix] of Object.entries(
|
||||
sortMultObj,
|
||||
sortMultObj
|
||||
)) {
|
||||
//we need to access the DOM schema container
|
||||
const schemaCtnr = document.querySelector(
|
||||
`[data-services-settings-multiple="${schemaCtnrName}"]`,
|
||||
`[data-services-settings-multiple="${schemaCtnrName}"]`
|
||||
);
|
||||
//now we have to loop on each multiple settings group
|
||||
for (const [suffix, settings] of Object.entries(multGroupBySuffix)) {
|
||||
|
|
@ -645,14 +685,14 @@ class Multiple {
|
|||
for (const [name, data] of Object.entries(settings)) {
|
||||
//get setting container of clone container
|
||||
const settingContainer = schemaCtnrClone.querySelector(
|
||||
`[data-setting-container="${name}"]`,
|
||||
`[data-setting-container="${name}"]`
|
||||
);
|
||||
//replace input info and disabled state
|
||||
this.setSetting(
|
||||
data["value"],
|
||||
data["method"],
|
||||
data["global"],
|
||||
settingContainer,
|
||||
settingContainer
|
||||
);
|
||||
}
|
||||
//send schema clone to DOM and show it
|
||||
|
|
@ -667,7 +707,7 @@ class Multiple {
|
|||
"data-services-settings-multiple",
|
||||
schemaCtnrClone
|
||||
.getAttribute("data-services-settings-multiple")
|
||||
.replace("_SCHEMA", suffix),
|
||||
.replace("_SCHEMA", suffix)
|
||||
);
|
||||
|
||||
//rename title
|
||||
|
|
@ -681,18 +721,18 @@ class Multiple {
|
|||
|
||||
//rename setting container
|
||||
const settingCtnrs = schemaCtnrClone.querySelectorAll(
|
||||
"[data-setting-container]",
|
||||
"[data-setting-container]"
|
||||
);
|
||||
settingCtnrs.forEach((settingCtnr) => {
|
||||
settingCtnr.setAttribute(
|
||||
"data-setting-container",
|
||||
settingCtnr
|
||||
.getAttribute("data-setting-container")
|
||||
.replace("_SCHEMA", suffix),
|
||||
.replace("_SCHEMA", suffix)
|
||||
);
|
||||
settingCtnr.setAttribute(
|
||||
"id",
|
||||
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix),
|
||||
settingCtnr.getAttribute("id").replace("_SCHEMA", suffix)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -769,15 +809,15 @@ class Multiple {
|
|||
//click the custom select dropdown btn value to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`,
|
||||
`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",
|
||||
)}]`,
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
);
|
||||
|
||||
this.setDisabledMultServ(btnCustom, method, global);
|
||||
|
|
@ -813,10 +853,10 @@ class Multiple {
|
|||
selects.forEach((select) => {
|
||||
const method = select.getAttribute("data-default-method");
|
||||
const name = select.getAttribute(
|
||||
"data-services-setting-select-default",
|
||||
"data-services-setting-select-default"
|
||||
);
|
||||
const selDOM = document.querySelector(
|
||||
`button[data-services-setting-select='${name}']`,
|
||||
`button[data-services-setting-select='${name}']`
|
||||
);
|
||||
if (method === "ui" || method === "default") {
|
||||
selDOM.removeAttribute("disabled", "");
|
||||
|
|
@ -851,7 +891,7 @@ class Multiple {
|
|||
hiddenIfNoMultiples() {
|
||||
//hide multiple btn if no multiple exist on a plugin
|
||||
const multiples = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple]`,
|
||||
`[data-${this.prefix}-settings-multiple]`
|
||||
);
|
||||
multiples.forEach((container) => {
|
||||
if (container.querySelectorAll(`[data-setting-container]`).length <= 0)
|
||||
|
|
@ -863,7 +903,7 @@ class Multiple {
|
|||
|
||||
removePrevMultiples() {
|
||||
const multiPlugins = document.querySelectorAll(
|
||||
`[data-${this.prefix}-settings-multiple]`,
|
||||
`[data-${this.prefix}-settings-multiple]`
|
||||
);
|
||||
multiPlugins.forEach((multiGrp) => {
|
||||
if (
|
||||
|
|
@ -901,7 +941,7 @@ const setModal = new ServiceModal();
|
|||
const format = new FormatValue();
|
||||
const setFilterGlobal = new FilterSettings(
|
||||
"settings-filter",
|
||||
"[data-service-content='settings']",
|
||||
"[data-service-content='settings']"
|
||||
);
|
||||
|
||||
const setMultiple = new Multiple("services");
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ class BackLogin {
|
|||
"href",
|
||||
window.location.href.replace(
|
||||
`/${this.currEndpoint}`,
|
||||
`/${this.backEndpoint}`
|
||||
)
|
||||
`/${this.backEndpoint}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
17
src/ui/templates/services.html
vendored
17
src/ui/templates/services.html
vendored
|
|
@ -15,7 +15,7 @@
|
|||
<!-- end actions -->
|
||||
<!-- services container-->
|
||||
<div
|
||||
class="p-0 my-4 sm:mx-4 md:px-8 grid grid-cols-12 col-span-12 relative min-w-0 break-words rounded-2xl bg-clip-border"
|
||||
class="p-0 my-4 sm:mx-4 md:px-4 grid grid-cols-12 col-span-12 lg:gap-x-4 relative min-w-0 break-words rounded-2xl bg-clip-border"
|
||||
>
|
||||
{% if services|length == 0 %}
|
||||
<div class="col-span-12 sm:col-span-4 sm:col-start-5">
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
services_batched %} {% set id_server_name =
|
||||
service["SERVER_NAME"]['value'].replace(".", "-") %}
|
||||
<div data-services-service
|
||||
class="dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
class="my-2 dark:brightness-110 overflow-hidden hover:scale-102 transition col-span-12 lg:col-span-6 3xl:col-span-4 p-4 w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<div data-services-settings class="hidden" data-value="{{service['settings']}}"></div>
|
||||
<div data-old-service-name class="hidden" data-value="{{service['SERVER_NAME']['full_value']}}"></div>
|
||||
|
|
@ -355,6 +355,19 @@
|
|||
</svg>
|
||||
</a>
|
||||
|
||||
<button
|
||||
data-services-action="clone"
|
||||
aria-label="clone service settings"
|
||||
data-services-name="{{service["SERVER_NAME"]['value']}}"
|
||||
|
||||
class="dark:brightness-90 z-20 mx-1 bg-emerald-500 hover:bg-emerald-500/80 focus:bg-emerald-500/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 fill-white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
data-services-action="edit"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class KubernetesTest(Test):
|
|||
"USE_PROXY_PROTOCOL": "yes",
|
||||
"REAL_IP_FROM": "100.64.0.0/10 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8",
|
||||
"REAL_IP_HEADER": "proxy_protocol",
|
||||
"LOG_LEVEL": "info"
|
||||
"LOG_LEVEL": "info",
|
||||
}
|
||||
replace_env = {"API_WHITELIST_IP": "127.0.0.1/8 100.64.0.0/10 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8"}
|
||||
for yaml in data:
|
||||
|
|
|
|||
Loading…
Reference in a new issue