mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
update vue js POC
* add widget builder JSON example * start some components example
This commit is contained in:
parent
6396e4779e
commit
d017514ccd
38 changed files with 2944 additions and 44 deletions
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "test-ui",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ function buildVite(dir) {
|
|||
return isErr;
|
||||
}
|
||||
|
||||
// CLIENT : Change dir structure
|
||||
// Change dir structure for flask app
|
||||
function updateClientDir() {
|
||||
let isErr = false;
|
||||
const srcDir = resolve(`./${clientBuildDir}/src/pages`);
|
||||
|
|
@ -58,6 +58,11 @@ function updateClientDir() {
|
|||
// And move from static to templates
|
||||
const templateDir = resolve(`./${clientBuildDir}/templates`);
|
||||
|
||||
// Create template dir if not exist
|
||||
if (!fs.existsSync(resolve("./templates"))) {
|
||||
fs.mkdirSync(resolve("./templates"));
|
||||
}
|
||||
|
||||
fs.readdir(templateDir, (err, subdirs) => {
|
||||
subdirs.forEach((subdir) => {
|
||||
// Get absolute path of current subdir
|
||||
|
|
@ -67,9 +72,11 @@ function updateClientDir() {
|
|||
// Copy file to move it from /template/page to /template
|
||||
fs.copyFileSync(
|
||||
`${currPath}/${subdir}.html`,
|
||||
resolve(`./static/templates/${subdir}.html`),
|
||||
resolve(`./templates/${subdir}.html`),
|
||||
);
|
||||
// Delete useless dir
|
||||
fs.rmSync(currPath, { recursive: true, force: true });
|
||||
fs.rmSync(`./${clientBuildDir}/templates/`, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
9
vuejs/client/src/components/Forms/Field/Base.vue
Normal file
9
vuejs/client/src/components/Forms/Field/Base.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-2 p-2">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
145
vuejs/client/src/components/Forms/Field/Checkbox.vue
Normal file
145
vuejs/client/src/components/Forms/Field/Checkbox.vue
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<script setup>
|
||||
import { reactive, defineProps, onMounted, ref } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Base from "@components/Forms/Field/Base.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
|
||||
/* PROPS ARGUMENTS
|
||||
*
|
||||
*
|
||||
id: string,
|
||||
value: string,
|
||||
disabled: boolean,
|
||||
required: boolean,
|
||||
label: string,
|
||||
name: string,
|
||||
version: string,
|
||||
hideLabel: boolean,
|
||||
required: boolean,
|
||||
headerClass: string,
|
||||
inpClass: string,
|
||||
tabId: string || number,
|
||||
*
|
||||
*
|
||||
*/
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
inpClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tabId: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const checkboxEl = ref(null);
|
||||
|
||||
const checkbox = reactive({
|
||||
value: props.value,
|
||||
isValid: false,
|
||||
});
|
||||
|
||||
const emits = defineEmits(["inp"]);
|
||||
|
||||
function updateValue() {
|
||||
checkbox.value = checkbox.value === "yes" ? "no" : "yes";
|
||||
checkbox.isValid = checkboxEl.value.checkValidity();
|
||||
return checkbox.value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkbox.isValid = checkboxEl.value.checkValidity();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Header :name="props.name" :label="props.label" :hideLabel="props.hideLabel" :headerClass="props.headerClass" />
|
||||
|
||||
<div class="relative z-10 flex flex-col items-start">
|
||||
<input
|
||||
ref="checkboxEl"
|
||||
:tabindex="props.tabId || contentIndex"
|
||||
@keyup.enter="$emit('inp', updateValue())"
|
||||
@click="$emit('inp', updateValue())"
|
||||
:id="props.id"
|
||||
:name="props.name"
|
||||
:disabled="props.disabled || false"
|
||||
:checked="checkbox.value === 'yes' ? true : false"
|
||||
:class="[
|
||||
'checkbox',
|
||||
checkbox.value === 'yes' ? 'check' : '',
|
||||
checkbox.isValid ? 'valid' : 'invalid',
|
||||
props.inpClass,
|
||||
]"
|
||||
type="checkbox"
|
||||
:value="checkbox.value"
|
||||
:required="props.required || false"
|
||||
/>
|
||||
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
v-show="checkbox.value === 'yes'"
|
||||
class="checkbox-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
class="pointer-events-none"
|
||||
d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<p
|
||||
:aria-hidden="checkbox.isValid ? 'true' : 'false'"
|
||||
role="alert"
|
||||
:class="[checkbox.isValid ? 'hidden' : '']"
|
||||
class="input-error-msg"
|
||||
>
|
||||
{{
|
||||
checkbox.isValid
|
||||
? $t("inp_input_valid")
|
||||
: $t("inp_input_error_required")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</Base>
|
||||
</template>
|
||||
259
vuejs/client/src/components/Forms/Field/Input.vue
Normal file
259
vuejs/client/src/components/Forms/Field/Input.vue
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<script setup>
|
||||
import { reactive, ref, defineEmits, onMounted, defineProps } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Base from "@components/Forms/Field/Base.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
|
||||
|
||||
/* PROPS ARGUMENTS
|
||||
*
|
||||
*
|
||||
id: string,
|
||||
name: string,
|
||||
type: string<"text"|"email"|"password"|"number"|"tel"|"url">,
|
||||
disabled: boolean,
|
||||
value: string,
|
||||
placeholder: string,
|
||||
pattern: string,
|
||||
clipboard: boolean,
|
||||
readonly: boolean,
|
||||
label: string,
|
||||
name: string,
|
||||
version: string,
|
||||
hideLabel: boolean,
|
||||
required: boolean,
|
||||
headerClass: string,
|
||||
inpClass: string,
|
||||
tabId: string || number,
|
||||
*
|
||||
*
|
||||
*/
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
pattern: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
clipboard: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
inpClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tabId: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
const inputEl = ref(null);
|
||||
|
||||
const inp = reactive({
|
||||
value: props.value,
|
||||
showInp: false,
|
||||
isClipAllow: false,
|
||||
isValid: false,
|
||||
});
|
||||
|
||||
const emits = defineEmits(["inp"]);
|
||||
|
||||
function copyClipboard() {
|
||||
if (!inp.clipboard || !inp.isClipAllow) return;
|
||||
|
||||
navigator.permissions.query({ name: "clipboard-write" }).then((result) => {
|
||||
if (result.state === "granted" || result.state === "prompt") {
|
||||
/* write to the clipboard now */
|
||||
|
||||
inputEl.select();
|
||||
inputEl.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
return navigator.clipboard.writeText(inputEl.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
inp.isValid = inputEl.value.checkValidity();
|
||||
// Clipboard not allowed on http
|
||||
if (!window.location.href.startsWith("https://")) return;
|
||||
|
||||
// Check clipboard permission
|
||||
navigator.permissions.query({ name: "clipboard-write" }).then((result) => {
|
||||
if (result.state === "granted" || result.state === "prompt") {
|
||||
inp.isClipAllow = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Header :name="props.name" :label="props.label" :hideLabel="props.hideLabel" :headerClass="props.headerClass" />
|
||||
|
||||
<div class="relative flex flex-col items-start">
|
||||
<input
|
||||
:tabindex="props.tabId || contentIndex"
|
||||
ref="inputEl"
|
||||
v-model="inp.value"
|
||||
@input="
|
||||
() => {
|
||||
inp.isValid = inputEl.checkValidity();
|
||||
$emit('inp', inp.value);
|
||||
}
|
||||
"
|
||||
:id="props.id"
|
||||
:class="[
|
||||
'input-regular',
|
||||
inp.isValid ? 'valid' : 'invalid',
|
||||
props.inpClass,
|
||||
]"
|
||||
:required="props.required || false"
|
||||
:readonly="props.readonly || false"
|
||||
:disabled="props.disabled || false"
|
||||
:placeholder="props.placeholder || ''"
|
||||
:pattern="props.pattern || '(?s).*'"
|
||||
:name="props.name"
|
||||
:value="inp.value"
|
||||
:type="
|
||||
props.type === 'password'
|
||||
? inp.showInp
|
||||
? 'text'
|
||||
: 'password'
|
||||
: props.type
|
||||
"
|
||||
/>
|
||||
<div
|
||||
v-if="props.clipboard && inp.isClipAllow"
|
||||
:class="[props.type === 'password' ? 'pw-input' : 'no-pw-input']"
|
||||
class="input-clipboard-container"
|
||||
>
|
||||
<button
|
||||
:tabindex="contentIndex"
|
||||
@click="copyClipboard()"
|
||||
:class="[props.disabled ? 'disabled' : 'enabled']"
|
||||
class="input-clipboard-button"
|
||||
:aria-describedby="`${props.id}-clipboard-text`"
|
||||
>
|
||||
<span :id="`${props.id}-clipboard-text`" class="sr-only"
|
||||
>{{ $t("inp_input_clipboard_desc") }}
|
||||
</span>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="input-clipboard-svg"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
<div v-if="props.type === 'password'" class="input-pw-container">
|
||||
<button
|
||||
:tabindex="contentIndex"
|
||||
:aria-description="$t('inp_input_password_desc')"
|
||||
:aria-controls="props.id"
|
||||
@click="inp.showInp = inp.showInp ? false : true"
|
||||
:class="[props.disabled ? 'disabled' : 'enabled']"
|
||||
class="input-pw-button"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
v-if="!inp.showInp"
|
||||
class="input-pw-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
>
|
||||
<path
|
||||
d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
v-if="inp.showInp"
|
||||
class="input-pw-svg scale-110"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
>
|
||||
<path
|
||||
d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
:aria-hidden="inp.isValid ? 'true' : 'false'"
|
||||
role="alert"
|
||||
:class="[inp.isValid ? 'hidden' : '']"
|
||||
class="input-error-msg"
|
||||
>
|
||||
{{
|
||||
inp.isValid
|
||||
? $t("inp_input_valid")
|
||||
: !inp.value
|
||||
? $t("inp_input_error_required")
|
||||
: $t("inp_input_error_format")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</Base>
|
||||
</template>
|
||||
237
vuejs/client/src/components/Forms/Field/Select.vue
Normal file
237
vuejs/client/src/components/Forms/Field/Select.vue
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<script setup>
|
||||
import { ref, reactive, watch, onMounted, defineEmits, defineProps } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Base from "@components/Forms/Field/Base.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
|
||||
|
||||
/* PROPS ARGUMENTS
|
||||
*
|
||||
*
|
||||
id: string,
|
||||
value: string,
|
||||
values: array,
|
||||
disabled: boolean,
|
||||
required: boolean,
|
||||
label: string,
|
||||
name: string,
|
||||
version: string,
|
||||
hideLabel: boolean,
|
||||
inpClass: string,
|
||||
headerClass: string,
|
||||
tabId: string || number,
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
values: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
inpClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
tabId: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// When mounted or when props changed, we want select to display new props values
|
||||
// When component value change itself, we want to switch to select.value
|
||||
// To avoid component to send and stick to props values (bad behavior)
|
||||
// Trick is to use select.value || props.value on template
|
||||
watch(props, (newProp, oldProp) => {
|
||||
if (newProp.value !== select.value) {
|
||||
select.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
const select = reactive({
|
||||
isOpen: false,
|
||||
// On mounted value is null to display props value
|
||||
// Then on new select we will switch to select.value
|
||||
// If we use select.value : props.value
|
||||
// Component will not re-render after props.value change
|
||||
value: "",
|
||||
});
|
||||
|
||||
const selectBtn = ref();
|
||||
const selectWidth = ref("");
|
||||
|
||||
// EVENTS
|
||||
function toggleSelect() {
|
||||
select.isOpen = select.isOpen ? false : true;
|
||||
}
|
||||
|
||||
function closeSelect() {
|
||||
select.isOpen = false;
|
||||
}
|
||||
|
||||
function changeValue(newValue) {
|
||||
// Allow on template to switch from prop value to component own value
|
||||
// Then send the new value to parent
|
||||
select.value = newValue;
|
||||
closeSelect();
|
||||
return newValue;
|
||||
}
|
||||
|
||||
// Close select dropdown when clicked outside element
|
||||
watch(select, () => {
|
||||
if (select.isOpen) {
|
||||
document.querySelector("body").addEventListener("click", closeOutside);
|
||||
} else {
|
||||
document.querySelector("body").removeEventListener("click", closeOutside);
|
||||
}
|
||||
});
|
||||
|
||||
// Close select when clicked outside logic
|
||||
function closeOutside(e) {
|
||||
try {
|
||||
if (e.target !== selectBtn.value) {
|
||||
select.isOpen = false;
|
||||
}
|
||||
} catch (err) {
|
||||
select.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
selectWidth.value = `${selectBtn.value.clientWidth}px`;
|
||||
window.addEventListener("resize", () => {
|
||||
try {
|
||||
selectWidth.value = `${selectBtn.value.clientWidth}px`;
|
||||
} catch (err) {}
|
||||
});
|
||||
});
|
||||
|
||||
const emits = defineEmits(["inp"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Header :name="props.name" :label="props.label" :hideLabel="props.hideLabel" :headerClass="props.headerClass" />
|
||||
|
||||
<select :name="props.name" class="hidden">
|
||||
<option
|
||||
v-for="(value, id) in props.values"
|
||||
:key="id"
|
||||
:value="value"
|
||||
@click="$emit('inp', changeValue(value))"
|
||||
:selected="select.value && select.value === value || !select.value && value === props.value ? true : false"
|
||||
>
|
||||
{{ value }}
|
||||
</option>
|
||||
</select>
|
||||
<!-- end default select -->
|
||||
|
||||
<!--custom-->
|
||||
<div class="relative">
|
||||
<button
|
||||
:name="`${props.name}-custom`"
|
||||
:tabindex="props.tabId || contentIndex"
|
||||
ref="selectBtn"
|
||||
:aria-controls="`${props.id}-custom`"
|
||||
:aria-expanded="select.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('inp_select_dropdown_button_desc')"
|
||||
data-select-dropdown
|
||||
:disabled="props.disabled || false"
|
||||
@click="toggleSelect()"
|
||||
:class="['select-btn', props.inpClass]"
|
||||
>
|
||||
<span
|
||||
v-if="props.required"
|
||||
class="font-bold text-red-500 absolute right-[5px] top-[-20px]"
|
||||
>*
|
||||
</span>
|
||||
<span :id="`${props.id}-text`" class="select-btn-name">
|
||||
{{ select.value || props.value }}
|
||||
</span>
|
||||
<!-- chevron -->
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
:class="[select.isOpen ? '-rotate-180' : '']"
|
||||
class="select-btn-svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
|
||||
/>
|
||||
</svg>
|
||||
<!-- end chevron -->
|
||||
</button>
|
||||
<!-- dropdown-->
|
||||
<div
|
||||
role="radiogroup"
|
||||
:style="{ width: selectWidth }"
|
||||
:id="`${props.id}-custom`"
|
||||
:class="[select.isOpen ? 'flex' : 'hidden']"
|
||||
class="select-dropdown-container"
|
||||
:aria-description="$t('inp_select_dropdown_desc')"
|
||||
>
|
||||
<button
|
||||
:tabindex="contentIndex"
|
||||
v-for="(value, id) in props.values"
|
||||
role="radio"
|
||||
@click="$emit('inp', changeValue(value))"
|
||||
:class="[
|
||||
id === 0 ? 'first' : '',
|
||||
id === props.values.length - 1 ? 'last' : '',
|
||||
value === select.value && select.value === value || !select.value && value === props.value ? 'active' : '',
|
||||
'select-dropdown-btn',
|
||||
]"
|
||||
:aria-controls="`${props.id}-text`"
|
||||
:aria-checked="select.value && select.value === value || !select.value && value === props.value ? 'true' : 'false'"
|
||||
>
|
||||
{{ value }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- end dropdown-->
|
||||
</div>
|
||||
<!-- end custom-->
|
||||
</Base>
|
||||
|
||||
</template>
|
||||
56
vuejs/client/src/components/Forms/Header/Field.vue
Normal file
56
vuejs/client/src/components/Forms/Header/Field.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
|
||||
/* PROPS ARGUMENTS
|
||||
*
|
||||
*
|
||||
label: string,
|
||||
name: string,
|
||||
version: string,
|
||||
hideLabel: boolean,
|
||||
required: boolean,
|
||||
headerClass: string,
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[props.hideLabel ? 'hidden' : '', props.headerClass]">
|
||||
<label
|
||||
:class="[props.label ? '' : 'sr-only']"
|
||||
:for="props.name"
|
||||
class="relative lowercase capitalize-first my-1 transition duration-300 ease-in-out text-sm sm:text-md font-bold m-0 dark:text-gray-300"
|
||||
>
|
||||
{{ props.label ? props.label : props.name }} <span v-if="props.version">{{ props.version }}</span>
|
||||
</label>
|
||||
<span
|
||||
v-if="props.required"
|
||||
class="font-bold text-red-500 absolute right-[5px] top-[-20px]"
|
||||
>*
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
title : {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<h1 class="text-4xl font-bold">{{ title }}</h1>
|
||||
</div>
|
||||
</template>
|
||||
45
vuejs/client/src/components/Widget/Base.vue
Normal file
45
vuejs/client/src/components/Widget/Base.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<script setup>
|
||||
const builder = [{
|
||||
// we are starting with the top level container name
|
||||
// this can be a "card", "modal", "table"... etc
|
||||
"card" : {
|
||||
// a card can have an icon and a color at the top right
|
||||
"icon": ["iconName", "iconColor"],
|
||||
// grid position for each screen
|
||||
"columns" : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
// container title
|
||||
"title" : "My awesome card",
|
||||
// group of widgets will share same columns
|
||||
// Case we want a button widget in a one column group and others in like a 3 columns group
|
||||
"groups" : [{
|
||||
// determine the number of columns for widgets in group for each screen
|
||||
"columns" : (1, 2, 3),
|
||||
"widgets" : [
|
||||
{
|
||||
"position" : "left", // "center" or "right" or "default"
|
||||
"type" : "widgetType", // "widgetType" is the name of the widget, for example "Detail" to render a Detail widget
|
||||
// Need to be a list in case we have multiple widgets in the same group
|
||||
// By default, widgetEls are horizontally aligned with flexbox
|
||||
"widgetEls" : [
|
||||
// data we need for one widget
|
||||
{
|
||||
"id" : "widgetId",
|
||||
"title" : "Widget title",
|
||||
"label" : "Widget label",
|
||||
"values" : ["value1", "value2"],
|
||||
},
|
||||
// ... other widgets
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// ... other groups
|
||||
]
|
||||
},
|
||||
// ... other containers
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
363
vuejs/client/src/lang/en.json
Normal file
363
vuejs/client/src/lang/en.json
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"setup_loader_default": "Loading",
|
||||
"setup_loader_setup": "Setting up...",
|
||||
"setup_logo_alt": "BunkerWeb logo image",
|
||||
"setup_title": "Setup Wizard",
|
||||
"setup_account": "Account",
|
||||
"setup_username": "username",
|
||||
"setup_username_placeholder": "username",
|
||||
"setup_password": "password",
|
||||
"setup_password_placeholder": "password",
|
||||
"setup_password_check": "confirm password",
|
||||
"setup_password_check_placeholder": "same as previous",
|
||||
"setup_password_check_invalid": "Value does not match previous password",
|
||||
"setup_settings": "Settings",
|
||||
"setup_ui_host": "ui host (REVERSE_PROXY_HOST)",
|
||||
"setup_ui_host_placeholder": "http://[hostname](:[port])",
|
||||
"setup_ui_url": "ui URL (REVERSE_PROXY_URL)",
|
||||
"setup_ui_url_placeholder": "/admin",
|
||||
"setup_server_name": "server name",
|
||||
"setup_server_name_placeholder": "www.example.com",
|
||||
"setup_server_name_value": "www.example.com",
|
||||
"setup_check_dns": "Check server name DNS",
|
||||
"setup_check_dns_button": "Check DNS",
|
||||
"setup_check_dns_color_desc": "Result color of the check DNS status.",
|
||||
"setup_check_dns_result_unknown": "check DNS status is unknown",
|
||||
"setup_check_dns_result_success": "check DNS status is success",
|
||||
"setup_check_dns_result_error": "check DNS status is error",
|
||||
"setup_lets_encrypt": "Auto let's encrypt",
|
||||
"setup_final_url": "Your BunkerWeb UI final URL will be",
|
||||
"setup_button": "Setup",
|
||||
"setup_form_invalid_submit": "Missing or invalid fields to submit",
|
||||
"setup_failed": "Error while setting up. Try again later.",
|
||||
"login_error": "Invalid username or password",
|
||||
"login_logo_alt": "BunkerWeb logo image",
|
||||
"login_title": "Log in",
|
||||
"login_username": "username",
|
||||
"login_username_placeholder": "enter username",
|
||||
"login_password": "password",
|
||||
"login_password_placeholder": "enter password",
|
||||
"login_log_button": "Log in",
|
||||
"totp_error": "TOTP code is invalid",
|
||||
"totp_logo_alt": "BunkerWeb logo image",
|
||||
"totp_title": "2FA",
|
||||
"totp_code ": "code",
|
||||
"totp_code_placeholder": "195214",
|
||||
"totp_button": "Send",
|
||||
"action_disable": "disable",
|
||||
"action_enable": "enable",
|
||||
"action_save": "save",
|
||||
"action_add": "add",
|
||||
"action_close": "close",
|
||||
"action_delete": "delete",
|
||||
"action_link": "link",
|
||||
"action_edit": "edit",
|
||||
"action_download": "download",
|
||||
"action_create": "create",
|
||||
"action_view": "view",
|
||||
"action_stop": "stop",
|
||||
"action_ping": "ping",
|
||||
"action_reload": "reload",
|
||||
"action_upload": "upload",
|
||||
"action_delete_all": "delete all",
|
||||
"api_pending": "Trying to retrieve {name}",
|
||||
"api_error": "Error retrieving {name}",
|
||||
"api_pending_default": "Trying to retrieve data",
|
||||
"api_error_default": "Error retrieving data",
|
||||
"dashboard_logo_alt": "BunkerWeb logo image",
|
||||
"dashboard_logo_link_label": "Redirect to home page",
|
||||
"dashboard_bw": "BunkerWeb",
|
||||
"dashboard_docs": "docs",
|
||||
"dashboard_blog": "blog",
|
||||
"dashboard_privacy": "privacy",
|
||||
"dashboard_license": "license",
|
||||
"dashboard_sitemap": "sitemap",
|
||||
"dashboard_default": "default",
|
||||
"dashboard_info": "info",
|
||||
"dashboard_filter": "filters",
|
||||
"dashboard_advanced": "advanced",
|
||||
"dashboard_loader": "loading",
|
||||
"dashboard_lang_dropdown_button_desc": "Toggle hide/show radio group (dropdown) to change langage.",
|
||||
"dashboard_refresh_desc": "refresh and retrieve all datas.",
|
||||
"dashboard_home": "home",
|
||||
"dashboard_instances": "instances",
|
||||
"dashboard_global_config": "global config",
|
||||
"dashboard_services": "services",
|
||||
"dashboard_configs": "configs",
|
||||
"dashboard_plugins": "plugins",
|
||||
"dashboard_jobs": "jobs",
|
||||
"dashboard_bans": "bans",
|
||||
"dashboard_actions": "actions",
|
||||
"dashboard_account": "account",
|
||||
"dashboard_reporting": "reporting",
|
||||
"dashboard_menu_toggle_sidebar": "Toggle menu sidebar.",
|
||||
"dashboard_menu_close_sidebar": "Close menu sidebar.",
|
||||
"dashboard_menu_twitter_label": "redirection vers le Twitter de BunkerWeb",
|
||||
"dashboard_menu_linkedin_label": "redirection vers le Linkedin de BunkerWeb",
|
||||
"dashboard_menu_discord_label": "redirection vers le Discord de BunkerWeb",
|
||||
"dashboard_menu_github_label": "redirection vers le Github de BunkerWeb",
|
||||
"dashboard_menu_plugins_title": "plugin pages",
|
||||
"dashboard_menu_plugins_none": "Want custom plugins ?",
|
||||
"dashboard_menu_plugins_none_doc": "check doc",
|
||||
"dashboard_menu_mode_light": "light mode",
|
||||
"dashboard_menu_mode_dark": "dark mode",
|
||||
"dashboard_menu_log_out": "log out",
|
||||
"dashboard_actions_title": "actions",
|
||||
"dashboard_actions_subtitle": "feedbacks list",
|
||||
"dashboard_feedback_close_sidebar": "close feedback sidebar",
|
||||
"dashboard_feedback_toggle_sidebar": "toggle feedback sidebar",
|
||||
"dashboard_ui": "ui",
|
||||
"dashboard_scheduler": "scheduler",
|
||||
"dashboard_autoconf": "autoconf",
|
||||
"dashboard_core": "core",
|
||||
"dashboard_global": "global",
|
||||
"dashboard_news_toggle_sidebar": "Toggle news sidebar.",
|
||||
"dashboard_news_close_sidebar": "Close news sidebar.",
|
||||
"dashboard_news_title": "news",
|
||||
"dashboard_news_subtitle": "Stay up to date !",
|
||||
"dashboard_news_fetch_error": "Impossible to retrieve news",
|
||||
"dashboard_newsletter_title": "Join newsletter",
|
||||
"dashboard_newsletter_placeholder": "john.doe{'@'}example.com",
|
||||
"dashboard_newsletter_privacy_text": "I'v read and agree",
|
||||
"dashboard_newsletter_privacy_text_link": "the privacy policy",
|
||||
"dashboard_newsletter_subscribe_button": "subscribe",
|
||||
"dashboard_api_state_desc": "Show details about data retrieving steps.",
|
||||
"dashboard_alert_close_desc": "Close alert.",
|
||||
"dashboard_feedback_alert_desc": "Own actions feedbacks.",
|
||||
"dashboard_feedback_logs_desc": "BunkerWeb actions feedbacks.",
|
||||
"dashboard_popover_button_desc": "Show setting details on hover.",
|
||||
"dashboard_popover_button": "Show popover with setting details.",
|
||||
"dashboard_popover_detail_desc": "Container with setting details.",
|
||||
"dashboard_up": "up",
|
||||
"dashboard_down": "down",
|
||||
"dashboard_banner_title_1": "Need premium support ?",
|
||||
"dashboard_banner_title_2": "Try BunkerWeb on our",
|
||||
"dashboard_banner_title_3": "All informations about BunkerWeb on our",
|
||||
"dashboard_banner_link_1": "https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_2": "https://demo.bunkerweb.io/link/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_3": "https://www.bunkerweb.io/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_text_1": "Check BunkerWeb Panel",
|
||||
"dashboard_banner_link_text_2": "demo wep app !",
|
||||
"dashboard_banner_link_text_3": "website !",
|
||||
"home_version_is_latest": "is the latest version",
|
||||
"home_version_latest_version": "latest version",
|
||||
"home_version": "version",
|
||||
"home_internal": "internal",
|
||||
"home_external": "external",
|
||||
"home_card_link_label": "redirect to page with related data",
|
||||
"instances_hostname": "hostname",
|
||||
"instances_hostname_placeholder": "bwapi",
|
||||
"instances_method": "method",
|
||||
"instances_port": "port",
|
||||
"instances_port_placeholder": "5000",
|
||||
"instances_active": "instance is active",
|
||||
"instances_inactive": "instance is inactive",
|
||||
"instances_modal_delete_msg": "Are you sure to delete instance with hostname {hostname} ?",
|
||||
"instances_modal_delete_title": "Delete instance ",
|
||||
"instances_add_instance": "Add instance",
|
||||
"instances_modal_add_title": "Create new instance",
|
||||
"instances_server_name": "server name",
|
||||
"instances_server_name_placeholder": "www.example.com",
|
||||
"instances_modal_edit_title": "Edit {name}",
|
||||
"instances_edit_instance": "Edit instance",
|
||||
"actions_total": "Actions total",
|
||||
"actions_ui": "Actions UI",
|
||||
"actions_core": "Actions CORE",
|
||||
"actions_filter_search": "search",
|
||||
"actions_filter_method": "methods",
|
||||
"actions_filter_api_method": "api methods",
|
||||
"actions_header_method": "method",
|
||||
"actions_header_title": "title",
|
||||
"actions_header_description": "description",
|
||||
"actions_header_date": "date",
|
||||
"actions_header_action": "action",
|
||||
"actions_table_summary": "Actions details done on BunkerWeb (CORE, UI, CLI...)",
|
||||
"bans_list": "Bans list",
|
||||
"bans_add_bans": "Add bans",
|
||||
"bans_add_entry": "Add entry",
|
||||
"bans_instances_total": "Total instances",
|
||||
"bans_ip_bans_total": "Total ip bans",
|
||||
"bans_filter_title": "Filter ban list",
|
||||
"bans_filter_search_ip": "search by ip",
|
||||
"bans_filter_reason": "select reason",
|
||||
"bans_list_no_bans": "no bans found",
|
||||
"bans_list_select_all": "select all",
|
||||
"bans_list_unselect_all": "unselect all",
|
||||
"bans_list_unban": "unban select",
|
||||
"bans_list_select_desc": "Select ban to perform actions.",
|
||||
"bans_list_header_check": "check",
|
||||
"bans_list_header_ip": "ip",
|
||||
"bans_list_header_reason": "reason",
|
||||
"bans_list_header_ban_start": "ban start",
|
||||
"bans_list_header_ban_end": "ban end",
|
||||
"bans_list_header_remain": "remaining",
|
||||
"bans_list_table_summary": "List of current bans (ip, reason, ban start, ban end and remaining time).",
|
||||
"bans_add_remove_ban_desc": "delete related field.",
|
||||
"bans_add_save_bans_warning": "Bans with conflict (like whitelist) will be added but ignored.",
|
||||
"bans_add_header_ip": "ip",
|
||||
"bans_add_header_ban_start": "start ban",
|
||||
"bans_add_header_ban_end": "end ban",
|
||||
"bans_add_header_reason": "reason",
|
||||
"bans_add_table_summary": "List of bans to add (ip, reason and date).",
|
||||
"bans_add_ip_placeholder": "127.0.0.1",
|
||||
"bans_add_reason_placeholder": "Manual",
|
||||
"custom_conf_total": "Configs total",
|
||||
"custom_conf_global": "Configs globals",
|
||||
"custom_conf_services": "Configs services",
|
||||
"custom_conf_filter_search_path": "search by path",
|
||||
"custom_conf_filter_search_name": "search by name",
|
||||
"custom_conf_filter_show_services_folders": "Show services",
|
||||
"custom_conf_filter_show_path_with_conf": "Path with conf file only",
|
||||
"custom_conf_add_file": "Add file",
|
||||
"custom_conf_breadcrumb": "Path with all folders to move on click.",
|
||||
"custom_conf_breadcrumb_item_desc": "Click to move to related path.",
|
||||
"custom_conf_breadcrumb_back_desc": "Go to previous folder path.",
|
||||
"custom_conf_modal_title": "{action} file",
|
||||
"custom_conf_modal_placeholder": "filename",
|
||||
"custom_conf_modal_path_desc": "Show complete path with input to set or update conf file.",
|
||||
"custom_conf_modal_editor_desc": "Fill with script that will be executed for this conf file.",
|
||||
"custom_conf_dot_conf": ".conf",
|
||||
"custom_conf_path": "Access folder path and show children files.",
|
||||
"custom_conf_dropdown_action": "Show possible actions on element.",
|
||||
"global_conf_select_plugin": "select plugin",
|
||||
"global_conf_select_plugin_placeholder": "search",
|
||||
"global_conf_filter_search": "search",
|
||||
"global_conf_filter_search_placeholder": "title, description...",
|
||||
"global_conf_filter_method": "methods",
|
||||
"jobs_total": "Total jobs",
|
||||
"jobs_reload": "Total reload",
|
||||
"jobs_success": "Total success",
|
||||
"jobs_filter_search": "search",
|
||||
"jobs_filter_search_placeholder": "keyword",
|
||||
"jobs_filter_success_state": "success state",
|
||||
"jobs_filter_reload_state": "reload state",
|
||||
"jobs_filter_interval": "interval",
|
||||
"jobs_headers_name": "name",
|
||||
"jobs_headers_every": "interval",
|
||||
"jobs_headers_history": "history",
|
||||
"jobs_headers_reload": "reload",
|
||||
"jobs_headers_success": "success",
|
||||
"jobs_headers_last_run": "last run",
|
||||
"jobs_headers_cache": "cache",
|
||||
"jobs_headers_action": "action",
|
||||
"jobs_state_reload_failed": "reload failed",
|
||||
"jobs_state_reload_succeed": "reload succeed",
|
||||
"jobs_state_success_failed": "failed",
|
||||
"jobs_state_success_succeed": "succeed",
|
||||
"jobs_actions_run": "run",
|
||||
"jobs_actions_show_history": "Show within a modal actions history.",
|
||||
"jobs_actions_cache_download": "Download cache",
|
||||
"jobs_history_title": "history",
|
||||
"jobs_history_headers_success": "success",
|
||||
"jobs_history_headers_start_date": "run started",
|
||||
"jobs_history_headers_end_date": "run ended",
|
||||
"jobs_table_summary": "List of jobs (name, interval, history, reload, success, cache and run).",
|
||||
"plugins_total": "plugins total",
|
||||
"plugins_internal": "plugins internal",
|
||||
"plugins_external": "plugins external",
|
||||
"plugins_filter_search": "search",
|
||||
"plugins_filter_search_placeholder": "keyword",
|
||||
"plugins_filter_type": "plugin type",
|
||||
"act": "plugins list",
|
||||
"plugins_list_actions_link": "link to custom plugin page.",
|
||||
"plugins_list_actions_delete": "delete related plugin.",
|
||||
"plugins_delete_modal_title": "Delete {name} ?",
|
||||
"plugins_delete_modal_text": "Are you sure to delete plugin {name} ?",
|
||||
"plugins_page_label": "Access to plugin page.",
|
||||
"services_total": "Service total",
|
||||
"services_methods_count": "Methods total",
|
||||
"services_filter_more_toggle": "Toggle more filters.",
|
||||
"services_service_search": "Search by name",
|
||||
"services_service_search_placeholder": "www.example.com",
|
||||
"services_service_select_method": "Methods",
|
||||
"services_service_select_bad_behavior": "Use bad behavior",
|
||||
"services_service_select_limit": "Use limit request",
|
||||
"services_service_select_reverse_proxy": "Use reverse proxy",
|
||||
"services_service_select_modsecurity": "Use ModSecurity",
|
||||
"services_service_select_cors": "Use CORS",
|
||||
"services_service_select_dnsbl": "Use DNSBL",
|
||||
"services_list_title": "Services and plugins",
|
||||
"services_list_switch_warning": "Select another services will reset unsave changes on current one.",
|
||||
"services_list_select_service": "select service",
|
||||
"services_list_select_plugin": "select plugin",
|
||||
"services_filter_search_setting": "search setting",
|
||||
"services_filter_search_setting_placeholder": "name, title, description...",
|
||||
"services_filter_method_setting": "Select method",
|
||||
"services_detail_active": "Setting is active.",
|
||||
"services_detail_inactive": "Setting is inactive.",
|
||||
"services_detail_bad_behavior": "Bad behavior",
|
||||
"services_detail_modsecurity": "ModSecurity",
|
||||
"services_detail_limit": "Limit request",
|
||||
"services_detail_reverse_proxy": "Reverse proxy",
|
||||
"services_detail_cors": "CORS",
|
||||
"services_detail_dnsbl": "DNSBL",
|
||||
"services_active_new": "Create new service",
|
||||
"services_active_clone": "Create new service (clone)",
|
||||
"services_active_delete": "Delete active service {name}",
|
||||
"services_active_base": "service {name}",
|
||||
"services_actions_new": "New service",
|
||||
"services_actions_warning": "At least an available and valid server name and/or update a setting to save.",
|
||||
"services_delete_title": "Delete service",
|
||||
"services_delete_msg": "Are you sure to delete service {name} ?",
|
||||
"services_redirect_desc": "redirect to service website / http content",
|
||||
"services_edit_desc": "open a modal to edit service",
|
||||
"services_delete_desc": "open a modal to delete service",
|
||||
"services_clone_desc": "Create new service using this one as base.",
|
||||
"services_draft": "Draft",
|
||||
"services_draft_desc": "Service is currently in draft mode",
|
||||
"services_online": "Online",
|
||||
"services_online_desc": "Service is currently online.",
|
||||
"reporting_total": "Total reports",
|
||||
"reporting_country": "Total countries",
|
||||
"reporting_reason": "Total reasons",
|
||||
"reporting_filter_search": "Search",
|
||||
"reporting_filter_search_placeholder": "url, user agent, data",
|
||||
"reporting_filter_method": "Method",
|
||||
"reporting_filter_country": "Country",
|
||||
"reporting_filter_status": "Status",
|
||||
"reporting_filter_reason": "Reason",
|
||||
"reporting_header_date": "Date",
|
||||
"reporting_header_ip": "IP",
|
||||
"reporting_header_country": "Country",
|
||||
"reporting_header_method": "Method",
|
||||
"reporting_header_url": "URL",
|
||||
"reporting_header_code": "Code",
|
||||
"reporting_header_user_agent": "User agent",
|
||||
"reporting_header_reason": "Reason",
|
||||
"reporting_header_data": "Data",
|
||||
"reporting_table_summary": "List of reports (date, ip, country, method, url, code, user agent, reason and data).",
|
||||
"account_settings": "Account settings",
|
||||
"account_tabs_username": "Username",
|
||||
"account_tabs_password": "Password",
|
||||
"account_tabs_totp": "TOTP",
|
||||
"account_password": "Account password",
|
||||
"account_password_new": "Account new password",
|
||||
"account_password_confirm": "Account confirm new password",
|
||||
"account_totp_qr_code": "TOTP QR code",
|
||||
"account_totp_secret": "TOTP secret key",
|
||||
"account_totp_code": "TOTP code",
|
||||
"account_totp_password": "TOTP code",
|
||||
"account_username": "Account username",
|
||||
"account_password_placeholder": "P@ssw0rd",
|
||||
"account_username_placeholder": "john.doe",
|
||||
"account_totp_code_placeholder": "123456",
|
||||
"account_totp_secret_placeholder": "secret key",
|
||||
"inp_select_default_desc": "Link to a custom visible select component. Shouldn't be used directly.",
|
||||
"inp_select_dropdown_button_desc": "Custom dropdown toggle button.",
|
||||
"inp_input_password_desc": "Toggle show/hide password type and so value.",
|
||||
"inp_input_clipboard_desc": "Copy value to clipboard.",
|
||||
"inp_input_error_required": "Field is required",
|
||||
"inp_input_error_format": "Invalid value format",
|
||||
"inp_input_valid": "Valid input.",
|
||||
"inp_select_label_empty": "Empty",
|
||||
"inp_upload_add": "click or drag-n-drop",
|
||||
"inp_upload_warning": "Uploaded files are directly executed.",
|
||||
"inp_upload_state_upload": "upload in progress",
|
||||
"inp_upload_state_fail": "upload failed",
|
||||
"inp_upload_state_success": "upload success",
|
||||
"plugin_info": "Plugin info",
|
||||
"plugin_test": "Test",
|
||||
"antibot_info": "Antibot plugin is a service that protect your website against bots.",
|
||||
"antibot_challenge": "Challenges",
|
||||
"antibot_challenge_detail": "Total number"
|
||||
}
|
||||
340
vuejs/client/src/lang/fr.json
Normal file
340
vuejs/client/src/lang/fr.json
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
{
|
||||
"setup_loader_default": "Chargement",
|
||||
"setup_loader_setup": "Mise en place...",
|
||||
"setup_logo_alt": "BunkerWeb image logo",
|
||||
"setup_title": "Assistant d'installation",
|
||||
"setup_account": "Compte",
|
||||
"setup_username": "identifiant",
|
||||
"setup_username_placeholder": "identifiant",
|
||||
"setup_password": "mot de passe",
|
||||
"setup_password_placeholder": "mot de passe",
|
||||
"setup_password_check": "confirmer mot de passe",
|
||||
"setup_password_check_placeholder": "même que le précédent",
|
||||
"setup_password_check_invalid": "La valeur ne correspond pas au précédent mot de passe",
|
||||
"setup_settings": "Paramètres",
|
||||
"setup_ui_host": "hôte dashboard (REVERSE_PROXY_HOST)",
|
||||
"setup_ui_host_placeholder": "http://[nom_hôte](:[port])",
|
||||
"setup_ui_url": "URL dashboard (REVERSE_PROXY_URL)",
|
||||
"setup_ui_url_placeholder": "/admin",
|
||||
"setup_server_name": "nom du serveur",
|
||||
"setup_server_name_placeholder": "www.example.com",
|
||||
"setup_server_name_value": "www.example.com",
|
||||
"setup_check_dns": "Vérifier le DNS du serveur ",
|
||||
"setup_check_dns_button": "Vérifier le DNS",
|
||||
"setup_check_dns_color_desc": "Couleur du résultat de la vérification du DNS",
|
||||
"setup_check_dns_result_unknown": "Le résultat de la vérification du DNS est inconnu",
|
||||
"setup_check_dns_result_success": "Le résultat de la vérification du DNS est un succès",
|
||||
"setup_check_dns_result_error": "Le résultat de la vérification du DNS est une erreur",
|
||||
"setup_lets_encrypt": "Auto let's encrypt",
|
||||
"setup_final_url": "Votre URL BunkerWeb final pour le dashboard est",
|
||||
"setup_button": "Mettre en place",
|
||||
"setup_form_invalid_submit": "Au moins un champ invalide ou manquant pour confirmer",
|
||||
"setup_failed": "Erreur pendant l'installation. Réessayez plus tard.",
|
||||
"login_title": "connexion",
|
||||
"login_username": "identifiant",
|
||||
"login_username_placeholder": "entrer identifiant",
|
||||
"login_password": "mot de passe",
|
||||
"login_password_placeholder": "entrer mot de passe",
|
||||
"login_log_button": "se connecter",
|
||||
"totp_error": "Le code du TOTP est invalide",
|
||||
"totp_logo_alt": "BunkerWeb logo",
|
||||
"totp_title": "Double authentification",
|
||||
"totp_code ": "code",
|
||||
"totp_code_placeholder": "954186",
|
||||
"totp_button": "Envoyer",
|
||||
"action_disable": "activer",
|
||||
"action_enable": "désactiver",
|
||||
"action_save": "sauvegarder",
|
||||
"action_add": "ajouter",
|
||||
"action_close": "fermer",
|
||||
"action_delete": "supprimer",
|
||||
"action_edit": "editer",
|
||||
"action_download": "télécharger",
|
||||
"action_create": "créer",
|
||||
"action_view": "voir",
|
||||
"action_stop": "stop",
|
||||
"action_ping": "ping",
|
||||
"action_reload": "reload",
|
||||
"action_upload": "upload",
|
||||
"action_delete_all": "supprimer tout",
|
||||
"api_pending": "Tentative de récupération de(s) {name}",
|
||||
"api_error": "Erreur de récupération de(s) {name}",
|
||||
"api_pending_default": "Tentative de récupération des données",
|
||||
"api_error_default": "Erreur de récupération des données",
|
||||
"dashboard_logo_alt": "image du logo BunkerWeb",
|
||||
"dashboard_logo_link_label": "Redirection vers l'accueil",
|
||||
"dashboard_bw": "BunkerWeb",
|
||||
"dashboard_docs": "docs",
|
||||
"dashboard_blog": "blog",
|
||||
"dashboard_privacy": "confidentialité",
|
||||
"dashboard_license": "licence",
|
||||
"dashboard_sitemap": "plan du site",
|
||||
"dashboard_default": "défaut",
|
||||
"dashboard_info": "info",
|
||||
"dashboard_filter": "filtres",
|
||||
"dashboard_advanced": "avancé",
|
||||
"dashboard_loader": "chargement",
|
||||
"dashboard_lang_dropdown_button_desc": "Toggle montre/cache le groupe radio (dropdown) pour changer de langue.",
|
||||
"dashboard_refresh_desc": "rafraîchissement de toutes les données.",
|
||||
"dashboard_home": "accueil",
|
||||
"dashboard_instances": "instances",
|
||||
"dashboard_global_config": "config globale",
|
||||
"dashboard_services": "services",
|
||||
"dashboard_configs": "configs",
|
||||
"dashboard_plugins": "plugins",
|
||||
"dashboard_jobs": "jobs",
|
||||
"dashboard_bans": "bans",
|
||||
"dashboard_actions": "actions",
|
||||
"dashboard_account": "compte",
|
||||
"dashboard_reporting": "rapports",
|
||||
"dashboard_menu_toggle_sidebar": "Ouvrir/fermer la sidebar de menu.",
|
||||
"dashboard_menu_close_sidebar": "Ferme la sidebar de menu.",
|
||||
"dashboard_menu_twitter_label": "redirection vers le Twitter de BunkerWeb",
|
||||
"dashboard_menu_linkedin_label": "redirection vers le Linkedin de BunkerWeb",
|
||||
"dashboard_menu_discord_label": "redirection vers le Discord de BunkerWeb",
|
||||
"dashboard_menu_github_label": "redirection vers le Github de BunkerWeb",
|
||||
"dashboard_menu_plugins_title": "pages plugin",
|
||||
"dashboard_menu_plugins_none": "envie de plugins custom ?",
|
||||
"dashboard_menu_plugins_none_doc": "voir la doc",
|
||||
"dashboard_menu_mode_light": "mode clair",
|
||||
"dashboard_menu_mode_dark": "mode sombre",
|
||||
"dashboard_menu_log_out": "se déconnecter",
|
||||
"dashboard_actions_title": "actions",
|
||||
"dashboard_actions_subtitle": "liste des feedbacks",
|
||||
"dashboard_feedback_close_sidebar": "fermer la sidebar de feeback",
|
||||
"dashboard_feedback_toggle_sidebar": "ouvrir/fermer la sidebar de feeback",
|
||||
"dashboard_ui": "iu",
|
||||
"dashboard_scheduler": "scheduler",
|
||||
"dashboard_autoconf": "autoconf",
|
||||
"dashboard_core": "core",
|
||||
"dashboard_global": "global",
|
||||
"dashboard_news_toggle_sidebar": "Ouvrir/fermer la sidebar de news.",
|
||||
"dashboard_news_close_sidebar": "Ferme la sidebar de news.",
|
||||
"dashboard_news_title": "news",
|
||||
"dashboard_news_subtitle": "restez à jour !",
|
||||
"dashboard_news_fetch_error": "Impossible de récupérer les news",
|
||||
"dashboard_newsletter_title": "Rejoindre la newsletter",
|
||||
"dashboard_newsletter_placeholder": "martin.dupont{'@'}example.com",
|
||||
"dashboard_newsletter_privacy_text": "j'ai lu et accepte",
|
||||
"dashboard_newsletter_privacy_text_link": "la politique de confidentialité",
|
||||
"dashboard_newsletter_subscribe_button": "souscrire",
|
||||
"dashboard_api_state_desc": "Montre les détails sur la récupération de données.",
|
||||
"dashboard_alert_close_desc": "Ferme l'alerte.",
|
||||
"dashboard_feedback_alert_desc": "Retour sur vos propres actions.",
|
||||
"dashboard_feedback_logs_desc": "Retour sur les actions BunkerWeb.",
|
||||
"dashboard_popover_button_desc": "Montre des détails sur les paramètres au survol.",
|
||||
"dashboard_popover_button": "Montre un popover avec des détails sur le paramètre",
|
||||
"dashboard_popover_detail_desc": "Un conteneur avec les détails sur le paramètre.",
|
||||
"dashboard_up": "up",
|
||||
"dashboard_down": "down",
|
||||
"dashboard_banner_title_1": "Besoin de support premium ?",
|
||||
"dashboard_banner_title_2": "Essayez Bunkerweb sur notre",
|
||||
"dashboard_banner_title_3": "Toutes informations disponibles sur",
|
||||
"dashboard_banner_link_1": "https://panel.bunkerweb.io/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_2": "https://demo.bunkerweb.io/link/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_3": "https://www.bunkerweb.io/?utm_campaign=self&utm_source=ui",
|
||||
"dashboard_banner_link_text_1": "BunkerWeb Panel est là !",
|
||||
"dashboard_banner_link_text_2": "demo web app !",
|
||||
"dashboard_banner_link_text_3": "notre website !",
|
||||
"home_version_is_latest": "est la dernière version",
|
||||
"home_version_latest_version": "dernière version",
|
||||
"home_version": "version",
|
||||
"home_internal": "internes",
|
||||
"home_external": "externes",
|
||||
"home_card_link_label": "redirection vers la page correspondante aux données",
|
||||
"instances_hostname": "hostname",
|
||||
"instances_method": "methode",
|
||||
"instances_port": "port",
|
||||
"instances_active": "instance active",
|
||||
"instances_inactive": "instance inactive",
|
||||
"instances_modal_delete_msg": "Etes-vous sûr de vouloir supprimer l'instance {hostname} ?",
|
||||
"instances_modal_delete_title": "Supprimer l'instance",
|
||||
"actions_total": "Actions total",
|
||||
"actions_ui": "Actions IU",
|
||||
"actions_core": "Actions CORE",
|
||||
"actions_filter_title": "filtres",
|
||||
"actions_filter_search": "rechercher",
|
||||
"actions_filter_method": "methodes",
|
||||
"actions_filter_api_method": "api methodes",
|
||||
"actions_header_method": "methode",
|
||||
"actions_header_title": "titre",
|
||||
"actions_header_description": "description",
|
||||
"actions_header_date": "date",
|
||||
"actions_header_action": "action",
|
||||
"actions_table_summary": "Détails des actions effectuées sur BunkerWeb (CORE, UI, CLI...)",
|
||||
"bans_list": "Liste des bans",
|
||||
"bans_add_bans": "Ajouter des bans",
|
||||
"bans_add_entry": "Ajouter une entrée",
|
||||
"bans_instances_total": "Total instances",
|
||||
"bans_ip_bans_total": "Total ip bans",
|
||||
"bans_filter_title": "Filtres liste des bans",
|
||||
"bans_filter_search_ip": "rechercher par ip",
|
||||
"bans_filter_reason": "sélectionner une raison",
|
||||
"bans_list_no_bans": "Aucun ban actif",
|
||||
"bans_list_select_all": "sélectionner tout",
|
||||
"bans_list_unselect_all": "déslectionner tout",
|
||||
"bans_list_unban": "déban sélection",
|
||||
"bans_list_select_desc": "Sélectionne un ban pour effectuer des actions.",
|
||||
"bans_list_header_check": "check",
|
||||
"bans_list_header_ip": "ip",
|
||||
"bans_list_header_reason": "raison",
|
||||
"bans_list_header_ban_start": "début ban",
|
||||
"bans_list_header_ban_end": "fin ban",
|
||||
"bans_list_header_remain": "durée restante",
|
||||
"bans_list_table_summary": "Liste des bans actuels (ip, raison, date et délai restant).",
|
||||
"bans_add": "Ajouter une entrée",
|
||||
"bans_add_remove_ban_desc": "Supprime le champ relié d'ajout de ban.",
|
||||
"bans_add_save_bans_warning": "Les bans avec un conflit (comme la whitelist) seront ajoutés mais ignorés.",
|
||||
"bans_add_header_ip": "ip",
|
||||
"bans_add_header_ban_start": "début ban",
|
||||
"bans_add_header_ban_end": "fin ban",
|
||||
"bans_add_header_reason": "raison",
|
||||
"bans_add_table_summary": "Liste des bans à ajouter (ip, raison et date).",
|
||||
"custom_conf_total": "Configs total",
|
||||
"custom_conf_global": "Configs globales",
|
||||
"custom_conf_services": "Configs services",
|
||||
"custom_conf_filter_search_path": "Rechercher par chemin",
|
||||
"custom_conf_filter_search_name": "Rechercher par nom",
|
||||
"custom_conf_filter_show_services_folders": "Voir les services",
|
||||
"custom_conf_filter_show_path_with_conf": "Chemin .conf seulement",
|
||||
"custom_conf_add_file": "Ajouter fichier",
|
||||
"custom_conf_breadcrumb": "Chemin avec chaque dossier cliquable pour s'y rendre.",
|
||||
"custom_conf_breadcrumb_item_desc": "Cliquer pour se rendre au chemin relié.",
|
||||
"custom_conf_breadcrumb_back_desc": "Aller au dossier parent.",
|
||||
"custom_conf_modal_title": "{action} fichier",
|
||||
"custom_conf_modal_placeholder": "nom du fichier",
|
||||
"custom_conf_modal_path_desc": "Montre le chemin entier avec un input avec le nom du fichier conf.",
|
||||
"custom_conf_modal_editor_desc": "Remplir avec le script qui doit s'exécuter pour ce fichier.",
|
||||
"custom_conf_dot_conf": ".conf",
|
||||
"custom_conf_path": "Accéder à l'intérieur du dossier pour voir les fichiers enfants.",
|
||||
"custom_conf_dropdown_action": "Affiche les actions possibles sur l'élément.",
|
||||
"global_conf_select_plugin": "Sélectionner un plugin",
|
||||
"global_conf_select_plugin_placeholder": "rechercher",
|
||||
"global_conf_filter_search": "rechercher",
|
||||
"global_conf_filter_search_placeholder": "titre, description...",
|
||||
"global_conf_filter_method": "methodes",
|
||||
"jobs_total": "Total jobs",
|
||||
"jobs_reload": "Total rechargés",
|
||||
"jobs_success": "Total réussis",
|
||||
"jobs_filter_search": "rechercher",
|
||||
"jobs_filter_search_placeholder": "mot clé",
|
||||
"jobs_filter_success_state": "état de réussite",
|
||||
"jobs_filter_reload_state": "état de rechargement",
|
||||
"jobs_filter_interval": "interval",
|
||||
"jobs_headers_name": "nom",
|
||||
"jobs_headers_every": "interval",
|
||||
"jobs_headers_history": "historique",
|
||||
"jobs_headers_reload": "rechargement",
|
||||
"jobs_headers_success": "réussite",
|
||||
"jobs_headers_last_run": "dernier tour",
|
||||
"jobs_headers_cache": "cache",
|
||||
"jobs_headers_action": "action",
|
||||
"jobs_state_reload_failed": "recharge échoué",
|
||||
"jobs_state_reload_succeed": "recharge réussie",
|
||||
"jobs_state_success_failed": "échec",
|
||||
"jobs_state_success_succeed": "succès",
|
||||
"jobs_actions_run": "lancer",
|
||||
"jobs_actions_show_history": "Montre dans un modal l'historique des actions au clique.",
|
||||
"jobs_actions_cache_download": "Télécharger le cache",
|
||||
"jobs_history_title": "historique",
|
||||
"jobs_history_headers_success": "réussite",
|
||||
"jobs_history_headers_start_date": "début exécution",
|
||||
"jobs_history_headers_end_date": "fin exécution",
|
||||
"jobs_table_summary": "Liste des jobs (nom, interval, historique, rechargement, réussite, cache et lancement).",
|
||||
"plugins_total": "plugins total",
|
||||
"plugins_internal": "plugins internes",
|
||||
"plugins_external": "plugins externes",
|
||||
"plugins_filter_search": "rechercher",
|
||||
"plugins_filter_search_placeholder": "mot clé",
|
||||
"plugins_filter_type": "type de plugin",
|
||||
"act": "liste des plugins",
|
||||
"plugins_list_actions_link": "lien vers la page custom du plugin",
|
||||
"plugins_list_actions_delete": "supprime le plugin relié.",
|
||||
"plugins_delete_modal_title": "Supprimer le {name} ?",
|
||||
"plugins_delete_modal_text": "Etes-vous sûr de vouloir supprimer le plugin {name} ?",
|
||||
"plugins_page_label": "Accède à la page du plugin concerné",
|
||||
"services_total": "total services",
|
||||
"services_methods_count": "Total méthodes",
|
||||
"services_filter_more_toggle": "Ouvrir/fermer les filtres supplémentaires.",
|
||||
"services_service_search": "rechercher par nom",
|
||||
"services_service_search_placeholder": "www.example.com",
|
||||
"services_service_select_method": "Méthodes",
|
||||
"services_service_select_bad_behavior": "Mauvais comportement",
|
||||
"services_service_select_limit": "Limite requête",
|
||||
"services_service_select_reverse_proxy": "Proxy inverse",
|
||||
"services_service_select_modsecurity": "ModSecurity",
|
||||
"services_service_select_cors": "CORS",
|
||||
"services_service_select_dnsbl": "DNSBL",
|
||||
"services_list_title": "Services and plugins",
|
||||
"services_list_switch_warning": "Changer de service réinitialise les changements non sauvegardés du service en cours.",
|
||||
"services_list_select_service": "sélectionner un service",
|
||||
"services_list_select_plugin": "sélectionner un plugin",
|
||||
"services_filter_search_setting": "rechercher un paramètre",
|
||||
"services_filter_search_setting_placeholder": "nom, titre, description...",
|
||||
"services_filter_method_setting": "Sélectionner une méthode",
|
||||
"services_detail_active": "Le paramètre est actif.",
|
||||
"services_detail_inactive": "Le paramètre est inactif.",
|
||||
"services_detail_bad_behavior": "Mauvais comportement",
|
||||
"services_detail_modsecurity": "ModSecurity",
|
||||
"services_detail_limit": "Limite requête",
|
||||
"services_detail_reverse_proxy": "Proxy inverse",
|
||||
"services_detail_cors": "CORS",
|
||||
"services_detail_dnsbl": "DNSBL",
|
||||
"services_active_new": "Créer un nouveau service",
|
||||
"services_active_clone": "Créer un nouveau service (clone)",
|
||||
"services_active_delete": "Supprime le service actif {name}",
|
||||
"services_active_base": "service {name}",
|
||||
"services_actions_new": "Nouveau service",
|
||||
"services_actions_warning": "Un nom de serveur valide et disponible et/ou un changement de paramètre pour sauvegarder.",
|
||||
"services_delete_title": "Supprimer un service",
|
||||
"services_delete_msg": "Etes-vous sûr de vouloir supprimer le service {name} ?",
|
||||
"services_redirect_desc": "redirection vers le site du service ou autre contenu http",
|
||||
"services_edit_desc": "ouvrir un modal pour éditer le service.",
|
||||
"services_delete_desc": "Ouvrir un modal pour supprimer le service.",
|
||||
"services_clone_desc": "Créer un nouveau service basé sur les paramètres de celui-ci.",
|
||||
"reporting_total": "Total rapports",
|
||||
"reporting_country": "Total pays",
|
||||
"reporting_reason": "Total raisons",
|
||||
"reporting_filter_search": "Recherche",
|
||||
"reporting_filter_search_placeholder": "url, agent utilisateur, data",
|
||||
"reporting_filter_method": "Méthode",
|
||||
"reporting_filter_country": "Pays",
|
||||
"reporting_filter_status": "Code",
|
||||
"reporting_filter_reason": "Raison",
|
||||
"reporting_header_date": "Date",
|
||||
"reporting_header_ip": "IP",
|
||||
"reporting_header_country": "Pays",
|
||||
"reporting_header_method": "Méthode",
|
||||
"reporting_header_url": "URL",
|
||||
"reporting_header_code": "Code",
|
||||
"reporting_header_user_agent": "Agent utilisateur",
|
||||
"reporting_header_reason": "Raison",
|
||||
"reporting_header_data": "Data",
|
||||
"reporting_table_summary": "Liste des rapports (date, ip, pays, méthode, url, code, agent utilisateur, raison, data).",
|
||||
"account_settings": "Paramètres de compte",
|
||||
"account_tabs_username": "Nom d'utilisateur",
|
||||
"account_tabs_password": "Mot de passe",
|
||||
"account_tabs_totp": "TOTP",
|
||||
"account_password": "Mot de passe",
|
||||
"account_password_new": "Nouveau mot de passe",
|
||||
"account_password_confirm": "Confirmer nouveau mot de passe",
|
||||
"account_totp_qr_code": "TOTP QR code",
|
||||
"account_totp_secret": "TOTP clé secrète",
|
||||
"account_totp_code": "TOTP code",
|
||||
"account_totp_password": "TOTP code",
|
||||
"account_username": "Nom d'utilisateur",
|
||||
"account_password_placeholder": "P@ssw0rd",
|
||||
"account_username_placeholder": "martin.dupont",
|
||||
"account_totp_code_placeholder": "123456",
|
||||
"account_totp_secret_placeholder": "clé secrète",
|
||||
"inp_select_default_desc": "Relié à un composant select custom. Ne pas utiliser directement.",
|
||||
"inp_select_dropdown_button_desc": "Bouton toggle de dropdown custom.",
|
||||
"inp_select_label_empty": "Vide",
|
||||
"inp_input_password_desc": "Toggle l'input pour montrer/cacher une donnée de type mot de passe.",
|
||||
"inp_input_clipboard_desc": "Copie la valeur dans le presse-papier.",
|
||||
"inp_upload_add": "cliquez ou glissez-déposez",
|
||||
"inp_upload_warning": "Les fichiers upload sont exécutés directement.",
|
||||
"inp_upload_state_upload": "upload en cours",
|
||||
"inp_upload_state_fail": "upload ratée",
|
||||
"inp_upload_state_success": "upload réussie"
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
import TestTile from "@components/TestTitle.vue";
|
||||
import Checkbox from "@components/Forms/Field/Checkbox.vue";
|
||||
import Select from "@components/Forms/Field/Select.vue";
|
||||
import Input from "@components/Forms/Field/Input.vue";
|
||||
|
||||
// Define reactive properties
|
||||
const data = reactive({
|
||||
|
|
@ -14,10 +16,48 @@ onBeforeMount(() => {
|
|||
data.title = dataEl.getAttribute('data-flask') && dataEl.getAttribute('data-flask') !== "{{ flask_data }}" ? dataEl.getAttribute('data-flask') : dataEl.getAttribute('data-default-value');
|
||||
})
|
||||
|
||||
const checkboxData = {
|
||||
id: 'test-checkbox',
|
||||
value: 'yes',
|
||||
name: 'test-checkbox',
|
||||
disabled: false,
|
||||
required: false,
|
||||
label: 'Test checkbox',
|
||||
tabId: '1',
|
||||
|
||||
}
|
||||
|
||||
const selectData = {
|
||||
id: 'test-select',
|
||||
value: 'yes',
|
||||
values: ['yes', 'no'],
|
||||
name: 'test-select',
|
||||
disabled: false,
|
||||
required: false,
|
||||
label: 'Test select',
|
||||
tabId: '1',
|
||||
}
|
||||
|
||||
const inputData = {
|
||||
id: 'test-input',
|
||||
value: 'yes',
|
||||
type: "text",
|
||||
name: 'test-input',
|
||||
disabled: false,
|
||||
required: false,
|
||||
label: 'Test input',
|
||||
pattern : "(test)",
|
||||
tabId: '1',
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-secondary flex flex-col items-center justify-center h-full">
|
||||
<TestTile :title="data.title" />
|
||||
<div style="width: 300px;">
|
||||
<Checkbox v-bind="checkboxData" />
|
||||
<Select v-bind="selectData" />
|
||||
<Input v-bind="inputData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { getI18n } from "@utils/lang.js";
|
||||
import Test from "./Test.vue";
|
||||
|
||||
createApp(Test).mount("#app");
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(Test)
|
||||
.use(pinia)
|
||||
.use(getI18n(["dashboard", "api", "action", "bans", "inp"]))
|
||||
.mount("#app");
|
||||
28
vuejs/client/src/store/bans.js
Normal file
28
vuejs/client/src/store/bans.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useSelectIPStore = defineStore("selectIP", () => {
|
||||
const data = ref(new Set());
|
||||
|
||||
function addIP(ip) {
|
||||
data.value.add(ip);
|
||||
}
|
||||
|
||||
function deleteIP(ip) {
|
||||
data.value.delete(ip);
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
data.value.clear();
|
||||
}
|
||||
|
||||
return { data, $reset, addIP, deleteIP };
|
||||
});
|
||||
|
||||
export const useAddModalStore = defineStore("addBanModal", () => {
|
||||
const isOpen = ref(false);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
};
|
||||
});
|
||||
31
vuejs/client/src/store/configs.js
Normal file
31
vuejs/client/src/store/configs.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useModalStore = defineStore("fileManagerModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
type: "folder",
|
||||
action: "view",
|
||||
path: "root/",
|
||||
pathLevel: 1,
|
||||
value: "",
|
||||
name: "root",
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
type: "folder",
|
||||
action: "view",
|
||||
path: "root/",
|
||||
pathLevel: 1,
|
||||
value: "",
|
||||
name: "root",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
55
vuejs/client/src/store/global.js
Normal file
55
vuejs/client/src/store/global.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useFeedbackStore = defineStore("feedback", () => {
|
||||
const data = ref([]);
|
||||
let feedID = 0;
|
||||
|
||||
async function addFeedback(type, status, message) {
|
||||
feedID++;
|
||||
data.value.push({
|
||||
id: feedID,
|
||||
isNew: true,
|
||||
type: type,
|
||||
status: status,
|
||||
message: message,
|
||||
});
|
||||
}
|
||||
|
||||
function removeFeedback(id) {
|
||||
data.value.splice(
|
||||
data.value.findIndex((item) => item["id"] === id),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
return { data, addFeedback, removeFeedback };
|
||||
});
|
||||
|
||||
export const useRefreshStore = defineStore("refresh", () => {
|
||||
const count = ref(0);
|
||||
|
||||
async function refresh() {
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, refresh };
|
||||
});
|
||||
|
||||
export const useBannerStore = defineStore("banner", () => {
|
||||
const isBanner = ref(true);
|
||||
const bannerClass = ref("banner");
|
||||
|
||||
async function setBannerVisible(bool) {
|
||||
isBanner.value = bool;
|
||||
bannerClass.value = bool ? "banner" : "no-banner";
|
||||
}
|
||||
|
||||
return { isBanner, bannerClass, setBannerVisible };
|
||||
});
|
||||
|
||||
export const useBackdropStore = defineStore("backdrop", () => {
|
||||
const clickCount = ref(0);
|
||||
|
||||
return { clickCount };
|
||||
});
|
||||
54
vuejs/client/src/store/instances.js
Normal file
54
vuejs/client/src/store/instances.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useDelModalStore = defineStore("delInstanceModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
hostname: "",
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
hostname: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
|
||||
export const useAddModalStore = defineStore("addInstanceModal", () => {
|
||||
const isOpen = ref(false);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
};
|
||||
});
|
||||
|
||||
export const useEditModalStore = defineStore("editInstanceModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
hostname: "",
|
||||
old_hostname: "",
|
||||
server_name: "",
|
||||
port: "",
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
hostname: "",
|
||||
old_hostname: "",
|
||||
server_name: "",
|
||||
port: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
23
vuejs/client/src/store/jobs.js
Normal file
23
vuejs/client/src/store/jobs.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useModalStore = defineStore("jobsModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
name: "",
|
||||
history: [],
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
name: "",
|
||||
history: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
16
vuejs/client/src/store/logs.js
Normal file
16
vuejs/client/src/store/logs.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useLogsStore = defineStore("logs", () => {
|
||||
const tags = ref([]);
|
||||
|
||||
function setTags(arr) {
|
||||
tags.value = arr;
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
tags.value = [];
|
||||
}
|
||||
|
||||
return { tags, $reset, setTags };
|
||||
});
|
||||
25
vuejs/client/src/store/plugins.js
Normal file
25
vuejs/client/src/store/plugins.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useDelModalStore = defineStore("delPluginModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
61
vuejs/client/src/store/services.js
Normal file
61
vuejs/client/src/store/services.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useModalStore = defineStore("ServiceModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
services: [],
|
||||
service: "",
|
||||
serviceName: "",
|
||||
operation: "",
|
||||
servicesNames: [],
|
||||
method: "",
|
||||
isDraft: false,
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
services: [],
|
||||
service: "",
|
||||
serviceName: "",
|
||||
operation: "",
|
||||
servicesNames: [],
|
||||
method: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
|
||||
export const useDelModalStore = defineStore("ServiceDelModal", () => {
|
||||
const isOpen = ref(false);
|
||||
const data = ref({
|
||||
service: "",
|
||||
method: "",
|
||||
});
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
service: "",
|
||||
method: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
data,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
|
||||
export const useFilterStore = defineStore("serviceCardFilter", () => {
|
||||
const isOpen = ref(false);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
};
|
||||
});
|
||||
60
vuejs/client/src/store/settings.js
Normal file
60
vuejs/client/src/store/settings.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useConfigStore = defineStore("config", () => {
|
||||
const data = ref({ global: {}, services: {} });
|
||||
|
||||
function updateConf(name, id, value, regex = ".*") {
|
||||
// Remove prev value
|
||||
removeConf(name, id);
|
||||
|
||||
// Try to update with another value
|
||||
const formatID = id.toUpperCase().replaceAll("-", "_");
|
||||
|
||||
// Case value is invalid to be added
|
||||
const validInp = new RegExp(regex);
|
||||
if (validInp.test(value) === false) return;
|
||||
|
||||
// Else add value
|
||||
if (name === "global") data.value[name][formatID] = value;
|
||||
if (name !== "global") {
|
||||
if (!(name in data.value["services"])) data.value["services"][name] = {};
|
||||
data.value["services"][name][formatID] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Case value is default or previous (and already in core config value)
|
||||
// Or before updating a value
|
||||
function removeConf(name, id) {
|
||||
if (!name || !id) return; // Need this to proceed
|
||||
|
||||
const formatID = id.toUpperCase().replaceAll("-", "_");
|
||||
|
||||
// Remove global value
|
||||
try {
|
||||
if (name === "global" && !!(formatID in data.value[name]))
|
||||
delete data.value[name][formatID];
|
||||
} catch (err) {}
|
||||
|
||||
// Remove service value
|
||||
try {
|
||||
if (name !== "global" && !!(formatID in data.value["services"][name]))
|
||||
delete data.value["services"][name][formatID];
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
data.value = {
|
||||
global: {},
|
||||
services: {},
|
||||
};
|
||||
}
|
||||
|
||||
return { data, $reset, updateConf, removeConf };
|
||||
});
|
||||
|
||||
export const useModesStore = defineStore("modes", () => {
|
||||
const data = ref(["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"]);
|
||||
|
||||
return { data, updateConf };
|
||||
});
|
||||
38
vuejs/client/src/utils/actions.js
Normal file
38
vuejs/client/src/utils/actions.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Filter actions
|
||||
export function getActionsByFilter(actions, filters) {
|
||||
actions.forEach((action, id) => {
|
||||
let isMatch = true;
|
||||
// Search filter
|
||||
if (
|
||||
(filters.search &&
|
||||
!action.title.toLowerCase().includes(filters.search.toLowerCase())) ||
|
||||
!action.description.toLowerCase().includes(filters.search.toLowerCase())
|
||||
)
|
||||
isMatch = false;
|
||||
// Method filter
|
||||
if (
|
||||
filters.method !== "all" &&
|
||||
!action.method.toLowerCase().includes(filters.method.toLowerCase())
|
||||
)
|
||||
isMatch = false;
|
||||
// Action api filter
|
||||
if (
|
||||
filters.actionApi !== "all" &&
|
||||
!action.api_method.toLowerCase().includes(filters.actionApi.toLowerCase())
|
||||
)
|
||||
isMatch = false;
|
||||
|
||||
action["isMatchFilter"] = isMatch;
|
||||
});
|
||||
|
||||
// Update actions
|
||||
return actions;
|
||||
}
|
||||
|
||||
export function getSelectList(baseItems, loopArr, keyCheck) {
|
||||
const arr = baseItems;
|
||||
loopArr.forEach((item) => {
|
||||
if (arr.indexOf(item[keyCheck]) === -1) arr.push(item[keyCheck]);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
107
vuejs/client/src/utils/api.js
Normal file
107
vuejs/client/src/utils/api.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// Setup response json
|
||||
export async function setResponse(type, status, message, data) {
|
||||
const res = { type: "", status: "", message: "", data: {} };
|
||||
|
||||
res["type"] = type;
|
||||
res["status"] = status;
|
||||
res["message"] = message;
|
||||
res["data"] = data;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Fetch api
|
||||
// state args must be reactive variable with isPend and isErr keys
|
||||
export async function fetchAPI(
|
||||
api,
|
||||
method,
|
||||
body = false,
|
||||
state = null,
|
||||
addFeedback = null,
|
||||
isJSON = true,
|
||||
fileName = null, // test.json (name + extension)
|
||||
) {
|
||||
// Block scope state object if any passed to avoid error
|
||||
!state ? (state = { isPend: false, isErr: false, data: {} }) : false;
|
||||
// Fetch
|
||||
// const baseURL = "http://localhost:7000"; // ? for dev
|
||||
const baseURL = window.location.origin; // ? for prod
|
||||
state.isPend = true;
|
||||
|
||||
return await fetch(`${baseURL}${api}`, {
|
||||
method: method.toUpperCase(),
|
||||
headers: {
|
||||
"Content-Type": isJSON ? "application/json" : "application/octet-stream",
|
||||
"X-CSRF-TOKEN": getCookie("csrf_access_token"),
|
||||
},
|
||||
// Only when exist and possible
|
||||
...(body &&
|
||||
method.toUpperCase() !== "GET" && { body: JSON.stringify(body) }),
|
||||
})
|
||||
.then((res) => {
|
||||
state.isPend = false;
|
||||
state.isErr = false;
|
||||
// Can be a json or a file
|
||||
return isJSON ? res.json() : res.blob();
|
||||
})
|
||||
.then((data) => {
|
||||
// Case no JSON, we handle file download
|
||||
if (!isJSON) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
|
||||
a.click();
|
||||
a.remove(); //afterwards we remove the element again
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isJSON) {
|
||||
state.isErr = data["type"] === "error" ? true : false;
|
||||
state.data = JSON.parse(
|
||||
typeof data["data"] === "string"
|
||||
? data["data"]
|
||||
: JSON.stringify(data["data"]),
|
||||
);
|
||||
addFeedback
|
||||
? addFeedback(data["type"], data["status"], data["message"])
|
||||
: false;
|
||||
return data;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
state.isPend = false;
|
||||
state.isErr = true;
|
||||
state.data = {};
|
||||
|
||||
if (!isJSON) {
|
||||
addFeedback
|
||||
? addFeedback(
|
||||
err["type"] || "error",
|
||||
err["status"] || 500,
|
||||
err["message"] ||
|
||||
"Internal Server Error, impossible to download file",
|
||||
)
|
||||
: false;
|
||||
}
|
||||
|
||||
if (isJSON) {
|
||||
addFeedback
|
||||
? addFeedback(
|
||||
err["type"] || "error",
|
||||
err["status"] || 500,
|
||||
err["message"] || "Internal Server Error, impossible to get JSON",
|
||||
)
|
||||
: false;
|
||||
}
|
||||
|
||||
// Set custom error data before throwing err
|
||||
return err;
|
||||
});
|
||||
}
|
||||
|
||||
export function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(";").shift();
|
||||
}
|
||||
14
vuejs/client/src/utils/bans.js
Normal file
14
vuejs/client/src/utils/bans.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Filter bans
|
||||
export function getBansByFilter(bans, filters) {
|
||||
bans.forEach((ban, id) => {
|
||||
let isMatch = true;
|
||||
|
||||
if (filters.search && !ban.ip.includes(filters.search)) isMatch = false;
|
||||
if (filters.reason !== "all" && !ban.reason.includes(filters.reason))
|
||||
isMatch = false;
|
||||
|
||||
ban["isMatchFilter"] = isMatch;
|
||||
});
|
||||
|
||||
return bans;
|
||||
}
|
||||
269
vuejs/client/src/utils/custom_configs.js
Normal file
269
vuejs/client/src/utils/custom_configs.js
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
// Custom configs types based on NGINX foundation
|
||||
// Will determine context and order to execute specific config
|
||||
export function getTypes() {
|
||||
return [
|
||||
"http",
|
||||
"server_http",
|
||||
"default_server_http",
|
||||
"modsec",
|
||||
"modsec_crs",
|
||||
"stream",
|
||||
"server_stream",
|
||||
];
|
||||
}
|
||||
|
||||
// Base structure for file manager with custom_configs
|
||||
// For UI we will convert types as base folders where we can create custom conf in it
|
||||
export function getBaseConfig() {
|
||||
const baseConfig = [];
|
||||
// We need a root folder
|
||||
baseConfig.push(generateItem("folder", "", false, false, false, false));
|
||||
// Base folders are after root
|
||||
const types = getTypes();
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
baseConfig.push(
|
||||
generateItem("folder", types[i], true, false, false, false),
|
||||
);
|
||||
}
|
||||
|
||||
return baseConfig;
|
||||
}
|
||||
|
||||
// Allow to create a valid item for file manager
|
||||
// type => "folder" || "file"
|
||||
// path => "path" (<type>/<service_id>/<name> or <type>/<name> or <name>)
|
||||
// canCreateFile => bool (possible to create a conf file on the folder)
|
||||
// canEdit => bool (possible to edit item like name or content)
|
||||
// canDelete => bool (allow to delete item)
|
||||
// children => [item] (item that need to be display when inside parent item)
|
||||
export function generateItem(
|
||||
type,
|
||||
path,
|
||||
canCreateFile,
|
||||
canCreateFolder,
|
||||
canEdit,
|
||||
canDelete,
|
||||
children = [],
|
||||
data = "",
|
||||
method = "static",
|
||||
) {
|
||||
const fullPath = `root${path ? `/${path}` : ``}`;
|
||||
return {
|
||||
type: type,
|
||||
path: fullPath,
|
||||
pathLevel: fullPath.split("/").length - 1,
|
||||
canDelete: canDelete,
|
||||
canEdit: canEdit,
|
||||
canCreateFile: canCreateFile,
|
||||
canCreateFolder: canCreateFolder,
|
||||
data: data,
|
||||
children: children,
|
||||
method: method,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateConfTree(configs, services) {
|
||||
const baseConf = getBaseConfig();
|
||||
|
||||
// Add services to base folders
|
||||
// Exclude some base folders that can only have roots
|
||||
const rootOnly = ["server_stream", "server_http", "modsec", "modsec_crs"];
|
||||
const servItems = [];
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
const servName = services[i];
|
||||
|
||||
baseConf.forEach((folder) => {
|
||||
// Target only base folder (pathLevel 1)
|
||||
if (folder.pathLevel !== 1) return;
|
||||
// Case exclude
|
||||
const folderName = folder["path"].replace("root/", "");
|
||||
if (rootOnly.includes(folderName)) return;
|
||||
|
||||
// Case not exclude
|
||||
const path = folder["path"].replace("root/", "");
|
||||
const servItem = generateItem(
|
||||
"folder",
|
||||
`${path}/${servName}`,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
folder.children.push(servItem);
|
||||
servItems.push(servItem);
|
||||
});
|
||||
}
|
||||
const conf = [...baseConf, ...servItems];
|
||||
|
||||
// Fetch config is always file type with data and actions
|
||||
// Retrieve file data and format
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
conf.push(
|
||||
generateItem(
|
||||
"file",
|
||||
`${configs[i].type}${
|
||||
configs[i].service_id ? `/${configs[i].service_id}` : ""
|
||||
}/${configs[i].name}`,
|
||||
false,
|
||||
false,
|
||||
configs[i].method === "static" ? false : true,
|
||||
configs[i].method === "static" ? false : true,
|
||||
[],
|
||||
configs[i].data || "",
|
||||
configs[i].method || "",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// We need to define parent and children to create tree structure
|
||||
// First we need to be sure that a parent exist or create it
|
||||
// (May not exist because fetch only file and not intermediate folders)
|
||||
const parents = [];
|
||||
for (let i = 0; i < conf.length; i++) {
|
||||
// Check only pathLevel > 2 because 0 = root, 1 = base
|
||||
const item = conf[i];
|
||||
if (item.pathLevel < 2) continue;
|
||||
// Get prev item path (parent)
|
||||
const splitPath = item["path"].split("/");
|
||||
splitPath.pop();
|
||||
const prevPath = splitPath.join("/");
|
||||
// Check if parent on base or on created ones
|
||||
const isParent =
|
||||
conf.filter((item) => item["path"] === prevPath).length === 0 &&
|
||||
parents.filter((item) => item["path"] === prevPath).length === 0
|
||||
? false
|
||||
: true;
|
||||
// Parent is always a folder because fetch return only file conf
|
||||
if (!isParent) {
|
||||
// Can create folder only on level 1 and 2
|
||||
const canCreateFolder =
|
||||
item.pathLevel === 1 || item.pathLevel === 2 ? true : false;
|
||||
|
||||
parents.push(
|
||||
generateItem(
|
||||
"folder",
|
||||
prevPath.replace("root/", ""),
|
||||
true,
|
||||
canCreateFolder,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Then we need to set children
|
||||
// children must be only one path level more than parent
|
||||
for (let i = 0; i < conf.length; i++) {
|
||||
const item = conf[i];
|
||||
|
||||
// Get children and set them to parent
|
||||
const children = [];
|
||||
conf.forEach((child) => {
|
||||
const isChild =
|
||||
child["path"].startsWith(item["path"]) &&
|
||||
child["pathLevel"] === item["pathLevel"] + 1
|
||||
? true
|
||||
: false;
|
||||
|
||||
if (isChild) children.push(child);
|
||||
});
|
||||
|
||||
item["children"] = children;
|
||||
}
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
// Filter custom configs
|
||||
export function getCustomConfByFilter(items, filters) {
|
||||
const itemsToDel = [];
|
||||
items.forEach((item, id) => {
|
||||
let isMatch = true;
|
||||
const path = item.path.replace("root/", "");
|
||||
const splitPath = path.split("/");
|
||||
const name = splitPath.pop();
|
||||
const pathLevel = item.pathLevel;
|
||||
// root exclude to avoid break
|
||||
if (pathLevel === 0) return;
|
||||
|
||||
// Check every filter
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
// Case no match by a previous filter
|
||||
if (!isMatch) continue;
|
||||
|
||||
// Case path keyword
|
||||
if (key === "pathKeyword" && value) {
|
||||
isMatch = path.includes(value) ? true : false;
|
||||
}
|
||||
|
||||
// Case name keyword
|
||||
if (key === "nameKeyword" && value) {
|
||||
isMatch = name.includes(value) ? true : false;
|
||||
}
|
||||
|
||||
// Case services folders
|
||||
if (key === "showServices" && value === "no" && pathLevel === 2) {
|
||||
isMatch = false;
|
||||
}
|
||||
|
||||
// Case check for .conf at end of path
|
||||
if (key === "showOnlyCaseConf" && value === "yes") {
|
||||
isMatch =
|
||||
items.filter(
|
||||
(item) => item.pathLevel === 3 && item.path.includes(path),
|
||||
).length === 0
|
||||
? false
|
||||
: true;
|
||||
}
|
||||
}
|
||||
|
||||
// Case no match
|
||||
if (!isMatch) itemsToDel.push(items[id]);
|
||||
});
|
||||
|
||||
// For items that didn't pass filter
|
||||
// We need to remove them itself as item and as other items children
|
||||
itemsToDel.forEach((itemDel) => {
|
||||
const delPath = itemDel.path;
|
||||
const delPathLevel = itemDel.pathLevel;
|
||||
// Get prev path level
|
||||
const prevPathLevel = itemDel.pathLevel - 1;
|
||||
// Avoid remove root
|
||||
if (prevPathLevel === -1) return;
|
||||
// Get prev path
|
||||
const splitPath = itemDel.path.split("/");
|
||||
splitPath.pop();
|
||||
const prevPath = splitPath.join("/");
|
||||
|
||||
// Remove item as children
|
||||
items.forEach((item) => {
|
||||
const path = item.path;
|
||||
const pathLevel = item.pathLevel;
|
||||
if (prevPathLevel !== pathLevel || prevPath !== path) return;
|
||||
// Get item that match
|
||||
const children = item.children;
|
||||
const matchIds = [];
|
||||
children.forEach((child, id) => {
|
||||
if (child.path !== delPath || child.pathLevel !== delPathLevel) return;
|
||||
matchIds.push(id);
|
||||
});
|
||||
|
||||
// Remove them using id
|
||||
matchIds.forEach((id) => {
|
||||
children[id] = "";
|
||||
});
|
||||
item.children = children.filter((item) => typeof item !== "string");
|
||||
|
||||
// At the end remove item itself
|
||||
item = "";
|
||||
});
|
||||
});
|
||||
|
||||
// Update items removing empty string
|
||||
return items
|
||||
.filter((item) => typeof item !== "string")
|
||||
.sort((a, b) => {
|
||||
if (a.type === "file" && b.type === "folder") return -1;
|
||||
});
|
||||
}
|
||||
19
vuejs/client/src/utils/global.js
Normal file
19
vuejs/client/src/utils/global.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
export function getDarkMode() {
|
||||
let darkMode = false;
|
||||
// Case on storage
|
||||
if (sessionStorage.getItem("mode")) {
|
||||
darkMode = sessionStorage.getItem("mode") === "dark" ? true : false;
|
||||
} else if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
// dark mode
|
||||
darkMode = true;
|
||||
sessionStorage.setItem("mode", "dark");
|
||||
} else {
|
||||
darkMode = false;
|
||||
sessionStorage.setItem("mode", "light");
|
||||
}
|
||||
|
||||
return darkMode;
|
||||
}
|
||||
83
vuejs/client/src/utils/jobs.js
Normal file
83
vuejs/client/src/utils/jobs.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
export function getJobsIntervalList() {
|
||||
return ["all", "once", "hour", "day", "week"];
|
||||
}
|
||||
|
||||
// Format to go from dict to array
|
||||
export async function jobsFormat(jobs) {
|
||||
const jobsArr = [];
|
||||
|
||||
Object.entries(jobs).forEach(([name, data]) => {
|
||||
jobsArr.push({ [name]: data });
|
||||
});
|
||||
|
||||
return jobsArr;
|
||||
}
|
||||
|
||||
// Format cache data for select
|
||||
export function getJobsCacheNames(caches) {
|
||||
const names = [];
|
||||
caches.forEach((cache) => {
|
||||
names.push(cache["file_name"]);
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
// Format cache data for select
|
||||
export function getServId(jobs, jobName, cacheName) {
|
||||
let servID;
|
||||
jobs.forEach((jobItem) => {
|
||||
for (const [jobN, job] of Object.entries(jobItem)) {
|
||||
if (jobN !== jobName) continue;
|
||||
for (const [key, value] of Object.entries(job["cache"])) {
|
||||
if (value["file_name"] !== cacheName) continue;
|
||||
servID = value["service_id"];
|
||||
}
|
||||
}
|
||||
});
|
||||
return servID;
|
||||
}
|
||||
|
||||
// Filter plugins
|
||||
export function getJobsByFilter(jobs, filters) {
|
||||
jobs.forEach((job, id) => {
|
||||
const jobName = Object.keys(job).join();
|
||||
const data = job[jobName];
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
// Check specific cases
|
||||
if (
|
||||
(!(key in data) && key !== "name" && key !== "success") ||
|
||||
(key === "name" && value === "") ||
|
||||
value === "all"
|
||||
)
|
||||
continue;
|
||||
const checkType = typeof value;
|
||||
let isMatch = true;
|
||||
|
||||
if (checkType === "string" && key === "name") {
|
||||
const filterValue = value.toLowerCase().trim();
|
||||
const checkValue = jobName.toLowerCase().trim();
|
||||
isMatch = checkValue.includes(filterValue) ? true : false;
|
||||
}
|
||||
|
||||
if (checkType === "string" && key !== "name") {
|
||||
const filterValue = value.toLowerCase().trim();
|
||||
const checkValue = data[key].toLowerCase().trim();
|
||||
isMatch = checkValue.includes(filterValue) ? true : false;
|
||||
}
|
||||
|
||||
if (checkType === "boolean" && key === "success") {
|
||||
isMatch = value === data["history"][0][key] ? true : false;
|
||||
}
|
||||
|
||||
if (checkType === "boolean" && key !== "success") {
|
||||
isMatch = value === data[key] ? true : false;
|
||||
}
|
||||
|
||||
// Result
|
||||
if (!isMatch) delete jobs[id];
|
||||
}
|
||||
});
|
||||
|
||||
// Update jobs removing empty index (deleted jobs)
|
||||
return jobs.filter(Object);
|
||||
}
|
||||
70
vuejs/client/src/utils/lang.js
Normal file
70
vuejs/client/src/utils/lang.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { createI18n } from "vue-i18n";
|
||||
|
||||
import fr from "@lang/fr.json" assert { type: "json" };
|
||||
import en from "@lang/en.json" assert { type: "json" };
|
||||
|
||||
const availablesLangs = ["en", "fr"];
|
||||
|
||||
function getAllLang() {
|
||||
return { fr: fr, en: en };
|
||||
}
|
||||
|
||||
function getAllLangCurrPage(pagesArr) {
|
||||
const langs = getAllLang();
|
||||
// for each lang
|
||||
for (const [langName, langVal] of Object.entries(langs)) {
|
||||
const data = {};
|
||||
for (const [key, value] of Object.entries(langVal)) {
|
||||
pagesArr.forEach((name) => {
|
||||
if (key.startsWith(`${name}_`)) data[key] = value;
|
||||
});
|
||||
}
|
||||
langs[langName] = data;
|
||||
}
|
||||
return langs;
|
||||
}
|
||||
|
||||
export function getI18n(pagesArr = []) {
|
||||
const messages =
|
||||
pagesArr.length > 0 ? getAllLangCurrPage(pagesArr) : getAllLang();
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: getLocalLang(), // set locale
|
||||
fallbackLocale: "en",
|
||||
messages, // set locale messages
|
||||
availableLocales: availablesLangs,
|
||||
});
|
||||
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export function getLocalLang() {
|
||||
// get store lang, or local, or default
|
||||
if (
|
||||
sessionStorage.getItem("lang") &&
|
||||
availablesLangs.indexOf(sessionStorage.getItem("lang")) !== -1
|
||||
) {
|
||||
return sessionStorage.getItem("lang");
|
||||
}
|
||||
|
||||
if (
|
||||
navigator.language &&
|
||||
availablesLangs.indexOf(navigator.language.split("-")[0].toLowerCase()) !==
|
||||
-1
|
||||
) {
|
||||
return navigator.language.split("-")[0].toLowerCase();
|
||||
}
|
||||
|
||||
if (
|
||||
navigator.languages &&
|
||||
navigator.languages > 0 &&
|
||||
availablesLangs.indexOf(
|
||||
navigator.languages[0].split("-")[0].toLowerCase(),
|
||||
) !== -1
|
||||
) {
|
||||
return navigator.languages[0].split("-")[0].toLowerCase();
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
27
vuejs/client/src/utils/logs.js
Normal file
27
vuejs/client/src/utils/logs.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Filter actions
|
||||
export function getLogsByFilter(actions, filters) {
|
||||
if (!Array.isArray(actions)) return [];
|
||||
|
||||
actions.forEach((action, id) => {
|
||||
let isMatch = true;
|
||||
// Tags filter
|
||||
if (filters.tags.indexOf("all") === -1) {
|
||||
let oneTagMatch = false;
|
||||
for (let i = 0; i < action.tags.length; i++) {
|
||||
const tag = action.tags[i];
|
||||
if (filters.tags.indexOf(tag) !== -1) oneTagMatch = true;
|
||||
}
|
||||
isMatch = oneTagMatch ? isMatch : false;
|
||||
}
|
||||
|
||||
action["isMatchFilter"] = isMatch;
|
||||
});
|
||||
|
||||
// Update actions
|
||||
return actions;
|
||||
}
|
||||
|
||||
// None limited list of tags
|
||||
export function getTags() {
|
||||
return ["plugin", "job", "action", "instance", "config", "custom_config"];
|
||||
}
|
||||
150
vuejs/client/src/utils/plugins.js
Normal file
150
vuejs/client/src/utils/plugins.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Set additional data to plugins
|
||||
export function setPluginsData(plugins) {
|
||||
plugins.forEach((plugin) => {
|
||||
const settings = plugin["settings"];
|
||||
// Replace some settings data
|
||||
Object.entries(settings).forEach(([setting, data]) => {
|
||||
// Add default method for filter
|
||||
if (!("method" in data)) data["method"] = "default";
|
||||
});
|
||||
});
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
// We want to add config data as value of settings of plugins
|
||||
export function addConfToPlugins(plugins, config) {
|
||||
plugins.forEach((plugin) => {
|
||||
const settings = plugin["settings"];
|
||||
|
||||
// Case not multiple, add direct custom value to setting
|
||||
Object.entries(settings).forEach(([settingName, settingData]) => {
|
||||
if (!("multiple" in settingData)) {
|
||||
try {
|
||||
settingData["value"] = config[settingName]["value"];
|
||||
settingData["method"] = config[settingName]["method"];
|
||||
delete config[settingName];
|
||||
} catch (err) {}
|
||||
}
|
||||
});
|
||||
|
||||
// Case multiple, format on config is setting_num
|
||||
// Multiples need to add a setting next to base one
|
||||
// To avoid loop issue by adding a setting
|
||||
// We need to add them after loop
|
||||
const multiples = [];
|
||||
Object.entries(settings).forEach(([settingName, settingData]) => {
|
||||
if (!!("multiple" in settingData)) {
|
||||
// We need to look on config
|
||||
// When a base name match config custom multiple
|
||||
Object.entries(config).forEach(([multipleName, multipleData]) => {
|
||||
if (!multipleName.startsWith(settingName)) return;
|
||||
// Case match, add multiple
|
||||
// Using base multiple model
|
||||
const cloneSetting = JSON.parse(JSON.stringify(settingData));
|
||||
cloneSetting["value"] = multipleData["value"];
|
||||
cloneSetting["method"] = multipleData["method"];
|
||||
multiples.push({ [multipleName]: cloneSetting });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Append custom multiple as regular settings
|
||||
// We may have only one custom setting on a group
|
||||
// We will fill empty settings from a group on settings multiple component logic
|
||||
for (let i = 0; i < multiples.length; i++) {
|
||||
const multSetting = multiples[i];
|
||||
const multName = Object.keys(multSetting).join();
|
||||
const multData = multSetting[multName];
|
||||
settings[multName] = multData;
|
||||
}
|
||||
});
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
// We want to remove settings that not match context
|
||||
// Or even entire plugin if all his settings not match context
|
||||
export function getPluginsByContext(plugins, context) {
|
||||
plugins.forEach((plugin, id) => {
|
||||
const settings = plugin["settings"];
|
||||
|
||||
Object.entries(settings).forEach(([setting, data]) => {
|
||||
// Remove settings that not match context
|
||||
const isContext = data["context"] === context ? true : false;
|
||||
if (!isContext) delete settings[setting];
|
||||
});
|
||||
|
||||
// Case no setting remaining, remove plugin
|
||||
if (Object.keys(plugin["settings"]).length === 0) delete plugins[id];
|
||||
});
|
||||
|
||||
// Update plugins removing empty index (deleted plugins)
|
||||
return plugins.filter(Object);
|
||||
}
|
||||
|
||||
// Filter plugins
|
||||
export function getPluginsByFilter(plugins, filters) {
|
||||
plugins.forEach((plugin, id) => {
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
// Case no key to check
|
||||
if (!(key in plugin) || value === "all") continue;
|
||||
const checkType = typeof value;
|
||||
let isMatch = true;
|
||||
|
||||
if (checkType === "string") {
|
||||
const filterValue = value.toLowerCase().trim();
|
||||
const checkValue = plugin[key].toLowerCase().trim();
|
||||
isMatch = checkValue.includes(filterValue) ? true : false;
|
||||
}
|
||||
|
||||
if (checkType === "boolean") {
|
||||
isMatch = value === plugin[key] ? true : false;
|
||||
}
|
||||
|
||||
// Result
|
||||
if (!isMatch) plugins[id] = "";
|
||||
}
|
||||
});
|
||||
|
||||
// Update plugins removing empty index (deleted plugins)
|
||||
return plugins.filter(String);
|
||||
}
|
||||
|
||||
// Translate keys that support multiple languages
|
||||
export function pluginI18n(plugins, lang, fallback) {
|
||||
plugins.forEach((plugin) => {
|
||||
// Main plugin info
|
||||
setLangOrFallback(plugin, "name", lang, fallback);
|
||||
setLangOrFallback(plugin, "description", lang, fallback);
|
||||
// Each settings info
|
||||
for (const [key, value] of Object.entries(plugin["settings"])) {
|
||||
setLangOrFallback(value, "help", lang, fallback);
|
||||
setLangOrFallback(value, "label", lang, fallback);
|
||||
}
|
||||
});
|
||||
return plugins;
|
||||
}
|
||||
|
||||
function setLangOrFallback(obj, key, lang, fallback) {
|
||||
try {
|
||||
if (!!(lang in obj[key])) {
|
||||
obj[key] = obj[key][lang];
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
// Case didn't find lang, we will get fallback (english)
|
||||
try {
|
||||
if (!!(fallback in obj[key])) {
|
||||
obj[key] = obj[key][fallback];
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
export function getRemainFromFilter(filterPlugins) {
|
||||
const remainPlugins = [];
|
||||
filterPlugins.forEach((item) => {
|
||||
item["isMatchFilter"] ? remainPlugins.push(item.name) : false;
|
||||
});
|
||||
return remainPlugins;
|
||||
}
|
||||
37
vuejs/client/src/utils/reporting.js
Normal file
37
vuejs/client/src/utils/reporting.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Filter reports
|
||||
export function getReportsByFilter(reports, filters) {
|
||||
reports.forEach((report, id) => {
|
||||
let isMatch = true;
|
||||
// Search filter
|
||||
if (
|
||||
(filters.search &&
|
||||
!report.title.toLowerCase().includes(filters.search.toLowerCase())) ||
|
||||
!report.description.toLowerCase().includes(filters.search.toLowerCase())
|
||||
)
|
||||
isMatch = false;
|
||||
|
||||
// Select filters
|
||||
const selectFilters = ["country", "method", "status", "reason"];
|
||||
|
||||
selectFilters.forEach((filter) => {
|
||||
if (
|
||||
filters[filter] !== "all" &&
|
||||
!report[filter].toLowerCase().includes(filters[filter].toLowerCase())
|
||||
)
|
||||
isMatch = false;
|
||||
});
|
||||
|
||||
report["isMatchFilter"] = isMatch;
|
||||
});
|
||||
|
||||
// Update reports
|
||||
return reports;
|
||||
}
|
||||
|
||||
export function getSelectList(baseItems, loopArr, keyCheck) {
|
||||
const arr = baseItems;
|
||||
loopArr.forEach((item) => {
|
||||
if (arr.indexOf(item[keyCheck]) === -1) arr.push(item[keyCheck]);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
61
vuejs/client/src/utils/services.js
Normal file
61
vuejs/client/src/utils/services.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Filter services
|
||||
export function getServicesByFilter(services, filters, details) {
|
||||
const result = {};
|
||||
for (const [key, plugins] of Object.entries(services)) {
|
||||
let isMatch = true;
|
||||
|
||||
// Check keyword
|
||||
if (filters.servName && !key.includes(filters.servName)) {
|
||||
isMatch = false;
|
||||
}
|
||||
|
||||
// Check is draft
|
||||
if (
|
||||
filters.isDraft !== "all" &&
|
||||
filters.isDraft !== services[key]["is_draft"]
|
||||
) {
|
||||
isMatch = false;
|
||||
}
|
||||
|
||||
// Check method
|
||||
if (filters.servMethod !== "all") {
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.id !== "general") return;
|
||||
const method = plugin.settings.SERVER_NAME.method;
|
||||
if (method !== filters.servMethod) isMatch = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove details with "all" filter
|
||||
const filteredDetails = details.filter(
|
||||
(detail) => filters[detail.id] !== "all",
|
||||
);
|
||||
|
||||
filteredDetails.forEach((detail) => {
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.id !== detail.id) return;
|
||||
const isSetting =
|
||||
plugin.settings[detail.setting].value === "yes" ? "true" : "false";
|
||||
if (isSetting !== filters[detail.id]) isMatch = false;
|
||||
});
|
||||
});
|
||||
|
||||
result[key] = isMatch;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get methods
|
||||
export function getServicesMethods(services) {
|
||||
const methods = [];
|
||||
for (const [key, plugins] of Object.entries(services)) {
|
||||
plugins.forEach((plugin) => {
|
||||
if (plugin.id !== "general") return;
|
||||
const method = plugin.settings.SERVER_NAME.method;
|
||||
if (!methods.includes(method)) methods.push(method);
|
||||
});
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
166
vuejs/client/src/utils/settings.js
Normal file
166
vuejs/client/src/utils/settings.js
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
export function getModes() {
|
||||
return ["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"];
|
||||
}
|
||||
|
||||
export function getMethodList() {
|
||||
return ["all", "ui", "default", "scheduler"];
|
||||
}
|
||||
|
||||
export function getDefaultMethod() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
// Filter plugins by settings
|
||||
export function getSettingsByFilter(plugins, filters) {
|
||||
plugins.forEach((plugin, id) => {
|
||||
const settings = plugin["settings"];
|
||||
// We will check if all settings failed to determine plugin display
|
||||
const settingsNum = Object.keys(settings).length;
|
||||
let noMatchCount = 0;
|
||||
|
||||
Object.entries(settings).forEach(([setting, data]) => {
|
||||
// Check if setting match
|
||||
let isMatch = true;
|
||||
// Look for every filter
|
||||
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
// Case one filter already fail
|
||||
if (!isMatch) continue;
|
||||
|
||||
// Case nothing to check
|
||||
if ((!(key in data) && key !== "keyword") || value === "all") {
|
||||
settings[setting]["isMatchFilter"] = true;
|
||||
continue;
|
||||
}
|
||||
const checkType = typeof value;
|
||||
|
||||
// Case filter is string like input or select
|
||||
if (checkType === "string") {
|
||||
const filterValue = value.toLowerCase().trim();
|
||||
// Case keyword filter, check multiple keys
|
||||
if (key === "keyword") {
|
||||
const label = !!("label" in data)
|
||||
? data["label"].toLowerCase().trim()
|
||||
: "";
|
||||
const help = !!("help" in data)
|
||||
? data["help"].toLowerCase().trim()
|
||||
: "";
|
||||
isMatch =
|
||||
label.includes(filterValue) || help.includes(filterValue)
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
// Case individual filter like method
|
||||
if (key !== "keyword") {
|
||||
const settingValue = data[key].toLowerCase().trim();
|
||||
|
||||
isMatch = settingValue.includes(filterValue) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// Case filter is checkbox-like
|
||||
if (checkType === "boolean") {
|
||||
isMatch = data[key] === value ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// After every filter check
|
||||
settings[setting]["isMatchFilter"] = isMatch;
|
||||
isMatch ? true : noMatchCount++;
|
||||
});
|
||||
|
||||
// Case no settings match, hide plugin
|
||||
plugin["isMatchFilter"] = settingsNum === noMatchCount ? false : true;
|
||||
});
|
||||
|
||||
// Update plugins removing empty index (deleted plugins)
|
||||
return plugins;
|
||||
}
|
||||
|
||||
// Keep only simple settings for specific plugin
|
||||
export function getSettingsSimple(settings) {
|
||||
Object.entries(settings).forEach(([setting, data]) => {
|
||||
// Remove settings that are multiple
|
||||
if (!!("multiple" in data)) delete settings[setting];
|
||||
});
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Keep only multiple settings for specific plugin
|
||||
export function getSettingsMultiple(settings) {
|
||||
Object.entries(settings).forEach(([setting, data]) => {
|
||||
// Remove settings that aren't multiple
|
||||
if (!("multiple" in data)) delete settings[setting];
|
||||
});
|
||||
return settings;
|
||||
}
|
||||
|
||||
// For a specific plugin, loop on settings
|
||||
// Get the number of different multiple names
|
||||
// Move multiple settings from plugin.settings to plugin.multiples
|
||||
// Every settings is order by multiple name like plugin.multiples.name = {settings}
|
||||
export function getSettingsMultipleList(settings) {
|
||||
// Check to keep only multiple settings
|
||||
const multipleSettings = getSettingsMultiple(settings);
|
||||
|
||||
// Case no multiple settings
|
||||
if (Object.keys(multipleSettings).length === 0) return false;
|
||||
|
||||
// Case multiple, create better list
|
||||
const multiples = {};
|
||||
const multGroups = {};
|
||||
|
||||
// Get group names and base data
|
||||
// Match only when end by _num
|
||||
const regex = new RegExp(/(_\d*).$/gm);
|
||||
|
||||
Object.entries(multipleSettings).forEach(([setting, data]) => {
|
||||
if (!!("multiple" in data)) {
|
||||
// Add name group if doesn't exist
|
||||
const multName = data["multiple"];
|
||||
if (!(multName in multGroups)) multGroups[multName] = {};
|
||||
// Add setting if base one
|
||||
const suffix =
|
||||
setting.match(regex) === null ? null : setting.match(regex).join();
|
||||
// Case base
|
||||
if (!suffix && !("base" in multGroups[multName]))
|
||||
multGroups[multName]["base"] = {};
|
||||
if (!suffix) return (multGroups[multName]["base"][setting] = data);
|
||||
const suffixNum = suffix.replace("_", "");
|
||||
if (suffix && !(suffixNum in multGroups[multName]))
|
||||
multGroups[multName][suffixNum] = {};
|
||||
if (suffix) multGroups[multName][suffixNum][setting] = data;
|
||||
}
|
||||
});
|
||||
|
||||
// Case no multiple group
|
||||
if (multGroups.length === 0) return false;
|
||||
|
||||
// Some group can have only few settings custom
|
||||
// We have to fill missing settings using base data
|
||||
Object.entries(multGroups).forEach(([groupName, groupSettings]) => {
|
||||
// Base to compare
|
||||
const baseSettings = groupSettings["base"];
|
||||
const baseLength = Object.keys(baseSettings).length;
|
||||
|
||||
Object.entries(groupSettings).forEach(([settingName, settings]) => {
|
||||
// Stop if base itself or already fill
|
||||
if (settingName === "base" || Object.keys(settings).length === baseLength)
|
||||
return;
|
||||
// Else, check for every setting if exist, if not create it
|
||||
Object.entries(settings).forEach(([settingName, data]) => {
|
||||
const suffix = settingName.match(regex).join();
|
||||
Object.entries(baseSettings).forEach(
|
||||
([baseSettingName, baseSettingData]) => {
|
||||
// Case setting match base
|
||||
if (settingName.startsWith(baseSettingName)) return;
|
||||
// Case not, create
|
||||
settings[`${baseSettingName}${suffix}`] = baseSettingData;
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return multGroups;
|
||||
}
|
||||
22
vuejs/client/src/utils/tabindex.js
Normal file
22
vuejs/client/src/utils/tabindex.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Determine the tab index for each component
|
||||
const bannerIndex = "-1";
|
||||
const footerIndex = "0";
|
||||
const menuIndex = "1";
|
||||
const langIndex = "2";
|
||||
const menuFloatIndex = "3";
|
||||
const refreshIndex = "4";
|
||||
const feedbackIndex = "5";
|
||||
const newsIndex = "6";
|
||||
const contentIndex = "7";
|
||||
|
||||
export {
|
||||
bannerIndex,
|
||||
menuIndex,
|
||||
menuFloatIndex,
|
||||
langIndex,
|
||||
refreshIndex,
|
||||
feedbackIndex,
|
||||
newsIndex,
|
||||
footerIndex,
|
||||
contentIndex,
|
||||
};
|
||||
|
|
@ -12,8 +12,14 @@ export default defineConfig({
|
|||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
"@store": resolve(__dirname, "./src/store"),
|
||||
"@utils": resolve(__dirname, "./src/utils"),
|
||||
"@layouts": resolve(__dirname, "./src/layouts"),
|
||||
"@pages": resolve(__dirname, "./src/pages"),
|
||||
"@components": resolve(__dirname, "./src/components"),
|
||||
"@assets": resolve(__dirname, "./src/assets"),
|
||||
"@lang": resolve(__dirname, "./src/lang"),
|
||||
"@public": resolve(__dirname, "./public"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||
|
|
@ -1,35 +1,14 @@
|
|||
from flask import Flask, render_template
|
||||
from flask import Blueprint
|
||||
from flask import redirect, url_for
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# I want to setup templates and static files
|
||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||
|
||||
templates = Blueprint("static", __name__, template_folder="static/templates")
|
||||
assets = Blueprint("assets", __name__, static_folder="static/assets")
|
||||
images = Blueprint("images", __name__, static_folder="static/images")
|
||||
style = Blueprint("style", __name__, static_folder="static/css")
|
||||
js = Blueprint("js", __name__, static_folder="static/js")
|
||||
app.register_blueprint(templates)
|
||||
app.register_blueprint(assets)
|
||||
app.register_blueprint(images)
|
||||
app.register_blueprint(style)
|
||||
app.register_blueprint(js)
|
||||
app = Flask(__name__, template_folder="templates", static_url_path="", static_folder="static")
|
||||
|
||||
@app.route("/", methods=['GET', 'POST'])
|
||||
def render_index():
|
||||
# redirect to test
|
||||
return redirect(url_for('render_test'))
|
||||
|
||||
@app.route("/test", methods=['GET', 'POST'])
|
||||
def render_test():
|
||||
return render_template("test.html", flask_data="Title from Flask !")
|
||||
|
||||
@app.route("/test2", methods=['GET', 'POST'])
|
||||
def render_test2():
|
||||
return render_template("test.html")
|
||||
return render_template("home.html", flask_data="Title from Flask !")
|
||||
|
||||
|
||||
app.debug = True
|
||||
|
|
|
|||
Loading…
Reference in a new issue