v0.15.1 批量删除服务器 bulk delete the servers

This commit is contained in:
naiba 2023-06-14 00:23:47 +08:00
parent 42c038c829
commit 45e26f4082
11 changed files with 156 additions and 63 deletions

View File

@ -4,7 +4,7 @@
<br>
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
<br><br>
<img src="https://img.shields.io/github/actions/workflow/status/naiba/nezha/dashboard.yml?branch=master&label=Dash%20v0.15.0&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?branch=v0.15.0&label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.15.0-brightgreen?style=for-the-badge&logo=linux">
<img src="https://img.shields.io/github/actions/workflow/status/naiba/nezha/dashboard.yml?branch=master&label=Dash%20v0.15.1&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?branch=v0.15.1&label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.15.0-brightgreen?style=for-the-badge&logo=linux">
<br>
<br>
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>

View File

@ -41,6 +41,7 @@ func (ma *memberAPI) serve() {
mr.GET("/cron/:id/manual", ma.manualTrigger)
mr.POST("/force-update", ma.forceUpdate)
mr.POST("/batch-update-server-group", ma.batchUpdateServerGroup)
mr.POST("/batch-delete-server", ma.batchDeleteServer)
mr.POST("/notification", ma.addOrEditNotification)
mr.POST("/alert-rule", ma.addOrEditAlertRule)
mr.POST("/setting", ma.updateSetting)
@ -187,37 +188,9 @@ func (ma *memberAPI) delete(c *gin.Context) {
if err == nil {
// 删除服务器
singleton.ServerLock.Lock()
tag := singleton.ServerList[id].Tag
delete(singleton.SecretToID, singleton.ServerList[id].Secret)
delete(singleton.ServerList, id)
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[tag]); i++ {
if singleton.ServerTagToIDList[tag][i] == id {
index = i
break
}
}
if index > -1 {
// 删除旧 Tag-ID 绑定关系
singleton.ServerTagToIDList[tag] = append(singleton.ServerTagToIDList[tag][:index], singleton.ServerTagToIDList[tag][index+1:]...)
if len(singleton.ServerTagToIDList[tag]) == 0 {
delete(singleton.ServerTagToIDList, tag)
}
}
onServerDelete(id)
singleton.ServerLock.Unlock()
singleton.ReSortServer()
// 删除循环流量状态中的此服务器相关的记录
singleton.AlertsLock.Lock()
for i := 0; i < len(singleton.Alerts); i++ {
if singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID] != nil {
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].ServerName, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].Transfer, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].NextUpdate, id)
}
}
singleton.AlertsLock.Unlock()
// 删除服务器相关循环流量记录
singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id)
}
case "notification":
err = singleton.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
@ -446,10 +419,12 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
if err == nil {
err = utils.Json.Unmarshal([]byte(mf.RecoverTriggerTasksRaw), &m.RecoverTriggerTasks)
}
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
err = singleton.DB.Save(&m).Error
if err == nil {
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
err = singleton.DB.Save(&m).Error
}
}
}
if err == nil {
@ -933,3 +908,63 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
Code: http.StatusOK,
})
}
func (ma *memberAPI) batchDeleteServer(c *gin.Context) {
var servers []uint64
if err := c.ShouldBindJSON(&servers); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
if err := singleton.DB.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
singleton.ServerLock.Lock()
for i := 0; i < len(servers); i++ {
id := servers[i]
onServerDelete(id)
}
singleton.ServerLock.Unlock()
singleton.ReSortServer()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
func onServerDelete(id uint64) {
tag := singleton.ServerList[id].Tag
delete(singleton.SecretToID, singleton.ServerList[id].Secret)
delete(singleton.ServerList, id)
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[tag]); i++ {
if singleton.ServerTagToIDList[tag][i] == id {
index = i
break
}
}
if index > -1 {
singleton.ServerTagToIDList[tag] = append(singleton.ServerTagToIDList[tag][:index], singleton.ServerTagToIDList[tag][index+1:]...)
if len(singleton.ServerTagToIDList[tag]) == 0 {
delete(singleton.ServerTagToIDList, tag)
}
}
singleton.AlertsLock.Lock()
for i := 0; i < len(singleton.Alerts); i++ {
if singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID] != nil {
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].ServerName, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].Transfer, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].NextUpdate, id)
}
}
singleton.AlertsLock.Unlock()
singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id)
}

