mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
started home page
* add stat widget in order to add home page * update some content to handle i18n or template inline feedback * change Grid.vue grid behavior * retrieve builder data from html *add data attributs to better differenciate components * update jsdoc
This commit is contained in:
parent
821f54d456
commit
d3ff909019
26 changed files with 452 additions and 109 deletions
|
|
@ -106,7 +106,7 @@ body {
|
|||
}
|
||||
|
||||
.content-wrap {
|
||||
@apply grid gap-y-4 gap-3 sm:gap-4 lg:gap-6 grid-cols-12 w-full max-w-[1920px];
|
||||
@apply grid gap-y-4 gap-3 sm:gap-4 lg:gap-4 grid-cols-12 w-full max-w-[1920px];
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
|
|
@ -795,47 +795,122 @@ body {
|
|||
.footer-list-item {
|
||||
@apply capitalize-first hover:italic hover:brightness-90 block sm:px-4 py-1 lg:pt-1 lg:pb-1 text-sm tracking-wide font-normal transition duration-300 ease-in-out text-white dark:text-white;
|
||||
}
|
||||
/* CARD STAT COMPONENT */
|
||||
/* STAT COMPONENT */
|
||||
|
||||
.stat-container {
|
||||
@apply col-span-12 relative;
|
||||
}
|
||||
|
||||
.stat-content-container.no-icon {
|
||||
@apply mr-1;
|
||||
}
|
||||
|
||||
.stat-content-container.is-icon {
|
||||
@apply mr-12;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
@apply mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-gray-400;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
@apply mb-1 font-bold dark:text-white/90;
|
||||
}
|
||||
|
||||
.stat-subtitle {
|
||||
@apply font-bold leading-normal text-sm mb-0;
|
||||
}
|
||||
|
||||
.info.stat-subtitle {
|
||||
@apply text-sky-500;
|
||||
}
|
||||
|
||||
.error.stat-subtitle {
|
||||
@apply text-red-500;
|
||||
}
|
||||
|
||||
.success.stat-subtitle {
|
||||
@apply text-green-500;
|
||||
}
|
||||
|
||||
.warning.stat-subtitle {
|
||||
@apply text-yellow-500;
|
||||
}
|
||||
|
||||
.stat-svg-container {
|
||||
@apply absolute top-0 right-0 min-w-12 dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle;
|
||||
}
|
||||
|
||||
.purple.stat-svg-container {
|
||||
@apply bg-purple-600;
|
||||
}
|
||||
|
||||
.green.stat-svg-container {
|
||||
@apply bg-green-700;
|
||||
}
|
||||
|
||||
.red.stat-svg-container {
|
||||
@apply bg-red-700;
|
||||
}
|
||||
|
||||
.orange.stat-svg-container {
|
||||
@apply bg-orange-600;
|
||||
}
|
||||
|
||||
.blue.stat-svg-container {
|
||||
@apply bg-blue-600;
|
||||
}
|
||||
|
||||
.yellow.stat-svg-container {
|
||||
@apply bg-yellow-600;
|
||||
}
|
||||
|
||||
.gray.stat-svg-container {
|
||||
@apply bg-gray-600;
|
||||
}
|
||||
|
||||
.dark.stat-svg-container {
|
||||
@apply bg-slate-600;
|
||||
}
|
||||
|
||||
.amber.stat-svg-container {
|
||||
@apply bg-amber-600;
|
||||
}
|
||||
|
||||
.emerald.stat-svg-container {
|
||||
@apply bg-emerald-600;
|
||||
}
|
||||
|
||||
.teal.stat-svg-container {
|
||||
@apply bg-teal-600;
|
||||
}
|
||||
|
||||
.indigo.stat-svg-container {
|
||||
@apply bg-indigo-600;
|
||||
}
|
||||
|
||||
.cyan.stat-svg-container {
|
||||
@apply bg-cyan-600;
|
||||
}
|
||||
|
||||
.sky.stat-svg-container {
|
||||
@apply bg-sky-700;
|
||||
}
|
||||
|
||||
.pink.stat-svg-container {
|
||||
@apply bg-pink-600;
|
||||
}
|
||||
|
||||
.lime.stat-svg-container {
|
||||
@apply bg-lime-600;
|
||||
}
|
||||
|
||||
/* CARD COMPONENT */
|
||||
|
||||
.card {
|
||||
@apply dark:brightness-110 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between items-start w-full shadow-md break-words dark:shadow-dark-xl rounded-2xl bg-clip-border;
|
||||
@apply transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border;
|
||||
}
|
||||
|
||||
.default.card {
|
||||
@apply bg-white dark:bg-slate-850;
|
||||
}
|
||||
|
||||
.pending.card {
|
||||
@apply text-white bg-blue-500 dark:brightness-95;
|
||||
}
|
||||
|
||||
.error.card {
|
||||
@apply text-white bg-red-500 dark:brightness-95;
|
||||
}
|
||||
|
||||
.card-stat {
|
||||
@apply dark:brightness-110 sm:max-h-28 transition col-span-12 md:col-span-6 2xl:col-span-4 flex p-4 justify-between w-full shadow-md break-words bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border;
|
||||
}
|
||||
|
||||
.card-stat-title {
|
||||
@apply mb-2 font-sans text-sm font-semibold leading-4 uppercase dark:text-gray-500 text-gray-700;
|
||||
}
|
||||
|
||||
.card-stat-count {
|
||||
@apply ml-1 mb-1 font-bold dark:text-white/90;
|
||||
}
|
||||
|
||||
.card-stat-detail-container {
|
||||
@apply mb-0 dark:text-gray-500;
|
||||
}
|
||||
|
||||
.card-stat-detail-container-item {
|
||||
@apply ml-1 font-bold leading-normal text-sm text-gray-700 dark:text-gray-500 mx-0.5;
|
||||
}
|
||||
|
||||
.card-stat-svg-container {
|
||||
@apply dark:brightness-90 inline-block w-12 h-12 text-center rounded-circle;
|
||||
}
|
||||
|
||||
/* CARD COUNT */
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +1,13 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
import Grid from "@components/Widget/Grid.vue";
|
||||
import GridLayout from "@components/Widget/GridLayout.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 Datepicker from "@components/Forms/Field/Datepicker.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
import Grid from "@components/Widget/Grid.vue";
|
||||
import Stat from "@components/Widget/Stat.vue";
|
||||
|
||||
/**
|
||||
@name Builder.vue
|
||||
|
|
@ -16,12 +17,20 @@ import Grid from "@components/Widget/Grid.vue";
|
|||
@example
|
||||
[
|
||||
{
|
||||
"type": "card", // this can be a "card", "modal", "table"... etc
|
||||
"containerClass": "", // tailwind css grid class (items-start, ...)
|
||||
// this can be a "card", "modal", "table"... etc
|
||||
"type": "card",
|
||||
|
||||
// container custom key
|
||||
"title" : "My awesome card",
|
||||
|
||||
// additionnal tailwind css class
|
||||
"containerClass": "",
|
||||
|
||||
// We can define the top level grid system (GridLayout.vue)
|
||||
"containerColumns" : {"pc": 12, "tablet": 12, "mobile": 12},
|
||||
"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
|
||||
// We need to send specific data for each widget typ
|
||||
widgets: [
|
||||
{
|
||||
type : "Checkbox",
|
||||
|
|
@ -45,24 +54,23 @@ const props = defineProps({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-12">
|
||||
<!-- top level grid (layout) -->
|
||||
<GridLayout v-for="(container, index) in props.builder" :key="index"
|
||||
:gridLayoutClass="container.containerClass"
|
||||
:type="container.type"
|
||||
:title="container.title"
|
||||
:columns="container.containerColumns">
|
||||
<!-- widget grid -->
|
||||
<Grid>
|
||||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<Checkbox v-if="widget.type === 'Checkbox'" v-bind="widget.data"></Checkbox>
|
||||
<Select v-if="widget.type === 'Select'" v-bind="widget.data"></Select>
|
||||
<Input v-if="widget.type === 'Input'" v-bind="widget.data"></Input>
|
||||
<Datepicker v-if="widget.type === 'Datepicker'" v-bind="widget.data"></Datepicker>
|
||||
<Button v-if="widget.type === 'Button'" v-bind="widget.data"></Button>
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
</div>
|
||||
<!-- top level grid (layout) -->
|
||||
<GridLayout v-for="(container, index) in props.builder" :key="index"
|
||||
:gridLayoutClass="container.containerClass"
|
||||
:type="container.type"
|
||||
:title="container.title"
|
||||
:columns="container.containerColumns">
|
||||
<!-- widget grid -->
|
||||
<Grid>
|
||||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<Checkbox v-if="widget.type === 'Checkbox'" v-bind="widget.data"></Checkbox>
|
||||
<Select v-if="widget.type === 'Select'" v-bind="widget.data"></Select>
|
||||
<Input v-if="widget.type === 'Input'" v-bind="widget.data"></Input>
|
||||
<Datepicker v-if="widget.type === 'Datepicker'" v-bind="widget.data"></Datepicker>
|
||||
<Button v-if="widget.type === 'Button'" v-bind="widget.data"></Button>
|
||||
<Stat v-if="widget.type === 'Stat'" v-bind="widget.data"></Stat>
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ onBeforeMount(() => {
|
|||
<h1 class="menu-account-title">
|
||||
{{ menu.username }}
|
||||
</h1>
|
||||
<a :tabindex=" menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'" class="menu-account-link" href="/account">manage account </a>
|
||||
<a :tabindex=" menu.isDesktop ? menuIndex : menu.isActive ? menuIndex : '-1'" class="menu-account-link" href="/account">{{ $t('dashboard_manage_account') }}</a>
|
||||
</div>
|
||||
<hr class="menu-separator" />
|
||||
<!-- end logo version -->
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ import ErrorField from "@components/Forms/Error/Field.vue";
|
|||
headerClass: "text-red-500"
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} name
|
||||
@param {string} label
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
|
||||
@param {string} value
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [required=false]
|
||||
@param {object} [columns={"pc": "12", "tab": "12", "mob": "12}]
|
||||
@param {object} [columns={"pc": "12", "tablet": "12", "mobile": "12}] - Field has a grid system. This allow to get multiple field in the same row if needed.
|
||||
@param {boolean} [hideLabel=false]
|
||||
@param {string} [containerClass=""]
|
||||
@param {string} [headerClass=""]
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ import "@assets/css/flatpickr.dark.css";
|
|||
inpClass: "text-center",
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} name
|
||||
@param {string} label
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
|
||||
@param {string|number|date} [defaultDate=null] - Default date when instanciate
|
||||
@param {string|number} [noPickBeforeStamp=""] - Impossible to pick a date before this date
|
||||
@param {string|number} [noPickAfterStamp=""] - Impossible to pick a date after this date
|
||||
@param {boolean} [hideLabel=false]
|
||||
@param {object|boolean} [columns={"pc": "12", "tab": "12", "mob": "12}]
|
||||
@param {object} [columns={"pc": "12", "tablet": "12", "mobile": "12}] - Field has a grid system. This allow to get multiple field in the same row if needed.
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [required=false]
|
||||
@param {string} [headerClass=""]
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@ import ErrorField from "@components/Forms/Error/Field.vue";
|
|||
pattern : "(test)",
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} name
|
||||
@param {string} type - text, email, password, number, tel, url
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text. @param {string} label
|
||||
@param {string} value
|
||||
@param {string} label
|
||||
@param {object} [columns={"pc": "12", "tablet": "12", "mobile": "12}] - Field has a grid system. This allow to get multiple field in the same row if needed.
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [required=false]
|
||||
@param {string} [placeholder=""]
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ import ErrorField from "@components/Forms/Error/Field.vue";
|
|||
label: 'Test select',
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} name
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
|
||||
@param {string} value
|
||||
@param {string} label
|
||||
@param {array} values
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [required=false]
|
||||
@param {array} [requiredValues=[]] - values that need to be selected to be valid, works only if required is true
|
||||
@param {object|boolean} [columns={"pc": "12", "tab": "12", "mob": "12}]
|
||||
@param {object} [columns={"pc": "12", "tablet": "12", "mobile": "12}] - Field has a grid system. This allow to get multiple field in the same row if needed.
|
||||
@param {boolean} [hideLabel=false]
|
||||
@param {string} [containerClass=""]
|
||||
@param {string} [inpClass=""]
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import { defineProps } from "vue";
|
|||
name: 'test-input',
|
||||
required: true,
|
||||
}
|
||||
@param {string} label
|
||||
@param {string} name
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
|
||||
@param {boolean} [required=false]
|
||||
@param {boolean} [hideLabel=false]
|
||||
@param {string} [headerClass=""]
|
||||
|
|
@ -55,7 +55,7 @@ const props = defineProps({
|
|||
:for="props.name"
|
||||
class="relative lowercase capitalize-first my-1 transition duration-300 ease-in-out text-sm sm:text-md font-bold m-0 dark:text-gray-300"
|
||||
>
|
||||
{{ props.label ? props.label : props.name }} <span v-if="props.version">{{ props.version }}</span>
|
||||
{{ props.label ? $t(props.label, props.label) : $t(props.name, props.name) }} <span v-if="props.version">{{ props.version }}</span>
|
||||
</label>
|
||||
<span
|
||||
v-if="props.required"
|
||||
|
|
|
|||
18
vuejs/client/src/components/Icons/Stat/Crown.vue
Normal file
18
vuejs/client/src/components/Icons/Stat/Crown.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Crown.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of a crown, mainly used for premium version stat.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="leading-none text-lg relative scale-[0.6]"
|
||||
viewBox="0 0 48 46"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="fill-white" d="M43.218 28.2327L43.6765 23.971C43.921 21.6973 44.0825 20.1957 43.9557 19.2497L44 19.25C46.071 19.25 47.75 17.5711 47.75 15.5C47.75 13.4289 46.071 11.75 44 11.75C41.929 11.75 40.25 13.4289 40.25 15.5C40.25 16.4366 40.5935 17.2931 41.1613 17.9503C40.346 18.4535 39.2805 19.515 37.6763 21.1128C36.4405 22.3438 35.8225 22.9593 35.1333 23.0548C34.7513 23.1075 34.3622 23.0532 34.0095 22.898C33.373 22.6175 32.9485 21.8567 32.0997 20.335L27.6262 12.3135C27.1025 11.3747 26.6642 10.5889 26.2692 9.95662C27.89 9.12967 29 7.44445 29 5.5C29 2.73857 26.7615 0.5 24 0.5C21.2385 0.5 19 2.73857 19 5.5C19 7.44445 20.11 9.12967 21.7308 9.95662C21.3358 10.589 20.8975 11.3746 20.3738 12.3135L15.9002 20.335C15.0514 21.8567 14.627 22.6175 13.9905 22.898C13.6379 23.0532 13.2487 23.1075 12.8668 23.0548C12.1774 22.9593 11.5595 22.3438 10.3238 21.1128C8.71968 19.515 7.6539 18.4535 6.83882 17.9503C7.4066 17.2931 7.75 16.4366 7.75 15.5C7.75 13.4289 6.07107 11.75 4 11.75C1.92893 11.75 0.25 13.4289 0.25 15.5C0.25 17.5711 1.92893 19.25 4 19.25L4.04428 19.2497C3.91755 20.1957 4.07905 21.6973 4.32362 23.971L4.782 28.2327C5.03645 30.5982 5.24802 32.849 5.50717 34.875H42.4928C42.752 32.849 42.9635 30.5982 43.218 28.2327Z" fill="#1C274C" />
|
||||
<path class="fill-white" d="M21.2803 45.5H26.7198C33.8098 45.5 37.3545 45.5 39.7198 43.383C40.7523 42.4588 41.4057 40.793 41.8775 38.625H6.1224C6.59413 40.793 7.24783 42.4588 8.2802 43.383C10.6454 45.5 14.1903 45.5 21.2803 45.5Z" fill="#1C274C" />
|
||||
</svg>
|
||||
</template>
|
||||
19
vuejs/client/src/components/Icons/Stat/Free.vue
Normal file
19
vuejs/client/src/components/Icons/Stat/Free.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Free.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of a free version stat.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="leading-none fill-white text-yellow-500 text-lg relative scale-[0.6]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
|
||||
</svg>
|
||||
</template>
|
||||
16
vuejs/client/src/components/Icons/Stat/Instances.vue
Normal file
16
vuejs/client/src/components/Icons/Stat/Instances.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Instances.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of the instances stat.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="translate-x-0.5 -translate-y-0.5 scale-50 leading-none text-lg relative fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512">
|
||||
<path d="M80 104c13.3 0 24-10.7 24-24s-10.7-24-24-24S56 66.7 56 80s10.7 24 24 24zm80-24c0 32.8-19.7 61-48 73.3v87.8c18.8-10.9 40.7-17.1 64-17.1h96c35.3 0 64-28.7 64-64v-6.7C307.7 141 288 112.8 288 80c0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V160c0 70.7-57.3 128-128 128H176c-35.3 0-64 28.7-64 64v6.7c28.3 12.3 48 40.5 48 73.3c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-32.8 19.7-61 48-73.3V352 153.3C19.7 141 0 112.8 0 80C0 35.8 35.8 0 80 0s80 35.8 80 80zm232 0c0-13.3-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24s24-10.7 24-24zM80 456c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24z" />
|
||||
</svg>
|
||||
</template>
|
||||
19
vuejs/client/src/components/Icons/Stat/Plugins.vue
Normal file
19
vuejs/client/src/components/Icons/Stat/Plugins.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Plugins.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of the plugins of the application.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="scale-75 leading-none text-lg relative fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 01-.657.643 48.39 48.39 0 01-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 01-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 00-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 01-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 00.657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 005.427-.63 48.05 48.05 0 00.582-4.717.532.532 0 00-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 00.658-.663 48.422 48.422 0 00-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 01-.61-.58v0z" />
|
||||
</svg>
|
||||
</template>
|
||||
19
vuejs/client/src/components/Icons/Stat/Services.vue
Normal file
19
vuejs/client/src/components/Icons/Stat/Services.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Services.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of the services of the application.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="scale-[0.6] stroke-orange-500 leading-none text-lg relative fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3m16.5 0h.008v.008h-.008v-.008zm-3 0h.008v.008h-.008v-.008z" />
|
||||
</svg>
|
||||
</template>
|
||||
16
vuejs/client/src/components/Icons/Stat/Version.vue
Normal file
16
vuejs/client/src/components/Icons/Stat/Version.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Icons/Stat/Version.vue
|
||||
@description This component is used to create a complete svg icon for stat card.
|
||||
This svg is a representation of the version of the application.
|
||||
*/
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<svg role="img"
|
||||
aria-hidden="true" class="translate-x-0.5 -translate-y-0.5 scale-50 leading-none text-lg relative fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512">
|
||||
<path d="M80 104c13.3 0 24-10.7 24-24s-10.7-24-24-24S56 66.7 56 80s10.7 24 24 24zm80-24c0 32.8-19.7 61-48 73.3v87.8c18.8-10.9 40.7-17.1 64-17.1h96c35.3 0 64-28.7 64-64v-6.7C307.7 141 288 112.8 288 80c0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V160c0 70.7-57.3 128-128 128H176c-35.3 0-64 28.7-64 64v6.7c28.3 12.3 48 40.5 48 73.3c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-32.8 19.7-61 48-73.3V352 153.3C19.7 141 0 112.8 0 80C0 35.8 35.8 0 80 0s80 35.8 80 80zm232 0c0-13.3-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24s24-10.7 24-24zM80 456c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24z" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -15,8 +15,8 @@ import { defineProps, defineEmits, reactive } from "vue";
|
|||
message: "Your action has been successfully completed",
|
||||
delayToClose: 5000,
|
||||
}
|
||||
@param {string} title - The title of the alert
|
||||
@param {string} message - The message of the alert
|
||||
@param {string} title - The title of the alert. Can be a translation key or by default raw text.
|
||||
@param {string} message - The message of the alert. Can be a translation key or by default raw text.
|
||||
@param {boolean} [canClose=true] - Determine if the alert can be closed by user (add a close button), by default it is closable
|
||||
@param {string} [id=`feedback-alert-${message.substring(0, 10)}`]
|
||||
@param {string} [isFixed=false] - Determine if the alert is fixed (visible bottom right of page) or relative (inside a container)
|
||||
|
|
@ -94,7 +94,7 @@ onMounted(() => {
|
|||
>
|
||||
<div class="feedback-alert-header">
|
||||
<h5 class="feedback-alert-title">
|
||||
{{ `${props.title}` }}
|
||||
{{ $t(props.title, props.title) }}
|
||||
</h5>
|
||||
<button
|
||||
:tabindex="props.tabId"
|
||||
|
|
@ -116,7 +116,7 @@ onMounted(() => {
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="feedback-alert-text">{{ props.message }}</p>
|
||||
<p class="feedback-alert-text">{{ $t(props.message, props.message) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import IconAdd from "@components/Icons/Button/Add.vue";
|
|||
eventAttr: {"store" : "modal", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"},7
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} text - Content of the button
|
||||
@param {string} text - Content of the button. Can be a translation key or by default raw text.
|
||||
@param {string} [type="button"] - Can be of type button || submit
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [hideText=false] - Hide text to only display icon
|
||||
|
|
@ -164,7 +164,7 @@ function updateData(isClick = false) {
|
|||
>
|
||||
<span :class="[props.hideText ? 'sr-only' : '',
|
||||
props.iconName ? 'mr-2' : ''
|
||||
]" :id="`${props.id}-text`">{{ props.text }}</span>
|
||||
]" :id="`${props.id}-text`">{{ $t(props.text, props.text) }}</span>
|
||||
<IconAdd v-if="props.iconName === 'add'" :iconName="props.iconName" :iconColor="props.iconColor" />
|
||||
</button>
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const gridClass = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[props.containerClass ? props.containerClass : '', gridClass]">
|
||||
<div data-container :class="[props.containerClass ? props.containerClass : '', gridClass]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -29,7 +29,7 @@ const flexClass = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[flexClass]">
|
||||
<div data-flex :class="[flexClass]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { computed } from 'vue';
|
|||
@name Widget/Grid.vue
|
||||
@description This component is a basic container that can be used to wrap other components.
|
||||
This container is based on a grid system and will return a grid container with 12 columns.
|
||||
In case we are working with grid system, we can add columns to position the container.
|
||||
We can define additional class too.
|
||||
This component is mainly use as widget container or as a child of a GridLayout.
|
||||
@example
|
||||
|
|
@ -14,15 +13,9 @@ import { computed } from 'vue';
|
|||
gridClass: "items-start"
|
||||
}
|
||||
@param {string} [gridClass="items-start"] - Additional class
|
||||
@param {object|boolean} [columns=false] - Work with grid system { pc: 12, tablet: 12, mobile: 12}
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
columns : {
|
||||
type: [Object, Boolean],
|
||||
required: false,
|
||||
default : false,
|
||||
},
|
||||
gridClass : {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -30,19 +23,10 @@ const props = defineProps({
|
|||
},
|
||||
})
|
||||
|
||||
|
||||
const gridClass = computed(() => {
|
||||
return `grid grid-cols-12 w-full ${props.gridClass}`;
|
||||
})
|
||||
|
||||
const columnClass = computed(() => {
|
||||
return props.columns ? `col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}` : ``;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[gridClass, columnClass]">
|
||||
<div data-grid :class="[props.gridClass, 'col-span-12 grid grid-cols-12 w-full']">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
const containerClass = computed(() => {
|
||||
if(props.type === 'card') return 'bg-white rounded-xl shadow-md w-full';
|
||||
if(props.type === 'card') return 'card';
|
||||
return '';
|
||||
})
|
||||
|
||||
const gridClass = computed(() => {
|
||||
return `grid grid-cols-12 w-full col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}`;
|
||||
return `break-words grid grid-cols-12 w-full col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}`;
|
||||
})
|
||||
|
||||
const titleClass = computed(() => {
|
||||
|
|
@ -63,8 +63,8 @@ const titleClass = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[containerClass, gridClass, props.gridLayoutClass, 'p-4 m-4']">
|
||||
<h1 v-if="props.title" :class="[titleClass, 'col-span-12']">{{ props.title }}</h1>
|
||||
<div data-grid-layout :class="[containerClass, gridClass, props.gridLayoutClass, 'p-4']">
|
||||
<h1 v-if="props.title" :class="[titleClass, 'col-span-12']">{{ $t(props.title, props.title) }}</h1>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
115
vuejs/client/src/components/Widget/Stat.vue
Normal file
115
vuejs/client/src/components/Widget/Stat.vue
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import IconCrown from '@components/Icons/Stat/Crown.vue';
|
||||
import IconFree from '@components/Icons/Stat/Free.vue';
|
||||
import IconInstances from '@components/Icons/Stat/Instances.vue';
|
||||
import IconPlugins from '@components/Icons/Stat/Plugins.vue';
|
||||
import IconServices from '@components/Icons/Stat/Services.vue';
|
||||
import IconVersion from '@components/Icons/Stat/Version.vue';
|
||||
|
||||
/**
|
||||
@name Widget/Stat.vue
|
||||
@description This component is a basic stat element that can be used to display a title, a value and an icon.
|
||||
This component has no grid system and will always get the full width of the parent.
|
||||
This component is mainly use inside a blank card.
|
||||
@example
|
||||
{
|
||||
title: "Total Users",
|
||||
value: 100,
|
||||
subtitle : "Last 30 days",
|
||||
icon: "user",
|
||||
iconColor: "sky",
|
||||
link: "/users",
|
||||
subtitleColor: "info",
|
||||
}
|
||||
@param {string} title - The title of the stat. Can be a translation key or by default raw text.
|
||||
@param {string|number} value - The value of the stat
|
||||
@param {string} [subtitle=""] - The subtitle of the stat. Can be a translation key or by default raw text.
|
||||
@param {string} [icon=""] - A top-right icon to display between icon available in Icons/Stat. Case falsy value, no icon displayed. The icon name is the name of the file without the extension on lowercase.
|
||||
@param {string} [iconColor="sky"] - The color of the icon between some tailwind css available colors (purple, green, red, orange, blue, yellow, gray, dark, amber, emerald, teal, indigo, cyan, sky, pink, lime, rose, fuchsia, violet, lightBlue, warmGray, trueGray, coolGray, blueGray, white, black)
|
||||
@param {string} [link=""] - A link to redirect when the card is clicked. Case falsy value, element is a div, , else it is an anchor.
|
||||
@param {string} [subtitleColor="info"] - The color of the subtitle between error, success, warning, info
|
||||
@param {string} [statClass=""] - Additional class
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : ""
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : ""
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "sky",
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : ""
|
||||
},
|
||||
subtitleColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "info",
|
||||
},
|
||||
statClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
const statEl = ref();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.link) {
|
||||
statEl.value.setAttribute('href', props.link);;
|
||||
statEl.value.setAttribute('rel', 'noopener')
|
||||
}
|
||||
|
||||
if(props.link && props.link.startsWith('http')) {
|
||||
statEl.value.setAttribute('target', '_blank');
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<component ref="statEl" :is="props.link ? 'a' : 'div'"
|
||||
:class="['stat-container', props.statClass]">
|
||||
<!-- text -->
|
||||
<div :class="['stat-content-container', props.icon ? 'is-icon' : 'no-icon']">
|
||||
<p class="stat-title">{{ $t(props.title, props.title) }}</p>
|
||||
<!-- version of user -->
|
||||
<h5 class="stat-value">{{ $t(props.value, props.value) }}</h5>
|
||||
<p v-if="props.subtitle" :class="['stat-subtitle', props.subtitleColor]">{{ $t(props.subtitle, props.subtitle) }}</p>
|
||||
</div>
|
||||
<!-- end text -->
|
||||
<!-- icon -->
|
||||
<div v-if="props.icon" role="img"
|
||||
aria-label="version"
|
||||
:class="['stat-svg-container', props.iconColor]">
|
||||
<IconCrown v-if="props.icon === 'crown'" />
|
||||
<IconFree v-else-if="props.icon === 'free'" />
|
||||
<IconInstances v-else-if="props.icon === 'instances'" />
|
||||
<IconPlugins v-else-if="props.icon === 'plugins'" />
|
||||
<IconServices v-else-if="props.icon === 'services'" />
|
||||
<IconVersion v-else-if="props.icon === 'version'" />
|
||||
</div>
|
||||
<!-- end icon -->
|
||||
</component>
|
||||
</template>
|
||||
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
"dashboard_advanced": "advanced",
|
||||
"dashboard_loading": "loading",
|
||||
"dashboard_lang_dropdown_button_desc": "Toggle hide/show radio group (dropdown) to change langage.",
|
||||
"dashboard_manage_account" : "manage account",
|
||||
"dashboard_home": "home",
|
||||
"dashboard_instances": "instances",
|
||||
"dashboard_global_config": "global config",
|
||||
|
|
|
|||
23
vuejs/client/src/pages/home/Home.vue
Normal file
23
vuejs/client/src/pages/home/Home.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
import Builder from "@components/Builder.vue";
|
||||
|
||||
const home = reactive({
|
||||
builder : ""
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
// Get builder data
|
||||
const dataAtt = 'data-server-builder';
|
||||
const dataEl = document.querySelector(`[${dataAtt}]`);
|
||||
const data = dataEl && !dataEl.getAttribute(dataAtt).includes(dataAtt) ? JSON.parse(dataEl.getAttribute(dataAtt)) : {};
|
||||
home.builder = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout >
|
||||
<Builder v-if="home.builder" :builder="home.builder" />
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
11
vuejs/client/src/pages/home/home.js
Normal file
11
vuejs/client/src/pages/home/home.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { getI18n } from "@utils/lang.js";
|
||||
import Home from "./Home.vue";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(Home)
|
||||
.use(pinia)
|
||||
.use(getI18n(["dashboard", "action", "home"]))
|
||||
.mount("#app");
|
||||
18
vuejs/client/src/pages/home/index.html
Normal file
18
vuejs/client/src/pages/home/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/flag-icons.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BunkerWeb | DASHBOARD</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hidden" data-server-global='{"username" : "admin"}'></div>
|
||||
<div class="hidden" 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","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"Version","subtitle":"all features available","subtitleColor":"success","value":"PRO","icon":"crown","iconColor":"amber"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"INSTANCES","subtitle":"1 ui, 1 autoconf","subtitleColor":"info","value":"2","icon":"instances","iconColor":"orange"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"PLUGINS","subtitle":"0 error","subtitleColor":"success","value":"48","icon":"version","iconColor":"blue"}}]}]'></div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="home.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue