feat: 面板设置过期跳转功能实现

This commit is contained in:
ssongliu 2022-09-29 16:15:59 +08:00 committed by ssongliu
parent 49f0fb10a8
commit 12dc630c89
43 changed files with 663 additions and 308 deletions

View file

@ -104,7 +104,22 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) {
return
}
if err := cronjobService.Save(id, req); err != nil {
upMap := make(map[string]interface{})
upMap["name"] = req.Name
upMap["script"] = req.Script
upMap["specType"] = req.SpecType
upMap["week"] = req.Week
upMap["day"] = req.Day
upMap["hour"] = req.Hour
upMap["minute"] = req.Minute
upMap["website"] = req.Website
upMap["exclusionRules"] = req.ExclusionRules
upMap["database"] = req.Database
upMap["url"] = req.URL
upMap["sourceDir"] = req.SourceDir
upMap["targetDirID"] = req.TargetDirID
upMap["retainCopies"] = req.RetainCopies
if err := cronjobService.Update(id, req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View file

@ -57,6 +57,24 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
var req dto.PasswordUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := settingService.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) SyncTime(c *gin.Context) {
var timeLayoutStr = "2006-01-02 15:04:05"

View file

@ -18,7 +18,7 @@ type CronjobCreate struct {
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
TargetDirID int `json:"targetDirID"`
RetainDays int `json:"retainDays" validate:"number,min=1"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
}
type CronjobUpdate struct {
@ -36,7 +36,7 @@ type CronjobUpdate struct {
URL string `json:"url"`
SourceDir string `json:"sourceDir"`
TargetDirID int `json:"targetDirID"`
RetainDays int `json:"retainDays" validate:"number,min=1"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
}
type CronjobUpdateStatus struct {
@ -71,9 +71,10 @@ type CronjobInfo struct {
SourceDir string `json:"sourceDir"`
TargetDir string `json:"targetDir"`
TargetDirID int `json:"targetDirID"`
RetainDays int `json:"retainDays"`
RetainCopies int `json:"retainCopies"`
Status string `json:"status"`
LastRecrodTime string `json:"lastRecrodTime"`
Status string `json:"status"`
}
type SearchRecord struct {

View file

@ -13,7 +13,7 @@ type SettingInfo struct {
ServerPort int `json:"serverPort"`
SecurityEntrance string `json:"securityEntrance"`
PasswordTimeOut string `json:"passwordTimeOut"`
ExpirationTime string `json:"expirationTime"`
ComplexityVerification string `json:"complexityVerification"`
MFAStatus string `json:"mfaStatus"`
MFASecret string `json:"mfaSecret"`

View file

@ -21,7 +21,7 @@ type Cronjob struct {
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
TargetDirID uint64 `gorm:"type:decimal" json:"targetDirID"`
ExclusionRules string `gorm:"longtext" json:"exclusionRules"`
RetainDays uint64 `gorm:"type:decimal" json:"retainDays"`
RetainCopies uint64 `gorm:"type:decimal" json:"retainCopies"`
Status string `gorm:"type:varchar(64)" json:"status"`
EntryID uint64 `gorm:"type:decimal" json:"entryID"`

View file

@ -14,6 +14,7 @@ type CronjobRepo struct{}
type ICronjobRepo interface {
Get(opts ...DBOption) (model.Cronjob, error)
GetRecord(opts ...DBOption) (model.JobRecords, error)
RecordFirst(id uint) (model.JobRecords, error)
ListRecord(opts ...DBOption) ([]model.JobRecords, error)
List(opts ...DBOption) ([]model.Cronjob, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error)
@ -84,6 +85,12 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro
return count, cronjobs, err
}
func (u *CronjobRepo) RecordFirst(id uint) (model.JobRecords, error) {
var record model.JobRecords
err := global.DB.Order("created_at desc").First(&record).Error
return record, err
}
func (u *CronjobRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) {
var cronjobs []model.JobRecords
db := global.DB.Model(&model.JobRecords{})

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/utils/cloud_storage"
"github.com/jinzhu/copier"
@ -18,6 +19,7 @@ type IBackupService interface {
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(id uint, upMap map[string]interface{}) error
BatchDelete(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
}
func NewIBackupService() IBackupService {
@ -81,3 +83,28 @@ func (u *BackupService) BatchDelete(ids []uint) error {
func (u *BackupService) Update(id uint, upMap map[string]interface{}) error {
return backupRepo.Update(id, upMap)
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
varMap["type"] = backup.Type
if backup.Type == "LOCAL" {
return nil, errors.New("not support")
}
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["secretKey"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
if err != nil {
return nil, err
}
return backClient, nil
}

View file

@ -2,13 +2,9 @@ package service
import (
"bufio"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
@ -22,11 +18,6 @@ import (
"github.com/robfig/cron/v3"
)
const (
errRecord = "errRecord"
errHandle = "errHandle"
)
type CronjobService struct{}
type ICronjobService interface {
@ -34,7 +25,7 @@ type ICronjobService interface {
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
Create(cronjobDto dto.CronjobCreate) error
HandleOnce(id uint) error
Save(id uint, req dto.CronjobUpdate) error
Update(id uint, req dto.CronjobUpdate) error
UpdateStatus(id uint, status string) error
Delete(ids []uint) error
}
@ -59,6 +50,12 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
} else {
item.TargetDir = "-"
}
record, _ := cronjobRepo.RecordFirst(cronjob.ID)
if record.ID != 0 {
item.LastRecrodTime = record.StartTime.Format("2006-01-02 15:04:05")
} else {
item.LastRecrodTime = "-"
}
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@ -184,15 +181,8 @@ func (u *CronjobService) StartJob(cronjob *model.Cronjob) error {
func (u *CronjobService) Delete(ids []uint) error {
if len(ids) == 1 {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(ids[0]))
if cronjob.ID == 0 {
return constant.ErrRecordNotFound
}
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(ids[0])))
if err := os.RemoveAll(fmt.Sprintf("%s/%s/%s-%v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID)); err != nil {
global.LOG.Errorf("rm file %s/%s/%s-%v failed, err: %v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID, err)
if err := u.HandleDelete(ids[0]); err != nil {
return err
}
return cronjobRepo.Delete(commonRepo.WithByID(ids[0]))
}
@ -201,24 +191,37 @@ func (u *CronjobService) Delete(ids []uint) error {
return err
}
for i := range cronjobs {
global.Cron.Remove(cron.EntryID(cronjobs[i].EntryID))
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(cronjobs[i].ID)))
if err := os.RemoveAll(fmt.Sprintf("%s/%s/%s-%v", constant.TaskDir, cronjobs[i].Type, cronjobs[i].Name, cronjobs[i].ID)); err != nil {
global.LOG.Errorf("rm file %s/%s/%s-%v failed, err: %v", constant.TaskDir, cronjobs[i].Type, cronjobs[i].Name, cronjobs[i].ID, err)
}
_ = u.HandleDelete(ids[i])
}
return cronjobRepo.Delete(commonRepo.WithIdsIn(ids))
}
func (u *CronjobService) Save(id uint, req dto.CronjobUpdate) error {
func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
var cronjob model.Cronjob
if err := copier.Copy(&cronjob, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
cronjob.Spec = loadSpec(cronjob)
if err := u.StartJob(&cronjob); err != nil {
return err
}
return cronjobRepo.Save(id, cronjob)
upMap := make(map[string]interface{})
upMap["name"] = req.Name
upMap["script"] = req.Script
upMap["spec_type"] = req.SpecType
upMap["week"] = req.Week
upMap["day"] = req.Day
upMap["hour"] = req.Hour
upMap["minute"] = req.Minute
upMap["website"] = req.Website
upMap["exclusion_rules"] = req.ExclusionRules
upMap["database"] = req.Database
upMap["url"] = req.URL
upMap["source_dir"] = req.SourceDir
upMap["target_dir_id"] = req.TargetDirID
upMap["retain_days"] = req.RetainCopies
return cronjobRepo.Update(id, upMap)
}
func (u *CronjobService) UpdateStatus(id uint, status string) error {
@ -248,55 +251,6 @@ func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) {
return int(entryID), nil
}
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
var (
message []byte
err error
)
record := cronjobRepo.StartRecords(cronjob.ID, "")
switch cronjob.Type {
case "shell":
cmd := exec.Command(cronjob.Script)
message, err = cmd.CombinedOutput()
case "website":
message, err = tarWithExclude(cronjob, record.StartTime)
case "database":
message, err = tarWithExclude(cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
return
}
message, err = tarWithExclude(cronjob, record.StartTime)
case "curl":
if len(cronjob.URL) == 0 {
return
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Timeout: 1 * time.Second, Transport: tr}
request, _ := http.NewRequest("GET", cronjob.URL, nil)
response, err := client.Do(request)
if err != nil {
record.Records = errHandle
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), errHandle)
}
defer response.Body.Close()
message, _ = ioutil.ReadAll(response.Body)
}
if err != nil {
record.Records = errHandle
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), errHandle)
return
}
record.Records, err = mkdirAndWriteFile(cronjob, record.StartTime, message)
if err != nil {
record.Records = errRecord
global.LOG.Errorf("save file %s failed, err: %v", record.Records, err)
}
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
}
func mkdirAndWriteFile(cronjob *model.Cronjob, startTime time.Time, msg []byte) (string, error) {
dir := fmt.Sprintf("%s%s/%s-%v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
@ -317,143 +271,6 @@ func mkdirAndWriteFile(cronjob *model.Cronjob, startTime time.Time, msg []byte)
return path, nil
}
func tarWithExclude(cronjob *model.Cronjob, startTime time.Time) ([]byte, error) {
varMaps, targetdir, err := loadTargetInfo(cronjob)
if err != nil {
return nil, fmt.Errorf("load target dir failed, err: %v", err)
}
exStr := []string{}
name := ""
if cronjob.Type == "database" {
exStr = append(exStr, "-zvPf")
name = fmt.Sprintf("%s/%s.gz", targetdir, startTime.Format("20060102150405"))
exStr = append(exStr, name)
} else {
exStr = append(exStr, "-zcvPf")
name = fmt.Sprintf("%s/%s.tar.gz", targetdir, startTime.Format("20060102150405"))
exStr = append(exStr, name)
excludes := strings.Split(cronjob.ExclusionRules, ";")
for _, exclude := range excludes {
exStr = append(exStr, "--exclude")
exStr = append(exStr, exclude)
}
}
exStr = append(exStr, cronjob.SourceDir)
cmd := exec.Command("tar", exStr...)
stdout, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("tar zcPf failed, err: %v", err)
}
var backClient cloud_storage.CloudStorageClient
if varMaps["type"] != "LOCAL" {
backClient, err = cloud_storage.NewCloudStorageClient(varMaps)
if err != nil {
return stdout, fmt.Errorf("new cloud storage client failed, err: %v", err)
}
isOK, err := backClient.Upload(name, strings.Replace(name, constant.TmpDir, "", -1))
if !isOK {
return nil, fmt.Errorf("cloud storage upload failed, err: %v", err)
}
}
if backType, ok := varMaps["type"].(string); ok {
rmExpiredRecords(backType, targetdir, cronjob, backClient)
}
return stdout, nil
}
func loadTargetInfo(cronjob *model.Cronjob) (map[string]interface{}, string, error) {
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return nil, "", err
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, "", err
}
dir := ""
varMap["type"] = backup.Type
if backup.Type != "LOCAL" {
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["secretKey"] = backup.Credential
}
dir = fmt.Sprintf("%s%s/%s", constant.TmpDir, cronjob.Type, cronjob.Name)
} else {
if _, ok := varMap["dir"]; !ok {
return nil, "", errors.New("load local backup dir failed")
}
dir = fmt.Sprintf("%v/%s/%s", varMap["dir"], cronjob.Type, cronjob.Name)
}
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
if err != nil {
return nil, "", fmt.Errorf("mkdir %s failed, err: %v", dir, err)
}
}
}
return varMap, dir, nil
}
func rmExpiredRecords(backType, path string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
timeNow := time.Now()
timeZero := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 0, 0, 0, 0, timeNow.Location())
timeStart := timeZero.AddDate(0, 0, -int(cronjob.RetainDays)+1)
var timePrefixs []string
for i := 0; i < int(cronjob.RetainDays); i++ {
timePrefixs = append(timePrefixs, timeZero.AddDate(0, 0, i).Format("20060102"))
}
if backType != "LOCAL" {
dir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
currentObjs, err := backClient.ListObjects(dir)
if err != nil {
global.LOG.Errorf("list bucket object %s failed, err: %v", dir, err)
return
}
for _, obj := range currentObjs {
objKey, ok := obj.(string)
if !ok {
continue
}
objKey = strings.ReplaceAll(objKey, dir, "")
isOk := false
for _, pre := range timePrefixs {
if strings.HasPrefix(objKey, pre) {
isOk = true
break
}
}
if !isOk {
_, _ = backClient.Delete(objKey)
}
}
return
}
files, err := ioutil.ReadDir(path)
if err != nil {
global.LOG.Errorf("read dir %s failed, err: %v", path, err)
return
}
for _, file := range files {
isOk := false
for _, pre := range timePrefixs {
if strings.HasPrefix(file.Name(), pre) {
isOk = true
break
}
}
if !isOk {
_ = os.Remove(path + "/" + file.Name())
}
}
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), cronjobRepo.WithByStartDate(timeStart))
}
func loadSpec(cronjob model.Cronjob) string {
switch cronjob.SpecType {
case "perMonth":

View file

@ -0,0 +1,192 @@
package service
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/utils/cloud_storage"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
var (
message []byte
err error
)
record := cronjobRepo.StartRecords(cronjob.ID, "")
switch cronjob.Type {
case "shell":
cmd := exec.Command(cronjob.Script)
message, err = cmd.CombinedOutput()
case "website":
message, err = u.HandleBackup(cronjob, record.StartTime)
case "database":
message, err = u.HandleBackup(cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
return
}
message, err = u.HandleBackup(cronjob, record.StartTime)
case "curl":
if len(cronjob.URL) == 0 {
return
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Timeout: 1 * time.Second, Transport: tr}
request, _ := http.NewRequest("GET", cronjob.URL, nil)
response, err := client.Do(request)
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
}
defer response.Body.Close()
message, _ = ioutil.ReadAll(response.Body)
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
return
}
record.Records, err = mkdirAndWriteFile(cronjob, record.StartTime, message)
if err != nil {
global.LOG.Errorf("save file %s failed, err: %v", record.Records, err)
}
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
}
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) ([]byte, error) {
var stdout []byte
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return nil, err
}
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
name := fmt.Sprintf("%s.gz", startTime.Format("20060102150405"))
if cronjob.Type != "database" {
name = fmt.Sprintf("%s.tar.gz", startTime.Format("20060102150405"))
}
if backup.Type == "LOCAL" {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
if _, ok := varMap["dir"]; !ok {
return nil, errors.New("load local backup dir failed")
}
baseDir := varMap["dir"].(string)
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
if err != nil {
return nil, fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
}
}
}
stdout, err = handleTar(cronjob.SourceDir, fmt.Sprintf("%s/%s", baseDir, commonDir), name, cronjob.ExclusionRules)
if err != nil {
return stdout, err
}
u.HandleRmExpired(backup.Type, fmt.Sprintf("%s/%s", baseDir, commonDir), cronjob, nil)
return stdout, nil
}
targetDir := constant.TmpDir + commonDir
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return nil, err
}
if cronjob.Type != "database" {
stdout, err = handleTar(cronjob.SourceDir, targetDir, name, cronjob.ExclusionRules)
if err != nil {
return stdout, err
}
}
if _, err = client.Upload(targetDir+name, commonDir+name); err != nil {
return nil, err
}
u.HandleRmExpired(backup.Type, commonDir+name, cronjob, client)
return stdout, nil
}
func (u *CronjobService) HandleDelete(id uint) error {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id))
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id)))
if err := os.RemoveAll(fmt.Sprintf("%s/%s-%v", constant.TaskDir, commonDir, cronjob.ID)); err != nil {
global.LOG.Errorf("rm file %s/%s-%v failed, err: %v", constant.TaskDir, commonDir, cronjob.ID, err)
}
return nil
}
func (u *CronjobService) HandleRmExpired(backType, path string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
if backType != "LOCAL" {
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
currentObjs, err := backClient.ListObjects(commonDir)
if err != nil {
global.LOG.Errorf("list bucket object %s failed, err: %v", commonDir, err)
return
}
for i := 0; i < len(currentObjs)-int(cronjob.RetainCopies); i++ {
_, _ = backClient.Delete(currentObjs[i].(string))
}
return
}
files, err := ioutil.ReadDir(path)
if err != nil {
global.LOG.Errorf("read dir %s failed, err: %v", path, err)
return
}
for i := 0; i < len(files)-int(cronjob.RetainCopies); i++ {
_ = os.Remove(path + "/" + files[i].Name())
}
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
if len(records) > int(cronjob.RetainCopies) {
for i := int(cronjob.RetainCopies); i < len(records); i++ {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(records[i].ID)))
}
}
}
func handleTar(sourceDir, targetDir, name, exclusionRules string) ([]byte, error) {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
return nil, err
}
}
exStr := []string{}
exStr = append(exStr, "zcvf")
exStr = append(exStr, targetDir+name)
excludes := strings.Split(exclusionRules, ";")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
}
exStr = append(exStr, "--exclude")
exStr = append(exStr, exclude)
}
if len(strings.Split(sourceDir, "/")) > 3 {
exStr = append(exStr, "-C")
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := strings.ReplaceAll(sourceDir, itemDir, "")
exStr = append(exStr, aheadDir)
exStr = append(exStr, itemDir)
} else {
exStr = append(exStr, sourceDir)
}
cmd := exec.Command("tar", exStr...)
return (cmd.CombinedOutput())
}

