初始提交:OCI 面板前端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { NSelect, useMessage } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { attachBootVolume, listInstances } from '@/api/instances'
|
||||
import FormField from '@/components/FormField.vue'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import { shortAd } from '@/composables/useFormat'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
import type { BootVolume } from '@/types/api'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
cfgId: number | null
|
||||
volume: BootVolume | null
|
||||
region?: string
|
||||
}>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; attached: [] }>()
|
||||
|
||||
const message = useMessage()
|
||||
const submitting = ref(false)
|
||||
const instanceId = ref<string | null>(null)
|
||||
|
||||
const instances = useAsync(
|
||||
() => (props.cfgId === null ? Promise.resolve([]) : listInstances(props.cfgId, props.region)),
|
||||
false,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(v) => {
|
||||
if (v) {
|
||||
instanceId.value = null
|
||||
void instances.run()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/** 仅同可用域且 STOPPED 的实例可选,其余给出禁用原因 */
|
||||
const instanceOptions = computed(() =>
|
||||
(instances.data.value ?? []).map((i) => {
|
||||
const sameAd = i.availabilityDomain === props.volume?.availabilityDomain
|
||||
const stopped = i.lifecycleState === 'STOPPED'
|
||||
const reason = !sameAd ? ` · ${shortAd(i.availabilityDomain)} 可用域不同` : !stopped ? ` · ${i.lifecycleState}` : ''
|
||||
return {
|
||||
label: `${i.displayName}${reason}`,
|
||||
value: i.id,
|
||||
disabled: !sameAd || !stopped,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
async function submit() {
|
||||
if (props.cfgId === null || !props.volume || !instanceId.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await attachBootVolume(props.cfgId, instanceId.value, props.volume.id, props.region)
|
||||
message.success('挂载已提交,稍后自动刷新')
|
||||
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="挂载引导卷"
|
||||
:width="440"
|
||||
:submitting="submitting"
|
||||
:submit-disabled="!instanceId"
|
||||
submit-text="挂载"
|
||||
@update:show="emit('update:show', $event)"
|
||||
@submit="submit"
|
||||
>
|
||||
<div class="mb-3 text-[13px] text-ink-2">
|
||||
将 <span class="font-medium">{{ volume?.displayName }}</span> 挂载为实例引导盘
|
||||
</div>
|
||||
<FormField
|
||||
label="目标实例"
|
||||
required
|
||||
hint="仅可挂到同可用域、处于 STOPPED 的实例;实例开机后从该卷启动"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="instanceId"
|
||||
filterable
|
||||
:options="instanceOptions"
|
||||
:loading="instances.loading.value"
|
||||
placeholder="选择实例"
|
||||
/>
|
||||
</FormField>
|
||||
</FormModal>
|
||||
</template>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { NInput, useMessage } from 'naive-ui'
|
||||
|
||||
import AppInputNumber from '@/components/AppInputNumber.vue'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { updateBootVolume } from '@/api/bootVolumes'
|
||||
import FormField from '@/components/FormField.vue'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import type { BootVolume, UpdateBootVolumeRequest } from '@/types/api'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
cfgId: number | null
|
||||
volume: BootVolume | null
|
||||
region?: string
|
||||
}>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; updated: [] }>()
|
||||
|
||||
const message = useMessage()
|
||||
const submitting = ref(false)
|
||||
|
||||
const form = reactive({ displayName: '', sizeInGBs: 50, vpusPerGB: 10 })
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(v) => {
|
||||
if (v && props.volume)
|
||||
Object.assign(form, {
|
||||
displayName: props.volume.displayName,
|
||||
sizeInGBs: props.volume.sizeInGBs,
|
||||
vpusPerGB: props.volume.vpusPerGB,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const sizeError = computed(() =>
|
||||
props.volume && form.sizeInGBs < props.volume.sizeInGBs ? '仅支持扩容,不能缩小' : '',
|
||||
)
|
||||
const canSubmit = computed(() => Boolean(form.displayName.trim()) && !sizeError.value)
|
||||
|
||||
/** 零值字段不更新:只提交与当前值不同的字段 */
|
||||
function buildBody(v: BootVolume): UpdateBootVolumeRequest {
|
||||
const body: UpdateBootVolumeRequest = {}
|
||||
if (form.displayName.trim() !== v.displayName) body.displayName = form.displayName.trim()
|
||||
if (form.sizeInGBs !== v.sizeInGBs) body.sizeInGBs = form.sizeInGBs
|
||||
if (form.vpusPerGB !== v.vpusPerGB) body.vpusPerGB = form.vpusPerGB
|
||||
return body
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (props.cfgId === null || !props.volume) return
|
||||
const body = buildBody(props.volume)
|
||||
if (!Object.keys(body).length) {
|
||||
emit('update:show', false)
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await updateBootVolume(props.cfgId, props.volume.id, body, props.region)
|
||||
message.success(body.sizeInGBs ? '修改已提交;扩容后需在实例 OS 内扩展文件系统' : '修改已提交')
|
||||
emit('update:show', false)
|
||||
emit('updated')
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '保存失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormModal
|
||||
:show="show"
|
||||
title="编辑引导卷"
|
||||
:width="440"
|
||||
:submitting="submitting"
|
||||
:submit-disabled="!canSubmit"
|
||||
submit-text="保存修改"
|
||||
@update:show="emit('update:show', $event)"
|
||||
@submit="submit"
|
||||
>
|
||||
<FormField label="显示名" required>
|
||||
<NInput v-model:value="form.displayName" placeholder="引导卷显示名" />
|
||||
</FormField>
|
||||
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
|
||||
<FormField
|
||||
label="大小(GB)"
|
||||
hint="仅支持扩容,不能缩小;扩容后需在实例 OS 内扩展文件系统"
|
||||
:error="sizeError"
|
||||
>
|
||||
<AppInputNumber
|
||||
v-model:value="form.sizeInGBs"
|
||||
:min="volume?.sizeInGBs ?? 47"
|
||||
:max="32768"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="性能(VPU/GB)" hint="10 均衡 / 20 高性能 / 30–120 超高性能,步进 10">
|
||||
<AppInputNumber
|
||||
v-model:value="form.vpusPerGB"
|
||||
:min="10"
|
||||
:max="120"
|
||||
:step="10"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</FormModal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user