初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
<script setup lang="ts">
import { NInput, NSelect, useMessage, type SelectOption } from 'naive-ui'
import { computed, ref, watch } from 'vue'
import { attachVnic } from '@/api/instances'
import { listSubnets } from '@/api/networks'
import FormField from '@/components/FormField.vue'
import FormModal from '@/components/FormModal.vue'
import ToggleCard from '@/components/ToggleCard.vue'
import { useAsync } from '@/composables/useAsync'
import { shortAd } from '@/composables/useFormat'
const props = defineProps<{
show: boolean
cfgId: number
instanceId: string
region?: string
/** 已附加网卡数,仅用于配额提示文案 */
attachedCount: number
}>()
const emit = defineEmits<{ 'update:show': [boolean]; attached: [] }>()
const message = useMessage()
const submitting = ref(false)
const subnetId = ref<string | null>(null)
const displayName = ref('')
const privateIp = ref('')
const assignPublicIp = ref(false)
const assignIpv6 = ref(false)
/** vcnId 传空列全部子网:次要网卡允许跨 VCN */
const subnets = useAsync(() => listSubnets(props.cfgId, '', props.region), false)
watch(
() => props.show,
(v) => {
if (v) {
subnetId.value = null
displayName.value = ''
privateIp.value = ''
assignPublicIp.value = false
assignIpv6.value = false
void subnets.run()
}
},
)
const options = computed<SelectOption[]>(() =>
(subnets.data.value ?? [])
.filter((s) => s.lifecycleState === 'AVAILABLE')
.map((s) => ({
label: `${s.displayName}${s.cidrBlock}${s.availabilityDomain ? ` · ${shortAd(s.availabilityDomain)}` : ''}`,
value: s.id,
})),
)
const selected = computed(() => (subnets.data.value ?? []).find((s) => s.id === subnetId.value))
/** 私有子网禁分公网 IP;未启用 IPv6 的子网禁自动分配 */
const publicIpBlocked = computed(() => !!selected.value?.prohibitPublicIp)
const ipv6Blocked = computed(() => !!selected.value && !selected.value.ipv6CidrBlock)
watch(selected, (s) => {
if (s?.prohibitPublicIp) assignPublicIp.value = false
if (s && !s.ipv6CidrBlock) assignIpv6.value = false
})
async function submit() {
if (!subnetId.value) return
submitting.value = true
try {
await attachVnic(props.cfgId, props.instanceId, {
region: props.region,
subnetId: subnetId.value,
displayName: displayName.value.trim(),
privateIp: privateIp.value.trim(),
assignPublicIp: assignPublicIp.value,
assignIpv6: assignIpv6.value,
})
message.success('附加请求已提交,OS 内需自行配置新网卡 IP')
emit('update:show', false)
emit('attached')
} catch (e) {
message.error(e instanceof Error ? e.message : '附加失败')
} finally {
submitting.value = false
}
}
</script>
<template>
<FormModal
:show="show"
title="附加 VNIC"
:submitting="submitting"
:submit-disabled="!subnetId"
submit-text="附加"
@update:show="emit('update:show', $event)"
@submit="submit"
>
<FormField
label="子网"
required
hint="与实例同 AD 的子网;跨 VCN 子网亦可选"
:error="
!subnets.loading.value && !options.length ? '该区域没有可用子网,请先创建' : undefined
"
>
<NSelect
v-model:value="subnetId"
:options="options"
:loading="subnets.loading.value"
filterable
placeholder="选择子网"
/>
</FormField>
<FormField label="显示名称">
<NInput v-model:value="displayName" placeholder="留空自动生成" />
</FormField>
<FormField label="私网 IP">
<NInput v-model:value="privateIp" placeholder="留空自动分配(推荐)" />
</FormField>
<div class="flex flex-col gap-2.5">
<ToggleCard
v-model="assignPublicIp"
title="分配公网 IP"
:desc="publicIpBlocked ? '所选子网为私有子网,不可分配公网 IP' : '仅公有子网可用;临时地址,分离即释放'"
:disabled="publicIpBlocked"
/>
<ToggleCard
v-model="assignIpv6"
title="自动分配 IPv6"
:desc="
ipv6Blocked
? '所选子网未启用 IPv6(可在 VCN 详情一键启用)'
: '附加时同时分一个 IPv6;后续可在网卡列表继续添加'
"
:disabled="ipv6Blocked"
/>
</div>
<div class="mt-2 rounded-md bg-wash px-3 py-2 text-xs leading-relaxed text-ink-3">
当前已附加 {{ attachedCount }} 张网卡可附加数量由 shape 决定 A1.Flex OCPU 1
超出配额时 OCI 会拒绝请求
</div>
</FormModal>
</template>