Merge pull request #51 from itzgyw/master

Add monitor script, project tag.
This commit is contained in:
zhenorzz 2023-05-09 09:11:05 +08:00 committed by GitHub
commit 261da44560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 853 additions and 4069 deletions

View File

@ -12,6 +12,8 @@ import (
"github.com/zhenorzz/goploy/internal/server/response" "github.com/zhenorzz/goploy/internal/server/response"
"github.com/zhenorzz/goploy/model" "github.com/zhenorzz/goploy/model"
"net/http" "net/http"
"strconv"
"strings"
) )
type Monitor API type Monitor API
@ -41,36 +43,80 @@ func (Monitor) GetList(gp *server.Goploy) server.Response {
func (Monitor) Check(gp *server.Goploy) server.Response { func (Monitor) Check(gp *server.Goploy) server.Response {
type ReqData struct { type ReqData struct {
Type int `json:"type" validate:"oneof=1 2 3 4 5"` Type int `json:"type" validate:"oneof=1 2 3 4 5"`
Target string `json:"target" validate:"required"` Target string `json:"target" validate:"required"`
SuccessServerID int64 `json:"successServerId"`
SuccessScript string `json:"successScript"`
FailServerID int64 `json:"failServerId"`
FailScript string `json:"failScript"`
} }
var reqData ReqData var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil { if err := decodeJson(gp.Body, &reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()} return response.JSON{Code: response.Error, Message: err.Error()}
} }
ms, err := monitor.NewMonitorFromTarget(reqData.Type, reqData.Target) ms, err := monitor.NewMonitorFromTarget(reqData.Type, reqData.Target,
monitor.NewScript(reqData.SuccessServerID, reqData.SuccessScript),
monitor.NewScript(reqData.FailServerID, reqData.FailScript),
)
if err != nil { if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()} return response.JSON{Code: response.Error, Message: err.Error()}
} }
sb := strings.Builder{}
if err := ms.Check(); err != nil { if err := ms.Check(); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()} sb.WriteString("MonitorErr : ")
sb.WriteString(err.Error())
var serverId int64
if e, ok := err.(monitor.ScriptError); ok {
serverId = e.ServerId
}
err := ms.RunFailScript(serverId)
if err != nil {
sb.WriteString("\nFailScriptErr : ")
sb.WriteString(err.Error())
}
} else {
sb.WriteString("Monitor : Success \n")
for _, item := range ms.Items {
serverId, err := strconv.ParseInt(item, 10, 64)
if err != nil {
err = ms.RunSuccessScript(0)
} else {
err = ms.RunSuccessScript(serverId)
}
if err != nil {
sb.WriteString("SuccessScriptErr: \n")
sb.WriteString(err.Error())
}
}
} }
return response.JSON{Message: "Connected"} if sb.Len() != 19 {
return response.JSON{Code: response.Error, Message: sb.String()}
} else {
return response.JSON{Message: "SUCCESS"}
}
} }
func (Monitor) Add(gp *server.Goploy) server.Response { func (Monitor) Add(gp *server.Goploy) server.Response {
type ReqData struct { type ReqData struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Type int `json:"type" validate:"oneof=1 2 3 4 5"` Type int `json:"type" validate:"oneof=1 2 3 4 5"`
Target string `json:"target" validate:"required"` Target string `json:"target" validate:"required"`
Second int `json:"second" validate:"gt=0"` Second int `json:"second" validate:"gt=0"`
Times uint16 `json:"times" validate:"gt=0"` Times uint16 `json:"times" validate:"gt=0"`
SilentCycle int `json:"silentCycle" validate:"required"` SilentCycle int `json:"silentCycle" validate:"required"`
NotifyType uint8 `json:"notifyType" validate:"gt=0"` NotifyType uint8 `json:"notifyType" validate:"gt=0"`
NotifyTarget string `json:"notifyTarget" validate:"required"` NotifyTarget string `json:"notifyTarget" validate:"required"`
Description string `json:"description" validate:"max=255"` Description string `json:"description" validate:"max=255"`
FailScript string `json:"failScript" `
SuccessScript string `json:"successScript" `
SuccessServerID int64 `json:"successServerId" `
FailServerID int64 `json:"failServerId" `
} }
var reqData ReqData var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil { if err := decodeJson(gp.Body, &reqData); err != nil {
@ -78,16 +124,20 @@ func (Monitor) Add(gp *server.Goploy) server.Response {
} }
id, err := model.Monitor{ id, err := model.Monitor{
NamespaceID: gp.Namespace.ID, NamespaceID: gp.Namespace.ID,
Name: reqData.Name, Name: reqData.Name,
Type: reqData.Type, Type: reqData.Type,
Target: reqData.Target, Target: reqData.Target,
Second: reqData.Second, Second: reqData.Second,
Times: reqData.Times, Times: reqData.Times,
SilentCycle: reqData.SilentCycle, SilentCycle: reqData.SilentCycle,
NotifyType: reqData.NotifyType, NotifyType: reqData.NotifyType,
NotifyTarget: reqData.NotifyTarget, NotifyTarget: reqData.NotifyTarget,
Description: reqData.Description, Description: reqData.Description,
FailScript: reqData.FailScript,
SuccessScript: reqData.SuccessScript,
SuccessServerID: reqData.SuccessServerID,
FailServerID: reqData.FailServerID,
}.AddRow() }.AddRow()
if err != nil { if err != nil {
@ -102,32 +152,40 @@ func (Monitor) Add(gp *server.Goploy) server.Response {
func (Monitor) Edit(gp *server.Goploy) server.Response { func (Monitor) Edit(gp *server.Goploy) server.Response {
type ReqData struct { type ReqData struct {
ID int64 `json:"id" validate:"gt=0"` ID int64 `json:"id" validate:"gt=0"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Type int `json:"type" validate:"oneof=1 2 3 4 5"` Type int `json:"type" validate:"oneof=1 2 3 4 5"`
Target string `json:"target" validate:"required"` Target string `json:"target" validate:"required"`
Second int `json:"second" validate:"gt=0"` Second int `json:"second" validate:"gt=0"`
Times uint16 `json:"times" validate:"gt=0"` Times uint16 `json:"times" validate:"gt=0"`
SilentCycle int `json:"silentCycle" validate:"required"` SilentCycle int `json:"silentCycle" validate:"required"`
NotifyType uint8 `json:"notifyType" validate:"gt=0"` NotifyType uint8 `json:"notifyType" validate:"gt=0"`
NotifyTarget string `json:"notifyTarget" validate:"required"` NotifyTarget string `json:"notifyTarget" validate:"required"`
Description string `json:"description" validate:"max=255"` Description string `json:"description" validate:"max=255"`
FailScript string `json:"failScript" `
SuccessScript string `json:"successScript" `
SuccessServerID int64 `json:"successServerId" `
FailServerID int64 `json:"failServerId" `
} }
var reqData ReqData var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil { if err := decodeJson(gp.Body, &reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()} return response.JSON{Code: response.Error, Message: err.Error()}
} }
err := model.Monitor{ err := model.Monitor{
ID: reqData.ID, ID: reqData.ID,
Name: reqData.Name, Name: reqData.Name,
Type: reqData.Type, Type: reqData.Type,
Target: reqData.Target, Target: reqData.Target,
Second: reqData.Second, Second: reqData.Second,
Times: reqData.Times, Times: reqData.Times,
SilentCycle: reqData.SilentCycle, SilentCycle: reqData.SilentCycle,
NotifyType: reqData.NotifyType, NotifyType: reqData.NotifyType,
NotifyTarget: reqData.NotifyTarget, NotifyTarget: reqData.NotifyTarget,
Description: reqData.Description, Description: reqData.Description,
FailScript: reqData.FailScript,
SuccessScript: reqData.SuccessScript,
SuccessServerID: reqData.SuccessServerID,
FailServerID: reqData.FailServerID,
}.EditRow() }.EditRow()
if err != nil { if err != nil {

View File

@ -28,6 +28,7 @@ type Project API
func (p Project) Handler() []server.Route { func (p Project) Handler() []server.Route {
return []server.Route{ return []server.Route{
server.NewRoute("/project/getList", http.MethodGet, p.GetList).Permissions(config.ShowProjectPage), server.NewRoute("/project/getList", http.MethodGet, p.GetList).Permissions(config.ShowProjectPage),
server.NewRoute("/project/tagList", http.MethodGet, p.TagList),
server.NewRoute("/project/pingRepos", http.MethodGet, p.PingRepos), server.NewRoute("/project/pingRepos", http.MethodGet, p.PingRepos),
server.NewRoute("/project/getRemoteBranchList", http.MethodGet, p.GetRemoteBranchList), server.NewRoute("/project/getRemoteBranchList", http.MethodGet, p.GetRemoteBranchList),
server.NewRoute("/project/getBindServerList", http.MethodGet, p.GetBindServerList), server.NewRoute("/project/getBindServerList", http.MethodGet, p.GetBindServerList),
@ -54,6 +55,23 @@ func (p Project) Handler() []server.Route {
} }
} }
func (Project) TagList(gp *server.Goploy) server.Response {
var tagList []string
var err error
if _, ok := gp.Namespace.PermissionIDs[config.GetAllProjectList]; ok {
tagList, err = model.Project{NamespaceID: gp.Namespace.ID}.GetTagList()
} else {
tagList, err = model.Project{NamespaceID: gp.Namespace.ID, UserID: gp.UserInfo.ID}.GetTagList()
}
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List []string `json:"list"`
}{List: tagList},
}
}
func (Project) GetList(gp *server.Goploy) server.Response { func (Project) GetList(gp *server.Goploy) server.Response {
var projectList model.Projects var projectList model.Projects
var err error var err error
@ -297,6 +315,7 @@ func (Project) Add(gp *server.Goploy) server.Response {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
RepoType string `json:"repoType" validate:"required"` RepoType string `json:"repoType" validate:"required"`
URL string `json:"url" validate:"required"` URL string `json:"url" validate:"required"`
Tag string `json:"tag"`
Path string `json:"path" validate:"required"` Path string `json:"path" validate:"required"`
Environment uint8 `json:"environment" validate:"required"` Environment uint8 `json:"environment" validate:"required"`
Branch string `json:"branch" validate:"required"` Branch string `json:"branch" validate:"required"`
@ -330,6 +349,7 @@ func (Project) Add(gp *server.Goploy) server.Response {
Name: reqData.Name, Name: reqData.Name,
RepoType: reqData.RepoType, RepoType: reqData.RepoType,
URL: reqData.URL, URL: reqData.URL,
Tag: reqData.Tag,
Path: reqData.Path, Path: reqData.Path,
Environment: reqData.Environment, Environment: reqData.Environment,
Branch: reqData.Branch, Branch: reqData.Branch,
@ -383,6 +403,7 @@ func (Project) Edit(gp *server.Goploy) server.Response {
Name string `json:"name"` Name string `json:"name"`
RepoType string `json:"repoType"` RepoType string `json:"repoType"`
URL string `json:"url"` URL string `json:"url"`
Tag string `json:"tag"`
Path string `json:"path"` Path string `json:"path"`
SymlinkPath string `json:"symlinkPath"` SymlinkPath string `json:"symlinkPath"`
SymlinkBackupNumber uint8 `json:"symlinkBackupNumber"` SymlinkBackupNumber uint8 `json:"symlinkBackupNumber"`
@ -421,6 +442,7 @@ func (Project) Edit(gp *server.Goploy) server.Response {
Name: reqData.Name, Name: reqData.Name,
RepoType: reqData.RepoType, RepoType: reqData.RepoType,
URL: reqData.URL, URL: reqData.URL,
Tag: reqData.Tag,
Path: reqData.Path, Path: reqData.Path,
Environment: reqData.Environment, Environment: reqData.Environment,
Branch: reqData.Branch, Branch: reqData.Branch,

View File

@ -10,6 +10,7 @@ import (
"github.com/zhenorzz/goploy/internal/log" "github.com/zhenorzz/goploy/internal/log"
"github.com/zhenorzz/goploy/internal/monitor" "github.com/zhenorzz/goploy/internal/monitor"
"github.com/zhenorzz/goploy/model" "github.com/zhenorzz/goploy/model"
"strconv"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@ -79,7 +80,10 @@ func monitorTask() {
now := time.Now().Unix() now := time.Now().Unix()
if int(now-monitorCache.time) >= m.Second { if int(now-monitorCache.time) >= m.Second {
monitorCache.time = now monitorCache.time = now
ms, err := monitor.NewMonitorFromTarget(m.Type, m.Target) ms, err := monitor.NewMonitorFromTarget(m.Type, m.Target,
monitor.NewScript(m.SuccessServerID, m.SuccessScript),
monitor.NewScript(m.FailServerID, m.FailScript),
)
if err != nil { if err != nil {
_ = m.TurnOff(err.Error()) _ = m.TurnOff(err.Error())
log.Error("m " + m.Name + " encounter error, " + err.Error()) log.Error("m " + m.Name + " encounter error, " + err.Error())
@ -105,8 +109,29 @@ func monitorTask() {
} }
} }
} }
var serverId int64
if e, ok := err.(monitor.ScriptError); ok {
serverId = e.ServerId
}
err = ms.RunFailScript(serverId)
if err != nil {
log.Error("Failed to run fail script ." + err.Error())
}
} else { } else {
for _, item := range ms.Items {
serverId, err := strconv.ParseInt(item, 10, 64)
if err != nil {
err = ms.RunSuccessScript(0)
} else {
err = ms.RunSuccessScript(serverId)
}
if err != nil {
log.Error("Failed to run successful script ." + err.Error())
}
}
monitorCache.errorTimes = 0 monitorCache.errorTimes = 0
} }
monitorCaches[m.ID] = monitorCache monitorCaches[m.ID] = monitorCache
} }

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/zhenorzz/goploy/model" "github.com/zhenorzz/goploy/model"
"golang.org/x/crypto/ssh"
"net" "net"
"net/http" "net/http"
"os/exec" "os/exec"
@ -18,19 +19,51 @@ import (
"time" "time"
) )
type Monitor struct { type ScriptError struct {
Type int Message string
Items []string ServerId int64
Timeout time.Duration
Process string
Script string
} }
func NewMonitorFromTarget(t int, target string) (Monitor, error) { func (ce ScriptError) Error() string {
return ce.Message
}
func (ce ScriptError) Server() int64 {
return ce.ServerId
}
type Script struct {
ServerId int64
Script string
}
func (ce Script) IsValid() bool {
return ce.Script != ""
}
func NewScript(serverId int64, script string) Script {
return Script{
ServerId: serverId,
Script: script,
}
}
type Monitor struct {
Type int
Items []string
Timeout time.Duration
Process string
Script string
FailScript Script
SuccessScript Script
}
func NewMonitorFromTarget(t int, target string, successScript Script, failScript Script) (Monitor, error) {
var m Monitor var m Monitor
if err := json.Unmarshal([]byte(target), &m); err != nil { if err := json.Unmarshal([]byte(target), &m); err != nil {
return m, err return m, err
} }
m.FailScript = failScript
m.SuccessScript = successScript
m.Type = t m.Type = t
return m, nil return m, nil
} }
@ -109,17 +142,7 @@ func (m Monitor) CheckScript() error {
if err != nil { if err != nil {
return err return err
} }
server, err := (model.Server{ID: serverID}).GetData() session, err := NewSession(serverID, m.Timeout*time.Second)
if err != nil {
return err
} else if server.State == model.Disable {
continue
}
client, err := server.ToSSHConfig().SetTimeout(m.Timeout * time.Second).Dial()
if err != nil {
return err
}
session, err := client.NewSession()
if err != nil { if err != nil {
return err return err
} }
@ -127,8 +150,58 @@ func (m Monitor) CheckScript() error {
session.Stdout = &stdout session.Stdout = &stdout
session.Stderr = &stderr session.Stderr = &stderr
if err := session.Run(m.Script); err != nil { if err := session.Run(m.Script); err != nil {
return errors.New(err.Error() + ", stdout: " + stdout.String() + ", stderr: " + stderr.String()) return ScriptError{Message: err.Error() + ", stdout: " + stdout.String() + ", stderr: " + stderr.String(), ServerId: serverID}
} }
} }
return nil return nil
} }
func (m Monitor) RunFailScript(serverId int64) error {
if m.FailScript.IsValid() {
sId := m.FailScript.ServerId
if sId == -1 {
if serverId == 0 {
return errors.New("the executor is not clear")
} else {
sId = serverId
}
}
session, err := NewSession(sId, m.Timeout*time.Second)
if err != nil {
return err
}
return session.Run(m.FailScript.Script)
}
return nil
}
func (m Monitor) RunSuccessScript(serverId int64) error {
if m.SuccessScript.IsValid() {
sId := m.SuccessScript.ServerId
if sId == -1 {
if serverId == 0 {
return errors.New("the executor is not clear")
} else {
sId = serverId
}
}
session, err := NewSession(sId, m.Timeout*time.Second)
if err != nil {
return err
}
return session.Run(m.SuccessScript.Script)
}
return nil
}
func NewSession(serverId int64, timeout time.Duration) (session *ssh.Session, err error) {
server, err := (model.Server{ID: serverId}).GetData()
if err != nil {
return nil, err
} else if server.State == model.Disable {
return nil, errors.New("Server Disable [" + server.Name + "]")
}
client, err := server.ToSSHConfig().SetTimeout(timeout).Dial()
if err != nil {
return nil, err
}
return client.NewSession()
}

View File

@ -16,28 +16,32 @@ import (
const monitorTable = "`monitor`" const monitorTable = "`monitor`"
type Monitor struct { type Monitor struct {
ID int64 `json:"id"` ID int64 `json:"id"`
NamespaceID int64 `json:"namespaceId"` NamespaceID int64 `json:"namespaceId"`
Name string `json:"name"` Name string `json:"name"`
Type int `json:"type"` Type int `json:"type"`
Target string `json:"target"` Target string `json:"target"`
Second int `json:"second"` Second int `json:"second"`
Times uint16 `json:"times"` Times uint16 `json:"times"`
SilentCycle int `json:"silentCycle"` SilentCycle int `json:"silentCycle"`
NotifyType uint8 `json:"notifyType"` NotifyType uint8 `json:"notifyType"`
NotifyTarget string `json:"notifyTarget"` NotifyTarget string `json:"notifyTarget"`
Description string `json:"description"` SuccessServerID int64 `json:"successServerId"`
ErrorContent string `json:"errorContent"` SuccessScript string `json:"successScript"`
State uint8 `json:"state"` FailServerID int64 `json:"failServerId"`
InsertTime string `json:"insertTime"` FailScript string `json:"failScript"`
UpdateTime string `json:"updateTime"` Description string `json:"description"`
ErrorContent string `json:"errorContent"`
State uint8 `json:"state"`
InsertTime string `json:"insertTime"`
UpdateTime string `json:"updateTime"`
} }
type Monitors []Monitor type Monitors []Monitor
func (m Monitor) GetList() (Monitors, error) { func (m Monitor) GetList() (Monitors, error) {
rows, err := sq. rows, err := sq.
Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, description, error_content, state, insert_time, update_time"). Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, description, error_content, state, success_script, fail_script, success_server_id, fail_server_id, insert_time, update_time").
From(monitorTable). From(monitorTable).
Where(sq.Eq{ Where(sq.Eq{
"namespace_id": m.NamespaceID, "namespace_id": m.NamespaceID,
@ -65,6 +69,10 @@ func (m Monitor) GetList() (Monitors, error) {
&monitor.Description, &monitor.Description,
&monitor.ErrorContent, &monitor.ErrorContent,
&monitor.State, &monitor.State,
&monitor.SuccessScript,
&monitor.FailScript,
&monitor.SuccessServerID,
&monitor.FailServerID,
&monitor.InsertTime, &monitor.InsertTime,
&monitor.UpdateTime); err != nil { &monitor.UpdateTime); err != nil {
return nil, err return nil, err
@ -78,13 +86,13 @@ func (m Monitor) GetList() (Monitors, error) {
func (m Monitor) GetData() (Monitor, error) { func (m Monitor) GetData() (Monitor, error) {
var monitor Monitor var monitor Monitor
err := sq. err := sq.
Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, state"). Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, state", "success_script", "fail_script", "success_server_id", "fail_server_id").
From(monitorTable). From(monitorTable).
Where(sq.Eq{"id": m.ID}). Where(sq.Eq{"id": m.ID}).
OrderBy("id DESC"). OrderBy("id DESC").
RunWith(DB). RunWith(DB).
QueryRow(). QueryRow().
Scan(&monitor.ID, &monitor.Name, &monitor.Type, &monitor.Target, &monitor.Second, &monitor.Times, &monitor.SilentCycle, &monitor.NotifyType, &monitor.NotifyTarget, &monitor.State) Scan(&monitor.ID, &monitor.Name, &monitor.Type, &monitor.Target, &monitor.Second, &monitor.Times, &monitor.SilentCycle, &monitor.NotifyType, &monitor.NotifyTarget, &monitor.State, &monitor.SuccessScript, &monitor.FailScript, &monitor.SuccessServerID, &monitor.FailServerID)
if err != nil { if err != nil {
return monitor, err return monitor, err
} }
@ -93,7 +101,7 @@ func (m Monitor) GetData() (Monitor, error) {
func (m Monitor) GetAllByState() (Monitors, error) { func (m Monitor) GetAllByState() (Monitors, error) {
rows, err := sq. rows, err := sq.
Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, description, update_time"). Select("id, name, type, target, second, times, silent_cycle, notify_type, notify_target, success_script, fail_script, success_server_id, fail_server_id, description, update_time").
From(monitorTable). From(monitorTable).
Where(sq.Eq{ Where(sq.Eq{
"state": m.State, "state": m.State,
@ -117,6 +125,10 @@ func (m Monitor) GetAllByState() (Monitors, error) {
&monitor.SilentCycle, &monitor.SilentCycle,
&monitor.NotifyType, &monitor.NotifyType,
&monitor.NotifyTarget, &monitor.NotifyTarget,
&monitor.SuccessScript,
&monitor.FailScript,
&monitor.SuccessServerID,
&monitor.FailServerID,
&monitor.Description, &monitor.Description,
&monitor.UpdateTime, &monitor.UpdateTime,
); err != nil { ); err != nil {
@ -131,8 +143,8 @@ func (m Monitor) GetAllByState() (Monitors, error) {
func (m Monitor) AddRow() (int64, error) { func (m Monitor) AddRow() (int64, error) {
result, err := sq. result, err := sq.
Insert(monitorTable). Insert(monitorTable).
Columns("namespace_id", "name", "type", "target", "second", "times", "silent_cycle", "notify_type", "notify_target", "description", "error_content"). Columns("namespace_id", "name", "type", "target", "second", "times", "silent_cycle", "notify_type", "notify_target", "description", "error_content", "success_script", "fail_script", "success_server_id", "fail_server_id").
Values(m.NamespaceID, m.Name, m.Type, m.Target, m.Second, m.Times, m.SilentCycle, m.NotifyType, m.NotifyTarget, m.Description, ""). Values(m.NamespaceID, m.Name, m.Type, m.Target, m.Second, m.Times, m.SilentCycle, m.NotifyType, m.NotifyTarget, m.Description, "", m.SuccessScript, m.FailScript, m.SuccessServerID, m.FailServerID).
RunWith(DB). RunWith(DB).
Exec() Exec()
if err != nil { if err != nil {
@ -146,15 +158,19 @@ func (m Monitor) EditRow() error {
_, err := sq. _, err := sq.
Update(monitorTable). Update(monitorTable).
SetMap(sq.Eq{ SetMap(sq.Eq{
"name": m.Name, "name": m.Name,
"type": m.Type, "type": m.Type,
"target": m.Target, "target": m.Target,
"second": m.Second, "second": m.Second,
"times": m.Times, "times": m.Times,
"silent_cycle": m.SilentCycle, "silent_cycle": m.SilentCycle,
"notify_type": m.NotifyType, "notify_type": m.NotifyType,
"notify_target": m.NotifyTarget, "notify_target": m.NotifyTarget,
"description": m.Description, "description": m.Description,
"success_script": m.SuccessScript,
"fail_script": m.FailScript,
"success_server_id": m.SuccessServerID,
"fail_server_id": m.FailServerID,
}). }).
Where(sq.Eq{"id": m.ID}). Where(sq.Eq{"id": m.ID}).
RunWith(DB). RunWith(DB).

View File

@ -22,6 +22,7 @@ type Project struct {
RepoType string `json:"repoType"` RepoType string `json:"repoType"`
Name string `json:"name"` Name string `json:"name"`
URL string `json:"url"` URL string `json:"url"`
Tag string `json:"tag"`
Path string `json:"path"` Path string `json:"path"`
Environment uint8 `json:"environment"` Environment uint8 `json:"environment"`
Branch string `json:"branch"` Branch string `json:"branch"`
@ -88,6 +89,7 @@ func (p Project) AddRow() (int64, error) {
"name", "name",
"repo_type", "repo_type",
"url", "url",
"tag",
"path", "path",
"environment", "environment",
"branch", "branch",
@ -110,6 +112,7 @@ func (p Project) AddRow() (int64, error) {
p.Name, p.Name,
p.RepoType, p.RepoType,
p.URL, p.URL,
p.Tag,
p.Path, p.Path,
p.Environment, p.Environment,
p.Branch, p.Branch,
@ -136,6 +139,45 @@ func (p Project) AddRow() (int64, error) {
return id, err return id, err
} }
func (p Project) GetTagList() (tags []string, err error) {
builder := sq.Select("tag").
From(projectTable).
Where(sq.Eq{
"namespace_id": p.NamespaceID,
"state": Enable,
}).
Distinct()
if p.UserID > 0 {
builder = builder.
Join(projectUserTable + " ON project_user.project_id = project.id").
Where(sq.Eq{"user_id": p.UserID})
}
rows, err := builder.
RunWith(DB).
Query()
if err != nil {
return nil, err
}
tagMap := map[string]bool{}
for rows.Next() {
var tag string
rows.Scan(&tag)
if tag != "" {
for _, item := range strings.Split(tag, ",") {
tagMap[item] = false
}
}
}
tags = make([]string, len(tagMap))
var i int
for k := range tagMap {
tags[i] = k
i++
}
return tags, nil
}
func (p Project) EditRow() error { func (p Project) EditRow() error {
_, err := sq. _, err := sq.
Update(projectTable). Update(projectTable).
@ -143,6 +185,7 @@ func (p Project) EditRow() error {
"name": p.Name, "name": p.Name,
"repo_type": p.RepoType, "repo_type": p.RepoType,
"url": p.URL, "url": p.URL,
"tag": p.Tag,
"path": p.Path, "path": p.Path,
"environment": p.Environment, "environment": p.Environment,
"branch": p.Branch, "branch": p.Branch,
@ -255,6 +298,7 @@ func (p Project) GetList() (Projects, error) {
name, name,
repo_type, repo_type,
url, url,
tag,
path, path,
environment, environment,
branch, branch,
@ -304,6 +348,7 @@ func (p Project) GetList() (Projects, error) {
&project.Name, &project.Name,
&project.RepoType, &project.RepoType,
&project.URL, &project.URL,
&project.Tag,
&project.Path, &project.Path,
&project.Environment, &project.Environment,
&project.Branch, &project.Branch,
@ -340,6 +385,7 @@ func (p Project) GetDeployList() (Projects, error) {
project.repo_type, project.repo_type,
project.transfer_type, project.transfer_type,
project.url, project.url,
project.tag,
project.publisher_id, project.publisher_id,
project.publisher_name, project.publisher_name,
IFNULL(publish_trace.ext, '{}'), IFNULL(publish_trace.ext, '{}'),
@ -382,6 +428,7 @@ func (p Project) GetDeployList() (Projects, error) {
&project.RepoType, &project.RepoType,
&project.TransferType, &project.TransferType,
&project.URL, &project.URL,
&project.Tag,
&project.PublisherID, &project.PublisherID,
&project.PublisherName, &project.PublisherName,
&project.PublishExt, &project.PublishExt,
@ -411,6 +458,7 @@ func (p Project) GetData() (Project, error) {
name, name,
repo_type, repo_type,
url, url,
tag,
path, path,
environment, environment,
branch, branch,
@ -443,6 +491,7 @@ func (p Project) GetData() (Project, error) {
&project.Name, &project.Name,
&project.RepoType, &project.RepoType,
&project.URL, &project.URL,
&project.Tag,
&project.Path, &project.Path,
&project.Environment, &project.Environment,
&project.Branch, &project.Branch,

5
model/sql/2023-05-06.sql Normal file
View File

@ -0,0 +1,5 @@
alter table monitor add success_script longtext;
alter table monitor add fail_script longtext ;
alter table monitor add success_server_id int(10) default -1;
alter table monitor add fail_server_id int(10) default -1 ;
alter table project add tag varchar(255) default '';

2
web/components.d.ts vendored
View File

@ -25,7 +25,6 @@ declare module '@vue/runtime-core' {
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
@ -50,7 +49,6 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
Hamburger: typeof import('./src/components/Hamburger/index.vue')['default'] Hamburger: typeof import('./src/components/Hamburger/index.vue')['default']

View File

@ -17,7 +17,7 @@
"cronstrue": "^1.113.0", "cronstrue": "^1.113.0",
"diff": "^5.0.0", "diff": "^5.0.0",
"echarts": "^5.2.1", "echarts": "^5.2.1",
"element-plus": "^2.2.28", "element-plus": "^2.3.4",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"listenercount": "^1.0.1", "listenercount": "^1.0.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",

View File

@ -11,6 +11,7 @@ export interface ProjectData {
path: string path: string
environment: number environment: number
branch: string branch: string
tag: string
symlinkPath: string symlinkPath: string
symlinkBackupNumber: number symlinkBackupNumber: number
review: number review: number
@ -107,6 +108,14 @@ export class ProjectList extends Request {
list: ProjectData[] list: ProjectData[]
} }
} }
export class TagList extends Request {
readonly url = '/project/tagList'
readonly method = 'get'
public declare datagram: {
list: string[]
}
}
export class ProjectPingRepos extends Request { export class ProjectPingRepos extends Request {
readonly url = '/project/pingRepos' readonly url = '/project/pingRepos'
@ -151,6 +160,7 @@ export class ProjectAdd extends Request {
name: string name: string
repoType: string repoType: string
url: string url: string
tag: string
path: string path: string
environment: number environment: number
branch: string branch: string
@ -183,6 +193,7 @@ export class ProjectEdit extends Request {
name: string name: string
repoType: string repoType: string
url: string url: string
tag: string
path: string path: string
symlinkPath: string symlinkPath: string
review: number review: number

21
web/src/const/const.ts Normal file
View File

@ -0,0 +1,21 @@
const scriptLang = {
Option: [
{ label: 'sh', value: 'sh', lang: 'sh' },
{ label: 'zsh', value: 'zsh', lang: 'sh' },
{ label: 'bash', value: 'bash', lang: 'sh' },
{ label: 'python', value: 'python', lang: 'python' },
{ label: 'php', value: 'php', lang: 'php' },
{ label: 'bat', value: 'cmd', lang: 'batchfile' },
],
getScriptLang: function (scriptMode = '') {
if (scriptMode !== '') {
const scriptInfo = scriptLang.Option.find(
(elem) => elem.value === scriptMode
)
return scriptInfo ? scriptInfo['lang'] : ''
} else {
return 'sh'
}
},
}
export { scriptLang }

View File

@ -1,6 +1,7 @@
{ {
"default": "Default", "default": "Default",
"name": "Name", "name": "Name",
"tag": "Tag",
"script": "Script", "script": "Script",
"owner": "Owner", "owner": "Owner",
"user": "User", "user": "User",
@ -48,7 +49,7 @@
"reject": "Reject", "reject": "Reject",
"manage": "Manage", "manage": "Manage",
"interval": "Interval", "interval": "Interval",
"sort" : "Sort", "sort": "Sort",
"asc": "ASC", "asc": "ASC",
"desc": "DESC", "desc": "DESC",
"size": "Size", "size": "Size",
@ -115,11 +116,29 @@
"state": "State", "state": "State",
"current": "Current", "current": "Current",
"unlimited": "Unlimited", "unlimited": "Unlimited",
"stateOption": ["Disable", "Enable"], "stateOption": [
"switchOption": ["Close", "Open"], "Disable",
"boolOption": ["No", "Yes"], "Enable"
"runOption": ["Not run", "Run"], ],
"envOption": ["Unknown", "Production", "Pre-release", "Test", "Development"], "switchOption": [
"Close",
"Open"
],
"boolOption": [
"No",
"Yes"
],
"runOption": [
"Not run",
"Run"
],
"envOption": [
"Unknown",
"Production",
"Pre-release",
"Test",
"Development"
],
"webhookOption": { "webhookOption": {
"0": "Nothing", "0": "Nothing",
"1": "WeCom", "1": "WeCom",
@ -289,24 +308,36 @@
"saveTemplate": "Save template" "saveTemplate": "Save template"
}, },
"monitorPage": { "monitorPage": {
"typeOption": ["Unknown", "Site check", "Port check", "Ping check", "Process check", "Custom script check"], "defaultServer": "Follow Host",
"scriptMode": "Script mode",
"typeOption": [
"Unknown",
"Site check",
"Port check",
"Ping check",
"Process check",
"Custom script check"
],
"successScriptLabel": "Success Script",
"failScriptLabel": "Fail Script",
"failScriptTips": "Script files executed when listening to unhealthy results .\n If the script is not filled in, it will not be executed",
"successScriptTips": "Script file executed when listening to healthy results .\n If the script is not filled in, it will not be executed",
"baseInfoLabel": "Base Info",
"testState": "Test state", "testState": "Test state",
"failTimes": "Failure times", "failTimes": "Failure times",
"silentCycle": "Silent cycle", "silentCycle": "Silent cycle",
"notifyTimes": "Notice times", "notifyTimes": "Notice times",
"errorContent": "Error content", "errorContent": "Error content",
"toggleStateTips": "toggleStateTips": "This action will suspend the monitoring application({monitorName}), continue?",
"This action will suspend the monitoring application({monitorName}), continue?", "removeMontiorTips": "This action will no longer monitor the app({monitorName}), continue?"
"removeMontiorTips":
"This action will no longer monitor the app({monitorName}), continue?"
}, },
"JSONPage": { "JSONPage": {
"expandAll": "Expand all", "expandAll": "Expand all",
"collapseAll": "Collapse all", "collapseAll": "Collapse all",
"unmarkAll": "Unmark all", "unmarkAll": "Unmark all",
"copyAll": "Copy all", "copyAll": "Copy all",
"tips": "tips": "1.Hold down ALT and click label to achieve highlighting<br>2.Hold down SHIFT and click label to view the JSON path"
"1.Hold down ALT and click label to achieve highlighting<br>2.Hold down SHIFT and click label to view the JSON path"
}, },
"projectPage": { "projectPage": {
"testConnection": "Test", "testConnection": "Test",
@ -348,7 +379,11 @@
"resetStateTips": "This action will reset project state, continue?", "resetStateTips": "This action will reset project state, continue?",
"reviewDeploy": "Review deploy", "reviewDeploy": "Review deploy",
"reviewTips": "This action will approve commit, continue?", "reviewTips": "This action will approve commit, continue?",
"reviewStateOption": ["Wait", "Reviewed", "Rejected"], "reviewStateOption": [
"Wait",
"Reviewed",
"Rejected"
],
"removeProjectTaskTips": "This action will delete the crontab task in {projectName}, continue?", "removeProjectTaskTips": "This action will delete the crontab task in {projectName}, continue?",
"publishCommitTips": "This action will rebuild {commit}, continue?" "publishCommitTips": "This action will rebuild {commit}, continue?"
} }

View File

@ -1,6 +1,7 @@
{ {
"default": "默认", "default": "默认",
"name": "名称", "name": "名称",
"tag": "标签",
"script": "脚本", "script": "脚本",
"owner": "拥有者", "owner": "拥有者",
"user": "用户", "user": "用户",
@ -290,12 +291,19 @@
"saveTemplate": "保存模板" "saveTemplate": "保存模板"
}, },
"monitorPage": { "monitorPage": {
"scriptMode": "脚本类型",
"defaultServer": "跟随宿主机",
"typeOption": ["未知", "站点检测", "端口检测", "Ping检测", "进程检测", "自定义脚本检测"], "typeOption": ["未知", "站点检测", "端口检测", "Ping检测", "进程检测", "自定义脚本检测"],
"testState": "测试状态", "testState": "测试状态",
"failTimes": "连续失败次数", "failTimes": "连续失败次数",
"silentCycle": "通道沉默周期", "silentCycle": "通道沉默周期",
"notifyTimes": "通知次数", "notifyTimes": "通知次数",
"errorContent": "错误内容", "errorContent": "错误内容",
"baseInfoLabel": "基本信息",
"successScriptLabel": "成功运行脚本",
"failScriptLabel": "失败运行脚本",
"failScriptTips": "监听结果不健康时执行的脚本文件.\n如果未填写脚本则不会执行",
"successScriptTips": "监听结果健康时执行的脚本文件.\n如果未填写脚本则不会执行",
"toggleStateTips": "此操作将暂停监控应用({monitorName}), 是否继续?", "toggleStateTips": "此操作将暂停监控应用({monitorName}), 是否继续?",
"removeMontiorTips": "此操作将不再监控应用({monitorName}), 是否继续?" "removeMontiorTips": "此操作将不再监控应用({monitorName}), 是否继续?"
}, },

View File

@ -29,6 +29,22 @@
<el-option :label="$t('envOption[3]')" :value="3" /> <el-option :label="$t('envOption[3]')" :value="3" />
<el-option :label="$t('envOption[4]')" :value="4" /> <el-option :label="$t('envOption[4]')" :value="4" />
</el-select> </el-select>
<el-select
v-model="searchProject.tag"
:max-collapse-tags="1"
style="width: 300px"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="Filter the project tag"
>
<el-option
v-for="item in tagList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-input <el-input
v-model="searchProject.name" v-model="searchProject.name"
style="width: 300px" style="width: 300px"
@ -59,51 +75,58 @@
style="margin-right: 5px; color: var(--el-color-warning)" style="margin-right: 5px; color: var(--el-color-warning)"
icon-class="pin" icon-class="pin"
/> />
<span <el-tooltip
v-if="row.environment === 1" class="box-item"
style=" effect="dark"
flex: 1; :content="row.tag"
overflow: hidden; placement="bottom"
text-overflow: ellipsis;
font-size: 14px;
font-weight: 600;
white-space: nowrap;
color: var(--el-color-danger);
"
> >
{{ row.name }} - <span
{{ $t(`envOption[${row.environment || 0}]`) }} v-if="row.environment === 1"
</span> style="
<span flex: 1;
v-else-if="row.environment === 3" overflow: hidden;
style=" text-overflow: ellipsis;
flex: 1; font-size: 14px;
overflow: hidden; font-weight: 600;
text-overflow: ellipsis; white-space: nowrap;
font-size: 14px; color: var(--el-color-danger);
font-weight: 600; "
white-space: nowrap; >
color: var(--el-color-warning); {{ row.name }} -
" {{ $t(`envOption[${row.environment || 0}]`) }}
> </span>
{{ row.name }} - <span
{{ $t(`envOption[${row.environment || 0}]`) }} v-else-if="row.environment === 3"
</span> style="
<span flex: 1;
v-else overflow: hidden;
style=" text-overflow: ellipsis;
flex: 1; font-size: 14px;
overflow: hidden; font-weight: 600;
text-overflow: ellipsis; white-space: nowrap;
font-size: 14px; color: var(--el-color-warning);
font-weight: 600; "
white-space: nowrap; >
color: var(--el-color-info); {{ row.name }} -
" {{ $t(`envOption[${row.environment || 0}]`) }}
> </span>
{{ row.name }} - <span
{{ $t(`envOption[${row.environment || 0}]`) }} v-else
</span> style="
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
font-weight: 600;
white-space: nowrap;
color: var(--el-color-info);
"
>
{{ row.name }} -
{{ $t(`envOption[${row.environment || 0}]`) }}
</span>
</el-tooltip>
<el-dropdown <el-dropdown
trigger="click" trigger="click"
@command="(funcName: string) => cardMoreFunc[funcName](row)" @command="(funcName: string) => cardMoreFunc[funcName](row)"
@ -188,7 +211,9 @@
type="primary" type="primary"
> >
{{ $t('submit') }} {{ $t('submit') }}
<el-icon class="el-icon--right"><arrow-down /></el-icon> <el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button> </el-button>
<span v-else>{{ $t('deploy') }}</span> <span v-else>{{ $t('deploy') }}</span>
<template #dropdown> <template #dropdown>
@ -209,7 +234,9 @@
> >
<el-button size="small" type="warning"> <el-button size="small" type="warning">
{{ $t('func') }} {{ $t('func') }}
<el-icon class="el-icon--right"><arrow-down /></el-icon> <el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu <el-dropdown-menu
@ -387,7 +414,7 @@ import {
DeployResetState, DeployResetState,
DeployGreyPublish, DeployGreyPublish,
} from '@/api/deploy' } from '@/api/deploy'
import { ProjectServerList, ProjectData } from '@/api/project' import { ProjectServerList, ProjectData, TagList } from '@/api/project'
import RepoURL from '@/components/RepoURL/index.vue' import RepoURL from '@/components/RepoURL/index.vue'
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
import TheDetailDialog from './TheDetailDialog.vue' import TheDetailDialog from './TheDetailDialog.vue'
@ -420,11 +447,13 @@ const searchProject = ref({
sort: getSort(), sort: getSort(),
name: '', name: '',
environment: '', environment: '',
tag: [] as string[],
pin: '', pin: '',
}) })
const selectedItem = ref({} as ProjectData) const selectedItem = ref({} as ProjectData)
const tableloading = ref(false) const tableloading = ref(false)
const tableData = ref<any[]>([]) const tableData = ref<any[]>([])
const tagList = ref<string[]>([])
const pagination = ref({ page: 1, rows: 20 }) const pagination = ref({ page: 1, rows: 20 })
const greyServerForm = ref<InstanceType<typeof ElForm>>() const greyServerForm = ref<InstanceType<typeof ElForm>>()
const greyServerFormProps = ref({ const greyServerFormProps = ref({
@ -463,6 +492,13 @@ const tablePage = computed(() => {
(item) => item.pin === searchProject.value.pin (item) => item.pin === searchProject.value.pin
) )
} }
if (searchProject.value.tag.length > 0) {
_tableData = _tableData.filter((item) =>
String(item.tag)
.split(',')
.find((p) => searchProject.value.tag.indexOf(p) > -1)
)
}
return { return {
list: _tableData.slice( list: _tableData.slice(
(pagination.value.page - 1) * pagination.value.rows, (pagination.value.page - 1) * pagination.value.rows,
@ -510,6 +546,7 @@ watch(
) )
getList() getList()
getTagList()
function getList() { function getList() {
tableloading.value = true tableloading.value = true
@ -550,6 +587,11 @@ function getList() {
tableloading.value = false tableloading.value = false
}) })
} }
function getTagList() {
new TagList().request().then((response) => {
tagList.value = response.data.list
})
}
function stickChange() { function stickChange() {
tableData.value = tableData.value.map((_) => { tableData.value = tableData.value.map((_) => {

View File

@ -2,34 +2,15 @@
<el-row class="app-container"> <el-row class="app-container">
<el-row class="app-bar" type="flex" justify="space-between"> <el-row class="app-bar" type="flex" justify="space-between">
<el-row> <el-row>
<el-input <el-input v-model="monitorName" style="width: 200px" placeholder="Filter the name" />
v-model="monitorName"
style="width: 200px"
placeholder="Filter the name"
/>
</el-row> </el-row>
<el-row> <el-row>
<el-button <el-button :loading="tableLoading" type="primary" :icon="Refresh" @click="refresList" />
:loading="tableLoading" <Button type="primary" :icon="Plus" :permissions="[pms.AddMonitor]" @click="handleAdd" />
type="primary"
:icon="Refresh"
@click="refresList"
/>
<Button
type="primary"
:icon="Plus"
:permissions="[pms.AddMonitor]"
@click="handleAdd"
/>
</el-row> </el-row>
</el-row> </el-row>
<el-row class="app-table"> <el-row class="app-table">
<el-table <el-table v-loading="tableLoading" height="100%" highlight-current-row :data="tablePage.list">
v-loading="tableLoading"
height="100%"
highlight-current-row
:data="tablePage.list"
>
<el-table-column prop="id" label="ID" width="100" /> <el-table-column prop="id" label="ID" width="100" />
<el-table-column prop="name" :label="$t('name')" min-width="120" /> <el-table-column prop="name" :label="$t('name')" min-width="120" />
<el-table-column prop="type" :label="$t('type')" min-width="120"> <el-table-column prop="type" :label="$t('type')" min-width="120">
@ -69,307 +50,231 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="second" :label="$t('interval') + '(s)'" width="95" />
prop="second" <el-table-column prop="times" :label="$t('monitorPage.failTimes')" width="115" />
:label="$t('interval') + '(s)'"
width="95"
/>
<el-table-column
prop="times"
:label="$t('monitorPage.failTimes')"
width="115"
/>
<el-table-column prop="notifyType" :label="$t('notice')" width="90"> <el-table-column prop="notifyType" :label="$t('notice')" width="90">
<template #default="scope"> <template #default="scope">
{{ $t(`webhookOption[${scope.row.notifyType || 0}]`) }} {{ $t(`webhookOption[${scope.row.notifyType || 0}]`) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="state" :label="$t('state')" width="120" align="center">
prop="state"
:label="$t('state')"
width="120"
align="center"
>
<template #default="scope"> <template #default="scope">
{{ $t(`switchOption[${scope.row.state || 0}]`) }} {{ $t(`switchOption[${scope.row.state || 0}]`) }}
<Switch <Switch :model-value="scope.row.state === 1" active-color="#13ce66" inactive-color="#ff4949"
:model-value="scope.row.state === 1" :permissions="[pms.EditMonitor]" @change="handleToggle(scope.row)" />
active-color="#13ce66"
inactive-color="#ff4949"
:permissions="[pms.EditMonitor]"
@change="handleToggle(scope.row)"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="errorContent" :label="$t('monitorPage.errorContent')" min-width="140"
prop="errorContent" show-overflow-tooltip />
:label="$t('monitorPage.errorContent')" <el-table-column prop="insertTime" :label="$t('insertTime')" width="160" align="center" />
min-width="140" <el-table-column prop="updateTime" :label="$t('updateTime')" width="160" align="center" />
show-overflow-tooltip <el-table-column prop="operation" :label="$t('op')" width="130" align="center" fixed="right">
/>
<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="130"
align="center"
fixed="right"
>
<template #default="scope"> <template #default="scope">
<Button <Button type="primary" :icon="Edit" :permissions="[pms.EditMonitor]" @click="handleEdit(scope.row)" />
type="primary" <Button type="danger" :icon="Delete" :permissions="[pms.DeleteMonitor]" @click="handleRemove(scope.row)" />
:icon="Edit"
:permissions="[pms.EditMonitor]"
@click="handleEdit(scope.row)"
/>
<Button
type="danger"
:icon="Delete"
:permissions="[pms.DeleteMonitor]"
@click="handleRemove(scope.row)"
/>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-row> </el-row>
<el-row type="flex" justify="end" class="app-page"> <el-row type="flex" justify="end" class="app-page">
<el-pagination <el-pagination :total="tablePage.total" :page-size="pagination.rows" background layout="total, prev, pager, next"
:total="tablePage.total" @current-change="handlePageChange" />
:page-size="pagination.rows"
background
layout="total, prev, pager, next"
@current-change="handlePageChange"
/>
</el-row> </el-row>
<el-dialog <el-dialog v-model="dialogVisible" :title="$t('setting')" :fullscreen="$store.state.app.device === 'mobile'"
v-model="dialogVisible" :close-on-click-modal="false">
:title="$t('setting')" <el-form ref="form" v-loading="formProps.loading"
:fullscreen="$store.state.app.device === 'mobile'" :class="$store.state.app.device === 'desktop' ? 'monitor-dialog' : ''" :model="formData" label-width="120px"
:close-on-click-modal="false"
>
<el-form
ref="form"
v-loading="formProps.loading"
:class="$store.state.app.device === 'desktop' ? 'monitor-dialog' : ''"
:model="formData"
label-width="120px"
:label-position=" :label-position="
$store.state.app.device === 'desktop' ? 'right' : 'top' $store.state.app.device === 'desktop' ? 'right' : 'top'
" ">
> <el-tabs type="border-card" class="demo-tabs">
<el-form-item <el-tab-pane>
:label="$t('name')" <template #label>
prop="name" <span style="vertical-align: middle; padding-right: 4px">
:rules="[ {{ $t('monitorPage.baseInfoLabel') }}
{ required: true, message: 'Name required', trigger: 'blur' }, </span>
]" </template>
>
<el-input v-model="formData.name" autocomplete="off" /> <el-form-item :label="$t('name')" prop="name" :rules="[
</el-form-item> { required: true, message: 'Name required', trigger: 'blur' },
<el-form-item ]">
:label="$t('type')" <el-input v-model="formData.name" autocomplete="off" />
prop="type" </el-form-item>
:rules="[ <el-form-item :label="$t('type')" prop="type" :rules="[
{ required: true, message: 'Type required', trigger: 'blur' }, { required: true, message: 'Type required', trigger: 'blur' },
]" ]">
> <el-select v-model="formData.type" style="width: 100%" @change="handleTypeChange">
<el-select <el-option :label="$t('monitorPage.typeOption[1]')" :value="1" />
v-model="formData.type" <el-option :label="$t('monitorPage.typeOption[2]')" :value="2" />
style="width: 100%" <el-option :label="$t('monitorPage.typeOption[3]')" :value="3" />
@change="handleTypeChange" <el-option :label="$t('monitorPage.typeOption[4]')" :value="4" />
> <el-option :label="$t('monitorPage.typeOption[5]')" :value="5" />
<el-option :label="$t('monitorPage.typeOption[1]')" :value="1" /> </el-select>
<el-option :label="$t('monitorPage.typeOption[2]')" :value="2" /> </el-form-item>
<el-option :label="$t('monitorPage.typeOption[3]')" :value="3" /> <template v-if="0 < formData.type && formData.type < 4">
<el-option :label="$t('monitorPage.typeOption[4]')" :value="4" /> <el-form-item :label="$t('target')">
<el-option :label="$t('monitorPage.typeOption[5]')" :value="5" /> <el-button type="primary" :icon="Plus" plain @click="formProps.items.push('')"></el-button>
</el-select> <el-input v-for="(_, index) in formProps.items" :key="index" v-model="formProps.items[index]"
</el-form-item> :placeholder="formProps.itemPlaceholder[formData.type]" clearable>
<template v-if="0 < formData.type && formData.type < 4"> <template #append>
<el-form-item :label="$t('target')"> <el-button :icon="Minus" @click="formProps.items.splice(index, 1)" />
<el-button </template>
type="primary" </el-input>
:icon="Plus" </el-form-item>
plain </template>
@click="formProps.items.push('')" <template v-else-if="formData.type === 4">
></el-button> <el-form-item :label="$t('target')">
<el-input <el-select v-model="formProps.items" multiple filterable style="width: 100%">
v-for="(_, index) in formProps.items" <el-option v-for="item in serverOption" :key="item.label" :label="item.label"
:key="index" :value="item.id.toString()" />
v-model="formProps.items[index]" </el-select>
:placeholder="formProps.itemPlaceholder[formData.type]" </el-form-item>
clearable <el-form-item :label="$t('process')">
> <el-input v-model="formProps.process" autocomplete="off" placeholder="The name within ps -ef" />
<template #append> </el-form-item>
<el-button </template>
:icon="Minus" <template v-else-if="formData.type === 5">
@click="formProps.items.splice(index, 1)" <el-form-item :label="$t('target')">
/> <el-select v-model="formProps.items" multiple filterable style="width: 100%">
</template> <el-option v-for="item in serverOption" :key="item.label" :label="item.label"
</el-input> :value="item.id.toString()" />
</el-form-item> </el-select>
</template> </el-form-item>
<template v-else-if="formData.type === 4"> <el-form-item :label="$t('script')">
<el-form-item :label="$t('target')"> <VAceEditor v-model:value="formProps.script" lang="sh" theme="github" style="height: 360px; width: 100%"
<el-select :options="{ newLineMode: 'unix' }" />
v-model="formProps.items" </el-form-item>
multiple </template>
filterable <el-form-item :label="$t('timeout') + '(s)'">
style="width: 100%" <el-input v-model="formProps.timeout" autocomplete="off" placeholder="" />
> </el-form-item>
<el-option <el-form-item :label="$t('interval') + '(s)'" prop="second" :rules="[
v-for="item in serverOption" {
:key="item.label" type: 'number',
:label="item.label" required: true,
:value="item.id.toString()" min: 1,
/> message: 'Interval required',
</el-select> trigger: 'blur',
</el-form-item> },
<el-form-item :label="$t('process')"> ]">
<el-input <el-radio-group v-model="formData.second">
v-model="formProps.process" <el-radio :label="60">1 min</el-radio>
autocomplete="off" <el-radio :label="300">5 min</el-radio>
placeholder="The name within ps -ef" <el-radio :label="900">15 min</el-radio>
/> <el-radio :label="1800">30 min</el-radio>
</el-form-item> <el-radio :label="3600">60 min</el-radio>
</template> </el-radio-group>
<template v-else-if="formData.type === 5"> </el-form-item>
<el-form-item :label="$t('target')"> <el-form-item :label="$t('monitorPage.failTimes')" prop="times" :rules="[
<el-select {
v-model="formProps.items" type: 'number',
multiple required: true,
filterable min: 1,
style="width: 100%" max: 65535,
> message: 'Times required',
<el-option trigger: 'blur',
v-for="item in serverOption" },
:key="item.label" ]">
:label="item.label" <el-radio-group v-model="formData.times">
:value="item.id.toString()" <el-radio :label="1">1</el-radio>
/> <el-radio :label="2">2</el-radio>
</el-select> <el-radio :label="3">3</el-radio>
</el-form-item> <el-radio :label="4">4</el-radio>
<el-form-item :label="$t('script')"> <el-radio :label="5">5</el-radio>
<VAceEditor </el-radio-group>
v-model:value="formProps.script" </el-form-item>
lang="sh" <el-form-item :label="$t('monitorPage.silentCycle')">
theme="github" <el-select v-model="formData.silentCycle" style="width: 100%" filterable>
style="height: 360px; width: 100%" <el-option label="5 min" :value="5" />
:options="{ newLineMode: 'unix' }" <el-option label="10 min" :value="10" />
/> <el-option label="15 min" :value="15" />
</el-form-item> <el-option label="30 min" :value="30" />
</template> <el-option label="60 min" :value="60" />
<el-form-item :label="$t('timeout') + '(s)'"> <el-option label="3 hour" :value="180" />
<el-input <el-option label="6 hour" :value="360" />
v-model="formProps.timeout" <el-option label="12 hour" :value="720" />
autocomplete="off" <el-option label="24 hour" :value="1440" />
placeholder="" </el-select>
/> </el-form-item>
</el-form-item> <el-form-item :label="$t('notice')" prop="notifyTarget"
<el-form-item :rules="[{ required: true, message: 'Webhook required' }]">
:label="$t('interval') + '(s)'" <el-row type="flex" style="width: 100%">
prop="second" <el-select v-model="formData.notifyType">
:rules="[ <el-option :label="$t('webhookOption[1]')" :value="1" />
{ <el-option :label="$t('webhookOption[2]')" :value="2" />
type: 'number', <el-option :label="$t('webhookOption[3]')" :value="3" />
required: true, <el-option :label="$t('webhookOption[255]')" :value="255" />
min: 1, </el-select>
message: 'Interval required', <el-input v-model.trim="formData.notifyTarget" style="flex: 1" autocomplete="off"
trigger: 'blur', placeholder="webhook" />
}, </el-row>
]" </el-form-item>
> <el-form-item :label="$t('description')" prop="description" :rules="[
<el-radio-group v-model="formData.second"> { max: 255, message: 'Max 255 characters', trigger: 'blur' },
<el-radio :label="60">1 min</el-radio> ]">
<el-radio :label="300">5 min</el-radio> <el-input v-model="formData.description" type="textarea" :autosize="{ minRows: 2 }" />
<el-radio :label="900">15 min</el-radio> </el-form-item>
<el-radio :label="1800">30 min</el-radio> </el-tab-pane>
<el-radio :label="3600">60 min</el-radio> <el-tab-pane>
</el-radio-group> <template #label>
</el-form-item> <span style="vertical-align: middle; padding-right: 4px">
<el-form-item {{ $t('monitorPage.successScriptLabel') }}
:label="$t('monitorPage.failTimes')" </span>
prop="times" <el-tooltip class="item" effect="dark" placement="bottom">
:rules="[ <template #content>
{ <div style="white-space: pre-line">
type: 'number', {{ $t('monitorPage.successScriptTips') }}
required: true, </div>
min: 1, </template>
max: 65535, <el-icon style="vertical-align: middle" :size="16">
message: 'Times required', <question-filled />
trigger: 'blur', </el-icon>
}, </el-tooltip>
]" </template>
> <el-form-item prop="successServerId" :label="$t('target')">
<el-radio-group v-model="formData.times"> <el-select v-model="formData.successServerId" style="width: 100%">
<el-radio :label="1">1</el-radio> <el-option v-for="item in [
<el-radio :label="2">2</el-radio> { id: -1, label: $t('monitorPage.defaultServer') },
<el-radio :label="3">3</el-radio> ...serverOption,
<el-radio :label="4">4</el-radio> ]" :key="item.label" :label="item.label" :value="item.id" />
<el-radio :label="5">5</el-radio> </el-select>
</el-radio-group> </el-form-item>
</el-form-item>
<el-form-item :label="$t('monitorPage.silentCycle')"> <el-form-item prop="successScript" :label="$t('script')">
<el-select <VAceEditor v-model:value="formData.successScript" lang="sh" theme="github"
v-model="formData.silentCycle" style="height: 360px; width: 100%" :options="{ newLineMode: 'unix' }" />
style="width: 100%" </el-form-item>
filterable </el-tab-pane>
> <el-tab-pane>
<el-option label="5 min" :value="5" /> <template #label>
<el-option label="10 min" :value="10" /> <span style="vertical-align: middle">
<el-option label="15 min" :value="15" /> {{ $t('monitorPage.failScriptLabel') }}
<el-option label="30 min" :value="30" /> </span>
<el-option label="60 min" :value="60" /> <el-tooltip class="item" effect="dark" placement="bottom">
<el-option label="3 hour" :value="180" /> <template #content>
<el-option label="6 hour" :value="360" /> <div style="white-space: pre-line">
<el-option label="12 hour" :value="720" /> {{ $t('monitorPage.failScriptTips') }}
<el-option label="24 hour" :value="1440" /> </div>
</el-select> </template>
</el-form-item> <el-icon style="vertical-align: middle" :size="16">
<el-form-item <question-filled />
:label="$t('notice')" </el-icon>
prop="notifyTarget" </el-tooltip>
:rules="[{ required: true, message: 'Webhook required' }]" </template>
> <el-form-item prop="failServerId" :label="$t('target')">
<el-row type="flex" style="width: 100%"> <el-select v-model="formData.failServerId" style="width: 100%">
<el-select v-model="formData.notifyType"> <el-option v-for="item in [
<el-option :label="$t('webhookOption[1]')" :value="1" /> { id: -1, label: $t('monitorPage.defaultServer') },
<el-option :label="$t('webhookOption[2]')" :value="2" /> ...serverOption,
<el-option :label="$t('webhookOption[3]')" :value="3" /> ]" :key="item.label" :label="item.label" :value="item.id" />
<el-option :label="$t('webhookOption[255]')" :value="255" /> </el-select>
</el-select> </el-form-item>
<el-input <el-form-item prop="failScript" :label="$t('script')">
v-model.trim="formData.notifyTarget" <VAceEditor v-model:value="formData.failScript" lang="sh" theme="github"
style="flex: 1" style="height: 360px; width: 100%" :options="{ newLineMode: 'unix' }" />
autocomplete="off" </el-form-item>
placeholder="webhook" </el-tab-pane>
/> </el-tabs>
</el-row>
</el-form-item>
<el-form-item
:label="$t('description')"
prop="description"
:rules="[
{ max: 255, message: 'Max 255 characters', trigger: 'blur' },
]"
>
<el-input
v-model="formData.description"
type="textarea"
:autosize="{ minRows: 2 }"
/>
</el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-row type="flex" justify="space-between"> <el-row type="flex" justify="space-between">
@ -380,11 +285,7 @@
<el-button @click="dialogVisible = false"> <el-button @click="dialogVisible = false">
{{ $t('cancel') }} {{ $t('cancel') }}
</el-button> </el-button>
<el-button <el-button :disabled="formProps.disabled" type="primary" @click="submit">
:disabled="formProps.disabled"
type="primary"
@click="submit"
>
{{ $t('confirm') }} {{ $t('confirm') }}
</el-button> </el-button>
</el-row> </el-row>
@ -399,7 +300,14 @@ export default { name: 'MonitorIndex' }
<script lang="ts" setup> <script lang="ts" setup>
import pms from '@/permission' import pms from '@/permission'
import { Button, Switch } from '@/components/Permission' import { Button, Switch } from '@/components/Permission'
import { Refresh, Plus, Edit, Delete, Minus } from '@element-plus/icons-vue' import {
Refresh,
Plus,
Edit,
Delete,
Minus,
QuestionFilled,
} from '@element-plus/icons-vue'
import { VAceEditor } from 'vue3-ace-editor' import { VAceEditor } from 'vue3-ace-editor'
import * as ace from 'ace-builds/src-noconflict/ace' import * as ace from 'ace-builds/src-noconflict/ace'
import { ServerOption } from '@/api/server' import { ServerOption } from '@/api/server'
@ -413,10 +321,13 @@ import {
MonitorData, MonitorData,
} from '@/api/monitor' } from '@/api/monitor'
import type { ElForm } from 'element-plus' import type { ElForm } from 'element-plus'
import { scriptLang } from '@/const/const'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { ref, watch, computed } from 'vue' import { ref, watch, computed } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDark } from '@vueuse/core'
const { t } = useI18n() const { t } = useI18n()
const store = useStore() const store = useStore()
ace.config.set( ace.config.set(
@ -445,7 +356,13 @@ const tempFormData = {
notifyType: 1, notifyType: 1,
notifyTarget: '', notifyTarget: '',
description: '', description: '',
successServerId: -1,
successScript: '',
failServerId: -1,
failScript: '',
} }
const isDark = useDark()
const formData = ref(tempFormData) const formData = ref(tempFormData)
const formProps = ref({ const formProps = ref({
loading: false, loading: false,
@ -478,10 +395,10 @@ watch(
} }
} }
) )
;(async () => { ; (async () => {
await getServerOption() await getServerOption()
await getList() await getList()
})() })()
const tablePage = computed(() => { const tablePage = computed(() => {
let _tableData = tableData.value let _tableData = tableData.value
@ -622,6 +539,10 @@ function check() {
formProps.value.loading = true formProps.value.loading = true
formProps.value.disabled = true formProps.value.disabled = true
new MonitorCheck({ new MonitorCheck({
failServerId: formData.value.failServerId,
failScript: formData.value.failScript,
successServerId: formData.value.successServerId,
successScript: formData.value.successScript,
type: formData.value.type, type: formData.value.type,
target: target, target: target,
}) })
@ -728,9 +649,10 @@ function restoreFormData() {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '@/styles/mixin.scss'; @import '@/styles/mixin.scss';
.monitor-dialog { .monitor-dialog {
padding-right: 10px; padding-right: 10px;
max-height: 50vh; max-height: 55vh;
overflow-y: auto; overflow-y: auto;
@include scrollBar(); @include scrollBar();
} }

View File

@ -3,11 +3,28 @@
<el-row class="app-bar" type="flex" justify="space-between"> <el-row class="app-bar" type="flex" justify="space-between">
<el-row> <el-row>
<el-input <el-input
v-model="projectName" v-model="searchProject.projectName"
style="width: 200px" style="width: 200px"
placeholder="Filter the project name" placeholder="Filter the project name"
/> />
<el-select
v-model="searchProject.tag"
:max-collapse-tags="1"
style="width: 300px"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="Filter the project tag"
>
<el-option
v-for="item in tagList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-row> </el-row>
<el-row> <el-row>
<el-button <el-button
:loading="tableLoading" :loading="tableLoading"
@ -40,7 +57,9 @@
target="_blank" target="_blank"
> >
{{ scope.row.name }} {{ scope.row.name }}
<el-icon><Link /></el-icon> <el-icon>
<Link />
</el-icon>
</el-link> </el-link>
<span v-else>{{ scope.row.name }}</span> <span v-else>{{ scope.row.name }}</span>
</template> </template>
@ -174,6 +193,7 @@
placeholder="goploy" placeholder="goploy"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
prop="url" prop="url"
:rules="[ :rules="[
@ -409,6 +429,28 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('tag')" prop="tag">
<el-select
v-model="formData.tag"
style="width: 100%"
:max-collapse-tags="5"
allow-create
:reserve-keyword="false"
collapse-tags-tooltip
multiple
clearable
filterable
default-first-option
placeholder="TAG"
>
<el-option
v-for="item in tagList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="review"> <el-tab-pane name="review">
<template #label> <template #label>
@ -534,7 +576,7 @@
@change="handleAfterPullScriptModeChange" @change="handleAfterPullScriptModeChange"
> >
<el-option <el-option
v-for="(item, index) in scriptLangOption" v-for="(item, index) in scriptLang.Option"
:key="index" :key="index"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
@ -644,7 +686,7 @@
<!-- <span>No support for demo</span> --> <!-- <span>No support for demo</span> -->
<v-ace-editor <v-ace-editor
v-model:value="formData.afterPullScript" v-model:value="formData.afterPullScript"
:lang="getScriptLang(formData.afterPullScriptMode)" :lang="scriptLang.getScriptLang(formData.afterPullScriptMode)"
:theme="isDark ? 'one_dark' : 'github'" :theme="isDark ? 'one_dark' : 'github'"
style="height: 400px; width: 100%" style="height: 400px; width: 100%"
placeholder="Already switched to project directory..." placeholder="Already switched to project directory..."
@ -682,7 +724,7 @@
@change="handleAfterDeployScriptModeChange" @change="handleAfterDeployScriptModeChange"
> >
<el-option <el-option
v-for="(item, index) in scriptLangOption" v-for="(item, index) in scriptLang.Option"
:key="index" :key="index"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
@ -816,7 +858,7 @@
<!-- <span>No support for demo</span> --> <!-- <span>No support for demo</span> -->
<v-ace-editor <v-ace-editor
v-model:value="formData.afterDeployScript" v-model:value="formData.afterDeployScript"
:lang="getScriptLang(formData.afterDeployScriptMode)" :lang="scriptLang.getScriptLang(formData.afterDeployScriptMode)"
:theme="isDark ? 'one_dark' : 'github'" :theme="isDark ? 'one_dark' : 'github'"
style="height: 400px; width: 100%" style="height: 400px; width: 100%"
:options="{ :options="{
@ -900,6 +942,7 @@ export default { name: 'ProjectIndex' }
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import pms from '@/permission' import pms from '@/permission'
import { scriptLang } from '@/const/const'
import Button from '@/components/Permission/Button.vue' import Button from '@/components/Permission/Button.vue'
import { import {
Search, Search,
@ -930,12 +973,14 @@ import {
ProjectRemove, ProjectRemove,
ProjectAutoDeploy, ProjectAutoDeploy,
ProjectData, ProjectData,
TagList,
} from '@/api/project' } from '@/api/project'
import type { ElRadioGroup, ElForm, FormItemRule } from 'element-plus' import type { ElRadioGroup, ElForm, FormItemRule } from 'element-plus'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useDark } from '@vueuse/core' import { useDark } from '@vueuse/core'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const isDark = useDark() const isDark = useDark()
@ -947,15 +992,11 @@ ace.config.set(
'themePath', 'themePath',
'https://cdn.jsdelivr.net/npm/ace-builds@' + ace.version + '/src-noconflict/' 'https://cdn.jsdelivr.net/npm/ace-builds@' + ace.version + '/src-noconflict/'
) )
const scriptLangOption = [
{ label: 'sh', value: 'sh', lang: 'sh' }, const searchProject = ref<{ projectName: string; tag: string[] }>({
{ label: 'zsh', value: 'zsh', lang: 'sh' }, projectName: '',
{ label: 'bash', value: 'bash', lang: 'sh' }, tag: [],
{ label: 'python', value: 'python', lang: 'python' }, })
{ label: 'php', value: 'php', lang: 'php' },
{ label: 'bat', value: 'cmd', lang: 'batchfile' },
]
const projectName = ref('')
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogAutoDeployVisible = ref(false) const dialogAutoDeployVisible = ref(false)
const serverOption = ref<ServerOption['datagram']['list']>([]) const serverOption = ref<ServerOption['datagram']['list']>([])
@ -963,6 +1004,7 @@ const userOption = ref<NamespaceUserOption['datagram']['list']>([])
const selectedItem = ref({} as ProjectData) const selectedItem = ref({} as ProjectData)
const tableLoading = ref(false) const tableLoading = ref(false)
const tableData = ref<ProjectList['datagram']['list']>([]) const tableData = ref<ProjectList['datagram']['list']>([])
const tagList = ref<TagList['datagram']['list']>([])
const pagination = ref({ page: 1, rows: 20 }) const pagination = ref({ page: 1, rows: 20 })
const form = ref<InstanceType<typeof ElForm>>() const form = ref<InstanceType<typeof ElForm>>()
const formProps = ref({ const formProps = ref({
@ -1009,6 +1051,7 @@ const formProps = ref({
reviewURLParam: ['callback=__CALLBACK__'], reviewURLParam: ['callback=__CALLBACK__'],
symlink: false, symlink: false,
disabled: false, disabled: false,
tag: [] as string[],
branch: [] as string[], branch: [] as string[],
pinging: false, pinging: false,
lsBranchLoading: false, lsBranchLoading: false,
@ -1017,6 +1060,7 @@ const formProps = ref({
const tempFormData = { const tempFormData = {
id: 0, id: 0,
name: '', name: '',
tag: [] as string[],
repoType: 'git', repoType: 'git',
url: '', url: '',
path: '', path: '',
@ -1059,12 +1103,18 @@ const autoDeployFormData = ref({ id: 0, autoDeploy: 0 })
getOptions() getOptions()
getList() getList()
getTagList()
const tablePage = computed(() => { const tablePage = computed(() => {
let _tableData = tableData.value let _tableData = tableData.value
if (projectName.value !== '') { if (searchProject.value.projectName !== '') {
_tableData = tableData.value.filter( _tableData = tableData.value.filter(
(item) => item.name.indexOf(projectName.value) !== -1 (item) => item.name.indexOf(searchProject.value.projectName) !== -1
)
}
if (searchProject.value.tag.length > 0) {
_tableData = _tableData.filter((item) =>
item.tag.split(',').find((p) => searchProject.value.tag.indexOf(p) !== -1)
) )
} }
return { return {
@ -1097,6 +1147,12 @@ function getList() {
}) })
} }
function getTagList() {
new TagList().request().then((response) => {
tagList.value = response.data.list
})
}
function handleAdd() { function handleAdd() {
if (formData.value.id > 0) { if (formData.value.id > 0) {
restoreFormData() restoreFormData()
@ -1112,6 +1168,8 @@ function handleEdit(data: ProjectData) {
formProps.value.reviewURL = '' formProps.value.reviewURL = ''
formProps.value.reviewURLParam = [] formProps.value.reviewURLParam = []
formProps.value.disabled = true formProps.value.disabled = true
formData.value.tag = data.tag.split(',')
Promise.all([ Promise.all([
new ProjectUserList({ id: data.id }).request(), new ProjectUserList({ id: data.id }).request(),
new ProjectServerList({ id: data.id }).request(), new ProjectServerList({ id: data.id }).request(),
@ -1152,6 +1210,7 @@ function handleRemove(data: ProjectData) {
new ProjectRemove({ id: data.id }).request().then(() => { new ProjectRemove({ id: data.id }).request().then(() => {
ElMessage.success('Success') ElMessage.success('Success')
getList() getList()
getTagList()
}) })
}) })
.catch(() => { .catch(() => {
@ -1159,17 +1218,6 @@ function handleRemove(data: ProjectData) {
}) })
} }
function getScriptLang(scriptMode = '') {
if (scriptMode !== '') {
const scriptInfo = scriptLangOption.find(
(elem) => elem.value === scriptMode
)
return scriptInfo ? scriptInfo['lang'] : ''
} else {
return 'sh'
}
}
function getSymlinkPath(projectPath: string) { function getSymlinkPath(projectPath: string) {
return path.normalize( return path.normalize(
path.dirname(projectPath) + '/goploy-symlink/' + path.basename(projectPath) path.dirname(projectPath) + '/goploy-symlink/' + path.basename(projectPath)
@ -1248,15 +1296,25 @@ function submit() {
} else { } else {
formData.value.reviewURL = '' formData.value.reviewURL = ''
} }
if (
formData.value.tag.filter((p) => String(p).indexOf(',') !== -1).length > 0
) {
ElMessage.error('Tag is not allowed to contain , ')
return false
}
;(formData.value.id === 0 ;(formData.value.id === 0
? new ProjectAdd(formData.value) ? new ProjectAdd({ ...formData.value, tag: formData.value.tag.join(',') })
: new ProjectEdit(formData.value) : new ProjectEdit({
...formData.value,
tag: formData.value.tag.join(','),
})
) )
.request() .request()
.then(() => { .then(() => {
dialogVisible.value = false dialogVisible.value = false
ElMessage.success('Success') ElMessage.success('Success')
getList() getList()
getTagList()
}) })
.finally(() => { .finally(() => {
formProps.value.disabled = false formProps.value.disabled = false
@ -1333,7 +1391,11 @@ function getRemoteBranchList() {
} }
function refresList() { function refresList() {
projectName.value = '' searchProject.value = {
projectName: '',
tag: [],
}
pagination.value.page = 1 pagination.value.page = 1
getList() getList()
getOptions() getOptions()

File diff suppressed because it is too large Load Diff