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) {