Added: notification template

This commit is contained in:
zhenorzz 2024-06-12 10:39:38 +08:00
parent 76232013a6
commit 5c24cb9d50
30 changed files with 892 additions and 453 deletions

View File

@ -4,6 +4,22 @@ All notable changes to the "goploy" extension will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [1.17.2] - 2024-06-12
### Added
- notification template
### Changed
- router order
### Bug fixed
- fix unknown driver
- fix deploy page css
## [1.17.1] - 2024-06-06
### Changed

View File

@ -23,16 +23,19 @@ then
fi
echo "Building goploy";
echo "env GOOS=linux go build -o goploy cmd/server/main.go";
env GOOS=linux go build -o goploy cmd/server/main.go
echo "env GOOS=linux GOARCH=arm64 go build -o goploy_arm64 cmd/server/main.go";
env GOOS=linux GOARCH=arm64 go build -o goploy_arm64 cmd/server/main.go
echo "env GOOS=darwin GOARCH=arm64 go build -o goploy_arm64.mac cmd/server/main.go";
env GOOS=darwin GOARCH=arm64 go build -o goploy_arm64.mac cmd/server/main.go
echo "env GOOS=darwin go build -o goploy.mac cmd/server/main.go";
env GOOS=darwin go build -o goploy.mac cmd/server/main.go
echo "env GOOS=windows go build -o goploy.exe cmd/server/main.go";
env GOOS=windows go build -o goploy.exe cmd/server/main.go

View File

@ -0,0 +1,73 @@
// Copyright 2022 The Goploy Authors. All rights reserved.
// Use of this source code is governed by a GPLv3-style
// license that can be found in the LICENSE file.
package notification
import (
"github.com/zhenorzz/goploy/cmd/server/api"
"github.com/zhenorzz/goploy/cmd/server/api/middleware"
"github.com/zhenorzz/goploy/config"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/server"
"github.com/zhenorzz/goploy/internal/server/response"
"net/http"
)
type Notification api.API
func (n Notification) Handler() []server.Route {
return []server.Route{
server.NewRoute("/notification/getList", http.MethodGet, n.GetList).Permissions(config.ShowNotificationPage),
server.NewRoute("/notification/edit", http.MethodPut, n.Edit).Permissions(config.EditNotification).LogFunc(middleware.AddOPLog),
}
}
// GetList lists notification template
// @Summary List notification template
// @Tags Notification
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Success 200 {object} response.JSON{data=notification.GetList.RespData}
// @Router /notification/getList [get]
func (Notification) GetList(*server.Goploy) server.Response {
list, err := model.NotificationTemplate{}.GetList()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
type RespData struct {
List model.NotificationTemplates `json:"list"`
}
return response.JSON{
Data: RespData{List: list},
}
}
// Edit edits the notification template
// @Summary Edit the notification template
// @Tags Notification
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Param request body notification.Edit.ReqData true "body params"
// @Success 200 {object} response.JSON
// @Router /notification/edit [put]
func (Notification) Edit(gp *server.Goploy) server.Response {
type ReqData struct {
ID int64 `json:"id" validate:"required,gt=0"`
Title string `json:"title" validate:"required"`
Template string `json:"template" validate:"required"`
}
var reqData ReqData
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
err := model.NotificationTemplate{ID: reqData.ID, Title: reqData.Title, Template: reqData.Template}.EditRow()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{}
}

View File

