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))
|
- [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 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))
|
- 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)
|
## **0.24.0** (2026-07-30)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<div class="pool-host-internal-networks-table">
|
<div class="pool-host-internal-networks-table">
|
||||||
<UiTitle>
|
<UiTitle>
|
||||||
{{ t('host-internal-networks') }}
|
{{ t('host-internal-networks') }}
|
||||||
|
<template #action>
|
||||||
|
<slot name="title-actions" />
|
||||||
|
</template>
|
||||||
</UiTitle>
|
</UiTitle>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
<UiQuerySearchBar @search="value => (searchQuery = value)" />
|
||||||
@@ -44,6 +47,10 @@ const { networks } = defineProps<{
|
|||||||
networks: XenApiNetwork[]
|
networks: XenApiNetwork[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
'title-actions'(): any
|
||||||
|
}>()
|
||||||
|
|
||||||
const { isReady, hasError } = useNetworkStore().subscribe()
|
const { isReady, hasError } = useNetworkStore().subscribe()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|||||||
@@ -93,5 +93,22 @@ export function createNetworkOperations(xenApi: XenApi) {
|
|||||||
|
|
||||||
return networkRef
|
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 type { NewBondedNetworkPayload } from '@/modules/network/jobs/bonded-network-create.job.ts'
|
||||||
import { defineJobArg } from '@core/packages/job'
|
import { defineJobArg } from '@core/packages/job'
|
||||||
|
|
||||||
export const payloadsArg = defineJobArg<NewBondedNetworkPayload>({
|
export const payloadArg = defineJobArg<NewBondedNetworkPayload>({
|
||||||
identify: payload => payload.name,
|
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 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 type { BaseNewNetworkPayload } from '@/modules/network/jobs/network-create.job.ts'
|
||||||
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
import { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||||
@@ -11,50 +11,44 @@ export type NewBondedNetworkPayload = BaseNewNetworkPayload & {
|
|||||||
bondMode: BOND_MODE
|
bondMode: BOND_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBondedNetworkCreateJob = defineJob('bonded-network.create', [payloadsArg], () => {
|
export const useBondedNetworkCreateJob = defineJob('bonded-network.create', [payloadArg], () => {
|
||||||
const xapi = useXenApiStore().getXapi()
|
const xapi = useXenApiStore().getXapi()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run(payloads): Promise<PromiseSettledResult<XenApiNetwork['uuid']>[]> {
|
async run(payload): Promise<XenApiNetwork['uuid']> {
|
||||||
return Promise.allSettled(
|
const networkRef = await xapi.network.createBonded({
|
||||||
payloads.map(async payload => {
|
nameLabel: payload.name,
|
||||||
const networkRef = await xapi.network.createBonded({
|
nameDescription: payload.description,
|
||||||
nameLabel: payload.name,
|
mtu: payload.mtu,
|
||||||
nameDescription: payload.description,
|
pifRefs: payload.pifRefs,
|
||||||
mtu: payload.mtu,
|
bondMode: payload.bondMode,
|
||||||
pifRefs: payload.pifRefs,
|
nbd: payload.nbd,
|
||||||
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) {
|
if (isRunning) {
|
||||||
throw new JobRunningError(t('job:create:in-progress'))
|
throw new JobRunningError(t('job:create:in-progress'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payloads.length === 0) {
|
if (payload === undefined) {
|
||||||
throw new JobError(t('job:arg:missing-payload'))
|
throw new JobError(t('job:arg:missing-payload'))
|
||||||
}
|
}
|
||||||
|
|
||||||
payloads.forEach(payload => {
|
if (payload.name.length === 0) {
|
||||||
if (payload.name.length === 0) {
|
throw new JobError(t('job:arg:name-required'))
|
||||||
throw new JobError(t('job:arg:name-required'))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.pifRefs.length === 0) {
|
if (payload.pifRefs.length === 0) {
|
||||||
throw new JobError(t('job:arg:pif-ids-required'))
|
throw new JobError(t('job:arg:pif-ids-required'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.bondMode === undefined) {
|
if (payload.bondMode === undefined) {
|
||||||
throw new JobError(t('job:arg:bond-mode-required'))
|
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 type { NewNetworkPayload } from '@/modules/network/jobs/network-create.job.ts'
|
||||||
import { defineJobArg } from '@core/packages/job'
|
import { defineJobArg } from '@core/packages/job'
|
||||||
|
|
||||||
export const payloadsArg = defineJobArg<NewNetworkPayload>({
|
export const payloadArg = defineJobArg<NewNetworkPayload>({
|
||||||
identify: payload => payload.name,
|
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 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 { useXenApiStore } from '@/stores/xen-api.store.ts'
|
||||||
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
import { defineJob, JobError, JobRunningError } from '@core/packages/job'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
@@ -16,46 +16,40 @@ export type NewNetworkPayload = BaseNewNetworkPayload & {
|
|||||||
vlan?: number
|
vlan?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNetworkCreateJob = defineJob('network.create', [payloadsArg], () => {
|
export const useNetworkCreateJob = defineJob('network.create', [payloadArg], () => {
|
||||||
const xapi = useXenApiStore().getXapi()
|
const xapi = useXenApiStore().getXapi()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run(payloads): Promise<PromiseSettledResult<XenApiNetwork['uuid']>[]> {
|
async run(payload): Promise<XenApiNetwork['uuid']> {
|
||||||
return Promise.allSettled(
|
const networkRef = await xapi.network.create({
|
||||||
payloads.map(async payload => {
|
nameLabel: payload.name,
|
||||||
const networkRef = await xapi.network.create({
|
nameDescription: payload.description,
|
||||||
nameLabel: payload.name,
|
mtu: payload.mtu,
|
||||||
nameDescription: payload.description,
|
vlan: payload.vlan,
|
||||||
mtu: payload.mtu,
|
pifRef: payload.pifRef,
|
||||||
vlan: payload.vlan,
|
nbd: payload.nbd,
|
||||||
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) {
|
if (isRunning) {
|
||||||
throw new JobRunningError(t('job:create:in-progress'))
|
throw new JobRunningError(t('job:create:in-progress'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payloads.length === 0) {
|
if (payload === undefined) {
|
||||||
throw new JobError(t('job:arg:missing-payload'))
|
throw new JobError(t('job:arg:missing-payload'))
|
||||||
}
|
}
|
||||||
|
|
||||||
payloads.forEach(payload => {
|
if (payload.name.length === 0) {
|
||||||
if (payload.name.length === 0) {
|
throw new JobError(t('job:arg:name-required'))
|
||||||
throw new JobError(t('job:arg:name-required'))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.pifRef === undefined) {
|
if (payload.pifRef === undefined) {
|
||||||
throw new JobError(t('job:arg:pif-id-required'))
|
throw new JobError(t('job:arg:pif-id-required'))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,15 +67,11 @@ async function createNetwork(newPayload: NewBondedNetworkPayload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [promiseCreateResult] = await create()
|
const networkId = await create()
|
||||||
|
|
||||||
if (promiseCreateResult.status === 'rejected') {
|
redirectAfterSuccess(networkId)
|
||||||
throw promiseCreateResult.reason
|
} catch (rawError) {
|
||||||
}
|
error.value = rawError as Error
|
||||||
|
|
||||||
redirectAfterSuccess(promiseCreateResult.value)
|
|
||||||
} catch (_error) {
|
|
||||||
error.value = _error 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 {
|
try {
|
||||||
const [promiseCreateResult] = await create()
|
const networkId = await create()
|
||||||
|
|
||||||
if (promiseCreateResult.status === 'rejected') {
|
redirectAfterSuccess(networkId)
|
||||||
throw promiseCreateResult.reason
|
} catch (rawError) {
|
||||||
}
|
error.value = rawError as Error
|
||||||
|
|
||||||
redirectAfterSuccess(promiseCreateResult.value)
|
|
||||||
} catch (_error) {
|
|
||||||
error.value = _error as Error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,13 @@
|
|||||||
</MenuList>
|
</MenuList>
|
||||||
</template>
|
</template>
|
||||||
</PoolNetworksTable>
|
</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>
|
</UiCard>
|
||||||
<PoolNetworkSidePanel :network="selectedNetwork" @close="selectedNetwork = undefined" />
|
<PoolNetworkSidePanel :network="selectedNetwork" @close="selectedNetwork = undefined" />
|
||||||
</VtsContentSidePanel>
|
</VtsContentSidePanel>
|
||||||
@@ -43,6 +49,7 @@ import { useRouteQuery } from '@core/composables/route-query.composable.ts'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
usePageTitleStore().setTitle(t('network'))
|
usePageTitleStore().setTitle(t('network'))
|
||||||
|
|
||||||
const { getByUuid, networksWithPifs, networksWithoutPifs, isReady, hasError } = useNetworkStore().subscribe()
|
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>,
|
Record<never, 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': RouteRecordInfo<
|
||||||
'/object-not-found',
|
'/object-not-found',
|
||||||
'/object-not-found',
|
'/object-not-found',
|
||||||
@@ -1144,6 +1151,12 @@ declare module 'vue-router/auto-routes' {
|
|||||||
views:
|
views:
|
||||||
| never
|
| never
|
||||||
}
|
}
|
||||||
|
'src/pages/network/new-internal.vue': {
|
||||||
|
routes:
|
||||||
|
| '/network/new-internal'
|
||||||
|
views:
|
||||||
|
| never
|
||||||
|
}
|
||||||
'src/pages/object-not-found.vue': {
|
'src/pages/object-not-found.vue': {
|
||||||
routes:
|
routes:
|
||||||
| '/object-not-found'
|
| '/object-not-found'
|
||||||
|
|||||||
Reference in New Issue
Block a user