View File

@ -202,6 +202,9 @@ other = "Add Server"
[BatchEditServerGroup]
other = "Batch Modify Groups"
[BatchDeleteServer]
other = "Batch Delete Server"
[InputServerGroupName]
other = "Enter the name of the group"
@ -307,7 +310,7 @@ other = "Click To Copy"
[DeleteServer]
other = "Delete Server"
[ConfirmToDeleteThisServer]
[ConfirmToDeleteServer]
other = "Confirm Delete?"
[NoServerSelected]

View File

@ -202,6 +202,9 @@ other = "Añadir un servidor"
[BatchEditServerGroup]
other = "Modificación de grupos por lotes"
[BatchDeleteServer]
other = "Batch Delete Server"
[InputServerGroupName]
other = "Introduzca un nombre de grupo"
@ -307,8 +310,8 @@ other = "Haga clic para copiar"
[DeleteServer]
other = "Eliminar el servidor"
[ConfirmToDeleteThisServer]
other = "¿Confirma que quiere eliminar este servidor?"
[ConfirmToDeleteServer]
other = "¿Confirma que quiere eliminar servidor?"
[NoServerSelected]
other = "No hay servidores seleccionados actualmente"

View File

@ -202,6 +202,9 @@ other = "添加服务器"
[BatchEditServerGroup]
other = "批量修改分组"
[BatchDeleteServer]
other = "批量删除服务器"
[InputServerGroupName]
other = "输入分组名称"
@ -307,8 +310,8 @@ other = "点击复制"
[DeleteServer]
other = "删除主机"
[ConfirmToDeleteThisServer]
other = "确认删除主机?"
[ConfirmToDeleteServer]
other = "确认删除主机?"
[NoServerSelected]
other = "当前没有选中的服务器"

View File

@ -202,6 +202,9 @@ other = "添加服務器"
[BatchEditServerGroup]
other = "批量修改分組"
[BatchDeleteServer]
other = "批量删除服务器"
[InputServerGroupName]
other = "輸入分組名稱"
@ -307,8 +310,8 @@ other = "點擊複製"
[DeleteServer]
other = "刪除主機"
[ConfirmToDeleteThisServer]
other = "確認刪除主機?"
[ConfirmToDeleteServer]
other = "確認刪除主機?"
[NoServerSelected]
other = "當前沒有選中的服務器"

View File

@ -44,6 +44,30 @@ function showConfirm(title, content, callFn, extData) {
.modal("show");
}
function postJson(url, data) {
return $.ajax({
url: url,
type: "POST",
contentType: "application/json",
data: JSON.stringify(data),
}).done((resp) => {
if (resp.code == 200) {
if (resp.message) {
alert(resp.message);
} else {
alert("删除成功");
}
window.location.reload();
} else {
alert("删除失败 " + resp.code + "" + resp.message);
confirmBtn.toggleClass("loading");
}
})
.fail((err) => {
alert("网络错误:" + err.responseText);
});
}
function showFormModal(modelSelector, formID, URL, getData) {
$(modelSelector)
.modal({
@ -344,11 +368,11 @@ function addOrEditMonitor(monitor) {
const node = modal.find("i.dropdown.icon.specificServer");
for (let i = 0; i < serverList.length; i++) {
node.after(
'<a class="ui label transition visible" data-value="' +
serverList[i] +
'" style="display: inline-block !important;">ID:' +
serverList[i] +
'<i class="delete icon"></i></a>'
'<a class="ui label transition visible" data-value="' +
serverList[i] +
'" style="display: inline-block !important;">ID:' +
serverList[i] +
'<i class="delete icon"></i></a>'
);
}
@ -360,31 +384,31 @@ function addOrEditMonitor(monitor) {
const node2 = modal.find("i.dropdown.icon.recoverTask");
for (let i = 0; i < failTriggerTasksList.length; i++) {
node1.after(
'<a class="ui label transition visible" data-value="' +
failTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' +
failTriggerTasksList[i] +
'<i class="delete icon"></i></a>'
'<a class="ui label transition visible" data-value="' +
failTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' +
failTriggerTasksList[i] +
'<i class="delete icon"></i></a>'
);
}
for (let i = 0; i < recoverTriggerTasksList.length; i++) {
node2.after(
'<a class="ui label transition visible" data-value="' +
recoverTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' +
recoverTriggerTasksList[i] +
'<i class="delete icon"></i></a>'
'<a class="ui label transition visible" data-value="' +
recoverTriggerTasksList[i] +
'" style="display: inline-block !important;">ID:' +
recoverTriggerTasksList[i] +
'<i class="delete icon"></i></a>'
);
}
}
modal
modal
.find("input[name=FailTriggerTasksRaw]")
.val(monitor ? "[]," + failTriggerTasks.substr(1, failTriggerTasks.length - 2) : "[]");
modal
modal
.find("input[name=RecoverTriggerTasksRaw]")
.val(monitor ? "[]," + recoverTriggerTasks.substr(1, recoverTriggerTasks.length - 2) : "[]");
modal
modal
.find("input[name=SkipServersRaw]")
.val(monitor ? "[]," + servers.substr(1, servers.length - 2) : "[]");
showFormModal(".monitor.modal", "#monitorForm", "/api/monitor");

View File

@ -10,7 +10,7 @@
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230417"></script>
<script src="/static/main.js?v20230614"></script>
<script>
(function () {
updateLang({{.LANG }});

View File

@ -8,6 +8,9 @@
<button class="ui right labeled nezha-primary-btn icon button" onclick="batchEditServerGroup()"><i
class="edit icon"></i> {{tr "BatchEditServerGroup"}}
</button>
<button class="ui right labeled nezha-primary-btn icon button" onclick="batchDeleteServer()"><i
class="trash icon"></i> {{tr "BatchDeleteServer"}}
</button>
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditServer()"><i
class="add icon"></i> {{tr "AddServer"}}
</button>
@ -69,7 +72,7 @@
<i class="edit icon"></i>
</button>
<button class="ui button"
onclick="showConfirm('{{tr "DeleteServer"}}','{{tr "ConfirmToDeleteThisServer"}}',deleteRequest,'/api/server/'+{{$server.ID}})">
onclick="showConfirm('{{tr "DeleteServer"}}','{{tr "ConfirmToDeleteServer"}}',deleteRequest,'/api/server/'+{{$server.ID}})">
<i class="trash alternate outline icon"></i>
</button>
</div>
@ -188,5 +191,24 @@
});
})
}
function batchDeleteServer() {
const servers = []
checkBoxList.forEach(cb => {
if (cb.checked) {
servers.push(parseInt(cb.value))
}
})
if (servers.length == 0) {
$.suiAlert({
title: '{{tr "NoServerSelected"}}',
description: '',
type: 'warning',
time: '2',
position: 'top-center',
});
return
}
showConfirm('{{tr "DeleteServer"}}', '{{tr "ConfirmToDeleteServer"}}', () => postJson('/api/batch-delete-server', servers), '')
}
</script>
{{end}}

View File

@ -12,7 +12,7 @@
<script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230417"></script>
<script src="/static/main.js?v20230614"></script>
<script>
(function () {
updateLang({{.LANG }});

View File

@ -12,7 +12,7 @@ import (
"github.com/naiba/nezha/pkg/utils"
)
var Version = "v0.15.0" // !!记得修改 README 中的 badge 版本!!
var Version = "v0.15.1" // !!记得修改 README 中的 badge 版本!!
var (
Conf *model.Config