update POC

* enhance and create some components
* removed useless files
* start pinia store logic
This commit is contained in:
Jordan Blasenhauer 2024-05-17 14:23:17 +02:00
parent fca7cc5d02
commit f352f7ac51
21 changed files with 406 additions and 383 deletions

View file

@ -6,22 +6,29 @@ import Input from "@components/Forms/Field/Input.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import Grid from "@components/Widget/Grid.vue";
const props = defineProps({
builder : {
type: Array,
required: true,
},
})
// Example of builder
const builder = [{
// we are starting with the top level container name
// this can be a "card", "modal", "table"... etc
"type": "card",
/*
COMPONENT DESCRIPTION
*
*
This Builder component is used to create complete pages content with multiple components.
This is an abstract component that will be used to create any kind of page content.
You need to determine each container and each widget inside it.
*
*
PROPS ARGUMENTS
*
*
builder : array,
*
*
PROPS EXAMPLE
*
*
[{
"type": "card", // this can be a "card", "modal", "table"... etc
"containerClass": "", // tailwind css grid class (items-start, ...)
"containerColumns" : {"pc": 12, "tablet": 12, "mobile": 12},
// container title
"title" : "My awesome card",
"title" : "My awesome card", // container title
// Each widget need a name (here type) and associated data
// We need to send specific data for each widget type
widgets: [
@ -33,8 +40,18 @@ const builder = [{
data : {containerClass : "", columns : {"pc": 6, "tablet": 12, "mobile": 12}, id: 'test-select', value: 'yes', values: ['yes', 'no'], name: 'test-select', disabled: false, required: true, label: 'Test select', tabId: '1',}
}
]
},
]
*
*
*/
const props = defineProps({
builder : {
type: Array,
required: true,
},
]
})
</script>
<template>

View file

@ -4,10 +4,14 @@ import { contentIndex } from "@utils/tabindex.js";
import Container from "@components/Widget/Container.vue";
import Header from "@components/Forms/Header/Field.vue";
/*
/*
COMPONENT DESCRIPTION
*
*
This checkbox component is used to create a complete checkbox (label, validator message).
It is mainly use for checkbox setting form.
*
*
PROPS ARGUMENTS
*
*
@ -27,6 +31,7 @@ import Header from "@components/Forms/Header/Field.vue";
tabId: string || number,
*
*
PROPS EXAMPLE
*/
const props = defineProps({

View file

@ -6,9 +6,13 @@ import Header from "@components/Forms/Header/Field.vue";
/*
COMPONENT DESCRIPTION
*
*
This input component is used to create a complete input (label, validator message).
It is mainly use for input setting form.
*
*
PROPS ARGUMENTS
*
*
@ -33,6 +37,22 @@ import Header from "@components/Forms/Header/Field.vue";
tabId: string || number,
*
*
PROPS EXAMPLE
*
*
{
id: 'test-input',
value: 'yes',
type: "text",
name: 'test-input',
disabled: false,
required: true,
label: 'Test input',
pattern : "(test)",
tabId: '1',
}
*
*
*/
const props = defineProps({
// id && value && method
@ -107,7 +127,7 @@ const props = defineProps({
},
tabId: {
type: [String, Number],
required: true,
required: false,
},
});

View file

@ -6,9 +6,13 @@ import Header from "@components/Forms/Header/Field.vue";
/*
COMPONENT DESCRIPTION
*
*
This select component is used to create a complete select (label, validator message).
It is mainly use for select setting form.
*
*
PROPS ARGUMENTS
*
*
@ -28,6 +32,20 @@ import Header from "@components/Forms/Header/Field.vue";
tabId: string || number,
*
*
PROPS EXAMPLE
*
*
{
id: 'test-input',
value: 'yes',
values : ['yes', 'no'],
name: 'test-input',
disabled: false,
required: true,
label: 'Test select',
}
*
*
*/

View file

@ -0,0 +1,49 @@
<script setup>
import { computed } from 'vue';
/*
COMPONENT DESCRIPTION
*
*
This svg component is used to create a complete svg icon for a button.
*
*
PROPS ARGUMENTS
*
*
iconColor: string,
*
*
PROPS EXAMPLE
*
*
iconColor: 'white',
*
*
*/
const props = defineProps({
iconColor : {
type: String,
required: false,
default: "white",
}
})
const svgClass = computed(() => {
return `ml-2 w-6 h-6 ${props.iconColor}`
})
</script>
<template>
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
:class="[svgClass]">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</template>

View file

@ -0,0 +1,174 @@
<script setup>
import { computed, ref, watch, onBeforeMount, onMounted } from 'vue';
import Container from "@components/Widget/Container.vue";
import { contentIndex } from "@utils/tabindex.js";
import { useEventStore } from "@store/event.js";
/*
COMPONENT DESCRIPTION
*
*
This button component is a standard button.
We can link this button to a store on click with eventAttr.
Stores allow to share a value with other components, for example switching form on a click.
We need to determine the store name and the value to send on click.
*
*
PROPS ARGUMENTS
*
*
id: string,
text: string,
disabled: boolean,
hideText: boolean,
color: string,
size: string<"sm"|"normal"|"lg"|"xl">,
iconName: string,
iconColor: string,
eventAttr: object,
tabId: string || number,
*
*
PROPS EXAMPLE
*
*
{
id: "open-modal-btn",
text: "Open modal",
disabled: false,
hideText: true,
color: "green",
size: "normal",
iconName: "modal",
iconColor: "white",
eventAttr: {"store" : "modal", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"},7
}
*
*
*/
const eventStore = useEventStore();
const props = defineProps({
id : {
type: String,
required: true,
},
// valid || delete || info
text : {
type: String,
required: true
},
disabled : {
type: Boolean,
required: false,
default : false
},
// case we want only icon but we need to add accessibility data
hideText : {
type: Boolean,
required: false,
default : false
},
color: {
type: String,
required: false,
default : "primary"
},
// sm || normal || lg || xl
size: {
type: String,
required: false,
default : "normal"
},
// Store on components/Icons/Button
// Check import ones
iconName : {
type: String,
required: false,
default : "",
},
// Defined on input.css
iconColor : {
type: String,
required: false,
default : ""
},
// {"store" : <store_name>, "default" : <default_value>, "value" : <value_stored_on_click>, "target"<optional> : <target_id_element>, "valueExpanded" : "expanded_value"}
// type will add additionnal accessibility attributs to the button
// for example, if button open a modal : {"store" : "modal", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"}
eventAttr: {
type: Object,
required: false,
default : {}
},
tabId : {
type: [String, Number],
required: false,
default : ""
}
});
const btnEl = ref();
const buttonClass = computed(() => {
return `btn btn-${props.color} btn-${props.size}`
})
onMounted(() => {
updateData();
})
watch(eventStore,() => {
updateData();
})
function updateData(isClick = false) {
const isStore = props.eventAttr?.store ? true : false;
const isValue = props.eventAttr?.value ? true : false;
const isDefault = props.eventAttr?.default ? true : false;
if(!isStore || !isValue || !isDefault) return;
isClick ? eventStore.updateEvent(props.eventAttr.store, props.eventAttr.value) : eventStore.addEvent(props.eventAttr.store, props.eventAttr.default);
try {
const expanded = props.eventAttr?.valueExpanded ? props.eventAttr.valueExpanded === eventStore.getEvent(props.eventAttr.store) ? 'true' : 'false' : false;
if(expanded) {
btnEl.value.setAttribute('aria-expanded', expanded);
}
if(!expanded) {
btnEl.value.removeAttribute('aria-expanded');
}
}catch(e) {
}
try {
const controls = props.eventAttr?.target ? props.eventAttr.target : false;
if(controls) {
btnEl.value.setAttribute('aria-controls', controls);
}
if(!controls) {
btnEl.value.removeAttribute('aria-controls');
}
}catch(e) {
}
}
</script>
<template>
<Container :containerClass="`w-full m-1 ${props.containerClass}`" :columns="props.columns">
<button ref="btnEl" @click="updateData(true)" :id="props.id"
:tabindex="props.tabId || contentIndex"
:class="[buttonClass]"
:disabled="props.disabled || false"
:aria-describedby="`${props.id}-text`"
>
<span :class="[props.hideText ? 'sr-only' : '']" :id="`${props.id}-text`">{{ props.text }}</span>
</button>
</Container>
</template>

View file

@ -2,10 +2,14 @@
import { computed } from 'vue';
/*
COMPONENT DESCRIPTION
*
*
This container component is mainly use as widget container.
Each widget can define the base class for the container.
In case we columns, this will use it too for positioning.
*
*
PROPS ARGUMENTS
*
*
@ -13,6 +17,15 @@ import { computed } from 'vue';
columns: object,
*
*
PROPS EXAMPLE
*
*
{
class: "w-full h-full bg-white rounded shadow-md",
columns: { pc: 12, tablet: 12, mobile: 12}
}
*
*
*/
const props = defineProps({

View file

@ -1,20 +1,29 @@
<script setup>
import { computed } from 'vue';
/*
/*
COMPONENT DESCRIPTION
*
*
This container component is used to align groups of components horizontally using flex.
*
*
PROPS ARGUMENTS
*
*
class: string,
horizontalAlign: string<"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly">,
flexClass: string,
*
*
PROPS EXAMPLE
*
*
flexClass: "flex-start"
*
*
*/
const props = defineProps({
class: {
flexClass: {
type: String,
required: false,
default : "flex-start"
@ -22,7 +31,7 @@ const props = defineProps({
})
const flexClass = computed(() => {
return `w-full flex ${props.class}`;
return `w-full flex ${props.flexClass}`;
})
</script>

View file

@ -2,9 +2,13 @@
import { computed } from 'vue';
/*
COMPONENT DESCRIPTION
*
*
This Grid component is a container with a grid system.
In case we are adding columns, this will be added, so it can be used with parent grid.
*
*
PROPS ARGUMENTS
*
*
@ -14,6 +18,13 @@ import { computed } from 'vue';
class : <"items-start"|"items-center"|"items-end">
*
*
PROPS EXAMPLE
*
*
columns: { pc: 12, tablet: 12, mobile: 12},
gridClass: "items-start"
*
*
*/
const props = defineProps({

View file

@ -2,18 +2,31 @@
import { computed } from 'vue';
/*
COMPONENT DESCRIPTION
*
*
This GridLayout component is used at the top level of a page layout.
This component will determine the position of layout components based on the grid columns.
We can create card, modal, table and others top level layout using this component.
The content of this component is grid based.
*
*
PROPS ARGUMENTS
*
*
type : <"card"|"table"|...> (will determine component style)
title: string,
columns : { pc: int, tablet: int, mobile: int},
verticalAlign : <"items-start"|"items-center"|"items-end">
gridLayoutClass : <"items-start"|"items-center"|"items-end">
*
*
PROPS EXAMPLE
*
*
type: "card",
title: "Test",
columns: { pc: 12, tablet: 12, mobile: 12},
gridLayoutClass: "items-start"
*
*
*/

View file

@ -3,6 +3,7 @@ import { reactive, onBeforeMount } from "vue";
import Checkbox from "@components/Forms/Field/Checkbox.vue";
import Select from "@components/Forms/Field/Select.vue";
import Input from "@components/Forms/Field/Input.vue";
import Button from "@components/Widget/Button.vue";
import Builder from "@components/Builder.vue";
// Define reactive properties
const data = reactive({
@ -38,11 +39,23 @@ const builder = [{
]
},
]
const buttonData = {
id: 'test-button',
text: 'Test button',
type: 'button',
disabled: false,
eventAttr: {"store" : "modal", "default" : "close", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"},
columns: {"pc": 12, "tablet": 12, "mobile": 12},
tabId: '1',
}
</script>
<template>
<div class="bg-secondary flex flex-col items-center justify-center h-full">
<div style="width: 300px;">
<Button v-bind="buttonData" />
<Builder :builder="builder" />
</div>
</div>

View file

@ -1,28 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useSelectIPStore = defineStore("selectIP", () => {
const data = ref(new Set());
function addIP(ip) {
data.value.add(ip);
}
function deleteIP(ip) {
data.value.delete(ip);
}
function $reset() {
data.value.clear();
}
return { data, $reset, addIP, deleteIP };
});
export const useAddModalStore = defineStore("addBanModal", () => {
const isOpen = ref(false);
return {
isOpen,
};
});

View file

@ -1,31 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useModalStore = defineStore("fileManagerModal", () => {
const isOpen = ref(false);
const data = ref({
type: "folder",
action: "view",
path: "root/",
pathLevel: 1,
value: "",
name: "root",
});
function $reset() {
data.value = {
type: "folder",
action: "view",
path: "root/",
pathLevel: 1,
value: "",
name: "root",
};
}
return {
isOpen,
data,
$reset,
};
});

View file

@ -0,0 +1,34 @@
import { data } from "autoprefixer";
import { defineStore } from "pinia";
import { ref } from "vue";
// This store allow to share data between components related to events
// For example, a click button event can create and store a value in this store to be use in another component
export const useEventStore = defineStore("event", () => {
const event = ref({});
// add only if the event is not already in the store
function addEvent(name, value) {
console.log(event.value)
if (!(name in event.value)) event.value[name] = value;
}
function updateEvent(name, value) {
event.value[name] = value;
}
function getEvent(name) {
return event.value[name];
}
function deleteEvent(name) {
delete event.value[name];
}
function $reset() {
data.value = {};
}
return { event, $reset, addEvent, updateEvent, deleteEvent, getEvent };
});

View file

@ -1,55 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useFeedbackStore = defineStore("feedback", () => {
const data = ref([]);
let feedID = 0;
async function addFeedback(type, status, message) {
feedID++;
data.value.push({
id: feedID,
isNew: true,
type: type,
status: status,
message: message,
});
}
function removeFeedback(id) {
data.value.splice(
data.value.findIndex((item) => item["id"] === id),
1,
);
}
return { data, addFeedback, removeFeedback };
});
export const useRefreshStore = defineStore("refresh", () => {
const count = ref(0);
async function refresh() {
count.value++;
}
return { count, refresh };
});
export const useBannerStore = defineStore("banner", () => {
const isBanner = ref(true);
const bannerClass = ref("banner");
async function setBannerVisible(bool) {
isBanner.value = bool;
bannerClass.value = bool ? "banner" : "no-banner";
}
return { isBanner, bannerClass, setBannerVisible };
});
export const useBackdropStore = defineStore("backdrop", () => {
const clickCount = ref(0);
return { clickCount };
});

View file

@ -1,54 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useDelModalStore = defineStore("delInstanceModal", () => {
const isOpen = ref(false);
const data = ref({
hostname: "",
});
function $reset() {
data.value = {
hostname: "",
};
}
return {
isOpen,
data,
$reset,
};
});
export const useAddModalStore = defineStore("addInstanceModal", () => {
const isOpen = ref(false);
return {
isOpen,
};
});
export const useEditModalStore = defineStore("editInstanceModal", () => {
const isOpen = ref(false);
const data = ref({
hostname: "",
old_hostname: "",
server_name: "",
port: "",
});
function $reset() {
data.value = {
hostname: "",
old_hostname: "",
server_name: "",
port: "",
};
}
return {
isOpen,
data,
$reset,
};
});

View file

@ -1,23 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useModalStore = defineStore("jobsModal", () => {
const isOpen = ref(false);
const data = ref({
name: "",
history: [],
});
function $reset() {
data.value = {
name: "",
history: [],
};
}
return {
isOpen,
data,
$reset,
};
});

View file

@ -1,16 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useLogsStore = defineStore("logs", () => {
const tags = ref([]);
function setTags(arr) {
tags.value = arr;
}
function $reset() {
tags.value = [];
}
return { tags, $reset, setTags };
});

View file

@ -1,25 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useDelModalStore = defineStore("delPluginModal", () => {
const isOpen = ref(false);
const data = ref({
id: "",
name: "",
description: "",
});
function $reset() {
data.value = {
id: "",
name: "",
description: "",
};
}
return {
isOpen,
data,
$reset,
};
});

View file

@ -1,61 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useModalStore = defineStore("ServiceModal", () => {
const isOpen = ref(false);
const data = ref({
services: [],
service: "",
serviceName: "",
operation: "",
servicesNames: [],
method: "",
isDraft: false,
});
function $reset() {
data.value = {
services: [],
service: "",
serviceName: "",
operation: "",
servicesNames: [],
method: "",
};
}
return {
isOpen,
data,
$reset,
};
});
export const useDelModalStore = defineStore("ServiceDelModal", () => {
const isOpen = ref(false);
const data = ref({
service: "",
method: "",
});
function $reset() {
data.value = {
service: "",
method: "",
};
}
return {
isOpen,
data,
$reset,
};
});
export const useFilterStore = defineStore("serviceCardFilter", () => {
const isOpen = ref(false);
return {
isOpen,
};
});

View file

@ -1,60 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useConfigStore = defineStore("config", () => {
const data = ref({ global: {}, services: {} });
function updateConf(name, id, value, regex = ".*") {
// Remove prev value
removeConf(name, id);
// Try to update with another value
const formatID = id.toUpperCase().replaceAll("-", "_");
// Case value is invalid to be added
const validInp = new RegExp(regex);
if (validInp.test(value) === false) return;
// Else add value
if (name === "global") data.value[name][formatID] = value;
if (name !== "global") {
if (!(name in data.value["services"])) data.value["services"][name] = {};
data.value["services"][name][formatID] = value;
}
}
// Case value is default or previous (and already in core config value)
// Or before updating a value
function removeConf(name, id) {
if (!name || !id) return; // Need this to proceed
const formatID = id.toUpperCase().replaceAll("-", "_");
// Remove global value
try {
if (name === "global" && !!(formatID in data.value[name]))
delete data.value[name][formatID];
} catch (err) {}
// Remove service value
try {
if (name !== "global" && !!(formatID in data.value["services"][name]))
delete data.value["services"][name][formatID];
} catch (err) {}
}
function $reset() {
data.value = {
global: {},
services: {},
};
}
return { data, $reset, updateConf, removeConf };
});
export const useModesStore = defineStore("modes", () => {
const data = ref(["AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE"]);
return { data, updateConf };
});