Compare commits

...

18 Commits

Author SHA1 Message Date
Joris-K
8f43d3a459 fix issue due to rebase 2025-12-08 15:58:05 +01:00
Joris-K
c4e6166b63 feat(xo6/site): implement tasks page 2025-12-08 15:58:05 +01:00
Joris-K
c5bf57dc57 feat(web-core): add task item and task list components 2025-12-08 15:58:05 +01:00
Joris-K
834e6b9300 add stories and fix issue border on list 2025-12-08 15:57:54 +01:00
Joris-K
b6a5e73d4c feat(web-core): add task item and task list components 2025-12-08 15:57:43 +01:00
Joris-K
1d3fae75fe update 2025-12-08 15:57:31 +01:00
Joris-K
9acd0fc751 feat(web-core): add task item and task list components 2025-12-08 15:55:46 +01:00
Joris-K
e9c0caca23 first feedback 2025-12-08 15:12:41 +01:00
Joris-K
842d0469f7 improved responsiveness 2025-12-08 13:36:21 +01:00
Joris-K
39f307877f remove file 2025-12-08 13:36:21 +01:00
Joris-K
a7b24c29c4 update 2025-12-08 13:36:21 +01:00
Joris-K
0ad02715dd Update progress accent 2025-12-08 13:36:21 +01:00
Joris-K
d06e692aeb add eye icon 2025-12-08 13:36:21 +01:00
Joris-K
80c66dd60a add eye icon 2025-12-08 13:36:21 +01:00
Joris-K
903e94c9b3 add stories and fix issue border on list 2025-12-08 13:36:21 +01:00
Olivier Floch
f9cae857f9 wip clip-path 2025-12-08 13:36:21 +01:00
Joris-K
50b8b13707 update 2025-12-08 13:36:21 +01:00
Joris-K
eb922e91ec feat(web-core): add task item and task list components 2025-12-08 13:36:21 +01:00
24 changed files with 601 additions and 28 deletions

View File

@@ -638,6 +638,7 @@ export type XoTask = {
name?: string
objectId?: string
params?: Record<string, unknown>
progress?: number
type?: string
userId?: string
[key: string]: unknown | undefined

View File

@@ -0,0 +1,14 @@
# Type
```typescript
type Task = {
id: string
infos?: { data: unknown; message: string }[]
name?: string
progress?: number
end?: number
status: 'failure' | 'interrupted' | 'pending' | 'success'
tasks?: Task[]
warning?: { data: unknown; message: string }[]
}
```

View File

@@ -0,0 +1,38 @@
<template>
<ComponentStory
v-slot="{ properties }"
:params="[
prop('task').type('object').required().widget().preset(task),
prop('expanded').type('boolean').widget(boolean()).preset(false),
prop('depth').type('number').required().preset(1),
event('select'),
]"
>
<ul>
<UiTaskItem v-bind="properties" />
</ul>
</ComponentStory>
</template>
<script lang="ts" setup>
import ComponentStory from '@/components/component-story/ComponentStory.vue'
import { event, prop } from '@/libs/story/story-param.ts'
import { boolean } from '@/libs/story/story-widget.ts'
import UiTaskItem, { type Task } from '@core/components/ui/task-item/UiTaskItem.vue'
const task: Task = {
id: '1',
name: 'VM backup',
status: 'pending',
progress: 25,
tasks: [
{
id: '1-1',
name: 'Snapshot',
end: Date.now() - 1000 * 60 * 3,
status: 'success',
progress: 100,
},
],
}
</script>

View File

@@ -0,0 +1,14 @@
# Type
```typescript
type Task = {
id: string
infos?: { data: unknown; message: string }[]
name?: string
progress?: number
end?: number
status: 'failure' | 'interrupted' | 'pending' | 'success'
tasks?: Task[]
warning?: { data: unknown; message: string }[]
}
```

View File

