Files
oci-portal-dash/src/components/instance/AttachVolumeModal.vue
T
Wang Defa 5464d2dfea
CI / test (push) Successful in 27s
Release / release (push) Successful in 26s
发布 0.1.0:通知渠道、告警规则与多项体验修复
2026-07-10 17:31:19 +08:00

116 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { NCheckbox, NSelect, 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'
import { useToast } from '@/composables/useToast'
const props = defineProps<{
show: boolean
cfgId: number
instanceId: string
region?: string
availabilityDomain: string
attachedVolumeIds: string[]
}>()
const emit = defineEmits<{ 'update:show': [boolean]; attached: [] }>()
const message = useToast()
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>