mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
加入用户设置与邮件通知
This commit is contained in:
@@ -37,4 +37,13 @@ other:
|
||||
tmppath: "/tmp"
|
||||
version: 0.4.3
|
||||
setting:
|
||||
allowRegister: "N"
|
||||
allowRegister: "N"
|
||||
notification:
|
||||
mail:
|
||||
server: ''
|
||||
port: ''
|
||||
senderEmail: ''
|
||||
senderIdentity: ''
|
||||
smtp:
|
||||
user: ''
|
||||
password: ''
|
||||
|
||||
7
backend/constants/notification.go
Normal file
7
backend/constants/notification.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
NotificationTriggerOnTaskEnd = "notification_trigger_on_task_end"
|
||||
NotificationTriggerOnTaskError = "notification_trigger_on_task_error"
|
||||
NotificationTriggerNever = "notification_trigger_never"
|
||||
)
|
||||
@@ -19,7 +19,9 @@ require (
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
|
||||
github.com/spf13/viper v1.4.0
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737
|
||||
gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
||||
@@ -235,6 +235,8 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -245,6 +247,8 @@ gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2G
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737 h1:NvePS/smRcFQ4bMtTddFtknbGCtoBkJxGmpSpVRafCc=
|
||||
gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
|
||||
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
|
||||
|
||||
@@ -204,6 +204,7 @@ func main() {
|
||||
authGroup.POST("/users/:id", routes.PostUser) // 更改用户
|
||||
authGroup.DELETE("/users/:id", routes.DeleteUser) // 删除用户
|
||||
authGroup.GET("/me", routes.GetMe) // 获取自己账户
|
||||
authGroup.POST("/me", routes.PostMe) // 修改自己账户
|
||||
// release版本
|
||||
authGroup.GET("/version", routes.GetVersion) // 获取发布的版本
|
||||
// 系统
|
||||
|
||||
@@ -23,6 +23,7 @@ type Schedule struct {
|
||||
NodeIds []bson.ObjectId `json:"node_ids" bson:"node_ids"`
|
||||
Status string `json:"status" bson:"status"`
|
||||
Enabled bool `json:"enabled" bson:"enabled"`
|
||||
UserId bson.ObjectId `json:"user_id" bson:"user_id"`
|
||||
|
||||
// 前端展示
|
||||
SpiderName string `json:"spider_name" bson:"spider_name"`
|
||||
@@ -49,27 +50,6 @@ func (sch *Schedule) Delete() error {
|
||||
return c.RemoveId(sch.Id)
|
||||
}
|
||||
|
||||
//func (sch *Schedule) SyncNodeIdAndSpiderId(node Node, spider Spider) {
|
||||
// sch.syncNodeId(node)
|
||||
// sch.syncSpiderId(spider)
|
||||
//}
|
||||
|
||||
//func (sch *Schedule) syncNodeId(node Node) {
|
||||
// if node.Id.Hex() == sch.NodeId.Hex() {
|
||||
// return
|
||||
// }
|
||||
// sch.NodeId = node.Id
|
||||
// _ = sch.Save()
|
||||
//}
|
||||
|
||||
//func (sch *Schedule) syncSpiderId(spider Spider) {
|
||||
// if spider.Id.Hex() == sch.SpiderId.Hex() {
|
||||
// return
|
||||
// }
|
||||
// sch.SpiderId = spider.Id
|
||||
// _ = sch.Save()
|
||||
//}
|
||||
|
||||
func GetScheduleList(filter interface{}) ([]Schedule, error) {
|
||||
s, c := database.GetCol("schedules")
|
||||
defer s.Close()
|
||||
@@ -125,13 +105,8 @@ func UpdateSchedule(id bson.ObjectId, item Schedule) error {
|
||||
if err := c.FindId(id).One(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
//node, err := GetNode(item.NodeId)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
item.UpdateTs = time.Now()
|
||||
//item.NodeKey = node.Key
|
||||
if err := item.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,15 +117,9 @@ func AddSchedule(item Schedule) error {
|
||||
s, c := database.GetCol("schedules")
|
||||
defer s.Close()
|
||||
|
||||
//node, err := GetNode(item.NodeId)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
item.Id = bson.NewObjectId()
|
||||
item.CreateTs = time.Now()
|
||||
item.UpdateTs = time.Now()
|
||||
//item.NodeKey = node.Key
|
||||
|
||||
if err := c.Insert(&item); err != nil {
|
||||
debug.PrintStack()
|
||||
|
||||
@@ -25,6 +25,7 @@ type Task struct {
|
||||
RuntimeDuration float64 `json:"runtime_duration" bson:"runtime_duration"`
|
||||
TotalDuration float64 `json:"total_duration" bson:"total_duration"`
|
||||
Pid int `json:"pid" bson:"pid"`
|
||||
UserId bson.ObjectId `json:"user_id" bson:"user_id"`
|
||||
|
||||
// 前端数据
|
||||
SpiderName string `json:"spider_name"`
|
||||
|
||||
@@ -16,11 +16,17 @@ type User struct {
|
||||
Username string `json:"username" bson:"username"`
|
||||
Password string `json:"password" bson:"password"`
|
||||
Role string `json:"role" bson:"role"`
|
||||
Email string `json:"email" bson:"email"`
|
||||
Setting UserSetting `json:"setting" bson:"setting"`
|
||||
|
||||
CreateTs time.Time `json:"create_ts" bson:"create_ts"`
|
||||
UpdateTs time.Time `json:"update_ts" bson:"update_ts"`
|
||||
}
|
||||
|
||||
type UserSetting struct {
|
||||
NotificationTrigger string `json:"notification_trigger" bson:"notification_trigger"`
|
||||
}
|
||||
|
||||
func (user *User) Save() error {
|
||||
s, c := database.GetCol("users")
|
||||
defer s.Close()
|
||||
|
||||
@@ -76,6 +76,9 @@ func PutSchedule(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 加入用户ID
|
||||
item.UserId = services.GetCurrentUser(c).Id
|
||||
|
||||
// 更新数据库
|
||||
if err := model.AddSchedule(item); err != nil {
|
||||
HandleError(http.StatusInternalServerError, c, err)
|
||||
|
||||
@@ -112,6 +112,7 @@ func PutTask(c *gin.Context) {
|
||||
SpiderId: reqBody.SpiderId,
|
||||
NodeId: node.Id,
|
||||
Param: reqBody.Param,
|
||||
UserId: services.GetCurrentUser(c).Id,
|
||||
}
|
||||
|
||||
if err := services.AddTask(t); err != nil {
|
||||
@@ -124,6 +125,7 @@ func PutTask(c *gin.Context) {
|
||||
t := model.Task{
|
||||
SpiderId: reqBody.SpiderId,
|
||||
Param: reqBody.Param,
|
||||
UserId: services.GetCurrentUser(c).Id,
|
||||
}
|
||||
if err := services.AddTask(t); err != nil {
|
||||
HandleError(http.StatusInternalServerError, c, err)
|
||||
@@ -136,6 +138,7 @@ func PutTask(c *gin.Context) {
|
||||
SpiderId: reqBody.SpiderId,
|
||||
NodeId: nodeId,
|
||||
Param: reqBody.Param,
|
||||
UserId: services.GetCurrentUser(c).Id,
|
||||
}
|
||||
|
||||
if err := services.AddTask(t); err != nil {
|
||||
|
||||
@@ -22,6 +22,7 @@ type UserRequestData struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Role string `json:"role"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func GetUser(c *gin.Context) {
|
||||
@@ -99,6 +100,7 @@ func PutUser(c *gin.Context) {
|
||||
Username: strings.ToLower(reqData.Username),
|
||||
Password: utils.EncryptPassword(reqData.Password),
|
||||
Role: reqData.Role,
|
||||
Email: reqData.Email,
|
||||
}
|
||||
if err := user.Add(); err != nil {
|
||||
HandleError(http.StatusInternalServerError, c, err)
|
||||
@@ -205,3 +207,39 @@ func GetMe(c *gin.Context) {
|
||||
User: user,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func PostMe(c *gin.Context) {
|
||||
type ReqBody struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
NotificationTrigger string `json:"notification_trigger"`
|
||||
}
|
||||
ctx := context.WithGinContext(c)
|
||||
user := ctx.User()
|
||||
if user == nil {
|
||||
ctx.FailedWithError(constants.ErrorUserNotFound, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
var reqBody ReqBody
|
||||
if err := c.ShouldBindJSON(&reqBody); err != nil {
|
||||
HandleErrorF(http.StatusBadRequest, c, "invalid request")
|
||||
return
|
||||
}
|
||||
if reqBody.Email != "" {
|
||||
user.Email = reqBody.Email
|
||||
}
|
||||
if reqBody.Password != "" {
|
||||
user.Password = utils.EncryptPassword(reqBody.Password)
|
||||
}
|
||||
if reqBody.NotificationTrigger != "" {
|
||||
user.Setting.NotificationTrigger = reqBody.NotificationTrigger
|
||||
}
|
||||
if err := user.Save(); err != nil {
|
||||
HandleError(http.StatusInternalServerError, c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Status: "ok",
|
||||
Message: "success",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,131 @@
|
||||
package notification
|
||||
|
||||
import "github.com/matcornic/hermes"
|
||||
import (
|
||||
"errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/matcornic/hermes"
|
||||
"gopkg.in/gomail.v2"
|
||||
"net/mail"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func SendMail() error {
|
||||
hermes
|
||||
func SendMail(toEmail string, subject string, content string) error {
|
||||
// hermes instance
|
||||
h := hermes.Hermes{
|
||||
Product: hermes.Product{
|
||||
Name: "Hermes",
|
||||
Link: "https://example-hermes.com/",
|
||||
Logo: "http://www.duchess-france.org/wp-content/uploads/2016/01/gopher.png",
|
||||
},
|
||||
}
|
||||
|
||||
// config
|
||||
port, _ := strconv.Atoi(os.Getenv("CRAWLAB_NOTIFICATION_MAIL_PORT"))
|
||||
password := os.Getenv("CRAWLAB_NOTIFICATION_MAIL_SMTP_PASSWORD")
|
||||
SMTPUser := os.Getenv("CRAWLAB_NOTIFICATION_MAIL_SMTP_USER")
|
||||
smtpConfig := smtpAuthentication{
|
||||
Server: os.Getenv("CRAWLAB_NOTIFICATION_MAIL_SERVER"),
|
||||
Port: port,
|
||||
SenderEmail: os.Getenv("CRAWLAB_NOTIFICATION_MAIL_SENDEREMAIL"),
|
||||
SenderIdentity: os.Getenv("CRAWLAB_NOTIFICATION_MAIL_SENDERIDENTITY"),
|
||||
SMTPPassword: password,
|
||||
SMTPUser: SMTPUser,
|
||||
}
|
||||
options := sendOptions{
|
||||
To: toEmail,
|
||||
Subject: subject,
|
||||
}
|
||||
|
||||
// email instance
|
||||
email := hermes.Email{
|
||||
Body: hermes.Body{
|
||||
FreeMarkdown: hermes.Markdown(content),
|
||||
},
|
||||
}
|
||||
|
||||
// generate html
|
||||
html, err := h.GenerateHTML(email)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
// generate text
|
||||
text, err := h.GeneratePlainText(email)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
// send the email
|
||||
if err := send(smtpConfig, options, html, text); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type smtpAuthentication struct {
|
||||
Server string
|
||||
Port int
|
||||
SenderEmail string
|
||||
SenderIdentity string
|
||||
SMTPUser string
|
||||
SMTPPassword string
|
||||
}
|
||||
|
||||
// sendOptions are options for sending an email
|
||||
type sendOptions struct {
|
||||
To string
|
||||
Subject string
|
||||
}
|
||||
|
||||
// send sends the email
|
||||
func send(smtpConfig smtpAuthentication, options sendOptions, htmlBody string, txtBody string) error {
|
||||
|
||||
if smtpConfig.Server == "" {
|
||||
return errors.New("SMTP server config is empty")
|
||||
}
|
||||
if smtpConfig.Port == 0 {
|
||||
return errors.New("SMTP port config is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SMTPUser == "" {
|
||||
return errors.New("SMTP user is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SenderIdentity == "" {
|
||||
return errors.New("SMTP sender identity is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SenderEmail == "" {
|
||||
return errors.New("SMTP sender email is empty")
|
||||
}
|
||||
|
||||
if options.To == "" {
|
||||
return errors.New("no receiver emails configured")
|
||||
}
|
||||
|
||||
from := mail.Address{
|
||||
Name: smtpConfig.SenderIdentity,
|
||||
Address: smtpConfig.SenderEmail,
|
||||
}
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", from.String())
|
||||
m.SetHeader("To", options.To)
|
||||
m.SetHeader("Subject", options.Subject)
|
||||
|
||||
m.SetBody("text/plain", txtBody)
|
||||
m.AddAlternative("text/html", htmlBody)
|
||||
|
||||
d := gomail.NewPlainDialer(smtpConfig.Server, smtpConfig.Port, smtpConfig.SMTPUser, smtpConfig.SMTPPassword)
|
||||
|
||||
return d.DialAndSend(m)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func AddScheduleTask(s model.Schedule) func() {
|
||||
SpiderId: s.SpiderId,
|
||||
NodeId: node.Id,
|
||||
Param: s.Param,
|
||||
UserId: s.UserId,
|
||||
}
|
||||
|
||||
if err := AddTask(t); err != nil {
|
||||
@@ -46,6 +47,7 @@ func AddScheduleTask(s model.Schedule) func() {
|
||||
Id: id.String(),
|
||||
SpiderId: s.SpiderId,
|
||||
Param: s.Param,
|
||||
UserId: s.UserId,
|
||||
}
|
||||
if err := AddTask(t); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
@@ -60,6 +62,7 @@ func AddScheduleTask(s model.Schedule) func() {
|
||||
SpiderId: s.SpiderId,
|
||||
NodeId: nodeId,
|
||||
Param: s.Param,
|
||||
UserId: s.UserId,
|
||||
}
|
||||
|
||||
if err := AddTask(t); err != nil {
|
||||
|
||||
@@ -6,9 +6,11 @@ import (
|
||||
"crawlab/entity"
|
||||
"crawlab/lib/cron"
|
||||
"crawlab/model"
|
||||
"crawlab/services/notification"
|
||||
"crawlab/utils"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/apex/log"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
@@ -499,6 +501,31 @@ func ExecuteTask(id int) {
|
||||
t.RuntimeDuration = t.FinishTs.Sub(t.StartTs).Seconds() // 运行时长
|
||||
t.TotalDuration = t.FinishTs.Sub(t.CreateTs).Seconds() // 总时长
|
||||
|
||||
// 获得触发任务用户
|
||||
user, err := model.GetUser(t.UserId)
|
||||
if err != nil {
|
||||
log.Errorf(GetWorkerPrefix(id) + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是任务结束时发送通知,则发送通知
|
||||
if user.Email != "" &&
|
||||
user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskEnd {
|
||||
emailContent := fmt.Sprintf(`
|
||||
# Your task has finished
|
||||
|
||||
The stats of the task are as below.
|
||||
|
||||
Metric | Value
|
||||
--- | ---
|
||||
Create Time | %s
|
||||
Start Time | %s
|
||||
Finish Time | %s
|
||||
Total Duration | %.1f
|
||||
`, t.CreateTs, t.StartTs, t.FinishTs, t.TotalDuration)
|
||||
_ = notification.SendMail(user.Email, fmt.Sprintf("[%s] Crawlab Task Finished", spider.Name), emailContent)
|
||||
}
|
||||
|
||||
// 保存任务
|
||||
if err := t.Save(); err != nil {
|
||||
log.Errorf(GetWorkerPrefix(id) + err.Error())
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crawlab/utils"
|
||||
"errors"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/spf13/viper"
|
||||
"time"
|
||||
@@ -91,3 +92,8 @@ func CheckToken(tokenStr string) (user model.User, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetCurrentUser(c *gin.Context) *model.User {
|
||||
data, _ := c.Get("currentUser")
|
||||
return data.(*model.User)
|
||||
}
|
||||
|
||||
4
backend/vendor/modules.txt
vendored
4
backend/vendor/modules.txt
vendored
@@ -121,10 +121,14 @@ golang.org/x/sys/unix
|
||||
# golang.org/x/text v0.3.0
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/norm
|
||||
# gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
|
||||
gopkg.in/alexcesaro/quotedprintable.v3
|
||||
# gopkg.in/go-playground/validator.v8 v8.18.2
|
||||
gopkg.in/go-playground/validator.v8
|
||||
# gopkg.in/go-playground/validator.v9 v9.29.1
|
||||
gopkg.in/go-playground/validator.v9
|
||||
# gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737
|
||||
gopkg.in/gomail.v2
|
||||
# gopkg.in/russross/blackfriday.v2 v2.0.0
|
||||
gopkg.in/russross/blackfriday.v2
|
||||
# gopkg.in/yaml.v2 v2.2.2
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
'Schedules': '定时任务',
|
||||
'Deploys': '部署',
|
||||
'Sites': '网站',
|
||||
'Setting': '设置',
|
||||
|
||||
// 标签
|
||||
'Overview': '概览',
|
||||
@@ -323,6 +324,7 @@ export default {
|
||||
'The schedule has been removed': '已删除定时任务',
|
||||
'The schedule has been added': '已添加定时任务',
|
||||
'The schedule has been saved': '已保存定时任务',
|
||||
'Email format invalid': '邮箱地址格式不正确',
|
||||
|
||||
// 登录
|
||||
'Sign in': '登录',
|
||||
@@ -343,6 +345,16 @@ export default {
|
||||
'Role': '角色',
|
||||
'Edit User': '更改用户',
|
||||
'Users': '用户',
|
||||
'Email': '邮箱',
|
||||
'Optional': '可选',
|
||||
|
||||
// 设置
|
||||
'Notification Trigger': '通知触发',
|
||||
'On Task End': '当任务结束',
|
||||
'On Task Error': '当任务发生错误',
|
||||
'Never': '从不',
|
||||
|
||||
// 其他
|
||||
tagsView: {
|
||||
closeOthers: '关闭其他',
|
||||
close: '关闭',
|
||||
|
||||
@@ -210,6 +210,24 @@ export const constantRouterMap = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Setting',
|
||||
icon: 'fa fa-gear'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('../views/setting/Setting'),
|
||||
meta: {
|
||||
title: 'Setting',
|
||||
icon: 'fa fa-gear'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{ path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
|
||||
@@ -91,6 +91,11 @@ const user = {
|
||||
})
|
||||
},
|
||||
|
||||
// 修改用户信息
|
||||
postInfo ({ commit }, form) {
|
||||
return request.post('/me', form)
|
||||
},
|
||||
|
||||
// 注册
|
||||
register ({ dispatch, commit, state }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
@keyup.enter.native="onKeyEnter"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isSignUp" prop="email" style="margin-bottom: 28px;">
|
||||
<el-input
|
||||
v-model="loginForm.email"
|
||||
name="email"
|
||||
:placeholder="$t('Email')"
|
||||
@keyup.enter.native="onKeyEnter"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item style="border: none">
|
||||
<el-button v-if="isSignUp" :loading="loading" type="primary" style="width:100%;"
|
||||
@click.native.prevent="handleSignup">
|
||||
@@ -104,7 +112,8 @@ export default {
|
||||
loginForm: {
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
confirmPassword: '',
|
||||
email: ''
|
||||
},
|
||||
loginRules: {
|
||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||||
|
||||
102
frontend/src/views/setting/Setting.vue
Normal file
102
frontend/src/views/setting/Setting.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="userInfo" class="setting-form" ref="setting-form" label-width="150px" :rules="rules"
|
||||
inline-message>
|
||||
<el-form-item prop="username" :label="$t('Username')">
|
||||
<el-input v-model="userInfo.username" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" :label="$t('Password')">
|
||||
<el-input v-model="userInfo.password" type="password" :placeholder="$t('Password')"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="email" :label="$t('Email')">
|
||||
<el-input v-model="userInfo.email" :placeholder="$t('Email')"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Notification Trigger')">
|
||||
<el-radio-group v-model="userInfo.setting.notification_trigger">
|
||||
<el-radio label="notification_trigger_on_task_end">
|
||||
{{$t('On Task End')}}
|
||||
</el-radio>
|
||||
<el-radio label="notification_trigger_on_task_error">
|
||||
{{$t('On Task Error')}}
|
||||
</el-radio>
|
||||
<el-radio label="notification_trigger_never">
|
||||
{{$t('Never')}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="buttons">
|
||||
<el-button type="success" @click="saveUserInfo">{{$t('Save')}}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Setting',
|
||||
data () {
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (!value) return callback()
|
||||
if (value.length < 5) {
|
||||
callback(new Error(this.$t('Password length should be no shorter than 5')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (!value) return callback()
|
||||
if (!value.match(/.+@.+/i)) {
|
||||
callback(new Error(this.$t('Email format invalid')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
userInfo: { setting: {} },
|
||||
rules: {
|
||||
password: [{ trigger: 'blur', validator: validatePass }],
|
||||
email: [{ trigger: 'blur', validator: validateEmail }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUserInfo () {
|
||||
const data = localStorage.getItem('user_info')
|
||||
if (!data) return {}
|
||||
this.userInfo = JSON.parse(data)
|
||||
if (!this.userInfo.setting) this.userInfo.setting = {}
|
||||
},
|
||||
saveUserInfo () {
|
||||
this.$refs['setting-form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
const res = await this.$store.dispatch('user/postInfo', {
|
||||
password: this.userInfo.password,
|
||||
email: this.userInfo.email,
|
||||
notification_trigger: this.userInfo.setting.notification_trigger
|
||||
})
|
||||
if (!res || res.error) {
|
||||
this.$message.error(res.error)
|
||||
} else {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.$store.dispatch('user/getInfo')
|
||||
this.getUserInfo()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.setting-form {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.setting-form .buttons {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -15,6 +15,9 @@
|
||||
<el-option value="normal" :label="$t('normal')"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="email" :label="$t('Email')">
|
||||
<el-input v-model="userForm.email" :placeholder="$t('Email')"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button size="small" @click="dialogVisible=false">{{$t('Cancel')}}</el-button>
|
||||
@@ -107,11 +110,20 @@ export default {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (!value) return callback()
|
||||
if (!value.match(/.+@.+/i)) {
|
||||
callback(new Error(this.$t('Email format invalid')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
dialogVisible: false,
|
||||
isAdd: false,
|
||||
rules: {
|
||||
password: [{ validator: validatePass }]
|
||||
password: [{ validator: validatePass }],
|
||||
email: [{ validator: validateEmail }]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -205,6 +217,8 @@ export default {
|
||||
this.isAdd = true
|
||||
this.$store.commit('user/SET_USER_FORM', {})
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onValidateEmail (value) {
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
||||
Reference in New Issue
Block a user