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:
Jordan Blasenhauer 2024-01-18 14:38:14 +01:00
parent 96a4c6853c
commit ceb81603dd
9 changed files with 109 additions and 57 deletions

View file

@ -2,4 +2,4 @@
"dependencies": {
"puppeteer": "^21.3.6"
}
}
}

View file

@ -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. |

View file

@ -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

View file

@ -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") {

View file

@ -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");

View file

@ -12,8 +12,8 @@ class BackLogin {
"href",
window.location.href.replace(
`/${this.currEndpoint}`,
`/${this.backEndpoint}`
)
`/${this.backEndpoint}`,
),
);
});
});

View file

@ -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"

View file

@ -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: