Skip to content

Commit

Permalink
feat(xo-lite): add host network tab and pif table / status component …
Browse files Browse the repository at this point in the history
…and pif store
  • Loading branch information
J0ris-K committed Dec 12, 2024
1 parent 1bc0f8f commit 8406813
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 4 deletions.
2 changes: 1 addition & 1 deletion @xen-orchestra/lite/src/components/host/HostTabBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<RouterTab :to="{ name: 'host.console', params: { uuid } }">
{{ $t('console') }}
</RouterTab>
<RouterTab :to="{ name: 'host.network', params: { uuid } }" disabled>
<RouterTab :to="{ name: 'host.network', params: { uuid } }">
{{ $t('network') }}
</RouterTab>
<RouterTab :to="{ name: 'host.tasks', params: { uuid } }" disabled>
Expand Down
43 changes: 43 additions & 0 deletions @xen-orchestra/lite/src/components/host/network/HostPifStatus.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<UiInfo class="pif-status text-ellipsis" :accent="getStatusProps(status).accent">
<p class="text-ellipsis" :class="{ card }">{{ getStatusProps(status).text }}</p>
</UiInfo>
</template>

<script setup lang="ts">
import UiInfo from '@core/components/ui/info/UiInfo.vue'
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import { faCheck, faExclamation } from '@fortawesome/free-solid-svg-icons'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const props = defineProps<{
pif: any
card?: boolean
}>()
const { t } = useI18n()
type NetworkStatus = 'connected' | 'disconnected' | 'partial'
type NetworkAccent = 'success' | 'warning' | 'danger'
const states = computed<Record<NetworkStatus, { text: string; icon: IconDefinition; accent: NetworkAccent }>>(() => ({
connected: { text: t('connected'), icon: faCheck, accent: 'success' },
disconnected: { text: t('disconnected'), icon: faCheck, accent: 'danger' },
partial: { text: t('disconnected-from-physical-device'), icon: faExclamation, accent: 'warning' },
}))
const status = computed(() => {
if (props.pif.currently_attached) {
return 'connected'
}
if (props.pif.currently_attached) {
return 'partial'
}
return 'disconnected'
})
const getStatusProps = (status: NetworkStatus) => states.value[status as NetworkStatus]
</script>

<style scoped lang="postcss">
p:not(.card) {
font-size: 1.4rem !important;
}
</style>
217 changes: 217 additions & 0 deletions @xen-orchestra/lite/src/components/host/network/HostPifTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<template>
<div class="container">
<UiTitle>
{{ $t('pifs') }}
<template #actions>
<UiButton :left-icon="faPlus" variant="secondary" accent="info" size="medium">{{ $t('scan-pifs') }}</UiButton>
</template>
</UiTitle>
<div class="content">
<UiQuerySearchBar class="table-query" @search="(value: string) => (searchQuery = value)" />
<UiTableActions title="Table actions">
<UiButton :left-icon="faEdit" variant="tertiary" accent="info" size="medium">{{ $t('edit') }}</UiButton>
<UiButton :left-icon="faTrash" variant="tertiary" accent="danger" size="medium">{{ $t('delete') }}</UiButton>
</UiTableActions>
<UiTopBottomTable
:selected-items="selected.length"
:total-items="usableRefs.length"
@toggle-select-all="toggleSelect"
/>
<div class="table">
<VtsTable vertical-border>
<thead>
<tr>
<template v-for="column of visibleColumns" :key="column.id">
<th v-if="column.id === 'checkbox'" class="checkbox">
<UiCheckbox :v-model="areAllSelected" accent="info" @update:model-value="toggleSelect" />
</th>
<th v-else-if="column.id === 'more'" class="more">
<UiButtonIcon size="small" accent="info" :icon="getHeaderIcon(column.id)" />
{{ column.label }}
</th>
<ColumnTitle v-else id="networks" :icon="getHeaderIcon(column.id)"> {{ column.label }}</ColumnTitle>
</template>
</tr>
</thead>
<tbody>
<tr v-for="row of rows" :key="row.id">
<td v-for="column of row.visibleColumns" :key="column.id" class="typo p2-regular">
<UiCheckbox v-if="column.id === 'checkbox'" v-model="selected" accent="info" :value="row.id" />
<!-- NEED TO REMOVE `as XenApiPif` -->
<div v-if="column.id === 'network' && (row.value as XenApiPif).network" class="network">
<UiComplexIcon size="medium" class="icon">
<VtsIcon :icon="faNetworkWired" accent="current" />
<VtsIcon accent="success" :icon="faCircle" :overlay-icon="faCheck" />
</UiComplexIcon>
<div v-tooltip class="text-ellipsis">{{ getNetworkName(row.value) }}</div>
</div>
<div v-if="column.id === 'device'">
{{ (row.value as XenApiPif).device }}
</div>
<div v-if="column.id === 'status'" v-tooltip>
<HostPifStatus class="status" :pif="row.value" />
</div>
<div v-if="column.id === 'VLAN'">
{{ (row.value as XenApiPif).VLAN === -1 ? $t('none') : (row.value as XenApiPif).VLAN }}
</div>
<div v-if="column.id === 'IP'" v-tooltip="{ placement: 'bottom-end' }" class="text-ellipsis">
{{ (row.value as XenApiPif).IP }}
</div>
<div v-if="column.id === 'MAC'" v-tooltip="{ placement: 'bottom-end' }" class="text-ellipsis">
{{ (row.value as XenApiPif).MAC }}
</div>
<div v-if="column.id === 'ip_configuration_mode'">
{{ (row.value as XenApiPif).ip_configuration_mode }}
</div>
<div v-if="column.id === 'more'">
<VtsIcon accent="info" :icon="faEllipsis" />
</div>
</td>
</tr>
</tbody>
</VtsTable>
</div>
<UiTopBottomTable
:selected-items="selected.length"
:total-items="usableRefs.length"
@toggle-select-all="toggleSelect"
/>
</div>
</div>
<UiCardSpinner v-if="!isReady" />
</template>

