[Feat] Add Force Delete Instance Button at AdminInstancesList Page #11

This commit is contained in:
Harry-zklcdc 2025-02-11 00:00:43 +08:00
parent 249f99e5d9
commit 8104773f40
5 changed files with 138 additions and 18 deletions

View File

@ -90,6 +90,9 @@ export default {
AdminInstancesDelete(id) {
return ajax(`admin/instances/${id}`, 'delete', {})
},
AdminInstancesForceDelete(id) {
return ajax(`admin/instances/${id}/force`, 'delete', {})
},
AdminUserList(params) {
return ajax('admin/users', 'get', { params })

View File

@ -288,6 +288,7 @@ const instanceMenuItemsTemplate = ref([
{ label: '重启实例', icon: 'pi pi-refresh !text-sky-500', command: () => { instanceRestart(instanceDetail.value.id) } },
{ label: '调整配置', icon: 'pi pi-sliders-h !text-indigo-500', command: () => { openInstanceModify() } },
{ label: '删除实例', icon: 'pi pi-trash !text-red-500', command: () => { openInstanceDelete() } },
{ label: '强制删除', icon: 'pi pi-exclamation-triangle !text-red-500', command: () => { openInstanceForceDelete() } }
])
const instanceMenuItems = ref([])
@ -410,6 +411,11 @@ const showMenu = (event, instance) => {
newItem.disabled = true
}
break
case '强制删除':
if (instanceDetail.value.status !== statusFail.value) {
newItem.disabled = true
}
break
}
instanceMenuItems.value.push(newItem)
})
@ -517,6 +523,20 @@ const instanceDelete = async (id) => {
})
}
const instanceForceDelete = async (id) => {
toast.add({ severity: 'info', summary: '强制释放实例', detail: '正在强制释放实例', life: 3000 });
setTimeout(() => {
getInstances()
}, 100);
await api.AdminInstancesForceDelete(id, { force: true }).then(async (res) => {
toast.add({ severity: 'success', summary: '强制释放实例', detail: '实例已强制释放', life: 3000 });
await getInstances()
}).catch(err => {
console.error(err)
toast.add({ severity: 'error', summary: '强制释放实例失败', detail: err.response.data.msg, life: 3000 });
})
}
const openInstanceModify = async () => {
instanceConfiguration.value.gpu_count = instanceDetail.value.gpu_count
instanceConfiguration.value.volume_size = instanceDetail.value.volume_size
@ -595,6 +615,27 @@ const openInstanceDelete = () => {
});
}
const openInstanceForceDelete = () => {
confirm.require({
header: '确认强制删除 实例ID: ' + instanceDetail.value.id,
message: '强制实例删除后,数据将无法恢复,请确认删除',
icon: 'pi pi-info-circle',
rejectProps: {
label: '取消',
severity: 'secondary',
outlined: true
},
acceptProps: {
label: '强制删除',
severity: 'danger'
},
accept: async () => {
await instanceForceDelete(instanceDetail.value.id)
},
reject: () => { }
});
}
const instanceModifyLabel = (id, label) => {
api.AdminInstancesModifyLabel(id, { label: label }).then(async (res) => {
toast.add({ severity: 'success', summary: '修改备注成功', detail: '已保存备注', life: 3000 });

View File

@ -0,0 +1,54 @@
package instances
import (
"megrez/models"
"megrez/routers/api/v1/middleware"
"megrez/services/database"
"megrez/services/dispatcher"
"megrez/services/redis"
"strconv"
"github.com/kataras/iris/v12"
)
func forceDeleteHandler(ctx iris.Context) {
l.SetFunction("forceDeleteHandler")
id, err := ctx.Params().GetUint("id")
if err != nil {
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
return
}
instance := models.Instances{
ID: id,
}
result := database.DB.First(&instance)
if result.Error != nil {
l.Error("get instance error: %v", result.Error)
middleware.Error(ctx, middleware.CodeInstanceDeleteError, iris.StatusInternalServerError)
return
}
status := instance.Status
if status != models.InstanceStatusFail {
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
return
}
if instance.FromAction == models.InstanceActionStop || instance.FromAction == models.InstanceActionPause || instance.FromAction == models.InstanceActionRestart {
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.GpuCount))
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.VolumeSize+30))
}
dispatcherData := dispatcher.Data{
Type: dispatcher.Delete,
Status: status,
InstanceID: instance.ID,
Force: true,
}
dispatcher.Push(instance.ServerID, dispatcherData)
middleware.Success(ctx)
}

View File

@ -34,26 +34,47 @@ func delete(serverID uint, data Data) (err error) {
err = instanceController.Delete(&instance)
if err != nil {
lc.Error("delete instance error: %v", err)
ctx := context.Background()
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(-instance.GpuCount))
if !data.Force {
ctx := context.Background()
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(-instance.GpuCount))
}
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(-instance.VolumeSize-30))
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionDelete)
return
}
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(-instance.VolumeSize-30))
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionDelete)
}
if !data.Force {
server.VolumeUsed -= instance.VolumeSize + 30
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
server.GpuUsed -= instance.GpuCount
}
result = database.DB.Save(&server)
if result.Error != nil {
lc.Error("save server error: %v", result.Error)
return result.Error
}
lc.Info("delete instance success: %v", instance.ID)
} else {
if instance.FromAction != models.InstanceActionCreate {
server.VolumeUsed -= instance.VolumeSize + 30
}
if instance.FromAction == models.InstanceActionStop || instance.FromAction == models.InstanceActionPause || instance.FromAction == models.InstanceActionRestart {
server.GpuUsed -= instance.GpuCount
}
result = database.DB.Save(&server)
if result.Error != nil {
lc.Error("save server error: %v", result.Error)
return result.Error
}
result = database.DB.Delete(&instance)
if result.Error != nil {
lc.Error("force delete instance error: %v", result.Error)
}
lc.Info("force delete instance success: %v", instance.ID)
return
}
server.VolumeUsed -= instance.VolumeSize + 30
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
server.GpuUsed -= instance.GpuCount
}
result = database.DB.Save(&server)
if result.Error != nil {
lc.Error("save server error: %v", result.Error)
return result.Error
}
lc.Info("delete instance success: %v", instance.ID)
return
}

View File

@ -21,6 +21,7 @@ type Data struct {
VolumeSize *int `json:"volume_size,omitempty"`
Action instanceController.Action `json:"action,omitempty"`
Force bool `json:"force,omitempty"`
}
type Type int