@xen-orchestra/web-core 0.0.3 → 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/lib/assets/css/_typography.pcss +1 -0
- package/lib/assets/css/typography/_utils.pcss +6 -0
- package/lib/components/UiTag.vue +3 -7
- package/lib/components/backdrop/Backdrop.vue +11 -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 +3 -4
- package/lib/components/console/RemoteConsole.vue +1 -1
- package/lib/components/divider/Divider.vue +25 -0
- package/lib/components/dropdown/DropdownItem.vue +1 -4
- package/lib/components/head-bar/HeadBar.vue +78 -0
- package/lib/components/input/UiInput.vue +133 -0
- package/lib/components/object-link/ObjectLink.vue +87 -0
- package/lib/components/search-bar/SearchBar.vue +60 -0
- package/lib/components/stacked-bar/StackedBarSegment.vue +2 -8
- package/lib/components/tab/TabList.vue +1 -0
- package/lib/components/table/UiTable.vue +6 -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/tree/TreeItem.vue +8 -8
- package/lib/components/tree/TreeItemLabel.vue +28 -16
- package/lib/composables/tab-list.composable.ts +33 -0
- package/lib/composables/tree/branch.ts +5 -5
- package/lib/i18n.ts +53 -0
- package/lib/layouts/CoreLayout.vue +2 -11
- package/lib/locales/de.json +7 -1
- package/lib/locales/en.json +22 -1
- package/lib/locales/fa.json +46 -0
- package/lib/locales/fr.json +22 -1
- package/lib/types/tab.type.ts +17 -0
- package/lib/types/task.type.ts +13 -0
- package/lib/types/utility.type.ts +1 -1
- package/lib/utils/if-else.utils.ts +3 -3
- package/lib/utils/open-url.utils.ts +3 -0
- package/package.json +3 -3
- package/lib/components/UiSeparator.vue +0 -11
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ButtonIcon
|
|
3
|
+
ref="buttonRef"
|
|
4
|
+
v-tooltip="{ content: $t('tasks.quick-view'), placement: 'bottom-end' }"
|
|
5
|
+
:dot="hasNewTask"
|
|
6
|
+
:icon="faBarsProgress"
|
|
7
|
+
size="large"
|
|
8
|
+
@click="isPanelOpen = true"
|
|
9
|
+
/>
|
|
10
|
+
<Teleport v-if="isPanelOpen" to="body">
|
|
11
|
+
<Backdrop @click="isPanelOpen = false" />
|
|
12
|
+
<QuickTaskPanel ref="panelRef" :loading :tasks />
|
|
13
|
+
</Teleport>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts" setup>
|
|
17
|
+
import Backdrop from '@core/components/backdrop/Backdrop.vue'
|
|
18
|
+
import ButtonIcon from '@core/components/button/ButtonIcon.vue'
|
|
19
|
+
import QuickTaskPanel from '@core/components/task/QuickTaskPanel.vue'
|
|
20
|
+
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
21
|
+
import type { Task } from '@core/types/task.type'
|
|
22
|
+
import { faBarsProgress } from '@fortawesome/free-solid-svg-icons'
|
|
23
|
+
import { unrefElement, watchArray, whenever } from '@vueuse/core'
|
|
24
|
+
import placementJs from 'placement.js'
|
|
25
|
+
import { computed, nextTick, ref } from 'vue'
|
|
26
|
+
|
|
27
|
+
const props = defineProps<{
|
|
28
|
+
tasks: Task[]
|
|
29
|
+
loading?: boolean
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
const ids = computed(() => props.tasks.map(task => task.id))
|
|
33
|
+
|
|
34
|
+
const isPanelOpen = ref(false)
|
|
35
|
+
const hasNewTask = ref(false)
|
|
36
|
+
|
|
37
|
+
watchArray(ids, (_newList, _oldList, addedIds) => {
|
|
38
|
+
if (addedIds.length > 0 && !isPanelOpen.value) {
|
|
39
|
+
hasNewTask.value = true
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const buttonRef = ref<HTMLButtonElement | null>(null)
|
|
44
|
+
const panelRef = ref<HTMLDivElement | null>(null)
|
|
45
|
+
|
|
46
|
+
whenever(isPanelOpen, async () => {
|
|
47
|
+
await nextTick()
|
|
48
|
+
|
|
49
|
+
const button = unrefElement(buttonRef)
|
|
50
|
+
const panel = unrefElement(panelRef)
|
|
51
|
+
|
|
52
|
+
if (!button || !panel) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
placementJs(button, panel, {
|
|
57
|
+
placement: 'bottom-end',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
hasNewTask.value = false
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li class="vts-quick-task-item">
|
|
3
|
+
<div v-if="hasSubTasks" class="toggle" @click="toggleExpand()">
|
|
4
|
+
<ButtonIcon :icon="isExpanded ? faAngleDown : faAngleRight" size="small" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="content">
|
|
7
|
+
<div class="typo p1-medium">
|
|
8
|
+
{{ task.name }}
|
|
9
|
+
</div>
|
|
10
|
+
<div class="informations">
|
|
11
|
+
<div class="line-1">
|
|
12
|
+
<UiTag v-if="task.tag" color="grey">{{ task.tag }}</UiTag>
|
|
13
|
+
<div v-if="hasSubTasks" class="subtasks">
|
|
14
|
+
<UiIcon :icon="faCircleNotch" />
|
|
15
|
+
<span class="typo p4-medium">{{ $t('tasks.n-subtasks', { n: subTasksCount }) }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div v-if="task.start" class="line-2 typo p4-regular">
|
|
19
|
+
{{ $d(task.start, 'datetime_short') }}
|
|
20
|
+
<template v-if="task.end">
|
|
21
|
+
<UiIcon :icon="faArrowRight" />
|
|
22
|
+
{{ $d(new Date(task.end), 'datetime_short') }}
|
|
23
|
+
</template>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<QuickTaskList v-if="hasSubTasks && isExpanded" :tasks="subTasks" sublist />
|
|
27
|
+
</div>
|
|
28
|
+
</li>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" setup>
|
|
32
|
+
import ButtonIcon from '@core/components/button/ButtonIcon.vue'
|
|
33
|
+
import UiIcon from '@core/components/icon/UiIcon.vue'
|
|
34
|
+
import QuickTaskList from '@core/components/task/QuickTaskList.vue'
|
|
35
|
+
import UiTag from '@core/components/UiTag.vue'
|
|
36
|
+
import type { Task } from '@core/types/task.type'
|
|
37
|
+
import { faAngleDown, faAngleRight, faArrowRight, faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
|
38
|
+
import { useToggle } from '@vueuse/core'
|
|
39
|
+
import { computed } from 'vue'
|
|
40
|
+
|
|
41
|
+
const props = defineProps<{
|
|
42
|
+
task: Task
|
|
43
|
+
}>()
|
|
44
|
+
|
|
45
|
+
const [isExpanded, toggleExpand] = useToggle()
|
|
46
|
+
|
|
47
|
+
const subTasks = computed(() => props.task.subtasks ?? [])
|
|
48
|
+
const subTasksCount = computed(() => subTasks.value.length)
|
|
49
|
+
const hasSubTasks = computed(() => subTasksCount.value > 0)
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style lang="postcss" scoped>
|
|
53
|
+
.vts-quick-task-item {
|
|
54
|
+
display: flex;
|
|
55
|
+
|
|
56
|
+
&:not(:last-child) {
|
|
57
|
+
border-bottom: 0.1rem solid var(--color-grey-500);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.toggle {
|
|
62
|
+
padding: 0.4rem 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.content {
|
|
66
|
+
flex: 1;
|
|
67
|
+
padding: 0.4rem 0.4rem 0.4rem 0.8rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.informations {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: 0.4rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.line-1 {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: 0.2rem 0.8rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.line-2 {
|
|
83
|
+
color: var(--color-grey-200);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.subtasks {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 0.4rem;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul :class="{ sublist }" class="vts-quick-task-list">
|
|
3
|
+
<li v-if="loading">
|
|
4
|
+
<div class="loading">
|
|
5
|
+
<UiSpinner />
|
|
6
|
+
<div>{{ $t('loading-in-progress') }}</div>
|
|
7
|
+
</div>
|
|
8
|
+
</li>
|
|
9
|
+
<template v-else>
|
|
10
|
+
<li v-if="tasks.length === 0" class="typo p1-medium">{{ $t('tasks.no-tasks') }}</li>
|
|
11
|
+
<QuickTaskItem v-for="task of tasks" :key="task.id" :task />
|
|
12
|
+
</template>
|
|
13
|
+
</ul>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts" setup>
|
|
17
|
+
import QuickTaskItem from '@core/components/task/QuickTaskItem.vue'
|
|
18
|
+
import UiSpinner from '@core/components/UiSpinner.vue'
|
|
19
|
+
import type { Task } from '@core/types/task.type'
|
|
20
|
+
|
|
21
|
+
defineProps<{
|
|
22
|
+
tasks: Task[]
|
|
23
|
+
sublist?: boolean
|
|
24
|
+
loading?: boolean
|
|
25
|
+
}>()
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style lang="postcss" scoped>
|
|
29
|
+
.vts-quick-task-list {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
background-color: var(--background-color-primary);
|
|
33
|
+
padding: 1rem 0;
|
|
34
|
+
|
|
35
|
+
&:not(.sublist) {
|
|
36
|
+
padding: 1.6rem 2rem;
|
|
37
|
+
max-height: 40rem;
|
|
38
|
+
overflow: auto;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.loading {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
gap: 1rem;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -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,12 +41,7 @@ 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'
|
|
@@ -59,12 +55,14 @@ defineProps<{
|
|
|
59
55
|
icon?: IconDefinition
|
|
60
56
|
route: RouteLocationRaw
|
|
61
57
|
active?: boolean
|
|
62
|
-
|
|
58
|
+
}>()
|
|
59
|
+
|
|
60
|
+
const emit = defineEmits<{
|
|
61
|
+
toggle: []
|
|
63
62
|
}>()
|
|
64
63
|
|
|
65
64
|
const hasToggle = inject(IK_TREE_ITEM_HAS_CHILDREN, ref(false))
|
|
66
65
|
|
|
67
|
-
const toggle = inject(IK_TREE_ITEM_TOGGLE, () => undefined)
|
|
68
66
|
const isExpanded = inject(IK_TREE_ITEM_EXPANDED, ref(true))
|
|
69
67
|
|
|
70
68
|
const depth = inject(IK_TREE_LIST_DEPTH, 0)
|
|
@@ -120,13 +118,27 @@ const depth = inject(IK_TREE_LIST_DEPTH, 0)
|
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
.text {
|
|
123
|
-
overflow: hidden;
|
|
124
|
-
white-space: nowrap;
|
|
125
|
-
text-overflow: ellipsis;
|
|
126
121
|
padding-inline-end: 0.4rem;
|
|
127
122
|
}
|
|
128
123
|
|
|
129
124
|
.icon {
|
|
130
125
|
font-size: 1.6rem;
|
|
131
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
|
+
}
|
|
132
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
|
}
|
package/lib/i18n.ts
CHANGED
|
@@ -21,6 +21,10 @@ 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({
|
|
@@ -163,6 +167,48 @@ export default createI18n({
|
|
|
163
167
|
minute: '2-digit',
|
|
164
168
|
},
|
|
165
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
|
+
},
|
|
166
212
|
},
|
|
167
213
|
numberFormats: {
|
|
168
214
|
en: {
|
|
@@ -186,5 +232,12 @@ export default createI18n({
|
|
|
186
232
|
maximumFractionDigits: 2,
|
|
187
233
|
},
|
|
188
234
|
},
|
|
235
|
+
fa: {
|
|
236
|
+
percent: {
|
|
237
|
+
style: 'percent',
|
|
238
|
+
minimumFractionDigits: 0,
|
|
239
|
+
maximumFractionDigits: 2,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
189
242
|
},
|
|
190
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-close-backdrop"
|
|
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" />
|
|
@@ -38,6 +34,7 @@
|
|
|
38
34
|
</template>
|
|
39
35
|
|
|
40
36
|
<script lang="ts" setup>
|
|
37
|
+
import Backdrop from '@core/components/backdrop/Backdrop.vue'
|
|
41
38
|
import UiButtonIcon from '@core/components/button/ButtonIcon.vue'
|
|
42
39
|
import LayoutSidebar from '@core/components/layout/LayoutSidebar.vue'
|
|
43
40
|
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
@@ -48,12 +45,6 @@ const sidebarStore = useSidebarStore()
|
|
|
48
45
|
</script>
|
|
49
46
|
|
|
50
47
|
<style lang="postcss" scoped>
|
|
51
|
-
.sidebar-close-backdrop {
|
|
52
|
-
position: fixed;
|
|
53
|
-
inset: 0;
|
|
54
|
-
z-index: 1000;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
48
|
.core-layout {
|
|
58
49
|
display: flex;
|
|
59
50
|
height: 100dvh;
|
package/lib/locales/de.json
CHANGED
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
"dashboard": "Dashboard",
|
|
21
21
|
"documentation-name": "{name} Dokumentation",
|
|
22
|
+
"hosts": "Hosts",
|
|
22
23
|
"loading-in-progress": "Ladevorgang läuft…",
|
|
23
24
|
"log-out": "Abmelden",
|
|
24
25
|
"master": "Primärer Host",
|
|
26
|
+
"n-vms": "1 VM | {n} VMs",
|
|
25
27
|
"network": "Netzwerk",
|
|
26
28
|
"object-not-found": "Objekt {id} wurde nicht gefunden…",
|
|
27
29
|
"power-on-for-console": "Konsole ist nach Start der VM verfügbar",
|
|
@@ -29,5 +31,9 @@
|
|
|
29
31
|
"storage": "Speicher",
|
|
30
32
|
"support-name": "{name} pro support",
|
|
31
33
|
"system": "System",
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
"tasks": "Aufgaben",
|
|
36
|
+
"tasks.quick-view": "Schnellansicht der Aufgaben",
|
|
37
|
+
|
|
38
|
+
"vms": "VMs"
|
|
33
39
|
}
|
package/lib/locales/en.json
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"coming-soon": "Coming soon!",
|
|
9
9
|
"console": "Console",
|
|
10
10
|
|
|
11
|
+
"core.copied": "Copied",
|
|
12
|
+
"core.copy-id": "Copy ID",
|
|
11
13
|
"core.close": "Close",
|
|
12
14
|
"core.current": "Current",
|
|
13
15
|
"core.filter": "Filter",
|
|
@@ -16,6 +18,12 @@
|
|
|
16
18
|
"core.open": "Open",
|
|
17
19
|
"core.quick-actions": "Quick actions",
|
|
18
20
|
|
|
21
|
+
"core.search": "Search",
|
|
22
|
+
|
|
23
|
+
"core.search-bar.label": "Search Engine",
|
|
24
|
+
"core.search-bar.placeholder": "Write your query…",
|
|
25
|
+
"core.search-bar.use-query-builder": "Use query builder",
|
|
26
|
+
|
|
19
27
|
"core.select.all": "Select all",
|
|
20
28
|
"core.select.none": "Select none",
|
|
21
29
|
|
|
@@ -33,15 +41,28 @@
|
|
|
33
41
|
|
|
34
42
|
"dashboard": "Dashboard",
|
|
35
43
|
"documentation-name": "{name} documentation",
|
|
44
|
+
"hosts": "Hosts",
|
|
36
45
|
"loading-in-progress": "Loading in progress…",
|
|
37
46
|
"log-out": "Log out",
|
|
38
47
|
"master": "Primary host",
|
|
48
|
+
"n-vms": "1 VM | {n} VMs",
|
|
39
49
|
"network": "Network",
|
|
40
50
|
"object-not-found": "Object {id} can't be found…",
|
|
41
51
|
"power-on-for-console": "Power on your VM to access its console",
|
|
52
|
+
"see-all": "See all",
|
|
42
53
|
"stats": "Stats",
|
|
43
54
|
"storage": "Storage",
|
|
44
55
|
"support-name": "{name} pro support",
|
|
45
56
|
"system": "System",
|
|
46
|
-
|
|
57
|
+
|
|
58
|
+
"tasks": "Tasks",
|
|
59
|
+
"tasks.n-subtasks": "{n} subtask | {n} subtasks",
|
|
60
|
+
"tasks.no-tasks": "No tasks",
|
|
61
|
+
"tasks.quick-view": "Tasks' quick view",
|
|
62
|
+
"tasks.quick-view.all": "All",
|
|
63
|
+
"tasks.quick-view.done": "Done",
|
|
64
|
+
"tasks.quick-view.failed": "Failed",
|
|
65
|
+
"tasks.quick-view.in-progress": "In progress",
|
|
66
|
+
|
|
67
|
+
"vms": "VMs"
|
|
47
68
|
}
|