plugins page modal working + add a11y utils

* now plugins page has a modal that allow to delete a plugin
* add some a11y to modal : force focus on a modal element on open + avoid focus elements outside modal + escape key will close modal
* add possibility to set attributs to Text and Popover components in order to add custom logic using attributs (like changing Text content or opening a modal on popover click)
* fix some false css properties
* enhanced modal elements style
This commit is contained in:
Jordan Blasenhauer 2024-06-21 16:36:58 +02:00
parent 18009189ab
commit ca09b28065
15 changed files with 503 additions and 84 deletions

View file

@ -507,7 +507,7 @@ body {
}
.layout-modal {
@apply relative min-w-[300px] sm:min-w-[450px] max-w-screen-xl max-h-[80vh] min-h-[200px] transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out px-6 py-4 overflow-hidden break-words grid grid-cols-12;
@apply relative min-w-[300px] sm:min-w-[450px] max-w-screen-xl max-h-[80vh] transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out px-6 py-4 overflow-hidden break-words grid grid-cols-12;
}
.layout-modal-button-container {
@ -1016,16 +1016,20 @@ body {
/* CONTENT COMPONENT */
.text-content {
@apply mb-0 break-word;
@apply col-span-12 mb-0 break-word;
}
.text-modal {
@apply col-span-12 mb-2 text-center break-word;
}
.text-unmatch {
@apply text-lg mb-0 break-word;
@apply col-span-12 text-lg mb-0 break-word;
}
.text-stat {
@apply my-1 font-bold dark:text-white/90 text-black uppercase break-word;
@apply col-span-12 my-1 font-bold dark:text-white/90 text-black uppercase break-word;
}
/* LIST COMPONENT */
@ -1087,6 +1091,14 @@ body {
@apply capitalize-first break-word w-full max-w-[80%] sm:max-w-[600px] col-span-12 font-sans text-sm font-semibold leading-normal uppercase dark:text-white/90 text-[#344767];
}
.title-modal {
@apply capitalize-first break-word w-full max-w-[80%] sm:max-w-[700px] col-span-12 font-bold dark:text-white/90 transition duration-300 ease-in-out text-xl text-[#344767] tracking-normal;
}
.no-subtitle.title-modal {
@apply mb-6;
}
.no-subtitle.title-container {
@apply mb-2.5;
}
@ -1105,6 +1117,7 @@ body {
.is-subtitle.title-container,
.is-subtitle.title-card,
.is-subtitle.title-modal,
.is-subtitle.title-stat,
.is-subtitle.title-content,
.is-subtitle.title-min {
@ -1283,6 +1296,12 @@ body {
@apply w-6.5 h-6.5;
}
/* BTN GROUP */
.btn-group-modal {
@apply col-span-12 justify-center items-center mt-4;
}
/* FILE MANAGER */
.file-manager-breadcrumb {
@ -2554,6 +2573,10 @@ body {
/* UTILS */
.bold {
@apply font-bold;
}
.-z-0 {
@apply -z-[0];
}

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,8 @@ import Grid from "@components/Widget/Grid.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import ListDetails from "@components/List/Details.vue";
import Title from "@components/Widget/Title.vue";
import Text from "@components/Widget/Text.vue";
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
/**
@name Builder/pLugin.vue
@ -74,6 +76,11 @@ const props = defineProps({
v-if="widget.type === 'ListDetails'"
v-bind="widget.data"
/>
<Text v-if="widget.type === 'Text'" v-bind="widget.data" />
<ButtonGroup
v-if="widget.type === 'ButtonGroup'"
v-bind="widget.data"
/>
</template>
</Grid>
</GridLayout>

View file

@ -36,7 +36,7 @@ import Button from "@components/Widget/Button.vue";
},
],
}
@param {string} [groupClass="justify-center align-center"] - Additional class for the flex container
@param {string} [groupClass="justify-center items-center"] - Additional class for the flex container
@param {array} buttons - List of buttons to display. Button component is used.
*/
@ -44,7 +44,7 @@ const props = defineProps({
groupClass: {
type: String,
required: false,
default: "justify-center align-center",
default: "justify-center items-center",
},
buttons: {
type: Array,

View file

@ -100,7 +100,7 @@ onMounted(() => {
<template>
<!-- modal -->
<template v-if="props.type === 'modal'">
<div class="layout-modal-container" :id="container.id">
<div data-modal class="layout-modal-container hidden" :id="container.id">
<div class="layout-backdrop"></div>
<div class="layout-modal-wrap" :data-hide-el="container.id">
<div class="layout-modal">

View file

@ -68,7 +68,7 @@ const props = defineProps({
<ListPairs :pairs="props.pairs" />
<ButtonGroup
:buttons="props.buttons"
:groupClass="'justify-end align-center'"
:groupClass="'justify-end item-center'"
/>
</Container>
</template>

View file

@ -13,11 +13,13 @@ import Icons from "@components/Widget/Icons.vue";
href: "#",
iconName: "info",
iconColor: "info",
attrs: { "data-popover": "test" },
}
@param {string} text - Content of the popover. Can be a translation key or by default raw text.
@param {string} [href="#"] - Link of the anchor. By default it is a # link.
@param {string} iconName - Name in lowercase of icons store on /Icons. If falsy value, no icon displayed.
@param {string} iconColor - Color of the icon between tailwind colors
@param {object} [attrs={}] - List of attributs to add to the text.
@param {string} [tag="a"] - By default it is a anchor tag, but we can use other tag like div in case of popover on another anchor
@param {string} [popoverClass=""] - Additional class for the popover container
@param {string} [svgSize="base"] - Determine svg size between sm, md, base and lg.
@ -160,6 +162,7 @@ onMounted(() => {
<template>
<component
v-bind="props.attrs"
ref="popoverBtn"
:tabindex="props.tabId"
:aria-controls="`${popover.id}-popover-text`"

View file

@ -9,11 +9,13 @@ import Flex from "@components/Widget/Flex.vue";
{
text: "This is a paragraph",
textClass: "text-3xl"
attrs: { id: "paragraph" },
}
@param {string} text - The text value. Can be a translation key or by default raw text.
@param {string} [textClass="text-content"] - Style of text. Can be replace by any class starting by 'text-' like 'text-stat'.
@param {string} [tag="p"] - The tag of the text. Can be p, span, div, h1, h2, h3, h4, h5, h6
@param {boolean|object} [icons=false] - The popover to display with the text. Check Popover component for more details.
@param {object} [attrs={}] - List of attributs to add to the text.
*/
const props = defineProps({
@ -36,17 +38,31 @@ const props = defineProps({
required: false,
default: false,
},
attrs: {
type: Object,
required: false,
default: {},
},
});
</script>
<template>
<component v-if="!props.icons" :is="props.tag" :class="[props.textClass]">
<component
v-if="!props.icons"
:is="props.tag"
v-bind="props.attrs"
:class="[props.textClass]"
>
{{ $t(props.text, props.text) }}
</component>
<Flex :flexClass="'justify-center'" v-if="props.icons">
<Icons v-if="props.icons" v-bind="props.icons" />
<component :is="props.tag" :class="[props.textClass, 'ml-2']">
<component
:is="props.tag"
v-bind="props.attrs"
:class="[props.textClass, 'ml-2']"
>
{{ $t(props.text, props.text) }}
</component>
</Flex>

View file

@ -64,6 +64,7 @@ const baseClass = computed(() => {
if (props.type === "content") return "title-content";
if (props.type === "min") return "title-min";
if (props.type === "stat") return "title-stat";
if (props.type === "modal") return "title-modal";
return "title-card";
});

View file

@ -213,5 +213,6 @@
"plugins_type": "Plugin type",
"plugins_type_desc": "Only show plugins of the chosen type",
"plugins_delete_desc": "Delete plugin",
"plugins_modal_delete": "Delete plugin"
"plugins_modal_delete_title": "Delete plugin",
"plugins_modal_delete_confirm": "Are you sure you want to delete the plugin below ?"
}

View file

@ -3,6 +3,7 @@ import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderPlugins from "@components/Builder/Plugins.vue";
import { useGlobal } from "@utils/global.js";
import { useForm } from "@utils/form.js";
/**
@name Page/PLugins.vue
@ -43,6 +44,7 @@ function redirectPlugin() {
function deletePlugin() {
const deleteData = {
name: "pluginName",
id: "pluginId",
type: "pluginType",
operation: "delete",
};
@ -59,14 +61,23 @@ function deletePlugin() {
return;
// Update data
deleteData.name = e.target
.closest("[data-plugin-name]")
.getAttribute("data-plugin-name");
deleteData.id = e.target
.closest("[data-plugin-id]")
.getAttribute("data-plugin-id");
deleteData.type = e.target
.closest("[data-plugin-type]")
.getAttribute("data-plugin-type");
// Attach data to submit button (need to check attributs data-delete-plugin)
const submitBtn = document.querySelector("[data-delete-plugin]");
submitBtn.setAttribute("data-delete-plugin", JSON.stringify(deleteData));
// Attach data to submit button (need to check attributs data-delete-plugin-submit)
const submitBtn = document.querySelector("[data-delete-plugin-submit]");
submitBtn.setAttribute("data-submit-form", JSON.stringify(deleteData));
// Prepare and show modal
const modal = document.querySelector("#modal-delete-plugin");
const modalPluginName = modal.querySelector("[data-modal-plugin-name]");
modalPluginName.textContent = deleteData.name;
modal.classList.remove("hidden");
},
true
);
@ -85,9 +96,11 @@ onBeforeMount(() => {
onMounted(() => {
useGlobal();
useForm();
redirectPlugin();
deletePlugin();
});
const builder = [
{
type: "modal",
@ -96,8 +109,53 @@ const builder = [
{
type: "Title",
data: {
title: "plugins_modal_delete",
type: "card",
title: "plugins_modal_delete_title",
type: "modal",
},
},
{
type: "Text",
data: {
text: "plugins_modal_delete_confirm",
textClass: "text-modal",
},
},
{
type: "Text",
data: {
text: "",
textClass: "text-modal bold",
attrs: {
"data-modal-plugin-name": "true",
},
},
},
{
type: "ButtonGroup",
data: {
buttons: [
{
id: "delete-plugin-btn",
text: "action_close",
disabled: false,
color: "close",
size: "normal",
attrs: {
"data-hide-el": "modal-delete-plugin",
},
},
{
id: "delete-plugin-btn",
text: "action_delete",
disabled: false,
color: "delete",
size: "normal",
attrs: {
"data-delete-plugin-submit": "",
},
},
],
groupClass: "btn-group-modal",
},
},
],
@ -185,6 +243,8 @@ const builder = [
"data-plugin-id": "general",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "pro",
"data-plugin-name": "General",
},
disabled: true,
popovers: [
@ -202,6 +262,8 @@ const builder = [
"data-plugin-id": "antibot",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Antibot",
},
disabled: false,
popovers: [
@ -219,6 +281,8 @@ const builder = [
"data-plugin-id": "authbasic",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Auth basic",
},
disabled: false,
popovers: [],
@ -230,6 +294,8 @@ const builder = [
"data-plugin-id": "backup",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "pro",
"data-plugin-name": "Backup",
},
disabled: true,
popovers: [
@ -252,6 +318,8 @@ const builder = [
"data-plugin-id": "badbehavior",
"data-plugin-delete": "true",
"data-plugin-redirect": "true",
"data-plugin-type": "external",
"data-plugin-name": "Bad behavior",
},
disabled: false,
popovers: [
@ -274,6 +342,8 @@ const builder = [
"data-plugin-id": "blacklist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Blacklist",
},
disabled: false,
popovers: [
@ -291,6 +361,8 @@ const builder = [
"data-plugin-id": "brotli",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Brotli",
},
disabled: false,
popovers: [],
@ -302,6 +374,8 @@ const builder = [
"data-plugin-id": "bunkernet",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "BunkerNet",
},
disabled: false,
popovers: [
@ -319,6 +393,8 @@ const builder = [
"data-plugin-id": "cors",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "CORS",
},
disabled: false,
popovers: [
@ -336,6 +412,8 @@ const builder = [
"data-plugin-id": "clientcache",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Client cache",
},
disabled: false,
popovers: [],
@ -347,6 +425,8 @@ const builder = [
"data-plugin-id": "country",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Country",
},
disabled: false,
popovers: [
@ -364,6 +444,8 @@ const builder = [
"data-plugin-id": "customcert",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Custom HTTPS certificate",
},
disabled: false,
popovers: [],
@ -375,6 +457,8 @@ const builder = [
"data-plugin-id": "db",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "DB",
},
disabled: false,
popovers: [],
@ -386,6 +470,8 @@ const builder = [
"data-plugin-id": "dnsbl",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "DNSBL",
},
disabled: false,
popovers: [
@ -403,6 +489,8 @@ const builder = [
"data-plugin-id": "errors",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Errors",
},
disabled: false,
popovers: [
@ -420,6 +508,8 @@ const builder = [
"data-plugin-id": "greylist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Greylist",
},
disabled: false,
popovers: [
@ -437,6 +527,8 @@ const builder = [
"data-plugin-id": "gzip",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Gzip",
},
disabled: false,
popovers: [],
@ -448,6 +540,8 @@ const builder = [
"data-plugin-id": "inject",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "HTML injection",
},
disabled: false,
popovers: [],
@ -459,6 +553,8 @@ const builder = [
"data-plugin-id": "headers",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Headers",
},
disabled: false,
popovers: [],
@ -470,6 +566,8 @@ const builder = [
"data-plugin-id": "jobs",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Jobs",
},
disabled: false,
popovers: [],
@ -481,6 +579,8 @@ const builder = [
"data-plugin-id": "letsencrypt",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Let's Encrypt",
},
disabled: false,
popovers: [],
@ -492,6 +592,8 @@ const builder = [
"data-plugin-id": "limit",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Limit",
},
disabled: false,
popovers: [
@ -509,6 +611,8 @@ const builder = [
"data-plugin-id": "metrics",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Metrics",
},
disabled: false,
popovers: [],
@ -520,6 +624,8 @@ const builder = [
"data-plugin-id": "misc",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Miscellaneous",
},
disabled: false,
popovers: [
@ -537,6 +643,8 @@ const builder = [
"data-plugin-id": "modsecurity",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "ModSecurity",
},
disabled: false,
popovers: [],
@ -548,6 +656,8 @@ const builder = [
"data-plugin-id": "php",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "PHP",
},
disabled: false,
popovers: [],
@ -559,6 +669,8 @@ const builder = [
"data-plugin-id": "pro",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Pro",
},
disabled: false,
popovers: [],
@ -570,6 +682,8 @@ const builder = [
"data-plugin-id": "realip",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Real IP",
},
disabled: false,
popovers: [],
@ -581,6 +695,8 @@ const builder = [
"data-plugin-id": "redirect",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Redirect",
},
disabled: false,
popovers: [],
@ -592,6 +708,8 @@ const builder = [
"data-plugin-id": "redis",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Redis",
},
disabled: false,
popovers: [
@ -609,6 +727,8 @@ const builder = [
"data-plugin-id": "reverseproxy",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Reverse proxy",
},
disabled: false,
popovers: [],
@ -620,6 +740,8 @@ const builder = [
"data-plugin-id": "reversescan",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Reverse scan",
},
disabled: false,
popovers: [
@ -637,6 +759,8 @@ const builder = [
"data-plugin-id": "selfsigned",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Self-signed certificate",
},
disabled: false,
popovers: [],
@ -648,6 +772,8 @@ const builder = [
"data-plugin-id": "sessions",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Sessions",
},
disabled: false,
popovers: [],
@ -659,6 +785,8 @@ const builder = [
"data-plugin-id": "ui",
"data-plugin-delete": "false",
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "UI",
},
disabled: false,
popovers: [],
@ -670,6 +798,8 @@ const builder = [
"data-plugin-id": "whitelist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Whitelist",
},
disabled: false,
popovers: [

View file

@ -9,7 +9,7 @@ import { v4 as uuidv4 } from "uuid";
/**
@name useGlobal
@description This function is a wrapper that contains all the global utils functions.
This function will for example update the aria-expanded attribute of an element in case we have an aria-controls attribute.
This function handle global click and keydown events to manage some states like show/hide elements, focus modals, and close modals.
*/
function useGlobal() {
setShowHideElA11y();
@ -19,6 +19,16 @@ function useGlobal() {
// Update some states
useShowEl(e);
useHideEl(e);
useFocusModal();
},
true
);
window.addEventListener(
"keydown",
(e) => {
if (e.key === "Escape") useCloseModal();
if (e.key === "Tab" || e.key === "Shift-Tab") useFocusModal();
},
true
);
@ -45,6 +55,27 @@ function setShowHideElA11y() {
}, 200);
}
/**
@name useHideEl
@description This function will check if an element controls an element visibility and close it if it's the case.
The element handler need to have a data-show-el attribute with the id of the target element as value.
This function needs to be link to an event listener to work.
This function will check if aria-controls and aria-expanded attributes are present, else will create them.
@example
<button data-close-el="modal">Close modal</button>
<div id="modal" class="">Modal content</div>
@param {Event} e - The event object.
*/
function useHideEl(e) {
if (!e.target.hasAttribute("data-hide-el")) return;
// hide
const hideElId = e.target.getAttribute("data-hide-el");
document.getElementById(hideElId).classList.add("hidden");
// Update a11y attributes
e.target.setAttribute("aria-controls", hideElId);
e.target.setAttribute("aria-expanded", "false");
}
/**
@name useShowEl
@description This function will check if an element controls an element visibility and show it if it's the case.
@ -67,24 +98,38 @@ function useShowEl(e) {
}
/**
@name useHideEl
@description This function will check if an element controls an element visibility and close it if it's the case.
The element handler need to have a data-show-el attribute with the id of the target element as value.
This function needs to be link to an event listener to work.
This function will check if aria-controls and aria-expanded attributes are present, else will create them.
@example
<button data-close-el="modal">Close modal</button>
<div id="modal" class="">Modal content</div>
@param {Event} e - The event object.
@name useFocusModal
@description This function check if a modal is present and a focusable element is present inside it.
If it's the case, the function will focus the element.
Case there is already a focused element inside the modal, avoid to focus it again.
@param {String} modalId - The id of the modal element.
*/
function useHideEl(e) {
if (!e.target.hasAttribute("data-hide-el")) return;
// hide
const hideElId = e.target.getAttribute("data-hide-el");
document.getElementById(hideElId).classList.add("hidden");
// Update a11y attributes
e.target.setAttribute("aria-controls", hideElId);
e.target.setAttribute("aria-expanded", "false");
function useFocusModal() {
setTimeout(() => {
// Check if a data-modal element without hidden class is present
const modalEl = document.querySelector("[data-modal]:not(.hidden)");
if (!modalEl) return;
// Get the current active element
const activeEl = document.activeElement;
// Check if the active element is inside the modal
if (modalEl.contains(activeEl)) return;
// Case not, focus first focusable element inside the modal
const focusable = modalEl.querySelector("[tabindex]");
if (focusable) focusable.focus();
}, 1);
}
/**
@name useCloseModal
@description This function check if a modal is present and will close it.
This is a shortcut to close a modal when the escape key is pressed, for example.
*/
function useCloseModal() {
// Check if a data-modal element without hidden class is present
const modalEl = document.querySelector("[data-modal]:not(.hidden)");
if (!modalEl) return;
// Close the modal
modalEl.classList.add("hidden");
}
/**

View file

@ -76,18 +76,18 @@ export default {
"sm:justify-start",
"md:justify-start",
"lg:justify-start",
"align-center",
"sm:align-center",
"md:align-center",
"lg:align-center",
"align-start",
"sm:align-start",
"md:align-start",
"lg:align-start",
"align-end",
"sm:align-end",
"md:align-end",
"lg:align-end",
"items-center",
"sm:items-center",
"md:items-center",
"lg:items-center",
"items-start",
"sm:items-start",
"md:items-start",
"lg:items-start",
"items-end",
"sm:items-end",
"md:items-end",
"lg:items-end",
"justify-content-center",
"sm:justify-content-center",
"md:justify-content-center",

View file

@ -3021,8 +3021,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
@ -3116,7 +3119,67 @@ def plugins_builder(plugins, data={}):
},
]
modal = {
"type": "modal",
"id": "modal-delete-plugin",
"widgets": [
{
"type": "Title",
"data": {
"title": "plugins_modal_delete_title",
"type": "modal",
},
},
{
"type": "Text",
"data": {
"text": "plugins_modal_delete_confirm",
"textClass": "text-modal",
},
},
{
"type": "Text",
"data": {
"text": "",
"textClass": "text-modal bold",
"attrs": {
"data-modal-plugin-name": "true",
},
},
},
{
"type": "ButtonGroup",
"data": {
"buttons": [
{
"id": "delete-plugin-btn",
"text": "action_close",
"disabled": False,
"color": "close",
"size": "normal",
"attrs": {
"data-hide-el": "modal-delete-plugin",
},
},
{
"id": "delete-plugin-btn",
"text": "action_delete",
"disabled": False,
"color": "delete",
"size": "normal",
"attrs": {
"data-delete-plugin-submit": "",
},
},
],
"groupClass": "btn-group-modal",
},
},
],
}
builder = [
modal,
{
"type": "card",
"widgets": [

View file

@ -1,4 +1,62 @@
[
{
"type": "modal",
"id": "modal-delete-plugin",
"widgets": [
{
"type": "Title",
"data": {
"title": "plugins_modal_delete_title",
"type": "modal"
}
},
{
"type": "Text",
"data": {
"text": "plugins_modal_delete_confirm",
"textClass": "text-modal"
}
},
{
"type": "Text",
"data": {
"text": "",
"textClass": "text-modal bold",
"attrs": {
"data-modal-plugin-name": "true"
}
}
},
{
"type": "ButtonGroup",
"data": {
"buttons": [
{
"id": "delete-plugin-btn",
"text": "action_close",
"disabled": false,
"color": "close",
"size": "normal",
"attrs": {
"data-hide-el": "modal-delete-plugin"
}
},
{
"id": "delete-plugin-btn",
"text": "action_delete",
"disabled": false,
"color": "delete",
"size": "normal",
"attrs": {
"data-delete-plugin-submit": ""
}
}
],
"groupClass": "btn-group-modal"
}
}
]
},
{
"type": "card",
"widgets": [
@ -90,7 +148,9 @@
"attrs": {
"data-plugin-id": "general",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "pro",
"data-plugin-name": "General"
},
"disabled": true,
"popovers": [
@ -107,7 +167,9 @@
"attrs": {
"data-plugin-id": "antibot",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Antibot"
},
"disabled": false,
"popovers": [
@ -124,7 +186,9 @@
"attrs": {
"data-plugin-id": "authbasic",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Auth basic"
},
"disabled": false,
"popovers": []
@ -135,7 +199,9 @@
"attrs": {
"data-plugin-id": "backup",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "pro",
"data-plugin-name": "Backup"
},
"disabled": true,
"popovers": [
@ -157,7 +223,9 @@
"attrs": {
"data-plugin-id": "badbehavior",
"data-plugin-delete": "true",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "external",
"data-plugin-name": "Bad behavior"
},
"disabled": false,
"popovers": [
@ -179,7 +247,9 @@
"attrs": {
"data-plugin-id": "blacklist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Blacklist"
},
"disabled": false,
"popovers": [
@ -196,7 +266,9 @@
"attrs": {
"data-plugin-id": "brotli",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Brotli"
},
"disabled": false,
"popovers": []
@ -207,7 +279,9 @@
"attrs": {
"data-plugin-id": "bunkernet",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "BunkerNet"
},
"disabled": false,
"popovers": [
@ -224,7 +298,9 @@
"attrs": {
"data-plugin-id": "cors",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "CORS"
},
"disabled": false,
"popovers": [
@ -241,7 +317,9 @@
"attrs": {
"data-plugin-id": "clientcache",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Client cache"
},
"disabled": false,
"popovers": []
@ -252,7 +330,9 @@
"attrs": {
"data-plugin-id": "country",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Country"
},
"disabled": false,
"popovers": [
@ -269,7 +349,9 @@
"attrs": {
"data-plugin-id": "customcert",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Custom HTTPS certificate"
},
"disabled": false,
"popovers": []
@ -280,7 +362,9 @@
"attrs": {
"data-plugin-id": "db",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "DB"
},
"disabled": false,
"popovers": []
@ -291,7 +375,9 @@
"attrs": {
"data-plugin-id": "dnsbl",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "DNSBL"
},
"disabled": false,
"popovers": [
@ -308,7 +394,9 @@
"attrs": {
"data-plugin-id": "errors",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Errors"
},
"disabled": false,
"popovers": [
@ -325,7 +413,9 @@
"attrs": {
"data-plugin-id": "greylist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Greylist"
},
"disabled": false,
"popovers": [
@ -342,7 +432,9 @@
"attrs": {
"data-plugin-id": "gzip",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Gzip"
},
"disabled": false,
"popovers": []
@ -353,7 +445,9 @@
"attrs": {
"data-plugin-id": "inject",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "HTML injection"
},
"disabled": false,
"popovers": []
@ -364,7 +458,9 @@
"attrs": {
"data-plugin-id": "headers",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Headers"
},
"disabled": false,
"popovers": []
@ -375,7 +471,9 @@
"attrs": {
"data-plugin-id": "jobs",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Jobs"
},
"disabled": false,
"popovers": []
@ -386,7 +484,9 @@
"attrs": {
"data-plugin-id": "letsencrypt",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Let's Encrypt"
},
"disabled": false,
"popovers": []
@ -397,7 +497,9 @@
"attrs": {
"data-plugin-id": "limit",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Limit"
},
"disabled": false,
"popovers": [
@ -414,7 +516,9 @@
"attrs": {
"data-plugin-id": "metrics",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Metrics"
},
"disabled": false,
"popovers": []
@ -425,7 +529,9 @@
"attrs": {
"data-plugin-id": "misc",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Miscellaneous"
},
"disabled": false,
"popovers": [
@ -442,7 +548,9 @@
"attrs": {
"data-plugin-id": "modsecurity",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "ModSecurity"
},
"disabled": false,
"popovers": []
@ -453,7 +561,9 @@
"attrs": {
"data-plugin-id": "php",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "PHP"
},
"disabled": false,
"popovers": []
@ -464,7 +574,9 @@
"attrs": {
"data-plugin-id": "pro",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Pro"
},
"disabled": false,
"popovers": []
@ -475,7 +587,9 @@
"attrs": {
"data-plugin-id": "realip",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Real IP"
},
"disabled": false,
"popovers": []
@ -486,7 +600,9 @@
"attrs": {
"data-plugin-id": "redirect",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Redirect"
},
"disabled": false,
"popovers": []
@ -497,7 +613,9 @@
"attrs": {
"data-plugin-id": "redis",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Redis"
},
"disabled": false,
"popovers": [
@ -514,7 +632,9 @@
"attrs": {
"data-plugin-id": "reverseproxy",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Reverse proxy"
},
"disabled": false,
"popovers": []
@ -525,7 +645,9 @@
"attrs": {
"data-plugin-id": "reversescan",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Reverse scan"
},
"disabled": false,
"popovers": [
@ -542,7 +664,9 @@
"attrs": {
"data-plugin-id": "selfsigned",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Self-signed certificate"
},
"disabled": false,
"popovers": []
@ -553,7 +677,9 @@
"attrs": {
"data-plugin-id": "sessions",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "Sessions"
},
"disabled": false,
"popovers": []
@ -564,7 +690,9 @@
"attrs": {
"data-plugin-id": "ui",
"data-plugin-delete": "false",
"data-plugin-redirect": "false"
"data-plugin-redirect": "false",
"data-plugin-type": "core",
"data-plugin-name": "UI"
},
"disabled": false,
"popovers": []
@ -575,7 +703,9 @@
"attrs": {
"data-plugin-id": "whitelist",
"data-plugin-delete": "false",
"data-plugin-redirect": "true"
"data-plugin-redirect": "true",
"data-plugin-type": "core",
"data-plugin-name": "Whitelist"
},
"disabled": false,
"popovers": [