mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
✨ feat: 后台服务器备注 close #72
This commit is contained in:
parent
29bc810a8f
commit
ec17948fe4
17
README.md
17
README.md
@ -1,6 +1,8 @@
|
||||
# 哪吒面板
|
||||
|
||||
系统状态监控报警;API(SSL证书变更、即将到期、到期)/TCP端口存活/PING 监控;计划任务可以定时在Agent上执行命令(备份、重启、What ever you want);极省资源,64M 服务器也能装 agent。
|
||||
![dashboard](https://img.shields.io/badge/管理面板-v0.3.2-brightgreen) ![agent](https://img.shields.io/badge/Agent-v0.3.1-brightgreen)
|
||||
|
||||
系统状态监控报警、API(SSL证书变更、即将到期、到期)/TCP端口存活/PING 监控、计划任务(可以定时在Agent上执行命令,备份、重启、What ever you want)、极省资源,64M 服务器也能装 agent。
|
||||
|
||||
| 首页截图1 | 首页截图2 |
|
||||
| ---- | ---- |
|
||||
@ -207,16 +209,3 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
||||
- [哪吒面板,一个便携服务器状态监控面板搭建教程,不想拥有一个自己的探针吗?](https://haoduck.com/644.html)
|
||||
- [哪吒面板:小鸡们的最佳探针](https://www.zhujizixun.com/2843.html) *(已过时)*
|
||||
- [>>更多教程](https://www.google.com/search?q=%22%E5%93%AA%E5%90%92%E9%9D%A2%E6%9D%BF%22+%22%E6%95%99%E7%A8%8B%22) (Google)
|
||||
|
||||
## 变更日志
|
||||
|
||||
只保留最后一次更新导致必须更新面板的说明。
|
||||
|
||||
- `agent 0.3.1`
|
||||
|
||||
- 0.3.0 的更新的一个问题导致 Windows 用户的 Agent 无法自动更新,需要下载 0.3.1 或更新的 release 手动替换掉二进制文件。
|
||||
- 增加了对 mips(路由器) 的支持,在 release 处下载后手动配置
|
||||
|
||||
- `dashboard 0.3.0` `agent 0.3.0` **重大更新**
|
||||
|
||||
增加了定时任务功能,可以定时在 Agent 上执行脚本(应用于定期备份、重启服务等计划运维场景)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@ -36,51 +37,65 @@ type ServiceItem struct {
|
||||
}
|
||||
|
||||
func (p *commonPage) service(c *gin.Context) {
|
||||
var ms []model.Monitor
|
||||
dao.DB.Find(&ms)
|
||||
year, month, day := time.Now().Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
var mhs []model.MonitorHistory
|
||||
dao.DB.Where("created_at >= ?", today.AddDate(0, 0, -29)).Find(&mhs)
|
||||
var msm map[uint64]*ServiceItem
|
||||
|
||||
msm := make(map[uint64]*ServiceItem)
|
||||
for i := 0; i < len(ms); i++ {
|
||||
msm[ms[i].ID] = &ServiceItem{
|
||||
Monitor: ms[i],
|
||||
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
Down: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
var cached bool
|
||||
if _, has := c.Get(model.CtxKeyAuthorizedUser); !has {
|
||||
data, has := dao.Cache.Get(model.CacheKeyServicePage)
|
||||
if has {
|
||||
log.Println("use cache")
|
||||
msm = data.(map[uint64]*ServiceItem)
|
||||
cached = true
|
||||
}
|
||||
}
|
||||
|
||||
// 整合数据
|
||||
todayStatus := make(map[uint64][]bool)
|
||||
for i := 0; i < len(mhs); i++ {
|
||||
dayIndex := 29
|
||||
if mhs[i].CreatedAt.Before(today) {
|
||||
dayIndex = 28 - (int(today.Sub(mhs[i].CreatedAt).Hours()) / 24)
|
||||
} else {
|
||||
todayStatus[mhs[i].MonitorID] = append(todayStatus[mhs[i].MonitorID], mhs[i].Successful)
|
||||
}
|
||||
if mhs[i].Successful {
|
||||
msm[mhs[i].MonitorID].TotalUp++
|
||||
msm[mhs[i].MonitorID].Delay[dayIndex] = (msm[mhs[i].MonitorID].Delay[dayIndex]*float32(msm[mhs[i].MonitorID].Up[dayIndex]) + mhs[i].Delay) / float32(msm[mhs[i].MonitorID].Up[dayIndex]+1)
|
||||
msm[mhs[i].MonitorID].Up[dayIndex]++
|
||||
} else {
|
||||
msm[mhs[i].MonitorID].TotalDown++
|
||||
msm[mhs[i].MonitorID].Down[dayIndex]++
|
||||
}
|
||||
}
|
||||
if !cached {
|
||||
msm = make(map[uint64]*ServiceItem)
|
||||
var ms []model.Monitor
|
||||
dao.DB.Find(&ms)
|
||||
year, month, day := time.Now().Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
var mhs []model.MonitorHistory
|
||||
dao.DB.Where("created_at >= ?", today.AddDate(0, 0, -29)).Find(&mhs)
|
||||
|
||||
// 当日最后 20 个采样作为当前状态
|
||||
for _, m := range msm {
|
||||
for i := len(todayStatus[m.Monitor.ID]) - 1; i >= 0 && i >= (len(todayStatus[m.Monitor.ID])-1-20); i-- {
|
||||
if todayStatus[m.Monitor.ID][i] {
|
||||
m.CurrentUp++
|
||||
} else {
|
||||
m.CurrentDown++
|
||||
for i := 0; i < len(ms); i++ {
|
||||
msm[ms[i].ID] = &ServiceItem{
|
||||
Monitor: ms[i],
|
||||
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
Down: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
}
|
||||
}
|
||||
// 整合数据
|
||||
todayStatus := make(map[uint64][]bool)
|
||||
for i := 0; i < len(mhs); i++ {
|
||||
dayIndex := 29
|
||||
if mhs[i].CreatedAt.Before(today) {
|
||||
dayIndex = 28 - (int(today.Sub(mhs[i].CreatedAt).Hours()) / 24)
|
||||
} else {
|
||||
todayStatus[mhs[i].MonitorID] = append(todayStatus[mhs[i].MonitorID], mhs[i].Successful)
|
||||
}
|
||||
if mhs[i].Successful {
|
||||
msm[mhs[i].MonitorID].TotalUp++
|
||||
msm[mhs[i].MonitorID].Delay[dayIndex] = (msm[mhs[i].MonitorID].Delay[dayIndex]*float32(msm[mhs[i].MonitorID].Up[dayIndex]) + mhs[i].Delay) / float32(msm[mhs[i].MonitorID].Up[dayIndex]+1)
|
||||
msm[mhs[i].MonitorID].Up[dayIndex]++
|
||||
} else {
|
||||
msm[mhs[i].MonitorID].TotalDown++
|
||||
msm[mhs[i].MonitorID].Down[dayIndex]++
|
||||
}
|
||||
}
|
||||
// 当日最后 20 个采样作为当前状态
|
||||
for _, m := range msm {
|
||||
for i := len(todayStatus[m.Monitor.ID]) - 1; i >= 0 && i >= (len(todayStatus[m.Monitor.ID])-1-20); i-- {
|
||||
if todayStatus[m.Monitor.ID][i] {
|
||||
m.CurrentUp++
|
||||
} else {
|
||||
m.CurrentDown++
|
||||
}
|
||||
}
|
||||
}
|
||||
// 未登录人员缓存十分钟
|
||||
dao.Cache.Set(model.CacheKeyServicePage, msm, time.Minute*10)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
|
||||
|
@ -108,6 +108,7 @@ type serverForm struct {
|
||||
DisplayIndex int
|
||||
Secret string
|
||||
Tag string
|
||||
Note string
|
||||
}
|
||||
|
||||
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
@ -122,6 +123,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
s.DisplayIndex = sf.DisplayIndex
|
||||
s.ID = sf.ID
|
||||
s.Tag = sf.Tag
|
||||
s.Note = sf.Note
|
||||
if sf.ID == 0 {
|
||||
s.Secret = utils.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
|
||||
s.Secret = s.Secret[:10]
|
||||
|
@ -28,14 +28,14 @@ func (oa *oauth2controller) serve() {
|
||||
|
||||
func (oa *oauth2controller) login(c *gin.Context) {
|
||||
state := utils.RandStringBytesMaskImprSrcUnsafe(6)
|
||||
dao.Cache.Set(fmt.Sprintf("%s%s", model.CtxKeyOauth2State, c.ClientIP()), state, 0)
|
||||
dao.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()), state, 0)
|
||||
url := oa.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOnline)
|
||||
c.Redirect(http.StatusFound, url)
|
||||
}
|
||||
|
||||
func (oa *oauth2controller) callback(c *gin.Context) {
|
||||
// 验证登录跳转时的 State
|
||||
state, ok := dao.Cache.Get(fmt.Sprintf("%s%s", model.CtxKeyOauth2State, c.ClientIP()))
|
||||
state, ok := dao.Cache.Get(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()))
|
||||
if !ok || state.(string) != c.Query("state") {
|
||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
Code: http.StatusBadRequest,
|
||||
|
@ -4,7 +4,8 @@ import "time"
|
||||
|
||||
const CtxKeyAuthorizedUser = "ckau"
|
||||
|
||||
const CtxKeyOauth2State = "cko2s"
|
||||
const CacheKeyOauth2State = "p:a:state"
|
||||
const CacheKeyServicePage = "p:c:service"
|
||||
|
||||
type Common struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
|
@ -11,8 +11,9 @@ import (
|
||||
type Server struct {
|
||||
Common
|
||||
Name string
|
||||
Tag string
|
||||
Tag string // 分组名
|
||||
Secret string `json:"-"`
|
||||
Note string `json:"-"` // 管理员可见备注
|
||||
DisplayIndex int // 展示权重,越大越靠前
|
||||
|
||||
Host *Host `gorm:"-"`
|
||||
@ -24,5 +25,5 @@ type Server struct {
|
||||
}
|
||||
|
||||
func (s Server) Marshal() template.JS {
|
||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":"%s","Secret":"%s"}`, s.ID, s.Name, s.Secret))
|
||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":"%s","Secret":"%s","DisplayIndex":%d,"Tag":"%s","Note":"%s"}`, s.ID, s.Name, s.Secret, s.DisplayIndex, s.Tag, s.Note))
|
||||
}
|
||||
|
@ -9,8 +9,8 @@
|
||||
<input type="text" name="name" placeholder="爱因斯坦-光速1号">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>标签</label>
|
||||
<input type="text" name="Tag" placeholder="服务器标签">
|
||||
<label>分组</label>
|
||||
<input type="text" name="Tag" placeholder="服务器分组">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>展示权重</label>
|
||||
@ -20,6 +20,10 @@
|
||||
<label>密钥</label>
|
||||
<input type="text" name="secret">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>隐藏备注</label>
|
||||
<textarea name="Note"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class=" actions">
|
||||
|
@ -41,7 +41,7 @@
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
||||
<i class="delete icon"></i>
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -34,7 +34,7 @@
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/notification/'+{{$notification.ID}})">
|
||||
<i class="delete icon"></i>
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@ -74,7 +74,7 @@
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/alert-rule/'+{{$rule.ID}})">
|
||||
<i class="delete icon"></i>
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -15,10 +15,11 @@
|
||||
<tr>
|
||||
<th>权重</th>
|
||||
<th>备注</th>
|
||||
<th>标签</th>
|
||||
<th>分组</th>
|
||||
<th>IP</th>
|
||||
<th>ID</th>
|
||||
<th>密钥</th>
|
||||
<th>隐藏备注</th>
|
||||
<th>管理</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -31,6 +32,7 @@
|
||||
<td>{{$server.Host.IP}}</td>
|
||||
<td>{{$server.ID}}</td>
|
||||
<td>{{$server.Secret}}</td>
|
||||
<td style="word-break: break-word;">{{$server.Note}}</td>
|
||||
<td>
|
||||
<div class="ui mini icon buttons">
|
||||
<button class="ui button" onclick="addOrEditServer({{$server.Marshal}})">
|
||||
@ -38,7 +40,7 @@
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除节点','确认删除此监控节点?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
||||
<i class="delete icon"></i>
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -26,7 +26,7 @@
|
||||
</td>
|
||||
<td class="ui center aligned">{{range $i,$d := $service.Delay}}
|
||||
<div class="ui icon button {{className (div (index $service.Up $i) (add (index $service.Up $i) (index $service.Down $i)))}}"
|
||||
data-tooltip="{{dayBefore $i}},平均延迟:{{float32f $d}}ms">
|
||||
data-tooltip="{{dayBefore $i}},在线率:{{float32f (div (index $service.Up $i) (add (index $service.Up $i) (index $service.Down $i)))}}%,平均延迟:{{float32f $d}}ms">
|
||||
</div> {{end}}
|
||||
</td>
|
||||
<td class="ui center aligned delay-today">
|
||||
|
@ -34,7 +34,7 @@ var CronLock sync.RWMutex
|
||||
var Crons map[uint64]*model.Cron
|
||||
var Cron *cron.Cron
|
||||
|
||||
var Version = "v0.3.1"
|
||||
var Version = "v0.3.2"
|
||||
|
||||
func ReSortServer() {
|
||||
ServerLock.RLock()
|
||||
@ -48,6 +48,9 @@ func ReSortServer() {
|
||||
}
|
||||
|
||||
sort.SliceStable(SortedServerList, func(i, j int) bool {
|
||||
if SortedServerList[i].DisplayIndex == SortedServerList[j].DisplayIndex {
|
||||
return SortedServerList[i].ID < SortedServerList[i].ID
|
||||
}
|
||||
return SortedServerList[i].DisplayIndex > SortedServerList[j].DisplayIndex
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user