From 3d8f76cac6b0f866c8e42a31c1ffcdba147239d8 Mon Sep 17 00:00:00 2001 From: shiran Date: Mon, 7 Sep 2026 16:07:21 +0800 Subject: [PATCH] feat: add AWS admin suspension and fix recycle restore network parameter --- src/api/admin/awsService.js | 5 +++ src/views/aws/AwsInstanceDetail.vue | 41 +++++++++++++++++++ src/views/virtualization/RecycleBinManage.vue | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/api/admin/awsService.js b/src/api/admin/awsService.js index 4e697b6..24c8411 100644 --- a/src/api/admin/awsService.js +++ b/src/api/admin/awsService.js @@ -25,6 +25,11 @@ export const deleteAwsGoods = (id) => http2.delete(`${base}/goods/delete`, { par export const getAwsInstanceDetail = (identifier) => http2.get(`${base}/instances/detail`, { params: typeof identifier === 'object' ? identifier : { instance_id: identifier } }) +export const actionAdminAwsInstance = (instanceId, action, reason) => http2.post(`${base}/instances/action`, { + instance_id: instanceId, + action, + ...(action === 'suspend' ? { reason } : {}) +}) export const retryAwsInstance = (instanceId, note = '') => http2.post(`${base}/instances/retry`, { instance_id: instanceId, note }) export const resyncAwsInstance = (instanceId, note = '') => http2.post(`${base}/instances/resync`, { instance_id: instanceId, note }) export const resolveAwsInstance = (instanceId, resolution, note = '') => http2.post(`${base}/instances/resolve`, { instance_id: instanceId, resolution, note }) diff --git a/src/views/aws/AwsInstanceDetail.vue b/src/views/aws/AwsInstanceDetail.vue index c1c5f6d..f8b1406 100644 --- a/src/views/aws/AwsInstanceDetail.vue +++ b/src/views/aws/AwsInstanceDetail.vue @@ -12,6 +12,23 @@ +
+ 管理员控制 +
+ 管理员暂停 + 解除暂停 +
+
+ + + + + + + + + +
履约记录与失败审核
重新同步审核重试确认解决驳回
@@ -85,6 +102,8 @@ const protocols=['tcp','udp','icmp','icmpv6','-1'],volumeTypes=['gp3','gp2','io1 const rule=reactive({direction:'ingress',protocol:'tcp',from_port:22,to_port:22,cidr:'0.0.0.0/0',description:''}) const volumeForm=reactive({volume_id:'',name:'',device_name:'/dev/sdf',size_gib:20,type:'gp3',iops:0,throughput:0,encrypted:true,kms_key_id:'',delete_on_termination:false}) const trafficForm=reactive({enabled:true,limit_gib:100}) +const suspendDialog=ref(false),suspendFormRef=ref(null),lifecycleSubmitting=ref(false) +const suspendForm=reactive({reason:''}) const envelope=r=>r?.data||r||{},dataOf=r=>envelope(r)?.data,message=(e,f)=>e?.response?.data?.message||e?.response?.data?.msg||envelope(e)?.message||e?.message||f const instance=computed(()=>baseDetail.value?.instance||null) const securityRules=computed(()=>{const d=security.value||{};if(Array.isArray(d.rules))return d.rules;return[...(d.ingress_rules||d.ingressRules||[]).map(v=>({...v,direction:v.direction||'ingress'})),...(d.egress_rules||d.egressRules||[]).map(v=>({...v,direction:v.direction||'egress'}))]}) @@ -119,6 +138,28 @@ async function loadVolumes(){const v=await safe(()=>api.getAdminAwsVolumes(insta async function loadOperations(){const v=await safe(()=>api.getAdminAwsOperations(instanceId,{page:operationPage.value,count:20}),'读取操作记录失败');if(v){operations.value=v.list||[];operationTotal.value=v.all_count||0}} function tabChanged(name){if(name==='runtime'){if(!runtimeDetails.value)loadRuntime();if(!monitoring.value)loadMonitoring();if(!trafficLimit.value)loadTraffic()}if(name==='network'&&!security.value)loadNetwork();if(name==='storage'&&!volumes.value.length)loadVolumes();if(name==='operations')loadOperations()} async function promptNote(title){try{return(await ElMessageBox.prompt('请输入操作备注(可留空)',title,{inputValue:''})).value}catch{return null}} +function openSuspend(){suspendForm.reason='';suspendFormRef.value?.clearValidate();suspendDialog.value=true} +async function submitSuspend(){ + if(lifecycleSubmitting.value)return + if(!await suspendFormRef.value?.validate().catch(()=>false))return + await submitLifecycleAction('suspend',suspendForm.reason.trim()) +} +async function resumeInstance(){ + if(lifecycleSubmitting.value)return + try{await ElMessageBox.confirm(`确认解除实例 #${instanceId} 的管理员暂停?解除后用户可恢复操作。`,'解除暂停',{type:'warning',confirmButtonText:'确认解除',cancelButtonText:'取消'})}catch{return} + await submitLifecycleAction('resume') +} +async function submitLifecycleAction(action,reason){ + if(lifecycleSubmitting.value)return + lifecycleSubmitting.value=true + try{ + const result=envelope(await api.actionAdminAwsInstance(instanceId,action,reason)) + if(result.code!==200)throw new Error(result.message||'实例操作失败') + suspendDialog.value=false + ElMessage.success(action==='suspend'?'管理员暂停已受理':'解除暂停已受理') + await Promise.all([loadBase(),loadRuntime()]) + }catch(e){ElMessage.error(message(e,'实例操作失败'))}finally{lifecycleSubmitting.value=false} +} async function fulfillmentAction(type){const note=await promptNote({resync:'重新同步履约',retry:'审核重试',resolved:'确认解决',reject:'驳回履约'}[type]);if(note===null)return;let call;if(type==='resync')call=()=>api.resyncAwsInstance(instanceId,note);else if(type==='retry')call=()=>api.retryAwsInstance(instanceId,note);else call=()=>api.resolveAwsInstance(instanceId,type,note);const v=await safe(call,'履约操作失败');if(v!==null){ElMessage.success('操作成功');loadBase()}} function stopPoll(){clearTimeout(pollTimer.value);pollTimer.value=null} function pollOperation(id,onSuccess){stopPoll();pollStarted.value=Date.now();const tick=async()=>{if(Date.now()-pollStarted.value>=120000)return ElMessage.warning('操作仍在处理,请稍后手动刷新');try{const r=await api.getAdminAwsOperationDetail(id);const op=dataOf(r);if(['queued','running'].includes(op?.status)){pollTimer.value=setTimeout(tick,5000);return}if(op?.status==='succeeded'){ElMessage.success('操作完成');await onSuccess?.()}else ElMessage.error(op?.errorMessage||'操作失败')}catch{pollTimer.value=setTimeout(tick,5000)}};pollTimer.value=setTimeout(tick,5000)} diff --git a/src/views/virtualization/RecycleBinManage.vue b/src/views/virtualization/RecycleBinManage.vue index b3014ed..ea66442 100644 --- a/src/views/virtualization/RecycleBinManage.vue +++ b/src/views/virtualization/RecycleBinManage.vue @@ -304,7 +304,7 @@ const submitRestore = async () => { selectedNatNetworks.value.forEach(n => fd.append('network_ids', n.id)) } if (selectedBridgeNetwork.value) { - fd.append('internet_network_id', selectedBridgeNetwork.value.id) + fd.append('network_ids', selectedBridgeNetwork.value.id) } const res = await restoreRecycleBin(fd) if (res?.data?.code === 200) {