add icons + modal working + start actions modal

* add icons to fit services table actions
* update modal and button component, they work together
* enhance modal content components style
* start actions modal ui components : manage, draft/online and delete done, starting plugins status
* update services builder
* add i18n for services page
* add utils
This commit is contained in:
Jordan Blasenhauer 2024-07-30 17:08:10 +02:00
parent 27f55edf3c
commit 1bf72bb55c
29 changed files with 526 additions and 411 deletions

View file

@ -12,7 +12,7 @@ import Filter from "@components/Widget/Filter.vue";
import GroupMultiple from "@components/Forms/Group/Multiple.vue";
import { plugin_types } from "@utils/variables";
import { useAdvancedForm } from "@store/form.js";
import { useCheckPluginsValidity } from "@utils/form.js";
import { useCheckPluginsValidity } from "@utils/global.js";
import { v4 as uuidv4 } from "uuid";
/**

View file

@ -7,7 +7,7 @@ import Subtitle from "@components/Widget/Subtitle.vue";
import Button from "@components/Widget/Button.vue";
import Text from "@components/Widget/Text.vue";
import { v4 as uuidv4 } from "uuid";
import { useCheckPluginsValidity } from "@utils/form.js";
import { useCheckPluginsValidity } from "@utils/global.js";
import { useEasyForm } from "@store/form.js";
/**

View file

@ -0,0 +1,52 @@
<script setup>
import { defineProps, reactive } from "vue";
/**
@name Icons/Document.vue
@description This component is a svg icon representing document.
@example
{
color: 'orange',
}
@param {string} [iconClass="icon-default"] - The class of the icon.
@param {string} [color="cyan"] - The color of the icon between some tailwind css available colors (purple, green, red, orange, blue, yellow, gray, dark, amber, emerald, teal, indigo, cyan, sky, pink...). Darker colors are also available using the base color and adding '-darker' (e.g. 'red-darker').
@param {boolean} [disabled=false] - If true, the icon will be disabled.
*/
const props = defineProps({
iconClass: {
type: String,
required: false,
default: "icon-default",
},
color: {
type: String,
required: false,
default: "cyan",
},
disabled: { type: Boolean, required: false, default: false },
});
const icon = reactive({
color: props.color || "cyan",
});
</script>
<template>
<svg
:data-color="icon.color"
:aria-disabled="props.disabled ? 'true' : 'false'"
data-svg="document"
role="img"
aria-hidden="true"
:class="[props.iconClass, icon.color, 'fill']"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625Z"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
</template>

View file

@ -0,0 +1,52 @@
<script setup>
import { defineProps, reactive } from "vue";
/**
@name Icons/Eye.vue
@description This component is a svg icon representing eye.
@example
{
color: 'green',
}
@param {string} [iconClass="icon-default"] - The class of the icon.
@param {string} [color="cyan"] - The color of the icon between some tailwind css available colors (purple, green, red, orange, blue, yellow, gray, dark, amber, emerald, teal, indigo, cyan, sky, pink...). Darker colors are also available using the base color and adding '-darker' (e.g. 'red-darker').
@param {boolean} [disabled=false] - If true, the icon will be disabled.
*/
const props = defineProps({
iconClass: {
type: String,
required: false,
default: "icon-default",
},
color: {
type: String,
required: false,
default: "green",
},
disabled: { type: Boolean, required: false, default: false },
});
const icon = reactive({
color: props.color || "green",
});
</script>
<template>
<svg
:data-color="icon.color"
:aria-disabled="props.disabled ? 'true' : 'false'"
data-svg="document"
role="img"
aria-hidden="true"
:class="[props.iconClass, icon.color, 'fill']"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
<path
fill-rule="evenodd"
d="M1.323 11.447C2.811 6.976 7.028 3.75 12.001 3.75c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113-1.487 4.471-5.705 7.697-10.677 7.697-4.97 0-9.186-3.223-10.675-7.69a1.762 1.762 0 0 1 0-1.113ZM17.25 12a5.25 5.25 0 1 1-10.5 0 5.25 5.25 0 0 1 10.5 0Z"
clip-rule="evenodd"
/>
</svg>
</template>

View file

