start raw format on both sides

This commit is contained in:
Jordan Blasenhauer 2024-06-12 19:11:02 +02:00
parent db9bbeb99c
commit bf10816c4a
14 changed files with 284 additions and 145 deletions

View file

@ -291,7 +291,7 @@ body {
}
.input-editor {
@apply z-10 min-h-[200px] w-full;
@apply z-10 min-h-[200px] h-full w-full;
}
.disabled.input-editor {
@ -307,7 +307,7 @@ body {
}
.editor.input-clipboard-container {
@apply top-2 right-4;
@apply top-2 right-6;
}
.pw-input.input-clipboard-container {
@ -913,9 +913,10 @@ body {
/* CONTENT COMPONENT */
.content-text {
@apply leading-normal text-base mb-0 lowercase;
.text-content {
@apply leading-normal mb-0 lowercase;
}
/* CONTENT DETAIL LSIT COMPONENT */
.content-detail-list-container {
@ -935,7 +936,7 @@ body {
}
.content-stat {
@apply my-1 font-bold dark:text-white/90 uppercase;
@apply my-1 font-bold dark:text-white/90 text-black uppercase;
}
/* TITLE */
@ -1383,7 +1384,8 @@ body {
.success.subtitle-card,
.success.title-card,
.success.title-container,
.success.title-stat {
.success.title-stat,
.success.text-content {
@apply text-green-500;
}
@ -1391,7 +1393,8 @@ body {
.error.subtitle-card,
.error.title-card,
.error.title-container,
.error.title-stat {
.error.title-stat,
.error.text-content {
@apply text-red-500;
}
@ -1399,7 +1402,8 @@ body {
.warning.subtitle-card,
.warning.title-card,
.warning.title-container,
.warning.title-stat {
.warning.title-stat,
.warning.text-content {
@apply text-yellow-500;
}
@ -1407,7 +1411,8 @@ body {
.info.subtitle-card,
.info.title-card,
.info.title-container,
.info.title-stat {
.info.title-stat,
.info.text-content {
@apply text-sky-500;
}
@ -1415,7 +1420,8 @@ body {
.purple.subtitle-card,
.purple.title-card,
.purple.title-container,
.purple.title-stat {
.purple.title-stat,
.purple.text-content {
@apply text-purple-500;
}
@ -1423,7 +1429,8 @@ body {
.green.subtitle-card,
.green.title-card,
.green.title-container,
.green.title-stat {
.green.title-stat,
.green.text-content {
@apply text-green-500;
}
@ -1431,7 +1438,8 @@ body {
.red.subtitle-card,
.red.title-card,
.red.title-container,
.red.title-stat {
.red.title-stat,
.red.text-content {
@apply text-red-500;
}
@ -1439,7 +1447,8 @@ body {
.orange.subtitle-card,
.orange.title-card,
.orange.title-container,
.orange.title-stat {
.orange.title-stat,
.orange.text-content {
@apply text-orange-500;
}
@ -1447,7 +1456,8 @@ body {
.blue.subtitle-card,
.blue.title-card,
.blue.title-container,
.blue.title-stat {
.blue.title-stat,
.blue.text-content {
@apply text-blue-500;
}
@ -1455,7 +1465,8 @@ body {
.yellow.subtitle-card,
.yellow.title-card,
.yellow.title-container,
.yellow.title-stat {
.yellow.title-stat,
.yellow.text-content {
@apply text-yellow-500;
}
@ -1463,7 +1474,8 @@ body {
.gray.subtitle-card,
.gray.title-card,
.gray.title-container,
.gray.title-stat {
.gray.title-stat,
.gray.text-content {
@apply text-gray-500;
}
@ -1471,7 +1483,8 @@ body {
.dark.subtitle-card,
.dark.title-card,
.dark.title-container,
.dark.title-stat {
.dark.title-stat,
.dark.text-content {
@apply text-slate-500;
}
@ -1479,7 +1492,8 @@ body {
.amber.subtitle-card,
.amber.title-card,
.amber.title-container,
.amber.title-stat {
.amber.title-stat,
.amber.text-content {
@apply text-amber-500;
}
@ -1487,7 +1501,8 @@ body {
.emerald.subtitle-card,
.emerald.title-card,
.emerald.title-container,
.emerald.title-stat {
.emerald.title-stat,
.emerald.text-content {
@apply text-emerald-500;
}
@ -1495,7 +1510,8 @@ body {
.teal.subtitle-card,
.teal.title-card,
.teal.title-container,
.teal.title-stat {
.teal.title-stat,
.teal.text-content {
@apply text-teal-500;
}
@ -1503,7 +1519,8 @@ body {
.indigo.subtitle-card,
.indigo.title-card,
.indigo.title-container,
.indigo.title-stat {
.indigo.title-stat,
.indigo.text-content {
@apply text-indigo-500;
}
@ -1511,7 +1528,8 @@ body {
.cyan.subtitle-card,
.cyan.title-card,
.cyan.title-container,
.cyan.title-stat {
.cyan.title-stat,
.cyan.text-content {
@apply text-cyan-500;
}
@ -1519,7 +1537,8 @@ body {
.sky.subtitle-card,
.sky.title-card,
.sky.title-container,
.sky.title-stat {
.sky.title-stat,
.sky.text-content {
@apply text-sky-500;
}
@ -1527,7 +1546,8 @@ body {
.pink.subtitle-card,
.pink.title-card,
.pink.title-container,
.pink.title-stat {
.pink.title-stat,
.pink.text-content {
@apply text-pink-500;
}
@ -1535,7 +1555,8 @@ body {
.lime.subtitle-card,
.lime.title-card,
.lime.title-container,
.lime.title-stat {
.lime.title-stat,
.lime.text-content {
@apply text-lime-500;
}
@ -1543,7 +1564,8 @@ body {
.purple.subtitle-card,
.purple.title-card,
.purple.title-container,
.purple.title-stat {
.purple.title-stat,
.purple.text-content {
@apply text-purple-600;
}
@ -1551,7 +1573,8 @@ body {
.green-dark.subtitle-card,
.green-dark.title-card,
.green-dark.title-container,
.green-dark.title-stat {
.green-dark.title-stat,
.green-darker.text-content {
@apply text-green-700;
}
@ -1559,7 +1582,8 @@ body {
.red-darker.subtitle-card,
.red-darker.title-card,
.red-darker.title-container,
.red-darker.title-stat {
.red-darker.title-stat,
.red-darker.text-content {
@apply text-red-700;
}
@ -1567,7 +1591,8 @@ body {
.orange-darker.subtitle-card,
.orange-darker.title-card,
.orange-darker.title-container,
.orange-darker.title-stat {
.orange-darker.title-stat,
.orange-darker.text-content {
@apply text-orange-600;
}
@ -1575,7 +1600,8 @@ body {
.blue-darker.subtitle-card,
.blue-darker.title-card,
.blue-darker.title-container,
.blue-darker.title-stat {
.blue-darker.title-stat,
.blue-darker.text-content {
@apply text-blue-600;
}
@ -1583,7 +1609,8 @@ body {
.yellow-darker.subtitle-card,
.yellow-darker.title-card,
.yellow-darker.title-container,
.yellow-darker.title-stat {
.yellow-darker.title-stat,
.yellow-darker.text-content {
@apply text-yellow-600;
}
@ -1591,7 +1618,8 @@ body {
.gray-darker.subtitle-card,
.gray-darker.title-card,
.gray-darker.title-container,
.gray-darker.title-stat {
.gray-darker.title-stat,
.gray-darker.text-content {
@apply text-gray-600;
}
@ -1599,7 +1627,8 @@ body {
.dark-darker.subtitle-card,
.dark-darker.title-card,
.dark-darker.title-container,
.dark-darker.title-stat {
.dark-darker.title-stat,
.dark-darker.text-content {
@apply text-slate-600;
}
@ -1607,7 +1636,8 @@ body {
.amber-darker.subtitle-card,
.amber-darker.title-card,
.amber-darker.title-container,
.amber-darker.title-stat {
.amber-darker.title-stat,
.amber-darker.text-content {
@apply text-amber-600;
}
@ -1615,7 +1645,8 @@ body {
.emerald-darker.subtitle-card,
.emerald-darker.title-card,
.emerald-darker.title-container,
.emerald-darker.title-stat {
.emerald-darker.title-stat,
.emerald-darker.text-content {
@apply text-emerald-600;
}
@ -1623,7 +1654,8 @@ body {
.teal-darker.subtitle-card,
.teal-darker.title-card,
.teal-darker.title-container,
.teal-darker.title-stat {
.teal-darker.title-stat,
.teal-darker.text-content {
@apply text-teal-600;
}
@ -1631,7 +1663,8 @@ body {
.indigo-darker.subtitle-card,
.indigo-darker.title-card,
.indigo-darker.title-container,
.indigo-darker.title-stat {
.indigo-darker.title-stat,
.indigo-darker.text-content {
@apply text-indigo-600;
}
@ -1639,7 +1672,8 @@ body {
.cyan-darker.subtitle-card,
.cyan-darker.title-card,
.cyan-darker.title-container,
.cyan-darker.title-stat {
.cyan-darker.title-stat,
.cyan-darker.text-content {
@apply text-cyan-600;
}
@ -1647,7 +1681,8 @@ body {
.sky-darker.subtitle-card,
.sky-darker.title-card,
.sky-darker.title-container,
.sky-darker.title-stat {
.sky-darker.title-stat,
.sky-darker.text-content {
@apply text-sky-700;
}
@ -1655,7 +1690,8 @@ body {
.pink-darker.subtitle-card,
.pink-darker.title-card,
.pink-darker.title-container,
.pink-darker.title-stat {
.pink-darker.title-stat,
.pink-darker.text-content {
@apply text-pink-600;
}
@ -1663,7 +1699,8 @@ body {
.lime-darker.subtitle-card,
.lime-darker.title-card,
.lime-darker.title-container,
.lime-darker.title-stat {
.lime-darker.title-stat,
.lime-darker.text-content {
@apply text-lime-600;
}

File diff suppressed because one or more lines are too long

View file

@ -11,6 +11,7 @@
}
@param {string} stat - The stat value. Can be a translation key or by default raw text.
@param {string} [statClass=""] - Additional class, useful when component is used directly on a grid system
@param {string} [tag="p"] - The tag of the stat. Can be p, span, div, h1, h2, h3, h4, h5, h6
*/
const props = defineProps({
@ -23,11 +24,16 @@ const props = defineProps({
required: false,
default: "",
},
tag: {
type: String,
required: false,
default: "p",
},
});
</script>
<template>
<h5 :class="['content-stat', props.statClass]">
<component :is="props.tag" :class="['content-stat', props.statClass]">
{{ $t(props.stat, props.stat) }}
</h5>
</component>
</template>

View file

@ -5,36 +5,20 @@ import Title from "@components/Widget/Title.vue";
import Subtitle from "@components/Widget/Subtitle.vue";
import Editor from "@components/Forms/Field/Editor.vue";
import Button from "@components/Widget/Button.vue";
import Text from "@components/Widget/Text.vue";
import { v4 as uuidv4 } from "uuid";
/**
@name Form/Advanced.vue
@description This component is used to create a complete advanced form with plugin selection.
@name Form/RAW.vue
@description This component is used to create a complete raw form with settings as JSON format.
@example
template: [
{
columns: { pc: 6, tablet: 12, mobile: 12 },
id: "test-check",
value: "yes",
label: "Checkbox",
name: "checkbox",
required: true,
hideLabel: false,
headerClass: "text-red-500",
inpType: "checkbox",
},
{
id: "test-input",
value: "yes",
type: "text",
name: "test-input",
disabled: false,
required: true,
label: "Test input",
pattern: "(test)",
inpType: "input",
},
],
{
"IS_LOADING": "no",
"NGINX_PREFIX": "/etc/nginx/",
"HTTP_PORT": "8080",
"HTTPS_PORT": "8443",
"MULTISITE": "yes"
}
@param {object} template - Template object with plugin and settings data.
@param {string} containerClass - Container
@param {object} columns - Columns object.
@ -60,28 +44,81 @@ const props = defineProps({
});
const data = reactive({
format: computed(() => {
const dataStr = JSON.stringify(props.template);
// Add a new line after the comma
return dataStr.replace(/,/g, ",\n");
str: JSON.stringify(props.template),
// Data on creation of editor
entry: computed(() => {
// TODO : WORK WITH LINE LOOP
// ONLY REPLACE FIRST ':' IS REPLACED (IN CASE VALUE CONTAIN ':')
let dataStr = data.str;
// Remove first and last curly brackets
dataStr = dataStr.slice(1, -1);
// Remove all '\"' from stringified JSON
dataStr = dataStr.replace(/\\"/g, '"');
// Remove all newlines
dataStr = dataStr.replace(/\n/g, "");
// Add new line only at the end of each key value
dataStr = dataStr.replace(/",/g, "\n");
// Replace ":" with "=" when between quotes ""
dataStr = dataStr.replace(/":"/g, "=");
// Remove quotes before a new line
dataStr = dataStr.replace(/\n"/g, "\n");
// Remove first char if it is a quote
dataStr = dataStr[0] === '"' ? dataStr.slice(1) : dataStr;
// Remove last char if it is a quote
dataStr = dataStr.slice(-1) === '"' ? dataStr.slice(0, -1) : dataStr;
return dataStr;
}),
// Data retrieve from editor after creation
inp: "",
isValid: computed(() => {
// Transform to a possible valid JSON
let dataToCheck = data.inp || data.entry;
// Replace quotes "" with quotes ''
dataToCheck = dataToCheck.replace(/"/g, "'");
// loop on each line
dataToCheck = dataToCheck.split("\n");
let jsonReady = "";
dataToCheck = dataToCheck.map((line) => {
// Get index of the first equal sign
const index = line.indexOf("=");
// Update at this index with a colon
jsonReady +=
'"' + line.slice(0, index) + '":"' + line.slice(index + 1) + '",';
});
jsonReady = "{" + jsonReady.slice(0, -1) + "}";
try {
JSON.parse(jsonReady);
return true;
} catch (e) {
console.log(e);
return false;
}
}),
});
const editorData = {
value: data.format,
name: "test-editor",
label: "Test editor",
value: data.inp || data.entry,
name: uuidv4(),
label: uuidv4(),
hideLabel: true,
columns: { pc: 12, tablet: 12, mobile: 12 },
editorClass: "min-h-96",
};
const buttonSave = {
id: uuidv4(),
text: "action_save",
disabled: false,
color: "success",
size: "normal",
type: "submit",
containerClass: "flex justify-center",
attrs: data.isValid
? {
"data-submit-form":
data.inp.replace(/\n/g, "") || data.entry.replace(/\n/g, ""),
}
: {},
};
</script>
@ -92,11 +129,17 @@ const buttonSave = {
:containerClass="`col-span-12 w-full m-1 p-1`"
:columns="props.columns"
>
{{ data.isValid ? "Valid" : "Not Valid" }}
<Title type="card" :title="'dashboard_raw_mode'" />
<Subtitle type="card" :subtitle="'dashboard_raw_mode_subtitle'" />
<Container class="col-span-12 w-full">
<Editor v-bind="editorData" />
<Editor @inp="(v) => (data.inp = v)" v-bind="editorData" />
</Container>
<Button v-bind="buttonSave" />
<Button :disabled="data.isValid ? false : true" v-bind="buttonSave" />
<Text
v-if="!data.isValid"
:text="'dashboard_raw_invalid'"
:textClass="'text-center error'"
/>
</Container>
</template>

View file

@ -276,6 +276,8 @@ onMounted(() => {
editorEl.readOnlyBool(props.disabled);
editorEl.editor.on("change", () => {
editor.value = editorEl.getValue();
// emit inp
emits("inp", editor.value);
});
// Add tabindex to editor
try {
@ -321,7 +323,11 @@ onUnmounted(() => {
class="input-editor-error"
></div>
<div
:class="['input-editor', props.disabled ? 'disabled' : 'enabled']"
:class="[
'input-editor',
props.disabled ? 'disabled' : 'enabled',
props.editorClass,
]"
:aria-description="$t('inp_editor_desc')"
:id="props.id"
></div>

View file

@ -115,6 +115,12 @@ onMounted(() => {
function setAttrs() {
for (const [key, value] of Object.entries(props.attrs)) {
// stringify if object
if (typeof value === "object") {
btnEl.value.setAttribute(key, JSON.stringify(value));
continue;
}
btnEl.value.setAttribute(key, value);
}
}

View file

@ -30,7 +30,6 @@ import { v4 as uuidv4 } from "uuid";
size: "normal",
iconName: "modal",
iconColor: "white",
eventAttr: {"store" : "modal", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"},
},
]
}

View file

@ -74,7 +74,7 @@ const props = defineProps({
props.iconName ? 'is-icon' : 'no-icon',
]"
>
<Title type="stat" :title="props.title" />
<Title :tag="'h3'" type="stat" :title="props.title" />
<ContentStat :stat="props.stat" />
<Subtitle
type="stat"

View file

@ -1,31 +1,37 @@
<script setup>
/**
@name Content/Text.vue
@description This component is used for regular paragraph.
@example
{
text: "This is a paragraph",
textClass: "text-3xl"
}
@param {string} text - The text value. Can be a translation key or by default raw text.
@param {string} [textClass=""] - Additional class if needef
*/
const props = defineProps({
text: {
type: [String, Number],
required: true,
},
textClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<h5 :class="['content-text', props.textClass]">
{{ $t(props.text, props.text) }}
</h5>
</template>
<script setup>
/**
@name Widget/Text.vue
@description This component is used for regular paragraph.
@example
{
text: "This is a paragraph",
textClass: "text-3xl"
}
@param {string} text - The text value. Can be a translation key or by default raw text.
@param {string} [textClass=""] - Additional class if needed
@param {string} [tag="p"] - The tag of the text. Can be p, span, div, h1, h2, h3, h4, h5, h6
*/
const props = defineProps({
text: {
type: [String, Number],
required: true,
},
textClass: {
type: String,
required: false,
default: "",
},
tag: {
type: String,
required: false,
default: "p",
},
});
</script>
<template>
<component :is="props.tag" :class="['text-content', props.textClass]">
{{ $t(props.text, props.text) }}
</component>
</template>

View file

@ -84,6 +84,7 @@
"dashboard_status_info": "status loading or waiting or unknown.",
"dashboard_raw_mode": "raw mode",
"dashboard_raw_mode_subtitle": "Raw mode shows settings as raw key-value pairs of settings.",
"dashboard_raw_invalid": "Invalid raw format. Impossible to save.",
"dashboard_advanced_mode": "Advanced mode",
"dashboard_advanced_mode_subtitle": "Advanced mode show settings by plugin in dedicated fields.",
"inp_input_valid": "input valid",

View file

@ -14,7 +14,7 @@
data-server-flash='[{"type" : "success", "title" : "title", "message" : "Success feedback"}, {"type" : "error", "title" : "title", "message" : "Error feedback"}, {"type" : "warning", "title" : "title", "message" : "Warning feedback"}, {"type" : "info", "title" : "title", "message" : "Info feedback"}]'>
</div>
<div class="hidden"
data-server-builder='[{"type":"card","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"bunkerweb"},{"key":"instances_type","value":"manual"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"bunkerweb","buttons":[{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"reload","data-submit-form":"true"},"text":"action_reload","color":"warning","size":"normal"},{"attrs":{"data-form-INSTANCE_ID":"bunkerweb","data-form-operation":"stop","data-submit-form":"true"},"text":"action_stop","color":"error","size":"normal"}]}}]}]'>
data-server-builder='[{"type":"card","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"bunkerweb"},{"key":"instances_type","value":"manual"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"bunkerweb","buttons":[{"attrs":{"data-submit-form": {"operation" : "reload", "INSTANCE_ID" : "bunkerweb"} },"text":"action_reload","color":"warning","size":"normal"},{"attrs":{"data-submit-form": { "operation" : "stop", "INSTANCE_ID" : "bunkerweb"} },"text":"action_stop","color":"error","size":"normal"}]}}]}]'>
</div>
<div id="app"></div>
<script type="module" src="instances.js"></script>

View file

@ -7,13 +7,17 @@
/**
@name useForm
@description This function is a composable wrapper that contains all the form utils functions.
This function will for example look for elements with data-submit-form attribute and submit the form with the data attributes.
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.
*/
function useForm() {
window.addEventListener("click", (e) => {
if (!e.target.hasAttribute("data-submit-form")) return;
const data = useGetFormDataAttr(e.target);
useSubmitForm(data);
try {
const data = JSON.parse(e.target.getAttribute("data-submit-form"));
useSubmitForm(data);
} catch (e) {
console.log(e);
}
});
}
@ -50,29 +54,9 @@ function useSubmitForm(data) {
}
// Append the form to the body and submit it
document.querySelector("body").appendChild(form);
console.log(form);
form.submit();
}
/**
@name useGetFormDataAttr
@description Get the form data store on attributes of the element.
Format is data-form-<key>="<value>"
@example document.querySelector("[data-submit-form]")
@param {DOMElement} el - Element to get the data attributes.
*/
function useGetFormDataAttr(el) {
const data = {};
const attributes = el.attributes;
for (let i = 0; i < attributes.length; i++) {
if (attributes[i].name.includes("data-form-")) {
const key = attributes[i].name.replace("data-form-", "");
data[key] = attributes[i].value;
}
}
return data;
}
/**
@name useFilter
@description Filter keys of an items list of objects with filters.

View file

@ -132,6 +132,58 @@ export default {
"sm:place-content-between",
"md:place-content-between",
"lg:place-content-between",
"text-base",
"sm:text-base",
"md:text-base",
"lg:text-base",
"text-sm",
"sm:text-sm",
"md:text-sm",
"lg:text-sm",
"text-lg",
"sm:text-lg",
"md:text-lg",
"lg:text-lg",
"text-xl",
"sm:text-xl",
"md:text-xl",
"lg:text-xl",
"text-2xl",
"sm:text-2xl",
"md:text-2xl",
"lg:text-2xl",
"text-3xl",
"sm:text-3xl",
"md:text-3xl",
"lg:text-3xl",
"text-center",
"sm:text-center",
"md:text-center",
"lg:text-center",
"text-left",
"sm:text-left",
"md:text-left",
"lg:text-left",
"text-right",
"sm:text-right",
"md:text-right",
"lg:text-right",
"min-h-screen",
"sm:min-h-screen",
"md:min-h-screen",
"lg:min-h-screen",
"h-full",
"sm:h-full",
"md:h-full",
"lg:h-full",
"min-h-96",
"sm:min-h-96",
"md:min-h-96",
"lg:min-h-96",
"w-full",
"sm:w-full",
"md:w-full",
"lg:w-full",
],
important: true,
darkMode: "class",

View file

@ -91,14 +91,13 @@ def set_raw(template, plugins_data):
# Update settings with global config data
for plugin in plugins :
for setting, value in plugin.get("settings").items() :
value["value"] = value.get("default")
# check if setting is in template
# avoid some methods
if value.get("method", "default") not in ("ui", "default", "manual") :
continue
# check if setting is in template (not a default value)
if setting in settings :
# Update value or set default as value
value["value"] = settings[setting].get("value", value.get("default"))
raw_settings[setting] = value.get("value", value.get("default"))
raw_settings[setting] = value.get("value", value.get("default"))
return raw_settings