@@ -0,0 +1,81 @@
<template>
<ComponentStory
v-slot="{ properties }"
:params="[prop('tasks').type('Task[]').required().preset(tasks), event('select')]"
>
<UiTaskList v-bind="properties" />
</ComponentStory>
</template>
<script lang="ts" setup>
import ComponentStory from '@/components/component-story/ComponentStory.vue'
import { event, prop } from '@/libs/story/story-param.ts'
import type { Task } from '@core/components/ui/task-item/UiTaskItem.vue'
import UiTaskList from '@core/components/ui/task-list/UiTaskList.vue'
const tasks: Task[] = [
{
id: '1',
name: 'VM backup',
status: 'pending',
progress: 25,
tasks: [
{
id: '1-1',
name: 'Snapshot',
status: 'success',
progress: 100,
},
{
id: '1-2',
name: 'Export',
status: 'pending',
progress: 10,
tasks: [
{
id: '1-2-1',
name: 'Sub task',
status: 'pending',
progress: 5,
},
],
},
],
},
{
id: '2',
name: 'VM Migration',
status: 'failure',
progress: 100,
warning: [
{
data: { file: 'server-1.log' },
message: 'warning',
},
],
},
{
id: '3',
name: 'Storage Migration',
status: 'interrupted',
progress: 100,
infos: [
{ data: { rows: 1200 }, message: 'info' },
{ data: null, message: 'info' },
],
},
{
id: '4',
name: 'Final task',
end: Date.now() - 1000 * 60 * 15,
status: 'success',
progress: 75,
warning: [
{
data: { file: 'server-1.log' },
message: 'warning',
},
],
},
]
</script>

View File

