feat: 后台服务器备注 close #72

This commit is contained in:
naiba 2021-01-20 19:24:59 +08:00
parent 29bc810a8f
commit ec17948fe4
12 changed files with 83 additions and 66 deletions

View File

@ -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 上执行脚本(应用于定期备份、重启服务等计划运维场景)

View File

@ -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{

View File

@ -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]

View File

@ -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,

View File

@ -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"`

View File

@ -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))
}

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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
})
}