Compare commits

...

2 Commits

Author SHA1 Message Date
Sébastian Czech
2dbbefad0f feat(xo-lite): implement locales 2024-11-15 08:42:03 +01:00
Sébastian Czech
189840f539 feat(xo-lite): implement network page 2024-11-14 17:17:44 +01:00
17 changed files with 367 additions and 15 deletions

View File

@@ -188,7 +188,6 @@
"start": "Start",
"start-on-host": "Start on specific host",
"static-ip": "Static IP",
"status": "Status",
"storage-usage": "Storage usage",
"support": "Support",
"suspend": "Suspend",

View File

@@ -188,7 +188,6 @@
"start": "Démarrer",
"start-on-host": "Démarrer sur un hôte spécifique",
"static-ip": "IP statique",
"status": "Statut",
"storage-usage": "Utilisation du stockage",
"support": "Support",
"suspend": "Suspendre",

View File

@@ -0,0 +1,32 @@
export default {
path: '/host/:uuid',
component: () => import('@/views/host/HostRootView.vue'),
redirect: { name: 'host.console' },
children: [
{
path: 'dashboard',
name: 'host.dashboard',
component: () => import('@/views/host/HostDashboardView.vue'),
},
{
path: 'console',
name: 'host.console',
component: () => import('@/views/host/HostConsoleView.vue'),
},
{
path: 'network',
name: 'host.network',
component: () => import('@/views/host/HostNetworkView.vue'),
},
{
path: 'tasks',
name: 'host.tasks',
component: () => import('@/views/host/HostTasksView.vue'),
},
{
path: 'vms',
name: 'host.vms',
component: () => import('@/views/host/HostVmsView.vue'),
},
],
}

View File