View file

@ -18,6 +18,7 @@ type ISettingService interface {
GetSettingInfo() (*dto.SettingInfo, error)
Update(c *gin.Context, key, value string) error
UpdatePassword(c *gin.Context, old, new string) error
HandlePasswordExpired(c *gin.Context, old, new string) error
}
func NewISettingService() ISettingService {
@ -47,13 +48,20 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
}
func (u *SettingService) Update(c *gin.Context, key, value string) error {
if key == "ExpirationDays" {
timeout, _ := strconv.Atoi(value)
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006.01.02 15:04:05")); err != nil {
return err
}
c.SetCookie(constant.PasswordExpiredName, encrypt.Md5(time.Now().Format("20060102150405")), 86400*timeout, "", "", false, false)
}
if err := settingRepo.Update(key, value); err != nil {
return err
}
return settingRepo.Update(key, value)
return nil
}
func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error {
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
return err
@ -70,15 +78,32 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
if err := settingRepo.Update("Password", newPassword); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
err := global.SESSION.Delete(sID)
if err != nil {
return err
}
expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
if err != nil {
return err
}
timeout, _ := strconv.Atoi(expiredSetting.Value)
c.SetCookie(constant.PasswordExpiredName, encrypt.Md5(time.Now().Format("20060102150405")), 86400*timeout, "", "", false, false)
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006.01.02 15:04:05")); err != nil {
return err
}
return nil
}
return constant.ErrInitialPassword
}
func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
if err := u.HandlePasswordExpired(c, old, new); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
err := global.SESSION.Delete(sID)
if err != nil {
return err
}
}
return nil
}

View file

@ -11,6 +11,7 @@ const (
CodeErrUnSafety = 402
CodeErrForbidden = 403
CodeErrNotFound = 404
CodePasswordExpired = 405
CodeErrInternalServer = 500
CodeErrHeader = 406
)
@ -32,10 +33,11 @@ var (
// api
var (
ErrTypeInternalServer = "ErrInternalServer"
ErrTypeInvalidParams = "ErrInvalidParams"
ErrTypeToken = "ErrToken"
ErrTypeTokenTimeOut = "ErrTokenTimeOut"
ErrTypeNotLogin = "ErrNotLogin"
ErrTypeNotSafety = "ErrNotSafety"
ErrTypeInternalServer = "ErrInternalServer"
ErrTypeInvalidParams = "ErrInvalidParams"
ErrTypeToken = "ErrToken"
ErrTypeTokenTimeOut = "ErrTokenTimeOut"
ErrTypeNotLogin = "ErrNotLogin"
ErrTypePasswordExpired = "ErrPasswordExpired"
ErrTypeNotSafety = "ErrNotSafety"
)

View file

@ -9,4 +9,6 @@ const (
JWTSigningKey = "1panelKey"
JWTBufferTime = 86400
JWTIssuer = "1Panel"
PasswordExpiredName = "expired"
)

View file

@ -10,4 +10,5 @@ ErrRecordNotFound: "Records not found: {{ .detail }}"
ErrStructTransform: "Type conversion failure: {{ .detail }}"
ErrNotLogin: "User is not Login: {{ .detail }}"
ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}"
ErrPasswordExpired: "The current password has expired: {{ .detail }}"
ErrNotSupportType: "The system does not support the current type: {{ .detail }}"

