✨ feat(notification): 添加通知方式
This commit is contained in:
parent
b118958f35
commit
d19de0edc2
@ -24,7 +24,8 @@
|
|||||||
sudo ./nezha.sh
|
sudo ./nezha.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主题自定义
|
## 使用说明
|
||||||
|
### 自定义 CSS
|
||||||
|
|
||||||
- 默认主题更改进度条颜色示例
|
- 默认主题更改进度条颜色示例
|
||||||
|
|
||||||
@ -42,6 +43,10 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 通知
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
|
|
||||||
### 数据备份恢复
|
### 数据备份恢复
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -30,8 +31,10 @@ func (ma *memberAPI) serve() {
|
|||||||
|
|
||||||
mr.POST("/logout", ma.logout)
|
mr.POST("/logout", ma.logout)
|
||||||
mr.POST("/server", ma.addOrEditServer)
|
mr.POST("/server", ma.addOrEditServer)
|
||||||
|
mr.POST("/notification", ma.addOrEditNotification)
|
||||||
mr.POST("/setting", ma.updateSetting)
|
mr.POST("/setting", ma.updateSetting)
|
||||||
mr.DELETE("/server/:id", ma.delete)
|
mr.DELETE("/server/:id", ma.delete)
|
||||||
|
mr.DELETE("/notification/:id", ma.deleteNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) delete(c *gin.Context) {
|
func (ma *memberAPI) delete(c *gin.Context) {
|
||||||
@ -58,6 +61,27 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ma *memberAPI) deleteNotification(c *gin.Context) {
|
||||||
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if id < 1 {
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "错误的 Notification ID",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dao.DB.Delete(&model.Notification{}, "id = ?", id).Error; err != nil {
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: fmt.Sprintf("数据库错误:%s", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type serverForm struct {
|
type serverForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string `binding:"required"`
|
Name string `binding:"required"`
|
||||||
@ -96,6 +120,51 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type notificationForm struct {
|
||||||
|
ID uint64
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
RequestMethod int
|
||||||
|
RequestType int
|
||||||
|
RequestBody string
|
||||||
|
VerifySSL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
||||||
|
var nf notificationForm
|
||||||
|
var n model.Notification
|
||||||
|
err := c.ShouldBindJSON(&nf)
|
||||||
|
if err == nil {
|
||||||
|
var data map[string]string
|
||||||
|
err = json.Unmarshal([]byte(nf.RequestBody), &data)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
n.Name = nf.Name
|
||||||
|
n.RequestMethod = nf.RequestMethod
|
||||||
|
n.RequestType = nf.RequestType
|
||||||
|
n.RequestBody = nf.RequestBody
|
||||||
|
n.URL = nf.URL
|
||||||
|
verifySSL := nf.VerifySSL == "on"
|
||||||
|
n.VerifySSL = &verifySSL
|
||||||
|
n.ID = nf.ID
|
||||||
|
if n.ID == 0 {
|
||||||
|
err = dao.DB.Create(&n).Error
|
||||||
|
} else {
|
||||||
|
err = dao.DB.Save(&n).Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: fmt.Sprintf("请求错误:%s", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type logoutForm struct {
|
type logoutForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
"github.com/naiba/nezha/pkg/mygin"
|
||||||
"github.com/naiba/nezha/service/dao"
|
"github.com/naiba/nezha/service/dao"
|
||||||
)
|
)
|
||||||
@ -22,6 +23,7 @@ func (mp *memberPage) serve() {
|
|||||||
Redirect: "/login",
|
Redirect: "/login",
|
||||||
}))
|
}))
|
||||||
mr.GET("/server", mp.server)
|
mr.GET("/server", mp.server)
|
||||||
|
mr.GET("/notification", mp.notification)
|
||||||
mr.GET("/setting", mp.setting)
|
mr.GET("/setting", mp.setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +36,15 @@ func (mp *memberPage) server(c *gin.Context) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mp *memberPage) notification(c *gin.Context) {
|
||||||
|
var nf []model.Notification
|
||||||
|
dao.DB.Find(&nf)
|
||||||
|
c.HTML(http.StatusOK, "dashboard/notification", mygin.CommonEnvironment(c, gin.H{
|
||||||
|
"Title": "通知管理",
|
||||||
|
"Notifications": nf,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (mp *memberPage) setting(c *gin.Context) {
|
func (mp *memberPage) setting(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "dashboard/setting", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard/setting", mygin.CommonEnvironment(c, gin.H{
|
||||||
"Title": "系统设置",
|
"Title": "系统设置",
|
||||||
|
@ -34,7 +34,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initDB() {
|
func initDB() {
|
||||||
dao.DB.AutoMigrate(model.Server{}, model.User{})
|
dao.DB.AutoMigrate(model.Server{}, model.User{}, model.Notification{})
|
||||||
// load cache
|
// load cache
|
||||||
var servers []model.Server
|
var servers []model.Server
|
||||||
dao.DB.Find(&servers)
|
dao.DB.Find(&servers)
|
||||||
|
100
model/notification.go
Normal file
100
model/notification.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
NotificationRequestTypeJSON
|
||||||
|
NotificationRequestTypeForm
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
NotificationRequestMethodGET
|
||||||
|
NotificationRequestMethodPOST
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotificatonSender struct {
|
||||||
|
Rule *Rule
|
||||||
|
Server *Server
|
||||||
|
State *State
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Common
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
RequestMethod int
|
||||||
|
RequestType int
|
||||||
|
RequestBody string `gorm:"type:longtext" `
|
||||||
|
VerifySSL *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notification) Send(sender *NotificatonSender, message string) {
|
||||||
|
var verifySSL bool
|
||||||
|
|
||||||
|
if n.VerifySSL != nil && *n.VerifySSL {
|
||||||
|
verifySSL = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
transCfg := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: verifySSL},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: transCfg, Timeout: time.Minute * 10}
|
||||||
|
var reqURL *url.URL
|
||||||
|
reqURL, err = url.Parse(n.URL)
|
||||||
|
var data map[string]string
|
||||||
|
if err == nil && (n.RequestMethod == NotificationRequestMethodGET || n.RequestType == NotificationRequestTypeForm) {
|
||||||
|
err = json.Unmarshal([]byte(n.RequestBody), &data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if n.RequestMethod == NotificationRequestMethodGET {
|
||||||
|
for k, v := range data {
|
||||||
|
reqURL.Query().Set(k, replaceParamsInString(v, sender))
|
||||||
|
}
|
||||||
|
client.Get(reqURL.String())
|
||||||
|
} else {
|
||||||
|
if n.RequestType == NotificationRequestTypeForm {
|
||||||
|
params := url.Values{}
|
||||||
|
for k, v := range data {
|
||||||
|
params.Add(k, replaceParamsInString(v, sender))
|
||||||
|
}
|
||||||
|
client.PostForm(reqURL.String(), params)
|
||||||
|
} else {
|
||||||
|
jsonValue := replaceParamsInJSON(n.RequestBody, sender)
|
||||||
|
if err == nil {
|
||||||
|
client.Post(reqURL.String(), "application/json", strings.NewReader(jsonValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceParamsInString(str string, sender *NotificatonSender) string {
|
||||||
|
str = strings.ReplaceAll(str, "#CPU#", fmt.Sprintf("%2f%%", sender.State.CPU))
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceParamsInJSON(str string, sender *NotificatonSender) string {
|
||||||
|
str = strings.ReplaceAll(str, "#CPU#", fmt.Sprintf("%2f%%", sender.State.CPU))
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonEscape(raw interface{}) string {
|
||||||
|
b, _ := json.Marshal(raw)
|
||||||
|
strb := string(b)
|
||||||
|
if strings.HasPrefix(strb, "\"") {
|
||||||
|
return strb[1 : len(strb)-1]
|
||||||
|
}
|
||||||
|
return strb
|
||||||
|
}
|
10
model/rule.go
Normal file
10
model/rule.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Rule struct {
|
||||||
|
Common
|
||||||
|
Name string
|
||||||
|
Type string // 指标类型,cpu、memory、swap、disk、net_in、net_out、net_all、transfer_in、transfer_out、transfer_all、offline
|
||||||
|
Min uint64 // 最小阈值
|
||||||
|
Max uint64 // 最大阈值
|
||||||
|
Duration uint64 // 持续时间
|
||||||
|
}
|
@ -2,6 +2,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pb "github.com/naiba/nezha/proto"
|
pb "github.com/naiba/nezha/proto"
|
||||||
@ -21,6 +22,6 @@ type Server struct {
|
|||||||
StreamClose chan<- error `gorm:"-" json:"-"`
|
StreamClose chan<- error `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) Marshal() string {
|
func (s Server) Marshal() template.JS {
|
||||||
return 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"}`, s.ID, s.Name, s.Secret))
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ function showFormModal(modelSelector, formID, URL, getData) {
|
|||||||
form.children('.message').remove()
|
form.children('.message').remove()
|
||||||
btn.toggleClass('loading')
|
btn.toggleClass('loading')
|
||||||
const data = getData ? getData() : $(formID).serializeArray().reduce(function (obj, item) {
|
const data = getData ? getData() : $(formID).serializeArray().reduce(function (obj, item) {
|
||||||
obj[item.name] = (item.name.endsWith('_id') || item.name === 'id') ? parseInt(item.value) : item.value;
|
obj[item.name] = (item.name.endsWith('_id') || item.name === 'id' || item.name === 'ID' || item.name === 'RequestType' || item.name === 'RequestMethod') ? parseInt(item.value) : item.value;
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
$.post(URL, JSON.stringify(data)).done(function (resp) {
|
$.post(URL, JSON.stringify(data)).done(function (resp) {
|
||||||
@ -70,11 +70,26 @@ function showFormModal(modelSelector, formID, URL, getData) {
|
|||||||
}).modal('show')
|
}).modal('show')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOrEditNotification(notification) {
|
||||||
|
const modal = $('.notification.modal')
|
||||||
|
modal.children('.header').text((notification ? '修改' : '添加') + '通知方式')
|
||||||
|
modal.find('.positive.button').html(notification ? '修改<i class="edit icon"></i>' : '添加<i class="add icon"></i>')
|
||||||
|
modal.find('input[name=ID]').val(notification ? notification.ID : null)
|
||||||
|
modal.find('input[name=Name]').val(notification ? notification.Name : null)
|
||||||
|
modal.find('input[name=URL]').val(notification ? notification.URL : null)
|
||||||
|
modal.find('textarea[name=RequestBody]').val(notification ? notification.RequestBody : null)
|
||||||
|
modal.find('select[name=RequestMethod]').val(notification ? notification.RequestMethod : 1)
|
||||||
|
modal.find('select[name=RequestType]').val(notification ? notification.RequestType : 1)
|
||||||
|
if (notification && notification.VerifySSL) {
|
||||||
|
modal.find('.ui.checkbox').checkbox('set checked')
|
||||||
|
} else {
|
||||||
|
modal.find('.ui.checkbox').checkbox('set unchecked')
|
||||||
|
}
|
||||||
|
showFormModal('.notification.modal', '#notificationForm', '/api/notification')
|
||||||
|
}
|
||||||
|
|
||||||
function addOrEditServer(server) {
|
function addOrEditServer(server) {
|
||||||
const modal = $('.server.modal')
|
const modal = $('.server.modal')
|
||||||
if (server) {
|
|
||||||
server = JSON.parse(server)
|
|
||||||
}
|
|
||||||
modal.children('.header').text((server ? '修改' : '添加') + '服务器')
|
modal.children('.header').text((server ? '修改' : '添加') + '服务器')
|
||||||
modal.find('.positive.button').html(server ? '修改<i class="edit icon"></i>' : '添加<i class="add icon"></i>')
|
modal.find('.positive.button').html(server ? '修改<i class="edit icon"></i>' : '添加<i class="add icon"></i>')
|
||||||
modal.find('input[name=id]').val(server ? server.ID : null)
|
modal.find('input[name=id]').val(server ? server.ID : null)
|
||||||
|
@ -1,21 +1,3 @@
|
|||||||
/* Dark mode */
|
|
||||||
|
|
||||||
#darkmodeButton {
|
|
||||||
background-color: black;
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 2rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
z-index: 9999;
|
|
||||||
border-radius: 50%;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark #darkmodeButton {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark {
|
body.dark {
|
||||||
background: #263236;
|
background: #263236;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@ -38,7 +20,8 @@ body.dark .panel {
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark .panel h3, body.dark .panel span {
|
body.dark .panel h3,
|
||||||
|
body.dark .panel span {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<img src="/static/logo.png">
|
<img src="/static/logo.png">
|
||||||
</div>
|
</div>
|
||||||
<a class="item{{if eq .MatchedPath "/"}} active{{end}}" href="/">首页</a>
|
<a class="item{{if eq .MatchedPath " /"}} active{{end}}" href="/">首页</a>
|
||||||
{{if .Admin}}
|
{{if .Admin}}
|
||||||
<a class="item{{if eq .MatchedPath "/server"}} active{{end}}" href="/server">服务器</a>
|
<a class="item{{if eq .MatchedPath " /server"}} active{{end}}" href="/server">服务器</a>
|
||||||
<a class="item{{if eq .MatchedPath "/setting"}} active{{end}}" href="/setting">设置</a>
|
<a class="item{{if eq .MatchedPath " /notification"}} active{{end}}" href="/notification">通知</a>
|
||||||
|
<a class="item{{if eq .MatchedPath " /setting"}} active{{end}}" href="/setting">设置</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="right menu">
|
<div class="right menu">
|
||||||
<a class="item" href="https://github.com/naiba/nezha/issues" target="_blank">反馈</a>
|
<a class="item" href="https://github.com/naiba/nezha/issues" target="_blank">反馈</a>
|
||||||
|
47
resource/template/component/notification.html
Normal file
47
resource/template/component/notification.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{{define "component/notification"}}
|
||||||
|
<div class="ui tiny notification modal transition hidden">
|
||||||
|
<div class="header">添加通知方式</div>
|
||||||
|
<div class="content">
|
||||||
|
<form id="notificationForm" class="ui form">
|
||||||
|
<input type="hidden" name="ID">
|
||||||
|
<div class="field">
|
||||||
|
<label>备注</label>
|
||||||
|
<input type="text" name="Name">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>URL</label>
|
||||||
|
<input type="text" name="URL">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>请求方式</label>
|
||||||
|
<select name="RequestMethod" class="ui fluid dropdown">
|
||||||
|
<option value="1">GET</option>
|
||||||
|
<option value="2">POST</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>请求类型</label>
|
||||||
|
<select name="RequestType" class="ui fluid dropdown">
|
||||||
|
<option value="1">JSON</option>
|
||||||
|
<option value="2">FORM</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="secret field">
|
||||||
|
<label>Body</label>
|
||||||
|
<textarea name="RequestBody"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="VerifySSL" type="checkbox" tabindex="0" class="hidden">
|
||||||
|
<label>验证SSL</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui negative button">取消</div>
|
||||||
|
<button class="ui positive right labeled icon button">确认<i class="checkmark icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
29
resource/template/component/rule.html
Normal file
29
resource/template/component/rule.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{{define "component/rule"}}
|
||||||
|
<div class="ui tiny notification modal transition hidden">
|
||||||
|
<div class="header">添加通知规则</div>
|
||||||
|
<div class="content">
|
||||||
|
<form id="notificationForm" class="ui form">
|
||||||
|
<input type="hidden" name="ID">
|
||||||
|
<div class="field">
|
||||||
|
<label>备注</label>
|
||||||
|
<input type="text" name="Name">
|
||||||
|
</div>
|
||||||
|
<div class="secret field">
|
||||||
|
<label>规则</label>
|
||||||
|
<textarea name="RequestBody"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="VerifySSL" type="checkbox" tabindex="0" class="hidden">
|
||||||
|
<label>启用</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui negative button">取消</div>
|
||||||
|
<button class="ui positive right labeled icon button">确认<i class="checkmark icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class=" actions">
|
<div class=" actions">
|
||||||
<div class="ui negative button">取消</div>
|
<div class="ui negative button">取消</div>
|
||||||
<button class="ui positive right labeled icon button">绑定<i class="checkmark icon"></i>
|
<button class="ui positive right labeled icon button">确认<i class="checkmark icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
88
resource/template/dashboard/notification.html
Normal file
88
resource/template/dashboard/notification.html
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{{define "dashboard/notification"}}
|
||||||
|
{{template "common/header" .}}
|
||||||
|
{{template "common/menu" .}}
|
||||||
|
<div class="nb-container">
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="right floated right aligned twelve wide column">
|
||||||
|
<button class="ui right labeled positive icon button" onclick="addOrEditNotification()"><i
|
||||||
|
class="add icon"></i> 添加通知方式
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="ui very basic table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>备注</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>管理</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $notification := .Notifications}}
|
||||||
|
<tr>
|
||||||
|
<td>{{$notification.ID}}</td>
|
||||||
|
<td>{{$notification.Name}}</td>
|
||||||
|
<td>{{$notification.URL}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="ui mini icon buttons">
|
||||||
|
<button class="ui button" onclick="addOrEditNotification({{$notification}})">
|
||||||
|
<i class="edit icon"></i>
|
||||||
|
</button>
|
||||||
|
<button class="ui button"
|
||||||
|
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/notification/'+{{$notification.ID}})">
|
||||||
|
<i class="delete icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="right floated right aligned twelve wide column">
|
||||||
|
<button class="ui right labeled positive icon button" onclick="addOrEditNotification()"><i
|
||||||
|
class="add icon"></i> 添加报警规则
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="ui very basic table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>备注</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>管理</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $notification := .Notifications}}
|
||||||
|
<tr>
|
||||||
|
<td>{{$notification.ID}}</td>
|
||||||
|
<td>{{$notification.Name}}</td>
|
||||||
|
<td>{{$notification.URL}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="ui mini icon buttons">
|
||||||
|
<button class="ui button" onclick="addOrEditNotification({{$notification}})">
|
||||||
|
<i class="edit icon"></i>
|
||||||
|
</button>
|
||||||
|
<button class="ui button"
|
||||||
|
onclick="showConfirm('删除通知方式','确认删除此通知方式?',deleteRequest,'/api/notification/'+{{$notification.ID}})">
|
||||||
|
<i class="delete icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{template "component/notification"}}
|
||||||
|
{{template "common/footer" .}}
|
||||||
|
<script>
|
||||||
|
$('.checkbox').checkbox()
|
||||||
|
</script>
|
||||||
|
{{end}}
|
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>自定义CSS</label>
|
<label>自定义CSS</label>
|
||||||
<textarea name="CustomCSS" value="{{.Conf.Site.CustomCSS}}"></textarea>
|
<textarea name="CustomCSS">{{.Conf.Site.CustomCSS}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui button" type="submit">保存</button>
|
<button class="ui button" type="submit">保存</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -134,8 +134,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="darkmodeButton"></button>
|
|
||||||
<footer>
|
<footer>
|
||||||
<p style="text-align:center;padding: 15px;">Powered by <a href="https://github.com/naiba/nezha">哪吒面板</a> build ·
|
<p style="text-align:center;padding: 15px;">Powered by <a href="https://github.com/naiba/nezha">哪吒面板</a> build ·
|
||||||
{{.Version}}
|
{{.Version}}
|
||||||
@ -202,30 +200,6 @@
|
|||||||
if (hour > 17 || hour < 4) {
|
if (hour > 17 || hour < 4) {
|
||||||
document.body.classList.add('dark');
|
document.body.classList.add('dark');
|
||||||
}
|
}
|
||||||
return
|
|
||||||
//darkmode
|
|
||||||
if (document.getElementById("darkmodeButton")) {
|
|
||||||
var night = parseInt(document.cookie.replace(/(?:(?:^|.*;\s*)dark\s*=\s*([^;]*).*$)|^.*$/, "$1") || '0');
|
|
||||||
if (night) {
|
|
||||||
document.body.classList.add('dark');
|
|
||||||
console.log('Dark mode on', night);
|
|
||||||
}
|
|
||||||
document.getElementById("darkmodeButton").onclick = function () {
|
|
||||||
night = parseInt(document.cookie.replace(/(?:(?:^|.*;\s*)dark\s*=\s*([^;]*).*$)|^.*$/, "$1") || '0');
|
|
||||||
if (!night) {
|
|
||||||
document.body.classList.add('dark');
|
|
||||||
document.cookie = "dark=1";
|
|
||||||
console.log('Dark mode on', night);
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('dark');
|
|
||||||
document.cookie = "dark=0";
|
|
||||||
console.log('Dark mode off', night);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.cookie && (document.cookie = "");
|
|
||||||
console.log('Darkmode not Support');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
secondToDate(s) {
|
secondToDate(s) {
|
||||||
var d = Math.floor(s / 3600 / 24);
|
var d = Math.floor(s / 3600 / 24);
|
||||||
|
Loading…
Reference in New Issue
Block a user