- 修复「修改接入网关失败」的问题
- 完成「[功能请求]审计的历史会话建议添加“已阅”的功能」close #194 - 增加一键删除登录日志和历史会话的功能
This commit is contained in:
parent
09040c316e
commit
f1432b6886
1
go.mod
1
go.mod
@ -21,6 +21,7 @@ require (
|
|||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
|
||||||
golang.org/x/text v0.3.6
|
golang.org/x/text v0.3.6
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gorm.io/driver/mysql v1.0.3
|
gorm.io/driver/mysql v1.0.3
|
||||||
|
@ -78,8 +78,7 @@ func AccessGatewayUpdateEndpoint(c echo.Context) error {
|
|||||||
if err := accessGatewayRepository.UpdateById(&item, id); err != nil {
|
if err := accessGatewayRepository.UpdateById(&item, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
accessGatewayService.DisconnectById(id)
|
accessGatewayService.ReConnect(&item)
|
||||||
_, _ = accessGatewayService.GetGatewayAndReconnectById(id)
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"next-terminal/server/constant"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,8 +50,6 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := userRepository.FindByUsername(loginAccount.Username)
|
|
||||||
|
|
||||||
// 存储登录失败次数信息
|
// 存储登录失败次数信息
|
||||||
loginFailCountKey := c.RealIP() + loginAccount.Username
|
loginFailCountKey := c.RealIP() + loginAccount.Username
|
||||||
v, ok := cache.GlobalCache.Get(loginFailCountKey)
|
v, ok := cache.GlobalCache.Get(loginFailCountKey)
|
||||||
@ -62,6 +61,7 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "登录失败次数过多,请等待5分钟后再试")
|
return Fail(c, -1, "登录失败次数过多,请等待5分钟后再试")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user, err := userRepository.FindByUsername(loginAccount.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
count++
|
count++
|
||||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
@ -72,6 +72,10 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Status == constant.StatusDisabled {
|
||||||
|
return Fail(c, -1, "该账户已停用")
|
||||||
|
}
|
||||||
|
|
||||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||||
count++
|
count++
|
||||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
@ -172,6 +176,10 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
|||||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Status == constant.StatusDisabled {
|
||||||
|
return Fail(c, -1, "该账户已停用")
|
||||||
|
}
|
||||||
|
|
||||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||||
count++
|
count++
|
||||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
@ -206,9 +214,7 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
func LogoutEndpoint(c echo.Context) error {
|
func LogoutEndpoint(c echo.Context) error {
|
||||||
token := GetToken(c)
|
token := GetToken(c)
|
||||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
err := userService.LogoutByToken(token)
|
||||||
cache.GlobalCache.Delete(cacheKey)
|
|
||||||
err := userService.Logout(token)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
102
server/api/backup.go
Normal file
102
server/api/backup.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backup struct {
|
||||||
|
Users []model.User `json:"users"`
|
||||||
|
UserGroups []model.UserGroup `json:"user_groups"`
|
||||||
|
UserGroupMembers []model.UserGroupMember `json:"user_group_members"`
|
||||||
|
|
||||||
|
Strategies []model.Strategy `json:"strategies"`
|
||||||
|
Jobs []model.Job `json:"jobs"`
|
||||||
|
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
||||||
|
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
||||||
|
Commands []model.Command `json:"commands"`
|
||||||
|
Credentials []model.Credential `json:"credentials"`
|
||||||
|
Assets []model.Asset `json:"assets"`
|
||||||
|
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BackupExportEndpoint(c echo.Context) error {
|
||||||
|
users, err := userRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range users {
|
||||||
|
users[i].Password = ""
|
||||||
|
}
|
||||||
|
userGroups, err := userGroupRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userGroupMembers, err := userGroupRepository.FindAllUserGroupMembers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
strategies, err := strategyRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jobs, err := jobRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessSecurities, err := accessSecurityRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessGateways, err := accessGatewayRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
commands, err := commandRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
credentials, err := credentialRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
assets, err := assetRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resourceSharers, err := resourceSharerRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
backup := Backup{
|
||||||
|
Users: users,
|
||||||
|
UserGroups: userGroups,
|
||||||
|
UserGroupMembers: userGroupMembers,
|
||||||
|
Strategies: strategies,
|
||||||
|
Jobs: jobs,
|
||||||
|
AccessSecurities: accessSecurities,
|
||||||
|
AccessGateways: accessGateways,
|
||||||
|
Commands: commands,
|
||||||
|
Credentials: credentials,
|
||||||
|
Assets: assets,
|
||||||
|
ResourceSharers: resourceSharers,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(backup)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=next-terminal_backup_%s.json", time.Now().Format("20060102150405")))
|
||||||
|
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(jsonBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BackupImportEndpoint(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,9 +4,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"next-terminal/server/global/cache"
|
|
||||||
"next-terminal/server/log"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,24 +28,26 @@ func LoginLogPagingEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
func LoginLogDeleteEndpoint(c echo.Context) error {
|
func LoginLogDeleteEndpoint(c echo.Context) error {
|
||||||
ids := c.Param("id")
|
ids := c.Param("id")
|
||||||
split := strings.Split(ids, ",")
|
tokens := strings.Split(ids, ",")
|
||||||
for i := range split {
|
if err := userService.DeleteLoginLogs(tokens); err != nil {
|
||||||
token := split[i]
|
|
||||||
cache.GlobalCache.Delete(token)
|
|
||||||
if err := userService.Logout(token); err != nil {
|
|
||||||
log.WithError(err).Error("Cache Delete Failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := loginLogRepository.DeleteByIdIn(split); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//func LoginLogClearEndpoint(c echo.Context) error {
|
func LoginLogClearEndpoint(c echo.Context) error {
|
||||||
// loginLogs, err := loginLogRepository.FindAliveLoginLogs()
|
loginLogs, err := loginLogRepository.FindAllLoginLogs()
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
//}
|
var tokens = make([]string, 0)
|
||||||
|
for i := range loginLogs {
|
||||||
|
tokens = append(tokens, loginLogs[i].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userService.DeleteLoginLogs(tokens); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
@ -110,6 +110,7 @@ func SetupRoutes(db *gorm.DB) *echo.Echo {
|
|||||||
users.POST("", UserCreateEndpoint)
|
users.POST("", UserCreateEndpoint)
|
||||||
users.GET("/paging", UserPagingEndpoint)
|
users.GET("/paging", UserPagingEndpoint)
|
||||||
users.PUT("/:id", UserUpdateEndpoint)
|
users.PUT("/:id", UserUpdateEndpoint)
|
||||||
|
users.PATCH("/:id/status", UserUpdateStatusEndpoint)
|
||||||
users.DELETE("/:id", UserDeleteEndpoint)
|
users.DELETE("/:id", UserDeleteEndpoint)
|
||||||
users.GET("/:id", UserGetEndpoint)
|
users.GET("/:id", UserGetEndpoint)
|
||||||
users.POST("/:id/change-password", UserChangePasswordEndpoint)
|
users.POST("/:id/change-password", UserChangePasswordEndpoint)
|
||||||
@ -169,6 +170,10 @@ func SetupRoutes(db *gorm.DB) *echo.Echo {
|
|||||||
sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
|
sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
|
||||||
sessions.GET("/:id/recording", Admin(SessionRecordingEndpoint))
|
sessions.GET("/:id/recording", Admin(SessionRecordingEndpoint))
|
||||||
sessions.GET("/:id", Admin(SessionGetEndpoint))
|
sessions.GET("/:id", Admin(SessionGetEndpoint))
|
||||||
|
sessions.POST("/:id/reviewed", Admin(SessionReviewedEndpoint))
|
||||||
|
sessions.POST("/:id/unreviewed", Admin(SessionUnViewedEndpoint))
|
||||||
|
sessions.POST("/clear", Admin(SessionClearEndpoint))
|
||||||
|
sessions.POST("/reviewed", Admin(SessionReviewedAllEndpoint))
|
||||||
|
|
||||||
sessions.POST("", SessionCreateEndpoint)
|
sessions.POST("", SessionCreateEndpoint)
|
||||||
sessions.POST("/:id/connect", SessionConnectEndpoint)
|
sessions.POST("/:id/connect", SessionConnectEndpoint)
|
||||||
@ -195,7 +200,7 @@ func SetupRoutes(db *gorm.DB) *echo.Echo {
|
|||||||
{
|
{
|
||||||
loginLogs.GET("/paging", LoginLogPagingEndpoint)
|
loginLogs.GET("/paging", LoginLogPagingEndpoint)
|
||||||
loginLogs.DELETE("/:id", LoginLogDeleteEndpoint)
|
loginLogs.DELETE("/:id", LoginLogDeleteEndpoint)
|
||||||
//loginLogs.DELETE("/clear", LoginLogClearEndpoint)
|
loginLogs.POST("/clear", LoginLogClearEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.GET("/properties", Admin(PropertyGetEndpoint))
|
e.GET("/properties", Admin(PropertyGetEndpoint))
|
||||||
@ -268,6 +273,12 @@ func SetupRoutes(db *gorm.DB) *echo.Echo {
|
|||||||
accessGateways.POST("/:id/reconnect", AccessGatewayReconnectEndpoint)
|
accessGateways.POST("/:id/reconnect", AccessGatewayReconnectEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backup := e.Group("/backup", Admin)
|
||||||
|
{
|
||||||
|
backup.GET("/export", BackupExportEndpoint)
|
||||||
|
backup.POST("/import", BackupImportEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ func SecurityCreateEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReloadAccessSecurity() error {
|
func ReloadAccessSecurity() error {
|
||||||
rules, err := accessSecurityRepository.FindAllAccessSecurities()
|
rules, err := accessSecurityRepository.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,9 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
clientIp := c.QueryParam("clientIp")
|
clientIp := c.QueryParam("clientIp")
|
||||||
assetId := c.QueryParam("assetId")
|
assetId := c.QueryParam("assetId")
|
||||||
protocol := c.QueryParam("protocol")
|
protocol := c.QueryParam("protocol")
|
||||||
|
reviewed := c.QueryParam("reviewed")
|
||||||
|
|
||||||
items, total, err := sessionRepository.Find(pageIndex, pageSize, status, userId, clientIp, assetId, protocol)
|
items, total, err := sessionRepository.Find(pageIndex, pageSize, status, userId, clientIp, assetId, protocol, reviewed)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -69,9 +70,8 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SessionDeleteEndpoint(c echo.Context) error {
|
func SessionDeleteEndpoint(c echo.Context) error {
|
||||||
sessionIds := c.Param("id")
|
sessionIds := strings.Split(c.Param("id"), ",")
|
||||||
split := strings.Split(sessionIds, ",")
|
err := sessionRepository.DeleteByIds(sessionIds)
|
||||||
err := sessionRepository.DeleteByIds(split)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -79,6 +79,37 @@ func SessionDeleteEndpoint(c echo.Context) error {
|
|||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SessionClearEndpoint(c echo.Context) error {
|
||||||
|
err := sessionService.ClearOfflineSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionReviewedEndpoint(c echo.Context) error {
|
||||||
|
sessionIds := strings.Split(c.Param("id"), ",")
|
||||||
|
if err := sessionRepository.UpdateReadByIds(true, sessionIds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionUnViewedEndpoint(c echo.Context) error {
|
||||||
|
sessionIds := strings.Split(c.Param("id"), ",")
|
||||||
|
if err := sessionRepository.UpdateReadByIds(false, sessionIds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionReviewedAllEndpoint(c echo.Context) error {
|
||||||
|
if err := sessionService.ReviewedAll(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func SessionConnectEndpoint(c echo.Context) error {
|
func SessionConnectEndpoint(c echo.Context) error {
|
||||||
sessionId := c.Param("id")
|
sessionId := c.Param("id")
|
||||||
|
|
||||||
@ -299,6 +330,7 @@ func SessionCreateEndpoint(c echo.Context) error {
|
|||||||
Edit: edit,
|
Edit: edit,
|
||||||
StorageId: storageId,
|
StorageId: storageId,
|
||||||
AccessGatewayId: asset.AccessGatewayId,
|
AccessGatewayId: asset.AccessGatewayId,
|
||||||
|
Reviewed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if asset.AccountType == "credential" {
|
if asset.AccountType == "credential" {
|
||||||
@ -645,6 +677,7 @@ func SessionRecordingEndpoint(c echo.Context) error {
|
|||||||
} else {
|
} else {
|
||||||
recording = s.Recording + "/recording"
|
recording = s.Recording + "/recording"
|
||||||
}
|
}
|
||||||
|
_ = sessionRepository.UpdateReadByIds(true, []string{sessionId})
|
||||||
|
|
||||||
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
|
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
|
||||||
return c.File(recording)
|
return c.File(recording)
|
||||||
|
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"next-terminal/server/constant"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ func UserCreateEndpoint(c echo.Context) (err error) {
|
|||||||
|
|
||||||
item.ID = utils.UUID()
|
item.ID = utils.UUID()
|
||||||
item.Created = utils.NowJsonTime()
|
item.Created = utils.NowJsonTime()
|
||||||
|
item.Status = constant.StatusEnabled
|
||||||
|
|
||||||
if err := userRepository.Create(&item); err != nil {
|
if err := userRepository.Create(&item); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -82,6 +84,21 @@ func UserUpdateEndpoint(c echo.Context) error {
|
|||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserUpdateStatusEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
status := c.QueryParam("status")
|
||||||
|
account, _ := GetCurrentAccount(c)
|
||||||
|
if account.ID == id {
|
||||||
|
return Fail(c, -1, "不能操作自身账户")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userService.UpdateStatusById(id, status); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func UserDeleteEndpoint(c echo.Context) error {
|
func UserDeleteEndpoint(c echo.Context) error {
|
||||||
ids := c.Param("id")
|
ids := c.Param("id")
|
||||||
account, found := GetCurrentAccount(c)
|
account, found := GetCurrentAccount(c)
|
||||||
@ -94,26 +111,10 @@ func UserDeleteEndpoint(c echo.Context) error {
|
|||||||
if account.ID == userId {
|
if account.ID == userId {
|
||||||
return Fail(c, -1, "不允许删除自身账户")
|
return Fail(c, -1, "不允许删除自身账户")
|
||||||
}
|
}
|
||||||
user, err := userRepository.FindById(userId)
|
// 下线该用户
|
||||||
if err != nil {
|
if err := userService.LogoutById(userId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 将用户强制下线
|
|
||||||
loginLogs, err := loginLogRepository.FindAliveLoginLogsByUsername(user.Username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := range loginLogs {
|
|
||||||
token := loginLogs[j].ID
|
|
||||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
|
||||||
cache.GlobalCache.Delete(cacheKey)
|
|
||||||
if err := userService.Logout(token); err != nil {
|
|
||||||
log.WithError(err).WithField("id:", token).Error("Cache Deleted Error")
|
|
||||||
return Fail(c, 500, "强制下线错误")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除用户
|
// 删除用户
|
||||||
if err := userRepository.DeleteById(userId); err != nil {
|
if err := userRepository.DeleteById(userId); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -56,6 +56,9 @@ const (
|
|||||||
|
|
||||||
TypeUser = "user" // 普通用户
|
TypeUser = "user" // 普通用户
|
||||||
TypeAdmin = "admin" // 管理员
|
TypeAdmin = "admin" // 管理员
|
||||||
|
|
||||||
|
StatusEnabled = "enabled"
|
||||||
|
StatusDisabled = "disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode}
|
var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode}
|
||||||
|
@ -34,6 +34,7 @@ type Session struct {
|
|||||||
CreateDir string `gorm:"type:varchar(1)" json:"createDir"`
|
CreateDir string `gorm:"type:varchar(1)" json:"createDir"`
|
||||||
StorageId string `gorm:"type:varchar(36)" json:"storageId"`
|
StorageId string `gorm:"type:varchar(36)" json:"storageId"`
|
||||||
AccessGatewayId string `gorm:"type:varchar(36)" json:"accessGatewayId"`
|
AccessGatewayId string `gorm:"type:varchar(36)" json:"accessGatewayId"`
|
||||||
|
Reviewed bool `gorm:"type:tinyint(1)" json:"reviewed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Session) TableName() string {
|
func (r *Session) TableName() string {
|
||||||
@ -61,6 +62,7 @@ type SessionForPage struct {
|
|||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
|
Reviewed bool `json:"reviewed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionForAccess struct {
|
type SessionForAccess struct {
|
||||||
|
@ -11,7 +11,7 @@ type User struct {
|
|||||||
Nickname string `gorm:"type:varchar(500)" json:"nickname"`
|
Nickname string `gorm:"type:varchar(500)" json:"nickname"`
|
||||||
TOTPSecret string `json:"-"`
|
TOTPSecret string `json:"-"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
Enabled bool `json:"enabled"`
|
Status string `gorm:"type:varchar(10)" json:"status"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Created utils.JsonTime `json:"created"`
|
||||||
Type string `gorm:"type:varchar(20)" json:"type"`
|
Type string `gorm:"type:varchar(20)" json:"type"`
|
||||||
Mail string `gorm:"type:varchar(500)" json:"mail"`
|
Mail string `gorm:"type:varchar(500)" json:"mail"`
|
||||||
@ -24,7 +24,7 @@ type UserForPage struct {
|
|||||||
TOTPSecret string `json:"totpSecret"`
|
TOTPSecret string `json:"totpSecret"`
|
||||||
Mail string `json:"mail"`
|
Mail string `json:"mail"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
Enabled bool `json:"enabled"`
|
Status string `json:"status"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Created utils.JsonTime `json:"created"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SharerAssetCount int64 `json:"sharerAssetCount"`
|
SharerAssetCount int64 `json:"sharerAssetCount"`
|
||||||
|
@ -75,11 +75,6 @@ func (r AccessGatewayRepository) FindById(id string) (o model.AccessGateway, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r AccessGatewayRepository) FindAll() (o []model.AccessGateway, err error) {
|
func (r AccessGatewayRepository) FindAll() (o []model.AccessGateway, err error) {
|
||||||
t := model.AccessGateway{}
|
err = r.DB.Find(&o).Error
|
||||||
db := r.DB.Table(t.TableName())
|
|
||||||
err = db.Find(&o).Error
|
|
||||||
if o == nil {
|
|
||||||
o = make([]model.AccessGateway, 0)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,8 @@ func NewAccessSecurityRepository(db *gorm.DB) *AccessSecurityRepository {
|
|||||||
return accessSecurityRepository
|
return accessSecurityRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r AccessSecurityRepository) FindAllAccessSecurities() (o []model.AccessSecurity, err error) {
|
func (r AccessSecurityRepository) FindAll() (o []model.AccessSecurity, err error) {
|
||||||
db := r.DB
|
err = r.DB.Order("priority asc").Find(&o).Error
|
||||||
err = db.Order("priority asc").Find(&o).Error
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,3 +94,8 @@ func (r CommandRepository) FindByUser(account model.User) (o []model.CommandForP
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r CommandRepository) FindAll() (o []model.Command, err error) {
|
||||||
|
err = r.DB.Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -54,6 +54,11 @@ func (r LoginLogRepository) FindAliveLoginLogs() (o []model.LoginLog, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r LoginLogRepository) FindAllLoginLogs() (o []model.LoginLog, err error) {
|
||||||
|
err = r.DB.Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (r LoginLogRepository) FindAliveLoginLogsByUsername(username string) (o []model.LoginLog, err error) {
|
func (r LoginLogRepository) FindAliveLoginLogsByUsername(username string) (o []model.LoginLog, err error) {
|
||||||
err = r.DB.Where("state = '1' and logout_time is null and username = ?", username).Find(&o).Error
|
err = r.DB.Where("state = '1' and logout_time is null and username = ?", username).Find(&o).Error
|
||||||
return
|
return
|
||||||
|
@ -218,3 +218,8 @@ func (r *ResourceSharerRepository) Find(resourceId, resourceType, userId, userGr
|
|||||||
err = db.Find(&resourceSharers).Error
|
err = db.Find(&resourceSharers).Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ResourceSharerRepository) FindAll() (o []model.ResourceSharer, err error) {
|
||||||
|
err = r.DB.Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -23,14 +23,14 @@ func NewSessionRepository(db *gorm.DB) *SessionRepository {
|
|||||||
return sessionRepository
|
return sessionRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SessionRepository) Find(pageIndex, pageSize int, status, userId, clientIp, assetId, protocol string) (results []model.SessionForPage, total int64, err error) {
|
func (r SessionRepository) Find(pageIndex, pageSize int, status, userId, clientIp, assetId, protocol, reviewed string) (results []model.SessionForPage, total int64, err error) {
|
||||||
|
|
||||||
db := r.DB
|
db := r.DB
|
||||||
var params []interface{}
|
var params []interface{}
|
||||||
|
|
||||||
params = append(params, status)
|
params = append(params, status)
|
||||||
|
|
||||||
itemSql := "SELECT s.id,s.mode, s.protocol,s.recording, s.connection_id, s.asset_id, s.creator, s.client_ip, s.width, s.height, s.ip, s.port, s.username, s.status, s.connected_time, s.disconnected_time,s.code, s.message, a.name AS asset_name, u.nickname AS creator_name FROM sessions s LEFT JOIN assets a ON s.asset_id = a.id LEFT JOIN users u ON s.creator = u.id WHERE s.STATUS = ? "
|
itemSql := "SELECT s.id,s.mode, s.protocol,s.recording, s.connection_id, s.asset_id, s.creator, s.client_ip, s.width, s.height, s.ip, s.port, s.username, s.status, s.connected_time, s.disconnected_time,s.code,s.reviewed, s.message, a.name AS asset_name, u.nickname AS creator_name FROM sessions s LEFT JOIN assets a ON s.asset_id = a.id LEFT JOIN users u ON s.creator = u.id WHERE s.STATUS = ? "
|
||||||
countSql := "select count(*) from sessions as s where s.status = ? "
|
countSql := "select count(*) from sessions as s where s.status = ? "
|
||||||
|
|
||||||
if len(userId) > 0 {
|
if len(userId) > 0 {
|
||||||
@ -57,6 +57,13 @@ func (r SessionRepository) Find(pageIndex, pageSize int, status, userId, clientI
|
|||||||
params = append(params, protocol)
|
params = append(params, protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reviewed != "" {
|
||||||
|
bReviewed := reviewed == "true"
|
||||||
|
itemSql += " and s.reviewed = ?"
|
||||||
|
countSql += " and s.reviewed = ?"
|
||||||
|
params = append(params, bReviewed)
|
||||||
|
}
|
||||||
|
|
||||||
params = append(params, (pageIndex-1)*pageSize, pageSize)
|
params = append(params, (pageIndex-1)*pageSize, pageSize)
|
||||||
itemSql += " order by s.connected_time desc LIMIT ?, ?"
|
itemSql += " order by s.connected_time desc LIMIT ?, ?"
|
||||||
|
|
||||||
@ -209,3 +216,13 @@ func (r SessionRepository) OverviewAccess(account model.User) (o []model.Session
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r SessionRepository) UpdateReadByIds(reviewed bool, ids []string) error {
|
||||||
|
sql := "update sessions set reviewed = ? where id in ?"
|
||||||
|
return r.DB.Exec(sql, reviewed, ids).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SessionRepository) FindAllUnReviewed() (o []model.Session, err error) {
|
||||||
|
err = r.DB.Where("reviewed = false or reviewed is null").Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -16,15 +16,13 @@ func NewUserRepository(db *gorm.DB) *UserRepository {
|
|||||||
return userRepository
|
return userRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r UserRepository) FindAll() (o []model.User) {
|
func (r UserRepository) FindAll() (o []model.User, err error) {
|
||||||
if r.DB.Find(&o).Error != nil {
|
err = r.DB.Find(&o).Error
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r UserRepository) Find(pageIndex, pageSize int, username, nickname, mail, order, field string, account model.User) (o []model.UserForPage, total int64, err error) {
|
func (r UserRepository) Find(pageIndex, pageSize int, username, nickname, mail, order, field string, account model.User) (o []model.UserForPage, total int64, err error) {
|
||||||
db := r.DB.Table("users").Select("users.id,users.username,users.nickname,users.mail,users.online,users.enabled,users.created,users.type, count(resource_sharers.user_id) as sharer_asset_count, users.totp_secret").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id")
|
db := r.DB.Table("users").Select("users.id,users.username,users.nickname,users.mail,users.online,users.created,users.type,users.status, count(resource_sharers.user_id) as sharer_asset_count, users.totp_secret").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id")
|
||||||
dbCounter := r.DB.Table("users")
|
dbCounter := r.DB.Table("users")
|
||||||
|
|
||||||
if constant.TypeUser == account.Type {
|
if constant.TypeUser == account.Type {
|
||||||
|
@ -16,10 +16,8 @@ func NewUserGroupRepository(db *gorm.DB) *UserGroupRepository {
|
|||||||
return userGroupRepository
|
return userGroupRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r UserGroupRepository) FindAll() (o []model.UserGroup) {
|
func (r UserGroupRepository) FindAll() (o []model.UserGroup, err error) {
|
||||||
if r.DB.Find(&o).Error != nil {
|
err = r.DB.Find(&o).Error
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,3 +139,8 @@ func AddUserGroupMembers(tx *gorm.DB, userIds []string, userGroupId string) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r UserGroupRepository) FindAllUserGroupMembers() (o []model.UserGroupMember, err error) {
|
||||||
|
err = r.DB.Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -39,9 +39,12 @@ func (r AccessGatewayService) ReConnectAll() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(gateways) > 0 {
|
||||||
for i := range gateways {
|
for i := range gateways {
|
||||||
r.ReConnect(&gateways[i])
|
r.ReConnect(&gateways[i])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,3 +37,41 @@ func (r SessionService) FixSessionState() error {
|
|||||||
func (r SessionService) EmptyPassword() error {
|
func (r SessionService) EmptyPassword() error {
|
||||||
return r.sessionRepository.EmptyPassword()
|
return r.sessionRepository.EmptyPassword()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r SessionService) ClearOfflineSession() error {
|
||||||
|
sessions, err := r.sessionRepository.FindByStatus(constant.Disconnected)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sessionIds := make([]string, 0)
|
||||||
|
for i := range sessions {
|
||||||
|
sessionIds = append(sessionIds, sessions[i].ID)
|
||||||
|
}
|
||||||
|
return r.sessionRepository.DeleteByIds(sessionIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SessionService) ReviewedAll() error {
|
||||||
|
sessions, err := r.sessionRepository.FindAllUnReviewed()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var sessionIds = make([]string, 0)
|
||||||
|
total := len(sessions)
|
||||||
|
for i := range sessions {
|
||||||
|
sessionIds = append(sessionIds, sessions[i].ID)
|
||||||
|
if i >= 100 && i%100 == 0 {
|
||||||
|
if err := r.sessionRepository.UpdateReadByIds(true, sessionIds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sessionIds = nil
|
||||||
|
} else {
|
||||||
|
if i == total-1 {
|
||||||
|
if err := r.sessionRepository.UpdateReadByIds(true, sessionIds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -26,7 +26,10 @@ func NewStorageService(storageRepository *repository.StorageRepository, userRepo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r StorageService) InitStorages() error {
|
func (r StorageService) InitStorages() error {
|
||||||
users := r.userRepository.FindAll()
|
users, err := r.userRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for i := range users {
|
for i := range users {
|
||||||
userId := users[i].ID
|
userId := users[i].ID
|
||||||
_, err := r.storageRepository.FindByOwnerIdAndDefault(userId, true)
|
_, err := r.storageRepository.FindByOwnerIdAndDefault(userId, true)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"next-terminal/server/global/cache"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"next-terminal/server/constant"
|
"next-terminal/server/constant"
|
||||||
@ -21,7 +22,10 @@ func NewUserService(userRepository *repository.UserRepository, loginLogRepositor
|
|||||||
|
|
||||||
func (r UserService) InitUser() (err error) {
|
func (r UserService) InitUser() (err error) {
|
||||||
|
|
||||||
users := r.userRepository.FindAll()
|
users, err := r.userRepository.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
initPassword := "admin"
|
initPassword := "admin"
|
||||||
@ -37,6 +41,7 @@ func (r UserService) InitUser() (err error) {
|
|||||||
Nickname: "超级管理员",
|
Nickname: "超级管理员",
|
||||||
Type: constant.TypeAdmin,
|
Type: constant.TypeAdmin,
|
||||||
Created: utils.NowJsonTime(),
|
Created: utils.NowJsonTime(),
|
||||||
|
Status: constant.StatusEnabled,
|
||||||
}
|
}
|
||||||
if err := r.userRepository.Create(&user); err != nil {
|
if err := r.userRepository.Create(&user); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -83,12 +88,14 @@ func (r UserService) FixUserOnlineState() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r UserService) Logout(token string) (err error) {
|
func (r UserService) LogoutByToken(token string) (err error) {
|
||||||
loginLog, err := r.loginLogRepository.FindById(token)
|
loginLog, err := r.loginLogRepository.FindById(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("登录日志「%v」获取失败", token)
|
log.Warnf("登录日志「%v」获取失败", token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cacheKey := r.BuildCacheKeyByToken(token)
|
||||||
|
cache.GlobalCache.Delete(cacheKey)
|
||||||
|
|
||||||
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
|
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
|
||||||
err = r.loginLogRepository.Update(loginLogForUpdate)
|
err = r.loginLogRepository.Update(loginLogForUpdate)
|
||||||
@ -107,6 +114,26 @@ func (r UserService) Logout(token string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r UserService) LogoutById(id string) error {
|
||||||
|
user, err := r.userRepository.FindById(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
username := user.Username
|
||||||
|
loginLogs, err := r.loginLogRepository.FindAliveLoginLogsByUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range loginLogs {
|
||||||
|
token := loginLogs[j].ID
|
||||||
|
if err := r.LogoutByToken(token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r UserService) BuildCacheKeyByToken(token string) string {
|
func (r UserService) BuildCacheKeyByToken(token string) string {
|
||||||
cacheKey := strings.Join([]string{constant.Token, token}, ":")
|
cacheKey := strings.Join([]string{constant.Token, token}, ":")
|
||||||
return cacheKey
|
return cacheKey
|
||||||
@ -121,9 +148,36 @@ func (r UserService) OnEvicted(key string, value interface{}) {
|
|||||||
if strings.HasPrefix(key, constant.Token) {
|
if strings.HasPrefix(key, constant.Token) {
|
||||||
token := r.GetTokenFormCacheKey(key)
|
token := r.GetTokenFormCacheKey(key)
|
||||||
log.Debugf("用户Token「%v」过期", token)
|
log.Debugf("用户Token「%v」过期", token)
|
||||||
err := r.Logout(token)
|
err := r.LogoutByToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("退出登录失败 %v", err)
|
log.Errorf("退出登录失败 %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r UserService) UpdateStatusById(id string, status string) error {
|
||||||
|
if constant.StatusDisabled == status {
|
||||||
|
// 将该用户下线
|
||||||
|
if err := r.LogoutById(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u := model.User{
|
||||||
|
ID: id,
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
return r.userRepository.Update(&u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r UserService) DeleteLoginLogs(tokens []string) error {
|
||||||
|
for i := range tokens {
|
||||||
|
token := tokens[i]
|
||||||
|
if err := r.LogoutByToken(token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.loginLogRepository.DeleteById(token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -49,6 +49,29 @@ func NewSshClient(ip string, port int, username, password, privateKey, passphras
|
|||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", ip, port)
|
addr := fmt.Sprintf("%s:%d", ip, port)
|
||||||
|
//
|
||||||
|
//socks5, err := proxy.SOCKS5("tcp", "",
|
||||||
|
// &proxy.Auth{User: "username", Password: "password"},
|
||||||
|
// &net.Dialer{
|
||||||
|
// Timeout: 30 * time.Second,
|
||||||
|
// KeepAlive: 30 * time.Second,
|
||||||
|
// },
|
||||||
|
//)
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//conn, err := socks5.Dial("tcp", addr)
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//clientConn, channels, requests, err := ssh.NewClientConn(conn, addr, config)
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//ssh.NewClient(clientConn, channels, requests)
|
||||||
|
|
||||||
return ssh.Dial("tcp", addr, config)
|
return ssh.Dial("tcp", addr, config)
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,13 @@ class AccessGateway extends Component {
|
|||||||
await this.showModal('更新接入网关', result.data);
|
await this.showModal('更新接入网关', result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconnect(id) {
|
async reconnect(id, index) {
|
||||||
|
let items = this.state.items;
|
||||||
|
try {
|
||||||
|
items[index]['reconnectLoading'] = true;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
message.info({content: '正在重连中...', key: id, duration: 5});
|
message.info({content: '正在重连中...', key: id, duration: 5});
|
||||||
let result = await request.post(`/access-gateways/${id}/reconnect`);
|
let result = await request.post(`/access-gateways/${id}/reconnect`);
|
||||||
if (result.code !== 1) {
|
if (result.code !== 1) {
|
||||||
@ -150,6 +156,12 @@ class AccessGateway extends Component {
|
|||||||
}
|
}
|
||||||
message.success({content: '重连完成。', key: id, duration: 3});
|
message.success({content: '重连完成。', key: id, duration: 3});
|
||||||
this.loadTableData(this.state.queryParams);
|
this.loadTableData(this.state.queryParams);
|
||||||
|
} finally {
|
||||||
|
items[index]['reconnectLoading'] = false;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showModal(title, obj) {
|
showModal(title, obj) {
|
||||||
@ -173,6 +185,7 @@ class AccessGateway extends Component {
|
|||||||
modalConfirmLoading: true
|
modalConfirmLoading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
if (formData.id) {
|
if (formData.id) {
|
||||||
// 向后台提交数据
|
// 向后台提交数据
|
||||||
const result = await request.put('/access-gateways/' + formData.id, formData);
|
const result = await request.put('/access-gateways/' + formData.id, formData);
|
||||||
@ -200,10 +213,11 @@ class AccessGateway extends Component {
|
|||||||
message.error('新增失败 :( ' + result.message, 10);
|
message.error('新增失败 :( ' + result.message, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalConfirmLoading: false
|
modalConfirmLoading: false
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
batchDelete = async () => {
|
batchDelete = async () => {
|
||||||
@ -340,11 +354,11 @@ class AccessGateway extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="link" size='small' loading={this.state.items[index]['execLoading']}
|
<Button type="link" size='small'
|
||||||
onClick={() => this.update(record['id'])}>编辑</Button>
|
onClick={() => this.update(record['id'])}>编辑</Button>
|
||||||
|
|
||||||
<Button type="link" size='small' loading={this.state.items[index]['execLoading']}
|
<Button type="link" size='small' loading={this.state.items[index]['reconnectLoading']}
|
||||||
onClick={() => this.reconnect(record['id'])}>重连</Button>
|
onClick={() => this.reconnect(record['id'], index)}>重连</Button>
|
||||||
|
|
||||||
<Button type="text" size='small' danger
|
<Button type="text" size='small' danger
|
||||||
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
||||||
|
@ -131,7 +131,7 @@ class Dashboard extends Component {
|
|||||||
<div style={{margin: 16, marginBottom: 0}}>
|
<div style={{margin: 16, marginBottom: 0}}>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card bordered={true} hoverable>
|
<Card bordered={true} hoverable={true}>
|
||||||
<Link to={'/user'}>
|
<Link to={'/user'}>
|
||||||
<Statistic title="在线用户" value={this.state.counter['user']}
|
<Statistic title="在线用户" value={this.state.counter['user']}
|
||||||
prefix={<UserOutlined/>}/>
|
prefix={<UserOutlined/>}/>
|
||||||
@ -139,7 +139,7 @@ class Dashboard extends Component {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card bordered={true} hoverable>
|
<Card bordered={true} hoverable={true}>
|
||||||
<Link to={'/asset'}>
|
<Link to={'/asset'}>
|
||||||
<Statistic title="资产数量" value={this.state.counter['asset']}
|
<Statistic title="资产数量" value={this.state.counter['asset']}
|
||||||
prefix={<DesktopOutlined/>}/>
|
prefix={<DesktopOutlined/>}/>
|
||||||
@ -147,7 +147,7 @@ class Dashboard extends Component {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card bordered={true} hoverable>
|
<Card bordered={true} hoverable={true}>
|
||||||
<Link to={'/credential'} hoverable>
|
<Link to={'/credential'} hoverable>
|
||||||
<Statistic title="授权凭证" value={this.state.counter['credential']}
|
<Statistic title="授权凭证" value={this.state.counter['credential']}
|
||||||
prefix={<IdcardOutlined/>}/>
|
prefix={<IdcardOutlined/>}/>
|
||||||
@ -156,7 +156,7 @@ class Dashboard extends Component {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card bordered={true} hoverable>
|
<Card bordered={true} hoverable={true}>
|
||||||
<Link to={'/online-session'}>
|
<Link to={'/online-session'}>
|
||||||
<Statistic title="在线会话" value={this.state.counter['onlineSession']}
|
<Statistic title="在线会话" value={this.state.counter['onlineSession']}
|
||||||
prefix={<LinkOutlined/>}/>
|
prefix={<LinkOutlined/>}/>
|
||||||
|
@ -20,7 +20,7 @@ import qs from "qs";
|
|||||||
import request from "../../common/request";
|
import request from "../../common/request";
|
||||||
import {formatDate, isEmpty} from "../../utils/utils";
|
import {formatDate, isEmpty} from "../../utils/utils";
|
||||||
import {message} from "antd/es";
|
import {message} from "antd/es";
|
||||||
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
|
import {ClearOutlined, DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
|
||||||
const confirm = Modal.confirm;
|
const confirm = Modal.confirm;
|
||||||
@ -152,6 +152,30 @@ class LoginLog extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearLoginLogs = async () => {
|
||||||
|
this.setState({
|
||||||
|
clearBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post('/login-logs/clear');
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功,即将跳转至登录页面。', 3);
|
||||||
|
this.setState({
|
||||||
|
selectedRowKeys: []
|
||||||
|
})
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
clearBtnLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
@ -353,6 +377,26 @@ class LoginLog extends Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="清空">
|
||||||
|
<Button type="primary" danger icon={<ClearOutlined/>}
|
||||||
|
loading={this.state.clearBtnLoading}
|
||||||
|
onClick={() => {
|
||||||
|
const title = <Text style={{color: 'red'}}
|
||||||
|
strong>您确定要清空全部的登录日志吗?</Text>;
|
||||||
|
confirm({
|
||||||
|
icon: <ExclamationCircleOutlined/>,
|
||||||
|
title: title,
|
||||||
|
content: '删除用户未注销的登录日志将会强制用户下线,当前登录的用户也会退出登录。',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: this.clearLoginLogs,
|
||||||
|
onCancel() {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
3
web/src/components/session/OfflineSession.css
Normal file
3
web/src/components/session/OfflineSession.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.unreviewed{
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
@ -20,9 +20,16 @@ import request from "../../common/request";
|
|||||||
import {differTime} from "../../utils/utils";
|
import {differTime} from "../../utils/utils";
|
||||||
import Playback from "./Playback";
|
import Playback from "./Playback";
|
||||||
import {message} from "antd/es";
|
import {message} from "antd/es";
|
||||||
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
|
import {
|
||||||
|
CheckOutlined,
|
||||||
|
ClearOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
ExclamationCircleOutlined, EyeInvisibleOutlined, EyeOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
UndoOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
import {MODE_COLORS, PROTOCOL_COLORS} from "../../common/constants";
|
import {MODE_COLORS, PROTOCOL_COLORS} from "../../common/constants";
|
||||||
|
import './OfflineSession.css'
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const confirm = Modal.confirm;
|
const confirm = Modal.confirm;
|
||||||
@ -42,7 +49,8 @@ class OfflineSession extends Component {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
protocol: '',
|
protocol: '',
|
||||||
userId: undefined,
|
userId: undefined,
|
||||||
assetId: undefined
|
assetId: undefined,
|
||||||
|
reviewed: undefined
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
playbackVisible: false,
|
playbackVisible: false,
|
||||||
@ -147,6 +155,16 @@ class OfflineSession extends Component {
|
|||||||
this.loadTableData(query);
|
this.loadTableData(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChangeByRead = reviewed => {
|
||||||
|
let query = {
|
||||||
|
...this.state.queryParams,
|
||||||
|
'pageIndex': 1,
|
||||||
|
'pageSize': this.state.queryParams.pageSize,
|
||||||
|
'reviewed': reviewed,
|
||||||
|
}
|
||||||
|
this.loadTableData(query);
|
||||||
|
}
|
||||||
|
|
||||||
handleSearchByNickname = async nickname => {
|
handleSearchByNickname = async nickname => {
|
||||||
const result = await request.get(`/users/paging?pageIndex=1&pageSize=1000&nickname=${nickname}`);
|
const result = await request.get(`/users/paging?pageIndex=1&pageSize=1000&nickname=${nickname}`);
|
||||||
if (result.code !== 1) {
|
if (result.code !== 1) {
|
||||||
@ -213,6 +231,110 @@ class OfflineSession extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAllReviewed = async () => {
|
||||||
|
this.setState({
|
||||||
|
reviewedAllBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post(`/sessions/reviewed`);
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
this.setState({
|
||||||
|
selectedRowKeys: []
|
||||||
|
})
|
||||||
|
await this.loadTableData(this.state.queryParams);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
reviewedAllBtnLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReviewed = async () => {
|
||||||
|
this.setState({
|
||||||
|
reviewedBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post(`/sessions/${this.state.selectedRowKeys.join(',')}/reviewed`);
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
this.setState({
|
||||||
|
selectedRowKeys: []
|
||||||
|
})
|
||||||
|
await this.loadTableData(this.state.queryParams);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
reviewedBtnLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUnreviewed = async () => {
|
||||||
|
this.setState({
|
||||||
|
unreviewedBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post(`/sessions/${this.state.selectedRowKeys.join(',')}/unreviewed`);
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
this.setState({
|
||||||
|
selectedRowKeys: []
|
||||||
|
})
|
||||||
|
await this.loadTableData(this.state.queryParams);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
unreviewedBtnLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
del = async (id) => {
|
||||||
|
const result = await request.delete(`/sessions/${id}`);
|
||||||
|
if (result.code === 1) {
|
||||||
|
notification['success']({
|
||||||
|
message: '提示',
|
||||||
|
description: '删除成功',
|
||||||
|
});
|
||||||
|
this.loadTableData();
|
||||||
|
} else {
|
||||||
|
notification['error']({
|
||||||
|
message: '提示',
|
||||||
|
description: result.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSession = async () => {
|
||||||
|
this.setState({
|
||||||
|
clearBtnLoading: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let result = await request.post('/sessions/clear');
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
this.setState({
|
||||||
|
selectedRowKeys: []
|
||||||
|
})
|
||||||
|
await this.loadTableData(this.state.queryParams);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({
|
||||||
|
clearBtnLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
@ -221,7 +343,7 @@ class OfflineSession extends Component {
|
|||||||
key: 'id',
|
key: 'id',
|
||||||
render: (id, record, index) => {
|
render: (id, record, index) => {
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
title: '来源IP',
|
title: '来源IP',
|
||||||
dataIndex: 'clientIp',
|
dataIndex: 'clientIp',
|
||||||
@ -312,34 +434,17 @@ class OfflineSession extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}>禁用IP</Button>
|
}}>禁用IP</Button>
|
||||||
<Button type="link" size='small' onClick={() => {
|
<Button type="link" size='small' danger onClick={() => {
|
||||||
confirm({
|
confirm({
|
||||||
title: '您确定要删除此会话吗?',
|
title: '您确定要删除此会话吗?',
|
||||||
content: '',
|
content: '',
|
||||||
okText: '确定',
|
okText: '确定',
|
||||||
okType: 'danger',
|
okType: 'danger',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk() {
|
onOk: () => {
|
||||||
del(record.id)
|
this.del(record.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const del = async (id) => {
|
|
||||||
const result = await request.delete(`/sessions/${id}`);
|
|
||||||
if (result.code === 1) {
|
|
||||||
notification['success']({
|
|
||||||
message: '提示',
|
|
||||||
description: '删除成功',
|
|
||||||
});
|
|
||||||
this.loadTableData();
|
|
||||||
} else {
|
|
||||||
notification['error']({
|
|
||||||
message: '提示',
|
|
||||||
description: result.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}}>删除</Button>
|
}}>删除</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -366,10 +471,10 @@ class OfflineSession extends Component {
|
|||||||
<Content className="site-layout-background page-content">
|
<Content className="site-layout-background page-content">
|
||||||
<div style={{marginBottom: 20}}>
|
<div style={{marginBottom: 20}}>
|
||||||
<Row justify="space-around" align="middle" gutter={24}>
|
<Row justify="space-around" align="middle" gutter={24}>
|
||||||
<Col span={8} key={1}>
|
<Col span={4} key={1}>
|
||||||
<Title level={3}>离线会话列表</Title>
|
<Title level={3}>离线会话列表</Title>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={16} key={2} style={{textAlign: 'right'}}>
|
<Col span={20} key={2} style={{textAlign: 'right'}}>
|
||||||
<Space>
|
<Space>
|
||||||
|
|
||||||
<Search
|
<Search
|
||||||
@ -380,7 +485,7 @@ class OfflineSession extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
style={{width: 150}}
|
style={{width: 140}}
|
||||||
showSearch
|
showSearch
|
||||||
value={this.state.queryParams.userId}
|
value={this.state.queryParams.userId}
|
||||||
placeholder='用户昵称'
|
placeholder='用户昵称'
|
||||||
@ -393,7 +498,7 @@ class OfflineSession extends Component {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
style={{width: 150}}
|
style={{width: 140}}
|
||||||
showSearch
|
showSearch
|
||||||
value={this.state.queryParams.assetId}
|
value={this.state.queryParams.assetId}
|
||||||
placeholder='资产名称'
|
placeholder='资产名称'
|
||||||
@ -404,6 +509,14 @@ class OfflineSession extends Component {
|
|||||||
{assetOptions}
|
{assetOptions}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
<Select onChange={this.handleChangeByRead}
|
||||||
|
value={this.state.queryParams.reviewed ? this.state.queryParams.reviewed : ''}
|
||||||
|
style={{width: 100}}>
|
||||||
|
<Select.Option value="">全部会话</Select.Option>
|
||||||
|
<Select.Option value="true">只看已读</Select.Option>
|
||||||
|
<Select.Option value="false">只看未读</Select.Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
<Select onChange={this.handleChangeByProtocol}
|
<Select onChange={this.handleChangeByProtocol}
|
||||||
value={this.state.queryParams.protocol ? this.state.queryParams.protocol : ''}
|
value={this.state.queryParams.protocol ? this.state.queryParams.protocol : ''}
|
||||||
style={{width: 100}}>
|
style={{width: 100}}>
|
||||||
@ -422,9 +535,7 @@ class OfflineSession extends Component {
|
|||||||
this.loadTableData({
|
this.loadTableData({
|
||||||
pageIndex: 1,
|
pageIndex: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
protocol: '',
|
protocol: ''
|
||||||
userId: undefined,
|
|
||||||
assetId: undefined
|
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
@ -441,6 +552,29 @@ class OfflineSession extends Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="全部标为已阅">
|
||||||
|
<Button icon={<CheckOutlined />}
|
||||||
|
loading={this.state.reviewedAllBtnLoading}
|
||||||
|
onClick={this.handleAllReviewed}>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="标为已阅">
|
||||||
|
<Button disabled={!hasSelected} icon={<EyeOutlined />}
|
||||||
|
loading={this.state.reviewedBtnLoading}
|
||||||
|
onClick={this.handleReviewed}>
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="标为未阅">
|
||||||
|
<Button disabled={!hasSelected} icon={<EyeInvisibleOutlined />}
|
||||||
|
loading={this.state.unreviewedBtnLoading}
|
||||||
|
onClick={this.handleUnreviewed}>
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip title="批量删除">
|
<Tooltip title="批量删除">
|
||||||
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
|
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
|
||||||
loading={this.state.delBtnLoading}
|
loading={this.state.delBtnLoading}
|
||||||
@ -457,6 +591,25 @@ class OfflineSession extends Component {
|
|||||||
},
|
},
|
||||||
onCancel() {
|
onCancel() {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="清空">
|
||||||
|
<Button type="primary" danger icon={<ClearOutlined/>}
|
||||||
|
loading={this.state.clearBtnLoading}
|
||||||
|
onClick={() => {
|
||||||
|
const content = <Text style={{color: 'red'}}
|
||||||
|
strong>您确定要清空全部的离线会话吗?</Text>;
|
||||||
|
confirm({
|
||||||
|
icon: <ExclamationCircleOutlined/>,
|
||||||
|
content: content,
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: this.clearSession,
|
||||||
|
onCancel() {
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
@ -482,6 +635,9 @@ class OfflineSession extends Component {
|
|||||||
showTotal: total => `总计 ${total} 条`
|
showTotal: total => `总计 ${total} 条`
|
||||||
}}
|
}}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
rowClassName={(record, index) => {
|
||||||
|
return record['reviewed'] ? '' : 'unreviewed';
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -249,9 +249,9 @@ class Playback extends Component {
|
|||||||
this.startSpeedUp();
|
this.startSpeedUp();
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<Select.Option key="1">1x</Select.Option>
|
<Select.Option key="1" value={1}>1x</Select.Option>
|
||||||
<Select.Option key="2">2x</Select.Option>
|
<Select.Option key="2" value={2}>2x</Select.Option>
|
||||||
<Select.Option key="5">5x</Select.Option>
|
<Select.Option key="5" value={5}>5x</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex='none'>
|
<Col flex='none'>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {Button, Form, Input, Layout, Select, Switch, Tabs, Tooltip, Typography} from "antd";
|
import {Alert, Button, Form, Input, Layout, Select, Space, Switch, Tabs, Tooltip, Typography} from "antd";
|
||||||
import request from "../../common/request";
|
import request from "../../common/request";
|
||||||
import {message} from "antd/es";
|
import {message} from "antd/es";
|
||||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||||
|
import {download, getToken} from "../../utils/utils";
|
||||||
|
import {server} from "../../common/env";
|
||||||
|
|
||||||
const {Content} = Layout;
|
const {Content} = Layout;
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
@ -91,6 +93,10 @@ class Setting extends Component {
|
|||||||
if (this.mailSettingFormRef.current) {
|
if (this.mailSettingFormRef.current) {
|
||||||
this.mailSettingFormRef.current.setFieldsValue(properties)
|
this.mailSettingFormRef.current.setFieldsValue(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.otherSettingFormRef.current) {
|
||||||
|
this.otherSettingFormRef.current.setFieldsValue(properties)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(result['message']);
|
message.error(result['message']);
|
||||||
}
|
}
|
||||||
@ -443,7 +449,6 @@ class Setting extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Form.Item {...formTailLayout}>
|
<Form.Item {...formTailLayout}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
更新
|
更新
|
||||||
@ -525,7 +530,7 @@ class Setting extends Component {
|
|||||||
|
|
||||||
<TabPane tab="其他配置" key="other">
|
<TabPane tab="其他配置" key="other">
|
||||||
<Title level={3}>其他配置</Title>
|
<Title level={3}>其他配置</Title>
|
||||||
<Form ref={this.guacdSettingFormRef} name="other" onFinish={this.changeProperties}
|
<Form ref={this.otherSettingFormRef} name="other" onFinish={this.changeProperties}
|
||||||
layout="vertical">
|
layout="vertical">
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -565,6 +570,30 @@ class Setting extends Component {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane tab="备份与恢复" key="backup">
|
||||||
|
<Title level={3}>备份与恢复</Title>
|
||||||
|
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Alert
|
||||||
|
message="恢复数据时,如存在登录账号相同的用户时,会保留原系统中的数据,此外由于登录密码加密之后不可逆,恢复的账户密码将随机产生。"
|
||||||
|
type="info"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
download(`${server}/backup/export?X-Auth-Token=${getToken()}&t=${new Date().getTime()}`);
|
||||||
|
}}>
|
||||||
|
导出备份
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button type="dashed">
|
||||||
|
恢复备份
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Content>
|
</Content>
|
||||||
</>
|
</>
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Row,
|
Row,
|
||||||
Space,
|
Space,
|
||||||
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@ -26,14 +27,14 @@ import {message} from "antd/es";
|
|||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined, FrownOutlined,
|
||||||
InsuranceOutlined,
|
InsuranceOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
UndoOutlined
|
UndoOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {hasPermission} from "../../service/permission";
|
import {getCurrentUser} from "../../service/permission";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import UserShareSelectedAsset from "./UserShareSelectedAsset";
|
import UserShareSelectedAsset from "./UserShareSelectedAsset";
|
||||||
|
|
||||||
@ -73,16 +74,6 @@ class User extends Component {
|
|||||||
this.loadTableData();
|
this.loadTableData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id) {
|
|
||||||
let result = await request.delete('/users/' + id);
|
|
||||||
if (result.code === 1) {
|
|
||||||
message.success('操作成功', 3);
|
|
||||||
await this.loadTableData(this.state.queryParams);
|
|
||||||
} else {
|
|
||||||
message.error(result.message, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadTableData(queryParams) {
|
async loadTableData(queryParams) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
@ -129,11 +120,10 @@ class User extends Component {
|
|||||||
queryParams: queryParams
|
queryParams: queryParams
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadTableData(queryParams).then(r => {
|
this.loadTableData(queryParams);
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
showDeleteConfirm(id, content) {
|
showDeleteConfirm(id, content, index) {
|
||||||
let self = this;
|
let self = this;
|
||||||
confirm({
|
confirm({
|
||||||
title: '您确定要删除此用户吗?',
|
title: '您确定要删除此用户吗?',
|
||||||
@ -142,7 +132,7 @@ class User extends Component {
|
|||||||
okType: 'danger',
|
okType: 'danger',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk() {
|
onOk() {
|
||||||
self.delete(id);
|
self.delete(id, index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -167,7 +157,6 @@ class User extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
modalConfirmLoading: true
|
modalConfirmLoading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (formData.id) {
|
if (formData.id) {
|
||||||
// 向后台提交数据
|
// 向后台提交数据
|
||||||
const result = await request.put('/users/' + formData.id, formData);
|
const result = await request.put('/users/' + formData.id, formData);
|
||||||
@ -293,6 +282,72 @@ class User extends Component {
|
|||||||
this.loadTableData(query);
|
this.loadTableData(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(id, index) {
|
||||||
|
let items = this.state.items;
|
||||||
|
try {
|
||||||
|
items[index]['delLoading'] = true;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
let result = await request.delete('/users/' + id);
|
||||||
|
if (result.code === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
await this.loadTableData(this.state.queryParams);
|
||||||
|
} else {
|
||||||
|
message.error(result.message, 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
items[index]['delLoading'] = false;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUserStatus = async (id, checked, index) => {
|
||||||
|
let items = this.state.items;
|
||||||
|
try {
|
||||||
|
items[index]['statusLoading'] = true;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
let result = await request.patch(`/users/${id}/status?status=${checked ? 'enabled' : 'disabled'}`);
|
||||||
|
if (result['code'] !== 1) {
|
||||||
|
message.error(result['message']);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loadTableData(this.state.queryParams);
|
||||||
|
} finally {
|
||||||
|
items[index]['statusLoading'] = false;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTOTP = async (id, index) => {
|
||||||
|
let items = this.state.items;
|
||||||
|
try {
|
||||||
|
items[index]['resetTOTPLoading'] = true;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
let result = await request.post(`/users/${id}/reset-totp`);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
message.success('操作成功', 3);
|
||||||
|
this.loadTableData();
|
||||||
|
} else {
|
||||||
|
message.error(result['message'], 10);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
items[index]['resetTOTPLoading'] = false;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
@ -348,15 +403,28 @@ class User extends Component {
|
|||||||
dataIndex: 'mail',
|
dataIndex: 'mail',
|
||||||
key: 'mail',
|
key: 'mail',
|
||||||
}, {
|
}, {
|
||||||
title: '二次认证',
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status, record, index) => {
|
||||||
|
return <Switch checkedChildren="启用" unCheckedChildren="停用"
|
||||||
|
disabled={getCurrentUser()['id'] === record['id']}
|
||||||
|
loading={record['statusLoading']}
|
||||||
|
checked={status !== 'disabled'}
|
||||||
|
onChange={checked => {
|
||||||
|
this.changeUserStatus(record['id'], checked, index);
|
||||||
|
}}/>
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
title: '双因素认证',
|
||||||
dataIndex: 'totpSecret',
|
dataIndex: 'totpSecret',
|
||||||
key: 'totpSecret',
|
key: 'totpSecret',
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
|
|
||||||
if (text === '1') {
|
if (text === '1') {
|
||||||
return <Tag icon={<InsuranceOutlined/>} color="success">开启</Tag>;
|
return <Tag icon={<InsuranceOutlined/>} color="success">已开启</Tag>;
|
||||||
} else {
|
} else {
|
||||||
return <Tag icon={<ExclamationCircleOutlined/>} color="warning">关闭</Tag>;
|
return <Tag icon={<FrownOutlined />} color="warning">未开启</Tag>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@ -398,7 +466,7 @@ class User extends Component {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (text, record) => {
|
render: (text, record, index) => {
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
@ -414,6 +482,7 @@ class User extends Component {
|
|||||||
|
|
||||||
<Menu.Item key="2">
|
<Menu.Item key="2">
|
||||||
<Button type="text" size='small'
|
<Button type="text" size='small'
|
||||||
|
loading={record['resetTOTPLoading']}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirm({
|
confirm({
|
||||||
title: '您确定要重置此用户的双因素认证吗?',
|
title: '您确定要重置此用户的双因素认证吗?',
|
||||||
@ -421,13 +490,7 @@ class User extends Component {
|
|||||||
okText: '确定',
|
okText: '确定',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
let result = await request.post(`/users/${record['id']}/reset-totp`);
|
this.resetTOTP(record['id'], index);
|
||||||
if (result['code'] === 1) {
|
|
||||||
message.success('操作成功', 3);
|
|
||||||
this.loadTableData();
|
|
||||||
} else {
|
|
||||||
message.error(result['message'], 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}>重置双因素认证</Button>
|
}}>重置双因素认证</Button>
|
||||||
@ -446,8 +509,9 @@ class User extends Component {
|
|||||||
<Menu.Divider/>
|
<Menu.Divider/>
|
||||||
<Menu.Item key="5">
|
<Menu.Item key="5">
|
||||||
<Button type="text" size='small' danger
|
<Button type="text" size='small' danger
|
||||||
disabled={!hasPermission(record['owner'])}
|
disabled={getCurrentUser()['id'] === record['id']}
|
||||||
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
loading={record['delLoading']}
|
||||||
|
onClick={() => this.showDeleteConfirm(record.id, record.name, index)}>删除</Button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
@ -544,53 +608,6 @@ class User extends Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/*<Tooltip title="批量启用">*/}
|
|
||||||
{/* <Button type="dashed" danger disabled={!hasSelected}*/}
|
|
||||||
{/* icon={<IssuesCloseOutlined/>}*/}
|
|
||||||
{/* loading={this.state.delBtnLoading}*/}
|
|
||||||
{/* onClick={() => {*/}
|
|
||||||
{/* constant content = <div>*/}
|
|
||||||
{/* 您确定要启用选中的<Text style={{color: '#1890FF'}}*/}
|
|
||||||
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
|
|
||||||
{/* </div>;*/}
|
|
||||||
{/* confirm({*/}
|
|
||||||
{/* icon: <ExclamationCircleOutlined/>,*/}
|
|
||||||
{/* content: content,*/}
|
|
||||||
{/* onOk: () => {*/}
|
|
||||||
|
|
||||||
{/* },*/}
|
|
||||||
{/* onCancel() {*/}
|
|
||||||
|
|
||||||
{/* },*/}
|
|
||||||
{/* });*/}
|
|
||||||
{/* }}>*/}
|
|
||||||
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/*</Tooltip>*/}
|
|
||||||
|
|
||||||
{/*<Tooltip title="批量禁用">*/}
|
|
||||||
{/* <Button type="default" danger disabled={!hasSelected} icon={<StopOutlined/>}*/}
|
|
||||||
{/* loading={this.state.delBtnLoading}*/}
|
|
||||||
{/* onClick={() => {*/}
|
|
||||||
{/* constant content = <div>*/}
|
|
||||||
{/* 您确定要禁用选中的<Text style={{color: '#1890FF'}}*/}
|
|
||||||
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
|
|
||||||
{/* </div>;*/}
|
|
||||||
{/* confirm({*/}
|
|
||||||
{/* icon: <ExclamationCircleOutlined/>,*/}
|
|
||||||
{/* content: content,*/}
|
|
||||||
{/* onOk: () => {*/}
|
|
||||||
|
|
||||||
{/* },*/}
|
|
||||||
{/* onCancel() {*/}
|
|
||||||
|
|
||||||
{/* },*/}
|
|
||||||
{/* });*/}
|
|
||||||
{/* }}>*/}
|
|
||||||
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/*</Tooltip>*/}
|
|
||||||
|
|
||||||
<Tooltip title="批量删除">
|
<Tooltip title="批量删除">
|
||||||
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
|
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
|
||||||
loading={this.state.delBtnLoading}
|
loading={this.state.delBtnLoading}
|
||||||
|
@ -61,7 +61,6 @@ const UserModal = ({title, visible, handleOk, handleCancel, confirmLoading, mode
|
|||||||
<Input type="password" autoComplete="new-password" placeholder="输入登录密码"/>
|
<Input type="password" autoComplete="new-password" placeholder="输入登录密码"/>
|
||||||
</Form.Item>) : null
|
</Form.Item>) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user