View file

@ -10,4 +10,5 @@ ErrRecordNotFound: "记录未能找到: {{ .detail }}"
ErrStructTransform: "类型转换失败: {{ .detail }}"
ErrNotLogin: "用户未登录: {{ .detail }}"
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"

View file

@ -54,7 +54,7 @@ var AddTableSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "UserName", Value: "admin"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Password", Value: "5WYEZ4XcitdomVvAyimt9WwJwBJJSbTTHncZoqyOraQ="}).Error; err != nil {
if err := tx.Create(&model.Setting{Key: "Password", Value: "Sr2qOhssQNg8rGRvqyWhsBDJx+tV5VfLEZXdbax//dA="}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Email", Value: ""}).Error; err != nil {
@ -84,7 +84,10 @@ var AddTableSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "onepanel"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "PasswordTimeOut", Value: time.Now().AddDate(0, 0, 10).Format("2016.01.02 15:04:05")}).Error; err != nil {
if err := tx.Create(&model.Setting{Key: "ExpirationTime", Value: time.Now().AddDate(0, 0, 10).Format("2006.01.02 15:04:05")}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ExpirationDays", Value: "10"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ComplexityVerification", Value: "enable"}).Error; err != nil {

View file

@ -0,0 +1,18 @@
package middleware
import (
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/constant"
"github.com/gin-gonic/gin"
)
func PasswordExpired() gin.HandlerFunc {
return func(c *gin.Context) {
_, err := c.Cookie(constant.PasswordExpiredName)
if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, nil)
return
}
c.Next()
}
}

View file

@ -11,7 +11,7 @@ type OperationLogRouter struct{}
func (s *OperationLogRouter) InitOperationLogRouter(Router *gin.RouterGroup) {
operationRouter := Router.Group("operations")
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
operationRouter.POST("", baseApi.GetOperationList)

View file

@ -10,8 +10,15 @@ import (
type BackupRouter struct{}
func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) {
baRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
baRouter := Router.Group("backups").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("backups").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
baRouter.GET("/search", baseApi.ListBackup)

View file

@ -10,8 +10,15 @@ import (
type CommandRouter struct{}
func (s *CommandRouter) InitCommandRouter(Router *gin.RouterGroup) {
cmdRouter := Router.Group("commands").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("commands").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
cmdRouter := Router.Group("commands").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("commands").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateCommand)

View file

@ -10,8 +10,15 @@ import (
type CronjobRouter struct{}
func (s *CronjobRouter) InitCronjobRouter(Router *gin.RouterGroup) {
cmdRouter := Router.Group("cronjobs").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("cronjobs").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
cmdRouter := Router.Group("cronjobs").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("cronjobs").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateCronjob)

View file

@ -11,7 +11,7 @@ type FileRouter struct {
func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
fileRouter := Router.Group("files")
fileRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
fileRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
//withRecordRouter := fileRouter.Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{

View file

@ -10,8 +10,15 @@ import (
type GroupRouter struct{}
func (s *GroupRouter) InitGroupRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("groups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
groupRouter := Router.Group("groups").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("groups").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateGroup)

View file

@ -10,8 +10,15 @@ import (
type HostRouter struct{}
func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter := Router.Group("hosts").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("hosts").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
hostRouter := Router.Group("hosts").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("hosts").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("", baseApi.CreateHost)

View file

@ -10,7 +10,10 @@ import (
type MonitorRouter struct{}
func (s *MonitorRouter) InitMonitorRouter(Router *gin.RouterGroup) {
monitorRouter := Router.Group("monitors").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
monitorRouter := Router.Group("monitors").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
monitorRouter.POST("/search", baseApi.LoadMonitor)

View file

@ -9,11 +9,20 @@ import (
type SettingRouter struct{}
func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter := Router.Group("settings").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
withRecordRouter := Router.Group("settings").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
baseRouter := Router.Group("settings")
settingRouter := Router.Group("settings").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
withRecordRouter := Router.Group("settings").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired()).
Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
settingRouter.POST("/search", baseApi.GetSettingInfo)
baseRouter.POST("/search", baseApi.GetSettingInfo)
baseRouter.PUT("/expired/handle", baseApi.HandlePasswordExpired)
withRecordRouter.PUT("", baseApi.UpdateSetting)
settingRouter.PUT("/password", baseApi.UpdatePassword)
settingRouter.POST("/time/sync", baseApi.SyncTime)

View file

@ -10,7 +10,10 @@ import (
type TerminalRouter struct{}
func (s *TerminalRouter) InitTerminalRouter(Router *gin.RouterGroup) {
terminalRouter := Router.Group("terminals").Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
terminalRouter := Router.Group("terminals").
Use(middleware.JwtAuth()).
Use(middleware.SessionAuth()).
Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
terminalRouter.GET("", baseApi.WsSsh)

View file

@ -9,7 +9,7 @@ import (
func TestStringEncrypt(t *testing.T) {
viper.Init()
p, err := StringEncrypt("Songliu123++")
p, err := StringEncrypt("1Panel@2022")
if err != nil {
t.Fatal(err)
}
@ -18,7 +18,7 @@ func TestStringEncrypt(t *testing.T) {
func TestStringDecrypt(t *testing.T) {
viper.Init()
p, err := StringDecrypt("Jmg4EUACGznt3dEQTJ+0ZRxwLaVNsNg7R5RcZ0V7ElQ=")
p, err := StringDecrypt("dXn5bVtea+KVLDrLJlpnPIJNfW8TAMmqX1QNMdSGp88=")
if err != nil {
t.Fatal(err)
}

View file

@ -2,13 +2,14 @@ package files
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/afero"
"os"
"path"
"path/filepath"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
type FileInfo struct {
@ -92,7 +93,7 @@ func (f *FileInfo) listChildren(dir, showHidden bool, page, pageSize int) error
if err != nil {
return err
}
f.ItemTotal = len(files)
f.ItemTotal = 0
var items []*FileInfo
for _, df := range files {
@ -106,6 +107,7 @@ func (f *FileInfo) listChildren(dir, showHidden bool, page, pageSize int) error
if !showHidden && IsHidden(name) {
continue
}
f.ItemTotal++
isSymlink, isInvalidLink := false, false
if IsSymlink(df.Mode()) {

View file

@ -55,6 +55,10 @@ class RequestHttp {
});
return data;
}
if (data.code == ResultEnum.EXPIRED) {
router.push({ name: 'Expired' });
return data;
}
if (data.code && data.code !== ResultEnum.SUCCESS) {
ElMessage.error(data.msg);
return Promise.reject(data);

View file

@ -19,7 +19,7 @@ export namespace Cronjob {
sourceDir: string;
targetDirID: number;
targetDir: string;
retainDays: number;
retainCopies: number;
status: string;
}
export interface CronjobCreate {
@ -38,7 +38,7 @@ export namespace Cronjob {
url: string;
sourceDir: string;
targetDirID: number;
retainDays: number;
retainCopies: number;
}
export interface CronjobUpdate {
id: number;
@ -55,7 +55,7 @@ export namespace Cronjob {
url: string;
sourceDir: string;
targetDirID: number;
retainDays: number;
retainCopies: number;
}
export interface UpdateStatus {
id: number;

View file

@ -13,7 +13,7 @@ export namespace Setting {
serverPort: number;
securityEntrance: string;
passwordTimeOut: string;
expirationTime: string;
complexityVerification: string;
mfaStatus: string;
mfaSecret: string;

View file

@ -13,6 +13,10 @@ export const updatePassword = (param: Setting.PasswordUpdate) => {
return http.put(`/settings/password`, param);
};
export const handleExpired = (param: Setting.PasswordUpdate) => {
return http.put(`/settings/expired/handle`, param);
};
export const syncTime = () => {
return http.post(`/settings/time/sync`, {});
};

View file

@ -4,6 +4,7 @@ export enum ResultEnum {
OVERDUE = 401,
UNSAFETY = 402,
FORBIDDEN = 403,
EXPIRED = 405,
TIMEOUT = 100000,
TYPE = 'success',
}

View file

@ -151,6 +151,8 @@ export default {
taskType: 'Task type',
shell: 'shell',
website: 'website',
rulesHelper: 'Compression exclusion rules (with; Is a delimiter), for example: \n*.log; *.sql',
lastRecrodTime: 'Last execution time',
failedFilter: 'Failed Task Filtering',
all: 'all',
database: 'database',
@ -165,7 +167,7 @@ export default {
exclusionRules: 'Exclusive rule',
url: 'URL Address',
target: 'Target',
retainDays: 'Retain days',
retainCopies: 'Retain copies',
cronSpecRule: 'Please enter a correct lifecycle',
perMonth: 'Per monthly',
perWeek: 'Per week',
@ -339,6 +341,7 @@ export default {
retryPassword: 'Confirm password',
backup: 'Backup',
noTypeForCreate: 'No backup type is currently created',
serverDisk: 'Server disks',
OSS: 'Ali OSS',
S3: 'Amazon S3',
@ -359,7 +362,8 @@ export default {
safeEntrance: 'Security entrance',
safeEntranceHelper:
'Panel management portal. You can log in to the panel only through a specified security portal, for example: onepanel',
passwordTimeout: 'Expiration Time',
expirationTime: 'Expiration Time',
expiredHelper: 'The current password has expired. Please change the password again.',
timeoutHelper:
'[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password',
complexity: 'Complexity verification',

View file

@ -148,7 +148,9 @@ export default {
taskType: '任务类型',
shell: 'Shell 脚本',
website: '备份网站',
rulesHelper: '压缩排除规则( ; 号为分隔符)例如 \n*.log;*.sql',
failedFilter: '失败任务过滤',
lastRecrodTime: '上次执行时间',
all: '所有',
database: '备份数据库',
missBackupAccount: '未能找到备份账号',
@ -162,7 +164,7 @@ export default {
exclusionRules: '排除规则',
url: 'URL 地址',
target: '备份到',
retainDays: '保留天',
retainCopies: '保留份',
cronSpecRule: '请输入正确的执行周期',
perMonth: '每月',
perWeek: '每周',
@ -335,6 +337,7 @@ export default {
retryPassword: '确认密码',
backup: '备份',
noTypeForCreate: '当前无可创建备份类型',
serverDisk: '服务器磁盘',
OSS: '阿里云 OSS',
S3: '亚马逊 S3 云存储',
@ -353,7 +356,8 @@ export default {
portHelper: '建议端口范围8888 - 65535注意有安全组的服务器请提前在安全组放行新端口',
safeEntrance: '安全入口',
safeEntranceHelper: '面板管理入口设置后只能通过指定安全入口登录面板: onepanel',
passwordTimeout: '密码过期时间',
expirationTime: '密码过期时间',
expiredHelper: '当前密码已过期请重新修改密码',
timeoutHelper: ' {0} 天后 面板密码即将过期过期后需要重新设置密码',
complexity: '密码复杂度验证',
complexityHelper: '密码必须满足密码长度大于 8 位且包含字母数字及特殊字符',

View file

@ -19,6 +19,16 @@ const settingRouter = {
key: 'Setting',
},
},
{
path: '/expired',
name: 'Expired',
hidden: true,
component: () => import('@/views/setting/expired.vue'),
meta: {
requiresAuth: true,
key: 'Expired',
},
},
],
};

View file

@ -55,7 +55,12 @@
{{ $t('cronjob.handle') }}
</template>
</el-table-column>
<el-table-column :label="$t('cronjob.retainDays')" prop="retainDays" />
<el-table-column :label="$t('cronjob.retainCopies')" prop="retainCopies" />
<el-table-column :label="$t('cronjob.lastRecrodTime')" prop="lastRecrodTime">
<template #default="{ row }">
{{ row.lastRecrodTime }}
</template>
</el-table-column>
<el-table-column :label="$t('cronjob.target')" prop="targetDir">
<template #default="{ row }">
{{ loadBackupName(row.targetDir) }}
@ -88,13 +93,13 @@ const switchState = ref<boolean>(false);
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 5,
pageSize: 10,
total: 0,
});
const logSearch = reactive({
page: 1,
pageSize: 5,
pageSize: 10,
});
const weekOptions = [
{ label: i18n.global.t('cronjob.monday'), value: 1 },
@ -133,7 +138,7 @@ const onOpenDialog = async (
day: 1,
hour: 2,
minute: 3,
retainDays: 7,
retainCopies: 7,
},
) => {
let params = {
@ -195,7 +200,7 @@ const buttons = [
},
},
{
label: i18n.global.t('commons.button.log'),
label: i18n.global.t('commons.button.view'),
icon: 'Clock',
click: (row: Cronjob.CronjobInfo) => {
onOpenRecordDialog(row);

View file

@ -108,8 +108,8 @@
/>
</el-select>
</el-form-item>
<el-form-item v-if="isBackup()" :label="$t('cronjob.retainDays')" prop="retainDays">
<el-input-number :min="1" :max="30" v-model.number="dialogData.rowData!.retainDays"></el-input-number>
<el-form-item v-if="isBackup()" :label="$t('cronjob.retainCopies')" prop="retainCopies">
<el-input-number :min="1" :max="30" v-model.number="dialogData.rowData!.retainCopies"></el-input-number>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url') + 'URL'" prop="url">
@ -124,6 +124,7 @@
<el-input
style="width: 100%"
type="textarea"
:placeholder="$t('cronjob.rulesHelper')"
:autosize="{ minRows: 3, maxRows: 6 }"
clearable
v-model="dialogData.rowData!.exclusionRules"
@ -265,7 +266,7 @@ const rules = reactive({
url: [Rules.requiredInput],
sourceDir: [Rules.requiredSelect],
targetDirID: [Rules.requiredSelect, Rules.number],
retainDays: [Rules.number],
retainCopies: [Rules.number],
});
type FormInstance = InstanceType<typeof ElForm>;

View file

@ -138,8 +138,8 @@
</el-form-item>
</el-col>
<el-col :span="8" v-if="isBackup()">
<el-form-item :label="$t('cronjob.retainDays')">
{{ dialogData.rowData!.retainDays }}
<el-form-item :label="$t('cronjob.retainCopies')">
{{ dialogData.rowData!.retainCopies }}
</el-form-item>
</el-col>
<el-col :span="8" v-if="dialogData.rowData!.type === 'curl'">
@ -152,9 +152,12 @@
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
>
<el-form-item :label="$t('cronjob.exclusionRules')">
<div v-for="item in dialogData.rowData!.exclusionRules.split(';')" :key="item">
<el-tag>{{ item }}</el-tag>
<div v-if="dialogData.rowData!.exclusionRules">
<div v-for="item in dialogData.rowData!.exclusionRules.split(';')" :key="item">
<el-tag>{{ item }}</el-tag>
</div>
</div>
<span v-else>-</span>
</el-form-item>
</el-col>
</el-row>
@ -191,30 +194,32 @@
<el-row>
<el-col :span="24">
<el-form-item :label="$t('commons.table.records')">
<span
style="color: red"
v-if="currentRecord?.records! === 'errRecord' || currentRecord?.records! === 'errHandle'|| currentRecord?.records! === 'noRecord'"
>
<span style="color: red" v-if="currentRecord?.status! === 'Failed'">
{{ currentRecord?.message }}
</span>
<el-popover
v-else
placement="right"
:width="600"
trigger="click"
style="white-space: pre-wrap"
>
<div style="margin-left: 20px; max-height: 400px; overflow: auto">
<span style="white-space: pre-wrap">
{{ currentRecordDetail }}
</span>
</div>
<template #reference>
<el-button type="primary" link @click="loadRecord(currentRecord?.records!)">
{{ $t('commons.button.expand') }}
</el-button>
</template>
</el-popover>
<div v-else>
<el-popover
placement="right"
:width="600"
trigger="click"
style="white-space: pre-wrap"
>
<div style="margin-left: 20px; max-height: 400px; overflow: auto">
<span style="white-space: pre-wrap">
{{ currentRecordDetail }}
</span>
</div>
<template #reference>
<el-button
type="primary"
link
@click="loadRecord(currentRecord?.records!)"
>
{{ $t('commons.button.expand') }}
</el-button>
</template>
</el-popover>
</div>
</el-form-item>
</el-col>
</el-row>

View file

@ -0,0 +1,132 @@
<template>
<div>
<el-card style="margin-top: 20px">
<template #header>
<div class="card-header">
<span style="font-size: 14px; font-weight: 500">当前密码已过期请重新修改密码</span>
</div>
</template>
<el-row>
<el-col :span="1"><br /></el-col>
<el-col :span="10">
<el-form
:model="passForm"
ref="passFormRef"
:rules="passRules"
label-position="left"
label-width="160px"
>
<el-form-item :label="$t('setting.oldPassword')" prop="oldPassword">
<el-input type="password" show-password clearable v-model="passForm.oldPassword" />
</el-form-item>
<el-form-item
v-if="settingForm.complexityVerification === 'disable'"
:label="$t('setting.newPassword')"
prop="newPassword"
>
<el-input type="password" show-password clearable v-model="passForm.newPassword" />
</el-form-item>
<el-form-item
v-if="settingForm.complexityVerification === 'enable'"
:label="$t('setting.newPassword')"
prop="newPasswordComplexity"
>
<el-input
type="password"
show-password
clearable
v-model="passForm.newPasswordComplexity"
/>
</el-form-item>
<el-form-item :label="$t('setting.retryPassword')" prop="retryPassword">
<el-input type="password" show-password clearable v-model="passForm.retryPassword" />
</el-form-item>
<el-form-item>
<el-button @click="submitChangePassword(passFormRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { Setting } from '@/api/interface/setting';
import { getSettingInfo, handleExpired } from '@/api/modules/setting';
import { ElForm, ElMessage } from 'element-plus';
import i18n from '@/lang';
import { Rules } from '@/global/form-rules';
import router from '@/routers';
let settingForm = reactive<Setting.SettingInfo>({
userName: '',
password: '',
email: '',
sessionTimeout: 86400,
localTime: '',
panelName: '',
theme: '',
language: '',
serverPort: 8888,
securityEntrance: '',
expirationTime: '',
complexityVerification: 'enable',
mfaStatus: '',
mfaSecret: '',
monitorStatus: '',
monitorStoreDays: 30,
messageType: '',
emailVars: '',
weChatVars: '',
dingVars: '',
});
type FormInstance = InstanceType<typeof ElForm>;
const passFormRef = ref<FormInstance>();
const passRules = reactive({
oldPassword: [Rules.requiredInput],
newPassword: [
Rules.requiredInput,
{ min: 6, message: i18n.global.t('commons.rule.commonPassword'), trigger: 'blur' },
],
newPasswordComplexity: [Rules.requiredInput, Rules.password],
retryPassword: [Rules.requiredInput, { validator: checkPassword, trigger: 'blur' }],
});
const passForm = reactive({
oldPassword: '',
newPassword: '',
newPasswordComplexity: '',
retryPassword: '',
});
function checkPassword(rule: any, value: any, callback: any) {
let password =
settingForm.complexityVerification === 'disable' ? passForm.newPassword : passForm.newPasswordComplexity;
if (password !== passForm.retryPassword) {
return callback(new Error(i18n.global.t('commons.rule.rePassword')));
}
callback();
}
const submitChangePassword = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let password =
settingForm.complexityVerification === 'disable' ? passForm.newPassword : passForm.newPasswordComplexity;
await handleExpired({ oldPassword: passForm.oldPassword, newPassword: password });
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
router.push({ name: 'home' });
});
};
const search = async () => {
const res = await getSettingInfo();
settingForm = res.data;
};
onMounted(() => {
search();
});
</script>

View file

@ -56,7 +56,7 @@ let form = ref<Setting.SettingInfo>({
language: '',
serverPort: 8888,
securityEntrance: '',
passwordTimeOut: '',
expirationTime: '',
complexityVerification: '',
mfaStatus: '',
mfaSecret: '',

View file

@ -198,6 +198,10 @@ const search = async () => {
const onCreate = () => {
loadOption();
if (!typeOptions.value || typeOptions.value.length === 0) {
ElMessage.info(i18n.global.t('setting.noTypeForCreate'));
return;
}
operation.value = 'create';
form.id = 0;
form.type = typeOptions.value[0].value;

View file

@ -63,11 +63,11 @@
</div>
</el-form-item>
<el-form-item
:label="$t('setting.passwordTimeout')"
prop="settingInfo.passwordTimeOut"
:label="$t('setting.expirationTime')"
prop="settingInfo.expirationTime"
:rules="Rules.requiredInput"
>
<el-input disabled v-model="form.settingInfo.passwordTimeOut">
<el-input disabled v-model="form.settingInfo.expirationTime">
<template #append>
<el-button @click="timeoutVisiable = true" icon="Collection">
{{ $t('commons.button.set') }}
@ -184,7 +184,7 @@ const form = withDefaults(defineProps<Props>(), {
settingInfo: {
serverPort: '',
securityEntrance: '',
passwordTimeOut: '',
ExpirationTime: '',
complexityVerification: '',
mfaStatus: '',
mfaSecret: '',
@ -237,14 +237,14 @@ const submitTimeout = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => {
if (!valid) return;
let time = new Date(new Date().getTime() + 3600 * 1000 * 24 * timeoutForm.days);
await updateSetting({ key: 'PasswordTimeOut', value: dateFromat(0, 0, time) });
form.settingInfo.passwordTimeOut = dateFromat(0, 0, time);
await updateSetting({ key: 'ExpirationTime', value: dateFromat(0, 0, time) });
form.settingInfo.ExpirationTime = dateFromat(0, 0, time);
timeoutVisiable.value = false;
});
};
function loadTimeOut() {
let staytimeGap = new Date(form.settingInfo.passwordTimeOut).getTime() - new Date().getTime();
let staytimeGap = new Date(form.settingInfo.ExpirationTime).getTime() - new Date().getTime();
return Math.floor(staytimeGap / (3600 * 1000 * 24));
}
</script>