@ -16,6 +16,7 @@ import (
logApi "github.com/zhenorzz/goploy/cmd/server/api/log"
"github.com/zhenorzz/goploy/cmd/server/api/monitor"
"github.com/zhenorzz/goploy/cmd/server/api/namespace"
"github.com/zhenorzz/goploy/cmd/server/api/notification"
"github.com/zhenorzz/goploy/cmd/server/api/project"
"github.com/zhenorzz/goploy/cmd/server/api/repository"
"github.com/zhenorzz/goploy/cmd/server/api/role"
@ -49,7 +50,7 @@ var (
s string
)
const appVersion = "1.17.1"
const appVersion = "1.17.2"
func init() {
flag.StringVar(&config.AssetDir, "asset-dir", "", "default: ./")
@ -64,7 +65,7 @@ func init() {
}
// @title Goploy
// @version 1.17.1
// @version 1.17.2
// @description A web deployment system tool!
// @contact.name zhenorzz
// @contact.url https://github.com/zhenorzz/goploy
@ -130,6 +131,7 @@ func main() {
srv.Router.Register(cron.Cron{})
srv.Router.Register(agent.Agent{})
srv.Router.Register(template.Template{})
srv.Router.Register(notification.Notification{})
srv.Router.Register(ws.GetHub())
go func() {
c := make(chan os.Signal, 1)
@ -188,9 +190,9 @@ func install() {
println("Start to install the database...")
// open connection without database
runner, err := model.Open(config.DBConfig{
Host: cfg.DB.Host, User: cfg.DB.User, Password: cfg.DB.Password, Port: cfg.DB.Port,
})
dbConfig := cfg.DB
dbConfig.Database = ""
runner, err := model.Open(dbConfig)
if err != nil {
panic(err)
}

View File

@ -197,7 +197,6 @@ func (gsync *Gsync) exec() {
log.Errorf(projectLogFormat, gsync.Project.ID, err)
}
gsync.notify(model.ProjectFail, err.Error())
}()
err = gsync.repoStage()

View File

@ -1,14 +1,8 @@
package deploy
import (
"bytes"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/zhenorzz/goploy/internal/model"
"io"
"net/http"
"strings"
"github.com/zhenorzz/goploy/internal/notify"
)
// commit id
@ -16,172 +10,18 @@ import (
// server ip & name
// deploy user name
// deploy time
func (gsync *Gsync) notify(deployState int, detail string) {
func (gsync *Gsync) notify(deployState uint8, detail string) {
if gsync.Project.NotifyType == 0 {
return
}
serverList := ""
for _, projectServer := range gsync.ProjectServers {
if projectServer.Server.Name != projectServer.Server.IP {
serverList += projectServer.Server.Name + "(" + projectServer.Server.IP + ")"
} else {
serverList += projectServer.Server.IP
}
serverList += ", "
}
serverList = strings.TrimRight(serverList, ", ")
project := gsync.Project
commitInfo := gsync.CommitInfo
var err error
var resp *http.Response
if project.NotifyType == model.NotifyWeiXin {
type markdown struct {
Content string `json:"content"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
content := "Deploy: <font color=\"warning\">" + project.Name + "</font>\n"
content += "Publisher: <font color=\"comment\">" + project.PublisherName + "</font>\n"
content += "Author: <font color=\"comment\">" + commitInfo.Author + "</font>\n"
if commitInfo.Tag != "" {
content += "Tag: <font color=\"comment\">" + commitInfo.Tag + "</font>\n"
}
content += "Branch: <font color=\"comment\">" + commitInfo.Branch + "</font>\n"
content += "CommitSHA: <font color=\"comment\">" + commitInfo.Commit + "</font>\n"
content += "CommitMessage: <font color=\"comment\">" + commitInfo.Message + "</font>\n"
content += "ServerList: <font color=\"comment\">" + serverList + "</font>\n"
if deployState == model.ProjectFail {
content += "State: <font color=\"red\">fail</font> \n"
content += "> Detail: <font color=\"comment\">" + detail + "</font>"
} else {
content += "State: <font color=\"green\">success</font>"
}
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Content: content,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if project.NotifyType == model.NotifyDingTalk {
type markdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
text := "#### Deploy" + project.Name + " \n "
text += "#### Publisher" + project.PublisherName + " \n "
text += "#### Author" + commitInfo.Author + " \n "
if commitInfo.Tag != "" {
text += "#### Tag" + commitInfo.Tag + " \n "
}
text += "#### Branch" + commitInfo.Branch + " \n "
text += "#### CommitSHA" + commitInfo.Commit + " \n "
text += "#### CommitMessage" + commitInfo.Message + " \n "
text += "#### ServerList" + serverList + " \n "
if deployState == model.ProjectFail {
text += "#### State <font color=\"red\">fail</font> \n "
text += "> Detail: <font color=\"comment\">" + detail + "</font>"
} else {
text += "#### State <font color=\"green\">success</font>"
}
_ = notify.Send(fmt.Sprintf("project%d-deploy", project.ID), notify.UseByDeploy, notify.DeployData{
DeployState: deployState,
Project: project,
ProjectServers: gsync.ProjectServers,
CommitInfo: gsync.CommitInfo,
DeployDetail: detail,
}, project.NotifyType, project.NotifyTarget)
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Title: project.Name,
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if project.NotifyType == model.NotifyFeiShu {
type content struct {
Text string `json:"text"`
}
type message struct {
MsgType string `json:"msg_type"`
Content content `json:"content"`
}
text := ""
text += "Deploy" + project.Name + "\n"
text += "Publisher: " + project.PublisherName + "\n"
text += "Author: " + commitInfo.Author + "\n"
if commitInfo.Tag != "" {
text += "Tag: " + commitInfo.Tag + "\n"
}
text += "Branch: " + commitInfo.Branch + "\n"
text += "CommitSHA: " + commitInfo.Commit + "\n"
text += "CommitMessage: " + commitInfo.Message + "\n"
text += "ServerList: " + serverList + "\n"
if deployState == model.ProjectFail {
text += "State: fail\n "
text += "Detail: " + detail
} else {
text += "State: success"
}
msg := message{
MsgType: "text",
Content: content{
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if project.NotifyType == model.NotifyCustom {
type message struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
ProjectID int64 `json:"projectId"`
ProjectName string `json:"projectName"`
Publisher string `json:"publisher"`
Author string `json:"author"`
Branch string `json:"branch"`
Tag string `json:"tag"`
CommitSHA string `json:"commitSHA"`
CommitMessage string `json:"commitMessage"`
ServerList string `json:"serverList"`
} `json:"data"`
}
code := 0
if deployState == model.ProjectFail {
code = 1
}
msg := message{
Code: code,
Message: detail,
}
msg.Data.ProjectID = project.ID
msg.Data.ProjectName = project.Name
msg.Data.Publisher = project.PublisherName
msg.Data.Author = commitInfo.Author
msg.Data.Branch = commitInfo.Branch
msg.Data.Tag = commitInfo.Tag
msg.Data.CommitSHA = commitInfo.Commit
msg.Data.CommitMessage = commitInfo.Message
msg.Data.ServerList = serverList
b, _ := json.Marshal(msg)
resp, err = http.Post(project.NotifyTarget, "application/json", bytes.NewBuffer(b))
}
if err != nil {
log.Error(fmt.Sprintf("projectID: %d notify exec err: %s", project.ID, err))
} else {
responseData, err := io.ReadAll(resp.Body)
if err != nil {
log.Error(fmt.Sprintf("projectID: %d notify read body err: %s", project.ID, err))
} else {
log.Trace(fmt.Sprintf("projectID: %d notify success: %s", project.ID, string(responseData)))
}
_ = resp.Body.Close()
}
}

View File

@ -270,9 +270,10 @@ func (gsync *Gsync) serverStage() error {
for i := 0; i < len(gsync.ProjectServers); i++ {
msg := <-ch
if msg.state == model.ProjectFail {
message += msg.serverName + " error message: " + msg.detail
message += msg.serverName + " error message: " + msg.detail + ", "
}
}
message = strings.TrimRight(message, ", ")
close(ch)
if message != "" {

View File

@ -7,10 +7,12 @@ package task
import (
"database/sql"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/zhenorzz/goploy/cmd/server/ws"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/monitor"
"github.com/zhenorzz/goploy/internal/notify"
"strconv"
"sync"
"sync/atomic"
@ -98,12 +100,14 @@ func monitorTask() {
monitorCache.errorTimes++
log.Error("m " + m.Name + " encounter error, " + monitorErrorContent)
if m.Times <= uint16(monitorCache.errorTimes) {
if body, err := m.Notify(monitorErrorContent); err != nil {
if err := notify.Send(fmt.Sprintf("monitor%d", m.ID), notify.UseByMonitor, notify.MonitorData{
Monitor: m,
ErrorMsg: monitorErrorContent,
}, m.NotifyType, m.NotifyTarget); err != nil {
log.Error("m " + m.Name + " notify error, " + err.Error())
} else {
monitorCache.errorTimes = 0
monitorCache.silentCycle = 1
log.Trace("m " + m.Name + " notify return " + body)
_ = m.UpdateLatestErrorContent(monitorErrorContent)
ws.Send(ws.Data{
Type: ws.TypeMonitor,

View File

@ -10,6 +10,7 @@ import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/notify"
"strings"
"sync"
"sync/atomic"
@ -108,12 +109,11 @@ func serverMonitorTask() {
}
serverCaches[serverMonitor.ServerID] = server
}
body, err := serverMonitor.Notify(serverCaches[serverMonitor.ServerID], cycleValue)
if err != nil {
log.Error(fmt.Sprintf("monitor task %d notify error, %s", serverMonitor.ID, err.Error()))
} else {
log.Trace(fmt.Sprintf("monitor task %d notify return %s", serverMonitor.ID, body))
}
_ = notify.Send(fmt.Sprintf("server-monitor%d", serverMonitor.ID), notify.UseByServerMonitor, notify.ServerMonitorData{
Server: serverCaches[serverMonitor.ServerID],
ServerMonitor: serverMonitor,
CycleValue: cycleValue,
}, serverMonitor.NotifyType, serverMonitor.NotifyTarget)
}
serverMonitorCaches[serverMonitor.ID] = monitorCache
}

View File

@ -11,12 +11,11 @@ const (
ShowSFTPLogPage = 4
ShowTerminalLogPage = 5
ShowTerminalRecord = 6
Member = 7
Setting = 7
ShowMemberPage = 8
AddMember = 9
EditMember = 10
DeleteMember = 11
Namespace = 12
ShowNamespacePage = 13
AddNamespace = 14
EditNamespace = 15
@ -90,4 +89,6 @@ const (
DeleteNginxConfig = 83
UnbindServerProject = 84
ManageRepository = 85
ShowNotificationPage = 85
EditNotification = 85
)

104
database/1.17.2.sql Normal file
View File

@ -0,0 +1,104 @@
CREATE TABLE IF NOT EXISTS `notification_template` (
`id` int unsigned auto_increment,
`type` tinyint unsigned default 0 not null,
`use_by` varchar(255) default '' not null,
`title` varchar(255) default '' not null,
`template` text not null,
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 collate = utf8mb4_general_ci;
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (86, 12, 'ShowNotificationPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (87, 12, 'EditNotification', 0, '');
UPDATE `permission` set `name` = 'Setting' WHERE `id` = 7;
UPDATE `permission` set `pid` = 7 WHERE `pid` = 12;
DELETE FROM `permission` WHERE `id` = 12;
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (1, 1, 'deploy', '{{ .Project.Name }}', 'Deploy: <font color="warning">{{ .Project.Name }}</font>
Publisher: <font color="comment">{{ .Project.PublisherName }}</font>
Author: <font color="comment">{{ .CommitInfo.Author }}</font>
{{ if ne .CommitInfo.Tag }}Tag: <font color="comment">{{ .CommitInfo.Tag }}</font>{{ end }}
Branch: <font color="comment">{{ .CommitInfo.Branch }}</font>
CommitSHA: <font color="comment">{{ .CommitInfo.Commit }}</font>
CommitMessage: <font color="comment">{{ .CommitInfo.Message }}</font>
ServerList:<font color="comment">
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}})
{{- else}}
{{- .Server.IP}}
{{- end}}
{{- end}}
</font>
{{- if eq .DeployState 2 }}
State: <font color="green">success</font>
{{- else }}
State: <font color="red">fail</font>
{{- end }}
{{- if ne .DeployDetail ""}}
Detail: <font color="comment">{{.DeployDetail}}</font>
{{- end }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (2, 2, 'deploy', '{{ .Project.Name }}', '#### Deploy{{ .Project.Name }}
#### Publisher{{ .Project.PublisherName }}
#### Author{{ .CommitInfo.Author }}
#### {{ if ne .CommitInfo.Tag }}Tag: {{ .CommitInfo.Tag }}{{ end }}
#### Branch{{ .CommitInfo.Branch }}
#### CommitSHA{{ .CommitInfo.Commit }}
#### CommitMessage {{ .CommitInfo.Message }}
#### ServerList:
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}})
{{- else}}
{{- .Server.IP}}
{{- end}}
{{- end}}
####
{{- if eq .DeployState 2 }}State: <font color="green">success</font>
{{- else }}State: <font color="red">fail</font>
{{- end }}
{{- if ne .DeployDetail ""}}
> Detail: <font color="comment">{{.DeployDetail}}</font>
{{- end }}
');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (3, 3, 'deploy', 'Deploy: {{ .Project.Name }}', 'Publisher: {{ .Project.PublisherName }}
Author: {{ .CommitInfo.Author }}
{{ if ne .CommitInfo.Tag "" }}Tag: {{ .CommitInfo.Tag }}{{ end }}
Branch: {{ .CommitInfo.Branch }}
CommitSHA: {{ .CommitInfo.Commit }}
CommitMessage: {{ .CommitInfo.Message }}
ServerList:
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}}),
{{- else}}
{{- .Server.IP}},
{{- end}}
{{- end}}
{{- if eq .DeployState 2 }}
State: success
{{- else }}
State: fail
{{- end }}
{{- if ne .DeployDetail ""}}
Detail: {{.DeployDetail }}
{{- end }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (4, 1, 'monitor', '{{ .Monitor.Name }}', 'Monitor: <font color="warning">{{ .Monitor.Name }}</font>
> <font color="warning">can not access</font>
> <font color="comment">{{ .ErrorMsg }}</font>');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (5, 2, 'monitor', '{{ .Monitor.Name }}', '#### Monitor: {{ .Monitor.Name }} can not access
{{ .ErrorMsg }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (6, 3, 'monitor', '{{ .Monitor.Name }}', 'can not access
detail: {{ .ErrorMsg }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (7, 1, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: <font color="warning">{{ .ServerMonitor.Item }} warning</font>
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (8, 2, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: {{ .ServerMonitor.Item }} warning
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');
INSERT INTO goploy.notification_template (id, type, use_by, title, template) VALUES (9, 3, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: {{ .ServerMonitor.Item }} warning
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');

View File

@ -389,10 +389,21 @@ CREATE TABLE IF NOT EXISTS `terminal_log` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `notification_template` (
`id` int unsigned auto_increment,
`type` tinyint unsigned default 0 not null,
`use_by` varchar(255) default '' not null,
`title` varchar(255) default '' not null,
`template` text not null,
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 collate = utf8mb4_general_ci;
INSERT IGNORE INTO `user`(`id`, `account`, `password`, `name`, `contact`, `state`, `super_manager`) VALUES (1, 'admin', '$2a$10$89ZJ2xeJj35GOw11Qiucr.phaEZP4.kBX6aKTs7oWFp1xcGBBgijm', '超管', '', 1, 1);
INSERT IGNORE INTO `namespace`(`id`, `name`) VALUES (1, 'goploy');
INSERT IGNORE INTO `namespace_user`(`id`, `namespace_id`, `user_id`, `role_id`) VALUES (1, 1, 1, 0);
INSERT IGNORE INTO `system_config` (`id`, `key`, `value`) VALUES (1, 'version', '1.17.1');
INSERT IGNORE INTO `system_config` (`id`, `key`, `value`) VALUES (1, 'version', '1.17.2');
INSERT IGNORE INTO `role`(`id`, `name`, `description`) VALUES (1, 'manager', '');
INSERT IGNORE INTO `role`(`id`, `name`, `description`) VALUES (2, 'member', '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (1, 0, 'Log', 0, '');
@ -401,22 +412,21 @@ INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALU
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (4, 1, 'ShowSFTPLogPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (5, 1, 'ShowTerminalLogPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (6, 1, 'ShowTerminalRecord', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (7, 0, 'Member', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (7, 0, 'Setting', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (8, 7, 'ShowMemberPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (9, 7, 'AddMember', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (10, 7, 'EditMember', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (11, 7, 'DeleteMember', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (12, 0, 'Namespace', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (13, 12, 'ShowNamespacePage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (14, 12, 'AddNamespace', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (15, 12, 'EditNamespace', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (16, 12, 'AddNamespaceUser', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (17, 12, 'DeleteNamespaceUser', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (18, 12, 'ShowRolePage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (19, 12, 'AddRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (20, 12, 'EditRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (21, 12, 'DeleteRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (22, 12, 'EditPermission', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (13, 7, 'ShowNamespacePage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (14, 7, 'AddNamespace', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (15, 7, 'EditNamespace', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (16, 7, 'AddNamespaceUser', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (17, 7, 'DeleteNamespaceUser', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (18, 7, 'ShowRolePage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (19, 7, 'AddRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (20, 7, 'EditRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (21, 7, 'DeleteRole', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (22, 7, 'EditPermission', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (23, 0, 'Server', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (24, 23, 'ShowServerPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (25, 23, 'AddServer', 0, '');
@ -479,6 +489,9 @@ INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALU
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (83, 23, 'DeleteNginxConfig', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (84, 23, 'UnbindServerProject', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (85, 43, 'ManageRepository', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (86, 7, 'ShowNotificationPage', 0, '');
INSERT IGNORE INTO `permission`(`id`, `pid`, `name`, `sort`, `description`) VALUES (87, 7, 'EditNotification', 0, '');
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (1, 14);
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (1, 15);
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (1, 16);
@ -535,3 +548,89 @@ INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (2, 59);
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (2, 60);
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (2, 61);
INSERT IGNORE INTO `role_permission`(`role_id`, `permission_id`) VALUES (2, 67);
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (1, 1, 'deploy', '{{ .Project.Name }}', 'Deploy: <font color="warning">{{ .Project.Name }}</font>
Publisher: <font color="comment">{{ .Project.PublisherName }}</font>
Author: <font color="comment">{{ .CommitInfo.Author }}</font>
{{ if ne .CommitInfo.Tag }}Tag: <font color="comment">{{ .CommitInfo.Tag }}</font>{{ end }}
Branch: <font color="comment">{{ .CommitInfo.Branch }}</font>
CommitSHA: <font color="comment">{{ .CommitInfo.Commit }}</font>
CommitMessage: <font color="comment">{{ .CommitInfo.Message }}</font>
ServerList:<font color="comment">
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}})
{{- else}}
{{- .Server.IP}}
{{- end}}
{{- end}}
</font>
{{- if eq .DeployState 2 }}
State: <font color="green">success</font>
{{- else }}
State: <font color="red">fail</font>
{{- end }}
{{- if ne .DeployDetail ""}}
Detail: <font color="comment">{{.DeployDetail}}</font>
{{- end }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (2, 2, 'deploy', '{{ .Project.Name }}', '#### Deploy{{ .Project.Name }}
#### Publisher{{ .Project.PublisherName }}
#### Author{{ .CommitInfo.Author }}
#### {{ if ne .CommitInfo.Tag }}Tag: {{ .CommitInfo.Tag }}{{ end }}
#### Branch{{ .CommitInfo.Branch }}
#### CommitSHA{{ .CommitInfo.Commit }}
#### CommitMessage {{ .CommitInfo.Message }}
#### ServerList:
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}})
{{- else}}
{{- .Server.IP}}
{{- end}}
{{- end}}
####
{{- if eq .DeployState 2 }}State: <font color="green">success</font>
{{- else }}State: <font color="red">fail</font>
{{- end }}
{{- if ne .DeployDetail ""}}
> Detail: <font color="comment">{{.DeployDetail}}</font>
{{- end }}
');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (3, 3, 'deploy', 'Deploy: {{ .Project.Name }}', 'Publisher: {{ .Project.PublisherName }}
Author: {{ .CommitInfo.Author }}
{{ if ne .CommitInfo.Tag "" }}Tag: {{ .CommitInfo.Tag }}{{ end }}
Branch: {{ .CommitInfo.Branch }}
CommitSHA: {{ .CommitInfo.Commit }}
CommitMessage: {{ .CommitInfo.Message }}
ServerList:
{{- range .ProjectServers}}
{{- if ne .Server.Name .Server.IP}}
{{- .Server.Name}}({{.Server.IP}}),
{{- else}}
{{- .Server.IP}},
{{- end}}
{{- end}}
{{- if eq .DeployState 2 }}
State: success
{{- else }}
State: fail
{{- end }}
{{- if ne .DeployDetail ""}}
Detail: {{.DeployDetail }}
{{- end }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (4, 1, 'monitor', '{{ .Monitor.Name }}', 'Monitor: <font color="warning">{{ .Monitor.Name }}</font>
> <font color="warning">can not access</font>
> <font color="comment">{{ .ErrorMsg }}</font>');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (5, 2, 'monitor', '{{ .Monitor.Name }}', '#### Monitor: {{ .Monitor.Name }} can not access
{{ .ErrorMsg }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (6, 3, 'monitor', '{{ .Monitor.Name }}', 'can not access
detail: {{ .ErrorMsg }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (7, 1, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: <font color="warning">{{ .ServerMonitor.Item }} warning</font>
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (8, 2, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: {{ .ServerMonitor.Item }} warning
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');
INSERT INTO `notification_template` (id, type, use_by, title, template) VALUES (9, 3, 'server_monitor', '{{ .Server.Name }} {{ .ServerMonitor.Item }} Warning', 'Server: {{ .Server.Name }}({{ .Server.Description }})
Item: {{ .ServerMonitor.Item }} warning
Event: {{ .ServerMonitor.Formula }} value: {{ .CycleValue }}, {{ .ServerMonitor.Operator }} {{ .ServerMonitor.Value }}');

View File

@ -1,7 +1,7 @@
# Import sql manually https://github.com/zhenorzz/goploy/blob/master/model/sql/goploy.sql
FROM alpine
LABEL maintainer="zhenorzz@gmail.com"
ARG GOPLOY_VER=v1.17.1
ARG GOPLOY_VER=v1.17.2
ENV GOPLOY_VER=${GOPLOY_VER}
RUN echo "https://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories

View File

@ -5,10 +5,7 @@
package model
import (
"bytes"
"encoding/json"
"io"
"net/http"
"time"
sq "github.com/Masterminds/squirrel"
@ -237,106 +234,3 @@ func (m Monitor) UpdateLatestErrorContent(errorContent string) error {
Exec()
return err
}
func (m Monitor) Notify(errMsg string) (string, error) {
var err error
var resp *http.Response
if m.NotifyType == NotifyWeiXin {
type markdown struct {
Content string `json:"content"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
content := "Monitor: <font color=\"warning\">" + m.Name + "</font>\n "
content += "> <font color=\"warning\">can not access</font> \n "
content += "> <font color=\"comment\">" + errMsg + "</font> \n "
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Content: content,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(m.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if m.NotifyType == NotifyDingTalk {
type markdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
text := "#### Monitor: " + m.Name + " can not access \n >" + errMsg
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Title: m.Name,
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(m.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if m.NotifyType == NotifyFeiShu {
type content struct {
Text string `json:"text"`
}
type message struct {
MsgType string `json:"msg_type"`
Content content `json:"content"`
}
text := m.Name + " can not access\n "
text += "detail: " + errMsg
msg := message{
MsgType: "text",
Content: content{
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(m.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if m.NotifyType == NotifyCustom {
type message struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
MonitorName string `json:"monitorName"`
Type int `json:"type"`
Target MonitorTarget `json:"target"`
Second int `json:"second"`
Times uint16 `json:"times"`
Error string `json:"error"`
} `json:"data"`
}
code := 0
msg := message{
Code: code,
Message: m.Name + " can not access",
}
msg.Data.MonitorName = m.Name
msg.Data.Type = m.Type
msg.Data.Target = m.Target
msg.Data.Second = m.Second
msg.Data.Times = m.Times
msg.Data.Error = errMsg
b, _ := json.Marshal(msg)
resp, err = http.Post(m.NotifyTarget, "application/json", bytes.NewBuffer(b))
}
if err != nil {
return "", err
}
defer resp.Body.Close()
responseData, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
} else {
return string(responseData), err
}
}

View File

@ -0,0 +1,112 @@
// Copyright 2022 The Goploy Authors. All rights reserved.
// Use of this source code is governed by a GPLv3-style
// license that can be found in the LICENSE file.
package model
import (
sq "github.com/Masterminds/squirrel"
)
const notificationTemplateTable = "`notification_template`"
type NotificationTemplate struct {
ID int64 `json:"id"`
Type uint8 `json:"type"`
UseBy string `json:"useBy"`
Title string `json:"title"`
Template string `json:"template"`
InsertTime string `json:"insertTime,omitempty"`
UpdateTime string `json:"updateTime,omitempty"`
}
type NotificationTemplates []NotificationTemplate
func (nt NotificationTemplate) EditRow() error {
_, err := sq.
Update(notificationTemplateTable).
Set("template", nt.Template).
Set("title", nt.Title).
Where(sq.Eq{"id": nt.ID}).
RunWith(DB).
Exec()
return err
}
func (nt NotificationTemplate) GetList() (NotificationTemplates, error) {
rows, err := sq.
Select("id, use_by, type, title, template, insert_time, update_time").
From(notificationTemplateTable).
OrderBy("id DESC").
RunWith(DB).
Query()
if err != nil {
return nil, err
}
notificationTemplates := NotificationTemplates{}
for rows.Next() {
var notificationTemplate NotificationTemplate
if err := rows.Scan(
&notificationTemplate.ID,
&notificationTemplate.UseBy,
&notificationTemplate.Type,
&notificationTemplate.Title,
&notificationTemplate.Template,
&notificationTemplate.InsertTime,
&notificationTemplate.UpdateTime); err != nil {
return nil, err
}
notificationTemplates = append(notificationTemplates, notificationTemplate)
}
return notificationTemplates, nil
}
func (nt NotificationTemplate) GetAll() (NotificationTemplates, error) {
rows, err := sq.
Select("id, use_by, type, template").
From(notificationTemplateTable).
OrderBy("id DESC").
RunWith(DB).
Query()
if err != nil {
return nil, err
}
notificationTemplates := NotificationTemplates{}
for rows.Next() {
var notificationTemplate NotificationTemplate
if err := rows.Scan(&notificationTemplate.ID, &notificationTemplate.UseBy, &notificationTemplate.Type, &notificationTemplate.Template); err != nil {
return notificationTemplates, err
}
notificationTemplates = append(notificationTemplates, notificationTemplate)
}
return notificationTemplates, nil
}
func (nt NotificationTemplate) GetData() (NotificationTemplate, error) {
var notificationTemplate NotificationTemplate
err := sq.
Select("use_by, type, template").
From(notificationTemplateTable).
Where(sq.Eq{"id": nt.ID}).
RunWith(DB).
QueryRow().
Scan(&notificationTemplate.UseBy, &notificationTemplate.Type, &notificationTemplate.Template)
return notificationTemplate, err
}
func (nt NotificationTemplate) GetTemplate() (NotificationTemplate, error) {
var notificationTemplate NotificationTemplate
err := sq.
Select("title, template").
From(notificationTemplateTable).
Where(sq.Eq{"use_by": nt.UseBy}).
Where(sq.Eq{"type": nt.Type}).
RunWith(DB).
QueryRow().
Scan(&notificationTemplate.Title, &notificationTemplate.Template)
return notificationTemplate, err
}

View File

@ -5,13 +5,7 @@
package model
import (
"bytes"
"encoding/json"
"fmt"
sq "github.com/Masterminds/squirrel"
"io"
"net/http"
"time"
)
const serverMonitorTable = "`server_monitor`"
@ -219,99 +213,3 @@ func (sm ServerMonitor) DeleteRow() error {
Exec()
return err
}
func (sm ServerMonitor) Notify(server Server, cycleValue string) (string, error) {
var err error
var resp *http.Response
if sm.NotifyType == NotifyWeiXin {
type markdown struct {
Content string `json:"content"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
content := fmt.Sprintf("Server: %s(%s)\n ", server.Name, server.Description)
content += "Item: <font color=\"warning\">" + sm.Item + " warning</font>\n "
content += fmt.Sprintf("Event: %s value: %s, %s %s \n ", sm.Formula, cycleValue, sm.Operator, sm.Value)
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Content: content,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(sm.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if sm.NotifyType == NotifyDingTalk {
type markdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
content := fmt.Sprintf("Server: %s(%s)\n ", server.Name, server.Description)
content += "Item: " + sm.Item + "\n "
content += fmt.Sprintf("Event: %s value: %s, %s %s \n ", sm.Formula, cycleValue, sm.Operator, sm.Value)
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Title: fmt.Sprintf("%s %s warning", server.Name, sm.Item),
Text: content,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(sm.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if sm.NotifyType == NotifyFeiShu {
type message struct {
Title string `json:"title"`
Text string `json:"text"`
}
content := fmt.Sprintf("Server: %s(%s)\n ", server.Name, server.Description)
content += "Item: " + sm.Item + "\n "
content += fmt.Sprintf("Event: %s value: %s, %s %s \n ", sm.Formula, cycleValue, sm.Operator, sm.Value)
msg := message{
Title: fmt.Sprintf("%s %s warning", server.Name, sm.Item),
Text: content,
}
b, _ := json.Marshal(msg)
resp, err = http.Post(sm.NotifyTarget, "application/json", bytes.NewBuffer(b))
} else if sm.NotifyType == NotifyCustom {
type message struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Server Server `json:"server"`
MonitorRule ServerMonitor `json:"monitorRule"`
Value string `json:"value"`
Time string `json:"time"`
} `json:"data"`
}
code := 0
msg := message{
Code: code,
Message: fmt.Sprintf("%s %s warning", server.Name, sm.Item),
}
msg.Data.Server = server
msg.Data.MonitorRule = sm
msg.Data.Value = cycleValue
msg.Data.Time = time.Now().Format("2006-01-02 15:04:05")
b, _ := json.Marshal(msg)
resp, err = http.Post(sm.NotifyTarget, "application/json", bytes.NewBuffer(b))
}
if err != nil {
return "", err
}
defer resp.Body.Close()
responseData, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
} else {
return string(responseData), err
}
}

167
internal/notify/notify.go Normal file
View File

@ -0,0 +1,167 @@
package notify
import (
"bytes"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/repo"
"io"
"net/http"
"text/template"
)
type DeployData struct {
DeployState uint8
Project model.Project
ProjectServers model.ProjectServers
CommitInfo repo.CommitInfo
DeployDetail string
}
type MonitorData struct {
Monitor model.Monitor
ErrorMsg string
}
type ServerMonitorData struct {
Server model.Server
ServerMonitor model.ServerMonitor
CycleValue string
}
const (
UseByDeploy = "deploy"
UseByMonitor = "monitor"
UseByServerMonitor = "server_monitor"
)
func Send(name string, useBy string, data any, notifyType uint8, notifyTarget string) error {
if notifyType == model.NotifyCustom {
type message struct {
Data any `json:"data"`
}
msg := message{
Data: data,
}
b, _ := json.Marshal(msg)
_, err := http.Post(notifyTarget, "application/json", bytes.NewBuffer(b))
if err != nil {
log.Error(fmt.Sprintf("%s notify exec err: %s", name, err))
return err
}
return nil
}
notificationData, err := model.NotificationTemplate{UseBy: useBy, Type: notifyType}.GetTemplate()
if err != nil {
log.Error(fmt.Sprintf("%s could not find notification template: %s", name, err))
return err
}
var buf bytes.Buffer
tmpl, err := template.New(name + "title").Parse(notificationData.Title)
if err != nil {
log.Error(fmt.Sprintf("%s parse notification title error: %s", name, err))
return err
}
err = tmpl.Execute(&buf, data)
if err != nil {
log.Error(fmt.Sprintf("%s execute notification title error: %s", name, err))
return err
}
title := buf.String()
tmpl, err = template.New(name + "template").Parse(notificationData.Template)
if err != nil {
log.Error(fmt.Sprintf("%s parse notification template error: %s", name, err))
return err
}
buf.Reset()
err = tmpl.Execute(&buf, data)
if err != nil {
log.Error(fmt.Sprintf("%s execute notification template error: %s", name, err))
return err
}
text := buf.String()
println(title)
println(text)
var resp *http.Response
if notifyType == model.NotifyWeiXin {
type markdown struct {
Content string `json:"content"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
text = title + "\n" + text
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Content: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(notifyTarget, "application/json", bytes.NewBuffer(b))
} else if notifyType == model.NotifyDingTalk {
type markdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
type message struct {
Msgtype string `json:"msgtype"`
Markdown markdown `json:"markdown"`
}
msg := message{
Msgtype: "markdown",
Markdown: markdown{
Title: title,
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(notifyTarget, "application/json", bytes.NewBuffer(b))
} else if notifyType == model.NotifyFeiShu {
type content struct {
Text string `json:"text"`
}
type message struct {
MsgType string `json:"msg_type"`
Content content `json:"content"`
}
text = title + "\n" + text
msg := message{
MsgType: "text",
Content: content{
Text: text,
},
}
b, _ := json.Marshal(msg)
resp, err = http.Post(notifyTarget, "application/json", bytes.NewBuffer(b))
}
if err != nil {
log.Error(fmt.Sprintf("%s notify exec err: %s", name, err))
return err
} else if resp != nil {
responseData, err := io.ReadAll(resp.Body)
if err != nil {
log.Error(fmt.Sprintf("%s notify read body err: %s", name, err))
} else {
log.Trace(fmt.Sprintf("%s notify success: %s", name, string(responseData)))
}
_ = resp.Body.Close()
}
return nil
}

View File

@ -1,6 +1,6 @@
{
"name": "goploy",
"version": "1.17.1",
"version": "1.17.2",
"license": "GPL-3.0-or-later",
"scripts": {
"dev": "vite",

View File

@ -0,0 +1,33 @@
import { Request, ID } from './types'
export interface NotificationData {
id: number
useBy: string
type: number
title: string
template: string
insertTime: string
updateTime: string
}
export class NotificationList extends Request {
readonly url = '/notification/getList'
readonly method = 'get'
public declare datagram: {
list: NotificationData[]
}
}
export class NotificationEdit extends Request {
readonly url = '/notification/edit'
readonly method = 'put'
public param: {
id: number
template: string
}
constructor(param: NotificationEdit['param']) {
super()
this.param = param
}
}

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1717659845942" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6115" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M961.834667 89.045333a42.666667 42.666667 0 0 1 0 26.965334l-273.024 819.029333a42.666667 42.666667 0 0 1-78.336 6.186667l-180.437334-347.392-347.392-180.48a42.666667 42.666667 0 0 1 6.186667-78.293334l819.029333-273.024a42.666667 42.666667 0 0 1 53.973334 27.008z m-107.946667 80.938667L211.498667 384.085333l244.181333 126.890667a21.248 21.248 0 0 1 5.162667-8.277333l90.496-90.496a21.333333 21.333333 0 0 1 30.165333 0l30.208 30.165333a21.333333 21.333333 0 0 1 0 30.165333l-90.538667 90.496a21.248 21.248 0 0 1-8.32 5.12l126.890667 244.224 214.186667-642.389333z" fill="#000000" opacity=".65" p-id="6116"></path></svg>

After

Width:  |  Height:  |  Size: 955 B

View File

@ -1,4 +1,5 @@
{
"title": "Title",
"default": "Default",
"select": "Select",
"name": "Name",
@ -171,9 +172,11 @@
"serverCrontab": "Crontab UI",
"serverCron": "Cron",
"serverNginx": "Nginx",
"namespace": "Namespace",
"setting": "Setting",
"namespaceSetting": "NS setting",
"roleSetting": "Role setting",
"memberSetting": "Member setting",
"notificationSetting": "Notify setting",
"member": "Member",
"log": "Log",
"loginLog": "Login log",

View File

@ -1,4 +1,5 @@
{
"title": "标题",
"default": "默认",
"select": "选择",
"name": "名称",
@ -153,9 +154,11 @@
"serverCrontab": "Crontab UI",
"serverCron": "定时任务",
"serverNginx": "Nginx管理",
"namespace": "空间",
"setting": "设置",
"namespaceSetting": "空间设置",
"roleSetting": "角色设置",
"memberSetting": "成员设置",
"notificationSetting": "推送设置",
"member": "成员设置",
"log": "日志",
"loginLog": "登录日志",
@ -171,7 +174,7 @@
"ShowSFTPLogPage": "查看SFTP日志",
"ShowTerminalLogPage": "查看终端日志",
"ShowTerminalRecord": "查看终端录屏",
"Member": "成员管理",
"Setting": "设置",
"ShowMemberPage": "查看成员设置",
"AddMember": "新增成员",
"EditMember": "编辑成员",
@ -186,6 +189,8 @@
"AddRole": "新增角色",
"EditRole": "编辑角色",
"DeleteRole": "删除角色",
"ShowNotificationPage": "查看推送设置",
"EditNotification": "编辑推送设置",
"EditPermission": "编辑角色权限",
"Server": "服务器管理",
"ShowServerPage": "查看服务器设置",

View File

@ -5,12 +5,11 @@ export default Object.freeze({
ShowSFTPLogPage: 4,
ShowTerminalLogPage: 5,
ShowTerminalRecord: 6,
Member: 7,
Setting: 7,
ShowMemberPage: 8,
AddMember: 9,
EditMember: 10,
DeleteMember: 11,
Namespace: 12,
ShowNamespacePage: 13,
AddNamespace: 14,
EditNamespace: 15,
@ -84,4 +83,6 @@ export default Object.freeze({
DeleteNginxConfig: 83,
UnbindServerProject: 84,
ManageRepository: 85,
ShowNotificationPage: 86,
EditNotification: 87,
})

View File

@ -169,19 +169,19 @@ export default <RouteRecordRaw[]>[
],
},
{
path: '/namespace',
path: '/setting',
component: Layout,
redirect: '/namespace/index',
name: 'namespace',
redirect: '/setting/namespace',
name: 'setting',
meta: {
title: 'namespace',
icon: 'namespace',
title: 'setting',
icon: 'setting',
},
children: [
{
path: 'index',
name: 'NamespaceIndex',
component: () => import('@/views/namespace/index.vue'),
path: 'namespace',
name: 'NamespaceSetting',
component: () => import('@/views/setting/namespace.vue'),
meta: {
title: 'namespaceSetting',
icon: 'namespaceSetting',
@ -190,36 +190,34 @@ export default <RouteRecordRaw[]>[
},
{
path: 'role',
name: 'NamespaceRole',
component: () => import('@/views/namespace/role.vue'),
name: 'RoleSetting',
component: () => import('@/views/setting/role.vue'),
meta: {
title: 'roleSetting',
icon: 'roleSetting',
permissions: [permission.ShowRolePage],
},
},
],
},
{
path: '/member',
component: Layout,
redirect: '/member/index',
name: 'member',
meta: {
title: 'member',
icon: 'user',
},
children: [
{
path: 'index',
name: 'MemberIndex',
component: () => import('@/views/member/index.vue'),
path: 'member',
name: 'MemberSetting',
component: () => import('@/views/setting/member.vue'),
meta: {
title: 'member',
title: 'memberSetting',
icon: 'user',
permissions: [permission.ShowMemberPage],
},
},
{
path: 'notification',
name: 'NotificationSetting',
component: () => import('@/views/setting/notification.vue'),
meta: {
title: 'notificationSetting',
icon: 'notifySetting',
permissions: [permission.ShowNotificationPage],
},
},
],
},
{

View File

@ -111,7 +111,7 @@
:href="row.name"
target="_blank"
:underline="false"
class="card-title__text"
class="card-title__link"
style="color: inherit"
>
{{ row.name }}
@ -292,6 +292,9 @@
<el-row style="white-space: pre-wrap" v-html="publishFormProps.title" />
</template>
<el-row>
<el-row v-if="selectedItem.deployState === DeployState.Uninitialized">
{{ $t('initial') }}
</el-row>
<el-row
v-for="(variable, index) in publishFormProps.customVariables"
:key="index"
@ -323,11 +326,13 @@
>
</el-input>
</el-row>
<el-row style="width: 100%">
<el-row
v-if="selectedItem.deployState !== DeployState.Uninitialized"
style="width: 100%"
>
<el-checkbox
v-model="publishFormProps.selectCommit"
:label="`Select Commit (default lastest ${selectedItem.branch})`"
:disabled="publishFormProps.selectTag"
@change="handleSelectCommit"
/>
<el-row
@ -338,7 +343,10 @@
{{ publishFormProps.commit }}
</el-row>
</el-row>
<el-row style="width: 100%; margin-top: 10px">
<el-row
v-if="selectedItem.deployState !== DeployState.Uninitialized"
style="width: 100%; margin-top: 10px"
>
<el-checkbox
v-model="publishFormProps.selectServer"
:label="`Select Server (default all)`"
@ -662,7 +670,7 @@ function handleRebuilt() {
tableData.value[projectIndex].deployState = 1
}
function handleSelectCommit(state) {
function handleSelectCommit(state: boolean) {
if (state == false) {
publishFormProps.value.branch = ''
publishFormProps.value.commit = ''
@ -679,7 +687,7 @@ function handleSelectServer() {
})
}
function handleCancelSelectCommit(state) {
function handleCancelSelectCommit() {
publishFormProps.value.branch = ''
publishFormProps.value.commit = ''
publishFormProps.value.selectCommit = false
@ -758,7 +766,7 @@ function publish(data: ProjectData) {
const customVariables = deepClone(data.script.customVariables)
publishFormProps.value.customVariables =
customVariables &&
customVariables.map((item) => {
customVariables.map((item: any) => {
if (item.type == 'list') {
item.value = ''
}
@ -880,6 +888,13 @@ function restorePublishForm() {
font-size: 14px;
font-weight: 600;
white-space: nowrap;
flex: 1;
}
.card-title__link {
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
white-space: nowrap;
}
}

View File

@ -165,7 +165,7 @@
</template>
<script lang="ts">
export default { name: 'MemberIndex' }
export default { name: 'MemberSetting' }
</script>
<script lang="ts" setup>
import permission from '@/permission'

View File

@ -127,7 +127,7 @@
</el-row>
</template>
<script lang="ts">
export default { name: 'NamespaceIndex' }
export default { name: 'NamespaceSetting' }
</script>
<script lang="ts" setup>
import permission from '@/permission'

View File

@ -0,0 +1,170 @@
<template>
<el-row class="app-container">
<el-row class="app-bar" type="flex" justify="space-between"></el-row>
<el-row class="app-table">
<el-table
:key="tableHeight"
v-loading="tableLoading"
highlight-current-row
height="100%"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="useBy" :label="'Use By'" width="100" />
<el-table-column prop="title" :label="$t('title')" width="200" />
<el-table-column prop="type" :label="$t('type')" width="100">
<template #default="scope">
{{ $t(`webhookOption[${scope.row.type || 0}]`) }}
</template>
</el-table-column>
<el-table-column prop="template" :label="$t('template')" />
<el-table-column
prop="insertTime"
:label="$t('insertTime')"
width="160"
align="center"
/>
<el-table-column
prop="updateTime"
:label="$t('updateTime')"
width="160"
align="center"
/>
<el-table-column
prop="operation"
:label="$t('op')"
width="100"
align="center"
:fixed="$store.state.app.device === 'mobile' ? false : 'right'"
>
<template #default="scope">
<Button
type="primary"
:icon="Edit"
:permissions="[permission.EditNotification]"
@click="handleEdit(scope.row)"
/>
</template>
</el-table-column>
</el-table>
</el-row>
<el-dialog
v-model="dialogVisible"
:title="$t('setting')"
:fullscreen="$store.state.app.device === 'mobile'"
>
<el-form
ref="form"
:model="formData"
label-width="80px"
:label-position="
$store.state.app.device === 'desktop' ? 'right' : 'top'
"
>
<el-form-item
:label="$t('title')"
prop="title"
:rules="[
{ required: true, message: 'Title required', trigger: 'blur' },
]"
>
<el-input v-model="formData.title" autocomplete="off" />
</el-form-item>
<el-form-item
:label="$t('template')"
prop="template"
:rules="[
{ required: true, message: 'Template required', trigger: 'blur' },
]"
>
<el-input
v-model="formData.template"
type="textarea"
:autosize="{ minRows: 8 }"
autocomplete="off"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">{{ $t('cancel') }}</el-button>
<el-button
:disabled="formProps.disabled"
type="primary"
@click="submit"
>
{{ $t('confirm') }}
</el-button>
</template>
</el-dialog>
</el-row>
</template>
<script lang="ts">
export default { name: 'NotificationSetting' }
</script>
<script lang="ts" setup>
import permission from '@/permission'
import Button from '@/components/Permission/Button.vue'
import { Edit } from '@element-plus/icons-vue'
import {
NotificationList,
NotificationEdit,
NotificationData,
} from '@/api/notification'
import getTableHeight from '@/composables/tableHeight'
import type { ElForm } from 'element-plus'
import { ref } from 'vue'
const { tableHeight } = getTableHeight()
const dialogVisible = ref(false)
const tableLoading = ref(false)
const tableData = ref<NotificationList['datagram']['list']>([])
const selectedItem = ref<NotificationData>()
const form = ref<InstanceType<typeof ElForm>>()
const tempFormData = { id: 0, title: '', template: '' }
const formData = ref(tempFormData)
const formProps = ref({ disabled: false })
getList()
function getList() {
tableLoading.value = true
new NotificationList()
.request()
.then((response) => {
tableData.value = response.data.list
})
.finally(() => {
tableLoading.value = false
})
}
function handleEdit(data: NotificationData) {
formData.value = Object.assign({}, data)
dialogVisible.value = true
}
function submit() {
form.value?.validate((valid) => {
if (valid) {
edit()
return Promise.resolve(true)
} else {
return Promise.reject(false)
}
})
}
function edit() {
formProps.value.disabled = true
new NotificationEdit(formData.value)
.request()
.then(() => {
getList()
ElMessage.success('Success')
})
.finally(() => {
formProps.value.disabled = dialogVisible.value = false
})
}
</script>

View File

@ -169,7 +169,7 @@
</el-row>
</template>
<script lang="ts">
export default { name: 'NamespaceRole' }
export default { name: 'RoleSetting' }
</script>
<script lang="ts" setup>
import pms from '@/permission'