<script lang="ts" setup>
import HostPifStatus from '@/components/host/network/HostPifStatus.vue'
import UiCardSpinner from '@/components/ui/UiCardSpinner.vue'
import useMultiSelect from '@/composables/multi-select.composable'
import type { XenApiNetwork, XenApiPif } from '@/libs/xen-api/xen-api.types'
import { useNetworkStore } from '@/stores/xen-api/network.store'
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 UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
import UiCheckbox from '@core/components/ui/checkbox/UiCheckbox.vue'
import UiComplexIcon from '@core/components/ui/complex-icon/UiComplexIcon.vue'
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
import UiTableActions from '@core/components/ui/table-actions/UiTableActions.vue'
import UiTitle from '@core/components/ui/title/UiTitle.vue'
import UiTopBottomTable from '@core/components/ui/top-bottom-table/UiTopBottomTable.vue'
import { useTable } from '@core/composables/table.composable'
import { vTooltip } from '@core/directives/tooltip.directive'
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import {
faAlignLeft,
faAt,
faCaretDown,
faCheck,
faCircle,
faEdit,
faEllipsis,
faNetworkWired,
faPlus,
faPowerOff,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
import { computed, ref } from 'vue'
const { pifs } = defineProps<{
pifs: XenApiPif[]
isReady: boolean
}>()
const { getByOpaqueRef } = useNetworkStore().subscribe()
const getNetworkName = (pif: XenApiPif) => {
const network: XenApiNetwork = getByOpaqueRef(pif.network)!
return network?.name_label ? network.name_label : ''
}
const searchQuery = ref('')
const filteredNetworks = computed(() => {
return searchQuery.value
? pifs.filter(pif =>
Object.values(pif).some(value => String(value).toLowerCase().includes(searchQuery.value.toLowerCase()))
)
: pifs
})
const usableRefs = computed(() => pifs.map(item => item.uuid))
const { selected, areAllSelected } = useMultiSelect(usableRefs)
const toggleSelect = () => {
selected.value = selected.value.length === 0 ? usableRefs.value : []
}
const { visibleColumns, rows } = useTable('pifs', filteredNetworks, {
rowId: record => record.uuid,
columns: define => [
define('checkbox', () => '', { label: '', isHideable: false }),
define('network', { label: 'Network', isHideable: true }),
define('device', { label: 'Device', isHideable: true }),
define('status', () => '', { label: 'Status', isHideable: true }),
define('VLAN', { label: 'Vlan', isHideable: true }),
define('IP', { label: 'IP', isHideable: true }),
define('MAC', { label: 'MAC', isHideable: true }),
define('ip_configuration_mode', { label: 'Mode', isHideable: true }),
define('more', () => '', { label: '', isHideable: false }),
],
})
type pifHeader = 'network' | 'device' | 'status' | 'VLAN' | 'IP' | 'MAC' | 'ip_configuration_mode' | 'more'
const headerIcon: Record<pifHeader, { icon: IconDefinition }> = {
network: { icon: faAlignLeft },
device: { icon: faAlignLeft },
status: { icon: faPowerOff },
VLAN: { icon: faAlignLeft },
IP: { icon: faAt },
MAC: { icon: faAt },
ip_configuration_mode: { icon: faCaretDown },
more: { icon: faEllipsis },
}
const getHeaderIcon = (status: pifHeader) => headerIcon[status].icon
</script>

<style scoped lang="postcss">
.container,
.content {
display: flex;
flex-direction: column;
}
.container {
gap: 2.4rem;
.content {
gap: 0.8rem;
.table {
overflow-x: auto;
.network {
display: flex;
align-items: center;
gap: 1.8rem;
}
tr:last-child {
border-bottom: 1px solid var(--color-neutral-border);
}
}
.checkbox,
.more {
width: 4.8rem;
}
}
@media (max-width: 1440px) {
.table {
table {
width: 160rem;
}
}
}
}
</style>
7 changes: 7 additions & 0 deletions @xen-orchestra/lite/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"close": "Close",
"community": "Community",
"community-name": "{name} community",
"connected": "Connected",
"configuration": "Configuration",
"confirm-cancel": "Are you sure you want to cancel?",
"confirm-delete": "You're about to delete {0}",
Expand All @@ -60,11 +61,14 @@
"descending": "descending",
"description": "Description",
"dhcp": "DHCP",
"disconnected": "Disconnected",
"disconnected-from-physical-device": "Disconnected from physical device",
"disabled": "Disabled",
"display": "Display",
"dns": "DNS",
"do-you-have-needs": "You have needs and/or expectations? Let us know",
"documentation": "Documentation",
"edit": "Edit",
"edit-config": "Edit config",
"enabled": "Enabled",
"error-occurred": "An error has occurred",
Expand Down Expand Up @@ -186,6 +190,7 @@
"new-features-are-coming": "New features are coming soon!",
"news": "News",
"news-name": "{name} news",
"none": "None",
"no-alarm-triggered": "No alarm triggered",
"no-result": "No result",
"no-selected-vm-can-be-exported": "No selected VM can be exported",
Expand All @@ -199,6 +204,7 @@
"password": "Password",
"password-invalid": "Password invalid",
"pause": "Pause",
"pifs": "PIFs",
"please-confirm": "Please confirm",
"pool-cpu-usage": "Pool CPU Usage",
"pool-ram-usage": "Pool RAM Usage",
Expand All @@ -220,6 +226,7 @@

"resume": "Resume",
"save": "Save",
"scan-pifs": "Scan PIFs",
"select-compression": "Select a compression",
"select-destination-host": "Select a destination host",
"selected-vms-in-execution": "Some selected VMs are running",
Expand Down
7 changes: 7 additions & 0 deletions @xen-orchestra/lite/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"community": "Communauté",
"community-name": "Communauté {name}",
"configuration": "Configuration",
"connected": "Connecté",
"confirm-cancel": "Êtes-vous sûr de vouloir annuler ?",
"confirm-delete": "Vous êtes sur le point de supprimer {0}",
"console-unavailable": "Console indisponible",
Expand All @@ -60,11 +61,14 @@
"descending": "descendant",
"description": "Description",
"dhcp": "DHCP",
"disconnected": "Déconnecté",
"disconnected-from-physical-device": "Déconnecté de l'appareil physique",
"dns": "DNS",
"disabled": "Désactivé",
"display": "Affichage",
"do-you-have-needs": "Vous avez des besoins et/ou des attentes ? Faites le nous savoir",
"documentation": "Documentation",
"edit": "Modifier",
"edit-config": "Modifier config",
"enabled": "Activé",
"error-occurred": "Une erreur est survenue",
Expand Down Expand Up @@ -186,6 +190,7 @@
"new-features-are-coming": "De nouvelles fonctionnalités arrivent bientôt !",
"news": "Actualités",
"news-name": "Actualités {name}",
"none": "Aucun",
"no-alarm-triggered": "Aucune alarme déclenchée",
"no-result": "Aucun résultat",
"no-selected-vm-can-be-exported": "Aucune VM sélectionnée ne peut être exportée",
Expand All @@ -199,6 +204,7 @@
"password": "Mot de passe",
"password-invalid": "Mot de passe incorrect",
"pause": "Pause",
"pifs": "PIFs",
"please-confirm": "Veuillez confirmer",
"pool-cpu-usage": "Utilisation CPU du Pool",
"pool-ram-usage": "Utilisation RAM du Pool",
Expand All @@ -220,6 +226,7 @@

"resume": "Reprendre",
"save": "Enregistrer",
"scan-pifs": "Scanner les PIFs",
"select-compression": "Sélectionnez une compression",
"select-destination-host": "Sélectionnez un hôte de destination",
"selected-vms-in-execution": "Certaines VMs sélectionnées sont en cours d'exécution",
Expand Down
Loading

0 comments on commit 8406813

Please sign in to comment.