-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/ui/client/dashboard/pages/bans/Bans.vue b/src/ui/client/dashboard/pages/bans/Bans.vue
index becb3d674..dcafeb337 100644
--- a/src/ui/client/dashboard/pages/bans/Bans.vue
+++ b/src/ui/client/dashboard/pages/bans/Bans.vue
@@ -1,9 +1,7 @@
diff --git a/src/ui/client/dashboard/pages/home/Home.vue b/src/ui/client/dashboard/pages/home/Home.vue
index 4c3c52681..8f3775f4f 100644
--- a/src/ui/client/dashboard/pages/home/Home.vue
+++ b/src/ui/client/dashboard/pages/home/Home.vue
@@ -1,6 +1,5 @@
diff --git a/src/ui/client/dashboard/pages/plugins/Plugins.vue b/src/ui/client/dashboard/pages/plugins/Plugins.vue
index 6d634f2e9..06803adcb 100644
--- a/src/ui/client/dashboard/pages/plugins/Plugins.vue
+++ b/src/ui/client/dashboard/pages/plugins/Plugins.vue
@@ -2,7 +2,6 @@
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";
/**
@@ -98,8 +97,6 @@ onBeforeMount(() => {
});
onMounted(() => {
- useGlobal();
- useForm();
redirectPlugin();
deletePlugin();
});
diff --git a/src/ui/client/dashboard/pages/reports/reports.vue b/src/ui/client/dashboard/pages/reports/reports.vue
index e904be50b..1bb720a5a 100644
--- a/src/ui/client/dashboard/pages/reports/reports.vue
+++ b/src/ui/client/dashboard/pages/reports/reports.vue
@@ -2,8 +2,6 @@
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.js";
-import { useForm } from "@utils/form.js";
/**
@name Page/Reports.vue
@@ -26,11 +24,6 @@ onBeforeMount(() => {
reports.builder = data;
});
-onMounted(() => {
- useGlobal();
- useForm();
-});
-
const builder = [
{
type: "card",
diff --git a/src/ui/client/dashboard/pages/services/index.html b/src/ui/client/dashboard/pages/services/index.html
index 4ebc0688b..8f6933f84 100644
--- a/src/ui/client/dashboard/pages/services/index.html
+++ b/src/ui/client/dashboard/pages/services/index.html
@@ -16,7 +16,7 @@
>
diff --git a/src/ui/client/dashboard/pages/services/services.vue b/src/ui/client/dashboard/pages/services/services.vue
index 601ea1ea7..d363b95a0 100644
--- a/src/ui/client/dashboard/pages/services/services.vue
+++ b/src/ui/client/dashboard/pages/services/services.vue
@@ -2,8 +2,6 @@
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.js";
-import { useForm } from "@utils/form.js";
/**
@name Page/Services.vue
@@ -25,11 +23,6 @@ onBeforeMount(() => {
: {};
services.builder = data;
});
-
-onMounted(() => {
- useGlobal();
- useForm();
-});
diff --git a/src/ui/client/dashboard/utils/form.js b/src/ui/client/dashboard/utils/form.js
index 9ae9c960b..7f12ee2c0 100644
--- a/src/ui/client/dashboard/utils/form.js
+++ b/src/ui/client/dashboard/utils/form.js
@@ -6,20 +6,18 @@
/**
@name useForm
- @description This function is a composable wrapper that contains all the form utils functions.
- This function will for example look for JSON-type in the data-submit-form attribute of an element and submit the form with the data object.
+ @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() {
- window.addEventListener("click", (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);
- }
- });
+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);
+ }
}
/**
diff --git a/src/ui/client/dashboard/utils/global.js b/src/ui/client/dashboard/utils/global.js
index 0b059b9d3..a99dc7cdf 100644
--- a/src/ui/client/dashboard/utils/global.js
+++ b/src/ui/client/dashboard/utils/global.js
@@ -6,167 +6,13 @@ import { v4 as uuidv4 } from "uuid";
This file contains functions related to accessibilities, cookies, and other global utils.
*/
-/**
- @name useGlobal
- @description This function is a wrapper that contains all the global utils functions.
- This function handle global click and keydown events to manage some states like show/hide elements, focus modals, and close modals.
- @returns {void}
-*/
-function useGlobal() {
- setShowHideElA11y();
- window.addEventListener(
- "click",
- (e) => {
- // 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
- );
-}
-
-/**
- @name setShowHideElA11y
- @description This function will check if aria-controls and aria-expanded attributes are present on elements that controls an element visibility.
- Case they are not present, the function will create them.
- @returns {void}
-*/
-function setShowHideElA11y() {
- // Wait that elements are mounted and ids are set
- setTimeout(() => {
- const els = document.querySelectorAll("[data-show-el], [data-hide-el]");
- els.forEach((el) => {
- const id =
- el.getAttribute("data-show-el") || el.getAttribute("data-hide-el");
- // Check current state of the element target
- const targetEl = document.getElementById(id);
- if (!targetEl) return;
- el.setAttribute("aria-controls", id);
- el.setAttribute("aria-expanded", isElHidden(targetEl) ? "false" : "true");
- });
- }, 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
- Close modal
- Modal content
- @param {Event} e - The event object.
- @returns {void}
-*/
-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.
- 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
- Open modal
- Modal content
- @param {Event} e - The event object.
- @returns {void}
-*/
-function useShowEl(e) {
- if (!e.target.hasAttribute("data-show-el")) return;
- // show
- const showElId = e.target.getAttribute("data-show-el");
- document.getElementById(showElId).classList.remove("hidden");
- // Update a11y attributes
- e.target.setAttribute("aria-controls", showElId);
- e.target.setAttribute("aria-expanded", "true");
-}
-
-/**
- @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.
- @returns {void}
-*/
-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.
- @returns {void}
-*/
-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");
-}
-
-/**
- @name isElHidden
- @description This function is a util that checks if an element is hidden.
- This will check for multiple ways to hide an element like aria-hidden, hidden class, display none, visibility hidden, and !hidden class.
- @returns {boolean} - True if the element is hidden, false if not.
-*/
-function isElHidden(el) {
- return el.hasAttribute("aria-hidden")
- ? el.getAttribute("aria-hidden") === "true"
- ? true
- : false
- : el.classList.contains("hidden")
- ? true
- : el.style.display === "none"
- ? true
- : el.style.visibility === "hidden"
- ? true
- : el.classList.contains("!hidden")
- ? true
- : false;
-}
-
/**
@name useUUID
@description This function return a unique identifier using uuidv4 and a random number.
Adding random number to avoid duplicate uuids when some components are rendered at the same time.
We can pass a possible existing id, the function will only generate one if the id is empty.
- @param {String} [id=""] - Possible existing id, check if it's empty to generate a new one.
- @retrurns {String} - The unique identifier.
+ @param {string} [id=""] - Possible existing id, check if it's empty to generate a new one.
+ @returns {string} - The unique identifier.
*/
function useUUID(id = "") {
if (id) return id;
@@ -176,4 +22,21 @@ function useUUID(id = "") {
return uuidv4() + random;
}
-export { useGlobal, useUUID };
+/**
+ @name useEqualStr
+ @description Get the type of a widget and format it to lowercase if possible. Else return an empty string.
+ @param {any} type - Try to convert the type to a string in lowercase to compare it with wanted value.
+ @param {string} compare - The value to compare with, in general the component name.
+ @returns {boolean} - True if matching, false if not.
+*/
+function useEqualStr(type, compare) {
+ // Check if valid string or can be converted to string
+ try {
+ return String(type).toLowerCase() === compare.toLowerCase() ? true : false;
+ } catch (e) {
+ console.log(e);
+ return false;
+ }
+}
+
+export { useUUID, useEqualStr };
diff --git a/src/ui/client/setup/Setup.vue b/src/ui/client/setup/Setup.vue
index 64e20f393..b78f7cb3f 100644
--- a/src/ui/client/setup/Setup.vue
+++ b/src/ui/client/setup/Setup.vue
@@ -1,6 +1,5 @@
diff --git a/src/ui/client/tests/services.json b/src/ui/client/tests/services.json
index 3bb58520b..7d9eee386 100644
--- a/src/ui/client/tests/services.json
+++ b/src/ui/client/tests/services.json
@@ -55,7 +55,34 @@
"color": "info",
"size": "normal",
"iconName": "settings",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_settings_title"
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-app1.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -67,7 +94,74 @@
"color": "success",
"size": "normal",
"iconName": "gear",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_manage_title"
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "manage-service-btn-app1.example.com",
+ "text": "services_easy",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/easy/app1.example.com"
+ }
+ },
+ {
+ "id": "manage-service-btn-app1.example.com",
+ "text": "services_advanced",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/advanced/app1.example.com"
+ }
+ },
+ {
+ "id": "manage-service-btn-app1.example.com",
+ "text": "services_raw",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/raw/app1.example.com"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-app1.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -80,7 +174,57 @@
"color": "cyan",
"size": "normal",
"iconName": "globe",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_edit_title"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "services_edit_subtitle"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "app1.example.com",
+ "bold": true
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-app1.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ },
+ {
+ "id": "edit-service-btn-app1.example.com",
+ "text": "action_edit",
+ "disabled": false,
+ "color": "cyan",
+ "size": "normal",
+ "attrs": {
+ "data-submit-form": "{\"SERVER_NAME\" : app1.example.com, \"OLD_SERVER_NAME\" : app1.example.com, \"operation\" : \"edit\", \"IS_DRAFT\" : no }"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -93,7 +237,57 @@
"color": "red",
"size": "normal",
"iconName": "trash",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_delete_title"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "services_delete_subtitle"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "app1.example.com",
+ "bold": true
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-app1.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ },
+ {
+ "id": "delete-service-btn-app1.example.com",
+ "text": "action_delete",
+ "disabled": false,
+ "color": "delete",
+ "size": "normal",
+ "attrs": {
+ "data-submit-form": "{\"SERVER_NAME\" : app1.example.com, \"operation\" : \"delete\" }"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
}
]
}
@@ -125,7 +319,34 @@
"color": "info",
"size": "normal",
"iconName": "settings",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_settings_title"
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-www.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -137,7 +358,74 @@
"color": "success",
"size": "normal",
"iconName": "gear",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_manage_title"
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "manage-service-btn-www.example.com",
+ "text": "services_easy",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/easy/www.example.com"
+ }
+ },
+ {
+ "id": "manage-service-btn-www.example.com",
+ "text": "services_advanced",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/advanced/www.example.com"
+ }
+ },
+ {
+ "id": "manage-service-btn-www.example.com",
+ "text": "services_raw",
+ "disabled": false,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": "services/raw/www.example.com"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-www.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -150,7 +438,57 @@
"color": "cyan",
"size": "normal",
"iconName": "globe",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_edit_title"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "services_edit_subtitle"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "www.example.com",
+ "bold": true
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-www.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ },
+ {
+ "id": "edit-service-btn-www.example.com",
+ "text": "action_edit",
+ "disabled": false,
+ "color": "cyan",
+ "size": "normal",
+ "attrs": {
+ "data-submit-form": "{\"SERVER_NAME\" : www.example.com, \"OLD_SERVER_NAME\" : www.example.com, \"operation\" : \"edit\", \"IS_DRAFT\" : no }"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
},
{
"attrs": {
@@ -163,7 +501,57 @@
"color": "red",
"size": "normal",
"iconName": "trash",
- "iconColor": "white"
+ "iconColor": "white",
+ "modal": {
+ "widgets": [
+ {
+ "type": "Title",
+ "data": {
+ "title": "services_delete_title"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "services_delete_subtitle"
+ }
+ },
+ {
+ "type": "Text",
+ "data": {
+ "text": "www.example.com",
+ "bold": true
+ }
+ },
+ {
+ "type": "ButtonGroup",
+ "data": {
+ "buttons": [
+ {
+ "id": "close-service-btn-www.example.com",
+ "text": "action_close",
+ "disabled": false,
+ "color": "close",
+ "size": "normal",
+ "attrs": {
+ "data-close-modal": ""
+ }
+ },
+ {
+ "id": "delete-service-btn-www.example.com",
+ "text": "action_delete",
+ "disabled": false,
+ "color": "delete",
+ "size": "normal",
+ "attrs": {
+ "data-submit-form": "{\"SERVER_NAME\" : www.example.com, \"operation\" : \"delete\" }"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
}
]
}
diff --git a/src/ui/client/tests/services.py b/src/ui/client/tests/services.py
index 8574c6784..45651306a 100644
--- a/src/ui/client/tests/services.py
+++ b/src/ui/client/tests/services.py
@@ -64,6 +64,7 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
"disabled": False,
"color": "close",
"size": "normal",
+ "attrs": {"data-close-modal": ""},
},
]
@@ -101,7 +102,6 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
"type": "Title",
"data": {
"title": title,
- "type": "modal",
},
},
]
@@ -128,26 +128,29 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
if operation == "manage":
modes = ("easy", "advanced", "raw")
+ mode_buttons = []
for mode in modes:
- content.append(
+ mode_buttons.append(
{
- "type": "ButtonGroup",
- "data": {
- "buttons": {
- "id": f"{operation}-service-btn-{server_name}",
- "text": f"services_{mode}",
- "disabled": False,
- "color": "green",
- "size": "normal",
- "attrs": {
- "role": "link",
- "data-link": f"services/{mode}/{server_name}",
- },
- },
+ "id": f"{operation}-service-btn-{server_name}",
+ "text": f"services_{mode}",
+ "disabled": False,
+ "color": "green",
+ "size": "normal",
+ "attrs": {
+ "role": "link",
+ "data-link": f"services/{mode}/{server_name}",
},
},
)
+ content.append(
+ {
+ "type": "ButtonGroup",
+ "data": {"buttons": mode_buttons},
+ }
+ )
+
content.append(
{
"type": "ButtonGroup",
@@ -156,8 +159,6 @@ def services_action(server_name: str = "", operation: str = "", title: str = "",
)
modal = {
- "type": "modal",
- "id": f"modal-{operation}-{server_name}",
"widgets": content,
}
@@ -189,6 +190,9 @@ def get_services_list(services):
"size": "normal",
"iconName": "settings",
"iconColor": "white",
+ "modal": services_action(
+ server_name=server_name, operation="settings", title="services_settings_title", subtitle="services_settings_subtitle"
+ ),
},
{
"attrs": {"data-server-name": server_name},
@@ -199,6 +203,9 @@ def get_services_list(services):
"size": "normal",
"iconName": "gear",
"iconColor": "white",
+ "modal": services_action(
+ server_name=server_name, operation="manage", title="services_manage_title", subtitle="services_manage_subtitle"
+ ),
},
{
"attrs": {"data-server-name": server_name, "data-is-draft": "yes" if is_draft else "no"},
@@ -209,6 +216,9 @@ def get_services_list(services):
"size": "normal",
"iconName": "pen" 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
+ ),
},
{
"attrs": {"data-server-name": server_name},
@@ -220,6 +230,9 @@ def get_services_list(services):
"size": "normal",
"iconName": "trash",
"iconColor": "white",
+ "modal": services_action(
+ server_name=server_name, operation="delete", title="services_delete_title", subtitle="services_delete_subtitle"
+ ),
},
]
},
diff --git a/src/ui/client/tests/services.txt b/src/ui/client/tests/services.txt
index ce2db2d29..0aa06ba02 100644
--- a/src/ui/client/tests/services.txt
+++ b/src/ui/client/tests/services.txt
@@ -1 +1 @@
-W3sidHlwZSI6ICJjYXJkIiwgImNvbnRhaW5lckNvbHVtbnMiOiB7InBjIjogMTIsICJ0YWJsZXQiOiAxMiwgIm1vYmlsZSI6IDEyfSwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogInNlcnZpY2VzX3RpdGxlIn19LCB7InR5cGUiOiAiVGFibGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfdGFibGVfdGl0bGUiLCAibWluV2lkdGgiOiAibWQiLCAiaGVhZGVyIjogWyJzZXJ2aWNlc190YWJsZV9uYW1lIiwgInNlcnZpY2VzX3RhYmxlX21ldGhvZCIsICJzZXJ2aWNlc190YWJsZV9hY3Rpb25zIl0sICJwb3NpdGlvbnMiOiBbNCwgNCwgNF0sICJpdGVtcyI6IFtbeyJuYW1lIjogImFwcDEuZXhhbXBsZS5jb20iLCAidHlwZSI6ICJUZXh0IiwgImRhdGEiOiB7InRleHQiOiAiYXBwMS5leGFtcGxlLmNvbSJ9fSwgeyJtZXRob2QiOiAic2NoZWR1bGVyIiwgInR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogInNjaGVkdWxlciJ9fSwgeyJ0eXBlIjogIkJ1dHRvbkdyb3VwIiwgImRhdGEiOiB7ImJ1dHRvbnMiOiBbeyJpZCI6ICJvcGVuLW1vZGFsLXNldHRpbmdzLTAiLCAidGV4dCI6ICJzZXR0aW5ncyIsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJpbmZvIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogInNldHRpbmdzIiwgImljb25Db2xvciI6ICJ3aGl0ZSJ9LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogImFwcDEuZXhhbXBsZS5jb20ifSwgImlkIjogIm9wZW4tbW9kYWwtbWFuYWdlLTAiLCAidGV4dCI6ICJtYW5hZ2UiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAic3VjY2VzcyIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJnZWFyIiwgImljb25Db2xvciI6ICJ3aGl0ZSJ9LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogImFwcDEuZXhhbXBsZS5jb20iLCAiZGF0YS1pcy1kcmFmdCI6ICJubyJ9LCAiaWQiOiAib3Blbi1tb2RhbC1kcmFmdC0wIiwgInRleHQiOiAib25saW5lIiwgImhpZGVUZXh0IjogdHJ1ZSwgImNvbG9yIjogImN5YW4iLCAic2l6ZSI6ICJub3JtYWwiLCAiaWNvbk5hbWUiOiAiZ2xvYmUiLCAiaWNvbkNvbG9yIjogIndoaXRlIn0sIHsiYXR0cnMiOiB7ImRhdGEtc2VydmVyLW5hbWUiOiAiYXBwMS5leGFtcGxlLmNvbSJ9LCAiaWQiOiAib3Blbi1tb2RhbC1kZWxldGUtMCIsICJ0ZXh0IjogImRlbGV0ZSIsICJkaXNhYmxlZCI6IHRydWUsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJyZWQiLCAic2l6ZSI6ICJub3JtYWwiLCAiaWNvbk5hbWUiOiAidHJhc2giLCAiaWNvbkNvbG9yIjogIndoaXRlIn1dfX1dLCBbeyJuYW1lIjogInd3dy5leGFtcGxlLmNvbSIsICJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJ3d3cuZXhhbXBsZS5jb20ifX0sIHsibWV0aG9kIjogInNjaGVkdWxlciIsICJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJzY2hlZHVsZXIifX0sIHsidHlwZSI6ICJCdXR0b25Hcm91cCIsICJkYXRhIjogeyJidXR0b25zIjogW3siaWQiOiAib3Blbi1tb2RhbC1zZXR0aW5ncy0xIiwgInRleHQiOiAic2V0dGluZ3MiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAiaW5mbyIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJzZXR0aW5ncyIsICJpY29uQ29sb3IiOiAid2hpdGUifSwgeyJhdHRycyI6IHsiZGF0YS1zZXJ2ZXItbmFtZSI6ICJ3d3cuZXhhbXBsZS5jb20ifSwgImlkIjogIm9wZW4tbW9kYWwtbWFuYWdlLTEiLCAidGV4dCI6ICJtYW5hZ2UiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAic3VjY2VzcyIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJnZWFyIiwgImljb25Db2xvciI6ICJ3aGl0ZSJ9LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogInd3dy5leGFtcGxlLmNvbSIsICJkYXRhLWlzLWRyYWZ0IjogIm5vIn0sICJpZCI6ICJvcGVuLW1vZGFsLWRyYWZ0LTEiLCAidGV4dCI6ICJvbmxpbmUiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAiY3lhbiIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJnbG9iZSIsICJpY29uQ29sb3IiOiAid2hpdGUifSwgeyJhdHRycyI6IHsiZGF0YS1zZXJ2ZXItbmFtZSI6ICJ3d3cuZXhhbXBsZS5jb20ifSwgImlkIjogIm9wZW4tbW9kYWwtZGVsZXRlLTEiLCAidGV4dCI6ICJkZWxldGUiLCAiZGlzYWJsZWQiOiB0cnVlLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAicmVkIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogInRyYXNoIiwgImljb25Db2xvciI6ICJ3aGl0ZSJ9XX19XV0sICJmaWx0ZXJzIjogW3siZmlsdGVyIjogInRhYmxlIiwgImZpbHRlck5hbWUiOiAia2V5d29yZCIsICJ0eXBlIjogImtleXdvcmQiLCAidmFsdWUiOiAiIiwgImtleXMiOiBbIm5hbWUiXSwgImZpZWxkIjogeyJpZCI6ICJzZXJ2aWNlcy1rZXl3b3JkIiwgInZhbHVlIjogIiIsICJ0eXBlIjogInRleHQiLCAibmFtZSI6ICJzZXJ2aWNlcy1rZXl3b3JkIiwgImxhYmVsIjogInNlcnZpY2VzX3NlYXJjaCIsICJwbGFjZWhvbGRlciI6ICJpbnBfa2V5d29yZCIsICJpc0NsaXBib2FyZCI6IGZhbHNlLCAicG9wb3ZlcnMiOiBbeyJ0ZXh0IjogInNlcnZpY2VzX3NlYXJjaF9kZXNjIiwgImljb25OYW1lIjogImluZm8ifV0sICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9fX0sIHsiZmlsdGVyIjogInRhYmxlIiwgImZpbHRlck5hbWUiOiAibWV0aG9kIiwgInR5cGUiOiAic2VsZWN0IiwgInZhbHVlIjogImFsbCIsICJrZXlzIjogWyJtZXRob2QiXSwgImZpZWxkIjogeyJpZCI6ICJzZXJ2aWNlcy1tZXRob2RzIiwgInZhbHVlIjogImFsbCIsICJ2YWx1ZXMiOiBbInNjaGVkdWxlciJdLCAibmFtZSI6ICJzZXJ2aWNlcy1tZXRob2RzIiwgIm9ubHlEb3duIjogdHJ1ZSwgImxhYmVsIjogInNlcnZpY2VzX21ldGhvZHMiLCAicG9wb3ZlcnMiOiBbeyJ0ZXh0IjogInNlcnZpY2VzX21ldGhvZHNfZGVzYyIsICJpY29uTmFtZSI6ICJpbmZvIn1dLCAiY29sdW1ucyI6IHsicGMiOiAzLCAidGFibGV0IjogNCwgIm1vYmlsZSI6IDEyfX19LCB7ImZpbHRlciI6ICJ0YWJsZSIsICJmaWx0ZXJOYW1lIjogImRyYWZ0IiwgInR5cGUiOiAic2VsZWN0IiwgInZhbHVlIjogImFsbCIsICJrZXlzIjogWyJkcmFmdCJdLCAiZmllbGQiOiB7ImlkIjogInNlcnZpY2VzLWRyYWZ0IiwgInZhbHVlIjogImFsbCIsICJ2YWx1ZXMiOiBbImFsbCIsICJvbmxpbmUiLCAiZHJhZnQiXSwgIm5hbWUiOiAic2VydmljZXMtZHJhZnQiLCAib25seURvd24iOiB0cnVlLCAibGFiZWwiOiAic2VydmljZXNfZHJhZnQiLCAicG9wb3ZlcnMiOiBbeyJ0ZXh0IjogInNlcnZpY2VzX2RyYWZ0X2Rlc2MiLCAiaWNvbk5hbWUiOiAiaW5mbyJ9XSwgImNvbHVtbnMiOiB7InBjIjogMywgInRhYmxldCI6IDQsICJtb2JpbGUiOiAxMn19fV19fV19XQ==
\ No newline at end of file
+W3sidHlwZSI6ICJjYXJkIiwgImNvbnRhaW5lckNvbHVtbnMiOiB7InBjIjogMTIsICJ0YWJsZXQiOiAxMiwgIm1vYmlsZSI6IDEyfSwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogInNlcnZpY2VzX3RpdGxlIn19LCB7InR5cGUiOiAiVGFibGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfdGFibGVfdGl0bGUiLCAibWluV2lkdGgiOiAibWQiLCAiaGVhZGVyIjogWyJzZXJ2aWNlc190YWJsZV9uYW1lIiwgInNlcnZpY2VzX3RhYmxlX21ldGhvZCIsICJzZXJ2aWNlc190YWJsZV9hY3Rpb25zIl0sICJwb3NpdGlvbnMiOiBbNCwgNCwgNF0sICJpdGVtcyI6IFtbeyJuYW1lIjogImFwcDEuZXhhbXBsZS5jb20iLCAidHlwZSI6ICJUZXh0IiwgImRhdGEiOiB7InRleHQiOiAiYXBwMS5leGFtcGxlLmNvbSJ9fSwgeyJtZXRob2QiOiAic2NoZWR1bGVyIiwgInR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogInNjaGVkdWxlciJ9fSwgeyJ0eXBlIjogIkJ1dHRvbkdyb3VwIiwgImRhdGEiOiB7ImJ1dHRvbnMiOiBbeyJpZCI6ICJvcGVuLW1vZGFsLXNldHRpbmdzLTAiLCAidGV4dCI6ICJzZXR0aW5ncyIsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJpbmZvIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogInNldHRpbmdzIiwgImljb25Db2xvciI6ICJ3aGl0ZSIsICJtb2RhbCI6IHsid2lkZ2V0cyI6IFt7InR5cGUiOiAiVGl0bGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfc2V0dGluZ3NfdGl0bGUifX0sIHsidHlwZSI6ICJCdXR0b25Hcm91cCIsICJkYXRhIjogeyJidXR0b25zIjogW3siaWQiOiAiY2xvc2Utc2VydmljZS1idG4tYXBwMS5leGFtcGxlLmNvbSIsICJ0ZXh0IjogImFjdGlvbl9jbG9zZSIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiY2xvc2UiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtY2xvc2UtbW9kYWwiOiAiIn19XX19XX19LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogImFwcDEuZXhhbXBsZS5jb20ifSwgImlkIjogIm9wZW4tbW9kYWwtbWFuYWdlLTAiLCAidGV4dCI6ICJtYW5hZ2UiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAic3VjY2VzcyIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJnZWFyIiwgImljb25Db2xvciI6ICJ3aGl0ZSIsICJtb2RhbCI6IHsid2lkZ2V0cyI6IFt7InR5cGUiOiAiVGl0bGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfbWFuYWdlX3RpdGxlIn19LCB7InR5cGUiOiAiQnV0dG9uR3JvdXAiLCAiZGF0YSI6IHsiYnV0dG9ucyI6IFt7ImlkIjogIm1hbmFnZS1zZXJ2aWNlLWJ0bi1hcHAxLmV4YW1wbGUuY29tIiwgInRleHQiOiAic2VydmljZXNfZWFzeSIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiZ3JlZW4iLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7InJvbGUiOiAibGluayIsICJkYXRhLWxpbmsiOiAic2VydmljZXMvZWFzeS9hcHAxLmV4YW1wbGUuY29tIn19LCB7ImlkIjogIm1hbmFnZS1zZXJ2aWNlLWJ0bi1hcHAxLmV4YW1wbGUuY29tIiwgInRleHQiOiAic2VydmljZXNfYWR2YW5jZWQiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImdyZWVuIiwgInNpemUiOiAibm9ybWFsIiwgImF0dHJzIjogeyJyb2xlIjogImxpbmsiLCAiZGF0YS1saW5rIjogInNlcnZpY2VzL2FkdmFuY2VkL2FwcDEuZXhhbXBsZS5jb20ifX0sIHsiaWQiOiAibWFuYWdlLXNlcnZpY2UtYnRuLWFwcDEuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJzZXJ2aWNlc19yYXciLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImdyZWVuIiwgInNpemUiOiAibm9ybWFsIiwgImF0dHJzIjogeyJyb2xlIjogImxpbmsiLCAiZGF0YS1saW5rIjogInNlcnZpY2VzL3Jhdy9hcHAxLmV4YW1wbGUuY29tIn19XX19LCB7InR5cGUiOiAiQnV0dG9uR3JvdXAiLCAiZGF0YSI6IHsiYnV0dG9ucyI6IFt7ImlkIjogImNsb3NlLXNlcnZpY2UtYnRuLWFwcDEuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJhY3Rpb25fY2xvc2UiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImNsb3NlIiwgInNpemUiOiAibm9ybWFsIiwgImF0dHJzIjogeyJkYXRhLWNsb3NlLW1vZGFsIjogIiJ9fV19fV19fSwgeyJhdHRycyI6IHsiZGF0YS1zZXJ2ZXItbmFtZSI6ICJhcHAxLmV4YW1wbGUuY29tIiwgImRhdGEtaXMtZHJhZnQiOiAibm8ifSwgImlkIjogIm9wZW4tbW9kYWwtZHJhZnQtMCIsICJ0ZXh0IjogIm9ubGluZSIsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJjeWFuIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogImdsb2JlIiwgImljb25Db2xvciI6ICJ3aGl0ZSIsICJtb2RhbCI6IHsid2lkZ2V0cyI6IFt7InR5cGUiOiAiVGl0bGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfZWRpdF90aXRsZSJ9fSwgeyJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJzZXJ2aWNlc19lZGl0X3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogImFwcDEuZXhhbXBsZS5jb20iLCAiYm9sZCI6IHRydWV9fSwgeyJ0eXBlIjogIkJ1dHRvbkdyb3VwIiwgImRhdGEiOiB7ImJ1dHRvbnMiOiBbeyJpZCI6ICJjbG9zZS1zZXJ2aWNlLWJ0bi1hcHAxLmV4YW1wbGUuY29tIiwgInRleHQiOiAiYWN0aW9uX2Nsb3NlIiwgImRpc2FibGVkIjogZmFsc2UsICJjb2xvciI6ICJjbG9zZSIsICJzaXplIjogIm5vcm1hbCIsICJhdHRycyI6IHsiZGF0YS1jbG9zZS1tb2RhbCI6ICIifX0sIHsiaWQiOiAiZWRpdC1zZXJ2aWNlLWJ0bi1hcHAxLmV4YW1wbGUuY29tIiwgInRleHQiOiAiYWN0aW9uX2VkaXQiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImN5YW4iLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtc3VibWl0LWZvcm0iOiAie1wiU0VSVkVSX05BTUVcIiA6IGFwcDEuZXhhbXBsZS5jb20sIFwiT0xEX1NFUlZFUl9OQU1FXCIgOiBhcHAxLmV4YW1wbGUuY29tLCBcIm9wZXJhdGlvblwiIDogXCJlZGl0XCIsIFwiSVNfRFJBRlRcIiA6IG5vIH0ifX1dfX1dfX0sIHsiYXR0cnMiOiB7ImRhdGEtc2VydmVyLW5hbWUiOiAiYXBwMS5leGFtcGxlLmNvbSJ9LCAiaWQiOiAib3Blbi1tb2RhbC1kZWxldGUtMCIsICJ0ZXh0IjogImRlbGV0ZSIsICJkaXNhYmxlZCI6IHRydWUsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJyZWQiLCAic2l6ZSI6ICJub3JtYWwiLCAiaWNvbk5hbWUiOiAidHJhc2giLCAiaWNvbkNvbG9yIjogIndoaXRlIiwgIm1vZGFsIjogeyJ3aWRnZXRzIjogW3sidHlwZSI6ICJUaXRsZSIsICJkYXRhIjogeyJ0aXRsZSI6ICJzZXJ2aWNlc19kZWxldGVfdGl0bGUifX0sIHsidHlwZSI6ICJUZXh0IiwgImRhdGEiOiB7InRleHQiOiAic2VydmljZXNfZGVsZXRlX3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogImFwcDEuZXhhbXBsZS5jb20iLCAiYm9sZCI6IHRydWV9fSwgeyJ0eXBlIjogIkJ1dHRvbkdyb3VwIiwgImRhdGEiOiB7ImJ1dHRvbnMiOiBbeyJpZCI6ICJjbG9zZS1zZXJ2aWNlLWJ0bi1hcHAxLmV4YW1wbGUuY29tIiwgInRleHQiOiAiYWN0aW9uX2Nsb3NlIiwgImRpc2FibGVkIjogZmFsc2UsICJjb2xvciI6ICJjbG9zZSIsICJzaXplIjogIm5vcm1hbCIsICJhdHRycyI6IHsiZGF0YS1jbG9zZS1tb2RhbCI6ICIifX0sIHsiaWQiOiAiZGVsZXRlLXNlcnZpY2UtYnRuLWFwcDEuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJhY3Rpb25fZGVsZXRlIiwgImRpc2FibGVkIjogZmFsc2UsICJjb2xvciI6ICJkZWxldGUiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtc3VibWl0LWZvcm0iOiAie1wiU0VSVkVSX05BTUVcIiA6IGFwcDEuZXhhbXBsZS5jb20sIFwib3BlcmF0aW9uXCIgOiBcImRlbGV0ZVwiIH0ifX1dfX1dfX1dfX1dLCBbeyJuYW1lIjogInd3dy5leGFtcGxlLmNvbSIsICJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJ3d3cuZXhhbXBsZS5jb20ifX0sIHsibWV0aG9kIjogInNjaGVkdWxlciIsICJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJzY2hlZHVsZXIifX0sIHsidHlwZSI6ICJCdXR0b25Hcm91cCIsICJkYXRhIjogeyJidXR0b25zIjogW3siaWQiOiAib3Blbi1tb2RhbC1zZXR0aW5ncy0xIiwgInRleHQiOiAic2V0dGluZ3MiLCAiaGlkZVRleHQiOiB0cnVlLCAiY29sb3IiOiAiaW5mbyIsICJzaXplIjogIm5vcm1hbCIsICJpY29uTmFtZSI6ICJzZXR0aW5ncyIsICJpY29uQ29sb3IiOiAid2hpdGUiLCAibW9kYWwiOiB7IndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogInNlcnZpY2VzX3NldHRpbmdzX3RpdGxlIn19LCB7InR5cGUiOiAiQnV0dG9uR3JvdXAiLCAiZGF0YSI6IHsiYnV0dG9ucyI6IFt7ImlkIjogImNsb3NlLXNlcnZpY2UtYnRuLXd3dy5leGFtcGxlLmNvbSIsICJ0ZXh0IjogImFjdGlvbl9jbG9zZSIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiY2xvc2UiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtY2xvc2UtbW9kYWwiOiAiIn19XX19XX19LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogInd3dy5leGFtcGxlLmNvbSJ9LCAiaWQiOiAib3Blbi1tb2RhbC1tYW5hZ2UtMSIsICJ0ZXh0IjogIm1hbmFnZSIsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJzdWNjZXNzIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogImdlYXIiLCAiaWNvbkNvbG9yIjogIndoaXRlIiwgIm1vZGFsIjogeyJ3aWRnZXRzIjogW3sidHlwZSI6ICJUaXRsZSIsICJkYXRhIjogeyJ0aXRsZSI6ICJzZXJ2aWNlc19tYW5hZ2VfdGl0bGUifX0sIHsidHlwZSI6ICJCdXR0b25Hcm91cCIsICJkYXRhIjogeyJidXR0b25zIjogW3siaWQiOiAibWFuYWdlLXNlcnZpY2UtYnRuLXd3dy5leGFtcGxlLmNvbSIsICJ0ZXh0IjogInNlcnZpY2VzX2Vhc3kiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImdyZWVuIiwgInNpemUiOiAibm9ybWFsIiwgImF0dHJzIjogeyJyb2xlIjogImxpbmsiLCAiZGF0YS1saW5rIjogInNlcnZpY2VzL2Vhc3kvd3d3LmV4YW1wbGUuY29tIn19LCB7ImlkIjogIm1hbmFnZS1zZXJ2aWNlLWJ0bi13d3cuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJzZXJ2aWNlc19hZHZhbmNlZCIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiZ3JlZW4iLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7InJvbGUiOiAibGluayIsICJkYXRhLWxpbmsiOiAic2VydmljZXMvYWR2YW5jZWQvd3d3LmV4YW1wbGUuY29tIn19LCB7ImlkIjogIm1hbmFnZS1zZXJ2aWNlLWJ0bi13d3cuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJzZXJ2aWNlc19yYXciLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImdyZWVuIiwgInNpemUiOiAibm9ybWFsIiwgImF0dHJzIjogeyJyb2xlIjogImxpbmsiLCAiZGF0YS1saW5rIjogInNlcnZpY2VzL3Jhdy93d3cuZXhhbXBsZS5jb20ifX1dfX0sIHsidHlwZSI6ICJCdXR0b25Hcm91cCIsICJkYXRhIjogeyJidXR0b25zIjogW3siaWQiOiAiY2xvc2Utc2VydmljZS1idG4td3d3LmV4YW1wbGUuY29tIiwgInRleHQiOiAiYWN0aW9uX2Nsb3NlIiwgImRpc2FibGVkIjogZmFsc2UsICJjb2xvciI6ICJjbG9zZSIsICJzaXplIjogIm5vcm1hbCIsICJhdHRycyI6IHsiZGF0YS1jbG9zZS1tb2RhbCI6ICIifX1dfX1dfX0sIHsiYXR0cnMiOiB7ImRhdGEtc2VydmVyLW5hbWUiOiAid3d3LmV4YW1wbGUuY29tIiwgImRhdGEtaXMtZHJhZnQiOiAibm8ifSwgImlkIjogIm9wZW4tbW9kYWwtZHJhZnQtMSIsICJ0ZXh0IjogIm9ubGluZSIsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJjeWFuIiwgInNpemUiOiAibm9ybWFsIiwgImljb25OYW1lIjogImdsb2JlIiwgImljb25Db2xvciI6ICJ3aGl0ZSIsICJtb2RhbCI6IHsid2lkZ2V0cyI6IFt7InR5cGUiOiAiVGl0bGUiLCAiZGF0YSI6IHsidGl0bGUiOiAic2VydmljZXNfZWRpdF90aXRsZSJ9fSwgeyJ0eXBlIjogIlRleHQiLCAiZGF0YSI6IHsidGV4dCI6ICJzZXJ2aWNlc19lZGl0X3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogInd3dy5leGFtcGxlLmNvbSIsICJib2xkIjogdHJ1ZX19LCB7InR5cGUiOiAiQnV0dG9uR3JvdXAiLCAiZGF0YSI6IHsiYnV0dG9ucyI6IFt7ImlkIjogImNsb3NlLXNlcnZpY2UtYnRuLXd3dy5leGFtcGxlLmNvbSIsICJ0ZXh0IjogImFjdGlvbl9jbG9zZSIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiY2xvc2UiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtY2xvc2UtbW9kYWwiOiAiIn19LCB7ImlkIjogImVkaXQtc2VydmljZS1idG4td3d3LmV4YW1wbGUuY29tIiwgInRleHQiOiAiYWN0aW9uX2VkaXQiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImNvbG9yIjogImN5YW4iLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtc3VibWl0LWZvcm0iOiAie1wiU0VSVkVSX05BTUVcIiA6IHd3dy5leGFtcGxlLmNvbSwgXCJPTERfU0VSVkVSX05BTUVcIiA6IHd3dy5leGFtcGxlLmNvbSwgXCJvcGVyYXRpb25cIiA6IFwiZWRpdFwiLCBcIklTX0RSQUZUXCIgOiBubyB9In19XX19XX19LCB7ImF0dHJzIjogeyJkYXRhLXNlcnZlci1uYW1lIjogInd3dy5leGFtcGxlLmNvbSJ9LCAiaWQiOiAib3Blbi1tb2RhbC1kZWxldGUtMSIsICJ0ZXh0IjogImRlbGV0ZSIsICJkaXNhYmxlZCI6IHRydWUsICJoaWRlVGV4dCI6IHRydWUsICJjb2xvciI6ICJyZWQiLCAic2l6ZSI6ICJub3JtYWwiLCAiaWNvbk5hbWUiOiAidHJhc2giLCAiaWNvbkNvbG9yIjogIndoaXRlIiwgIm1vZGFsIjogeyJ3aWRnZXRzIjogW3sidHlwZSI6ICJUaXRsZSIsICJkYXRhIjogeyJ0aXRsZSI6ICJzZXJ2aWNlc19kZWxldGVfdGl0bGUifX0sIHsidHlwZSI6ICJUZXh0IiwgImRhdGEiOiB7InRleHQiOiAic2VydmljZXNfZGVsZXRlX3N1YnRpdGxlIn19LCB7InR5cGUiOiAiVGV4dCIsICJkYXRhIjogeyJ0ZXh0IjogInd3dy5leGFtcGxlLmNvbSIsICJib2xkIjogdHJ1ZX19LCB7InR5cGUiOiAiQnV0dG9uR3JvdXAiLCAiZGF0YSI6IHsiYnV0dG9ucyI6IFt7ImlkIjogImNsb3NlLXNlcnZpY2UtYnRuLXd3dy5leGFtcGxlLmNvbSIsICJ0ZXh0IjogImFjdGlvbl9jbG9zZSIsICJkaXNhYmxlZCI6IGZhbHNlLCAiY29sb3IiOiAiY2xvc2UiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtY2xvc2UtbW9kYWwiOiAiIn19LCB7ImlkIjogImRlbGV0ZS1zZXJ2aWNlLWJ0bi13d3cuZXhhbXBsZS5jb20iLCAidGV4dCI6ICJhY3Rpb25fZGVsZXRlIiwgImRpc2FibGVkIjogZmFsc2UsICJjb2xvciI6ICJkZWxldGUiLCAic2l6ZSI6ICJub3JtYWwiLCAiYXR0cnMiOiB7ImRhdGEtc3VibWl0LWZvcm0iOiAie1wiU0VSVkVSX05BTUVcIiA6IHd3dy5leGFtcGxlLmNvbSwgXCJvcGVyYXRpb25cIiA6IFwiZGVsZXRlXCIgfSJ9fV19fV19fV19fV1dLCAiZmlsdGVycyI6IFt7ImZpbHRlciI6ICJ0YWJsZSIsICJmaWx0ZXJOYW1lIjogImtleXdvcmQiLCAidHlwZSI6ICJrZXl3b3JkIiwgInZhbHVlIjogIiIsICJrZXlzIjogWyJuYW1lIl0sICJmaWVsZCI6IHsiaWQiOiAic2VydmljZXMta2V5d29yZCIsICJ2YWx1ZSI6ICIiLCAidHlwZSI6ICJ0ZXh0IiwgIm5hbWUiOiAic2VydmljZXMta2V5d29yZCIsICJsYWJlbCI6ICJzZXJ2aWNlc19zZWFyY2giLCAicGxhY2Vob2xkZXIiOiAiaW5wX2tleXdvcmQiLCAiaXNDbGlwYm9hcmQiOiBmYWxzZSwgInBvcG92ZXJzIjogW3sidGV4dCI6ICJzZXJ2aWNlc19zZWFyY2hfZGVzYyIsICJpY29uTmFtZSI6ICJpbmZvIn1dLCAiY29sdW1ucyI6IHsicGMiOiAzLCAidGFibGV0IjogNCwgIm1vYmlsZSI6IDEyfX19LCB7ImZpbHRlciI6ICJ0YWJsZSIsICJmaWx0ZXJOYW1lIjogIm1ldGhvZCIsICJ0eXBlIjogInNlbGVjdCIsICJ2YWx1ZSI6ICJhbGwiLCAia2V5cyI6IFsibWV0aG9kIl0sICJmaWVsZCI6IHsiaWQiOiAic2VydmljZXMtbWV0aG9kcyIsICJ2YWx1ZSI6ICJhbGwiLCAidmFsdWVzIjogWyJzY2hlZHVsZXIiXSwgIm5hbWUiOiAic2VydmljZXMtbWV0aG9kcyIsICJvbmx5RG93biI6IHRydWUsICJsYWJlbCI6ICJzZXJ2aWNlc19tZXRob2RzIiwgInBvcG92ZXJzIjogW3sidGV4dCI6ICJzZXJ2aWNlc19tZXRob2RzX2Rlc2MiLCAiaWNvbk5hbWUiOiAiaW5mbyJ9XSwgImNvbHVtbnMiOiB7InBjIjogMywgInRhYmxldCI6IDQsICJtb2JpbGUiOiAxMn19fSwgeyJmaWx0ZXIiOiAidGFibGUiLCAiZmlsdGVyTmFtZSI6ICJkcmFmdCIsICJ0eXBlIjogInNlbGVjdCIsICJ2YWx1ZSI6ICJhbGwiLCAia2V5cyI6IFsiZHJhZnQiXSwgImZpZWxkIjogeyJpZCI6ICJzZXJ2aWNlcy1kcmFmdCIsICJ2YWx1ZSI6ICJhbGwiLCAidmFsdWVzIjogWyJhbGwiLCAib25saW5lIiwgImRyYWZ0Il0sICJuYW1lIjogInNlcnZpY2VzLWRyYWZ0IiwgIm9ubHlEb3duIjogdHJ1ZSwgImxhYmVsIjogInNlcnZpY2VzX2RyYWZ0IiwgInBvcG92ZXJzIjogW3sidGV4dCI6ICJzZXJ2aWNlc19kcmFmdF9kZXNjIiwgImljb25OYW1lIjogImluZm8ifV0sICJjb2x1bW5zIjogeyJwYyI6IDMsICJ0YWJsZXQiOiA0LCAibW9iaWxlIjogMTJ9fX1dfX1dfV0=
\ No newline at end of file