@@ -4,6 +4,8 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { listAvailabilityDomains, listImages, listShapes } from '@/api/instances'
|
||||
import { listSubnets, listVcns } from '@/api/networks'
|
||||
import { listReservedIps } from '@/api/reservedips'
|
||||
import { freeReservedIpOptions, renderReservedIpLabel } from '@/components/network/reservedIpSelect'
|
||||
import { listLimits } from '@/api/tenant'
|
||||
import AppInputNumber from '@/components/AppInputNumber.vue'
|
||||
import FormField from '@/components/FormField.vue'
|
||||
@@ -42,6 +44,8 @@ const blank = {
|
||||
bootVolumeSizeGBs: null as number | null,
|
||||
bootVolumeVpusPerGB: 10,
|
||||
assignPublicIp: true,
|
||||
publicIpMode: 'ephemeral' as 'ephemeral' | 'reserved' | 'none',
|
||||
reservedPublicIpId: null as string | null,
|
||||
assignIpv6: true,
|
||||
authMode: 'password' as 'password' | 'ssh',
|
||||
randomPassword: true,
|
||||
@@ -173,6 +177,12 @@ function applySpec(spec: Record<string, unknown>) {
|
||||
if (typeof s.bootVolumeSizeGBs === 'number') form.bootVolumeSizeGBs = s.bootVolumeSizeGBs
|
||||
if (typeof s.bootVolumeVpusPerGB === 'number') form.bootVolumeVpusPerGB = s.bootVolumeVpusPerGB
|
||||
form.assignPublicIp = s.assignPublicIp !== false
|
||||
if (typeof s.reservedPublicIpId === 'string' && s.reservedPublicIpId) {
|
||||
form.publicIpMode = 'reserved'
|
||||
form.reservedPublicIpId = s.reservedPublicIpId
|
||||
} else {
|
||||
form.publicIpMode = s.assignPublicIp !== false ? 'ephemeral' : 'none'
|
||||
}
|
||||
form.assignIpv6 = s.assignIpv6 !== false
|
||||
form.authMode = typeof s.sshPublicKey === 'string' && s.sshPublicKey ? 'ssh' : 'password'
|
||||
form.sshPublicKey = typeof s.sshPublicKey === 'string' ? s.sshPublicKey : ''
|
||||
@@ -376,9 +386,33 @@ watch(ipv6Supported, (v) => {
|
||||
const valid = computed(() => {
|
||||
if (!form.imageId) return false
|
||||
if (form.vcnId !== AUTO_VCN && !form.subnetId) return false
|
||||
if (form.publicIpMode === 'reserved' && !form.reservedPublicIpId) return false
|
||||
return !isFlex.value || form.ocpus > 0
|
||||
})
|
||||
|
||||
// ---- 公网 IPv4 分配方式:临时 / 保留 / 不分配 ----
|
||||
const publicIpModes = [
|
||||
{ label: '临时 IP', value: 'ephemeral' },
|
||||
{ label: '保留 IP', value: 'reserved' },
|
||||
{ label: '不分配', value: 'none' },
|
||||
]
|
||||
const reservedIps = useAsync(async () => {
|
||||
if (props.cfgId === null) return []
|
||||
try {
|
||||
return await listReservedIps(props.cfgId, props.region || undefined, props.compartmentId || undefined)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}, false)
|
||||
const freeReservedOptions = computed(() => freeReservedIpOptions(reservedIps.data.value ?? []))
|
||||
watch(
|
||||
() => form.publicIpMode,
|
||||
(mode) => {
|
||||
if (mode === 'reserved') void reservedIps.run()
|
||||
else form.reservedPublicIpId = null
|
||||
},
|
||||
)
|
||||
|
||||
/** 规格摘要(父表单底部预估条用):shape 短名 + 弹性核内存 */
|
||||
const summary = computed(() => {
|
||||
const parts = [form.shape.replace(/^VM\.Standard\.?/, '') || form.shape]
|
||||
@@ -399,7 +433,9 @@ function buildSpec(): Record<string, unknown> {
|
||||
bootVolumeSizeGBs: form.bootVolumeSizeGBs ?? undefined,
|
||||
// 性能独立于大小:留空按镜像默认大小时同样生效(后端仅镜像启动源使用)
|
||||
bootVolumeVpusPerGB: form.bootVolumeVpusPerGB || undefined,
|
||||
assignPublicIp: form.assignPublicIp,
|
||||
assignPublicIp: form.publicIpMode === 'ephemeral',
|
||||
reservedPublicIpId:
|
||||
form.publicIpMode === 'reserved' ? (form.reservedPublicIpId ?? undefined) : undefined,
|
||||
assignIpv6: form.assignIpv6,
|
||||
generateRootPassword: usePwd && form.randomPassword ? true : undefined,
|
||||
rootPassword:
|
||||
@@ -578,8 +614,23 @@ defineExpose({ valid, buildSpec, reset, applySpec, summary })
|
||||
placeholder="ssh-ed25519 AAAA…"
|
||||
/>
|
||||
</FormField>
|
||||
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
|
||||
<FormField label="公网 IPv4" hint="保留 IP:实例就绪后自动绑定,期间短暂无公网地址;仅单台创建可用">
|
||||
<NSelect v-model:value="form.publicIpMode" :options="publicIpModes" />
|
||||
</FormField>
|
||||
<FormField v-if="form.publicIpMode === 'reserved'" label="保留 IP" required>
|
||||
<NSelect
|
||||
v-model:value="form.reservedPublicIpId"
|
||||
:options="freeReservedOptions"
|
||||
:loading="reservedIps.loading.value"
|
||||
:render-label="renderReservedIpLabel"
|
||||
:consistent-menu-width="false"
|
||||
placeholder="选择未绑定的保留 IP"
|
||||
filterable
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-5">
|
||||
<NCheckbox v-model:checked="form.assignPublicIp">分配公网 IPv4</NCheckbox>
|
||||
<NCheckbox v-model:checked="form.assignIpv6" :disabled="!ipv6Supported">
|
||||
分配 IPv6{{ ipv6Supported ? '' : '(所选子网未启用)' }}
|
||||
</NCheckbox>
|
||||
|
||||
Reference in New Issue
Block a user