diff --git a/README.md b/README.md index a7f7c15..db08c49 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ \>> QQ 交流群:872069346 **加群要求:已搭建好哪吒监控 & 有 2+ 服务器, 机器人自动审核** -\>> [Use Cases | 我们的用户](https://www.google.com/search?q=%22powered+by+%E5%93%AA%E5%90%92%E7%9B%91%E6%8E%A7%22+OR+%22powered+by+Nezha+Monitoring%22&filter=0) (Google) +\>> [Use Cases | 我们的用户](https://www.google.com/search?q=%22powered+by+Nezha+Monitoring%22+OR+%22powered+by+%E5%93%AA%E5%90%92%E7%9B%91%E6%8E%A7%22) (Google) | Default Theme | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index bd1997a..b718167 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -87,7 +87,7 @@ var funcMap = template.FuncMap{ } }, "tf": func(t time.Time) string { - return t.In(singleton.Loc).Format("2006年1月2号 15:04:05") + return t.In(singleton.Loc).Format("02/01/2006 15:04:05") }, "len": func(slice []interface{}) string { return strconv.Itoa(len(slice)) @@ -99,7 +99,7 @@ var funcMap = template.FuncMap{ return template.HTML(`<` + s + `>`) // #nosec }, "stf": func(s uint64) string { - return time.Unix(int64(s), 0).In(singleton.Loc).Format("2006年1月2号 15:04") + return time.Unix(int64(s), 0).In(singleton.Loc).Format("02/01/2006 15:04") }, "sf": func(duration uint64) string { return time.Duration(time.Duration(duration) * time.Second).String() @@ -151,7 +151,7 @@ var funcMap = template.FuncMap{ "dayBefore": func(i int) string { year, month, day := time.Now().Date() today := time.Date(year, month, day, 0, 0, 0, 0, time.Local) - return today.AddDate(0, 0, i-29).Format("1月2号") + return today.AddDate(0, 0, i-29).Format("02/01") }, "className": func(percent float32) string { if percent == 0 { @@ -165,16 +165,7 @@ var funcMap = template.FuncMap{ } return "danger" }, - "statusName": func(percent float32) string { - if percent == 0 { - return "无数据" - } - if percent > 95 { - return "良好" - } - if percent > 80 { - return "低可用" - } - return "故障" + "statusName": func(val float32) string { + return singleton.StatusCodeToString(singleton.GetStatusCode(val)) }, } diff --git a/resource/l10n/zh-CN.toml b/resource/l10n/zh-CN.toml index fbbd248..7eb1faa 100644 --- a/resource/l10n/zh-CN.toml +++ b/resource/l10n/zh-CN.toml @@ -489,3 +489,27 @@ other = "报警规则" [NotificationMethod] other = "通知方式" + +[Incident] +other = "故障" + +[Resolved] +other = "恢复" + +[StatusNoData] +other = "无数据" + +[StatusGood] +other = "良好" + +[StatusLowAvailability] +other = "低可用" + +[ScheduledTaskExecutedSuccessfully] +other = "任务成功" + +[ScheduledTaskExecutedFailed] +other = "任务失败" + +[IPChanged] +other = "IP变更" diff --git a/service/rpc/nezha.go b/service/rpc/nezha.go index 7cae949..4baeb04 100644 --- a/service/rpc/nezha.go +++ b/service/rpc/nezha.go @@ -3,9 +3,11 @@ package rpc import ( "context" "fmt" - "github.com/jinzhu/copier" "time" + "github.com/jinzhu/copier" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/naiba/nezha/model" pb "github.com/naiba/nezha/proto" "github.com/naiba/nezha/service/singleton" @@ -33,10 +35,18 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece curServer := model.Server{} copier.Copy(&curServer, singleton.ServerList[clientID]) if cr.PushSuccessful && r.GetSuccessful() { - singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false, &curServer) + singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.MustLocalize( + &i18n.LocalizeConfig{ + MessageID: "ScheduledTaskExecutedSuccessfully", + }, + ), cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false, &curServer) } if !r.GetSuccessful() { - singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false, &curServer) + singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.MustLocalize( + &i18n.LocalizeConfig{ + MessageID: "ScheduledTaskExecutedFailed", + }, + ), cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false, &curServer) } singleton.DB.Model(cr).Updates(model.Cron{ LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())), @@ -108,7 +118,10 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece host.IP != "" && singleton.ServerList[clientID].Host.IP != host.IP { singleton.SendNotification(singleton.Conf.IPChangeNotificationTag, fmt.Sprintf( - "[IP变更] %s ,旧IP:%s,新IP:%s。", + "[%s] %s, %s => %s", + singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: "IPChanged", + }), singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), singleton.IPDesensitize(host.IP)), true) } diff --git a/service/singleton/alertsentinel.go b/service/singleton/alertsentinel.go index 44af4b2..f178ae1 100644 --- a/service/singleton/alertsentinel.go +++ b/service/singleton/alertsentinel.go @@ -2,11 +2,13 @@ package singleton import ( "fmt" - "github.com/jinzhu/copier" "log" "sync" "time" + "github.com/jinzhu/copier" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/naiba/nezha/model" ) @@ -153,11 +155,15 @@ func checkStatus() { copier.Copy(&curServer, server) if !passed { alertsPrevState[alert.ID][server.ID] = _RuleCheckFail - message := fmt.Sprintf("[主机故障] %s(%s) 规则:%s", server.Name, IPDesensitize(server.Host.IP), alert.Name) + message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: "Incident", + }), server.Name, IPDesensitize(server.Host.IP), alert.Name) go SendNotification(alert.NotificationTag, message, true, &curServer) } else { if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail { - message := fmt.Sprintf("[主机恢复] %s(%s) 规则:%s", server.Name, IPDesensitize(server.Host.IP), alert.Name) + message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: "Resolved", + }), server.Name, IPDesensitize(server.Host.IP), alert.Name) go SendNotification(alert.NotificationTag, message, true, &curServer) } alertsPrevState[alert.ID][server.ID] = _RuleCheckPass diff --git a/service/singleton/servicesentinel.go b/service/singleton/servicesentinel.go index 5049924..bd5ac75 100644 --- a/service/singleton/servicesentinel.go +++ b/service/singleton/servicesentinel.go @@ -10,11 +10,11 @@ import ( "github.com/naiba/nezha/model" pb "github.com/naiba/nezha/proto" + "github.com/nicksnyder/go-i18n/v2/i18n" ) const ( _CurrentStatusSize = 30 // 统计 15 分钟内的数据为当前状态 - _StatusOk = "良好" ) var ServiceSentinelShared *ServiceSentinel @@ -39,7 +39,7 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) { serviceCurrentStatusIndex: make(map[uint64]int), serviceCurrentStatusData: make(map[uint64][]model.MonitorHistory), latestDate: make(map[uint64]string), - lastStatus: make(map[uint64]string), + lastStatus: make(map[uint64]int), serviceResponseDataStoreCurrentUp: make(map[uint64]uint64), serviceResponseDataStoreCurrentDown: make(map[uint64]uint64), monitors: make(map[uint64]*model.Monitor), @@ -97,7 +97,7 @@ type ServiceSentinel struct { serviceCurrentStatusIndex map[uint64]int // [monitor_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标 serviceCurrentStatusData map[uint64][]model.MonitorHistory // [monitor_id] -> []model.MonitorHistory latestDate map[uint64]string // 最近一次更新时间 - lastStatus map[uint64]string + lastStatus map[uint64]int serviceResponseDataStoreCurrentUp map[uint64]uint64 // [monitor_id] -> 当前服务在线计数 serviceResponseDataStoreCurrentDown map[uint64]uint64 // [monitor_id] -> 当前服务离线计数 monitors map[uint64]*model.Monitor // [monitor_id] -> model.Monitor @@ -277,20 +277,6 @@ func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceItemResponse { return ss.monthlyStatus } -// getStateStr 根据服务在线率返回对应的状态字符串 -func getStateStr(percent uint64) string { - if percent == 0 { - return "无数据" - } - if percent > 95 { - return _StatusOk - } - if percent > 80 { - return "低可用" - } - return "故障" -} - // worker 服务监控的实际工作流程 func (ss *ServiceSentinel) worker() { // 从服务状态汇报管道获取汇报的服务数据 @@ -321,7 +307,7 @@ func (ss *ServiceSentinel) worker() { } else { ss.serviceStatusToday[mh.MonitorID].Down++ ServerLock.RLock() - log.Println("NEZHA>> 服务故障上报:", ss.monitors[mh.MonitorID].Target, "上报者:", ServerList[r.Reporter].Name, "错误信息:", mh.Data) + log.Println("NEZHA>> Services Incident:", ss.monitors[mh.MonitorID].Target, "Reporter:", ServerList[r.Reporter].Name, "Error:", mh.Data) ServerLock.RUnlock() } // 写入当前数据 @@ -343,25 +329,25 @@ func (ss *ServiceSentinel) worker() { if ss.serviceResponseDataStoreCurrentDown[mh.MonitorID]+ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] > 0 { upPercent = ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] * 100 / (ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] + ss.serviceResponseDataStoreCurrentUp[mh.MonitorID]) } - stateStr := getStateStr(upPercent) + stateCode := GetStatusCode(upPercent) // 数据持久化 if ss.serviceCurrentStatusIndex[mh.MonitorID] == _CurrentStatusSize { ss.serviceCurrentStatusIndex[mh.MonitorID] = 0 if err := DB.Create(&model.MonitorHistory{ MonitorID: mh.MonitorID, Delay: ss.serviceStatusToday[mh.MonitorID].Delay, - Successful: stateStr == _StatusOk, + Successful: stateCode == StatusGood, Data: mh.Data, }).Error; err != nil { log.Println("NEZHA>> 服务监控数据持久化失败:", err) } } - if stateStr == "故障" || stateStr != ss.lastStatus[mh.MonitorID] { + if stateCode == StatusDown || stateCode != ss.lastStatus[mh.MonitorID] { ss.monitorsLock.RLock() - isNeedSendNotification := (ss.lastStatus[mh.MonitorID] != "" || stateStr == "故障") && ss.monitors[mh.MonitorID].Notify - ss.lastStatus[mh.MonitorID] = stateStr + isNeedSendNotification := (ss.lastStatus[mh.MonitorID] != 0 || stateCode == StatusDown) && ss.monitors[mh.MonitorID].Notify + ss.lastStatus[mh.MonitorID] = stateCode if isNeedSendNotification { - go SendNotification(ss.monitors[mh.MonitorID].NotificationTag, fmt.Sprintf("[服务%s] %s", stateStr, ss.monitors[mh.MonitorID].Name), true) + go SendNotification(ss.monitors[mh.MonitorID].NotificationTag, fmt.Sprintf("[%s] %s", StatusCodeToString(stateCode), ss.monitors[mh.MonitorID].Name), true) } ss.monitorsLock.RUnlock() } @@ -385,7 +371,7 @@ func (ss *ServiceSentinel) worker() { // 证书过期提醒 if expiresNew.Before(time.Now().AddDate(0, 0, 7)) { errMsg = fmt.Sprintf( - "SSL证书将在七天内过期,过期时间:%s。", + "The SSL certificate will expire within seven days. Expiration time: %s", expiresNew.Format("2006-01-02 15:04:05")) } // 证书变更提醒 @@ -397,7 +383,7 @@ func (ss *ServiceSentinel) worker() { if oldCert[0] != newCert[0] && !expiresNew.Equal(expiresOld) { ss.sslCertCache[mh.MonitorID] = mh.Data errMsg = fmt.Sprintf( - "SSL证书变更,旧:%s, %s 过期;新:%s, %s 过期。", + "SSL certificate changed, old: %s, %s expired; new: %s, %s expired.", oldCert[0], expiresOld.Format("2006-01-02 15:04:05"), newCert[0], expiresNew.Format("2006-01-02 15:04:05")) } } @@ -411,3 +397,39 @@ func (ss *ServiceSentinel) worker() { } } } + +const ( + _ = iota + StatusNoData + StatusGood + StatusLowAvailability + StatusDown +) + +func GetStatusCode[T float32 | uint64](percent T) int { + if percent == 0 { + return StatusNoData + } + if percent > 95 { + return StatusGood + } + if percent > 80 { + return StatusLowAvailability + } + return StatusDown +} + +func StatusCodeToString(statusCode int) string { + switch statusCode { + case StatusNoData: + return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusNoData"}) + case StatusGood: + return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusGood"}) + case StatusLowAvailability: + return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusLowAvailability"}) + case StatusDown: + return Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "StatusDown"}) + default: + return "" + } +}