* 优化图标和LOGO

* 修改登录页面动画的速度为3

* 增加对websocket的异常处理

* 修复了用户组和用户名唯一判断错误的问题

* 提示版本号

* 修复readme错别字

* 修复单词拼写错误的问题

* 修复代码格式

* 修改Windows资产属性名称

* Docker 打包流程增加 upx 压缩

* 升级依赖文件,修改sqlite驱动为 github.com/glebarez/sqlite

* 修复第一次查询「授权令牌」的错误

* 移除无关代码

* 修改docker打包脚本

* 增加打包脚本

* 增加微信群

* 修复单词拼写错误的问题

* 修复代码格式

* 修改Windows资产属性名称

* Docker 打包流程增加 upx 压缩

* 修改docker打包脚本

* - 替换 sqlite 驱动为 github.com/glebarez/sqlite
- 修复数据库锁定的问题
- 修复部分代码不完善的问题
- 修复策略显示不完整的问题
- 修复编辑文件换行符的问题
- 优化guacd连接
This commit is contained in:
dushixiang 2022-03-29 22:40:26 +08:00 committed by GitHub
parent 3ec6e5332e
commit 04f8d0079b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 436 additions and 568 deletions

View File

@ -168,10 +168,7 @@ func (api AccountApi) LoginWithTotpEndpoint(c echo.Context) error {
func (api AccountApi) LogoutEndpoint(c echo.Context) error {
token := GetToken(c)
err := service.UserService.LogoutByToken(token)
if err != nil {
return err
}
service.UserService.Logout(token)
return Success(c, nil)
}
@ -317,6 +314,10 @@ func (api AccountApi) AccountAssetEndpoint(c echo.Context) error {
if err != nil {
return err
}
for i := range items {
items[i].IP = ""
items[i].Port = 0
}
return Success(c, Map{
"total": total,

View File

@ -28,7 +28,7 @@ func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
m["owner"] = account.ID
if _, err := service.AssetService.Create(m); err != nil {
if _, err := service.AssetService.Create(context.TODO(), m); err != nil {
return err
}

View File

@ -25,7 +25,7 @@ func (api BackupApi) BackupExportEndpoint(c echo.Context) error {
if err != nil {
return err
}
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=next-terminal_backup_%s.json", time.Now().Format("20060102150405")))
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=backup_%s.json", time.Now().Format("20060102150405")))
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(jsonBytes))
}

View File

@ -64,7 +64,7 @@ func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
item.Encrypted = true
if err := service.CredentialService.Create(&item); err != nil {
if err := service.CredentialService.Create(context.TODO(), &item); err != nil {
return err
}

View File

@ -159,6 +159,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
defer guacamoleHandler.Stop()
for {
_, message, err := ws.ReadMessage()
@ -168,7 +169,6 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
_ = guacdTunnel.Close()
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
guacamoleHandler.Stop()
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)
@ -193,7 +193,7 @@ func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Ses
}
realPath := path.Join(service.StorageService.GetBaseDrivePath(), storageId)
configuration.SetParameter(guacd.EnableDrive, "true")
configuration.SetParameter(guacd.DriveName, "Next Terminal Filesystem")
configuration.SetParameter(guacd.DriveName, "Filesystem")
configuration.SetParameter(guacd.DrivePath, realPath)
log.Debugf("[%v] 会话 %v:%v 映射目录地址为 %v", s.ID, s.IP, s.Port, realPath)
} else {
@ -258,6 +258,7 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
defer guacamoleHandler.Stop()
for {
_, message, err := ws.ReadMessage()
@ -269,7 +270,6 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
observerId := nextSession.ID
forObsSession.Observer.Del <- observerId
log.Debugf("[%v:%v] 观察者[%v]退出会话", sessionId, connectionId, observerId)
guacamoleHandler.Stop()
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)

View File

@ -2,7 +2,6 @@ package api
import (
"context"
"time"
"next-terminal/server/guacd"
"next-terminal/server/log"
@ -12,73 +11,47 @@ import (
)
type GuacamoleHandler struct {
ws *websocket.Conn
tunnel *guacd.Tunnel
ctx context.Context
cancel context.CancelFunc
dataChan chan []byte
tick *time.Ticker
ws *websocket.Conn
tunnel *guacd.Tunnel
ctx context.Context
cancel context.CancelFunc
}
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacd.Tunnel) *GuacamoleHandler {
ctx, cancel := context.WithCancel(context.Background())
tick := time.NewTicker(time.Millisecond * time.Duration(60))
return &GuacamoleHandler{
ws: ws,
tunnel: tunnel,
ctx: ctx,
cancel: cancel,
dataChan: make(chan []byte),
tick: tick,
ws: ws,
tunnel: tunnel,
ctx: ctx,
cancel: cancel,
}
}
func (r GuacamoleHandler) Start() {
go r.readFormTunnel()
go r.writeToWebsocket()
}
func (r GuacamoleHandler) Stop() {
r.tick.Stop()
r.cancel()
}
func (r GuacamoleHandler) readFormTunnel() {
for {
select {
case <-r.ctx.Done():
return
default:
instruction, err := r.tunnel.Read()
if err != nil {
utils.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
go func() {
for {
select {
case <-r.ctx.Done():
return
}
if len(instruction) == 0 {
continue
}
r.dataChan <- instruction
}
}
}
func (r GuacamoleHandler) writeToWebsocket() {
var buf []byte
for {
select {
case <-r.ctx.Done():
return
case <-r.tick.C:
if len(buf) > 0 {
err := r.ws.WriteMessage(websocket.TextMessage, buf)
default:
instruction, err := r.tunnel.Read()
if err != nil {
utils.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
return
}
if len(instruction) == 0 {
continue
}
err = r.ws.WriteMessage(websocket.TextMessage, instruction)
if err != nil {
log.Debugf("WebSocket写入失败即将关闭Guacd连接...")
return
}
buf = []byte{}
}
case data := <-r.dataChan:
buf = append(buf, data...)
}
}
}()
}
func (r GuacamoleHandler) Stop() {
r.cancel()
}

View File

@ -25,7 +25,7 @@ func (api JobApi) JobCreateEndpoint(c echo.Context) error {
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
if err := service.JobService.Create(&item); err != nil {
if err := service.JobService.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"next-terminal/server/service"
"next-terminal/server/dto"
"next-terminal/server/repository"
@ -42,7 +43,7 @@ func (api ResourceSharerApi) ResourceAddByUserIdAssignEndPoint(c echo.Context) e
return err
}
if err := repository.ResourceSharerRepository.AddSharerResources(ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
if err := service.UserService.AddSharerResources(context.TODO(), ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
return err
}

View File

@ -312,16 +312,16 @@ func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
if s.Download != "1" {
return errors.New("禁止操作")
}
remoteFile := c.QueryParam("file")
file := c.QueryParam("file")
// 获取带后缀的文件名称
filenameWithSuffix := path.Base(remoteFile)
filenameWithSuffix := path.Base(file)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
dstFile, err := nextSession.NextTerminal.SftpClient.Open(remoteFile)
dstFile, err := nextSession.NextTerminal.SftpClient.Open(file)
if err != nil {
return err
}
@ -337,7 +337,7 @@ func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes()))
} else if "rdp" == s.Protocol {
storageId := s.StorageId
return service.StorageService.StorageDownload(c, remoteFile, storageId)
return service.StorageService.StorageDownload(c, file, storageId)
}
return err
@ -541,7 +541,9 @@ func (api SessionApi) SessionRecordingEndpoint(c echo.Context) error {
_ = repository.SessionRepository.UpdateReadByIds(context.TODO(), true, []string{sessionId})
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
return c.File(recording)
http.ServeFile(c.Response(), c.Request(), recording)
return nil
}
func (api SessionApi) SessionGetEndpoint(c echo.Context) error {

View File

@ -134,7 +134,7 @@ func (api StorageApi) StorageDeleteEndpoint(c echo.Context) error {
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := service.StorageService.DeleteStorageById(id, false); err != nil {
if err := service.StorageService.DeleteStorageById(context.TODO(), id, false); err != nil {
return err
}
}
@ -173,8 +173,8 @@ func (api StorageApi) StorageDownloadEndpoint(c echo.Context) error {
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
remoteFile := c.QueryParam("file")
return service.StorageService.StorageDownload(c, remoteFile, storageId)
file := c.QueryParam("file")
return service.StorageService.StorageDownload(c, file, storageId)
}
func (api StorageApi) StorageUploadEndpoint(c echo.Context) error {

View File

@ -157,6 +157,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
termHandler := NewTermHandler(sessionId, isRecording, ws, nextTerminal)
termHandler.Start()
defer termHandler.Stop()
for {
_, message, err := ws.ReadMessage()
@ -164,7 +165,6 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
// web socket会话关闭后主动关闭ssh会话
log.Debugf("WebSocket已关闭")
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
termHandler.Stop()
break
}

View File

@ -20,7 +20,7 @@ func (userGroupApi UserGroupApi) UserGroupCreateEndpoint(c echo.Context) error {
return err
}
if _, err := service.UserGroupService.Create(item.Name, item.Members); err != nil {
if _, err := service.UserGroupService.Create(context.TODO(), item.Name, item.Members); err != nil {
return err
}

View File

@ -2,7 +2,7 @@ package api
import (
"context"
"fmt"
"strconv"
"strings"
@ -141,7 +141,11 @@ func (userApi UserApi) UserChangePasswordEndpoint(c echo.Context) error {
}
if user.Mail != "" {
go service.MailService.SendMail(user.Mail, "[Next Terminal] 密码修改通知", "你好,"+user.Nickname+"。管理员已将你的密码修改为:"+password)
subject := "密码修改通知"
text := fmt.Sprintf(`您好%s
管理员已将你的密码修改为%s
`, user.Username, password)
go service.MailService.SendMail(user.Mail, subject, text)
}
return Success(c, "")

View File

@ -26,13 +26,6 @@ func newApp() *App {
func init() {
setupCache()
app = newApp()
if err := app.InitDBData(); err != nil {
panic(err)
}
if err := app.ReloadData(); err != nil {
panic(err)
}
app.Server = setupRoutes()
}
func (app App) InitDBData() (err error) {
@ -96,7 +89,15 @@ func (app App) ReloadData() error {
func Run() error {
fmt.Printf(constant.Banner, constant.Version)
fmt.Printf(constant.AppBanner, constant.AppVersion)
if err := app.InitDBData(); err != nil {
panic(err)
}
if err := app.ReloadData(); err != nil {
panic(err)
}
app.Server = setupRoutes()
if config.GlobalCfg.Debug {
jsonBytes, err := json.MarshalIndent(config.GlobalCfg, "", " ")

View File

@ -5,8 +5,9 @@ import (
)
const (
Version = "v1.2.5"
Banner = `
AppVersion = "v1.2.5"
AppName = "Next Terminal"
AppBanner = `
_______ __ ___________ .__ .__
\ \ ____ ___ ____/ |_ \__ ___/__________ _____ |__| ____ _____ | |
/ | \_/ __ \\ \/ /\ __\ | |_/ __ \_ __ \/ \| |/ \\__ \ | |
@ -72,8 +73,9 @@ const (
SocksProxyUsername = "socks-proxy-username"
SocksProxyPassword = "socks-proxy-password"
LoginToken = "login-token"
AccessToken = "access-token"
LoginToken = "login-token"
AccessToken = "access-token"
ShareSession = "share-session"
Anonymous = "anonymous"
)

6
server/env/db.go vendored
View File

@ -36,8 +36,10 @@ func setupDB() *gorm.DB {
Logger: logMode,
})
} else {
db, err = gorm.Open(sqlite.Open(config.GlobalCfg.Sqlite.File), &gorm.Config{
Logger: logMode,
dsn := fmt.Sprintf("file:%s?cache=shared&mode=rwc", config.GlobalCfg.Sqlite.File)
db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logMode,
SkipDefaultTransaction: true,
})
}

View File

@ -13,14 +13,9 @@ type baseRepository struct {
}
func (b *baseRepository) GetDB(c context.Context) *gorm.DB {
db := c.Value(constant.DB)
if db == nil {
return env.GetDB()
}
switch val := db.(type) {
case gorm.DB:
return &val
default:
db, ok := c.Value(constant.DB).(*gorm.DB)
if !ok {
return env.GetDB()
}
return db
}

View File

@ -7,7 +7,6 @@ import (
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"gorm.io/gorm"
)
@ -101,57 +100,8 @@ func (r *resourceSharerRepository) DeleteByUserGroupId(c context.Context, userGr
return r.GetDB(c).Where("user_group_id = ?", userGroupId).Delete(&model.ResourceSharer{}).Error
}
func (r *resourceSharerRepository) AddSharerResources(userGroupId, userId, strategyId, resourceType string, resourceIds []string) error {
return r.GetDB(context.TODO()).Transaction(func(tx *gorm.DB) (err error) {
for i := range resourceIds {
resourceId := resourceIds[i]
var owner string
// 检查资产是否存在
switch resourceType {
case "asset":
resource := model.Asset{}
if err = tx.Where("id = ?", resourceId).First(&resource).Error; err != nil {
return errors.Wrap(err, "find asset fail")
}
owner = resource.Owner
case "command":
resource := model.Command{}
if err = tx.Where("id = ?", resourceId).First(&resource).Error; err != nil {
return errors.Wrap(err, "find command fail")
}
owner = resource.Owner
case "credential":
resource := model.Credential{}
if err = tx.Where("id = ?", resourceId).First(&resource).Error; err != nil {
return errors.Wrap(err, "find credential fail")
}
owner = resource.Owner
}
if owner == userId {
return echo.NewHTTPError(400, "参数错误")
}
// 保证同一个资产只能分配给一个用户或者组
id := utils.Sign([]string{resourceId, resourceType, userId, userGroupId})
resource := &model.ResourceSharer{
ID: id,
ResourceId: resourceId,
ResourceType: resourceType,
StrategyId: strategyId,
UserId: userId,
UserGroupId: userGroupId,
}
err = tx.Create(resource).Error
if err != nil {
return err
}
}
return nil
})
func (r *resourceSharerRepository) AddSharerResource(c context.Context, m *model.ResourceSharer) error {
return r.GetDB(c).Create(m).Error
}
func (r *resourceSharerRepository) FindAssetIdsByUserId(c context.Context, userId string) (assetIds []string, err error) {
@ -228,3 +178,7 @@ func (r *resourceSharerRepository) FindAll(c context.Context) (o []model.Resourc
err = r.GetDB(c).Find(&o).Error
return
}
func (r *resourceSharerRepository) DeleteById(ctx context.Context, id string) error {
return r.GetDB(ctx).Where("id = ?", id).Delete(&model.ResourceSharer{}).Error
}

View File

@ -19,14 +19,9 @@ type accessTokenService struct {
baseService
}
func (service accessTokenService) FindByUserId(userId string) (model.AccessToken, error) {
return repository.AccessTokenRepository.FindByUserId(context.TODO(), userId)
}
func (service accessTokenService) GenAccessToken(userId string) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
ctx := service.Context(tx)
user, err := repository.UserRepository.FindById(ctx, userId)
if err != nil {
return err

View File

@ -119,7 +119,7 @@ func (s assetService) FindByIdAndDecrypt(c context.Context, id string) (model.As
func (s assetService) CheckStatus(accessGatewayId string, ip string, port int) (active bool, err error) {
if accessGatewayId != "" && accessGatewayId != "-" {
g, e1 := GatewayService.GetGatewayAndReconnectById(accessGatewayId)
if err != nil {
if e1 != nil {
return false, e1
}
@ -141,7 +141,7 @@ func (s assetService) CheckStatus(accessGatewayId string, ip string, port int) (
return active, err
}
func (s assetService) Create(m echo.Map) (model.Asset, error) {
func (s assetService) Create(ctx context.Context, m echo.Map) (model.Asset, error) {
data, err := json.Marshal(m)
if err != nil {
@ -156,29 +156,36 @@ func (s assetService) Create(m echo.Map) (model.Asset, error) {
item.Created = utils.NowJsonTime()
item.Active = true
return item, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := s.Context(tx)
if s.InTransaction(ctx) {
return item, s.create(ctx, item, m)
} else {
return item, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := s.Context(tx)
return s.create(c, item, m)
})
}
}
if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := repository.AssetRepository.Create(c, &item); err != nil {
return err
}
func (s assetService) create(c context.Context, item model.Asset, m echo.Map) error {
if err := s.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
if err := repository.AssetRepository.Create(c, &item); err != nil {
return err
}
if err := repository.AssetRepository.UpdateAttributes(c, item.ID, item.Protocol, m); err != nil {
return err
}
if err := repository.AssetRepository.UpdateAttributes(c, item.ID, item.Protocol, m); err != nil {
return err
}
go func() {
active, _ := s.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
if item.Active != active {
_ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.ID)
}
}()
return nil
})
//go func() {
// active, _ := s.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
//
// if item.Active != active {
// _ = repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.ID)
// }
//}()
return nil
}
func (s assetService) DeleteById(id string) error {

View File

@ -128,12 +128,12 @@ func (service backupService) Export() (error, *dto.Backup) {
func (service backupService) Import(backup *dto.Backup) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
ctx := service.Context(tx)
var userIdMapping = make(map[string]string)
if len(backup.Users) > 0 {
for _, item := range backup.Users {
oldId := item.ID
exist, err := repository.UserRepository.ExistByUsername(c, item.Username)
exist, err := repository.UserRepository.ExistByUsername(ctx, item.Username)
if err != nil {
return err
}
@ -144,7 +144,7 @@ func (service backupService) Import(backup *dto.Backup) error {
newId := utils.UUID()
item.ID = newId
item.Password = utils.GenPassword()
if err := repository.UserRepository.Create(c, &item); err != nil {
if err := repository.UserRepository.Create(ctx, &item); err != nil {
return err
}
userIdMapping[oldId] = newId
@ -163,7 +163,7 @@ func (service backupService) Import(backup *dto.Backup) error {
}
}
userGroup, err := UserGroupService.Create(item.Name, members)
userGroup, err := UserGroupService.Create(ctx, item.Name, members)
if err != nil {
if errors.Is(constant.ErrNameAlreadyUsed, err) {
// 删除名称重复的用户组
@ -187,7 +187,7 @@ func (service backupService) Import(backup *dto.Backup) error {
item.ID = utils.UUID()
item.Owner = owner
item.Created = utils.NowJsonTime()
if err := repository.StorageRepository.Create(c, &item); err != nil {
if err := repository.StorageRepository.Create(ctx, &item); err != nil {
return err
}
}
@ -200,7 +200,7 @@ func (service backupService) Import(backup *dto.Backup) error {
newId := utils.UUID()
item.ID = newId
item.Created = utils.NowJsonTime()
if err := repository.StrategyRepository.Create(c, &item); err != nil {
if err := repository.StrategyRepository.Create(ctx, &item); err != nil {
return err
}
strategyIdMapping[oldId] = newId
@ -210,7 +210,7 @@ func (service backupService) Import(backup *dto.Backup) error {
if len(backup.AccessSecurities) > 0 {
for _, item := range backup.AccessSecurities {
item.ID = utils.UUID()
if err := repository.SecurityRepository.Create(c, &item); err != nil {
if err := repository.SecurityRepository.Create(ctx, &item); err != nil {
return err
}
// 更新内存中的安全规则
@ -231,7 +231,7 @@ func (service backupService) Import(backup *dto.Backup) error {
newId := utils.UUID()
item.ID = newId
item.Created = utils.NowJsonTime()
if err := repository.GatewayRepository.Create(c, &item); err != nil {
if err := repository.GatewayRepository.Create(ctx, &item); err != nil {
return err
}
accessGatewayIdMapping[oldId] = newId
@ -242,7 +242,7 @@ func (service backupService) Import(backup *dto.Backup) error {
for _, item := range backup.Commands {
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
if err := repository.CommandRepository.Create(c, &item); err != nil {
if err := repository.CommandRepository.Create(ctx, &item); err != nil {
return err
}
}
@ -254,7 +254,7 @@ func (service backupService) Import(backup *dto.Backup) error {
oldId := item.ID
newId := utils.UUID()
item.ID = newId
if err := CredentialService.Create(&item); err != nil {
if err := CredentialService.Create(ctx, &item); err != nil {
return err
}
credentialIdMapping[oldId] = newId
@ -282,7 +282,7 @@ func (service backupService) Import(backup *dto.Backup) error {
}
oldId := m["id"].(string)
asset, err := AssetService.Create(m)
asset, err := AssetService.Create(ctx, m)
if err != nil {
return err
}
@ -299,7 +299,7 @@ func (service backupService) Import(backup *dto.Backup) error {
strategyId := strategyIdMapping[item.StrategyId]
resourceId := assetIdMapping[item.ResourceId]
if err := repository.ResourceSharerRepository.AddSharerResources(userGroupId, userId, strategyId, item.ResourceType, []string{resourceId}); err != nil {
if err := UserService.AddSharerResources(ctx, userGroupId, userId, strategyId, item.ResourceType, []string{resourceId}); err != nil {
return err
}
}
@ -311,6 +311,7 @@ func (service backupService) Import(backup *dto.Backup) error {
continue
}
item.ID = utils.UUID()
resourceIds := strings.Split(item.ResourceIds, ",")
if len(resourceIds) > 0 {
var newResourceIds = make([]string, 0)
@ -319,7 +320,7 @@ func (service backupService) Import(backup *dto.Backup) error {
}
item.ResourceIds = strings.Join(newResourceIds, ",")
}
if err := JobService.Create(&item); err != nil {
if err := JobService.Create(ctx, &item); err != nil {
return err
}
}

View File

@ -14,3 +14,8 @@ type baseService struct {
func (service baseService) Context(db *gorm.DB) context.Context {
return context.WithValue(context.TODO(), constant.DB, db)
}
func (service baseService) InTransaction(ctx context.Context) bool {
_, ok := ctx.Value(constant.DB).(*gorm.DB)
return ok
}

View File

@ -99,8 +99,8 @@ func (s credentialService) Decrypt(item *model.Credential, password []byte) erro
return nil
}
func (s credentialService) FindByIdAndDecrypt(c context.Context, id string) (o model.Credential, err error) {
credential, err := repository.CredentialRepository.FindById(c, id)
func (s credentialService) FindByIdAndDecrypt(ctx context.Context, id string) (o model.Credential, err error) {
credential, err := repository.CredentialRepository.FindById(ctx, id)
if err != nil {
return o, err
}
@ -110,10 +110,10 @@ func (s credentialService) FindByIdAndDecrypt(c context.Context, id string) (o m
return credential, nil
}
func (s credentialService) Create(item *model.Credential) error {
func (s credentialService) Create(ctx context.Context, item *model.Credential) error {
// 加密密码之后进行存储
if err := s.Encrypt(item, config.GlobalCfg.EncryptionPassword); err != nil {
return err
}
return repository.CredentialRepository.Create(context.TODO(), item)
return repository.CredentialRepository.Create(ctx, item)
}

View File

@ -103,7 +103,7 @@ func (r jobService) InitJob() error {
return nil
}
func (r jobService) Create(o *model.Job) (err error) {
func (r jobService) Create(ctx context.Context, o *model.Job) (err error) {
if o.Status == constant.JobStatusRunning {
j, err := getJob(o)
@ -117,7 +117,7 @@ func (r jobService) Create(o *model.Job) (err error) {
o.CronJobId = int(jobId)
}
return repository.JobRepository.Create(context.TODO(), o)
return repository.JobRepository.Create(ctx, o)
}
func (r jobService) DeleteJobById(id string) error {

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"fmt"
"net/smtp"
"next-terminal/server/constant"
@ -27,7 +28,7 @@ func (r mailService) SendMail(to, subject, text string) {
}
e := email.NewEmail()
e.From = "Next Terminal <" + username + ">"
e.From = fmt.Sprintf("%s <%s>", constant.AppName, username)
e.To = []string{to}
e.Subject = subject
e.Text = []byte(text)

View File

@ -17,161 +17,63 @@ type propertyService struct {
baseService
}
var deprecatedPropertyNames = []string{
guacd.EnableDrive,
guacd.DrivePath,
guacd.DriveName,
guacd.DisableGlyphCaching,
guacd.CreateRecordingPath,
}
var defaultProperties = map[string]string{
guacd.EnableRecording: "true",
guacd.FontName: "menlo",
guacd.FontSize: "12",
guacd.ColorScheme: "gray-black",
guacd.EnableWallpaper: "true",
guacd.EnableTheming: "true",
guacd.EnableFontSmoothing: "true",
guacd.EnableFullWindowDrag: "true",
guacd.EnableDesktopComposition: "true",
guacd.EnableMenuAnimations: "true",
guacd.DisableBitmapCaching: "false",
guacd.DisableOffscreenCaching: "false",
"cron-log-saved-limit": "360",
"login-log-saved-limit": "360",
"session-saved-limit": "360",
"user-default-storage-size": "5120",
}
func (service propertyService) InitProperties() error {
propertyMap := repository.PropertyRepository.FindAllMap(context.TODO())
if len(propertyMap[guacd.EnableRecording]) == 0 {
property := model.Property{
Name: guacd.EnableRecording,
Value: "true",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
for name, value := range defaultProperties {
if err := service.CreateIfAbsent(propertyMap, name, value); err != nil {
return err
}
}
if len(propertyMap[guacd.CreateRecordingPath]) == 0 {
property := model.Property{
Name: guacd.CreateRecordingPath,
Value: "true",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
return nil
}
if len(propertyMap[guacd.FontName]) == 0 {
func (service propertyService) CreateIfAbsent(propertyMap map[string]string, name, value string) error {
if len(propertyMap[name]) == 0 {
property := model.Property{
Name: guacd.FontName,
Value: "menlo",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.FontSize]) == 0 {
property := model.Property{
Name: guacd.FontSize,
Value: "12",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.ColorScheme]) == 0 {
property := model.Property{
Name: guacd.ColorScheme,
Value: "gray-black",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableWallpaper]) == 0 {
property := model.Property{
Name: guacd.EnableWallpaper,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableTheming]) == 0 {
property := model.Property{
Name: guacd.EnableTheming,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFontSmoothing]) == 0 {
property := model.Property{
Name: guacd.EnableFontSmoothing,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFullWindowDrag]) == 0 {
property := model.Property{
Name: guacd.EnableFullWindowDrag,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableDesktopComposition]) == 0 {
property := model.Property{
Name: guacd.EnableDesktopComposition,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableMenuAnimations]) == 0 {
property := model.Property{
Name: guacd.EnableMenuAnimations,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableBitmapCaching]) == 0 {
property := model.Property{
Name: guacd.DisableBitmapCaching,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableOffscreenCaching]) == 0 {
property := model.Property{
Name: guacd.DisableOffscreenCaching,
Value: "false",
}
if err := repository.PropertyRepository.Create(context.TODO(), &property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableGlyphCaching]) > 0 {
if err := repository.PropertyRepository.DeleteByName(context.TODO(), guacd.DisableGlyphCaching); err != nil {
return err
Name: name,
Value: value,
}
return repository.PropertyRepository.Create(context.TODO(), &property)
}
return nil
}
func (service propertyService) DeleteDeprecatedProperty() error {
propertyMap := repository.PropertyRepository.FindAllMap(context.TODO())
if propertyMap[guacd.EnableDrive] != "" {
if err := repository.PropertyRepository.DeleteByName(context.TODO(), guacd.DriveName); err != nil {
return err
for _, name := range deprecatedPropertyNames {
if propertyMap[name] == "" {
continue
}
}
if propertyMap[guacd.DrivePath] != "" {
if err := repository.PropertyRepository.DeleteByName(context.TODO(), guacd.DrivePath); err != nil {
return err
}
}
if propertyMap[guacd.DriveName] != "" {
if err := repository.PropertyRepository.DeleteByName(context.TODO(), guacd.DriveName); err != nil {
if err := repository.PropertyRepository.DeleteByName(context.TODO(), name); err != nil {
return err
}
}

View File

@ -4,11 +4,14 @@ import (
"bufio"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path"
"strconv"
"strings"
"next-terminal/server/config"
@ -33,7 +36,7 @@ func (service storageService) InitStorages() error {
userId := users[i].ID
_, err := repository.StorageRepository.FindByOwnerIdAndDefault(context.TODO(), userId, true)
if errors.Is(err, gorm.ErrRecordNotFound) {
err = service.CreateStorageByUser(&users[i])
err = service.CreateStorageByUser(context.TODO(), &users[i])
if err != nil {
return err
}
@ -58,7 +61,7 @@ func (service storageService) InitStorages() error {
}
if !userExist {
if err := service.DeleteStorageById(storage.ID, true); err != nil {
if err := service.DeleteStorageById(context.TODO(), storage.ID, true); err != nil {
return err
}
}
@ -75,14 +78,29 @@ func (service storageService) InitStorages() error {
return nil
}
func (service storageService) CreateStorageByUser(user *model.User) error {
func (service storageService) CreateStorageByUser(c context.Context, user *model.User) error {
drivePath := service.GetBaseDrivePath()
var limitSize int64 = -1
property, err := repository.PropertyRepository.FindByName(c, "user-default-storage-size")
if err != nil {
return err
}
limitSize, err = strconv.ParseInt(property.Value, 10, 64)
if err != nil {
return err
}
limitSize = limitSize * 1024 * 1024
if limitSize < 0 {
limitSize = -1
}
storage := model.Storage{
ID: user.ID,
Name: user.Nickname + "的默认空间",
IsShare: false,
IsDefault: true,
LimitSize: -1,
LimitSize: limitSize,
Owner: user.ID,
Created: utils.NowJsonTime(),
}
@ -91,8 +109,9 @@ func (service storageService) CreateStorageByUser(user *model.User) error {
return err
}
log.Infof("创建storage:「%v」文件夹: %v", storage.Name, storageDir)
err := repository.StorageRepository.Create(context.TODO(), &storage)
err = repository.StorageRepository.Create(c, &storage)
if err != nil {
_ = os.RemoveAll(storageDir)
return err
}
return nil
@ -135,9 +154,9 @@ func (service storageService) GetBaseDrivePath() string {
return config.GlobalCfg.Guacd.Drive
}
func (service storageService) DeleteStorageById(id string, force bool) error {
func (service storageService) DeleteStorageById(c context.Context, id string, force bool) error {
drivePath := service.GetBaseDrivePath()
storage, err := repository.StorageRepository.FindById(context.TODO(), id)
storage, err := repository.StorageRepository.FindById(c, id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
@ -152,7 +171,7 @@ func (service storageService) DeleteStorageById(id string, force bool) error {
if err := os.RemoveAll(path.Join(drivePath, id)); err != nil {
return err
}
if err := repository.StorageRepository.DeleteById(context.TODO(), id); err != nil {
if err := repository.StorageRepository.DeleteById(c, id); err != nil {
return err
}
return nil
@ -229,14 +248,20 @@ func (service storageService) StorageEdit(file string, fileContent string, stora
return nil
}
func (service storageService) StorageDownload(c echo.Context, remoteFile, storageId string) error {
func (service storageService) StorageDownload(c echo.Context, file, storageId string) error {
drivePath := service.GetBaseDrivePath()
if strings.Contains(remoteFile, "../") {
if strings.Contains(file, "../") {
return errors.New("非法请求 :(")
}
// 获取带后缀的文件名称
filenameWithSuffix := path.Base(remoteFile)
return c.Attachment(path.Join(path.Join(drivePath, storageId), remoteFile), filenameWithSuffix)
filenameWithSuffix := path.Base(file)
p := path.Join(path.Join(drivePath, storageId), file)
//log.Infof("download %v", p)
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filenameWithSuffix))
c.Response().Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(c.Response(), c.Request(), p)
return nil
}
func (service storageService) StorageLs(remoteDir, storageId string) (error, []File) {

View File

@ -90,31 +90,31 @@ func (service userService) FixUserOnlineState() error {
return nil
}
func (service userService) Logout(token string) {
cache.TokenManager.Delete(token)
}
func (service userService) LogoutByToken(token string) (err error) {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
loginLog, err := repository.LoginLogRepository.FindById(c, token)
if err != nil {
return err
}
cache.TokenManager.Delete(token)
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
err = repository.LoginLogRepository.Update(c, loginLogForUpdate)
if err != nil {
return err
}
loginLogs, err := repository.LoginLogRepository.FindAliveLoginLogsByUsername(c, loginLog.Username)
if err != nil {
return err
}
if len(loginLogs) == 0 {
err = repository.UserRepository.UpdateOnlineByUsername(c, loginLog.Username, false)
}
loginLog, err := repository.LoginLogRepository.FindById(context.TODO(), token)
if err != nil {
return err
})
}
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
err = repository.LoginLogRepository.Update(context.TODO(), loginLogForUpdate)
if err != nil {
return err
}
loginLogs, err := repository.LoginLogRepository.FindAliveLoginLogsByUsername(context.TODO(), loginLog.Username)
if err != nil {
return err
}
if len(loginLogs) == 0 {
err = repository.UserRepository.UpdateOnlineByUsername(context.TODO(), loginLog.Username, false)
}
return err
}
func (service userService) LogoutById(c context.Context, id string) error {
@ -130,13 +130,26 @@ func (service userService) LogoutById(c context.Context, id string) error {
for j := range loginLogs {
token := loginLogs[j].ID
if err := service.LogoutByToken(token); err != nil {
return err
}
service.Logout(token)
}
return nil
}
func (service userService) GetUserLoginToken(c context.Context, username string) ([]string, error) {
loginLogs, err := repository.LoginLogRepository.FindAliveLoginLogsByUsername(c, username)
if err != nil {
return nil, err
}
var tokens []string
for j := range loginLogs {
token := loginLogs[j].ID
tokens = append(tokens, token)
}
return tokens, nil
}
func (service userService) OnEvicted(token string, value interface{}) {
if strings.HasPrefix(token, "forever") {
@ -144,30 +157,24 @@ func (service userService) OnEvicted(token string, value interface{}) {
} else {
log.Debugf("用户Token「%v」过期", token)
err := service.LogoutByToken(token)
if err != nil {
if err != nil && !errors.Is(gorm.ErrRecordNotFound, err) {
log.Errorf("退出登录失败 %v", err)
}
}
}
func (service userService) UpdateStatusById(id string, status string) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
if c.Value(constant.DB) == nil {
c = context.WithValue(c, constant.DB, env.GetDB())
if constant.StatusDisabled == status {
// 将该用户下线
if err := service.LogoutById(context.TODO(), id); err != nil {
return err
}
if constant.StatusDisabled == status {
// 将该用户下线
if err := service.LogoutById(c, id); err != nil {
return err
}
}
u := model.User{
ID: id,
Status: status,
}
return repository.UserRepository.Update(c, &u)
})
}
u := model.User{
ID: id,
Status: status,
}
return repository.UserRepository.Update(context.TODO(), &u)
}
@ -231,13 +238,19 @@ func (service userService) CreateUser(user model.User) (err error) {
if err := repository.UserRepository.Create(c, &user); err != nil {
return err
}
err = StorageService.CreateStorageByUser(&user)
err = StorageService.CreateStorageByUser(c, &user)
if err != nil {
return err
}
if user.Mail != "" {
go MailService.SendMail(user.Mail, "[Next Terminal] 注册通知", "你好,"+user.Nickname+"。管理员为你注册了账号:"+user.Username+" 密码:"+password)
subject := fmt.Sprintf("%s 注册通知", constant.AppName)
text := fmt.Sprintf(`您好%s
管理员为你开通了账户
账号%s
密码%s
`, user.Username, user.Username, password)
go MailService.SendMail(user.Mail, subject, text)
}
return nil
})
@ -245,16 +258,19 @@ func (service userService) CreateUser(user model.User) (err error) {
}
func (service userService) DeleteUserById(userId string) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
user, err := repository.UserRepository.FindById(context.TODO(), userId)
if err != nil {
return err
}
username := user.Username
// 下线该用户
loginTokens, err := service.GetUserLoginToken(context.TODO(), username)
if err != nil {
return err
}
err = env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
// 下线该用户
if err := service.LogoutById(c, userId); err != nil {
return err
}
// 删除用户
if err := repository.UserRepository.DeleteById(c, userId); err != nil {
return err
}
// 删除用户与用户组的关系
if err := repository.UserGroupMemberRepository.DeleteByUserId(c, userId); err != nil {
return err
@ -264,19 +280,37 @@ func (service userService) DeleteUserById(userId string) error {
return err
}
// 删除用户的默认磁盘空间
if err := StorageService.DeleteStorageById(userId, true); err != nil {
if err := StorageService.DeleteStorageById(c, userId, true); err != nil {
return err
}
// 删除用户
if err := repository.UserRepository.DeleteById(c, userId); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
for _, token := range loginTokens {
service.Logout(token)
}
return nil
}
func (service userService) DeleteLoginLogs(tokens []string) error {
if len(tokens) > 0 {
for _, token := range tokens {
// 手动触发用户退出登录
if err := service.LogoutByToken(token); err != nil {
return err
}
// 移除缓存中的token
service.Logout(token)
// 删除登录日志
if err := repository.LoginLogRepository.DeleteById(context.TODO(), token); err != nil {
return err
}
@ -337,3 +371,37 @@ func (service userService) UpdateUser(id string, user model.User) error {
})
}
func (service userService) AddSharerResources(ctx context.Context, userGroupId, userId, strategyId, resourceType string, resourceIds []string) error {
if service.InTransaction(ctx) {
return service.addSharerResources(ctx, resourceIds, userGroupId, userId, strategyId, resourceType)
} else {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
ctx2 := service.Context(tx)
return service.addSharerResources(ctx2, resourceIds, userGroupId, userId, strategyId, resourceType)
})
}
}
func (service userService) addSharerResources(ctx context.Context, resourceIds []string, userGroupId string, userId string, strategyId string, resourceType string) error {
for i := range resourceIds {
resourceId := resourceIds[i]
// 保证同一个资产只能分配给一个用户或者组
id := utils.Sign([]string{resourceId, resourceType, userId, userGroupId})
if err := repository.ResourceSharerRepository.DeleteById(ctx, id); err != nil {
return err
}
rs := &model.ResourceSharer{
ID: id,
ResourceId: resourceId,
ResourceType: resourceType,
StrategyId: strategyId,
UserId: userId,
UserGroupId: userGroupId,
}
if err := repository.ResourceSharerRepository.AddSharerResource(ctx, rs); err != nil {
return err
}
}
return nil
}

View File

@ -13,11 +13,12 @@ import (
)
type userGroupService struct {
baseService
}
func (service userGroupService) DeleteById(userGroupId string) error {
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := context.WithValue(context.TODO(), constant.DB, tx)
c := service.Context(tx)
// 删除用户组
if err := repository.UserGroupRepository.DeleteById(c, userGroupId); err != nil {
return err
@ -34,8 +35,8 @@ func (service userGroupService) DeleteById(userGroupId string) error {
})
}
func (service userGroupService) Create(name string, members []string) (model.UserGroup, error) {
exist, err := repository.UserGroupRepository.ExistByName(context.TODO(), name)
func (service userGroupService) Create(ctx context.Context, name string, members []string) (model.UserGroup, error) {
exist, err := repository.UserGroupRepository.ExistByName(ctx, name)
if err != nil {
return model.UserGroup{}, err
}
@ -51,26 +52,33 @@ func (service userGroupService) Create(name string, members []string) (model.Use
Name: name,
}
return userGroup, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := context.WithValue(context.TODO(), constant.DB, tx)
if err := repository.UserGroupRepository.Create(c, &userGroup); err != nil {
return err
}
if len(members) > 0 {
for _, member := range members {
userGroupMember := model.UserGroupMember{
ID: utils.Sign([]string{userGroupId, member}),
UserId: member,
UserGroupId: userGroupId,
}
if err := repository.UserGroupMemberRepository.Create(c, &userGroupMember); err != nil {
return err
}
if service.InTransaction(ctx) {
return userGroup, service.create(ctx, userGroup, members, userGroupId)
} else {
return userGroup, env.GetDB().Transaction(func(tx *gorm.DB) error {
c := service.Context(tx)
return service.create(c, userGroup, members, userGroupId)
})
}
}
func (service userGroupService) create(c context.Context, userGroup model.UserGroup, members []string, userGroupId string) error {
if err := repository.UserGroupRepository.Create(c, &userGroup); err != nil {
return err
}
if len(members) > 0 {
for _, member := range members {
userGroupMember := model.UserGroupMember{
ID: utils.Sign([]string{userGroupId, member}),
UserId: member,
UserGroupId: userGroupId,
}
if err := repository.UserGroupMemberRepository.Create(c, &userGroupMember); err != nil {
return err
}
}
return nil
})
}
return nil
}
func (service userGroupService) Update(userGroupId string, name string, members []string) (err error) {
@ -91,7 +99,7 @@ func (service userGroupService) Update(userGroupId string, name string, members
}
return env.GetDB().Transaction(func(tx *gorm.DB) error {
c := context.WithValue(context.TODO(), constant.DB, tx)
c := service.Context(tx)
userGroup := model.UserGroup{
ID: userGroupId,
Name: name,

View File

@ -131,7 +131,7 @@ func (sshd sshd) sessionHandler(sess *ssh.Session) {
func (sshd sshd) Serve() {
ssh.Handle(func(s ssh.Session) {
_, _ = io.WriteString(s, fmt.Sprintf(constant.Banner, constant.Version))
_, _ = io.WriteString(s, fmt.Sprintf(constant.AppBanner, constant.AppVersion))
sshd.sessionHandler(&s)
})

View File

@ -59,19 +59,22 @@ func (gui Gui) AssetUI(sess *ssh.Session, user model.User) {
return
}
for i := range assets {
assets[i].IP = ""
assets[i].Port = 0
}
quitItem := model.Asset{ID: "quit", Name: "返回上级菜单", Description: "这里是返回上级菜单的选项"}
assets = append([]model.Asset{quitItem}, assets...)
templates := &promptui.SelectTemplates{
Label: "{{ . }}?",
Active: "\U0001F336 {{ .Name | cyan }} ({{ .IP | red }}:{{ .Port | red }})",
Inactive: " {{ .Name | cyan }} ({{ .IP | red }}:{{ .Port | red }})",
Active: "\U0001F336 {{ .Name | cyan }}",
Inactive: " {{ .Name | cyan }}",
Selected: "\U0001F336 {{ .Name | red | cyan }}",
Details: `
--------- 详细信息 ----------
{{ "名称:" | faint }} {{ .Name }}
{{ "主机:" | faint }} {{ .IP }}
{{ "端口:" | faint }} {{ .Port }}
{{ "标签:" | faint }} {{ .Tags }}
{{ "备注:" | faint }} {{ .Description }}
`,

View File

@ -18,7 +18,6 @@
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.0",
"react-tsparticles": "^1.37.5",
"xterm": "^4.9.0",
"xterm-addon-fit": "^0.4.0",
"xterm-addon-web-links": "^0.4.0"

View File

@ -5,8 +5,7 @@ import request from "../common/request";
import {message} from "antd/es";
import {withRouter} from "react-router-dom";
import {LockOutlined, OneToOneOutlined, UserOutlined} from '@ant-design/icons';
import Particles from "react-tsparticles";
import Background from '../images/bg.png'
import Background from '../images/bg.jpg'
import {setToken} from "../utils/utils";
const {Title} = Typography;
@ -109,90 +108,7 @@ class LoginForm extends Component {
render() {
return (
<div className='login-bg'
style={{width: this.state.width, height: this.state.height}}>
<Particles
id="tsparticles"
options={{
background: {
color: {
// value: "#0d47a1",
},
image: `url(${Background})`,
repeat: 'no-repeat',
size: '100% 100%'
},
fpsLimit: 60,
interactivity: {
events: {
onClick: {
enable: true,
mode: "push",
},
onHover: {
enable: true,
mode: "repulse",
},
resize: true,
},
modes: {
bubble: {
distance: 400,
duration: 2,
opacity: 0.8,
size: 40,
},
push: {
quantity: 4,
},
repulse: {
distance: 200,
duration: 0.4,
},
},
},
particles: {
color: {
value: "#ffffff",
},
links: {
color: "#ffffff",
distance: 150,
enable: true,
opacity: 0.5,
width: 1,
},
collisions: {
enable: true,
},
move: {
direction: "none",
enable: true,
outMode: "bounce",
random: false,
speed: 3,
straight: false,
},
number: {
density: {
enable: true,
value_area: 800,
},
value: 80,
},
opacity: {
value: 0.5,
},
shape: {
type: "circle",
},
size: {
random: true,
value: 5,
},
},
detectRetina: true,
}}
/>
style={{width: this.state.width, height: this.state.height, background: `url(${Background})`}}>
<Card className='login-card' title={null}>
<div style={{textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff'}}>
<Title level={1}>Next Terminal</Title>

View File

@ -127,7 +127,7 @@ class Access extends Component {
sendClipboard(data) {
if (this.state.session['paste'] === '0') {
message.warn('禁止粘贴');
// message.warn('禁止粘贴');
return
}
let writer;
@ -338,7 +338,7 @@ class Access extends Component {
clientClipboardReceived = (stream, mimetype) => {
if (this.state.session['copy'] === '0') {
message.warn('禁止复制');
// message.warn('禁止复制');
return
}
let reader;

View File

@ -82,7 +82,9 @@ class Term extends Component {
document.body.oncopy = (event) => {
event.preventDefault();
if (this.state.session['copy'] === '0') {
message.warn('禁止复制')
// message.warn('禁止复制')
return false;
}else {
if (event.clipboardData) {
return event.clipboardData.setData('text', '');
} else {
@ -90,13 +92,12 @@ class Term extends Component {
return window.clipboardData.setData("text", '');
}
}
return true;
}
document.body.onpaste = (event) => {
event.preventDefault();
if (this.state.session['paste'] === '0') {
message.warn('禁止粘贴')
// message.warn('禁止粘贴')
return false;
}
return true;

View File

@ -412,7 +412,7 @@ Windows需要对远程应用程序的名称使用特殊的符号。
<Panel header={<Text strong>映射网络驱动器</Text>} key="storage">
<Form.Item
name="enable-drive"
label="启用映射网络驱动器"
label="启用"
valuePropName="checked"
>
<Switch checkedChildren="开启" unCheckedChildren="关闭"
@ -424,7 +424,7 @@ Windows需要对远程应用程序的名称使用特殊的符号。
enableDrive ?
<Form.Item
name="drive-path"
label="映射网络驱动器"
label="映射空间"
tooltip='用于文件传输的映射网络驱动器,为空时使用操作人的默认空间'
>
<Select onChange={null} allowClear placeholder='为空时使用操作人的默认空间'>
@ -456,7 +456,7 @@ Windows需要对远程应用程序的名称使用特殊的符号。
setSshMode(value)
}}>
<Option value="">guacd</Option>
<Option value="native">原生</Option>
<Option value="naive">原生</Option>
</Select>
</Form.Item>
</Panel>

View File

@ -18,7 +18,6 @@ import {
import {
CheckCircleOutlined,
CodeOutlined,
DesktopOutlined,
ExclamationCircleOutlined,
SyncOutlined,
TagsOutlined,
@ -195,14 +194,6 @@ class MyAsset extends Component {
style={{width: 200}}
/>
<Search
ref={this.inputRefOfIp}
placeholder="资产IP"
allowClear
onSearch={this.handleSearchByIp}
style={{width: 200}}
/>
<Select mode="multiple"
allowClear
value={this.state.selectedTags}
@ -302,9 +293,6 @@ class MyAsset extends Component {
<Descriptions.Item label={<div><CodeOutlined/> 资产协议</div>}>
<strong>{item['protocol']}</strong>
</Descriptions.Item>
<Descriptions.Item label={<div><DesktopOutlined/> 主机地址</div>}>
<strong>{item['ip'] + ':' + item['port']}</strong>
</Descriptions.Item>
<Descriptions.Item label={<div><TagsOutlined/> 标签</div>}>
<strong>{this.renderTags(item['tags'])}</strong>
</Descriptions.Item>

View File

@ -9,7 +9,7 @@ const formItemLayout = {
const StorageModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
const [form] = Form.useForm();
if(!model){
if (!model) {
model = {
isShare: false
}
@ -46,11 +46,13 @@ const StorageModal = ({title, visible, handleOk, handleCancel, confirmLoading, m
<Input autoComplete="off" placeholder="网盘的名称"/>
</Form.Item>
<Form.Item label="是否共享" name='isShare' rules={[{required: true, message: '请选择是否共享'}]} valuePropName="checked">
<Switch checkedChildren="是" unCheckedChildren="否" />
<Form.Item label="是否共享" name='isShare' rules={[{required: true, message: '请选择是否共享'}]}
valuePropName="checked">
<Switch checkedChildren="是" unCheckedChildren="否"/>
</Form.Item>
<Form.Item label="大小限制" name='limitSize' rules={[{required: true, message: '请输入大小限制'}]}>
<Form.Item label="大小限制" name='limitSize' rules={[{required: true, message: '请输入大小限制'}]}
tooltip='无限制请填写-1'>
<Input type={'number'} min={-1} suffix="MB"/>
</Form.Item>

View File

@ -25,7 +25,6 @@ class Setting extends Component {
state = {
refs: [],
properties: {},
ldapUserSyncLoading: false
}
rdpSettingFormRef = React.createRef();
@ -33,8 +32,8 @@ class Setting extends Component {
vncSettingFormRef = React.createRef();
guacdSettingFormRef = React.createRef();
mailSettingFormRef = React.createRef();
ldapSettingFormRef = React.createRef();
logSettingFormRef = React.createRef();
otherSettingFormRef = React.createRef();
componentDidMount() {
// eslint-disable-next-line no-extend-native
@ -43,7 +42,15 @@ class Setting extends Component {
};
this.setState({
refs: [this.rdpSettingFormRef, this.sshSettingFormRef, this.vncSettingFormRef, this.guacdSettingFormRef, this.mailSettingFormRef, this.logSettingFormRef]
refs: [
this.rdpSettingFormRef,
this.sshSettingFormRef,
this.vncSettingFormRef,
this.guacdSettingFormRef,
this.mailSettingFormRef,
this.logSettingFormRef,
this.otherSettingFormRef
]
}, this.getProperties)
}
@ -121,26 +128,6 @@ class Setting extends Component {
reader.readAsText(files[0]);
}
ldapUserSync = async () => {
const id = 'ldap-user-sync'
try {
this.setState({
ldapUserSyncLoading: true
});
message.info({content: '同步中...', key: id, duration: 5});
let result = await request.post(`/properties/ldap-user-sync`);
if (result.code !== 1) {
message.error({content: result.message, key: id, duration: 10});
return;
}
message.success({content: '同步成功。', key: id, duration: 3});
} finally {
this.setState({
ldapUserSyncLoading: false
});
}
}
render() {
return (
<>
@ -551,7 +538,7 @@ class Setting extends Component {
</TabPane>
<TabPane tab="日志配置" key="log">
<Title level={3}>其他配置</Title>
<Title level={3}>日志配置</Title>
<Form ref={this.logSettingFormRef} name="log" onFinish={this.changeProperties}
layout="vertical">
@ -593,6 +580,28 @@ class Setting extends Component {
</Form>
</TabPane>
<TabPane tab="其他配置" key="other">
<Title level={3}>其他配置</Title>
<Form ref={this.otherSettingFormRef} name="other" onFinish={this.changeProperties}
layout="vertical">
<Form.Item
{...formItemLayout}
name="user-default-storage-size"
label="用户空间默认大小"
tooltip='无限制请填写-1'
>
<Input type={'number'} min={-1} suffix="MB"/>
</Form.Item>
<Form.Item {...formTailLayout}>
<Button type="primary" htmlType="submit">
更新
</Button>
</Form.Item>
</Form>
</TabPane>
<TabPane tab="备份与恢复" key="backup">
<Title level={3}>备份与恢复</Title>

View File

@ -280,7 +280,7 @@ class User extends Component {
let result = await request.delete('/users/' + id);
if (result.code === 1) {
message.success('操作成功', 3);
await this.loadTableData(this.state.queryParams);
this.loadTableData(this.state.queryParams);
} else {
message.error(result.message, 10);
}

View File

@ -425,8 +425,11 @@ class UserShareSelectedAsset extends Component {
<div>
<p>上传{renderStatus(item['strategy']['upload'])}</p>
<p>下载{renderStatus(item['strategy']['download'])}</p>
<p>编辑{renderStatus(item['strategy']['edit'])}</p>
<p>删除{renderStatus(item['strategy']['delete'])}</p>
<p>改名{renderStatus(item['strategy']['rename'])}</p>
<p>重命名{renderStatus(item['strategy']['rename'])}</p>
<p>复制{renderStatus(item['strategy']['copy'])}</p>
<p>粘贴{renderStatus(item['strategy']['paste'])}</p>
</div>
);
}

BIN
web/src/images/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB