diff --git a/cmd/dashboard/controller/notification.go b/cmd/dashboard/controller/notification.go index f5f5aa1..774ecf2 100644 --- a/cmd/dashboard/controller/notification.go +++ b/cmd/dashboard/controller/notification.go @@ -59,6 +59,8 @@ func createNotification(c *gin.Context) (uint64, error) { n.URL = nf.URL verifyTLS := nf.VerifyTLS n.VerifyTLS = &verifyTLS + formatMetricUnits := nf.FormatMetricUnits + n.FormatMetricUnits = &formatMetricUnits ns := model.NotificationServerBundle{ Notification: &n, @@ -120,6 +122,8 @@ func updateNotification(c *gin.Context) (any, error) { n.URL = nf.URL verifyTLS := nf.VerifyTLS n.VerifyTLS = &verifyTLS + formatMetricUnits := nf.FormatMetricUnits + n.FormatMetricUnits = &formatMetricUnits ns := model.NotificationServerBundle{ Notification: &n, diff --git a/model/notification.go b/model/notification.go index e6f570d..db32e6b 100644 --- a/model/notification.go +++ b/model/notification.go @@ -33,13 +33,14 @@ type NotificationServerBundle struct { type Notification struct { Common - Name string `json:"name"` - URL string `json:"url"` - RequestMethod uint8 `json:"request_method"` - RequestType uint8 `json:"request_type"` - RequestHeader string `json:"request_header" gorm:"type:longtext"` - RequestBody string `json:"request_body" gorm:"type:longtext"` - VerifyTLS *bool `json:"verify_tls,omitempty"` + Name string `json:"name"` + URL string `json:"url"` + RequestMethod uint8 `json:"request_method"` + RequestType uint8 `json:"request_type"` + RequestHeader string `json:"request_header" gorm:"type:longtext"` + RequestBody string `json:"request_body" gorm:"type:longtext"` + VerifyTLS *bool `json:"verify_tls,omitempty"` + FormatMetricUnits *bool `json:"format_metric_units,omitempty"` } func (ns *NotificationServerBundle) reqURL(message string) string { @@ -172,10 +173,19 @@ func (ns *NotificationServerBundle) replaceParamsInString(str string, message st replacements = append(replacements, "#SERVER.NAME#", mod(ns.Server.Name), "#SERVER.ID#", mod(fmt.Sprintf("%d", ns.Server.ID)), - "#SERVER.CPU#", mod(fmt.Sprintf("%f", ns.Server.State.CPU)), - "#SERVER.MEM#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)), - "#SERVER.SWAP#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)), - "#SERVER.DISK#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)), + + // Converted metrics + "#SERVER.CPU#", mod(ns.formatUsage(false, ns.Server.State.CPU)), + "#SERVER.MEM#", mod(ns.formatUsage(true, float64(ns.Server.State.MemUsed)/float64(ns.Server.Host.MemTotal))), + "#SERVER.SWAP#", mod(ns.formatUsage(true, float64(ns.Server.State.SwapUsed)/float64(ns.Server.Host.SwapTotal))), + "#SERVER.DISK#", mod(ns.formatUsage(true, float64(ns.Server.State.DiskUsed)/float64(ns.Server.Host.DiskTotal))), + "#SERVER.SPEEDIN#", mod(fmt.Sprintf("%s/s", ns.formatSize(ns.Server.State.NetInSpeed))), + "#SERVER.SPEEDOUT#", mod(fmt.Sprintf("%s/s", ns.formatSize(ns.Server.State.NetOutSpeed))), + "#SERVER.TRANSFERIN#", mod(ns.formatSize(ns.Server.State.NetInTransfer)), + "#SERVER.TRANSFEROUT#", mod(ns.formatSize(ns.Server.State.NetOutTransfer)), + + // Raw metrics + "#SERVER.CPUUSED#", mod(fmt.Sprintf("%f", ns.Server.State.CPU)), "#SERVER.MEMUSED#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)), "#SERVER.SWAPUSED#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)), "#SERVER.DISKUSED#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)), @@ -184,8 +194,6 @@ func (ns *NotificationServerBundle) replaceParamsInString(str string, message st "#SERVER.DISKTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.DiskTotal)), "#SERVER.NETINSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetInSpeed)), "#SERVER.NETOUTSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutSpeed)), - "#SERVER.TRANSFERIN#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)), - "#SERVER.TRANSFEROUT#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)), "#SERVER.NETINTRANSFER#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)), "#SERVER.NETOUTTRANSFER#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)), "#SERVER.LOAD1#", mod(fmt.Sprintf("%f", ns.Server.State.Load1)), @@ -219,3 +227,21 @@ func (ns *NotificationServerBundle) replaceParamsInString(str string, message st replacer := strings.NewReplacer(replacements...) return replacer.Replace(str) } + +func (ns *NotificationServerBundle) formatUsage(toPercentage bool, usage float64) string { + if ns.Notification.FormatMetricUnits != nil && *ns.Notification.FormatMetricUnits { + if toPercentage { + usage = usage * 100 + } + + return fmt.Sprintf("%.2f %%", usage) + } + return fmt.Sprintf("%f", usage) +} + +func (ns *NotificationServerBundle) formatSize(size uint64) string { + if ns.Notification.FormatMetricUnits != nil && *ns.Notification.FormatMetricUnits { + return utils.Bytes(size) + } + return fmt.Sprintf("%d", size) +} diff --git a/model/notification_api.go b/model/notification_api.go index 5bee8d2..a30971e 100644 --- a/model/notification_api.go +++ b/model/notification_api.go @@ -1,12 +1,13 @@ package model type NotificationForm struct { - Name string `json:"name,omitempty" minLength:"1"` - URL string `json:"url,omitempty"` - RequestMethod uint8 `json:"request_method,omitempty"` - RequestType uint8 `json:"request_type,omitempty"` - RequestHeader string `json:"request_header,omitempty"` - RequestBody string `json:"request_body,omitempty"` - VerifyTLS bool `json:"verify_tls,omitempty" validate:"optional"` - SkipCheck bool `json:"skip_check,omitempty" validate:"optional"` + Name string `json:"name,omitempty" minLength:"1"` + URL string `json:"url,omitempty"` + RequestMethod uint8 `json:"request_method,omitempty"` + RequestType uint8 `json:"request_type,omitempty"` + RequestHeader string `json:"request_header,omitempty"` + RequestBody string `json:"request_body,omitempty"` + VerifyTLS bool `json:"verify_tls,omitempty" validate:"optional"` + SkipCheck bool `json:"skip_check,omitempty" validate:"optional"` + FormatMetricUnits bool `json:"format_metric_units,omitempty" validate:"optional"` } diff --git a/model/notification_test.go b/model/notification_test.go index 480a319..5b7c197 100644 --- a/model/notification_test.go +++ b/model/notification_test.go @@ -27,12 +27,14 @@ type testSt struct { } func execCase(t *testing.T, item testSt) { + trueBool := true n := Notification{ - URL: item.url, - RequestMethod: item.reqMethod, - RequestType: item.reqType, - RequestBody: item.body, - RequestHeader: item.header, + URL: item.url, + RequestMethod: item.reqMethod, + RequestType: item.reqType, + RequestBody: item.body, + RequestHeader: item.header, + FormatMetricUnits: &trueBool, } server := Server{ Common: Common{}, @@ -45,7 +47,7 @@ func execCase(t *testing.T, item testSt) { CPU: nil, MemTotal: 0, DiskTotal: 0, - SwapTotal: 0, + SwapTotal: 8888, Arch: "", Virtualization: "", BootTime: 0, @@ -184,7 +186,29 @@ func TestNotification(t *testing.T) { }, { url: "https://example.com/?m=#NEZHA#", - body: `{"Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":#SERVER.SWAP#}`, + body: `{"Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":"#SERVER.SWAP#"}`, + reqMethod: NotificationRequestMethodPOST, + header: `{"asd":"dsa11"}`, + reqType: NotificationRequestTypeJSON, + expectURL: "https://example.com/?m=" + msg, + expectMethod: http.MethodPost, + expectContentType: reqTypeJSON, + expectBody: `{"Server":"ServerName","ServerIP":"1.1.1.1","ServerSWAP":"100.00 %"}`, + expectHeader: map[string]string{"asd": "dsa11"}, + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"#NEZHA#":"#NEZHA#","Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":"#SERVER.SWAP#"}`, + reqMethod: NotificationRequestMethodPOST, + reqType: NotificationRequestTypeForm, + expectURL: "https://example.com/?m=" + msg, + expectMethod: http.MethodPost, + expectContentType: reqTypeForm, + expectBody: "%23NEZHA%23=" + msg + "&Server=ServerName&ServerIP=1.1.1.1&ServerSWAP=100.00+%25", + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":#SERVER.SWAPUSED#}`, reqMethod: NotificationRequestMethodPOST, header: `{"asd":"dsa11"}`, reqType: NotificationRequestTypeJSON, @@ -196,7 +220,7 @@ func TestNotification(t *testing.T) { }, { url: "https://example.com/?m=#NEZHA#", - body: `{"#NEZHA#":"#NEZHA#","Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":"#SERVER.SWAP#"}`, + body: `{"#NEZHA#":"#NEZHA#","Server":"#SERVER.NAME#","ServerIP":"#SERVER.IP#","ServerSWAP":"#SERVER.SWAPUSED#"}`, reqMethod: NotificationRequestMethodPOST, reqType: NotificationRequestTypeForm, expectURL: "https://example.com/?m=" + msg, diff --git a/pkg/utils/bytes.go b/pkg/utils/bytes.go new file mode 100644 index 0000000..2de2701 --- /dev/null +++ b/pkg/utils/bytes.go @@ -0,0 +1,40 @@ +package utils + +import ( + "fmt" + "math" +) + +// https://github.com/dustin/go-humanize/blob/master/bytes.go + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func countDigits(n int64) int { + digits := 0 + for n != 0 { + n /= 10 + digits += 1 + } + return digits +} + +func humanateBytes(s uint64, base float64, minDigits int, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%d B", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[min(len(sizes)-1, int(e))] // #nosec G602 + rounding := math.Pow10(minDigits - 1) + val := math.Floor(float64(s)/math.Pow(base, e)*rounding+0.5) / rounding + ff := "%%.%df %%s" + digits := max(minDigits-countDigits(int64(val)), 0) + f := fmt.Sprintf(ff, digits) + return fmt.Sprintf(f, val, suffix) +} + +func Bytes(s uint64) string { + sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(s, 1024, 2, sizes) +}