mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 20:58: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 |
|
| 首页截图1 | 首页截图2 |
|
||||||
| ---- | ---- |
|
| ---- | ---- |
|
||||||
@ -207,16 +209,3 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
|||||||
- [哪吒面板,一个便携服务器状态监控面板搭建教程,不想拥有一个自己的探针吗?](https://haoduck.com/644.html)
|
- [哪吒面板,一个便携服务器状态监控面板搭建教程,不想拥有一个自己的探针吗?](https://haoduck.com/644.html)
|
||||||
- [哪吒面板:小鸡们的最佳探针](https://www.zhujizixun.com/2843.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)
|
- [>>更多教程](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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -36,6 +37,20 @@ type ServiceItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *commonPage) service(c *gin.Context) {
|
func (p *commonPage) service(c *gin.Context) {
|
||||||
|
var msm map[uint64]*ServiceItem
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cached {
|
||||||
|
msm = make(map[uint64]*ServiceItem)
|
||||||
var ms []model.Monitor
|
var ms []model.Monitor
|
||||||
dao.DB.Find(&ms)
|
dao.DB.Find(&ms)
|
||||||
year, month, day := time.Now().Date()
|
year, month, day := time.Now().Date()
|
||||||
@ -43,7 +58,6 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
var mhs []model.MonitorHistory
|
var mhs []model.MonitorHistory
|
||||||
dao.DB.Where("created_at >= ?", today.AddDate(0, 0, -29)).Find(&mhs)
|
dao.DB.Where("created_at >= ?", today.AddDate(0, 0, -29)).Find(&mhs)
|
||||||
|
|
||||||
msm := make(map[uint64]*ServiceItem)
|
|
||||||
for i := 0; i < len(ms); i++ {
|
for i := 0; i < len(ms); i++ {
|
||||||
msm[ms[i].ID] = &ServiceItem{
|
msm[ms[i].ID] = &ServiceItem{
|
||||||
Monitor: ms[i],
|
Monitor: ms[i],
|
||||||
@ -52,7 +66,6 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
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},
|
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)
|
todayStatus := make(map[uint64][]bool)
|
||||||
for i := 0; i < len(mhs); i++ {
|
for i := 0; i < len(mhs); i++ {
|
||||||
@ -71,7 +84,6 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
msm[mhs[i].MonitorID].Down[dayIndex]++
|
msm[mhs[i].MonitorID].Down[dayIndex]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当日最后 20 个采样作为当前状态
|
// 当日最后 20 个采样作为当前状态
|
||||||
for _, m := range msm {
|
for _, m := range msm {
|
||||||
for i := len(todayStatus[m.Monitor.ID]) - 1; i >= 0 && i >= (len(todayStatus[m.Monitor.ID])-1-20); i-- {
|
for i := len(todayStatus[m.Monitor.ID]) - 1; i >= 0 && i >= (len(todayStatus[m.Monitor.ID])-1-20); i-- {
|
||||||
@ -82,6 +94,9 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 未登录人员缓存十分钟
|
||||||
|
dao.Cache.Set(model.CacheKeyServicePage, msm, time.Minute*10)
|
||||||
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
|
||||||
"Title": "服务状态",
|
"Title": "服务状态",
|
||||||
|
@ -108,6 +108,7 @@ type serverForm struct {
|
|||||||
DisplayIndex int
|
DisplayIndex int
|
||||||
Secret string
|
Secret string
|
||||||
Tag string
|
Tag string
|
||||||
|
Note string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||||
@ -122,6 +123,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
|||||||
s.DisplayIndex = sf.DisplayIndex
|
s.DisplayIndex = sf.DisplayIndex
|
||||||
s.ID = sf.ID
|
s.ID = sf.ID
|
||||||
s.Tag = sf.Tag
|
s.Tag = sf.Tag
|
||||||
|
s.Note = sf.Note
|
||||||
if sf.ID == 0 {
|
if sf.ID == 0 {
|
||||||
s.Secret = utils.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
|
s.Secret = utils.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
|
||||||
s.Secret = s.Secret[:10]
|
s.Secret = s.Secret[:10]
|
||||||
|
@ -28,14 +28,14 @@ func (oa *oauth2controller) serve() {
|
|||||||
|
|
||||||
func (oa *oauth2controller) login(c *gin.Context) {
|
func (oa *oauth2controller) login(c *gin.Context) {
|
||||||
state := utils.RandStringBytesMaskImprSrcUnsafe(6)
|
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)
|
url := oa.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOnline)
|
||||||
c.Redirect(http.StatusFound, url)
|
c.Redirect(http.StatusFound, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oa *oauth2controller) callback(c *gin.Context) {
|
func (oa *oauth2controller) callback(c *gin.Context) {
|
||||||
// 验证登录跳转时的 State
|
// 验证登录跳转时的 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") {
|
if !ok || state.(string) != c.Query("state") {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
@ -4,7 +4,8 @@ import "time"
|
|||||||
|
|
||||||
const CtxKeyAuthorizedUser = "ckau"
|
const CtxKeyAuthorizedUser = "ckau"
|
||||||
|
|
||||||
const CtxKeyOauth2State = "cko2s"
|
const CacheKeyOauth2State = "p:a:state"
|
||||||
|
const CacheKeyServicePage = "p:c:service"
|
||||||
|
|
||||||
type Common struct {
|
type Common struct {
|
||||||
ID uint64 `gorm:"primary_key"`
|
ID uint64 `gorm:"primary_key"`
|
||||||
|
@ -11,8 +11,9 @@ import (
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
Tag string
|
Tag string // 分组名
|
||||||
Secret string `json:"-"`
|
Secret string `json:"-"`
|
||||||
|
Note string `json:"-"` // 管理员可见备注
|
||||||
DisplayIndex int // 展示权重,越大越靠前
|
DisplayIndex int // 展示权重,越大越靠前
|
||||||
|
|
||||||
Host *Host `gorm:"-"`
|
Host *Host `gorm:"-"`
|
||||||
@ -24,5 +25,5 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) Marshal() template.JS {
|
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号">
|
<input type="text" name="name" placeholder="爱因斯坦-光速1号">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>标签</label>
|
<label>分组</label>
|
||||||
<input type="text" name="Tag" placeholder="服务器标签">
|
<input type="text" name="Tag" placeholder="服务器分组">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>展示权重</label>
|
<label>展示权重</label>
|
||||||
@ -20,6 +20,10 @@
|
|||||||
<label>密钥</label>
|
<label>密钥</label>
|
||||||
<input type="text" name="secret">
|
<input type="text" name="secret">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>隐藏备注</label>
|
||||||
|
<textarea name="Note"></textarea>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class=" actions">
|
<div class=" actions">
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
||||||
<i class="delete icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/notification/'+{{$notification.ID}})">
|
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/notification/'+{{$notification.ID}})">
|
||||||
<i class="delete icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -74,7 +74,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/alert-rule/'+{{$rule.ID}})">
|
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/alert-rule/'+{{$rule.ID}})">
|
||||||
<i class="delete icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>权重</th>
|
<th>权重</th>
|
||||||
<th>备注</th>
|
<th>备注</th>
|
||||||
<th>标签</th>
|
<th>分组</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>密钥</th>
|
<th>密钥</th>
|
||||||
|
<th>隐藏备注</th>
|
||||||
<th>管理</th>
|
<th>管理</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -31,6 +32,7 @@
|
|||||||
<td>{{$server.Host.IP}}</td>
|
<td>{{$server.Host.IP}}</td>
|
||||||
<td>{{$server.ID}}</td>
|
<td>{{$server.ID}}</td>
|
||||||
<td>{{$server.Secret}}</td>
|
<td>{{$server.Secret}}</td>
|
||||||
|
<td style="word-break: break-word;">{{$server.Note}}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="ui mini icon buttons">
|
<div class="ui mini icon buttons">
|
||||||
<button class="ui button" onclick="addOrEditServer({{$server.Marshal}})">
|
<button class="ui button" onclick="addOrEditServer({{$server.Marshal}})">
|
||||||
@ -38,7 +40,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除节点','确认删除此监控节点?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
onclick="showConfirm('删除节点','确认删除此监控节点?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
||||||
<i class="delete icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="ui center aligned">{{range $i,$d := $service.Delay}}
|
<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)))}}"
|
<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}}
|
</div> {{end}}
|
||||||
</td>
|
</td>
|
||||||
<td class="ui center aligned delay-today">
|
<td class="ui center aligned delay-today">
|
||||||
|
@ -34,7 +34,7 @@ var CronLock sync.RWMutex
|
|||||||
var Crons map[uint64]*model.Cron
|
var Crons map[uint64]*model.Cron
|
||||||
var Cron *cron.Cron
|
var Cron *cron.Cron
|
||||||
|
|
||||||
var Version = "v0.3.1"
|
var Version = "v0.3.2"
|
||||||
|
|
||||||
func ReSortServer() {
|
func ReSortServer() {
|
||||||
ServerLock.RLock()
|
ServerLock.RLock()
|
||||||
@ -48,6 +48,9 @@ func ReSortServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(SortedServerList, func(i, j int) bool {
|
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
|
return SortedServerList[i].DisplayIndex > SortedServerList[j].DisplayIndex
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user