@xen-orchestra/web-core 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/env.d.ts +1 -0
- package/lib/assets/css/_typography.pcss +1 -0
- package/lib/assets/css/typography/_utils.pcss +6 -0
- package/lib/assets/no-result.svg +81 -0
- package/lib/assets/under-construction.svg +195 -0
- package/lib/components/CardNumbers.vue +94 -0
- package/lib/components/DonutChart.vue +92 -0
- package/lib/components/LegendTitle.vue +31 -0
- package/lib/components/StatusPill.vue +2 -2
- package/lib/components/UiCard.vue +29 -0
- package/lib/components/UiLegend.vue +68 -0
- package/lib/components/UiTag.vue +3 -7
- package/lib/components/backdrop/Backdrop.vue +11 -0
- package/lib/components/card/CardSubtitle.vue +24 -0
- package/lib/components/card/CardTitle.vue +52 -0
- package/lib/components/cell-object/CellObject.vue +54 -0
- package/lib/components/cell-text/CellText.vue +40 -0
- package/lib/components/chip/UiChip.vue +5 -6
- package/lib/components/console/RemoteConsole.vue +2 -2
- package/lib/components/divider/Divider.vue +25 -0
- package/lib/components/dropdown/DropdownItem.vue +1 -4
- package/lib/components/dropdown/DropdownTitle.vue +7 -3
- package/lib/components/head-bar/HeadBar.vue +78 -0
- package/lib/components/icon/VmIcon.vue +1 -1
- package/lib/components/input/UiInput.vue +133 -0
- package/lib/components/menu/MenuItem.vue +1 -1
- package/lib/components/menu/MenuList.vue +5 -5
- package/lib/components/object-link/ObjectLink.vue +87 -0
- package/lib/components/search-bar/SearchBar.vue +60 -0
- package/lib/components/stacked-bar/StackedBar.vue +49 -0
- package/lib/components/stacked-bar/StackedBarSegment.vue +67 -0
- package/lib/components/state-hero/ComingSoonHero.vue +9 -0
- package/lib/components/state-hero/LoadingHero.vue +9 -0
- package/lib/components/state-hero/ObjectNotFoundHero.vue +13 -0
- package/lib/components/state-hero/StateHero.vue +53 -0
- package/lib/components/tab/TabList.vue +1 -0
- package/lib/components/table/ColumnTitle.vue +150 -0
- package/lib/components/table/UiTable.vue +64 -0
- package/lib/components/task/QuickTaskButton.vue +62 -0
- package/lib/components/task/QuickTaskItem.vue +91 -0
- package/lib/components/task/QuickTaskList.vue +48 -0
- package/lib/components/task/QuickTaskPanel.vue +65 -0
- package/lib/components/task/QuickTaskTabBar.vue +46 -0
- package/lib/components/tooltip/TooltipList.vue +0 -2
- package/lib/components/tree/TreeItem.vue +8 -8
- package/lib/components/tree/TreeItemLabel.vue +32 -16
- package/lib/composables/tab-list.composable.ts +33 -0
- package/lib/composables/tree/branch.ts +5 -5
- package/lib/composables/tree/types.ts +1 -1
- package/lib/i18n.ts +86 -1
- package/lib/layouts/CoreLayout.vue +6 -106
- package/lib/locales/de.json +37 -4
- package/lib/locales/en.json +66 -13
- package/lib/locales/fa.json +46 -0
- package/lib/locales/fr.json +66 -13
- package/lib/types/tab.type.ts +17 -0
- package/lib/types/task.type.ts +13 -0
- package/lib/types/utility.type.ts +2 -0
- package/lib/utils/if-else.utils.ts +1 -1
- package/lib/utils/open-url.utils.ts +3 -0
- package/package.json +16 -8
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="{ mobile: isMobile }" class="vts-quick-tasks">
|
|
3
|
+
<QuickTaskTabBar
|
|
4
|
+
v-model="currentTab"
|
|
5
|
+
:failure-count="failureTasks.length"
|
|
6
|
+
:loading
|
|
7
|
+
:pending-count="pendingTasks.length"
|
|
8
|
+
:success-count="successTasks.length"
|
|
9
|
+
/>
|
|
10
|
+
<QuickTaskList :loading :tasks="currentTasks" />
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts" setup>
|
|
15
|
+
import QuickTaskList from '@core/components/task/QuickTaskList.vue'
|
|
16
|
+
import QuickTaskTabBar from '@core/components/task/QuickTaskTabBar.vue'
|
|
17
|
+
import { useUiStore } from '@core/stores/ui.store'
|
|
18
|
+
import type { Task, TaskTab } from '@core/types/task.type'
|
|
19
|
+
import { computed, ref } from 'vue'
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
tasks: Task[]
|
|
23
|
+
loading?: boolean
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const { isMobile } = useUiStore()
|
|
27
|
+
|
|
28
|
+
const currentTab = ref<TaskTab>('pending')
|
|
29
|
+
|
|
30
|
+
const pendingTasks = computed(() => props.tasks.filter(task => task.status === 'pending'))
|
|
31
|
+
const successTasks = computed(() => props.tasks.filter(task => task.status === 'success'))
|
|
32
|
+
const failureTasks = computed(() => props.tasks.filter(task => task.status === 'failure'))
|
|
33
|
+
|
|
34
|
+
const currentTasks = computed(() => {
|
|
35
|
+
switch (currentTab.value) {
|
|
36
|
+
case 'pending':
|
|
37
|
+
return pendingTasks.value
|
|
38
|
+
case 'success':
|
|
39
|
+
return successTasks.value
|
|
40
|
+
case 'failure':
|
|
41
|
+
return failureTasks.value
|
|
42
|
+
case 'all':
|
|
43
|
+
return props.tasks
|
|
44
|
+
default:
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style lang="postcss" scoped>
|
|
51
|
+
.vts-quick-tasks {
|
|
52
|
+
width: fit-content;
|
|
53
|
+
min-width: 65rem;
|
|
54
|
+
border-radius: 0.8rem;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
border: 0.1rem solid var(--color-grey-500);
|
|
57
|
+
z-index: 1001;
|
|
58
|
+
|
|
59
|
+
&.mobile {
|
|
60
|
+
width: 100%;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
border-radius: 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TabList :disabled="loading">
|
|
3
|
+
<TabItem v-bind="tabs.pending.bindings">
|
|
4
|
+
{{ $t('tasks.quick-view.in-progress') }}
|
|
5
|
+
<UiCounter v-if="pendingCount !== undefined" :value="pendingCount" color="info" />
|
|
6
|
+
</TabItem>
|
|
7
|
+
<TabItem v-bind="tabs.success.bindings">
|
|
8
|
+
{{ $t('tasks.quick-view.done') }}
|
|
9
|
+
<UiCounter v-if="successCount !== undefined" :value="successCount" color="success" />
|
|
10
|
+
</TabItem>
|
|
11
|
+
<TabItem v-bind="tabs.failure.bindings">
|
|
12
|
+
{{ $t('tasks.quick-view.failed') }}
|
|
13
|
+
<UiCounter v-if="failureCount !== undefined" :value="failureCount" color="danger" />
|
|
14
|
+
</TabItem>
|
|
15
|
+
<Divider type="tab" />
|
|
16
|
+
<TabItem v-bind="tabs.all.bindings">
|
|
17
|
+
{{ $t('tasks.quick-view.all') }}
|
|
18
|
+
</TabItem>
|
|
19
|
+
<!--
|
|
20
|
+
TODO
|
|
21
|
+
<UiButton :right-icon="faAngleRight" class="see-all" level="tertiary" size="extra-small">
|
|
22
|
+
{{ $t('see-all') }}
|
|
23
|
+
</UiButton>
|
|
24
|
+
-->
|
|
25
|
+
</TabList>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script lang="ts" setup>
|
|
29
|
+
import Divider from '@core/components/divider/Divider.vue'
|
|
30
|
+
import TabItem from '@core/components/tab/TabItem.vue'
|
|
31
|
+
import TabList from '@core/components/tab/TabList.vue'
|
|
32
|
+
import UiCounter from '@core/components/UiCounter.vue'
|
|
33
|
+
import { useTabList } from '@core/composables/tab-list.composable'
|
|
34
|
+
import type { TaskTab } from '@core/types/task.type'
|
|
35
|
+
|
|
36
|
+
defineProps<{
|
|
37
|
+
loading?: boolean
|
|
38
|
+
pendingCount?: number
|
|
39
|
+
successCount?: number
|
|
40
|
+
failureCount?: number
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
const currentTab = defineModel<TaskTab>({ required: true })
|
|
44
|
+
|
|
45
|
+
const { tabs } = useTabList<TaskTab>(['pending', 'success', 'failure', 'all'], currentTab)
|
|
46
|
+
</script>
|
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
<template>
|
|
3
3
|
<li class="tree-item">
|
|
4
4
|
<slot />
|
|
5
|
-
<slot v-if="
|
|
5
|
+
<slot v-if="expanded" name="sublist" />
|
|
6
6
|
</li>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script lang="ts" setup>
|
|
10
|
-
import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN } from '@core/utils/injection-keys.util'
|
|
11
|
+
import { onBeforeMount, onBeforeUpdate, provide, ref, toRef, useSlots } from 'vue'
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
expanded?: boolean
|
|
15
|
+
}>()
|
|
13
16
|
|
|
14
17
|
defineSlots<{
|
|
15
18
|
default: () => void
|
|
16
19
|
sublist: () => void
|
|
17
20
|
}>()
|
|
18
21
|
|
|
19
|
-
const [isExpanded, toggle] = useToggle(true)
|
|
20
|
-
|
|
21
22
|
const hasChildren = ref(false)
|
|
22
23
|
|
|
23
24
|
const updateHasChildren = () => {
|
|
@@ -29,6 +30,5 @@ onBeforeMount(() => updateHasChildren())
|
|
|
29
30
|
onBeforeUpdate(() => updateHasChildren())
|
|
30
31
|
|
|
31
32
|
provide(IK_TREE_ITEM_HAS_CHILDREN, hasChildren)
|
|
32
|
-
provide(
|
|
33
|
-
provide(IK_TREE_ITEM_EXPANDED, isExpanded)
|
|
33
|
+
provide(IK_TREE_ITEM_EXPANDED, toRef(props, 'expanded'))
|
|
34
34
|
</script>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!-- v1.1 -->
|
|
2
2
|
<template>
|
|
3
|
-
<RouterLink v-slot="{ isExactActive, href, navigate }" :to="route" custom>
|
|
3
|
+
<RouterLink v-slot="{ isExactActive, isActive, href, navigate }" :to="route" custom>
|
|
4
4
|
<div
|
|
5
|
-
:class="isExactActive ? 'exact-active' : active ? 'active' : undefined"
|
|
5
|
+
:class="isExactActive ? 'exact-active' : active || isActive ? 'active' : undefined"
|
|
6
6
|
class="tree-item-label"
|
|
7
7
|
v-bind="$attrs"
|
|
8
8
|
>
|
|
@@ -17,16 +17,17 @@
|
|
|
17
17
|
<ButtonIcon
|
|
18
18
|
v-if="hasToggle"
|
|
19
19
|
v-tooltip="isExpanded ? $t('core.close') : $t('core.open')"
|
|
20
|
+
class="toggle"
|
|
20
21
|
:icon="isExpanded ? faAngleDown : faAngleRight"
|
|
21
22
|
size="small"
|
|
22
|
-
@click="toggle
|
|
23
|
+
@click="emit('toggle')"
|
|
23
24
|
/>
|
|
24
|
-
<
|
|
25
|
+
<div v-else class="h-line" />
|
|
25
26
|
<a v-tooltip="{ selector: '.text' }" :href class="link typo p2-medium" @click="navigate">
|
|
26
27
|
<slot name="icon">
|
|
27
28
|
<UiIcon :icon class="icon" />
|
|
28
29
|
</slot>
|
|
29
|
-
<div class="text">
|
|
30
|
+
<div class="text text-ellipsis">
|
|
30
31
|
<slot />
|
|
31
32
|
</div>
|
|
32
33
|
</a>
|
|
@@ -40,27 +41,28 @@ import ButtonIcon from '@core/components/button/ButtonIcon.vue'
|
|
|
40
41
|
import UiIcon from '@core/components/icon/UiIcon.vue'
|
|
41
42
|
import TreeLine from '@core/components/tree/TreeLine.vue'
|
|
42
43
|
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
43
|
-
import {
|
|
44
|
-
IK_TREE_ITEM_EXPANDED,
|
|
45
|
-
IK_TREE_ITEM_HAS_CHILDREN,
|
|
46
|
-
IK_TREE_ITEM_TOGGLE,
|
|
47
|
-
IK_TREE_LIST_DEPTH,
|
|
48
|
-
} from '@core/utils/injection-keys.util'
|
|
44
|
+
import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN, IK_TREE_LIST_DEPTH } from '@core/utils/injection-keys.util'
|
|
49
45
|
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
|
|
50
46
|
import { faAngleDown, faAngleRight } from '@fortawesome/free-solid-svg-icons'
|
|
51
47
|
import { inject, ref } from 'vue'
|
|
52
48
|
import type { RouteLocationRaw } from 'vue-router'
|
|
53
49
|
|
|
50
|
+
defineOptions({
|
|
51
|
+
inheritAttrs: false,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
54
|
defineProps<{
|
|
55
55
|
icon?: IconDefinition
|
|
56
56
|
route: RouteLocationRaw
|
|
57
57
|
active?: boolean
|
|
58
|
-
|
|
58
|
+
}>()
|
|
59
|
+
|
|
60
|
+
const emit = defineEmits<{
|
|
61
|
+
toggle: []
|
|
59
62
|
}>()
|
|
60
63
|
|
|
61
64
|
const hasToggle = inject(IK_TREE_ITEM_HAS_CHILDREN, ref(false))
|
|
62
65
|
|
|
63
|
-
const toggle = inject(IK_TREE_ITEM_TOGGLE, () => undefined)
|
|
64
66
|
const isExpanded = inject(IK_TREE_ITEM_EXPANDED, ref(true))
|
|
65
67
|
|
|
66
68
|
const depth = inject(IK_TREE_LIST_DEPTH, 0)
|
|
@@ -116,13 +118,27 @@ const depth = inject(IK_TREE_LIST_DEPTH, 0)
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
.text {
|
|
119
|
-
overflow: hidden;
|
|
120
|
-
white-space: nowrap;
|
|
121
|
-
text-overflow: ellipsis;
|
|
122
121
|
padding-inline-end: 0.4rem;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
.icon {
|
|
126
125
|
font-size: 1.6rem;
|
|
127
126
|
}
|
|
127
|
+
|
|
128
|
+
.h-line {
|
|
129
|
+
width: 2rem;
|
|
130
|
+
border-bottom: 0.1rem solid var(--color-purple-base);
|
|
131
|
+
margin-left: -0.4rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* Increase the size of the clickable area,
|
|
136
|
+
* without changing the padding of the ButtonIcon component
|
|
137
|
+
*/
|
|
138
|
+
.toggle::after {
|
|
139
|
+
content: '';
|
|
140
|
+
position: absolute;
|
|
141
|
+
inset: 0;
|
|
142
|
+
transform: scale(1.5, 2);
|
|
143
|
+
}
|
|
128
144
|
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Tab, TabList } from '@core/types/tab.type'
|
|
2
|
+
import { computed, type MaybeRefOrGetter, type Ref, toRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useTabList<TName extends string>(names: TName[], initialTab?: MaybeRefOrGetter<TName>) {
|
|
5
|
+
const currentTab = toRef(initialTab) as Ref<TName | undefined>
|
|
6
|
+
|
|
7
|
+
const activate = (name: TName | undefined) => {
|
|
8
|
+
currentTab.value = name
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const isActive = (name: TName) => currentTab.value === name
|
|
12
|
+
|
|
13
|
+
const tabs = computed(() => {
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
names.map(name => [
|
|
16
|
+
name,
|
|
17
|
+
{
|
|
18
|
+
isActive: isActive(name),
|
|
19
|
+
activate: () => activate(name),
|
|
20
|
+
bindings: {
|
|
21
|
+
onClick: (event: MouseEvent) => {
|
|
22
|
+
event.preventDefault()
|
|
23
|
+
activate(name)
|
|
24
|
+
},
|
|
25
|
+
active: isActive(name),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
])
|
|
29
|
+
) as Record<TName, Tab>
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return { currentTab, activate, isActive, tabs } as TabList<TName>
|
|
33
|
+
}
|
|
@@ -40,7 +40,11 @@ export class Branch<
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
get failsFilterDownwards(): boolean {
|
|
43
|
-
|
|
43
|
+
if (this.passesFilter !== undefined) {
|
|
44
|
+
return !this.passesFilter
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this.rawChildren.length > 0 && this.rawChildren.every(child => child.failsFilterDownwards)
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
get isExcluded() {
|
|
@@ -48,10 +52,6 @@ export class Branch<
|
|
|
48
52
|
return true
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
if (!this.hasChildren) {
|
|
52
|
-
return true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
55
|
if (this.passesFilterUpwards || this.passesFilterDownwards) {
|
|
56
56
|
return false
|
|
57
57
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useTree } from '@core/composables/tree.composable'
|
|
2
1
|
import type { Branch } from '@core/composables/tree/branch'
|
|
3
2
|
import type { BranchDefinition } from '@core/composables/tree/branch-definition'
|
|
4
3
|
import type { Leaf } from '@core/composables/tree/leaf'
|
|
5
4
|
import type { LeafDefinition } from '@core/composables/tree/leaf-definition'
|
|
6
5
|
import type { TreeNodeBase } from '@core/composables/tree/tree-node-base'
|
|
6
|
+
import { useTree } from '@core/composables/tree.composable'
|
|
7
7
|
|
|
8
8
|
export type TreeNodeId = string | number
|
|
9
9
|
|
package/lib/i18n.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createI18n } from 'vue-i18n'
|
|
2
1
|
import messages from '@intlify/unplugin-vue-i18n/messages'
|
|
2
|
+
import { createI18n } from 'vue-i18n'
|
|
3
3
|
|
|
4
4
|
interface Locales {
|
|
5
5
|
[key: string]: {
|
|
@@ -21,12 +21,25 @@ export const locales: Locales = {
|
|
|
21
21
|
code: 'de',
|
|
22
22
|
name: 'Deutsch',
|
|
23
23
|
},
|
|
24
|
+
fa: {
|
|
25
|
+
code: 'fa',
|
|
26
|
+
name: 'Persian',
|
|
27
|
+
},
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export default createI18n({
|
|
27
31
|
locale: localStorage.getItem('lang') ?? 'en',
|
|
28
32
|
fallbackLocale: 'en',
|
|
29
33
|
messages,
|
|
34
|
+
missing: (locale, key, vm) => {
|
|
35
|
+
if (!import.meta.env.DEV) {
|
|
36
|
+
return key
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.warn(`i18n key not found: ${key}`, `Used in ${vm?.type.__name ?? 'unknown component'}`)
|
|
40
|
+
|
|
41
|
+
return `🌎❗⟨${key}⟩`
|
|
42
|
+
},
|
|
30
43
|
datetimeFormats: {
|
|
31
44
|
en: {
|
|
32
45
|
date_short: {
|
|
@@ -154,5 +167,77 @@ export default createI18n({
|
|
|
154
167
|
minute: '2-digit',
|
|
155
168
|
},
|
|
156
169
|
},
|
|
170
|
+
fa: {
|
|
171
|
+
date_short: {
|
|
172
|
+
year: 'numeric',
|
|
173
|
+
month: 'numeric',
|
|
174
|
+
day: 'numeric',
|
|
175
|
+
},
|
|
176
|
+
date_medium: {
|
|
177
|
+
year: 'numeric',
|
|
178
|
+
month: 'short',
|
|
179
|
+
day: 'numeric',
|
|
180
|
+
},
|
|
181
|
+
date_long: {
|
|
182
|
+
year: 'numeric',
|
|
183
|
+
month: 'long',
|
|
184
|
+
day: 'numeric',
|
|
185
|
+
},
|
|
186
|
+
datetime_short: {
|
|
187
|
+
year: 'numeric',
|
|
188
|
+
month: 'numeric',
|
|
189
|
+
day: 'numeric',
|
|
190
|
+
hour: '2-digit',
|
|
191
|
+
minute: '2-digit',
|
|
192
|
+
},
|
|
193
|
+
datetime_medium: {
|
|
194
|
+
year: 'numeric',
|
|
195
|
+
month: 'short',
|
|
196
|
+
day: 'numeric',
|
|
197
|
+
hour: '2-digit',
|
|
198
|
+
minute: '2-digit',
|
|
199
|
+
},
|
|
200
|
+
datetime_long: {
|
|
201
|
+
year: 'numeric',
|
|
202
|
+
month: 'long',
|
|
203
|
+
day: 'numeric',
|
|
204
|
+
hour: '2-digit',
|
|
205
|
+
minute: '2-digit',
|
|
206
|
+
},
|
|
207
|
+
time: {
|
|
208
|
+
hour: '2-digit',
|
|
209
|
+
minute: '2-digit',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
numberFormats: {
|
|
214
|
+
en: {
|
|
215
|
+
percent: {
|
|
216
|
+
style: 'percent',
|
|
217
|
+
minimumFractionDigits: 0,
|
|
218
|
+
maximumFractionDigits: 2,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
fr: {
|
|
222
|
+
percent: {
|
|
223
|
+
style: 'percent',
|
|
224
|
+
minimumFractionDigits: 0,
|
|
225
|
+
maximumFractionDigits: 2,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
de: {
|
|
229
|
+
percent: {
|
|
230
|
+
style: 'percent',
|
|
231
|
+
minimumFractionDigits: 0,
|
|
232
|
+
maximumFractionDigits: 2,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
fa: {
|
|
236
|
+
percent: {
|
|
237
|
+
style: 'percent',
|
|
238
|
+
minimumFractionDigits: 0,
|
|
239
|
+
maximumFractionDigits: 2,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
157
242
|
},
|
|
158
243
|
})
|
|
@@ -14,11 +14,7 @@
|
|
|
14
14
|
<slot name="app-header" />
|
|
15
15
|
</header>
|
|
16
16
|
<div class="container">
|
|
17
|
-
<
|
|
18
|
-
v-if="sidebarStore.isExpanded && !sidebarStore.isLocked"
|
|
19
|
-
class="sidebar-overlay"
|
|
20
|
-
@click="sidebarStore.toggleExpand(false)"
|
|
21
|
-
/>
|
|
17
|
+
<Backdrop v-if="sidebarStore.isExpanded && !sidebarStore.isLocked" @click="sidebarStore.toggleExpand(false)" />
|
|
22
18
|
<LayoutSidebar class="sidebar">
|
|
23
19
|
<template #header>
|
|
24
20
|
<slot name="sidebar-header" />
|
|
@@ -30,76 +26,25 @@
|
|
|
30
26
|
<slot name="sidebar-footer" />
|
|
31
27
|
</template>
|
|
32
28
|
</LayoutSidebar>
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
</header>
|
|
37
|
-
<main class="main">
|
|
38
|
-
<div class="content">
|
|
39
|
-
<slot name="content" />
|
|
40
|
-
</div>
|
|
41
|
-
<div v-if="isPanelVisible" :class="{ mobile: uiStore.isMobile }" class="panel">
|
|
42
|
-
<header v-if="$slots['panel-header'] || uiStore.isMobile" class="panel-header">
|
|
43
|
-
<UiButtonIcon
|
|
44
|
-
v-if="uiStore.isMobile"
|
|
45
|
-
:icon="faAngleLeft"
|
|
46
|
-
class="panel-close-icon"
|
|
47
|
-
@click="panelStore.close()"
|
|
48
|
-
/>
|
|
49
|
-
<slot name="panel-header" />
|
|
50
|
-
</header>
|
|
51
|
-
<div v-if="$slots['panel-content']" class="panel-content">
|
|
52
|
-
<slot name="panel-content" />
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</main>
|
|
56
|
-
</div>
|
|
29
|
+
<main class="main-container">
|
|
30
|
+
<slot name="content" />
|
|
31
|
+
</main>
|
|
57
32
|
</div>
|
|
58
33
|
</div>
|
|
59
34
|
</template>
|
|
60
35
|
|
|
61
36
|
<script lang="ts" setup>
|
|
37
|
+
import Backdrop from '@core/components/backdrop/Backdrop.vue'
|
|
62
38
|
import UiButtonIcon from '@core/components/button/ButtonIcon.vue'
|
|
63
39
|
import LayoutSidebar from '@core/components/layout/LayoutSidebar.vue'
|
|
64
40
|
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
65
|
-
import { usePanelStore } from '@core/stores/panel.store'
|
|
66
41
|
import { useSidebarStore } from '@core/stores/sidebar.store'
|
|
67
|
-
import {
|
|
68
|
-
import { faAngleDoubleLeft, faAngleLeft, faBars } from '@fortawesome/free-solid-svg-icons'
|
|
69
|
-
import { computed } from 'vue'
|
|
42
|
+
import { faAngleDoubleLeft, faBars } from '@fortawesome/free-solid-svg-icons'
|
|
70
43
|
|
|
71
44
|
const sidebarStore = useSidebarStore()
|
|
72
|
-
const panelStore = usePanelStore()
|
|
73
|
-
const uiStore = useUiStore()
|
|
74
|
-
|
|
75
|
-
const slots = defineSlots<{
|
|
76
|
-
'app-logo'(): any
|
|
77
|
-
'app-header'(): any
|
|
78
|
-
'sidebar-header'(): any
|
|
79
|
-
'sidebar-content'(): any
|
|
80
|
-
'sidebar-footer'(): any
|
|
81
|
-
'content-header'(): any
|
|
82
|
-
content(): any
|
|
83
|
-
'panel-header'(): any
|
|
84
|
-
'panel-content'(): any
|
|
85
|
-
}>()
|
|
86
|
-
|
|
87
|
-
const isPanelVisible = computed(() => {
|
|
88
|
-
if (!slots['panel-header'] && !slots['panel-content']) {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return panelStore.isExpanded
|
|
93
|
-
})
|
|
94
45
|
</script>
|
|
95
46
|
|
|
96
47
|
<style lang="postcss" scoped>
|
|
97
|
-
.sidebar-overlay {
|
|
98
|
-
position: fixed;
|
|
99
|
-
inset: 0;
|
|
100
|
-
z-index: 1000;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
48
|
.core-layout {
|
|
104
49
|
display: flex;
|
|
105
50
|
height: 100dvh;
|
|
@@ -132,51 +77,6 @@ const isPanelVisible = computed(() => {
|
|
|
132
77
|
overflow: auto;
|
|
133
78
|
display: flex;
|
|
134
79
|
flex-direction: column;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.main {
|
|
138
|
-
background-color: var(--background-color-secondary);
|
|
139
|
-
display: flex;
|
|
140
|
-
flex: 1;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.content {
|
|
144
|
-
padding: 0.8rem;
|
|
145
|
-
flex: 1;
|
|
146
|
-
border-right: 0.1rem solid var(--color-grey-500);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.panel {
|
|
150
|
-
display: flex;
|
|
151
|
-
flex-direction: column;
|
|
152
|
-
width: 40rem;
|
|
153
80
|
background-color: var(--background-color-secondary);
|
|
154
|
-
|
|
155
|
-
&.mobile {
|
|
156
|
-
width: 100%;
|
|
157
|
-
position: fixed;
|
|
158
|
-
inset: 0 0 0 auto;
|
|
159
|
-
z-index: 1000;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.panel-header {
|
|
164
|
-
display: flex;
|
|
165
|
-
align-items: center;
|
|
166
|
-
padding: 0.4rem 1.6rem;
|
|
167
|
-
background-color: var(--background-color-primary);
|
|
168
|
-
border-bottom: 0.1rem solid var(--color-grey-500);
|
|
169
|
-
min-height: 4.8rem;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.panel-close-icon {
|
|
173
|
-
margin-right: auto;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.panel-content {
|
|
177
|
-
flex: 1;
|
|
178
|
-
padding: 0.8rem;
|
|
179
|
-
overflow: auto;
|
|
180
|
-
min-height: 0;
|
|
181
81
|
}
|
|
182
82
|
</style>
|
package/lib/locales/de.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"alarms": "Alarme",
|
|
3
|
+
|
|
4
|
+
"bytes.ki": "KiB",
|
|
5
|
+
"bytes.mi": "MiB",
|
|
6
|
+
"bytes.gi": "GiB",
|
|
7
|
+
|
|
8
|
+
"coming-soon": "Bald verfügbar!",
|
|
9
|
+
"console": "Konsole",
|
|
10
|
+
|
|
11
|
+
"core.close": "Schließen",
|
|
12
|
+
|
|
13
|
+
"core.select.all": "Alles auswählen",
|
|
14
|
+
"core.select.none": "Alle abwählen",
|
|
15
|
+
|
|
16
|
+
"dark-mode.enable": "Aktivieren Sie den Dunkelmodus",
|
|
17
|
+
"dark-mode.disable": "Deaktivieren Sie den Dunkelmodus",
|
|
18
|
+
"dark-mode.auto": "Automatischer Dunkelmodus",
|
|
19
|
+
|
|
20
|
+
"dashboard": "Dashboard",
|
|
21
|
+
"documentation-name": "{name} Dokumentation",
|
|
22
|
+
"hosts": "Hosts",
|
|
23
|
+
"loading-in-progress": "Ladevorgang läuft…",
|
|
24
|
+
"log-out": "Abmelden",
|
|
25
|
+
"master": "Primärer Host",
|
|
26
|
+
"n-vms": "1 VM | {n} VMs",
|
|
27
|
+
"network": "Netzwerk",
|
|
28
|
+
"object-not-found": "Objekt {id} wurde nicht gefunden…",
|
|
29
|
+
"power-on-for-console": "Konsole ist nach Start der VM verfügbar",
|
|
30
|
+
"stats": "Statistiken",
|
|
31
|
+
"storage": "Speicher",
|
|
32
|
+
"support-name": "{name} pro support",
|
|
33
|
+
"system": "System",
|
|
34
|
+
|
|
35
|
+
"tasks": "Aufgaben",
|
|
36
|
+
"tasks.quick-view": "Schnellansicht der Aufgaben",
|
|
37
|
+
|
|
38
|
+
"vms": "VMs"
|
|
6
39
|
}
|