@@ -1,3 +1,4 @@
import host from '@/router/host'
import pool from '@/router/pool'
import story from '@/router/story'
import vm from '@/router/vm'
@@ -25,17 +26,7 @@ const router = createRouter({
story,
pool,
vm,
{
path: '/host/:uuid',
component: () => import('@/views/host/HostRootView.vue'),
children: [
{
path: '',
name: 'host.dashboard',
component: () => import('@/views/host/HostDashboardView.vue'),
},
],
},
host,
{
path: '/:pathMatch(.*)*',
name: 'not-found',

View File

@@ -0,0 +1,11 @@
<template>
<PageUnderConstruction />
</template>
<script lang="ts" setup>
import PageUnderConstruction from '@/components/PageUnderConstruction.vue'
import { usePageTitleStore } from '@/stores/page-title.store'
import { useI18n } from 'vue-i18n'
usePageTitleStore().setTitle(useI18n().t('dashboard'))
</script>

View File

@@ -0,0 +1,19 @@
<template>
<TitleBar :icon="faServer">
{{ name }}
</TitleBar>
</template>
<script lang="ts" setup>
import TitleBar from '@/components/TitleBar.vue'
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
import { useHostStore } from '@/stores/xen-api/host.store'
import { faServer } from '@fortawesome/free-solid-svg-icons'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const { getByUuid: getHostByUuid } = useHostStore().subscribe()
const route = useRoute()
const host = computed(() => getHostByUuid(route.params.uuid as XenApiHost['uuid']))
const name = computed(() => host.value?.name_label)
</script>

View File

@@ -0,0 +1,225 @@
<template>
<div class="hostNetworkView">
<UiTitle type="h4" class="header">
<slot>{{ $t('pifs') }}</slot>
<template #actions>
<UiButton size="medium" variant="secondary" accent="info" :left-icon="faArrowsRotate">
{{ $t('scan-pifs') }}
</UiButton>
</template>
</UiTitle>
<div class="table-container">
<UiQuerySearchBar class="table-query" @search="(value: string) => (filter = value)" />
<VtsTable vertical-border class="table">
<thead>
<tr>
<ColumnTitle id="network" :icon="faAlignLeft">{{ $t('network') }}</ColumnTitle>
<ColumnTitle id="device" :icon="faAlignLeft">{{ $t('device') }}</ColumnTitle>
<ColumnTitle id="status" :icon="faPowerOff">{{ $t('status') }}</ColumnTitle>
<ColumnTitle id="vlan" :icon="faAlignLeft">{{ $t('vlan') }}</ColumnTitle>
<ColumnTitle id="ipAddresses" :icon="faAt">{{ $t('ip-addresses') }}</ColumnTitle>
<ColumnTitle id="macAddress" :icon="faAt">{{ $t('mac-address') }}</ColumnTitle>
<ColumnTitle id="ipMode" :icon="faCaretDown">{{ $t('ip-mode') }}</ColumnTitle>
</tr>
</thead>
<tbody>
<tr v-for="pif in pifs" :key="pif.id">
<VtsCellObject>
<UiObjectLink :route="`/vm/${pif.data.id}/console`">
<template #icon>
<UiComplexIcon size="medium">
<VtsIcon :icon="faNetworkWired" accent="current" />
<VtsIcon accent="success" :icon="faCircle" :overlay-icon="faCheck" />
</UiComplexIcon>
</template>
{{ pif.data.name_label }}
</UiObjectLink>
</VtsCellObject>
<VtsCellText>{{ pif.data.device }}</VtsCellText>
<VtsCellObject>
<VtsIcon
:accent="getStatusProps(pif.data.status as NetworkStatus).accent"
:icon="faCircle"
:overlay-icon="getStatusProps(pif.data.status as NetworkStatus).icon"
/>
{{ pif.data.status }}
</VtsCellObject>
<VtsCellText>{{ pif.data.vlan }}</VtsCellText>
<VtsCellText>{{ pif.data.ip }}</VtsCellText>
<VtsCellText>{{ pif.data.mac }}</VtsCellText>
<VtsCellText>{{ pif.data.ip_mode }}</VtsCellText>
</tr>
</tbody>
</VtsTable>
</div>
</div>
</template>
<script setup lang="ts">
import VtsCellObject from '@core/components/cell-object/VtsCellObject.vue'
import VtsCellText from '@core/components/cell-text/VtsCellText.vue'
import VtsIcon from '@core/components/icon/VtsIcon.vue'
import ColumnTitle from '@core/components/table/ColumnTitle.vue'
import VtsTable from '@core/components/table/VtsTable.vue'
import UiButton from '@core/components/ui/button/UiButton.vue'
import UiComplexIcon from '@core/components/ui/complex-icon/UiComplexIcon.vue'
import UiObjectLink from '@core/components/ui/object-link/UiObjectLink.vue'
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
import UiTitle from '@core/components/ui/title/UiTitle.vue'
import { defineTree } from '@core/composables/tree/define-tree'
import { useTreeFilter } from '@core/composables/tree-filter.composable'
import { useTree } from '@core/composables/tree.composable'
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import {
faAlignLeft,
faArrowsRotate,
faAt,
faCaretDown,
faCheck,
faCircle,
faExclamation,
faNetworkWired,
faPowerOff,
} from '@fortawesome/free-solid-svg-icons'
const data = [
{
id: 'e8ec9a13-7b65-495e-2d90-224c17e02b67',
name_label: '[Team XCPng] - Win X centers',
status: 'connected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: 'e40c1da3-918b-c173-506c-e8c2c069181f',
name_label: 'AS-YA test VM pharpp_2023-10-30T15:03:17.071Z',
status: 'connected',
device: 'eth1',
vlan: 101,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: '989cd0ad-17f8-c198-15c6-8549da1f3b9f',
name_label: 'BRS - XenServer 8',
status: 'disconnected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: 'bfbacbd4-8a09-c769-4e7e-59c2a0f77095',
name_label: 'Debian Teddy',
status: 'other',
device: 'eth1',
vlan: null,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: '03a631e2-a39e-7c63-7bb3-ac7a5ef6c47e',
name_label: 'DML - 8.2.1 - Host 1',
status: 'connected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'Static',
},
{
id: 'ea57eb75-c55b-d9c9-6cb4-e0ea235eb16f',
name_label: 'ElasticSearch FMD',
status: 'disconnected',
device: 'eth1',
vlan: 102,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: '559b0b63-2173-b6a1-ef65-7155d56401b6',
name_label: 'flo-ubuntu',
status: 'connected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: '99e196e1-33f9-461b-93e3-d70a0eb29b3e',
name_label: 'Test BRS',
status: 'disconnected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'Static',
},
{
id: 'cd418934-1f70-22bc-688e-eb6da266aa57',
name_label: 'XCP-ng 8.2.1 UEFI Ext (TEe)',
status: 'connected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'DHCP',
},
{
id: '6d9c33fc-ebec-240d-e31b-18152911795d',
name_label: 'XCP-ng 8.3 A1 Lyon - BRS',
status: 'connected',
device: 'eth1',
vlan: 100,
ip: '127.0.0.1',
mac: '00:1A:2B:3C:4D:5E',
ip_mode: 'Static',
},
]
const { filter, predicate } = useTreeFilter()
const definitions = defineTree(data, {
getLabel: 'name_label',
predicate,
})
const { nodes: pifs } = useTree(definitions, { expand: false })
type NetworkStatus = 'connected' | 'disconnected' | 'other'
type NetworkAccent = 'success' | 'warning' | 'danger'
const states: Record<NetworkStatus, { icon: IconDefinition; accent: NetworkAccent }> = {
connected: { icon: faCheck, accent: 'success' },
disconnected: { icon: faCheck, accent: 'danger' },
other: { icon: faExclamation, accent: 'warning' },
}
const getStatusProps = (status: NetworkStatus) => states[status as NetworkStatus]
</script>
<style scoped lang="postcss">
.hostNetworkView {
padding: 1.6rem;
margin: 0.8rem;
border: solid 0.1rem var(--color-neutral-border);
border-radius: 0.8rem;
background-color: var(--color-neutral-background-primary);
.table-container {
margin-top: 2.4rem;
.table {
margin-top: 2.4rem;
}
}
}
</style>

View File

@@ -1,5 +1,9 @@
<template>
<ObjectNotFoundWrapper :is-ready="isReady" :uuid-checker="hasUuid">
<template v-if="uiStore.hasUi">
<HostHeader />
<HostTabBar :uuid="currentHost!.uuid" />
</template>
<RouterView />
</ObjectNotFoundWrapper>
</template>
@@ -9,6 +13,8 @@ import ObjectNotFoundWrapper from '@/components/ObjectNotFoundWrapper.vue'
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
import { usePageTitleStore } from '@/stores/page-title.store'
import { useHostStore } from '@/stores/xen-api/host.store'
import HostHeader from '@/views/host/HostHeader.vue'
import HostTabBar from '@/views/host/HostTabBar.vue'
import { useUiStore } from '@core/stores/ui.store'
import { computed, watchEffect } from 'vue'
import { useRoute } from 'vue-router'

View File

@@ -0,0 +1,28 @@
<template>
<TabList>
<RouterTab :to="{ name: 'host.dashboard', params: { uuid } }">
{{ $t('dashboard') }}
</RouterTab>
<RouterTab :to="{ name: 'host.console', params: { uuid } }" disabled>
{{ $t('console') }}
</RouterTab>
<RouterTab :to="{ name: 'host.network', params: { uuid } }">
{{ $t('network') }}
</RouterTab>
<RouterTab :to="{ name: 'host.tasks', params: { uuid } }" disabled>
{{ $t('tasks') }}
</RouterTab>
<RouterTab :to="{ name: 'host.vms', params: { uuid } }" disabled>
{{ $t('vms') }}
</RouterTab>
</TabList>
</template>
<script lang="ts" setup>
import RouterTab from '@/components/RouterTab.vue'
import TabList from '@core/components/tab/TabList.vue'
defineProps<{
uuid: string
}>()
</script>

View File

@@ -0,0 +1,11 @@
<template>
<PageUnderConstruction />
</template>
<script lang="ts" setup>
import PageUnderConstruction from '@/components/PageUnderConstruction.vue'
import { usePageTitleStore } from '@/stores/page-title.store'
import { useI18n } from 'vue-i18n'
usePageTitleStore().setTitle(useI18n().t('dashboard'))
</script>

View File

@@ -0,0 +1,11 @@
<template>
<PageUnderConstruction />
</template>
<script lang="ts" setup>
import PageUnderConstruction from '@/components/PageUnderConstruction.vue'
import { usePageTitleStore } from '@/stores/page-title.store'
import { useI18n } from 'vue-i18n'
usePageTitleStore().setTitle(useI18n().t('dashboard'))
</script>

View File

@@ -72,6 +72,7 @@ const classNames = computed(() => [
border-width: 0.1rem;
outline: none;
border-style: solid;
white-space: nowrap;
&.lock {
cursor: default;

View File

@@ -88,6 +88,7 @@ const id = computed(() => uniqueId('input-'))
/* IMPLEMENTATION */
.ui-input {
position: relative;
width: 100%;
.before + .input {
padding-inline-start: 4.8rem;

View File

@@ -7,6 +7,7 @@
<UiInput
:id
v-model="value"
class="ui-query-search-bar-input"
:aria-label="uiStore.isMobile ? $t('core.query-search-bar.label') : undefined"
:icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
:placeholder="$t('core.query-search-bar.placeholder')"
@@ -62,6 +63,7 @@ const value = ref<string>('')
.label {
color: var(--color-neutral-txt-secondary);
white-space: nowrap;
}
}
</style>

View File

@@ -23,10 +23,10 @@ const slots = defineSlots<{
align-items: center;
gap: 2.4rem;
padding-block-end: 0.4rem;
border-bottom: 0.1rem solid var(--color-normal-txt-base);
border-bottom: 0.1rem solid var(--color-info-txt-base);
.label {
color: var(--color-normal-txt-base);
color: var(--color-info-txt-base);
}
.actions {

View File

@@ -40,11 +40,15 @@
"dark-mode.auto": "Auto dark mode",
"dashboard": "Dashboard",
"device": "Device",
"documentation-name": "{name} documentation",
"hosts": "Hosts",
"ip-addresses": "IP Adresses",
"ip-mode": "IP mode",
"learn-more": "Learn more",
"loading-in-progress": "Loading in progress…",
"log-out": "Log out",
"mac-address": "MAC address",
"master": "Primary host",
"n-vms": "1 VM | {n} VMs",
"network": "Network",
@@ -52,11 +56,14 @@
"object-not-found": "Object {id} can't be found…",
"other": "Other",
"patches": "Patches",
"pifs": "PIFs",
"power-on-vm-for-console": "Power on your VM to access its console",
"power-on-host-for-console": "Power on your host to access its console",
"running-vm": "Running VM | Running VMs",
"scan-pifs": "Scan PIFs",
"see-all": "See all",
"stats": "Stats",
"status": "Status",
"storage": "Storage",
"support-name": "{name} pro support",
"system": "System",
@@ -72,5 +79,6 @@
"total": "Total",
"total-cpus": "Total CPUs",
"vlan": "VLAN",
"vms": "VMs"
}

View File

@@ -40,11 +40,15 @@
"dark-mode.auto": "Mode sombre automatique",
"dashboard": "Tableau de bord",
"device": "Appareil",
"documentation-name": "Documentation {name}",
"hosts": "Hôtes",
"ip-addresses": "Adresses IP",
"ip-mode": "Mode IP",
"learn-more": "En savoir plus",
"loading-in-progress": "Chargement en cours…",
"log-out": "Se déconnecter",
"mac-address": "Adresse MAC",
"master": "Hôte primaire",
"n-vms": "1 VM | {n} VMs",
"network": "Réseau",
@@ -52,11 +56,14 @@
"object-not-found": "L'objet {id} est introuvable…",
"other": "Autre",
"patches": "Patches",
"pifs": "PIFs",
"power-on-vm-for-console": "Allumez votre VM pour accéder à sa console",
"power-on-host-for-console": "Allumez votre hôte pour accéder à sa console",
"running-vm": "VM en cours d'exécution | VMs en cours d'exécution",
"scan-pifs": "Scanner les PIFs",
"see-all": "Voir tout",
"stats": "Stats",
"status": "Statut",
"storage": "Stockage",
"support-name": "Support pro {name}",
"system": "Système",
@@ -72,5 +79,6 @@
"total": "Total",
"total-cpus": "Total CPUs",
"vlan": "VLAN",
"vms": "VMs"
}