@ -43,9 +43,10 @@ const icon = reactive({
fill="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32l8.4-8.4Z"
/>
<path
d="M5.25 5.25a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a.75.75 0 0 0-1.5 0v5.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5V8.25a1.5 1.5 0 0 1 1.5-1.5h5.25a.75.75 0 0 0 0-1.5H5.25Z"
/>
</svg>
</template>

View file

@ -13,7 +13,6 @@ import { contentIndex } from "@utils/tabindex.js";
import Container from "@components/Widget/Container.vue";
import Icons from "@components/Widget/Icons.vue";
import { useUUID } from "@utils/global.js";
import { useForm } from "@utils/form.js";
/**
@name Widget/Button.vue
@description This component is a standard button.
@ -136,7 +135,6 @@ onBeforeMount(() => {
});
onMounted(() => {
window.addEventListener("click", useForm);
// Case modal, add accessibility data
if (typeof props.modal === "object") {
btnEl.value.setAttribute("aria-controls", btn.modalId);
@ -156,10 +154,6 @@ watch(
}
}
);
onUnmounted(() => {
window.removeEventListener("click", useForm);
});
</script>
<template>
@ -209,6 +203,7 @@ onUnmounted(() => {
v-if="btn.openModal"
:widgets="props.modal.widgets"
:isOpen="btn.openModal"
@close="btn.openModal = false"
/>
</Container>
</template>

View file

@ -64,6 +64,15 @@ onMounted(() => {
.closest("[data-is]")
.getAttribute("data-is")}`
: "button-group-default";
// Additionnal class for modal
if (group.class.includes("modal")) {
// Check if next sibling exists
// Else, this is the last element, we can add a margin top because this is main modal action buttons
if (!groupEl.value.nextElementSibling) {
group.class += " last";
}
}
});
</script>

View file

@ -1,5 +1,6 @@
<script setup>
import { defineProps, reactive, onMounted, ref, computed } from "vue";
import { useEqualStr } from "@utils/global.js";
import Box from "@components/Icons/Box.vue";
import Carton from "@components/Icons/Carton.vue";
import Core from "@components/Icons/Core.vue";
@ -33,6 +34,9 @@ import Lock from "@components/Icons/Lock.vue";
import Search from "@components/Icons/Search.vue";
import Exclamation from "@components/Icons/Exclamation.vue";
import Close from "@components/Icons/Close.vue";
import Pen from "@components/Icons/Pen.vue";
import Document from "@components/Icons/Document.vue";
import Eye from "@components/Icons/Eye.vue";
/**
@name Widget/Icons.vue
@ -81,14 +85,13 @@ const props = defineProps({
const icon = reactive({
color: props.color,
class: props.iconClass,
name: computed(() => props.iconName.toLowerCase()),
iconClass: props.iconClass,
});
const iconEl = ref();
onMounted(() => {
icon.class =
icon.iconClass =
props.iconClass || iconEl.value.closest("[data-is]")
? `icon-${iconEl.value.closest("[data-is]").getAttribute("data-is")}`
: "icon-default";
@ -97,197 +100,43 @@ onMounted(() => {
<template>
<div ref="iconEl" :class="[props.isStick ? 'stick' : '', 'icon-container']">
<Exclamation
v-if="icon.name === 'exclamation'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
v-if="useEqualStr(props.iconName, 'exclamation')"
v-bind="icon"
/>
<Box
v-if="icon.name === 'box'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Carton
v-if="icon.name === 'carton'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Core
v-if="icon.name === 'core'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<External
v-if="icon.name === 'external'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Search
v-if="icon.name === 'search'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Trash
v-if="icon.name === 'trash'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Lock
v-if="icon.name === 'lock'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Crown
v-if="icon.name === 'crown'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Discord
v-if="icon.name === 'discord'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Disk
v-if="icon.name === 'disk'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Disks
v-if="icon.name === 'disks'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Globe
v-if="icon.name === 'globe'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Info
v-if="icon.name === 'info'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Flag
v-if="icon.name === 'flag'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Gear
v-if="icon.name === 'gear'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Github
v-if="icon.name === 'github'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<House
v-if="icon.name === 'house'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<List
v-if="icon.name === 'list'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Key
v-if="icon.name === 'key'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Linkedin
v-if="icon.name === 'linkedin'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Plus
v-if="icon.name === 'plus'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Puzzle
v-if="icon.name === 'puzzle'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Settings
v-if="icon.name === 'settings'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Task
v-if="icon.name === 'task'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Trespass
v-if="icon.name === 'trespass'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Check
v-if="icon.name === 'check'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Cross
v-if="icon.name === 'cross'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Twitter
v-if="icon.name === 'twitter'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Wire
v-if="icon.name === 'wire'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Funnel
v-if="icon.name === 'funnel'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Redirect
v-if="icon.name === 'redirect'"
:iconClass="icon.class"
:color="props.color"
:disabled="props.disabled"
/>
<Close v-if="icon.name === 'close'" :iconClass="icon.class" />
<Box v-if="useEqualStr(props.iconName, 'box')" v-bind="icon" />
<Carton v-if="useEqualStr(props.iconName, 'carton')" v-bind="icon" />
<Core v-if="useEqualStr(props.iconName, 'core')" v-bind="icon" />
<External v-if="useEqualStr(props.iconName, 'external')" v-bind="icon" />
<Search v-if="useEqualStr(props.iconName, 'search')" v-bind="icon" />
<Trash v-if="useEqualStr(props.iconName, 'trash')" v-bind="icon" />
<Lock v-if="useEqualStr(props.iconName, 'lock')" v-bind="icon" />
<Crown v-if="useEqualStr(props.iconName, 'crown')" v-bind="icon" />
<Discord v-if="useEqualStr(props.iconName, 'discord')" v-bind="icon" />
<Disk v-if="useEqualStr(props.iconName, 'disk')" v-bind="icon" />
<Disks v-if="useEqualStr(props.iconName, 'disks')" v-bind="icon" />
<Globe v-if="useEqualStr(props.iconName, 'globe')" v-bind="icon" />
<Info v-if="useEqualStr(props.iconName, 'info')" v-bind="icon" />
<Flag v-if="useEqualStr(props.iconName, 'flag')" v-bind="icon" />
<Gear v-if="useEqualStr(props.iconName, 'gear')" v-bind="icon" />
<Github v-if="useEqualStr(props.iconName, 'github')" v-bind="icon" />
<House v-if="useEqualStr(props.iconName, 'house')" v-bind="icon" />
<List v-if="useEqualStr(props.iconName, 'list')" v-bind="icon" />
<Key v-if="useEqualStr(props.iconName, 'key')" v-bind="icon" />
<Linkedin v-if="useEqualStr(props.iconName, 'linkedin')" v-bind="icon" />
<Plus v-if="useEqualStr(props.iconName, 'plus')" v-bind="icon" />
<Puzzle v-if="useEqualStr(props.iconName, 'puzzle')" v-bind="icon" />
<Settings v-if="useEqualStr(props.iconName, 'settings')" v-bind="icon" />
<Task v-if="useEqualStr(props.iconName, 'task')" v-bind="icon" />
<Trespass v-if="useEqualStr(props.iconName, 'trespass')" v-bind="icon" />
<Check v-if="useEqualStr(props.iconName, 'check')" v-bind="icon" />
<Cross v-if="useEqualStr(props.iconName, 'cross')" v-bind="icon" />
<Twitter v-if="useEqualStr(props.iconName, 'twitter')" v-bind="icon" />
<Wire v-if="useEqualStr(props.iconName, 'wire')" v-bind="icon" />
<Funnel v-if="useEqualStr(props.iconName, 'funnel')" v-bind="icon" />
<Redirect v-if="useEqualStr(props.iconName, 'redirect')" v-bind="icon" />
<Close v-if="useEqualStr(props.iconName, 'close')" v-bind="icon" />
<Pen v-if="useEqualStr(props.iconName, 'pen')" v-bind="icon" />
<Document v-if="useEqualStr(props.iconName, 'document')" v-bind="icon" />
<Eye v-if="useEqualStr(props.iconName, 'eye')" v-bind="icon" />
</div>
</template>

View file

@ -132,7 +132,10 @@ function modalKeyboardEvents(e) {
}
function modalClickEvents(e) {
if (e.target.closest("[data-modal]") !== modalEl.value && modalEl.value)
if (
(e.target.closest("[data-modal]") !== modalEl.value && modalEl.value) ||
!modalEl.value
)
return;
if (e.target.hasAttribute("data-close-modal")) useCloseModal();
}
@ -167,12 +170,12 @@ const emits = defineEmits(["close"]);
:class="['layout-modal-container', props.isOpen ? '' : 'hidden']"
:id="props.id"
>
<div data-close-modal class="layout-backdrop"></div>
<div class="layout-modal-wrap">
<div class="layout-backdrop"></div>
<div data-close-modal class="layout-modal-wrap">
<div class="layout-modal">
<div class="layout-modal-button-container">
<Button
data-close-modal
:attrs="{ 'data-close-modal': '' }"
:text="'action_close_modal'"
:hideText="true"
:iconName="'close'"

View file

@ -141,6 +141,7 @@
"icons_github_desc": "Github icon representing a link to a Github repository.",
"icons_linkedin_desc": "Linkedin icon representing a link to a Linkedin profile.",
"icons_twitter_desc": "Twitter icon representing a link to a Twitter account.",
"action_switch": "switch {name}",
"action_send": "send {name}",
"action_start": "start {name}",
"action_disable": "disable {name}",
@ -271,6 +272,20 @@
"services_search_desc": "Search within service name",
"services_methods": "Methods",
"services_methods_desc": "Only show services of the chosen method",
"services_draft": "Draft status",
"services_draft_desc": "Only show services of the chosen draft status"
"services_draft": "draft",
"services_online": "online",
"services_draft_desc": "Only show services of the chosen draft status",
"services_plugins_title": "plugins",
"services_plugins_subtitle": "Main plugins status for this service.",
"services_manage_title": "Manage settings",
"services_manage_subtitle": "Choose a mode to manage service",
"services_mode_easy": "Easy mode",
"services_mode_raw": "Raw mode",
"services_mode_advanced": "Advanced mode",
"services_draft_subtitle": "Service is currently in draft (configuration is not apply).",
"services_draft_switch_subtitle": "Switch to online ?",
"services_online_subtitle": "Service is currently online (configuration is apply).",
"services_online_switch_subtitle": "Switch to draft ?",
"services_delete_title": "Delete service",
"services_delete_subtitle": "Are you sure you want to delete the service below ?"
}

View file

@ -1,7 +1,8 @@
<script setup>
import { reactive, onBeforeMount } from "vue";
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderBans from "@components/Builder/Bans.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Bans.vue
@ -24,6 +25,10 @@ onBeforeMount(() => {
bans.builder = data;
});
onMounted(() => {
useGlobal();
});
const builder = [
{
type: "void",

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderGlobalConfig from "@components/Builder/GlobalConfig.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/GlobalConfig.vue
@ -23,6 +24,11 @@ onBeforeMount(() => {
: {};
globalConfig.builder = data;
});
onMounted(() => {
// Set the page title
useGlobal();
});
</script>
<template>

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderHome from "@components/Builder/Home.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Home.vue
@ -24,6 +25,10 @@ onBeforeMount(() => {
home.builder = data;
});
onMounted(() => {
// Set the page title
useGlobal();
});
// const data = [
// {
// type: "card",

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderInstances from "@components/Builder/Instances.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Instances.vue
@ -24,6 +25,10 @@ onBeforeMount(() => {
instances.builder = data;
});
onMounted(() => {
// Set the page title
useGlobal();
});
// const data = [
// {
// type: "Instance",

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderJobs from "@components/Builder/Jobs.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Jobs.vue
@ -88,6 +89,7 @@ function downloadCacheEvent() {
onMounted(() => {
downloadCacheEvent();
useGlobal();
});
</script>

View file

@ -2,7 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderPlugins from "@components/Builder/Plugins.vue";
import { useForm } from "@utils/form.js";
import { useGlobal } from "@utils/global";
/**
@name Page/PLugins.vue
@ -99,6 +99,7 @@ onBeforeMount(() => {
onMounted(() => {
redirectPlugin();
deletePlugin();
useGlobal();
});
const builder = [

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderReports from "@components/Builder/Reports.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Reports.vue
@ -24,6 +25,11 @@ onBeforeMount(() => {
reports.builder = data;
});
onMounted(() => {
// Set the page title
useGlobal();
});
const builder = [
{
type: "card",

File diff suppressed because one or more lines are too long

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderServices from "@components/Builder/Services.vue";
import { useGlobal } from "@utils/global";
/**
@name Page/Services.vue
@ -23,6 +24,11 @@ onBeforeMount(() => {
: {};
services.builder = data;
});
onMounted(() => {
// Set the page title
useGlobal();
});
</script>
<template>

View file

@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useSubmitForm } from "@utils/form.js";
import { useSubmitForm } from "@utils/global.js";
/**
@name createFormStore

View file

@ -1,112 +0,0 @@
/**
@name utils/form.js
@description This file contains form utils that will be used in the application by default.
This file contains functions related to form validation, form submission, and other form utils.
*/
/**
@name useForm
@description This function needs to be attach to an event like a click event.
This will check if the target element contains a data-submit-form attribute and try to parse it to submit the form.
@returns {void}
*/
function useForm(e) {
if (!e.target.hasAttribute("data-submit-form")) return;
try {
const data = JSON.parse(e.target.getAttribute("data-submit-form"));
useSubmitForm(data);
} catch (e) {
console.error(e);
}
}
/**
@name useSubmitForm
@description Create programmatically a form element and submit it with the given data object of type {key: value}.
This will create a FormData and append data arguments to it, retrieve the csrf token and send it with a regular form.
@example
{
instance: "1",
operation: "delete",
}
@param {object} data - Object with the form data to submit.
@returns {void}
*/
function useSubmitForm(data) {
// Create a form element
const form = document.createElement("form");
form.style.display = "none";
form.method = "POST";
// Retrieve csrf token form data-crfs-token
try {
const csrfToken = document.querySelector("[data-csrf-token]");
if (csrfToken) {
data.csrf_token = csrfToken.getAttribute("data-csrf-token");
}
} catch (e) {}
// Add input elements with the data object
for (const key in data) {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
// Append to be able to submit
document.querySelector("body").appendChild(form);
form.submit();
}
/**
@name useCheckPluginsValidity
@description Check all items keys if at least one match exactly the filter value.
@example
const template = [
{
name: "test",
settings: {
test: {
required: true,
value: "",
pattern: "^[a-zA-Z0-9]*$",
},
},
},
@param template - Template with plugins list and detail settings
@returns {array} - Array with error flags and error details
*/
function useCheckPluginsValidity(template) {
let isRegErr = false;
let isReqErr = false;
let settingErr = "";
let pluginErr = "";
let settingNameErr = "";
let id = 0;
template.forEach((plugin, index) => {
id = index;
for (const [key, value] of Object.entries(plugin.settings)) {
if (value.required && !value.value) {
isReqErr = true;
settingErr = key;
settingNameErr = value.name;
pluginErr = plugin.name;
break;
}
if (value.pattern && value.value) {
const regex = new RegExp(value.pattern);
if (!regex.test(value.value)) {
isRegErr = true;
settingErr = key;
settingNameErr = value.name;
pluginErr = plugin.name;
break;
}
}
}
});
return [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id];
}
export { useForm, useSubmitForm, useCheckPluginsValidity };

View file

@ -6,6 +6,123 @@ import { v4 as uuidv4 } from "uuid";
This file contains functions related to accessibilities, cookies, and other global utils.
*/
/**
@name useGlobal
@description This function needs to be apply on global level and will do some logic related to form, attributes, and other global actions.
For example, it will check if the target element has a data-link attribute and redirect the user to the define link.
It will check if the target element has a data-submit-form attribute and try to parse it to submit the form.
@returns {void}
*/
function useGlobal() {
window.addEventListener("click", useDataLinkAttr);
window.addEventListener("click", useSubmitAttr);
}
/**
@name useSubmitAttr
@description This function needs to be attach to an event like a click event.
This will check if the target element contains a data-submit-form attribute and try to parse it to submit the form.
@returns {void}
*/
function useSubmitAttr(e) {
if (!e.target.hasAttribute("data-submit-form")) return;
try {
const data = JSON.parse(e.target.getAttribute("data-submit-form"));
useSubmitForm(data);
} catch (e) {
console.error(e);
}
}
/**
@name useSubmitForm
@description Create programmatically a form element and submit it with the given data object of type {key: value}.
This will create a FormData and append data arguments to it, retrieve the csrf token and send it with a regular form.
@example
{
instance: "1",
operation: "delete",
}
@param {object} data - Object with the form data to submit.
@returns {void}
*/
function useSubmitForm(data) {
// Create a form element
const form = document.createElement("form");
form.style.display = "none";
form.method = "POST";
// Retrieve csrf token form data-crfs-token
try {
const csrfToken = document.querySelector("[data-csrf-token]");
if (csrfToken) {
data.csrf_token = csrfToken.getAttribute("data-csrf-token");
}
} catch (e) {}
// Add input elements with the data object
for (const key in data) {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
// Append to be able to submit
document.querySelector("body").appendChild(form);
form.submit();
}
/**
@name useCheckPluginsValidity
@description Check all items keys if at least one match exactly the filter value.
@example
const template = [
{
name: "test",
settings: {
test: {
required: true,
value: "",
pattern: "^[a-zA-Z0-9]*$",
},
},
},
@param template - Template with plugins list and detail settings
@returns {array} - Array with error flags and error details
*/
function useCheckPluginsValidity(template) {
let isRegErr = false;
let isReqErr = false;
let settingErr = "";
let pluginErr = "";
let settingNameErr = "";
let id = 0;
template.forEach((plugin, index) => {
id = index;
for (const [key, value] of Object.entries(plugin.settings)) {
if (value.required && !value.value) {
isReqErr = true;
settingErr = key;
settingNameErr = value.name;
pluginErr = plugin.name;
break;
}
if (value.pattern && value.value) {
const regex = new RegExp(value.pattern);
if (!regex.test(value.value)) {
isRegErr = true;
settingErr = key;
settingNameErr = value.name;
pluginErr = plugin.name;
break;
}
}
}
});
return [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id];
}
/**
@name useUUID
@description This function return a unique identifier using uuidv4 and a random number.
@ -39,4 +156,23 @@ function useEqualStr(type, compare) {
}
}
export { useUUID, useEqualStr };
/**
@name useDataLinkAttr
@description Check from event if the target has a data-link attribute. Case it is, it will be used to redirect the user to the define link.
This is useful to avoid using the <a> tag and use a <div> or <button> instead.
@param {e} event - The event to attach the function logic
@returns {void}
*/
function useDataLinkAttr(e) {
if (!e.target.hasAttribute("data-link")) return;
const link = e.target.getAttribute("data-link");
window.location.href = link;
}
export {
useGlobal,
useUUID,
useEqualStr,
useSubmitForm,
useCheckPluginsValidity,
};

View file

@ -527,6 +527,10 @@ body {
@apply absolute right-4 top-4 z-[30];
}
.layout-modal-content {
@apply gap-2 col-span-12 grid grid-cols-12 w-full relative;
}
.layout-settings {
@apply py-4 sm:gap-x-8 gap-y-8 col-span-12 grid grid-cols-12 w-full relative;
}
@ -1142,7 +1146,11 @@ body {
}
.button-group-modal {
@apply col-span-12 flex justify-center items-center mt-4 mx-4;
@apply col-span-12 flex justify-center items-center my-1 mx-4;
}
.button-group-modal.last {
@apply mt-5 mb-0;
}
.button-group-table {
@ -1234,7 +1242,7 @@ body {
}
.no-subtitle.title-modal {
@apply mb-6;
@apply mb-4;
}
.no-subtitle.title-container {

File diff suppressed because one or more lines are too long

View file

@ -76,7 +76,6 @@ export default {
"sm:flex-nowrap",
"md:flex-nowrap",
"lg:flex-nowrap",
"justify-center",
"sm:justify-center",
"md:justify-center",
@ -217,6 +216,22 @@ export default {
"sm:fixed",
"md:fixed",
"lg:fixed",
"mb-0",
"sm:mb-0",
"md:mb-0",
"lg:mb-0",
"ml-0",
"sm:ml-0",
"md:ml-0",
"lg:ml-0",
"mr-0",
"sm:mr-0",
"md:mr-0",
"lg:mr-0",
"mt-0",
"sm:mt-0",
"md:mt-0",
"lg:mt-0",
"ml-2",
"sm:ml-2",
"md:ml-2",
@ -241,6 +256,30 @@ export default {
"sm:mx-2",
"md:mx-2",
"lg:mx-2",
"m-4",
"sm:m-4",
"md:m-4",
"lg:m-4",
"mx-4",
"sm:mx-4",
"md:mx-4",
"lg:mx-4",
"mt-4",
"sm:mt-4",
"md:mt-4",
"lg:mt-4",
"mb-4",
"sm:mb-4",
"md:mb-4",
"lg:mb-4",
"ml-4",
"sm:ml-4",
"md:ml-4",
"lg:ml-4",
"mr-4",
"sm:mr-4",
"md:mr-4",
"lg:mr-4",
"pl-2",
"sm:pl-2",
"md:pl-2",

View file

@ -3007,9 +3007,7 @@ def plugins_builder(plugins, data={}):
plugins_details = []
for plugin in plugins:
can_be_delete = plugin.get("type", "ui") == "external" and plugin.get(
"method", "ui"
) in (
can_be_delete = plugin.get("type", "ui") == "external" and plugin.get("method", "ui") in (
"manual",
"default",
"ui",
@ -3021,18 +3019,11 @@ def plugins_builder(plugins, data={}):
"attrs": {
"data-plugin-id": plugin.get("id"),
"data-plugin-delete": "true" if can_be_delete else "false",
"data-plugin-redirect": (
"true" if plugin.get("page", False) else "false"
),
"data-plugin-redirect": ("true" if plugin.get("page", False) else "false"),
"data-plugin-type": plugin.get("type", "").lower(),
"data-plugin-name": plugin.get("name"),
},
"disabled": (
True
if is_readonly
or (plugin.get("type", "ui") == "pro" and not is_pro_version)
else False
),
"disabled": (True if is_readonly or (plugin.get("type", "ui") == "pro" and not is_pro_version) else False),
"popovers": [],
}

View file

@ -49,19 +49,25 @@
"data": {
"buttons": [
{
"id": "open-modal-settings-0",
"text": "settings",
"id": "open-modal-plugins-0",
"text": "plugins",
"hideText": true,
"color": "info",
"color": "success",
"size": "normal",
"iconName": "settings",
"iconName": "eye",
"iconColor": "white",
"modal": {
"widgets": [
{
"type": "Title",
"data": {
"title": "services_settings_title"
"title": "services_plugins_title"
}
},
{
"type": "Text",
"data": {
"text": "services_plugins_subtitle"
}
},
{
@ -91,9 +97,9 @@
"id": "open-modal-manage-0",
"text": "manage",
"hideText": true,
"color": "success",
"color": "edit",
"size": "normal",
"iconName": "gear",
"iconName": "pen",
"iconColor": "white",
"modal": {
"widgets": [
@ -103,15 +109,21 @@
"title": "services_manage_title"
}
},
{
"type": "Text",
"data": {
"text": "services_manage_subtitle"
}
},
{
"type": "ButtonGroup",
"data": {
"buttons": [
{
"id": "manage-service-btn-app1.example.com",
"text": "services_easy",
"text": "services_mode_easy",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -120,9 +132,9 @@
},
{
"id": "manage-service-btn-app1.example.com",
"text": "services_advanced",
"text": "services_mode_advanced",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -131,9 +143,9 @@
},
{
"id": "manage-service-btn-app1.example.com",
"text": "services_raw",
"text": "services_mode_raw",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -171,7 +183,7 @@
"id": "open-modal-draft-0",
"text": "online",
"hideText": true,
"color": "cyan",
"color": "blue",
"size": "normal",
"iconName": "globe",
"iconColor": "white",
@ -180,20 +192,20 @@
{
"type": "Title",
"data": {
"title": "services_edit_title"
"title": "services_online"
}
},
{
"type": "Text",
"data": {
"text": "services_edit_subtitle"
"text": "services_online_subtitle"
}
},
{
"type": "Text",
"data": {
"text": "app1.example.com",
"bold": true
"bold": true,
"text": "services_online_switch_subtitle"
}
},
{
@ -212,9 +224,9 @@
},
{
"id": "edit-service-btn-app1.example.com",
"text": "action_edit",
"text": "action_switch",
"disabled": false,
"color": "cyan",
"color": "success",
"size": "normal",
"attrs": {
"data-submit-form": "{\"SERVER_NAME\" : app1.example.com, \"OLD_SERVER_NAME\" : app1.example.com, \"operation\" : \"edit\", \"IS_DRAFT\" : no }"
@ -302,10 +314,10 @@
}
},
{
"method": "scheduler",
"method": "ui",
"type": "Text",
"data": {
"text": "scheduler"
"text": "ui"
}
},
{
@ -313,19 +325,25 @@
"data": {
"buttons": [
{
"id": "open-modal-settings-1",
"text": "settings",
"id": "open-modal-plugins-1",
"text": "plugins",
"hideText": true,
"color": "info",
"color": "success",
"size": "normal",
"iconName": "settings",
"iconName": "eye",
"iconColor": "white",
"modal": {
"widgets": [
{
"type": "Title",
"data": {
"title": "services_settings_title"
"title": "services_plugins_title"
}
},
{
"type": "Text",
"data": {
"text": "services_plugins_subtitle"
}
},
{
@ -355,9 +373,9 @@
"id": "open-modal-manage-1",
"text": "manage",
"hideText": true,
"color": "success",
"color": "edit",
"size": "normal",
"iconName": "gear",
"iconName": "pen",
"iconColor": "white",
"modal": {
"widgets": [
@ -367,15 +385,21 @@
"title": "services_manage_title"
}
},
{
"type": "Text",
"data": {
"text": "services_manage_subtitle"
}
},
{
"type": "ButtonGroup",
"data": {
"buttons": [
{
"id": "manage-service-btn-www.example.com",
"text": "services_easy",
"text": "services_mode_easy",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -384,9 +408,9 @@
},
{
"id": "manage-service-btn-www.example.com",
"text": "services_advanced",
"text": "services_mode_advanced",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -395,9 +419,9 @@
},
{
"id": "manage-service-btn-www.example.com",
"text": "services_raw",
"text": "services_mode_raw",
"disabled": false,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -430,34 +454,34 @@
{
"attrs": {
"data-server-name": "www.example.com",
"data-is-draft": "no"
"data-is-draft": "yes"
},
"id": "open-modal-draft-1",
"text": "online",
"text": "draft",
"hideText": true,
"color": "cyan",
"color": "blue",
"size": "normal",
"iconName": "globe",
"iconName": "document",
"iconColor": "white",
"modal": {
"widgets": [
{
"type": "Title",
"data": {
"title": "services_edit_title"
"title": "services_draft"
}
},
{
"type": "Text",
"data": {
"text": "services_edit_subtitle"
"text": "services_draft_subtitle"
}
},
{
"type": "Text",
"data": {
"text": "www.example.com",
"bold": true
"bold": true,
"text": "services_draft_switch_subtitle"
}
},
{
@ -476,12 +500,12 @@
},
{
"id": "edit-service-btn-www.example.com",
"text": "action_edit",
"text": "action_switch",
"disabled": false,
"color": "cyan",
"color": "success",
"size": "normal",
"attrs": {
"data-submit-form": "{\"SERVER_NAME\" : www.example.com, \"OLD_SERVER_NAME\" : www.example.com, \"operation\" : \"edit\", \"IS_DRAFT\" : no }"
"data-submit-form": "{\"SERVER_NAME\" : www.example.com, \"OLD_SERVER_NAME\" : www.example.com, \"operation\" : \"edit\", \"IS_DRAFT\" : yes }"
}
}
]
@ -496,7 +520,7 @@
},
"id": "open-modal-delete-1",
"text": "delete",
"disabled": true,
"disabled": false,
"hideText": true,
"color": "red",
"size": "normal",
@ -600,6 +624,7 @@
"id": "services-methods",
"value": "all",
"values": [
"ui",
"scheduler"
],
"name": "services-methods",

View file

@ -18,7 +18,7 @@ services = [
},
{
"USE_REVERSE_PROXY": {"value": "yes", "method": "scheduler", "global": False},
"IS_DRAFT": {"value": "no", "method": "default", "global": False},
"IS_DRAFT": {"value": "yes", "method": "default", "global": False},
"SERVE_FILES": {"value": "no", "method": "scheduler", "global": True},
"REMOTE_PHP": {"value": "", "method": "default", "global": True},
"AUTO_LETS_ENCRYPT": {"value": "no", "method": "default", "global": True},
@ -27,7 +27,7 @@ services = [
"USE_BAD_BEHAVIOR": {"value": "yes", "method": "default", "global": True},
"USE_LIMIT_REQ": {"value": "yes", "method": "default", "global": True},
"USE_DNSBL": {"value": "yes", "method": "default", "global": True},
"SERVER_NAME": {"value": "www.example.com", "method": "scheduler", "global": False},
"SERVER_NAME": {"value": "www.example.com", "method": "ui", "global": False},
},
]
@ -55,7 +55,9 @@ def table_widget(positions, header, items, filters, minWidth, title):
}
def services_action(server_name: str = "", operation: str = "", title: str = "", subtitle: str = "", is_draft: Union[bool, None] = None) -> dict:
def services_action(
server_name: str = "", operation: str = "", title: str = "", subtitle: str = "", additionnal: str = "", is_draft: Union[bool, None] = None
) -> dict:
buttons = [
{
@ -87,9 +89,9 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
buttons.append(
{
"id": f"{operation}-service-btn-{server_name}",
"text": f"action_{operation}",
"text": "action_switch",
"disabled": False,
"color": "cyan",
"color": "success",
"size": "normal",
"attrs": {
"data-submit-form": f"""{{"SERVER_NAME" : {server_name}, "OLD_SERVER_NAME" : {server_name}, "operation" : "edit", "IS_DRAFT" : {draft_value} }}""",
@ -104,17 +106,26 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
"title": title,
},
},
{
"type": "Text",
"data": {
"text": subtitle,
},
},
]
if operation == "delete" or operation == "edit":
if additionnal:
content.append(
{
"type": "Text",
"data": {
"text": subtitle,
"bold": True,
"text": additionnal,
},
}
)
if operation == "delete":
content.append(
{
"type": "Text",
@ -133,9 +144,9 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
mode_buttons.append(
{
"id": f"{operation}-service-btn-{server_name}",
"text": f"services_{mode}",
"text": f"services_mode_{mode}",
"disabled": False,
"color": "green",
"color": "info",
"size": "normal",
"attrs": {
"role": "link",
@ -183,15 +194,15 @@ def get_services_list(services):
"data": {
"buttons": [
{
"id": f"open-modal-settings-{index}",
"text": "settings",
"id": f"open-modal-plugins-{index}",
"text": "plugins",
"hideText": True,
"color": "info",
"color": "success",
"size": "normal",
"iconName": "settings",
"iconName": "eye",
"iconColor": "white",
"modal": services_action(
server_name=server_name, operation="settings", title="services_settings_title", subtitle="services_settings_subtitle"
server_name=server_name, operation="plugins", title="services_plugins_title", subtitle="services_plugins_subtitle"
),
},
{
@ -199,9 +210,9 @@ def get_services_list(services):
"id": f"open-modal-manage-{index}",
"text": "manage",
"hideText": True,
"color": "success",
"color": "edit",
"size": "normal",
"iconName": "gear",
"iconName": "pen",
"iconColor": "white",
"modal": services_action(
server_name=server_name, operation="manage", title="services_manage_title", subtitle="services_manage_subtitle"
@ -212,12 +223,17 @@ def get_services_list(services):
"id": f"open-modal-draft-{index}",
"text": "draft" if is_draft else "online",
"hideText": True,
"color": "cyan",
"color": "blue",
"size": "normal",
"iconName": "pen" if is_draft else "globe",
"iconName": "document" if is_draft else "globe",
"iconColor": "white",
"modal": services_action(
server_name=server_name, operation="edit", title="services_edit_title", subtitle="services_edit_subtitle", is_draft=is_draft
server_name=server_name,
operation="edit",
title="services_draft" if is_draft else "services_online",
subtitle="services_draft_subtitle" if is_draft else "services_online_subtitle",
additionnal="services_draft_switch_subtitle" if is_draft else "services_online_switch_subtitle",
is_draft=is_draft,
),
},
{

File diff suppressed because one or more lines are too long