@@ -117,6 +117,8 @@ declare module 'vue-router/auto-routes' {
'/story/web-core/ui/table-pagination/ui-table-pagination': RouteRecordInfo<'/story/web-core/ui/table-pagination/ui-table-pagination', '/story/web-core/ui/table-pagination/ui-table-pagination', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/tag/ui-tag': RouteRecordInfo<'/story/web-core/ui/tag/ui-tag', '/story/web-core/ui/tag/ui-tag', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/tag/ui-tags-list': RouteRecordInfo<'/story/web-core/ui/tag/ui-tags-list', '/story/web-core/ui/tag/ui-tags-list', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/task-item/ui-task-item': RouteRecordInfo<'/story/web-core/ui/task-item/ui-task-item', '/story/web-core/ui/task-item/ui-task-item', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/task-list/ui-task-list': RouteRecordInfo<'/story/web-core/ui/task-list/ui-task-list', '/story/web-core/ui/task-list/ui-task-list', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/title/ui-title': RouteRecordInfo<'/story/web-core/ui/title/ui-title', '/story/web-core/ui/title/ui-title', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/toaster/ui-toaster': RouteRecordInfo<'/story/web-core/ui/toaster/ui-toaster', '/story/web-core/ui/toaster/ui-toaster', Record<never, never>, Record<never, never>>,
'/story/web-core/ui/toggle/ui-toggle': RouteRecordInfo<'/story/web-core/ui/toggle/ui-toggle', '/story/web-core/ui/toggle/ui-toggle', Record<never, never>, Record<never, never>>,

View File

@@ -18,9 +18,9 @@
<script lang="ts" setup>
import VtsBackdrop from '@core/components/backdrop/VtsBackdrop.vue'
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
import type { Task } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import UiQuickTaskPanel from '@core/components/ui/quick-task-panel/UiQuickTaskPanel.vue'
import { vTooltip } from '@core/directives/tooltip.directive'
import type { Task } from '@core/types/task.type.ts'
import { unrefElement, watchArray, whenever } from '@vueuse/core'
import placementJs from 'placement.js'
import { computed, nextTick, ref } from 'vue'

View File

@@ -26,7 +26,8 @@
<script lang="ts" setup>
import UiLoader from '@core/components/ui/loader/UiLoader.vue'
import UiQuickTaskItem, { type Task } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import UiQuickTaskItem from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import type { Task } from '@core/types/task.type.ts'
import { useI18n } from 'vue-i18n'
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'

View File

@@ -48,8 +48,8 @@ import VtsDivider from '@core/components/divider/VtsDivider.vue'
import TabItem from '@core/components/tab/TabItem.vue'
import TabList from '@core/components/tab/TabList.vue'
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
import type { TaskStatus } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import { useTabList } from '@core/composables/tab-list.composable'
import type { TaskStatus } from '@core/types/task.type.ts'
import { useI18n } from 'vue-i18n'
export type TaskTab = TaskStatus | 'all'

View File

@@ -34,22 +34,11 @@ import VtsIcon from '@core/components/icon/VtsIcon.vue'
import VtsQuickTaskList from '@core/components/task/VtsQuickTaskList.vue'
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
import UiTag from '@core/components/ui/tag/UiTag.vue'
import type { Task } from '@core/types/task.type.ts'
import { useToggle } from '@vueuse/core'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export type TaskStatus = 'pending' | 'success' | 'failure'
export type Task = {
id: string | number
name: string
status: TaskStatus
tag?: string
start?: number
end?: number
subtasks?: Task[]
}
const props = defineProps<{
task: Task
}>()

View File

@@ -15,8 +15,8 @@
<script lang="ts" setup>
import VtsQuickTaskList from '@core/components/task/VtsQuickTaskList.vue'
import VtsQuickTaskTabBar, { type TaskTab } from '@core/components/task/VtsQuickTaskTabBar.vue'
import type { Task } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import { useUiStore } from '@core/stores/ui.store'
import type { Task } from '@core/types/task.type.ts'
import { computed, ref } from 'vue'
const props = defineProps<{

View File

@@ -0,0 +1,224 @@
<!-- v10 -->
<template>
<li class="ui-task-item" :data-depth="depth">
<div class="container">
<div class="tree-section">
<div class="tree-lines">
<div v-for="index in depth - 1" :key="index" class="tree-line">
<div class="tree-line-vertical" />
</div>
</div>
<UiButtonIcon
v-if="hasSubTasks"
v-tooltip="expanded ? t('core.close') : t('core.open')"
accent="brand"
:icon="expanded ? 'fa:angle-down' : 'fa:angle-right'"
size="small"
:target-scale="{ x: 1.5, y: 2 }"
@click="emit('expand')"
/>
<div v-else class="h-space" />
</div>
<div class="main-content">
<div v-if="task.name" class="content-left">
<UiLink disabled :to="`#task-${task.id}`" size="medium">
{{ task.name }}
</UiLink>
<div v-if="shouldShowInfos || hasSubTasks" class="infos">
<UiCounter v-if="hasSubTasks" :value="subTasksCount" accent="brand" variant="secondary" size="small" />
<UiInfo v-if="hasInfos" accent="info" />
<UiInfo v-if="hasWarnings" accent="warning" />
<UiInfo v-if="isError" accent="danger" />
</div>
</div>
<div class="content-right typo-body-regular-small">
<span v-if="task.end">
{{ `${t('task.ended')} ${end}` }}
</span>
<div class="progress">
<UiCircleProgressBar v-if="task.progress" :accent="progressAccent" size="small" :value="task.progress" />
</div>
<div class="actions">
<UiButtonIcon icon="fa:eye" size="medium" accent="brand" @click="emit('select')" />
</div>
</div>
</div>
</div>
<template v-if="hasSubTasks && expanded">
<UiTaskList :tasks="subTasks" :depth />
</template>
</li>
</template>
<script lang="ts" setup>
import UiCounter from '@core/components/ui/counter/UiCounter.vue'
import UiInfo from '@core/components/ui/info/UiInfo.vue'
import UiLink from '@core/components/ui/link/UiLink.vue'
import UiTaskList from '@core/components/ui/task-list/UiTaskList.vue'
import { useTimeAgo } from '@core/composables/locale-time-ago.composable.ts'
import { vTooltip } from '@core/directives/tooltip.directive'
import { logicOr } from '@vueuse/math'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import UiButtonIcon from '../button-icon/UiButtonIcon.vue'
import UiCircleProgressBar from '../circle-progress-bar/UiCircleProgressBar.vue'
export type Task = {
id: string
infos?: { data: unknown; message: string }[]
name?: string
progress?: number
end?: number
status: 'failure' | 'interrupted' | 'pending' | 'success'
tasks?: Task[]
warning?: { data: unknown; message: string }[]
}
const { task } = defineProps<{
task: Task
depth: number
expanded?: boolean
}>()
const emit = defineEmits<{
expand: []
select: []
}>()
const { t } = useI18n()
const subTasks = computed(() => task.tasks ?? [])
const subTasksCount = computed(() => subTasks.value.length)
const hasSubTasks = computed(() => subTasksCount.value > 0)
const isError = computed(() => task.status === 'failure' || task.status === 'interrupted')
const end = useTimeAgo(() => task.end ?? 0)
const hasWarnings = computed(() => task.warning && task.warning.length > 0)
const hasInfos = computed(() => task.infos && task.infos.length > 0)
const shouldShowInfos = logicOr(isError, hasWarnings, hasInfos)
const progressAccent = computed(() => (isError.value ? 'danger' : 'info'))
</script>
<style lang="postcss" scoped>
.ui-task-item {
&[data-depth='1']:last-child {
border-bottom: 0.1rem solid var(--color-neutral-border);
}
.container {
display: flex;
min-height: 4.8rem;
position: relative;
&::after {
content: '';
width: 100%;
position: absolute;
clip-path: inset(0 0 0 calc(4rem * v-bind(depth - 1)));
height: 0.1rem;
background: var(--color-neutral-border);
}
.tree-section {
display: flex;
align-items: center;
padding-left: 1.6rem;
gap: 0.4rem;
.tree-lines {
display: flex;
align-self: stretch;
}
.tree-line {
flex: 0 0 2.8rem;
display: flex;
align-items: center;
justify-content: center;
.tree-line-vertical {
width: 0.1rem;
background: var(--color-brand-txt-base);
height: 100%;
}
}
.h-space {
width: 2.4rem;
}
}
.main-content {
display: flex;
flex-direction: row;
flex: 1;
min-width: 0;
gap: 1.6rem;
padding: 0.4rem 1.6rem;
justify-content: space-between;
align-items: center;
}
.content-left {
display: flex;
gap: 1.6rem;
align-items: center;
flex-wrap: wrap;
color: var(--color-neutral-txt-secondary);
word-break: break-word;
.infos {
display: flex;
align-items: center;
gap: 0.8rem;
}
}
.content-right {
display: flex;
align-items: center;
gap: 1.6rem;
color: var(--color-neutral-txt-secondary);
flex-shrink: 0;
.progress {
display: flex;
width: 4rem;
}
.actions {
display: flex;
gap: 1.6rem;
}
}
}
@media (max-width: 768px) {
.container {
.main-content {
flex-direction: column;
align-items: flex-start;
gap: 0.8rem;
padding: 0.8rem;
}
.content-right {
gap: 0.8rem;
.actions {
gap: 0.8rem;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<ul class="ui-task-list">
<UiTaskItem
v-for="task of tasksItems"
:key="task.id"
:task="task.source"
:expanded="task.flags.expanded"
:depth="depth + 1"
@select="emit('select', task.id)"
@expand="task.toggleFlag('expanded')"
/>
</ul>
</template>
<script lang="ts" setup>
import UiTaskItem from '@core/components/ui/task-item/UiTaskItem.vue'
import { useCollection } from '@core/packages/collection'
import type { Task } from '@core/types/task.type.ts'
const { tasks, depth = 0 } = defineProps<{
tasks: Task[]
depth?: number
}>()
const emit = defineEmits<{
select: [id: string]
}>()
const { items: tasksItems } = useCollection(() => tasks, {
flags: ['expanded'],
})
</script>
<style scoped lang="postcss">
.ui-task-list {
overflow-x: auto;
overflow-y: hidden;
}
</style>

View File

@@ -7,12 +7,7 @@
v-bind="attrs"
>
<template v-if="depth > 1">
<VtsTreeLine
v-for="i in depth - 1"
:key="i"
:half-height="(!hasToggle && i === depth - 1) || !isExpanded"
:right="i === depth - 1"
/>
<VtsTreeLine v-for="i in depth - 1" :key="i" />
</template>
<UiButtonIcon
v-if="hasToggle"
@@ -116,7 +111,7 @@ const depth = inject(IK_TREE_LIST_DEPTH, 0)
.h-line {
width: 2rem;
border-bottom: 0.1rem solid var(--color-brand-txt-base);
//border-bottom: 0.1rem solid var(--color-brand-txt-base);
margin-left: -0.4rem;
}

View File

@@ -84,6 +84,7 @@
"boot-firmware-uefi": "The boot firmware is UEFI",
"boot-vm": "Boot VM",
"build-number": "Build number",
"by": "By",
"bytes.gi": "GiB",
"bytes.ki": "KiB",
"bytes.mi": "MiB",
@@ -465,6 +466,7 @@
"no-selected-vm-can-be-migrated": "No selected VM can be migrated",
"no-server-detected": "No server detected",
"no-storage-repositories-detected": "No storage repositories detected.",
"no-tasks-detected": "No tasks detected",
"no-tasks": "No tasks",
"no-vdis-attached": "No VDIs attached",
"no-vdis-detected": "No VDIs detected",
@@ -662,6 +664,7 @@
"table-actions": "Table actions",
"tags": "Tags",
"task": "Task",
"task.ended": "Ended",
"task.estimated-end": "Estimated end",
"task.progress": "Progress",
"task.started": "Started",

View File

@@ -84,6 +84,7 @@
"boot-firmware-uefi": "Le boot firmware est UEFI",
"boot-vm": "Démarrer la VM",
"build-number": "Numéro de build",
"by": "Par",
"bytes.gi": "Gio",
"bytes.ki": "Kio",
"bytes.mi": "Mio",
@@ -466,6 +467,7 @@
"no-server-detected": "Aucun serveur détecté",
"no-storage-repositories-detected": "Aucun dépôt de stockage détecté.",
"no-tasks": "Aucune tâche",
"no-tasks-detected": "Aucune tâche détectée",
"no-vdis-attached": "Aucun VDI attaché",
"no-vdis-detected": "Aucun VDI détecté",
"no-vif-detected": "Aucun VIF détecté",
@@ -662,9 +664,10 @@
"table-actions": "Actions du tableau",
"tags": "Tags",
"task": "Tâche",
"task.ended": "Terminée",
"task.estimated-end": "Fin estimée",
"task.progress": "Progression",
"task.started": "Démarré",
"task.started": "Démarrée",
"tasks": "Tâches",
"tasks.n-subtasks": "{n} sous-tâche | {n} sous-tâches",
"tasks.no-tasks": "Aucune tâche",

View File

@@ -0,0 +1,15 @@
export type TaskStatus = 'failure' | 'interrupted' | 'pending' | 'success'
export type Task = {
id: string
infos?: { data: unknown; message: string }[]
name?: string
progress?: number
tag?: string
userName?: string
start?: number
end?: number
status: TaskStatus
subtasks?: Task[]
warning?: { data: unknown; message: string }[]
}

View File

@@ -18,7 +18,11 @@
{{ t('backups') }}
</TabItem>
</RouterLink>
<TabItem disabled>{{ t('tasks') }}</TabItem>
<RouterLink v-slot="{ isExactActive, href }" to="/tasks" custom>
<TabItem :active="isExactActive" :href tag="a">
{{ t('tasks') }}
</TabItem>
</RouterLink>
<RouterLink v-slot="{ isExactActive, href }" to="/pools" custom>
<TabItem :active="isExactActive" :href tag="a">{{ t('pools') }}</TabItem>
</RouterLink>

View File

@@ -0,0 +1,78 @@
<template>
<div class="tasks-list">
<div class="container">
<div class="actions">
<UiQuerySearchBar @search="value => (searchQuery = value)" />
<UiTopBottomTable :selected-items="0" :total-items="0">
<UiTablePagination v-bind="paginationBindings" />
</UiTopBottomTable>
</div>
<VtsStateHero v-if="hasError" format="table" type="error" size="small" no-background>
{{ t('error-no-data') }}
</VtsStateHero>
<VtsStateHero v-else-if="tasks.length === 0" format="table" type="no-data" size="small">
{{ t('no-tasks-detected') }}
</VtsStateHero>
<UiTaskList v-else :tasks="tasksRecords" />
<VtsStateHero v-if="searchQuery && filteredTasks.length === 0" format="table" type="no-result" size="small">
{{ t('no-result') }}
</VtsStateHero>
<UiTopBottomTable :selected-items="0" :total-items="0">
<UiTablePagination v-bind="paginationBindings" />
</UiTopBottomTable>
</div>
</div>
</template>
<script setup lang="ts">
import type { Task } from '@core/types/task.type.ts'
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
import UiTablePagination from '@core/components/ui/table-pagination/UiTablePagination.vue'
import UiTaskList from '@core/components/ui/task-list/UiTaskList.vue'
import UiTopBottomTable from '@core/components/ui/top-bottom-table/UiTopBottomTable.vue'
import { usePagination } from '@core/composables/pagination.composable'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { tasks } = defineProps<{
tasks: Task[]
hasError: boolean
}>()
const { t } = useI18n()
// const selectedTaskId = useRouteQuery('id')
const searchQuery = ref('')
const filteredTasks = computed(() => {
const searchTerm = searchQuery.value.trim().toLocaleLowerCase()
if (!searchTerm) {
return tasks
}
return tasks.filter(task => Object.values(task).some(value => String(value).toLocaleLowerCase().includes(searchTerm)))
})
const { pageRecords: tasksRecords, paginationBindings } = usePagination('tasks', filteredTasks)
</script>
<style scoped lang="postcss">
.tasks-list,
.actions,
.container {
display: flex;
flex-direction: column;
}
.tasks-list {
gap: 2.4rem;
.container,
.actions {
gap: 0.8rem;
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="tasks" :class="{ mobile: uiStore.isMobile }">
<UiCard class="container">
<VtsStateHero v-if="areTasksFetching" busy format="card" size="medium" />
<TasksList v-else :tasks="convertedTasks" :has-error="hasTaskFetchError" />
</UiCard>
</div>
</template>
<script setup lang="ts">
import TasksList from '@/components/site/tasks/TasksList.vue'
import { useXoTaskCollection } from '@/remote-resources/use-xo-task-collection.ts'
import { useXoUserCollection } from '@/remote-resources/use-xo-user-collections.ts'
import { convertTaskToCore } from '@/utils/convert-task-to-core.util.ts'
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import { useUiStore } from '@core/stores/ui.store'
import type { XoUser } from '@vates/types'
import { computed } from 'vue'
const uiStore = useUiStore()
const { tasks, hasTaskFetchError, areTasksFetching } = useXoTaskCollection()
const { getUserById } = useXoUserCollection()
const convertedTasks = computed(() =>
tasks.value.map(task => {
const userId = task.properties.userId
if (!userId) {
return convertTaskToCore(task)
}
const user = getUserById(userId as XoUser['id'])
// TODO , just put username when it is available in endpoint
return convertTaskToCore(task, user?.name ? user?.name : user?.email)
})
)
</script>
<style scoped lang="postcss">
.tasks {
&:not(.mobile) {
display: grid;
grid-template-columns: minmax(0, 1fr);
}
.container {
height: fit-content;
gap: 4rem;
margin: 0.8rem;
}
}
</style>

View File

@@ -0,0 +1,14 @@
import { useXoCollectionState } from '@/composables/xo-collection-state/use-xo-collection-state.ts'
import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts'
import type { XoUser } from '@vates/types'
export const useXoUserCollection = defineRemoteResource({
url: `/rest/v0/users?fields=id,name,permissions,id,email,groups`,
initialData: () => [] as XoUser[],
state: (users, context) => {
return useXoCollectionState(users, {
context,
baseName: 'user',
})
},
})

View File

@@ -1,12 +1,14 @@
import type { Task as CoreTask } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
import type { Task } from '@core/types/task.type.ts'
import type { XoTask } from '@vates/types'
export const convertTaskToCore = (task: XoTask): CoreTask => ({
export const convertTaskToCore = (task: XoTask, userName?: string): Task => ({
id: task.id,
status: task.status === 'interrupted' ? 'failure' : task.status,
name: task.properties.name ?? '',
tag: task.properties.type,
progress: typeof task.properties.progress === 'number' ? task.properties.progress : 0,
userName,
start: task.start,
end: task.end,
subtasks: task.tasks !== undefined ? task.tasks.map(subtask => convertTaskToCore(subtask)) : undefined,
subtasks: task.tasks?.map(subtask => convertTaskToCore(subtask, userName)),
})

View File

@@ -23,6 +23,7 @@ declare module 'vue-router/auto-routes' {
'/(site)/dashboard': RouteRecordInfo<'/(site)/dashboard', '/dashboard', Record<never, never>, Record<never, never>>,
'/(site)/hosts': RouteRecordInfo<'/(site)/hosts', '/hosts', Record<never, never>, Record<never, never>>,
'/(site)/pools': RouteRecordInfo<'/(site)/pools', '/pools', Record<never, never>, Record<never, never>>,
'/(site)/tasks': RouteRecordInfo<'/(site)/tasks', '/tasks', Record<never, never>, Record<never, never>>,
'/(site)/vms': RouteRecordInfo<'/(site)/vms', '/vms', Record<never, never>, Record<never, never>>,
'/[...path]': RouteRecordInfo<'/[...path]', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
'/backup/[id]': RouteRecordInfo<'/backup/[id]', '/backup/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,