初始提交: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
@@ -0,0 +1,114 @@
<script setup lang="ts">
import { NCheckbox, NSelect, useMessage, type SelectGroupOption } from 'naive-ui'
import { computed, ref, watch } from 'vue'
import { listBootVolumes } from '@/api/bootVolumes'
import { attachVolume, listVolumes } from '@/api/instances'
import FormField from '@/components/FormField.vue'
import FormModal from '@/components/FormModal.vue'
import { useAsync } from '@/composables/useAsync'
const props = defineProps<{
show: boolean
cfgId: number
instanceId: string
region?: string
availabilityDomain: string
attachedVolumeIds: string[]
}>()
const emit = defineEmits<{ 'update:show': [boolean]; attached: [] }>()
const message = useMessage()
const submitting = ref(false)
const volumeId = ref<string | null>(null)
const readOnly = ref(false)
const volumes = useAsync(
() => listVolumes(props.cfgId, props.availabilityDomain, undefined, props.region),
false,
)
/** 未挂载的引导卷可作为数据卷附加(OCI attachVolume 直接接受引导卷 OCID */
const bootVolumes = useAsync(
() =>
listBootVolumes(props.cfgId, props.availabilityDomain, undefined, props.region).catch(() => []),
false,
)
watch(
() => props.show,
(v) => {
if (v) {
volumeId.value = null
readOnly.value = false
void volumes.run()
void bootVolumes.run()
}
},
)
const options = computed<SelectGroupOption[]>(() => {
const vols = (volumes.data.value ?? [])
.filter((v) => v.lifecycleState === 'AVAILABLE' && !props.attachedVolumeIds.includes(v.id))
.map((v) => ({ label: `${v.displayName}${v.sizeInGBs} GB`, value: v.id }))
const bvs = (bootVolumes.data.value ?? [])
.filter(
(b) =>
b.lifecycleState === 'AVAILABLE' &&
!b.attachedInstanceId &&
!props.attachedVolumeIds.includes(b.id),
)
.map((b) => ({ label: `${b.displayName}${b.sizeInGBs} GB`, value: b.id }))
const groups: SelectGroupOption[] = []
if (vols.length) groups.push({ type: 'group', label: '块卷', key: 'vol', children: vols })
if (bvs.length) groups.push({ type: 'group', label: '引导卷(作数据卷)', key: 'bv', children: bvs })
return groups
})
const loading = computed(() => volumes.loading.value || bootVolumes.loading.value)
async function submit() {
if (!volumeId.value) return
submitting.value = true
try {
await attachVolume(props.cfgId, props.instanceId, volumeId.value, readOnly.value, props.region)
message.success('附加请求已提交,稍候在 OS 内可见新磁盘')
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="附加块卷"
:submitting="submitting"
:submit-disabled="!volumeId"
submit-text="附加"
@update:show="emit('update:show', $event)"
@submit="submit"
>
<FormField
label="块卷 / 引导卷"
required
hint="同可用域的块卷,或未挂载的引导卷(作为数据卷附加)"
:error="!loading && !options.length ? '同可用域内没有可附加的块卷或引导卷' : undefined"
>
<NSelect
v-model:value="volumeId"
:options="options"
:loading="loading"
placeholder="选择块卷或引导卷"
/>
</FormField>
<NCheckbox v-model:checked="readOnly">只读附加</NCheckbox>
<div class="mt-2 text-xs leading-relaxed text-ink-3">
半虚拟化附加运行中实例可热插拔附加后仍需在实例 OS 内分区挂载文件系统
</div>
</FormModal>
</template>