mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
feat(lite): add action create internal network (#10235)
* feat(lite): add create network and network bonded actions * remove vlan required and info message * feedback number one * feat(lite): add create network and network bonded actions * feat(lite): add action create internal network * update changelog file * fix rebase * feedback number one * remove as const in cancelRoute * feedback number two
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- [Host/Network] Add ability to rescan physical network interfaces (PIFs) (PR [#10147](https://github.com/vatesfr/xen-orchestra/pull/10147))
|
||||
- Fix inconsistent spacing in side panel cards (PR [#10279](https://github.com/vatesfr/xen-orchestra/pull/10279))
|
||||
- Fix missing collapse buttons on pools and hosts in the tree sidebar (added `hasChildren` prop) (PR [#10244](https://github.com/vatesfr/xen-orchestra/pull/10244))
|
||||
- [Pool/networks] Add the possibility to create new internal network (PR [#10235](https://github.com/vatesfr/xen-orchestra/pull/10235))
|
||||
|
||||
## **0.24.0** (2026-07-30)
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<div class="pool-host-internal-networks-table">
|
||||
<UiTitle>
|
||||
{{ t('host-internal-networks') }}
|
||||
<template #action>
|
||||
<slot name="title-actions" />
|
||||
</template>
|
||||
</UiTitle>
|
||||
<div class="container">
|
||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||
@@ -44,6 +47,10 @@ const { networks } = defineProps<{
|
||||
networks: XenApiNetwork[]
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
'title-actions'(): any
|
||||
}>()
|
||||
|
||||
const { isReady, hasError } = useNetworkStore().subscribe()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -93,5 +93,22 @@ export function createNetworkOperations(xenApi: XenApi) {
|
||||
|
||||
return networkRef
|
||||
},
|
||||
|
||||
createInternal: async (params: BaseNetworkCreateParams): Promise<XenApiNetwork['$ref']> => {
|
||||
const { nameLabel, nameDescription = '', mtu = 1500, nbd } = params
|
||||
|
||||
const networkRef = await createEmptyNetwork(nameLabel, nameDescription, mtu)
|
||||
|
||||
if (nbd) {
|
||||
try {
|
||||
await xenApi.call('network.add_purpose', [networkRef, 'nbd'])
|
||||
} catch (error) {
|
||||
await xenApi.call('network.destroy', [networkRef])
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return networkRef
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<form class="new-internal-network-form" novalidate @submit.prevent="onSubmit()">
|
||||
<NetworkFormTextInput v-bind="poolInputBindings" class="pool" />
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<NetworkFormTextInput v-bind="nameInputBindings" />
|
||||
<NetworkFormNumberInput v-bind="mtuInputBindings" />
|
||||
</div>
|
||||
<NewNetworkDescriptionTextarea v-bind="descriptionInputBindings" />
|
||||
</div>
|
||||
<div class="nbd">
|
||||
<NewNetworkNbdCheckbox v-bind="nbdCheckboxBindings" />
|
||||
</div>
|
||||
<NewNetworkButtonsSection :cancel-to :submit-label="t('action:create-internal-network')" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NetworkFormNumberInput from '@/modules/network/components/form/new/inputs/NetworkFormNumberInput.vue'
|
||||
import NetworkFormTextInput from '@/modules/network/components/form/new/inputs/NetworkFormTextInput.vue'
|
||||
import NewNetworkDescriptionTextarea from '@/modules/network/components/form/new/inputs/NewNetworkDescriptionTextarea.vue'
|
||||
import NewNetworkNbdCheckbox from '@/modules/network/components/form/new/inputs/NewNetworkNbdCheckbox.vue'
|
||||
import NewNetworkButtonsSection from '@/modules/network/components/form/new/NewNetworkButtonsSection.vue'
|
||||
import { useNewInternalNetworkForm } from '@/modules/network/form/new-internal/use-new-internal-network-form.ts'
|
||||
import type { NewInternalNetworkPayload } from '@/modules/network/jobs/internal-network-create.job.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
defineProps<{
|
||||
cancelTo: RouteLocationRaw
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
create: [data: NewInternalNetworkPayload]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const {
|
||||
poolInputBindings,
|
||||
nameInputBindings,
|
||||
mtuInputBindings,
|
||||
descriptionInputBindings,
|
||||
nbdCheckboxBindings,
|
||||
validateAndBuildPayload,
|
||||
} = useNewInternalNetworkForm()
|
||||
|
||||
async function onSubmit() {
|
||||
const payload = await validateAndBuildPayload()
|
||||
|
||||
if (payload !== undefined) {
|
||||
emit('create', payload)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.new-internal-network-form {
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
gap: 2.4rem;
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (--medium-or-large) {
|
||||
flex-direction: row;
|
||||
gap: 8rem;
|
||||
max-width: 88rem;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-block-start: 2.4rem;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--medium-or-large) {
|
||||
.pool {
|
||||
max-width: 40rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nbd {
|
||||
margin-block-start: 2.4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { type BaseNetworkFormData, useNetworkFormBase } from '@/modules/network/form/use-network-form-base.ts'
|
||||
import type { NewInternalNetworkPayload } from '@/modules/network/jobs/internal-network-create.job.ts'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export function useNewInternalNetworkForm() {
|
||||
const formData = reactive<BaseNetworkFormData>({
|
||||
name: '',
|
||||
description: '',
|
||||
mtu: undefined,
|
||||
nbd: false,
|
||||
})
|
||||
|
||||
const {
|
||||
buildBasePayload,
|
||||
poolInputBindings,
|
||||
validate,
|
||||
nameInputBindings,
|
||||
descriptionInputBindings,
|
||||
mtuInputBindings,
|
||||
nbdCheckboxBindings,
|
||||
} = useNetworkFormBase(formData)
|
||||
|
||||
async function validateAndBuildPayload(): Promise<NewInternalNetworkPayload | undefined> {
|
||||
const valid = await validate()
|
||||
|
||||
if (!valid) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return buildBasePayload()
|
||||
}
|
||||
|
||||
return {
|
||||
poolInputBindings,
|
||||
nameInputBindings,
|
||||
descriptionInputBindings,
|
||||
mtuInputBindings,
|
||||
nbdCheckboxBindings,
|
||||
validateAndBuildPayload,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NewBondedNetworkPayload } from '@/modules/network/jobs/bonded-network-create.job.ts'
|
||||
import { defineJobArg } from '@core/packages/job'
|
||||
|
||||
export const payloadsArg = defineJobArg<NewBondedNetworkPayload>({
|
||||
export const payloadArg = defineJobArg<NewBondedNetworkPayload>({
|
||||
identify: payload => payload.name,
|
||||
toArray: true,
|
||||
toArray: false,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { XenApiNetwork, XenApiPif } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { payloadsArg } from '@/modules/network/jobs/bonded-network-create-args.ts'
|
||||
import { payloadArg } from '@/modules/network/jobs/bonded-network-create-args.ts'
|
||||
import type { BaseNewNetworkPayload } from '@/modules/network/jobs/network-create.job.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||
@@ -11,50 +11,44 @@ export type NewBondedNetworkPayload = BaseNewNetworkPayload & {
|
||||
bondMode: BOND_MODE
|
||||
}
|
||||
|
||||
export const useBondedNetworkCreateJob = defineJob('bonded-network.create', [payloadsArg], () => {
|
||||
export const useBondedNetworkCreateJob = defineJob('bonded-network.create', [payloadArg], () => {
|
||||
const xapi = useXenApiStore().getXapi()
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
run(payloads): Promise<PromiseSettledResult<XenApiNetwork['uuid']>[]> {
|
||||
return Promise.allSettled(
|
||||
payloads.map(async payload => {
|
||||
const networkRef = await xapi.network.createBonded({
|
||||
nameLabel: payload.name,
|
||||
nameDescription: payload.description,
|
||||
mtu: payload.mtu,
|
||||
pifRefs: payload.pifRefs,
|
||||
bondMode: payload.bondMode,
|
||||
nbd: payload.nbd,
|
||||
})
|
||||
async run(payload): Promise<XenApiNetwork['uuid']> {
|
||||
const networkRef = await xapi.network.createBonded({
|
||||
nameLabel: payload.name,
|
||||
nameDescription: payload.description,
|
||||
mtu: payload.mtu,
|
||||
pifRefs: payload.pifRefs,
|
||||
bondMode: payload.bondMode,
|
||||
nbd: payload.nbd,
|
||||
})
|
||||
|
||||
return xapi.getField<XenApiNetwork['uuid']>('network', networkRef, 'uuid')
|
||||
})
|
||||
)
|
||||
return xapi.getField<XenApiNetwork['uuid']>('network', networkRef, 'uuid')
|
||||
},
|
||||
|
||||
validate(isRunning, payloads) {
|
||||
validate(isRunning, payload) {
|
||||
if (isRunning) {
|
||||
throw new JobRunningError(t('job:create:in-progress'))
|
||||
}
|
||||
|
||||
if (payloads.length === 0) {
|
||||
if (payload === undefined) {
|
||||
throw new JobError(t('job:arg:missing-payload'))
|
||||
}
|
||||
|
||||
payloads.forEach(payload => {
|
||||
if (payload.name.length === 0) {
|
||||
throw new JobError(t('job:arg:name-required'))
|
||||
}
|
||||
if (payload.name.length === 0) {
|
||||
throw new JobError(t('job:arg:name-required'))
|
||||
}
|
||||
|
||||
if (payload.pifRefs.length === 0) {
|
||||
throw new JobError(t('job:arg:pif-ids-required'))
|
||||
}
|
||||
if (payload.pifRefs.length === 0) {
|
||||
throw new JobError(t('job:arg:pif-ids-required'))
|
||||
}
|
||||
|
||||
if (payload.bondMode === undefined) {
|
||||
throw new JobError(t('job:arg:bond-mode-required'))
|
||||
}
|
||||
})
|
||||
if (payload.bondMode === undefined) {
|
||||
throw new JobError(t('job:arg:bond-mode-required'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { NewInternalNetworkPayload } from '@/modules/network/jobs/internal-network-create.job.ts'
|
||||
import { defineJobArg } from '@core/packages/job'
|
||||
|
||||
export const payloadArg = defineJobArg<NewInternalNetworkPayload>({
|
||||
identify: payload => payload.name,
|
||||
toArray: false,
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { payloadArg } from '@/modules/network/jobs/internal-network-create-args.ts'
|
||||
import type { BaseNewNetworkPayload } from '@/modules/network/jobs/network-create.job.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export type NewInternalNetworkPayload = BaseNewNetworkPayload
|
||||
|
||||
export const useInternalNetworkCreateJob = defineJob('internal-network.create', [payloadArg], () => {
|
||||
const xapi = useXenApiStore().getXapi()
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
async run(payload): Promise<XenApiNetwork['uuid']> {
|
||||
const networkRef = await xapi.network.createInternal({
|
||||
nameLabel: payload.name,
|
||||
nameDescription: payload.description,
|
||||
mtu: payload.mtu,
|
||||
nbd: payload.nbd,
|
||||
})
|
||||
|
||||
return xapi.getField<XenApiNetwork['uuid']>('network', networkRef, 'uuid')
|
||||
},
|
||||
|
||||
validate(isRunning, payload) {
|
||||
if (isRunning) {
|
||||
throw new JobRunningError(t('job:create:in-progress'))
|
||||
}
|
||||
|
||||
if (payload === undefined) {
|
||||
throw new JobError(t('job:arg:missing-payload'))
|
||||
}
|
||||
|
||||
if (payload.name.length === 0) {
|
||||
throw new JobError(t('job:arg:name-required'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NewNetworkPayload } from '@/modules/network/jobs/network-create.job.ts'
|
||||
import { defineJobArg } from '@core/packages/job'
|
||||
|
||||
export const payloadsArg = defineJobArg<NewNetworkPayload>({
|
||||
export const payloadArg = defineJobArg<NewNetworkPayload>({
|
||||
identify: payload => payload.name,
|
||||
toArray: true,
|
||||
toArray: false,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { XenApiNetwork, XenApiPif } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import { payloadsArg } from '@/modules/network/jobs/network-create-args.ts'
|
||||
import { payloadArg } from '@/modules/network/jobs/network-create-args.ts'
|
||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -16,46 +16,40 @@ export type NewNetworkPayload = BaseNewNetworkPayload & {
|
||||
vlan?: number
|
||||
}
|
||||
|
||||
export const useNetworkCreateJob = defineJob('network.create', [payloadsArg], () => {
|
||||
export const useNetworkCreateJob = defineJob('network.create', [payloadArg], () => {
|
||||
const xapi = useXenApiStore().getXapi()
|
||||
const { t } = useI18n()
|
||||
|
||||
return {
|
||||
run(payloads): Promise<PromiseSettledResult<XenApiNetwork['uuid']>[]> {
|
||||
return Promise.allSettled(
|
||||
payloads.map(async payload => {
|
||||
const networkRef = await xapi.network.create({
|
||||
nameLabel: payload.name,
|
||||
nameDescription: payload.description,
|
||||
mtu: payload.mtu,
|
||||
vlan: payload.vlan,
|
||||
pifRef: payload.pifRef,
|
||||
nbd: payload.nbd,
|
||||
})
|
||||
async run(payload): Promise<XenApiNetwork['uuid']> {
|
||||
const networkRef = await xapi.network.create({
|
||||
nameLabel: payload.name,
|
||||
nameDescription: payload.description,
|
||||
mtu: payload.mtu,
|
||||
vlan: payload.vlan,
|
||||
pifRef: payload.pifRef,
|
||||
nbd: payload.nbd,
|
||||
})
|
||||
|
||||
return xapi.getField<XenApiNetwork['uuid']>('network', networkRef, 'uuid')
|
||||
})
|
||||
)
|
||||
return xapi.getField<XenApiNetwork['uuid']>('network', networkRef, 'uuid')
|
||||
},
|
||||
|
||||
validate(isRunning, payloads) {
|
||||
validate(isRunning, payload) {
|
||||
if (isRunning) {
|
||||
throw new JobRunningError(t('job:create:in-progress'))
|
||||
}
|
||||
|
||||
if (payloads.length === 0) {
|
||||
if (payload === undefined) {
|
||||
throw new JobError(t('job:arg:missing-payload'))
|
||||
}
|
||||
|
||||
payloads.forEach(payload => {
|
||||
if (payload.name.length === 0) {
|
||||
throw new JobError(t('job:arg:name-required'))
|
||||
}
|
||||
if (payload.name.length === 0) {
|
||||
throw new JobError(t('job:arg:name-required'))
|
||||
}
|
||||
|
||||
if (payload.pifRef === undefined) {
|
||||
throw new JobError(t('job:arg:pif-id-required'))
|
||||
}
|
||||
})
|
||||
if (payload.pifRef === undefined) {
|
||||
throw new JobError(t('job:arg:pif-id-required'))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -67,15 +67,11 @@ async function createNetwork(newPayload: NewBondedNetworkPayload) {
|
||||
}
|
||||
|
||||
try {
|
||||
const [promiseCreateResult] = await create()
|
||||
const networkId = await create()
|
||||
|
||||
if (promiseCreateResult.status === 'rejected') {
|
||||
throw promiseCreateResult.reason
|
||||
}
|
||||
|
||||
redirectAfterSuccess(promiseCreateResult.value)
|
||||
} catch (_error) {
|
||||
error.value = _error as Error
|
||||
redirectAfterSuccess(networkId)
|
||||
} catch (rawError) {
|
||||
error.value = rawError as Error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
103
@xen-orchestra/lite/src/pages/network/new-internal.vue
Normal file
103
@xen-orchestra/lite/src/pages/network/new-internal.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<UiHeadBar icon="fa:plus">
|
||||
{{ t('new-network:add-internal') }}
|
||||
</UiHeadBar>
|
||||
|
||||
<div class="card-container">
|
||||
<VtsOperationPendingCard v-if="isRunning" :title="t('creating-new-network')" />
|
||||
|
||||
<VtsOperationErrorCard
|
||||
v-else-if="hasNetworkCreationError && error"
|
||||
:title="t('unable-to-create-new-internal-network')"
|
||||
:error
|
||||
:error-message="t('new-network:error-message')"
|
||||
>
|
||||
<template #actions>
|
||||
<UiButton variant="secondary" accent="brand" size="medium" @click="handleGoBack()">
|
||||
{{ t('action:go-back') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</VtsOperationErrorCard>
|
||||
|
||||
<UiCard v-show="canDisplayForm">
|
||||
<UiTitle>{{ t('configuration') }}</UiTitle>
|
||||
<NewInternalNetworkForm :cancel-to="cancelRoute" @create="createNetwork" />
|
||||
</UiCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { XenApiNetwork } from '@/libs/xen-api/xen-api.types.ts'
|
||||
import NewInternalNetworkForm from '@/modules/network/components/form/new/NewInternalNetworkForm.vue'
|
||||
import {
|
||||
type NewInternalNetworkPayload,
|
||||
useInternalNetworkCreateJob,
|
||||
} from '@/modules/network/jobs/internal-network-create.job.ts'
|
||||
import { getPoolNetworkRoute } from '@/modules/network/utils/network.util.ts'
|
||||
import { usePoolStore } from '@/stores/xen-api/pool.store.ts'
|
||||
import VtsOperationErrorCard from '@core/components/operation-error-card/VtsOperationErrorCard.vue'
|
||||
import VtsOperationPendingCard from '@core/components/operation-pending-card/VtsOperationPendingCard.vue'
|
||||
import UiButton from '@core/components/ui/button/UiButton.vue'
|
||||
import UiCard from '@core/components/ui/card/UiCard.vue'
|
||||
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
|
||||
import UiTitle from '@core/components/ui/title/UiTitle.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { type RouteLocationRaw, useRouter } from 'vue-router'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const router = useRouter()
|
||||
const { pool } = usePoolStore().subscribe()
|
||||
|
||||
const error = ref<Error | undefined>()
|
||||
const hasNetworkCreationError = computed(() => error.value !== undefined)
|
||||
|
||||
const formPayload = ref<NewInternalNetworkPayload>()
|
||||
|
||||
const { canRun, run: create, isRunning } = useInternalNetworkCreateJob(formPayload)
|
||||
|
||||
const canDisplayForm = computed(() => !isRunning.value && !hasNetworkCreationError.value)
|
||||
|
||||
const cancelRoute = computed<RouteLocationRaw>(() => {
|
||||
if (pool.value === undefined) {
|
||||
return { name: '/' }
|
||||
}
|
||||
|
||||
return getPoolNetworkRoute(pool.value.uuid)
|
||||
})
|
||||
|
||||
async function createNetwork(newPayload: NewInternalNetworkPayload) {
|
||||
formPayload.value = newPayload
|
||||
|
||||
if (!canRun.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const networkId = await create()
|
||||
|
||||
redirectAfterSuccess(networkId)
|
||||
} catch (rawError) {
|
||||
error.value = rawError as Error
|
||||
}
|
||||
}
|
||||
|
||||
function handleGoBack() {
|
||||
error.value = undefined
|
||||
}
|
||||
|
||||
function redirectAfterSuccess(networkId: XenApiNetwork['uuid']) {
|
||||
if (pool.value === undefined) {
|
||||
return router.push({ name: '/' })
|
||||
}
|
||||
|
||||
return router.push(getPoolNetworkRoute(pool.value.uuid, networkId))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.card-container {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
@@ -64,15 +64,11 @@ async function createNetwork(newPayload: NewNetworkPayload) {
|
||||
}
|
||||
|
||||
try {
|
||||
const [promiseCreateResult] = await create()
|
||||
const networkId = await create()
|
||||
|
||||
if (promiseCreateResult.status === 'rejected') {
|
||||
throw promiseCreateResult.reason
|
||||
}
|
||||
|
||||
redirectAfterSuccess(promiseCreateResult.value)
|
||||
} catch (_error) {
|
||||
error.value = _error as Error
|
||||
redirectAfterSuccess(networkId)
|
||||
} catch (rawError) {
|
||||
error.value = rawError as Error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,13 @@
|
||||
</MenuList>
|
||||
</template>
|
||||
</PoolNetworksTable>
|
||||
<PoolHostInternalNetworksTable :networks="networksWithoutPifs" />
|
||||
<PoolHostInternalNetworksTable :networks="networksWithoutPifs">
|
||||
<template #title-actions>
|
||||
<UiLink :to="{ name: '/network/new-internal' }" icon="fa:plus" size="medium">
|
||||
{{ t('new') }}
|
||||
</UiLink>
|
||||
</template>
|
||||
</PoolHostInternalNetworksTable>
|
||||
</UiCard>
|
||||
<PoolNetworkSidePanel :network="selectedNetwork" @close="selectedNetwork = undefined" />
|
||||
</VtsContentSidePanel>
|
||||
@@ -43,6 +49,7 @@ import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
usePageTitleStore().setTitle(t('network'))
|
||||
|
||||
const { getByUuid, networksWithPifs, networksWithoutPifs, isReady, hasError } = useNetworkStore().subscribe()
|
||||
|
||||
13
@xen-orchestra/lite/src/route-map.d.ts
vendored
13
@xen-orchestra/lite/src/route-map.d.ts
vendored
@@ -120,6 +120,13 @@ declare module 'vue-router/auto-routes' {
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/network/new-internal': RouteRecordInfo<
|
||||
'/network/new-internal',
|
||||
'/network/new-internal',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/object-not-found': RouteRecordInfo<
|
||||
'/object-not-found',
|
||||
'/object-not-found',
|
||||
@@ -1144,6 +1151,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/network/new-internal.vue': {
|
||||
routes:
|
||||
| '/network/new-internal'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/object-not-found.vue': {
|
||||
routes:
|
||||
| '/object-not-found'
|
||||
|
||||
Reference in New Issue
Block a user