mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
add instances components + start instances page
* create global components that can work with instances card (DetailList.vue, IconStatus, TitleCardContent, ButtonGroup...) * update some others widgets to work with Container of Flex component instead of own container element and class * create widget Instances using subcomponents * add an instances page with a basic instance card + define the translation key * add translation key for card details * add missing JSdoc on some components * update or add some css
This commit is contained in:
parent
38e3b2465e
commit
e199cae258
23 changed files with 718 additions and 188 deletions
|
|
@ -807,17 +807,42 @@ body {
|
|||
@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;
|
||||
}
|
||||
|
||||
/* STATUS COMPONENT */
|
||||
|
||||
.status-svg-container {
|
||||
@apply absolute top-1 right-0;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
@apply h-6 w-6 rounded-full;
|
||||
}
|
||||
|
||||
/* CONTENT COMPONENT */
|
||||
|
||||
.content-title {
|
||||
@apply text-lg font-bold mb-2 col-span-12;
|
||||
}
|
||||
|
||||
/* STAT COMPONENT */
|
||||
/* CONTENT DETAIL LSIT COMPONENT */
|
||||
|
||||
.stat-container {
|
||||
@apply col-span-12 relative;
|
||||
.content-detail-list-container {
|
||||
@apply grid grid-cols-12 gap-2 my-4;
|
||||
}
|
||||
|
||||
.content-detail-list-item {
|
||||
@apply flex items-center col-span-1 py-1 sm:py-0;
|
||||
}
|
||||
|
||||
.content-detail-list-title {
|
||||
@apply min-w-fit transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500;
|
||||
}
|
||||
|
||||
.content-detail-list-subtitle {
|
||||
@apply min-w-[2rem] break-all transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-gray-100;
|
||||
}
|
||||
|
||||
/* STAT COMPONENT */
|
||||
|
||||
.stat-content-container.no-icon {
|
||||
@apply mr-1;
|
||||
}
|
||||
|
|
@ -853,7 +878,11 @@ body {
|
|||
}
|
||||
|
||||
.card-title {
|
||||
@apply text-2xl font-bold mb-2 col-span-12;
|
||||
@apply break-words max-w-[80%] font-bold mb-2 col-span-12 dark:text-white/90 transition duration-300 ease-in-out text-2xl;
|
||||
}
|
||||
|
||||
.card-content-title {
|
||||
@apply break-words max-w-[80%] mb-2 font-bold dark:text-white/90 transition duration-300 ease-in-out text-xl;
|
||||
}
|
||||
|
||||
/* CARD INFO */
|
||||
|
|
@ -900,19 +929,23 @@ body {
|
|||
@apply bg-white text-red-500 border border-red-500 hover:border-red-500/80 focus:border-red-500/80 hover:text-red-500/80 focus:text-red-500/80;
|
||||
}
|
||||
|
||||
.btn.delete {
|
||||
.btn.delete,
|
||||
.btn.error {
|
||||
@apply bg-red-500 hover:bg-red-500/80 focus:bg-red-500/80;
|
||||
}
|
||||
|
||||
.btn.valid {
|
||||
.btn.valid,
|
||||
.btn.success {
|
||||
@apply bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80;
|
||||
}
|
||||
|
||||
.btn.edit {
|
||||
.btn.edit,
|
||||
.btn.warning {
|
||||
@apply bg-yellow-500 hover:bg-yellow-500/80 focus:bg-yellow-500/80;
|
||||
}
|
||||
|
||||
.btn.info {
|
||||
.btn.info,
|
||||
.btn.load {
|
||||
@apply bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80;
|
||||
}
|
||||
|
||||
|
|
@ -1387,21 +1420,25 @@ body {
|
|||
|
||||
/* BACKGROUND COLOR MODIFIER */
|
||||
|
||||
.success.status-icon,
|
||||
.success.stat-svg-container,
|
||||
.success.feedback-alert-wrap {
|
||||
@apply bg-green-500;
|
||||
}
|
||||
|
||||
.error.status-icon,
|
||||
.error.stat-svg-container,
|
||||
.error.feedback-alert-wrap {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
.warning.status-icon,
|
||||
.warning.stat-svg-container,
|
||||
.warning.feedback-alert-wrap {
|
||||
@apply bg-yellow-500;
|
||||
}
|
||||
|
||||
.info.status-icon,
|
||||
.info.stat-svg-container,
|
||||
.info.feedback-alert-wrap {
|
||||
@apply bg-sky-500;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,18 +1,30 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
// Containers
|
||||
import Grid from "@components/Widget/Grid.vue";
|
||||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
// Title
|
||||
import TitleCard from "@components/Title/Card.vue";
|
||||
import TitleCardContent from "@components/Title/CardContent.vue";
|
||||
import TitleStat from "@components/Title/Stat.vue";
|
||||
// Subtitle
|
||||
import SubtitleStat from "@components/Subtitle/Stat.vue";
|
||||
// Content
|
||||
import ContentStat from "@components/Content/Stat.vue";
|
||||
import ContentDetailList from "@components/Content/DetailList.vue";
|
||||
// Icon
|
||||
import IconStat from "@components/Icon/Stat.vue";
|
||||
import IconStatus from "@components/Icon/Status.vue";
|
||||
// Form
|
||||
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";
|
||||
// Widget
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import Stat from "@components/Widget/Stat.vue";
|
||||
import TitleStat from "@components/Title/Stat.vue";
|
||||
import ContentStat from "@components/Content/Stat.vue";
|
||||
import SubtitleStat from "@components/Subtitle/Stat.vue";
|
||||
import IconStat from "@components/Icon/Stat.vue";
|
||||
import Instance from "@components/Widget/Instance.vue";
|
||||
|
||||
/**
|
||||
@name Builder.vue
|
||||
|
|
@ -74,6 +86,10 @@ const props = defineProps({
|
|||
<!-- widget element -->
|
||||
<template v-for="(widget, index) in container.widgets" :key="index">
|
||||
<TitleCard v-if="widget.type === 'TitleCard'" v-bind="widget.data" />
|
||||
<TitleCardContent
|
||||
v-if="widget.type === 'TitleCardContent'"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<Checkbox v-if="widget.type === 'Checkbox'" v-bind="widget.data" />
|
||||
<Select v-if="widget.type === 'Select'" v-bind="widget.data" />
|
||||
<Input v-if="widget.type === 'Input'" v-bind="widget.data" />
|
||||
|
|
@ -90,6 +106,16 @@ const props = defineProps({
|
|||
v-bind="widget.data"
|
||||
/>
|
||||
<IconStat v-if="widget.type === 'IconStat'" v-bind="widget.data" />
|
||||
<Instance v-if="widget.type === 'Instance'" v-bind="widget.data" />
|
||||
<IconStatus v-if="widget.type === 'IconStatus'" v-bind="widget.data" />
|
||||
<ContentDetailList
|
||||
v-if="widget.type === 'ContentDetailList'"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<ButtonGroup
|
||||
v-if="widget.type === 'ButtonGroup'"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
|
|
|
|||
47
vuejs/client/src/components/Content/DetailList.vue
Normal file
47
vuejs/client/src/components/Content/DetailList.vue
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<script setup>
|
||||
import { computed, defineProps } from "vue";
|
||||
/**
|
||||
@name Content/DetailList.vue
|
||||
@description This component is used to display key value information in a list.
|
||||
@example
|
||||
{
|
||||
details : [{key: "Total Users", value: "100"}],
|
||||
itemColumns: {pc: 12, tablet: 12, mobile: 12}
|
||||
}
|
||||
@param {array} details - The list of key value information. The key and value can be a translation key or a raw text.
|
||||
@param {object} [itemColumns={pc: 12, tablet: 12, mobile: 12}] - Determine the position of the items in the grid system.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
details: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
itemColumns: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: { pc: 12, tablet: 12, mobile: 12 },
|
||||
},
|
||||
});
|
||||
|
||||
const gridClass = computed(() => {
|
||||
return props.itemColumns
|
||||
? `col-span-${props.itemColumns.mobile} md:col-span-${props.itemColumns.tablet} lg:col-span-${props.itemColumns.pc}`
|
||||
: "";
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ul v-if="props.details" :class="['content-detail-list-container']">
|
||||
<li
|
||||
v-for="item in props.details"
|
||||
:class="['content-detail-list-item', gridClass]"
|
||||
>
|
||||
<span class="content-detail-list-title">
|
||||
{{ $t(item["key"], item["key"]) }}
|
||||
</span>
|
||||
<span class="content-detail-list-subtitle">
|
||||
{{ $t(item["value"], item["value"]) }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup>
|
||||
import Icons from "@/components/Widget/Icons.vue";
|
||||
|
||||
/**
|
||||
@name Icon/Stat.vue
|
||||
@description This component is a icon used with stats.
|
||||
|
|
|
|||
58
vuejs/client/src/components/Icon/Status.vue
Normal file
58
vuejs/client/src/components/Icon/Status.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script setup>
|
||||
import { defineProps, computed } from "vue";
|
||||
|
||||
/**
|
||||
@name Icon/Status.vue
|
||||
@description This component is a icon used with status.
|
||||
@example
|
||||
{
|
||||
id: "instance-1",
|
||||
status: "success",
|
||||
statusClass: "col-span-12",
|
||||
}
|
||||
@param {string} id - The id of the status icon.
|
||||
@param {string} [status="info"] - The color of the icon between error, success, warning, info
|
||||
@param {string} [statusClass=""] - Additional class, for example to use with grid system.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "1",
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "info",
|
||||
},
|
||||
statusClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const statusDesc = computed(() => {
|
||||
if (props.status === "success")
|
||||
return ["dashboard_status_success", "status active or success."];
|
||||
if (props.status === "error")
|
||||
return ["dashboard_status_error", "status inactive or error."];
|
||||
if (props.status === "warning")
|
||||
return ["dashboard_status_warning", "status warning or alert."];
|
||||
if (props.status === "info")
|
||||
return ["dashboard_status_info", "status loading or waiting or unknown."];
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="[props.statusClass, 'status-svg-container']">
|
||||
<div
|
||||
role="img"
|
||||
:aria-describedby="`instance-status-${props.id}`"
|
||||
:class="[props.status, 'status-icon']"
|
||||
></div>
|
||||
<p :id="`instance-status-${props.id}`" class="sr-only">
|
||||
{{ $t(statusDesc[0], statusDesc[1]) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,4 +1,17 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Title/Card.vue
|
||||
@description This component is a used as card title.
|
||||
This can be used outside of the card container.
|
||||
@example
|
||||
{
|
||||
title: "Total Users",
|
||||
titleClass: "text-lg"
|
||||
}
|
||||
@param {string} title - Can be a translation key or by default raw text.
|
||||
@param {string} [titleClass=""] - Additional class, useful when component is used directly on a grid system
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
|
|
|
|||
32
vuejs/client/src/components/Title/CardContent.vue
Normal file
32
vuejs/client/src/components/Title/CardContent.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Title/CardContent.vue
|
||||
@description This component is similar to TitleCard but with a lower size.
|
||||
This can be used outside of the card container.
|
||||
@example
|
||||
{
|
||||
title: "Total Users",
|
||||
titleClass: "text-lg"
|
||||
}
|
||||
@param {string} title - Can be a translation key or by default raw text.
|
||||
@param {string} [titleClass=""] - Additional class, useful when component is used directly on a grid system
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
titleClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 v-if="props.title" :class="[props.titleClass, 'card-content-title']">
|
||||
{{ $t(props.title, props.title) }}
|
||||
</h2>
|
||||
</template>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
titleClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 v-if="props.title" :class="[props.titleClass, 'content-title']">
|
||||
{{ $t(props.title, props.title) }}
|
||||
</h1>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, onBeforeMount, onMounted } from 'vue';
|
||||
import { computed, ref, watch, onBeforeMount, onMounted } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import { useEventStore } from "@store/event.js";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
|
|
@ -40,132 +40,156 @@ import Icons from "@components/Widget/Icons.vue";
|
|||
const eventStore = useEventStore();
|
||||
|
||||
const props = defineProps({
|
||||
id : {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// valid || delete || info
|
||||
text : {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
type : {
|
||||
// valid || delete || info
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : "button"
|
||||
default: "button",
|
||||
},
|
||||
disabled : {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default : false
|
||||
default: false,
|
||||
},
|
||||
// case we want only icon but we need to add accessibility data
|
||||
hideText : {
|
||||
hideText: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default : false
|
||||
default: false,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : "primary"
|
||||
default: "primary",
|
||||
},
|
||||
// sm || normal || lg || xl
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : "normal"
|
||||
default: "normal",
|
||||
},
|
||||
// Store on components/Icons/Button
|
||||
// Check import ones
|
||||
iconName : {
|
||||
iconName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : "",
|
||||
default: "",
|
||||
},
|
||||
// Defined on input.css
|
||||
iconColor : {
|
||||
iconColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : ""
|
||||
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"}
|
||||
// for example, if button open a modal : {"store" : "modal", "default" : "close", "value" : "open", "target" : "modal_id", "valueExpanded" : "open"}
|
||||
eventAttr: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default : {}
|
||||
default: {},
|
||||
},
|
||||
tabId : {
|
||||
tabId: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default : ""
|
||||
}
|
||||
default: "",
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const btnEl = ref();
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
return `btn ${props.color} ${props.size}`
|
||||
})
|
||||
return `btn ${props.color} ${props.size}`;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
updateData();
|
||||
})
|
||||
updateData();
|
||||
});
|
||||
|
||||
watch(eventStore,() => {
|
||||
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;
|
||||
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;
|
||||
|
||||
if(!isStore || !isValue || !isDefault) return;
|
||||
isClick
|
||||
? eventStore.updateEvent(props.eventAttr.store, props.eventAttr.value)
|
||||
: eventStore.addEvent(props.eventAttr.store, props.eventAttr.default);
|
||||
|
||||
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 expanded = props.eventAttr?.valueExpanded
|
||||
? props.eventAttr.valueExpanded ===
|
||||
eventStore.getEvent(props.eventAttr.store)
|
||||
? "true"
|
||||
: "false"
|
||||
: false;
|
||||
if (expanded) {
|
||||
btnEl.value.setAttribute("aria-expanded", expanded);
|
||||
}
|
||||
|
||||
try {
|
||||
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) {
|
||||
|
||||
if (controls) {
|
||||
btnEl.value.setAttribute("aria-controls", controls);
|
||||
}
|
||||
|
||||
if (!controls) {
|
||||
btnEl.value.removeAttribute("aria-controls");
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container :containerClass="`w-full m-2 ${props.containerClass}`" :columns="props.columns">
|
||||
<button :type="props.type" ref="btnEl" @click="updateData(true)" :id="props.id"
|
||||
:tabindex="props.tabId || contentIndex"
|
||||
:class="[buttonClass]"
|
||||
:disabled="props.disabled || false"
|
||||
:aria-describedby="`${props.id}-text`"
|
||||
<Container
|
||||
:containerClass="`${props.containerClass}`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
<span :class="[props.hideText ? 'sr-only' : '',
|
||||
props.iconName ? 'mr-2' : ''
|
||||
]" :id="`${props.id}-text`">{{ $t(props.text, props.text) }}</span>
|
||||
<Icons :iconName="props.iconName" :iconClass="'btn-svg'" :iconColor="props.iconColor" />
|
||||
</button>
|
||||
</Container>
|
||||
<button
|
||||
:type="props.type"
|
||||
ref="btnEl"
|
||||
@click="updateData(true)"
|
||||
:id="props.id"
|
||||
:tabindex="props.tabId || contentIndex"
|
||||
:class="[buttonClass]"
|
||||
:disabled="props.disabled || false"
|
||||
:aria-describedby="`text-${props.id}`"
|
||||
>
|
||||
<span
|
||||
:class="[props.hideText ? 'sr-only' : '', props.iconName ? 'mr-2' : '']"
|
||||
:id="`text-${props.id}`"
|
||||
>{{ $t(props.text, props.text) }}</span
|
||||
>
|
||||
<Icons
|
||||
v-if="props.iconName"
|
||||
:iconName="props.iconName"
|
||||
:iconClass="'btn-svg'"
|
||||
:iconColor="props.iconColor"
|
||||
/>
|
||||
</button>
|
||||
</Container>
|
||||
</template>
|
||||
|
|
|
|||
74
vuejs/client/src/components/Widget/ButtonGroup.vue
Normal file
74
vuejs/client/src/components/Widget/ButtonGroup.vue
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, onBeforeMount, onMounted } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Flex from "@components/Widget/Flex.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
|
||||
/**
|
||||
@name Widget/ButtonGroup.vue
|
||||
@description This component allow to display multiple buttons in the same row using flex.
|
||||
We can define additional class too for the flex container.
|
||||
We need a list of buttons to display.
|
||||
@example
|
||||
{
|
||||
id: "group-btn",
|
||||
groupClass : "justify-center",
|
||||
buttons: [
|
||||
{
|
||||
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"},
|
||||
},
|
||||
{
|
||||
id: "close-modal-btn",
|
||||
text: "Close modal",
|
||||
disabled: false,
|
||||
hideText: true,
|
||||
color: "red",
|
||||
size: "normal",
|
||||
iconName: "modal",
|
||||
iconColor: "white",
|
||||
eventAttr: {"store" : "modal", "value" : "close", "target" : "modal_id", "valueExpanded" : "close"},
|
||||
},
|
||||
],
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} [groupClass="justify-center align-center"] - Additional class for the flex container
|
||||
@param {array} buttons - List of buttons to display. Button component is used.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "group-btn",
|
||||
},
|
||||
groupClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "justify-center align-center",
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Flex v-if="props.buttons.length > 0" :flexClass="props.groupClass">
|
||||
<Button
|
||||
v-for="(button, id) in props.buttons"
|
||||
:key="button.id"
|
||||
v-bind="button"
|
||||
:class="[id === props.buttons.length - 1 ? '' : 'mr-2']"
|
||||
/>
|
||||
</Flex>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed } from "vue";
|
||||
|
||||
/**
|
||||
@name Widget/Container.vue
|
||||
|
|
@ -17,25 +17,30 @@ import { computed } from 'vue';
|
|||
*/
|
||||
|
||||
const props = defineProps({
|
||||
containerClass : {
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : ""
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
required: false,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const gridClass = computed(() => {
|
||||
return props.columns ? `col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}` : '';
|
||||
})
|
||||
return props.columns
|
||||
? `col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}`
|
||||
: "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-container :class="[props.containerClass ? props.containerClass : '', gridClass]">
|
||||
<div
|
||||
data-container
|
||||
:class="[props.containerClass ? props.containerClass : '', gridClass]"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed } from "vue";
|
||||
|
||||
/**
|
||||
@name Widget/Flex.vue
|
||||
|
|
@ -15,21 +15,20 @@ import { computed } from 'vue';
|
|||
*/
|
||||
|
||||
const props = defineProps({
|
||||
flexClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default : "flex-start"
|
||||
},
|
||||
})
|
||||
flexClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "flex-start",
|
||||
},
|
||||
});
|
||||
|
||||
const flexClass = computed(() => {
|
||||
return `w-full flex ${props.flexClass}`;
|
||||
})
|
||||
|
||||
return `w-full flex ${props.flexClass}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-flex :class="[flexClass]">
|
||||
<div data-flex :class="[flexClass]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
/**
|
||||
@name Widget/Grid.vue
|
||||
@description This component is a basic container that can be used to wrap other components.
|
||||
|
|
|
|||
82
vuejs/client/src/components/Widget/Instance.vue
Normal file
82
vuejs/client/src/components/Widget/Instance.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import TitleCardContent from "@components/Title/CardContent.vue";
|
||||
import IconStatus from "@components/Icon/Status.vue";
|
||||
import ContentDetailList from "@components/Content/DetailList.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
|
||||
/**
|
||||
@name Widget/Instance.vue
|
||||
@description This component is an instance widget.
|
||||
This component is using the Container, TitleCardContent, IconStatus, ContentDetailList and ButtonGroup components.
|
||||
@example
|
||||
{
|
||||
id: "instance-1",
|
||||
title: "Instance 1",
|
||||
status: "success",
|
||||
details: [
|
||||
{ key: "Version", value: "1.0.0" },
|
||||
{ key: "Status", value: "Running" },
|
||||
{ key: "Created", value: "2021-01-01" },
|
||||
],
|
||||
buttons : [
|
||||
{
|
||||
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"},
|
||||
},
|
||||
]
|
||||
}
|
||||
@param {string} id
|
||||
@param {string} title
|
||||
@param {string} status
|
||||
@param {array} details - List of details to display
|
||||
@param {array} buttons - List of buttons to display
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
details: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: [],
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container :columns="{ pc: 12, tablet: 12, mobile: 12 }">
|
||||
<IconStatus :id="props.title" :status="props.status" />
|
||||
<TitleCardContent :title="props.title" />
|
||||
<ContentDetailList :details="props.details" />
|
||||
<ButtonGroup
|
||||
:id="props.id"
|
||||
:buttons="props.buttons"
|
||||
:groupClass="'justify-end align-center'"
|
||||
/>
|
||||
</Container>
|
||||
</template>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup>
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import TitleStat from "@components/Title/Stat.vue";
|
||||
import ContentStat from "@components/Content/Stat.vue";
|
||||
import SubtitleStat from "@components/Subtitle/Stat.vue";
|
||||
|
|
@ -65,7 +66,7 @@ const props = defineProps({
|
|||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="['stat-container', props.statClass]">
|
||||
<Container :columns="{ pc: 12, tablet: 12, mobile: 12 }">
|
||||
<!-- text -->
|
||||
<div
|
||||
:class="[
|
||||
|
|
@ -86,5 +87,5 @@ const props = defineProps({
|
|||
:iconName="props.iconName"
|
||||
:iconColor="props.iconColor"
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@
|
|||
"dashboard_banner_link_text_1": "Check BunkerWeb Panel",
|
||||
"dashboard_banner_link_text_2": "demo wep app !",
|
||||
"dashboard_banner_link_text_3": "website !",
|
||||
"dashboard_status_success": "status active or success.",
|
||||
"dashboard_status_error": "status inactive or error.",
|
||||
"dashboard_status_warning": "status warning or alert.",
|
||||
"dashboard_status_info": "status loading or waiting or unknown.",
|
||||
"action_send": "send {name}",
|
||||
"action_disable": "disable {name}",
|
||||
"action_enable": "enable {name}",
|
||||
|
|
@ -103,5 +107,11 @@
|
|||
"home_all_methods_included": "all methods included",
|
||||
"home_plugins": "plugins",
|
||||
"home_no_error": "no error",
|
||||
"home_errors_found": "errors found"
|
||||
"home_errors_found": "errors found",
|
||||
"instance_hostname": "hostname",
|
||||
"instances_method": "method",
|
||||
"instances_port": "port",
|
||||
"instances_status": "status",
|
||||
"instances_active": "active",
|
||||
"instances_inactive": "inactive"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,53 @@
|
|||
{
|
||||
"dashboard_logo_alt": "Image du logo BunkerWeb",
|
||||
"dashboard_logo_link_label": "Rediriger vers la page d'accueil",
|
||||
"dashboard_logo_alt": "BunkerWeb logo image",
|
||||
"dashboard_logo_link_label": "Redirect to home page",
|
||||
"dashboard_bw": "BunkerWeb",
|
||||
"dashboard_docs": "documents",
|
||||
"dashboard_docs": "docs",
|
||||
"dashboard_blog": "blog",
|
||||
"dashboard_privacy": "confidentialité",
|
||||
"dashboard_license": "licence",
|
||||
"dashboard_sitemap": "plan du site",
|
||||
"dashboard_default": "défautt",
|
||||
"dashboard_privacy": "privacy",
|
||||
"dashboard_license": "license",
|
||||
"dashboard_sitemap": "sitemap",
|
||||
"dashboard_default": "default",
|
||||
"dashboard_info": "info",
|
||||
"dashboard_filter": "filtres",
|
||||
"dashboard_advanced": "avancé",
|
||||
"dashboard_loading": "chargement",
|
||||
"dashboard_lang_dropdown_button_desc": "Basculer entre cacher/afficher le groupe de boutons radio (menu déroulant) pour changer la langue.",
|
||||
"dashboard_home": "accueil",
|
||||
"dashboard_filter": "filters",
|
||||
"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": "config globale",
|
||||
"dashboard_global_config": "global config",
|
||||
"dashboard_services": "services",
|
||||
"dashboard_configs": "configs",
|
||||
"dashboard_plugins": "plugins",
|
||||
"dashboard_jobs": "jobs",
|
||||
"dashboard_bans": "bans",
|
||||
"dashboard_actions": "actions",
|
||||
"dashboard_account": "compte",
|
||||
"dashboard_reports": "rapports",
|
||||
"dashboard_account": "account",
|
||||
"dashboard_reports": "reports",
|
||||
"dashboard_cache": "cache",
|
||||
"dashboard_logs": "journaux",
|
||||
"dashboard_feedback_toggle_sidebar": "Basculer la barre latérale de feedback.",
|
||||
"dashboard_feedback_close_sidebar": "Fermer la barre latérale de feedback.",
|
||||
"dashboard_feedback_title": "Feedback",
|
||||
"dashboard_feedback_subtitle": "Actions BunkerWeb",
|
||||
"dashboard_menu_toggle_sidebar": "Basculer la barre latérale du menu.",
|
||||
"dashboard_menu_close_sidebar": "Fermer la barre latérale du menu.",
|
||||
"dashboard_menu_twitter_label": "Rediriger vers le Twitter de BunkerWeb",
|
||||
"dashboard_menu_linkedin_label": "Rediriger vers le Linkedin de BunkerWeb",
|
||||
"dashboard_menu_discord_label": "Rediriger vers le Discord de BunkerWeb",
|
||||
"dashboard_menu_github_label": "Rediriger vers le Github de BunkerWeb",
|
||||
"dashboard_menu_plugins_title": "Pages des plugins",
|
||||
"dashboard_menu_plugins_none": "Vous voulez des plugins personnalisés ?",
|
||||
"dashboard_menu_plugins_none_doc": "consulter la documentation",
|
||||
"dashboard_menu_mode_light": "mode clair",
|
||||
"dashboard_menu_mode_dark": "mode sombre",
|
||||
"dashboard_menu_log_out": "se déconnecter",
|
||||
"dashboard_logs": "logs",
|
||||
"dashboard_feedback_toggle_sidebar": "Toggle feedback sidebar.",
|
||||
"dashboard_feedback_close_sidebar": "Close feedback sidebar.",
|
||||
"dashboard_feedback_title": "feedback",
|
||||
"dashboard_feedback_subtitle": "BunkerWeb actions",
|
||||
"dashboard_menu_toggle_sidebar": "Toggle menu sidebar.",
|
||||
"dashboard_menu_close_sidebar": "Close menu sidebar.",
|
||||
"dashboard_menu_twitter_label": "Redirect to BunkerWeb Twitter",
|
||||
"dashboard_menu_linkedin_label": "Redirect to BunkerWeb Linkedin",
|
||||
"dashboard_menu_discord_label": "Redirect to BunkerWeb Discord",
|
||||
"dashboard_menu_github_label": "Redirect to BunkerWeb Github",
|
||||
"dashboard_menu_plugins_title": "plugin pages",
|
||||
"dashboard_menu_mode_light": "light mode",
|
||||
"dashboard_menu_mode_dark": "dark mode",
|
||||
"dashboard_menu_log_out": "log out",
|
||||
"dashboard_ui": "ui",
|
||||
"dashboard_scheduler": "scheduler",
|
||||
"dashboard_autoconf": "autoconf",
|
||||
"dashboard_core": "core",
|
||||
"dashboard_global": "global",
|
||||
"dashboard_news_toggle_sidebar": "Basculer la barre latérale des nouvelles.",
|
||||
"dashboard_news_close_sidebar": "Fermer la barre latérale des nouvelles.",
|
||||
"dashboard_news_toggle_sidebar": "Toggle news sidebar.",
|
||||
"dashboard_news_close_sidebar": "Close news sidebar.",
|
||||
"dashboard_news_title": "news",
|
||||
"dashboard_news_subtitle": "Stay up to date !",
|
||||
"dashboard_news_fetch_error": "Impossible to retrieve news",
|
||||
|
|
@ -73,22 +72,10 @@
|
|||
"dashboard_banner_link_text_1": "Check BunkerWeb Panel",
|
||||
"dashboard_banner_link_text_2": "demo wep app !",
|
||||
"dashboard_banner_link_text_3": "website !",
|
||||
"home_version_is_latest": "is the latest version",
|
||||
"home_version_latest_version": "latest version",
|
||||
"home_version": "version",
|
||||
"home_internal": "internal",
|
||||
"home_external": "external",
|
||||
"home_card_link_label": "Redirect to page with related data.",
|
||||
"instances_hostname": "hostname",
|
||||
"instances_hostname_placeholder": "bwapi",
|
||||
"instances_method": "method",
|
||||
"instances_port": "port",
|
||||
"instances_port_placeholder": "5000",
|
||||
"instances_active": "Instance is active.",
|
||||
"instances_inactive": "Instance is inactive.",
|
||||
"instances_modal_delete_msg": "Are you sure to delete instance with hostname {hostname} ?",
|
||||
"instances_server_name": "server name",
|
||||
"instances_server_name_placeholder": "www.example.com",
|
||||
"dashboard_status_success": "status active or success.",
|
||||
"dashboard_status_error": "status inactive or error.",
|
||||
"dashboard_status_warning": "status warning or alert.",
|
||||
"dashboard_status_info": "status loading or waiting or unknown.",
|
||||
"action_send": "send {name}",
|
||||
"action_disable": "disable {name}",
|
||||
"action_enable": "enable {name}",
|
||||
|
|
@ -105,5 +92,26 @@
|
|||
"action_ping": "ping {name}",
|
||||
"action_reload": "reload {name}",
|
||||
"action_upload": "upload {name}",
|
||||
"action_delete_all": "delete all {name}"
|
||||
}
|
||||
"action_delete_all": "delete all {name}",
|
||||
"home_version": "version",
|
||||
"home_all_features_available": "all features are available",
|
||||
"home_upgrade_pro": "upgrade to pro",
|
||||
"home_pro": "pro",
|
||||
"home_free": "free",
|
||||
"home_version_number": "version number",
|
||||
"home_latest_version": "latest version",
|
||||
"home_upgrade_available": "upgrade available",
|
||||
"home_instances": "instances",
|
||||
"home_total_number": "total number",
|
||||
"home_services": "services",
|
||||
"home_all_methods_included": "all methods included",
|
||||
"home_plugins": "plugins",
|
||||
"home_no_error": "no error",
|
||||
"home_errors_found": "errors found",
|
||||
"instance_hostname": "hostname",
|
||||
"instances_method": "method",
|
||||
"instances_port": "port",
|
||||
"instances_status": "status",
|
||||
"instances_active": "active",
|
||||
"instances_inactive": "inactive"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ const builder = [
|
|||
{
|
||||
// we are starting with the top level container name
|
||||
// this can be a "card", "modal", "table"... etc
|
||||
type: "card",
|
||||
containerColumns: { pc: 12, tablet: 12, mobile: 12 },
|
||||
// Each widget need a name (here type) and associated data
|
||||
// We need to send specific data for each widget type
|
||||
|
|
@ -71,7 +70,6 @@ const builder = [
|
|||
containerClass: "", // tailwind css grid class (items-start, ...)
|
||||
containerColumns: { pc: 12, tablet: 12, mobile: 12 },
|
||||
// container title
|
||||
title: "My awesome card",
|
||||
// Each widget need a name (here type) and associated data
|
||||
// We need to send specific data for each widget type
|
||||
widgets: [
|
||||
|
|
@ -93,8 +91,8 @@ const builder = [
|
|||
{
|
||||
type: "ContentStat",
|
||||
data: {
|
||||
value: "20",
|
||||
valueClass: "col-span-12",
|
||||
stat: "20",
|
||||
statClass: "col-span-12",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -106,12 +104,58 @@ const builder = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// we are starting with the top level container name
|
||||
// this can be a "card", "modal", "table"... etc
|
||||
type: "card",
|
||||
containerClass: "", // tailwind css grid class (items-start, ...)
|
||||
containerColumns: { pc: 12, tablet: 12, mobile: 12 },
|
||||
// container title
|
||||
// Each widget need a name (here type) and associated data
|
||||
// We need to send specific data for each widget type
|
||||
widgets: [
|
||||
{
|
||||
type: "Instance",
|
||||
data: {
|
||||
details: [
|
||||
{ key: "HOSTNAME", value: "www.example.com" },
|
||||
{ key: "METHOD", value: "UI" },
|
||||
{ key: "PORT", value: "1084" },
|
||||
{ key: "STATUS", value: "active" },
|
||||
],
|
||||
status: "success",
|
||||
title: "www.example.com",
|
||||
buttons: [
|
||||
{
|
||||
text: "reload",
|
||||
color: "edit",
|
||||
size: "normal",
|
||||
},
|
||||
{
|
||||
text: "Stop",
|
||||
color: "error",
|
||||
size: "normal",
|
||||
eventAttr: {
|
||||
store: "modal",
|
||||
default: "close",
|
||||
value: "open",
|
||||
target: "modal_id",
|
||||
valueExpanded: "open",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-secondary flex flex-col items-center justify-center h-full">
|
||||
<div style="width: 600px">
|
||||
<div
|
||||
class="bg-secondary flex flex-col items-center justify-center h-full gap-4"
|
||||
>
|
||||
<div style="width: 600px" class="gap-4 grid grid-cols-12">
|
||||
<Builder :builder="builder" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
55
vuejs/client/src/pages/instances/Instances.vue
Normal file
55
vuejs/client/src/pages/instances/Instances.vue
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount } from "vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
import Builder from "@components/Builder.vue";
|
||||
|
||||
/**
|
||||
@name Page/Home.vue
|
||||
@description This component is the home page.
|
||||
This page displays an overview of multiple stats related to BunkerWeb.
|
||||
*/
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
// const data = [
|
||||
// {
|
||||
// type: "Instance",
|
||||
// data: {
|
||||
// details: [
|
||||
// { key: <instances_hostname="hostname">, value: "www.example.com" },
|
||||
// { key: <instances_method="method">, value: <dashboard_ui> or <dashboard_scheduler>...},
|
||||
// { key: <instances_port="port">, value: "1084" },
|
||||
// { key: <instances_status="status">, value: <instances_active="active"> or <instances_inactive="inactive"> },
|
||||
// ],
|
||||
// status: "success",
|
||||
// title: "www.example.com",
|
||||
// buttons: [
|
||||
// {
|
||||
// text: <action_*>,
|
||||
// color: "edit",
|
||||
// size: "normal",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout>
|
||||
<Builder v-if="home.builder" :builder="home.builder" />
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
22
vuejs/client/src/pages/instances/index.html
Normal file
22
vuejs/client/src/pages/instances/index.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!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", "link" : "/services", "containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat", "link": "https://github.com/bunkerity/bunkerweb","data":{"title":"home_version","subtitle":"home_all_features_available","subtitleColor":"success","value":"home_pro","iconName":"crown","iconColor":"amber"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_version_number","subtitle":"home_latest_version","subtitleColor":"success","value":"1.5.7","iconName":"wire","iconColor":"teal"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_instances","subtitle":"home_total_number","subtitleColor":"info", "value":"1","iconName":"box","iconColor":"dark"}}]}, {"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_services","subtitle":"home_all_methods_included","subtitleColor":"info","value":"2","iconName":"disk","iconColor":"orange"}}]}, {"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_plugins","subtitle":"home_no_error","subtitleColor":"success","value":"42","iconName":"puzzle","iconColor":"yellow"}}]}]'>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="instances.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
11
vuejs/client/src/pages/instances/instances.js
Normal file
11
vuejs/client/src/pages/instances/instances.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { getI18n } from "@utils/lang.js";
|
||||
import Instances from "./Instances.vue";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
createApp(Instances)
|
||||
.use(pinia)
|
||||
.use(getI18n(["dashboard", "action", "instances"]))
|
||||
.mount("#app");
|
||||
|
|
@ -14,7 +14,7 @@ import fr from "@lang/fr.json" assert { type: "json" };
|
|||
const availablesLangs = ["en", "fr"];
|
||||
|
||||
function getAllLang() {
|
||||
return { en: en, fr : fr };
|
||||
return { en: en, fr: fr };
|
||||
}
|
||||
|
||||
function getAllLangCurrPage(pagesArr) {
|
||||
|
|
@ -42,6 +42,8 @@ export function getI18n(pagesArr = []) {
|
|||
fallbackLocale: "en",
|
||||
messages, // set locale messages
|
||||
availableLocales: availablesLangs,
|
||||
fallbackWarn: false,
|
||||
missingWarn: false,
|
||||
});
|
||||
|
||||
return i18n;
|
||||
|
|
@ -68,7 +70,7 @@ export function getLocalLang() {
|
|||
navigator.languages &&
|
||||
navigator.languages > 0 &&
|
||||
availablesLangs.indexOf(
|
||||
navigator.languages[0].split("-")[0].toLowerCase(),
|
||||
navigator.languages[0].split("-")[0].toLowerCase()
|
||||
) !== -1
|
||||
) {
|
||||
return navigator.languages[0].split("-")[0].toLowerCase();
|
||||
|
|
|
|||
Loading…
Reference in a new issue