feat: 完善日志审计功能
- 实现文件系统日志(FilesystemLog)记录文件管理器操作 - 实现操作日志(OperationLog)记录用户操作行为 - 实现数据库SQL日志(DatabaseSQLLog)模型和API - 实现SSH会话命令记录(SessionCommand)含命令输出和风险等级 - 添加IP提取服务支持X-Real-IP和X-Forwarded-For - 添加日志自动清理功能 - 修复ProFormSwitch required验证问题 - 修复设置页面默认值问题 - 修复文件上传错误检测逻辑 - 修复资产树key前缀问题 - 添加VNC/RDP设置默认值 - 修复文件管理标题翻译
This commit is contained in:
@@ -30,7 +30,8 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存储登录失败次数信息
|
// 存储登录失败次数信息
|
||||||
loginFailCountKey := c.RealIP() + loginAccount.Username
|
clientIP := service.PropertyService.GetClientIP(c)
|
||||||
|
loginFailCountKey := clientIP + loginAccount.Username
|
||||||
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
|
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
v = 1
|
v = 1
|
||||||
@@ -49,7 +50,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
|||||||
count++
|
count++
|
||||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||||
// 保存登录日志
|
// 保存登录日志
|
||||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
@@ -63,7 +64,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
|||||||
count++
|
count++
|
||||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||||
// 保存登录日志
|
// 保存登录日志
|
||||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
@@ -78,7 +79,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
|||||||
count++
|
count++
|
||||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||||
// 保存登录日志
|
// 保存登录日志
|
||||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
|
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
|
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
|
||||||
@@ -86,12 +87,12 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := api.LoginSuccess(loginAccount, user, c.RealIP())
|
token, err := api.LoginSuccess(loginAccount, user, clientIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 保存登录日志
|
// 保存登录日志
|
||||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,21 @@ import (
|
|||||||
|
|
||||||
type AssetApi struct{}
|
type AssetApi struct{}
|
||||||
|
|
||||||
|
func recordOperationLog(c echo.Context, action, content, status, errorMessage string) {
|
||||||
|
account, _ := GetCurrentAccount(c)
|
||||||
|
clientIP := service.PropertyService.GetClientIP(c)
|
||||||
|
_ = service.OperationLogService.Record(context.TODO(), service.OperationLogParams{
|
||||||
|
AccountId: account.ID,
|
||||||
|
AccountName: account.Username,
|
||||||
|
Action: action,
|
||||||
|
Content: content,
|
||||||
|
IP: clientIP,
|
||||||
|
Status: status,
|
||||||
|
ErrorMessage: errorMessage,
|
||||||
|
UserAgent: c.Request().UserAgent(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
||||||
m := maps.Map{}
|
m := maps.Map{}
|
||||||
if err := c.Bind(&m); err != nil {
|
if err := c.Bind(&m); err != nil {
|
||||||
@@ -30,10 +45,13 @@ func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
|||||||
account, _ := GetCurrentAccount(c)
|
account, _ := GetCurrentAccount(c)
|
||||||
m["owner"] = account.ID
|
m["owner"] = account.ID
|
||||||
|
|
||||||
|
assetName, _ := m["name"].(string)
|
||||||
if _, err := service.AssetService.Create(context.TODO(), m); err != nil {
|
if _, err := service.AssetService.Create(context.TODO(), m); err != nil {
|
||||||
|
recordOperationLog(c, "asset-add", "创建资产: "+assetName, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "asset-add", "创建资产: "+assetName, "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,9 +185,12 @@ func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
|
|||||||
if err := c.Bind(&m); err != nil {
|
if err := c.Bind(&m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
assetName, _ := m["name"].(string)
|
||||||
if err := service.AssetService.UpdateById(id, m); err != nil {
|
if err := service.AssetService.UpdateById(id, m); err != nil {
|
||||||
|
recordOperationLog(c, "asset-edit", "更新资产: "+assetName, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
recordOperationLog(c, "asset-edit", "更新资产: "+assetName, "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,10 +199,12 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
|
|||||||
split := strings.Split(id, ",")
|
split := strings.Split(id, ",")
|
||||||
for i := range split {
|
for i := range split {
|
||||||
if err := service.AssetService.DeleteById(split[i]); err != nil {
|
if err := service.AssetService.DeleteById(split[i]); err != nil {
|
||||||
|
recordOperationLog(c, "asset-del", "删除资产: "+id, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "asset-del", "删除资产: "+id, "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId str
|
|||||||
node := maps.Map{
|
node := maps.Map{
|
||||||
"id": g.ID,
|
"id": g.ID,
|
||||||
"name": g.Name,
|
"name": g.Name,
|
||||||
"key": g.ID,
|
"key": "group_" + g.ID,
|
||||||
"title": g.Name,
|
"title": g.Name,
|
||||||
"value": g.ID,
|
"value": g.ID,
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId str
|
|||||||
nodes = append(nodes, maps.Map{
|
nodes = append(nodes, maps.Map{
|
||||||
"id": a.ID,
|
"id": a.ID,
|
||||||
"name": a.Name,
|
"name": a.Name,
|
||||||
"key": a.ID,
|
"key": "asset_" + a.ID,
|
||||||
"title": a.Name,
|
"title": a.Name,
|
||||||
"value": a.ID,
|
"value": a.ID,
|
||||||
"isLeaf": true,
|
"isLeaf": true,
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"next-terminal/server/common"
|
||||||
"next-terminal/server/common/maps"
|
"next-terminal/server/common/maps"
|
||||||
"next-terminal/server/global/session"
|
"next-terminal/server/global/session"
|
||||||
"next-terminal/server/log"
|
"next-terminal/server/log"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/repository"
|
||||||
|
"next-terminal/server/utils"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -17,6 +22,28 @@ import (
|
|||||||
|
|
||||||
type FileSystemApi struct{}
|
type FileSystemApi struct{}
|
||||||
|
|
||||||
|
func (api FileSystemApi) recordFilesystemLog(ctx context.Context, sessionId, action, fileName string) {
|
||||||
|
sess, err := repository.SessionRepository.FindById(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to find session for filesystem log", log.NamedError("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filesystemLog := &model.FilesystemLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
AssetId: sess.AssetId,
|
||||||
|
SessionId: sessionId,
|
||||||
|
UserId: sess.Creator,
|
||||||
|
Action: action,
|
||||||
|
FileName: fileName,
|
||||||
|
Created: common.NowJsonTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repository.FilesystemLogRepository.Create(ctx, filesystemLog); err != nil {
|
||||||
|
log.Error("Failed to create filesystem log", log.NamedError("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (api FileSystemApi) LsEndpoint(c echo.Context) error {
|
func (api FileSystemApi) LsEndpoint(c echo.Context) error {
|
||||||
sessionId := c.Param("id")
|
sessionId := c.Param("id")
|
||||||
dir := c.QueryParam("dir")
|
dir := c.QueryParam("dir")
|
||||||
@@ -84,6 +111,8 @@ func (api FileSystemApi) MkdirEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to create directory: "+err.Error())
|
return Fail(c, -1, "failed to create directory: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "mkdir", dir)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +139,8 @@ func (api FileSystemApi) TouchEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "touch", filename)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +181,8 @@ func (api FileSystemApi) RmEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "rm", filename)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +233,8 @@ func (api FileSystemApi) RenameEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to rename: "+err.Error())
|
return Fail(c, -1, "failed to rename: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "rename", oldName+" -> "+newName)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +273,8 @@ func (api FileSystemApi) EditEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to write file: "+err.Error())
|
return Fail(c, -1, "failed to write file: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "edit", req.Filename)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +306,8 @@ func (api FileSystemApi) ReadEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to read file: "+err.Error())
|
return Fail(c, -1, "failed to read file: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "read", filename)
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"content": content,
|
"content": content,
|
||||||
"path": filename,
|
"path": filename,
|
||||||
@@ -322,6 +361,8 @@ func (api FileSystemApi) ChmodEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to chmod: "+err.Error())
|
return Fail(c, -1, "failed to chmod: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "chmod", filename+" ("+modeStr+")")
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +410,8 @@ func (api FileSystemApi) DownloadEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "failed to stat file: "+err.Error())
|
return Fail(c, -1, "failed to stat file: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "download", filename)
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Disposition", "attachment; filename="+path.Base(filename))
|
c.Response().Header().Set("Content-Disposition", "attachment; filename="+path.Base(filename))
|
||||||
c.Response().Header().Set("Content-Type", "application/octet-stream")
|
c.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||||
c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
|
c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
|
||||||
@@ -425,6 +468,8 @@ func (api FileSystemApi) UploadEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go api.recordFilesystemLog(context.TODO(), sessionId, "upload", destPath)
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"path": destPath,
|
"path": destPath,
|
||||||
"size": file.Size,
|
"size": file.Size,
|
||||||
|
|||||||
+17
-30
@@ -65,10 +65,10 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
|||||||
tree := make([]maps.Map, 0)
|
tree := make([]maps.Map, 0)
|
||||||
for _, g := range groups {
|
for _, g := range groups {
|
||||||
node := maps.Map{
|
node := maps.Map{
|
||||||
"key": g.ID,
|
"key": "group_" + g.ID,
|
||||||
"title": g.Name,
|
"title": g.Name,
|
||||||
"isLeaf": false,
|
"isLeaf": false,
|
||||||
"children": buildPortalAssetChildren(items, g.ID, keyword),
|
"children": []interface{}{},
|
||||||
}
|
}
|
||||||
tree = append(tree, node)
|
tree = append(tree, node)
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
node := maps.Map{
|
node := maps.Map{
|
||||||
"key": a.ID,
|
"key": "asset_" + a.ID,
|
||||||
"title": a.Name,
|
"title": a.Name,
|
||||||
"isLeaf": true,
|
"isLeaf": true,
|
||||||
"extra": maps.Map{
|
"extra": maps.Map{
|
||||||
@@ -93,29 +93,6 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
|||||||
return Success(c, tree)
|
return Success(c, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPortalAssetChildren(assets []model.Asset, groupId, keyword string) []maps.Map {
|
|
||||||
children := make([]maps.Map, 0)
|
|
||||||
for _, a := range assets {
|
|
||||||
if keyword != "" && !containsKeyword(a.Name, keyword) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
node := maps.Map{
|
|
||||||
"key": a.ID,
|
|
||||||
"title": a.Name,
|
|
||||||
"isLeaf": true,
|
|
||||||
"extra": maps.Map{
|
|
||||||
"protocol": a.Protocol,
|
|
||||||
"logo": "",
|
|
||||||
"status": "unknown",
|
|
||||||
"network": a.IP,
|
|
||||||
"wolEnabled": false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
children = append(children, node)
|
|
||||||
}
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsKeyword(name, keyword string) bool {
|
func containsKeyword(name, keyword string) bool {
|
||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return true
|
return true
|
||||||
@@ -139,7 +116,7 @@ func (api PortalApi) WebsitesTreeEndpoint(c echo.Context) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
node := maps.Map{
|
node := maps.Map{
|
||||||
"key": w.ID,
|
"key": "website_" + w.ID,
|
||||||
"title": w.Name,
|
"title": w.Name,
|
||||||
"isLeaf": true,
|
"isLeaf": true,
|
||||||
"extra": maps.Map{
|
"extra": maps.Map{
|
||||||
@@ -159,7 +136,7 @@ func (api PortalApi) AssetsGroupTreeEndpoint(c echo.Context) error {
|
|||||||
tree := make([]maps.Map, 0)
|
tree := make([]maps.Map, 0)
|
||||||
for _, g := range groups {
|
for _, g := range groups {
|
||||||
node := maps.Map{
|
node := maps.Map{
|
||||||
"key": g.ID,
|
"key": "group_" + g.ID,
|
||||||
"title": g.Name,
|
"title": g.Name,
|
||||||
"isLeaf": false,
|
"isLeaf": false,
|
||||||
"children": []interface{}{},
|
"children": []interface{}{},
|
||||||
@@ -186,7 +163,8 @@ func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
account, _ := GetCurrentAccount(c)
|
account, _ := GetCurrentAccount(c)
|
||||||
|
|
||||||
s, err := service.SessionService.Create(c.RealIP(), assetId, nt.Native, account)
|
clientIP := service.PropertyService.GetClientIP(c)
|
||||||
|
s, err := service.SessionService.Create(clientIP, assetId, nt.Native, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -201,7 +179,16 @@ func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
|
|||||||
"id": s.ID,
|
"id": s.ID,
|
||||||
"protocol": s.Protocol,
|
"protocol": s.Protocol,
|
||||||
"assetName": assetName,
|
"assetName": assetName,
|
||||||
"strategy": maps.Map{},
|
"strategy": maps.Map{
|
||||||
|
"upload": s.Upload == "1",
|
||||||
|
"download": s.Download == "1",
|
||||||
|
"delete": s.Delete == "1",
|
||||||
|
"rename": s.Rename == "1",
|
||||||
|
"edit": s.Edit == "1",
|
||||||
|
"copy": s.Copy == "1",
|
||||||
|
"paste": s.Paste == "1",
|
||||||
|
"fileSystem": s.FileSystem == "1",
|
||||||
|
},
|
||||||
"url": "",
|
"url": "",
|
||||||
"watermark": maps.Map{},
|
"watermark": maps.Map{},
|
||||||
"readonly": false,
|
"readonly": false,
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
user, _ := GetCurrentAccount(c)
|
user, _ := GetCurrentAccount(c)
|
||||||
|
|
||||||
s, err := service.SessionService.Create(c.RealIP(), assetId, mode, user)
|
clientIP := service.PropertyService.GetClientIP(c)
|
||||||
|
s, err := service.SessionService.Create(clientIP, assetId, mode, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+150
-10
@@ -2,7 +2,11 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"next-terminal/server/common/maps"
|
"next-terminal/server/common/maps"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/repository"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
@@ -10,18 +14,37 @@ import (
|
|||||||
type SessionCommandApi struct{}
|
type SessionCommandApi struct{}
|
||||||
|
|
||||||
func (api SessionCommandApi) AllEndpoint(c echo.Context) error {
|
func (api SessionCommandApi) AllEndpoint(c echo.Context) error {
|
||||||
return Success(c, []interface{}{})
|
sessionId := c.QueryParam("sessionId")
|
||||||
|
items, err := repository.SessionCommandRepository.FindBySessionId(context.TODO(), sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api SessionCommandApi) PagingEndpoint(c echo.Context) error {
|
func (api SessionCommandApi) PagingEndpoint(c echo.Context) error {
|
||||||
|
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||||
|
sessionId := c.QueryParam("sessionId")
|
||||||
|
|
||||||
|
items, total, err := repository.SessionCommandRepository.FindByPage(context.TODO(), pageIndex, pageSize, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"total": 0,
|
"total": total,
|
||||||
"items": []interface{}{},
|
"items": items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api SessionCommandApi) GetEndpoint(c echo.Context) error {
|
func (api SessionCommandApi) GetEndpoint(c echo.Context) error {
|
||||||
return Success(c, nil)
|
id := c.Param("id")
|
||||||
|
var item model.SessionCommand
|
||||||
|
err := repository.SessionCommandRepository.GetDB(context.TODO()).Where("id = ?", id).First(&item).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OperationLogApi struct{}
|
type OperationLogApi struct{}
|
||||||
@@ -31,17 +54,55 @@ func (api OperationLogApi) AllEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api OperationLogApi) PagingEndpoint(c echo.Context) error {
|
func (api OperationLogApi) PagingEndpoint(c echo.Context) error {
|
||||||
|
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||||
|
accountName := c.QueryParam("accountName")
|
||||||
|
action := c.QueryParam("action")
|
||||||
|
status := c.QueryParam("status")
|
||||||
|
order := c.QueryParam("order")
|
||||||
|
field := c.QueryParam("field")
|
||||||
|
|
||||||
|
items, total, err := repository.OperationLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, accountName, action, status, order, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0)
|
||||||
|
for _, item := range items {
|
||||||
|
result = append(result, maps.Map{
|
||||||
|
"id": item.ID,
|
||||||
|
"accountId": item.AccountId,
|
||||||
|
"accountName": item.AccountName,
|
||||||
|
"action": item.Action,
|
||||||
|
"content": item.Content,
|
||||||
|
"ip": item.IP,
|
||||||
|
"region": item.Region,
|
||||||
|
"userAgent": item.UserAgent,
|
||||||
|
"status": item.Status,
|
||||||
|
"errorMessage": item.ErrorMessage,
|
||||||
|
"remark": item.Remark,
|
||||||
|
"createdAt": item.Created,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"total": 0,
|
"total": total,
|
||||||
"items": []interface{}{},
|
"items": result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api OperationLogApi) ClearEndpoint(c echo.Context) error {
|
func (api OperationLogApi) ClearEndpoint(c echo.Context) error {
|
||||||
|
if err := repository.OperationLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api OperationLogApi) DeleteEndpoint(c echo.Context) error {
|
func (api OperationLogApi) DeleteEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := repository.OperationLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,17 +172,57 @@ func (api FilesystemLogApi) AllEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api FilesystemLogApi) PagingEndpoint(c echo.Context) error {
|
func (api FilesystemLogApi) PagingEndpoint(c echo.Context) error {
|
||||||
|
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||||
|
action := c.QueryParam("action")
|
||||||
|
|
||||||
|
items, total, err := repository.FilesystemLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, action)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0)
|
||||||
|
for _, item := range items {
|
||||||
|
var assetName, userName string
|
||||||
|
if item.AssetId != "" {
|
||||||
|
asset, _ := repository.AssetRepository.FindById(context.TODO(), item.AssetId)
|
||||||
|
assetName = asset.Name
|
||||||
|
}
|
||||||
|
if item.UserId != "" {
|
||||||
|
user, _ := repository.UserRepository.FindById(context.TODO(), item.UserId)
|
||||||
|
userName = user.Username
|
||||||
|
}
|
||||||
|
result = append(result, maps.Map{
|
||||||
|
"id": item.ID,
|
||||||
|
"assetId": item.AssetId,
|
||||||
|
"sessionId": item.SessionId,
|
||||||
|
"userId": item.UserId,
|
||||||
|
"action": item.Action,
|
||||||
|
"fileName": item.FileName,
|
||||||
|
"createdAt": item.Created,
|
||||||
|
"assetName": assetName,
|
||||||
|
"userName": userName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"total": 0,
|
"total": total,
|
||||||
"items": []interface{}{},
|
"items": result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api FilesystemLogApi) DeleteEndpoint(c echo.Context) error {
|
func (api FilesystemLogApi) DeleteEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := repository.FilesystemLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api FilesystemLogApi) ClearEndpoint(c echo.Context) error {
|
func (api FilesystemLogApi) ClearEndpoint(c echo.Context) error {
|
||||||
|
if err := repository.FilesystemLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,17 +262,56 @@ func (api AccessLogApi) AllEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api AccessLogApi) PagingEndpoint(c echo.Context) error {
|
func (api AccessLogApi) PagingEndpoint(c echo.Context) error {
|
||||||
|
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||||
|
domain := c.QueryParam("domain")
|
||||||
|
websiteId := c.QueryParam("websiteId")
|
||||||
|
accountId := c.QueryParam("accountId")
|
||||||
|
|
||||||
|
items, total, err := repository.AccessLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, domain, websiteId, accountId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0)
|
||||||
|
for _, item := range items {
|
||||||
|
result = append(result, maps.Map{
|
||||||
|
"id": item.ID,
|
||||||
|
"domain": item.Domain,
|
||||||
|
"websiteId": item.WebsiteId,
|
||||||
|
"accountId": item.AccountId,
|
||||||
|
"method": item.Method,
|
||||||
|
"uri": item.Uri,
|
||||||
|
"statusCode": item.StatusCode,
|
||||||
|
"responseSize": item.ResponseSize,
|
||||||
|
"clientIp": item.ClientIp,
|
||||||
|
"region": item.Region,
|
||||||
|
"userAgent": item.UserAgent,
|
||||||
|
"referer": item.Referer,
|
||||||
|
"requestTime": item.RequestTime,
|
||||||
|
"responseTime": item.ResponseTime,
|
||||||
|
"createdAt": item.Created,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"total": 0,
|
"total": total,
|
||||||
"items": []interface{}{},
|
"items": result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api AccessLogApi) DeleteEndpoint(c echo.Context) error {
|
func (api AccessLogApi) DeleteEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := repository.AccessLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api AccessLogApi) ClearEndpoint(c echo.Context) error {
|
func (api AccessLogApi) ClearEndpoint(c echo.Context) error {
|
||||||
|
if err := repository.AccessLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -537,13 +537,59 @@ func (api DbWorkOrderApi) DeleteEndpoint(c echo.Context) error {
|
|||||||
type DatabaseSQLLogApi struct{}
|
type DatabaseSQLLogApi struct{}
|
||||||
|
|
||||||
func (api DatabaseSQLLogApi) PagingEndpoint(c echo.Context) error {
|
func (api DatabaseSQLLogApi) PagingEndpoint(c echo.Context) error {
|
||||||
|
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||||
|
assetId := c.QueryParam("assetId")
|
||||||
|
userId := c.QueryParam("userId")
|
||||||
|
status := c.QueryParam("status")
|
||||||
|
source := c.QueryParam("source")
|
||||||
|
order := c.QueryParam("order")
|
||||||
|
field := c.QueryParam("field")
|
||||||
|
|
||||||
|
items, total, err := repository.DatabaseSQLLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, assetId, userId, status, source, order, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0)
|
||||||
|
for _, item := range items {
|
||||||
|
var assetName, userName string
|
||||||
|
if item.AssetId != "" {
|
||||||
|
asset, _ := repository.DatabaseAssetRepository.FindById(context.TODO(), item.AssetId)
|
||||||
|
assetName = asset.Name
|
||||||
|
}
|
||||||
|
if item.UserId != "" {
|
||||||
|
user, _ := repository.UserRepository.FindById(context.TODO(), item.UserId)
|
||||||
|
userName = user.Username
|
||||||
|
}
|
||||||
|
result = append(result, maps.Map{
|
||||||
|
"id": item.ID,
|
||||||
|
"assetId": item.AssetId,
|
||||||
|
"assetName": assetName,
|
||||||
|
"database": item.Database,
|
||||||
|
"userId": item.UserId,
|
||||||
|
"userName": userName,
|
||||||
|
"clientIp": item.ClientIP,
|
||||||
|
"sql": item.SQL,
|
||||||
|
"durationMs": item.DurationMs,
|
||||||
|
"rowsAffected": item.RowsAffected,
|
||||||
|
"status": item.Status,
|
||||||
|
"errorMessage": item.ErrorMessage,
|
||||||
|
"source": item.Source,
|
||||||
|
"createdAt": item.Created,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Success(c, maps.Map{
|
return Success(c, maps.Map{
|
||||||
"total": 0,
|
"total": total,
|
||||||
"items": []interface{}{},
|
"items": result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api DatabaseSQLLogApi) ClearEndpoint(c echo.Context) error {
|
func (api DatabaseSQLLogApi) ClearEndpoint(c echo.Context) error {
|
||||||
|
if err := repository.DatabaseSQLLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -188,7 +188,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||||
} else {
|
} else {
|
||||||
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
|
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, msg.Content))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,7 @@ func (api WebTerminalApi) AccessTerminalEndpoint(c echo.Context) error {
|
|||||||
termHandler.Write([]byte(msg.Content))
|
termHandler.Write([]byte(msg.Content))
|
||||||
case Ping:
|
case Ping:
|
||||||
termHandler.SendRequest()
|
termHandler.SendRequest()
|
||||||
termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
|
termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, msg.Content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+117
-3
@@ -3,18 +3,27 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"next-terminal/server/common"
|
||||||
"next-terminal/server/common/term"
|
"next-terminal/server/common/term"
|
||||||
"next-terminal/server/dto"
|
"next-terminal/server/dto"
|
||||||
"next-terminal/server/global/session"
|
"next-terminal/server/global/session"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/repository"
|
||||||
|
"next-terminal/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxOutputLength = 10000
|
||||||
|
|
||||||
type TermHandler struct {
|
type TermHandler struct {
|
||||||
sessionId string
|
sessionId string
|
||||||
|
userId string
|
||||||
|
assetId string
|
||||||
isRecording bool
|
isRecording bool
|
||||||
webSocket *websocket.Conn
|
webSocket *websocket.Conn
|
||||||
nextTerminal *term.NextTerminal
|
nextTerminal *term.NextTerminal
|
||||||
@@ -24,6 +33,10 @@ type TermHandler struct {
|
|||||||
tick *time.Ticker
|
tick *time.Ticker
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
|
commandBuf bytes.Buffer
|
||||||
|
outputBuf bytes.Buffer
|
||||||
|
lastCommand *model.SessionCommand
|
||||||
|
outputMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
|
func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
|
||||||
@@ -32,6 +45,8 @@ func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *web
|
|||||||
|
|
||||||
return &TermHandler{
|
return &TermHandler{
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
|
userId: userId,
|
||||||
|
assetId: assetId,
|
||||||
isRecording: isRecording,
|
isRecording: isRecording,
|
||||||
webSocket: ws,
|
webSocket: ws,
|
||||||
nextTerminal: nextTerminal,
|
nextTerminal: nextTerminal,
|
||||||
@@ -48,9 +63,9 @@ func (r *TermHandler) Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *TermHandler) Stop() {
|
func (r *TermHandler) Stop() {
|
||||||
// 会话结束时记录最后一个命令
|
|
||||||
r.tick.Stop()
|
r.tick.Stop()
|
||||||
r.cancel()
|
r.cancel()
|
||||||
|
r.saveLastCommandOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TermHandler) readFormTunnel() {
|
func (r *TermHandler) readFormTunnel() {
|
||||||
@@ -86,8 +101,8 @@ func (r *TermHandler) writeToWebsocket() {
|
|||||||
if r.isRecording && r.nextTerminal.Recorder != nil {
|
if r.isRecording && r.nextTerminal.Recorder != nil {
|
||||||
_ = r.nextTerminal.Recorder.WriteData(s)
|
_ = r.nextTerminal.Recorder.WriteData(s)
|
||||||
}
|
}
|
||||||
// 监控
|
|
||||||
SendObData(r.sessionId, s)
|
SendObData(r.sessionId, s)
|
||||||
|
r.collectOutput(s)
|
||||||
r.buf.Reset()
|
r.buf.Reset()
|
||||||
case data := <-r.dataChan:
|
case data := <-r.dataChan:
|
||||||
if data != utf8.RuneError {
|
if data != utf8.RuneError {
|
||||||
@@ -101,12 +116,111 @@ func (r *TermHandler) writeToWebsocket() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *TermHandler) collectOutput(output string) {
|
||||||
|
r.outputMutex.Lock()
|
||||||
|
defer r.outputMutex.Unlock()
|
||||||
|
if r.outputBuf.Len()+len(output) <= maxOutputLength {
|
||||||
|
r.outputBuf.WriteString(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TermHandler) getAndClearOutput() string {
|
||||||
|
r.outputMutex.Lock()
|
||||||
|
defer r.outputMutex.Unlock()
|
||||||
|
output := r.outputBuf.String()
|
||||||
|
r.outputBuf.Reset()
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TermHandler) saveLastCommandOutput() {
|
||||||
|
r.outputMutex.Lock()
|
||||||
|
defer r.outputMutex.Unlock()
|
||||||
|
if r.lastCommand != nil && r.outputBuf.Len() > 0 {
|
||||||
|
output := cleanOutput(r.outputBuf.String())
|
||||||
|
if len(output) > maxOutputLength {
|
||||||
|
output = output[:maxOutputLength]
|
||||||
|
}
|
||||||
|
r.lastCommand.Output = output
|
||||||
|
_ = repository.SessionCommandRepository.Create(context.TODO(), r.lastCommand)
|
||||||
|
r.lastCommand = nil
|
||||||
|
r.outputBuf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanOutput(output string) string {
|
||||||
|
output = strings.ReplaceAll(output, "\x1b[0m", "")
|
||||||
|
output = strings.ReplaceAll(output, "\x1b[?2004h", "")
|
||||||
|
output = strings.ReplaceAll(output, "\x1b[?2004l", "")
|
||||||
|
var result strings.Builder
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
result.WriteString(line)
|
||||||
|
result.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(result.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (r *TermHandler) Write(input []byte) error {
|
func (r *TermHandler) Write(input []byte) error {
|
||||||
// 正常的字符输入
|
for _, b := range input {
|
||||||
|
switch b {
|
||||||
|
case 13: // Enter key
|
||||||
|
command := strings.TrimSpace(r.commandBuf.String())
|
||||||
|
if command != "" {
|
||||||
|
r.saveLastCommandOutput()
|
||||||
|
r.lastCommand = &model.SessionCommand{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
SessionId: r.sessionId,
|
||||||
|
RiskLevel: detectRiskLevel(command),
|
||||||
|
Command: command,
|
||||||
|
Output: "",
|
||||||
|
Created: common.NowJsonTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.commandBuf.Reset()
|
||||||
|
case 127, 8: // Backspace, Delete
|
||||||
|
if r.commandBuf.Len() > 0 {
|
||||||
|
r.commandBuf.Truncate(r.commandBuf.Len() - 1)
|
||||||
|
}
|
||||||
|
case 27: // Escape sequence (arrow keys, etc.)
|
||||||
|
default:
|
||||||
|
if b >= 32 && b < 127 { // Printable ASCII
|
||||||
|
r.commandBuf.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_, err := r.nextTerminal.Write(input)
|
_, err := r.nextTerminal.Write(input)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectRiskLevel(command string) int {
|
||||||
|
highRiskPatterns := []string{
|
||||||
|
"rm -rf /", "rm -rf /*", "mkfs", "dd if=", "> /dev/sd",
|
||||||
|
"chmod -R 777", "chown -R", ":(){ :|:& };:",
|
||||||
|
}
|
||||||
|
mediumRiskPatterns := []string{
|
||||||
|
"rm -rf", "rm -r", "passwd", "useradd", "userdel",
|
||||||
|
"chmod 777", "chmod -R", "chown", "wget", "curl",
|
||||||
|
"apt-get", "yum install", "dnf install", "pacman",
|
||||||
|
"systemctl stop", "systemctl disable", "service stop",
|
||||||
|
"iptables -F", "ufw disable", "setenforce 0",
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerCmd := strings.ToLower(command)
|
||||||
|
for _, pattern := range highRiskPatterns {
|
||||||
|
if strings.Contains(lowerCmd, strings.ToLower(pattern)) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pattern := range mediumRiskPatterns {
|
||||||
|
if strings.Contains(lowerCmd, strings.ToLower(pattern)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (r *TermHandler) WindowChange(h int, w int) error {
|
func (r *TermHandler) WindowChange(h int, w int) error {
|
||||||
return r.nextTerminal.WindowChange(h, w)
|
return r.nextTerminal.WindowChange(h, w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ func (userApi UserApi) CreateEndpoint(c echo.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := service.UserService.CreateUser(item); err != nil {
|
if err := service.UserService.CreateUser(item); err != nil {
|
||||||
|
recordOperationLog(c, "user-add", "创建用户: "+item.Username, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "user-add", "创建用户: "+item.Username, "success", "")
|
||||||
return Success(c, item)
|
return Success(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +70,11 @@ func (userApi UserApi) UpdateEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := service.UserService.UpdateUser(id, item); err != nil {
|
if err := service.UserService.UpdateUser(id, item); err != nil {
|
||||||
|
recordOperationLog(c, "user-edit", "更新用户: "+item.Username, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "user-edit", "更新用户: "+item.Username, "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +87,11 @@ func (userApi UserApi) UpdateStatusEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := service.UserService.UpdateStatusById(id, status); err != nil {
|
if err := service.UserService.UpdateStatusById(id, status); err != nil {
|
||||||
|
recordOperationLog(c, "user-edit", "更新用户状态", "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "user-edit", "更新用户状态", "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +108,12 @@ func (userApi UserApi) DeleteEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "不允许删除自身账户")
|
return Fail(c, -1, "不允许删除自身账户")
|
||||||
}
|
}
|
||||||
if err := service.UserService.DeleteUserById(userId); err != nil {
|
if err := service.UserService.DeleteUserById(userId); err != nil {
|
||||||
|
recordOperationLog(c, "user-del", "删除用户: "+ids, "failed", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordOperationLog(c, "user-del", "删除用户: "+ids, "success", "")
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func buildWebsiteGroupTree(groups []model.WebsiteGroup, parentId string) []maps.
|
|||||||
"id": g.ID,
|
"id": g.ID,
|
||||||
"name": g.Name,
|
"name": g.Name,
|
||||||
"title": g.Name,
|
"title": g.Name,
|
||||||
"key": g.ID,
|
"key": "wgroup_" + g.ID,
|
||||||
"value": g.ID,
|
"value": g.ID,
|
||||||
}
|
}
|
||||||
children := buildWebsiteGroupTree(groups, g.ID)
|
children := buildWebsiteGroupTree(groups, g.ID)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"next-terminal/server/common/nt"
|
"next-terminal/server/common/nt"
|
||||||
|
"next-terminal/server/service"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"next-terminal/server/api"
|
"next-terminal/server/api"
|
||||||
@@ -20,7 +21,7 @@ func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := c.RealIP()
|
ip := service.PropertyService.GetClientIP(c)
|
||||||
|
|
||||||
var pass = true
|
var pass = true
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -55,7 +55,7 @@ func setupDB() *gorm.DB {
|
|||||||
&model.Role{}, &model.RoleMenuRef{}, &model.UserRoleRef{},
|
&model.Role{}, &model.RoleMenuRef{}, &model.UserRoleRef{},
|
||||||
&model.LoginPolicy{}, &model.LoginPolicyUserRef{}, &model.TimePeriod{},
|
&model.LoginPolicy{}, &model.LoginPolicyUserRef{}, &model.TimePeriod{},
|
||||||
&model.StorageLog{}, &model.Authorised{}, &model.Logo{}, &model.AssetGroup{},
|
&model.StorageLog{}, &model.Authorised{}, &model.Logo{}, &model.AssetGroup{},
|
||||||
&model.AgentGateway{}, &model.SshGateway{}, &model.GatewayGroup{}, &model.Website{}, &model.Certificate{}, &model.Snippet{}, &model.SessionAudit{}, &model.Department{}, &model.UserDepartmentRef{}, &model.DatabaseAsset{}, &model.CommandFilter{}, &model.CommandFilterRule{}, &model.AuthorisedAsset{}, &model.AuthorisedDatabaseAsset{}, &model.AuthorisedWebsite{}, &model.WebsiteGroup{}); err != nil {
|
&model.AgentGateway{}, &model.SshGateway{}, &model.GatewayGroup{}, &model.Website{}, &model.Certificate{}, &model.Snippet{}, &model.SessionAudit{}, &model.Department{}, &model.UserDepartmentRef{}, &model.DatabaseAsset{}, &model.CommandFilter{}, &model.CommandFilterRule{}, &model.AuthorisedAsset{}, &model.AuthorisedDatabaseAsset{}, &model.AuthorisedWebsite{}, &model.WebsiteGroup{}, &model.AccessLog{}, &model.FilesystemLog{}, &model.OperationLog{}, &model.DatabaseSQLLog{}, &model.SessionCommand{}); err != nil {
|
||||||
panic(fmt.Errorf("初始化数据库表结构异常: %v", err.Error()))
|
panic(fmt.Errorf("初始化数据库表结构异常: %v", err.Error()))
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "next-terminal/server/common"
|
||||||
|
|
||||||
|
type AccessLog struct {
|
||||||
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
|
Domain string `gorm:"type:varchar(500);index" json:"domain"`
|
||||||
|
WebsiteId string `gorm:"type:varchar(36);index" json:"websiteId"`
|
||||||
|
AccountId string `gorm:"type:varchar(36);index" json:"accountId"`
|
||||||
|
Method string `gorm:"type:varchar(10)" json:"method"`
|
||||||
|
Uri string `gorm:"type:varchar(2000)" json:"uri"`
|
||||||
|
StatusCode int `json:"statusCode"`
|
||||||
|
ResponseSize int64 `json:"responseSize"`
|
||||||
|
ClientIp string `gorm:"type:varchar(50);index" json:"clientIp"`
|
||||||
|
Region string `gorm:"type:varchar(100)" json:"region"`
|
||||||
|
UserAgent string `gorm:"type:varchar(500)" json:"userAgent"`
|
||||||
|
Referer string `gorm:"type:varchar(2000)" json:"referer"`
|
||||||
|
RequestTime int `json:"requestTime"`
|
||||||
|
ResponseTime int `json:"responseTime"`
|
||||||
|
Created common.JsonTime `gorm:"type:datetime;index" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLog) TableName() string {
|
||||||
|
return "access_logs"
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"next-terminal/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseSQLLog struct {
|
||||||
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
|
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
|
||||||
|
Database string `gorm:"type:varchar(200)" json:"database"`
|
||||||
|
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
|
||||||
|
ClientIP string `gorm:"type:varchar(50);index" json:"clientIp"`
|
||||||
|
SQL string `gorm:"type:text" json:"sql"`
|
||||||
|
DurationMs int `json:"durationMs"`
|
||||||
|
RowsAffected int `json:"rowsAffected"`
|
||||||
|
Status string `gorm:"type:varchar(20);index" json:"status"`
|
||||||
|
ErrorMessage string `gorm:"type:text" json:"errorMessage"`
|
||||||
|
Source string `gorm:"type:varchar(50);index" json:"source"`
|
||||||
|
Created common.JsonTime `gorm:"type:datetime;index" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DatabaseSQLLog) TableName() string {
|
||||||
|
return "database_sql_logs"
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"next-terminal/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilesystemLog struct {
|
||||||
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
|
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
|
||||||
|
SessionId string `gorm:"index,type:varchar(36)" json:"sessionId"`
|
||||||
|
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
|
||||||
|
Action string `gorm:"type:varchar(50);index" json:"action"`
|
||||||
|
FileName string `gorm:"type:varchar(500)" json:"fileName"`
|
||||||
|
Created common.JsonTime `gorm:"type:datetime;index" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FilesystemLog) TableName() string {
|
||||||
|
return "filesystem_logs"
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"next-terminal/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OperationLog struct {
|
||||||
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
|
AccountId string `gorm:"index,type:varchar(36)" json:"accountId"`
|
||||||
|
AccountName string `gorm:"type:varchar(200)" json:"accountName"`
|
||||||
|
Action string `gorm:"type:varchar(100);index" json:"action"`
|
||||||
|
Content string `gorm:"type:text" json:"content"`
|
||||||
|
IP string `gorm:"type:varchar(50);index" json:"ip"`
|
||||||
|
Region string `gorm:"type:varchar(200)" json:"region"`
|
||||||
|
UserAgent string `gorm:"type:varchar(500)" json:"userAgent"`
|
||||||
|
Status string `gorm:"type:varchar(20);index" json:"status"`
|
||||||
|
ErrorMessage string `gorm:"type:text" json:"errorMessage"`
|
||||||
|
Remark string `gorm:"type:varchar(500)" json:"remark"`
|
||||||
|
Created common.JsonTime `gorm:"type:datetime;index" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OperationLog) TableName() string {
|
||||||
|
return "operation_logs"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"next-terminal/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionCommand struct {
|
||||||
|
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
|
||||||
|
SessionId string `gorm:"index,type:varchar(36)" json:"sessionId"`
|
||||||
|
RiskLevel int `json:"riskLevel"`
|
||||||
|
Command string `gorm:"type:text" json:"command"`
|
||||||
|
Output string `gorm:"type:text" json:"output"`
|
||||||
|
Created common.JsonTime `gorm:"type:datetime;index" json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionCommand) TableName() string {
|
||||||
|
return "session_commands"
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"next-terminal/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AccessLogRepository = new(accessLogRepository)
|
||||||
|
|
||||||
|
type accessLogRepository struct {
|
||||||
|
baseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) Create(c context.Context, o *model.AccessLog) error {
|
||||||
|
return r.GetDB(c).Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) FindById(c context.Context, id string) (o model.AccessLog, err error) {
|
||||||
|
err = r.GetDB(c).Where("id = ?", id).First(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) FindByPage(c context.Context, pageIndex, pageSize int, domain, websiteId, accountId string) (o []model.AccessLog, total int64, err error) {
|
||||||
|
db := r.GetDB(c).Model(&model.AccessLog{})
|
||||||
|
if domain != "" {
|
||||||
|
db = db.Where("domain LIKE ?", "%"+domain+"%")
|
||||||
|
}
|
||||||
|
if websiteId != "" {
|
||||||
|
db = db.Where("website_id = ?", websiteId)
|
||||||
|
}
|
||||||
|
if accountId != "" {
|
||||||
|
db = db.Where("account_id = ?", accountId)
|
||||||
|
}
|
||||||
|
err = db.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) DeleteById(c context.Context, id string) error {
|
||||||
|
return r.GetDB(c).Where("id = ?", id).Delete(&model.AccessLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) DeleteAll(c context.Context) error {
|
||||||
|
return r.GetDB(c).Where("1 = 1").Delete(&model.AccessLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) FindOutTimeLog(c context.Context, limit int) (o []model.AccessLog, err error) {
|
||||||
|
err = r.GetDB(c).Where("strftime('%s', created) < strftime('%s', 'now', '-' || ? || ' days')", limit).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r accessLogRepository) Count(c context.Context) (total int64, err error) {
|
||||||
|
err = r.GetDB(c).Model(&model.AccessLog{}).Count(&total).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"next-terminal/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DatabaseSQLLogRepository = new(databaseSQLLogRepository)
|
||||||
|
|
||||||
|
type databaseSQLLogRepository struct {
|
||||||
|
baseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) FindByPage(c context.Context, pageIndex, pageSize int, assetId, userId, status, source string, order, field string) (o []model.DatabaseSQLLog, total int64, err error) {
|
||||||
|
m := model.DatabaseSQLLog{}
|
||||||
|
db := r.GetDB(c).Table(m.TableName())
|
||||||
|
dbCounter := r.GetDB(c).Table(m.TableName())
|
||||||
|
|
||||||
|
if assetId != "" {
|
||||||
|
db = db.Where("asset_id = ?", assetId)
|
||||||
|
dbCounter = dbCounter.Where("asset_id = ?", assetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userId != "" {
|
||||||
|
db = db.Where("user_id = ?", userId)
|
||||||
|
dbCounter = dbCounter.Where("user_id = ?", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != "" {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
dbCounter = dbCounter.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if source != "" {
|
||||||
|
db = db.Where("source = ?", source)
|
||||||
|
dbCounter = dbCounter.Where("source = ?", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbCounter.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orderBy := "created desc"
|
||||||
|
if order != "" && field != "" {
|
||||||
|
orderBy = field + " " + order
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Order(orderBy).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
|
||||||
|
if o == nil {
|
||||||
|
o = make([]model.DatabaseSQLLog, 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) Create(c context.Context, o *model.DatabaseSQLLog) error {
|
||||||
|
return r.GetDB(c).Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) DeleteById(c context.Context, id string) error {
|
||||||
|
return r.GetDB(c).Where("id = ?", id).Delete(&model.DatabaseSQLLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) DeleteAll(c context.Context) error {
|
||||||
|
return r.GetDB(c).Where("1 = 1").Delete(&model.DatabaseSQLLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) FindOutTimeLog(c context.Context, dayLimit int) (o []model.DatabaseSQLLog, err error) {
|
||||||
|
limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour)
|
||||||
|
err = r.GetDB(c).Where("created < ?", limitTime).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r databaseSQLLogRepository) Count(c context.Context) (total int64, err error) {
|
||||||
|
err = r.GetDB(c).Model(&model.DatabaseSQLLog{}).Count(&total).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"next-terminal/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FilesystemLogRepository = new(filesystemLogRepository)
|
||||||
|
|
||||||
|
type filesystemLogRepository struct {
|
||||||
|
baseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) FindByPage(c context.Context, pageIndex, pageSize int, action string) (o []model.FilesystemLog, total int64, err error) {
|
||||||
|
m := model.FilesystemLog{}
|
||||||
|
db := r.GetDB(c).Table(m.TableName())
|
||||||
|
dbCounter := r.GetDB(c).Table(m.TableName())
|
||||||
|
|
||||||
|
if action != "" {
|
||||||
|
db = db.Where("action = ?", action)
|
||||||
|
dbCounter = dbCounter.Where("action = ?", action)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbCounter.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
|
||||||
|
if o == nil {
|
||||||
|
o = make([]model.FilesystemLog, 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) Create(c context.Context, o *model.FilesystemLog) error {
|
||||||
|
return r.GetDB(c).Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) DeleteById(c context.Context, id string) error {
|
||||||
|
return r.GetDB(c).Where("id = ?", id).Delete(&model.FilesystemLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) DeleteAll(c context.Context) error {
|
||||||
|
return r.GetDB(c).Where("1 = 1").Delete(&model.FilesystemLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) FindOutTimeLog(c context.Context, dayLimit int) (o []model.FilesystemLog, err error) {
|
||||||
|
limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour)
|
||||||
|
err = r.GetDB(c).Where("created < ?", limitTime).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r filesystemLogRepository) Count(c context.Context) (total int64, err error) {
|
||||||
|
err = r.GetDB(c).Model(&model.FilesystemLog{}).Count(&total).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"next-terminal/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var OperationLogRepository = new(operationLogRepository)
|
||||||
|
|
||||||
|
type operationLogRepository struct {
|
||||||
|
baseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) FindByPage(c context.Context, pageIndex, pageSize int, accountName, action, status string, order, field string) (o []model.OperationLog, total int64, err error) {
|
||||||
|
m := model.OperationLog{}
|
||||||
|
db := r.GetDB(c).Table(m.TableName())
|
||||||
|
dbCounter := r.GetDB(c).Table(m.TableName())
|
||||||
|
|
||||||
|
if accountName != "" {
|
||||||
|
db = db.Where("account_name like ?", "%"+accountName+"%")
|
||||||
|
dbCounter = dbCounter.Where("account_name like ?", "%"+accountName+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if action != "" {
|
||||||
|
db = db.Where("action = ?", action)
|
||||||
|
dbCounter = dbCounter.Where("action = ?", action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != "" {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
dbCounter = dbCounter.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbCounter.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orderBy := "created desc"
|
||||||
|
if order != "" && field != "" {
|
||||||
|
orderBy = field + " " + order
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Order(orderBy).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
|
||||||
|
if o == nil {
|
||||||
|
o = make([]model.OperationLog, 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) Create(c context.Context, o *model.OperationLog) error {
|
||||||
|
return r.GetDB(c).Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) DeleteById(c context.Context, id string) error {
|
||||||
|
return r.GetDB(c).Where("id = ?", id).Delete(&model.OperationLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) DeleteAll(c context.Context) error {
|
||||||
|
return r.GetDB(c).Where("1 = 1").Delete(&model.OperationLog{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) FindOutTimeLog(c context.Context, dayLimit int) (o []model.OperationLog, err error) {
|
||||||
|
limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour)
|
||||||
|
err = r.GetDB(c).Where("created < ?", limitTime).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r operationLogRepository) Count(c context.Context) (total int64, err error) {
|
||||||
|
err = r.GetDB(c).Model(&model.OperationLog{}).Count(&total).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"next-terminal/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SessionCommandRepository = new(sessionCommandRepository)
|
||||||
|
|
||||||
|
type sessionCommandRepository struct {
|
||||||
|
baseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionCommandRepository) FindBySessionId(c context.Context, sessionId string) (o []model.SessionCommand, err error) {
|
||||||
|
err = r.GetDB(c).Where("session_id = ?", sessionId).Order("created asc").Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionCommandRepository) FindByPage(c context.Context, pageIndex, pageSize int, sessionId string) (o []model.SessionCommand, total int64, err error) {
|
||||||
|
m := model.SessionCommand{}
|
||||||
|
db := r.GetDB(c).Table(m.TableName())
|
||||||
|
dbCounter := r.GetDB(c).Table(m.TableName())
|
||||||
|
|
||||||
|
if sessionId != "" {
|
||||||
|
db = db.Where("session_id = ?", sessionId)
|
||||||
|
dbCounter = dbCounter.Where("session_id = ?", sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbCounter.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Order("created desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error
|
||||||
|
if o == nil {
|
||||||
|
o = make([]model.SessionCommand, 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionCommandRepository) Create(c context.Context, o *model.SessionCommand) error {
|
||||||
|
return r.GetDB(c).Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionCommandRepository) DeleteBySessionId(c context.Context, sessionId string) error {
|
||||||
|
return r.GetDB(c).Where("session_id = ?", sessionId).Delete(&model.SessionCommand{}).Error
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"next-terminal/server/common"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/repository"
|
||||||
|
"next-terminal/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DatabaseSQLLogService = new(databaseSQLLogService)
|
||||||
|
|
||||||
|
type databaseSQLLogService struct {
|
||||||
|
baseService
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseSQLLogParams struct {
|
||||||
|
AssetId string
|
||||||
|
Database string
|
||||||
|
UserId string
|
||||||
|
ClientIP string
|
||||||
|
SQL string
|
||||||
|
DurationMs int
|
||||||
|
RowsAffected int
|
||||||
|
Status string
|
||||||
|
ErrorMessage string
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s databaseSQLLogService) Record(ctx context.Context, params DatabaseSQLLogParams) error {
|
||||||
|
log := &model.DatabaseSQLLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
AssetId: params.AssetId,
|
||||||
|
Database: params.Database,
|
||||||
|
UserId: params.UserId,
|
||||||
|
ClientIP: params.ClientIP,
|
||||||
|
SQL: params.SQL,
|
||||||
|
DurationMs: params.DurationMs,
|
||||||
|
RowsAffected: params.RowsAffected,
|
||||||
|
Status: params.Status,
|
||||||
|
ErrorMessage: params.ErrorMessage,
|
||||||
|
Source: params.Source,
|
||||||
|
Created: common.NowJsonTime(),
|
||||||
|
}
|
||||||
|
return repository.DatabaseSQLLogRepository.Create(ctx, log)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"next-terminal/server/common"
|
||||||
|
"next-terminal/server/model"
|
||||||
|
"next-terminal/server/repository"
|
||||||
|
"next-terminal/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var OperationLogService = new(operationLogService)
|
||||||
|
|
||||||
|
type operationLogService struct {
|
||||||
|
baseService
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationLogParams struct {
|
||||||
|
AccountId string
|
||||||
|
AccountName string
|
||||||
|
Action string
|
||||||
|
Content string
|
||||||
|
IP string
|
||||||
|
Region string
|
||||||
|
UserAgent string
|
||||||
|
Status string
|
||||||
|
ErrorMessage string
|
||||||
|
Remark string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s operationLogService) Record(ctx context.Context, params OperationLogParams) error {
|
||||||
|
log := &model.OperationLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
AccountId: params.AccountId,
|
||||||
|
AccountName: params.AccountName,
|
||||||
|
Action: params.Action,
|
||||||
|
Content: params.Content,
|
||||||
|
IP: params.IP,
|
||||||
|
Region: params.Region,
|
||||||
|
UserAgent: params.UserAgent,
|
||||||
|
Status: params.Status,
|
||||||
|
ErrorMessage: params.ErrorMessage,
|
||||||
|
Remark: params.Remark,
|
||||||
|
Created: common.NowJsonTime(),
|
||||||
|
}
|
||||||
|
return repository.OperationLogRepository.Create(ctx, log)
|
||||||
|
}
|
||||||
@@ -4,12 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"next-terminal/server/common/guacamole"
|
"next-terminal/server/common/guacamole"
|
||||||
"next-terminal/server/env"
|
"next-terminal/server/env"
|
||||||
"next-terminal/server/model"
|
"next-terminal/server/model"
|
||||||
"next-terminal/server/repository"
|
"next-terminal/server/repository"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,10 +43,19 @@ var defaultProperties = map[string]string{
|
|||||||
guacamole.EnableMenuAnimations: "true",
|
guacamole.EnableMenuAnimations: "true",
|
||||||
guacamole.DisableBitmapCaching: "false",
|
guacamole.DisableBitmapCaching: "false",
|
||||||
guacamole.DisableOffscreenCaching: "false",
|
guacamole.DisableOffscreenCaching: "false",
|
||||||
|
guacamole.ColorDepth: "",
|
||||||
|
guacamole.Cursor: "",
|
||||||
|
guacamole.SwapRedBlue: "false",
|
||||||
"cron-log-saved-limit": "360",
|
"cron-log-saved-limit": "360",
|
||||||
"login-log-saved-limit": "360",
|
"login-log-saved-limit": "360",
|
||||||
"session-saved-limit": "360",
|
"session-saved-limit": "360",
|
||||||
|
"access-log-saved-limit": "30",
|
||||||
|
"filesystem-log-saved-limit": "30",
|
||||||
|
"operation-log-saved-limit": "30",
|
||||||
|
"database-sql-log-saved-limit": "30",
|
||||||
"user-default-storage-size": "5120",
|
"user-default-storage-size": "5120",
|
||||||
|
"ip-extractor": "direct",
|
||||||
|
"ip-trust-list": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service propertyService) InitProperties() error {
|
func (service propertyService) InitProperties() error {
|
||||||
@@ -60,6 +72,9 @@ func (service propertyService) InitProperties() error {
|
|||||||
|
|
||||||
func (service propertyService) CreateIfAbsent(propertyMap map[string]string, name, value string) error {
|
func (service propertyService) CreateIfAbsent(propertyMap map[string]string, name, value string) error {
|
||||||
if len(propertyMap[name]) == 0 {
|
if len(propertyMap[name]) == 0 {
|
||||||
|
if value == "" {
|
||||||
|
value = "-"
|
||||||
|
}
|
||||||
property := model.Property{
|
property := model.Property{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -117,3 +132,76 @@ func (service propertyService) Update(item map[string]interface{}) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service propertyService) GetClientIP(c echo.Context) string {
|
||||||
|
propertyMap := repository.PropertyRepository.FindAllMap(context.TODO())
|
||||||
|
extractor := propertyMap["ip-extractor"]
|
||||||
|
trustList := propertyMap["ip-trust-list"]
|
||||||
|
|
||||||
|
directIP := c.RealIP()
|
||||||
|
|
||||||
|
if extractor == "" || extractor == "direct" {
|
||||||
|
return directIP
|
||||||
|
}
|
||||||
|
|
||||||
|
if !service.isTrustedIP(directIP, trustList) {
|
||||||
|
return directIP
|
||||||
|
}
|
||||||
|
|
||||||
|
switch extractor {
|
||||||
|
case "x-real-ip":
|
||||||
|
xRealIP := c.Request().Header.Get("X-Real-IP")
|
||||||
|
if xRealIP != "" {
|
||||||
|
return xRealIP
|
||||||
|
}
|
||||||
|
case "x-forwarded-for":
|
||||||
|
xForwardedFor := c.Request().Header.Get("X-Forwarded-For")
|
||||||
|
if xForwardedFor != "" {
|
||||||
|
ips := strings.Split(xForwardedFor, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
ip := strings.TrimSpace(ips[0])
|
||||||
|
if ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return directIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service propertyService) isTrustedIP(clientIP string, trustList string) bool {
|
||||||
|
if trustList == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
trustIPs := strings.Split(trustList, ",")
|
||||||
|
clientIPAddr := net.ParseIP(clientIP)
|
||||||
|
if clientIPAddr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, trustIP := range trustIPs {
|
||||||
|
trustIP = strings.TrimSpace(trustIP)
|
||||||
|
if trustIP == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(trustIP, "/") {
|
||||||
|
_, ipNet, err := net.ParseCIDR(trustIP)
|
||||||
|
if err == nil && ipNet.Contains(clientIPAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if trustIP == clientIP {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
trustIPAddr := net.ParseIP(trustIP)
|
||||||
|
if trustIPAddr != nil && trustIPAddr.Equal(clientIPAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ func (t *Ticker) SetupTicker() {
|
|||||||
deleteOutTimeSession()
|
deleteOutTimeSession()
|
||||||
deleteOutTimeLoginLog()
|
deleteOutTimeLoginLog()
|
||||||
deleteOutTimeJobLog()
|
deleteOutTimeJobLog()
|
||||||
|
deleteOutTimeAccessLog()
|
||||||
|
deleteOutTimeFilesystemLog()
|
||||||
|
deleteOutTimeOperationLog()
|
||||||
|
deleteOutTimeDatabaseSQLLog()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -297,3 +301,115 @@ func deleteOutTimeJobLog() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteOutTimeAccessLog() {
|
||||||
|
property, err := repository.PropertyRepository.FindByName(context.TODO(), "access-log-saved-limit")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if property.Value == "" || property.Value == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(property.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessLogs, err := repository.AccessLogRepository.FindOutTimeLog(context.TODO(), limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accessLogs) > 0 {
|
||||||
|
for i := range accessLogs {
|
||||||
|
err := repository.AccessLogRepository.DeleteById(context.TODO(), accessLogs[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("删除访问日志失败", log.NamedError("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteOutTimeFilesystemLog() {
|
||||||
|
property, err := repository.PropertyRepository.FindByName(context.TODO(), "filesystem-log-saved-limit")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if property.Value == "" || property.Value == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(property.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := repository.FilesystemLogRepository.FindOutTimeLog(context.TODO(), limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) > 0 {
|
||||||
|
for i := range logs {
|
||||||
|
err := repository.FilesystemLogRepository.DeleteById(context.TODO(), logs[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("删除文件系统日志失败", log.NamedError("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteOutTimeOperationLog() {
|
||||||
|
property, err := repository.PropertyRepository.FindByName(context.TODO(), "operation-log-saved-limit")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if property.Value == "" || property.Value == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(property.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := repository.OperationLogRepository.FindOutTimeLog(context.TODO(), limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) > 0 {
|
||||||
|
for i := range logs {
|
||||||
|
err := repository.OperationLogRepository.DeleteById(context.TODO(), logs[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("删除操作日志失败", log.NamedError("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteOutTimeDatabaseSQLLog() {
|
||||||
|
property, err := repository.PropertyRepository.FindByName(context.TODO(), "database-sql-log-saved-limit")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if property.Value == "" || property.Value == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(property.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := repository.DatabaseSQLLogRepository.FindOutTimeLog(context.TODO(), limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) > 0 {
|
||||||
|
for i := range logs {
|
||||||
|
err := repository.DatabaseSQLLogRepository.DeleteById(context.TODO(), logs[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("删除数据库SQL日志失败", log.NamedError("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,10 +160,15 @@ const AccessPage = () => {
|
|||||||
|
|
||||||
// 处理树节点双击
|
// 处理树节点双击
|
||||||
const handleNodeDoubleClick = useCallback((node: any) => {
|
const handleNodeDoubleClick = useCallback((node: any) => {
|
||||||
|
let assetId = node.key as string;
|
||||||
|
if (typeof assetId === 'string' && assetId.startsWith('asset_')) {
|
||||||
|
assetId = assetId.substring(6);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否需要 WOL 唤醒
|
// 检查是否需要 WOL 唤醒
|
||||||
if (node.extra?.status === 'inactive' && node.extra?.wolEnabled) {
|
if (node.extra?.status === 'inactive' && node.extra?.wolEnabled) {
|
||||||
setWolAssetInfo({
|
setWolAssetInfo({
|
||||||
id: node.key as string,
|
id: assetId,
|
||||||
name: node.title as string,
|
name: node.title as string,
|
||||||
protocol: node.extra?.protocol,
|
protocol: node.extra?.protocol,
|
||||||
});
|
});
|
||||||
@@ -173,7 +178,7 @@ const AccessPage = () => {
|
|||||||
|
|
||||||
// 直接打开连接
|
// 直接打开连接
|
||||||
openAssetTab({
|
openAssetTab({
|
||||||
id: node.key,
|
id: assetId,
|
||||||
name: node.title,
|
name: node.title,
|
||||||
protocol: node.extra?.protocol,
|
protocol: node.extra?.protocol,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,13 @@ const AccessSshChooser = ({handleOk, handleCancel, open}: Props) => {
|
|||||||
|
|
||||||
const onCheck: TreeProps['onCheck'] = (checkedKeysValue, {checkedNodes}) => {
|
const onCheck: TreeProps['onCheck'] = (checkedKeysValue, {checkedNodes}) => {
|
||||||
// console.log('onCheck', checkedKeysValue, checkedNodes);
|
// console.log('onCheck', checkedKeysValue, checkedNodes);
|
||||||
let keys = checkedNodes.filter(item => item.isLeaf).map((item) => item.key);
|
let keys = checkedNodes.filter(item => item.isLeaf).map((item) => {
|
||||||
|
let key = item.key as string;
|
||||||
|
if (key.startsWith('asset_')) {
|
||||||
|
return key.substring(6);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
});
|
||||||
setSshAssetKeys(keys as string[]);
|
setSshAssetKeys(keys as string[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -762,7 +762,6 @@ const FileSystemPage = forwardRef<FileSystem, Props>(({
|
|||||||
console.log(`Upload response - Status: ${xhr.status}, Response: ${xhr.responseText}`);
|
console.log(`Upload response - Status: ${xhr.status}, Response: ${xhr.responseText}`);
|
||||||
|
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
// 检查响应体是否包含错误信息
|
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
|
|
||||||
@@ -772,15 +771,13 @@ const FileSystemPage = forwardRef<FileSystem, Props>(({
|
|||||||
const result = JSON.parse(responseText);
|
const result = JSON.parse(responseText);
|
||||||
console.log('Upload response parsed:', result);
|
console.log('Upload response parsed:', result);
|
||||||
|
|
||||||
// 检查是否是标准错误响应格式
|
if (result.error === true || (result.code && result.code !== 1 && result.code !== 0)) {
|
||||||
if (result.error === true || (result.message && result.code)) {
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
errorMessage = result.message || 'Upload failed';
|
errorMessage = result.message || 'Upload failed';
|
||||||
console.error('Upload failed with parsed error:', errorMessage);
|
console.error('Upload failed with parsed error:', errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// JSON解析失败,可能是成功的空响应,继续处理为成功
|
|
||||||
console.log('Upload response parse failed, treating as success:', e);
|
console.log('Upload response parse failed, treating as success:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1126,7 +1123,7 @@ const FileSystemPage = forwardRef<FileSystem, Props>(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Drawer title="FileSystem"
|
<Drawer title={t('fs.title')}
|
||||||
placement="right"
|
placement="right"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
open={open}
|
open={open}
|
||||||
@@ -1314,7 +1311,7 @@ const FileSystemPage = forwardRef<FileSystem, Props>(({
|
|||||||
)}
|
)}
|
||||||
<Table
|
<Table
|
||||||
virtual
|
virtual
|
||||||
// scroll={{y: window.innerHeight - 240}}
|
scroll={{y: 400}}
|
||||||
rowKey={'path'}
|
rowKey={'path'}
|
||||||
columns={fileColumns}
|
columns={fileColumns}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ const AssetPage = () => {
|
|||||||
let [groupId, setGroupId] = useState(searchParams.get('groupId') || '');
|
let [groupId, setGroupId] = useState(searchParams.get('groupId') || '');
|
||||||
let [dataSource, setDataSource] = useState<Asset[]>([]);
|
let [dataSource, setDataSource] = useState<Asset[]>([]);
|
||||||
|
|
||||||
|
const handleTreeSelect = (key: string) => {
|
||||||
|
if (key.startsWith('group_')) {
|
||||||
|
setGroupId(key.substring(6));
|
||||||
|
} else if (key.startsWith('asset_')) {
|
||||||
|
setGroupId('');
|
||||||
|
} else {
|
||||||
|
setGroupId(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let [selectedTags, setSelectedTags] = useState<string[]>([]);
|
let [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
let [groupChooserOpen, setGroupChooserOpen] = useState(false);
|
let [groupChooserOpen, setGroupChooserOpen] = useState(false);
|
||||||
let [gatewayChooserOpen, setGatewayChooserOpen] = useState(false);
|
let [gatewayChooserOpen, setGatewayChooserOpen] = useState(false);
|
||||||
@@ -608,7 +618,7 @@ const AssetPage = () => {
|
|||||||
/* 移动端:垂直布局,树在上,标签过�?+ 表格在下 */
|
/* 移动端:垂直布局,树在上,标签过�?+ 表格在下 */
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 bg-white dark:bg-gray-800 rounded-lg">
|
<div className="mb-4 bg-white dark:bg-gray-800 rounded-lg">
|
||||||
<AssetTree selected={groupId} onSelect={setGroupId}/>
|
<AssetTree selected={groupId} onSelect={handleTreeSelect}/>
|
||||||
</div>
|
</div>
|
||||||
{tagFilter}
|
{tagFilter}
|
||||||
<ProTable {...tableProps}/>
|
<ProTable {...tableProps}/>
|
||||||
@@ -621,7 +631,7 @@ const AssetPage = () => {
|
|||||||
)}>
|
)}>
|
||||||
<div className="relative rounded-md bg-gray-50 dark:bg-[#141414]">
|
<div className="relative rounded-md bg-gray-50 dark:bg-[#141414]">
|
||||||
{!isTreeCollapsed && (
|
{!isTreeCollapsed && (
|
||||||
<AssetTree selected={groupId} onSelect={setGroupId}/>
|
<AssetTree selected={groupId} onSelect={handleTreeSelect}/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const AssetTree = ({selected, onSelect}: Props) => {
|
|||||||
let [op, setOP] = useState<OP>();
|
let [op, setOP] = useState<OP>();
|
||||||
let [expandedKeys, setExpandedKeys] = useState([]);
|
let [expandedKeys, setExpandedKeys] = useState([]);
|
||||||
|
|
||||||
let [selectedKeys, setSelectedKeys] = useState<React.Key[]>([selected]);
|
let [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||||
const [theme] = useNTTheme();
|
const [theme] = useNTTheme();
|
||||||
|
|
||||||
let query = useQuery({
|
let query = useQuery({
|
||||||
@@ -38,6 +38,14 @@ const AssetTree = ({selected, onSelect}: Props) => {
|
|||||||
queryFn: assetApi.getGroups,
|
queryFn: assetApi.getGroups,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selected) {
|
||||||
|
setSelectedKeys(['group_' + selected]);
|
||||||
|
} else {
|
||||||
|
setSelectedKeys([]);
|
||||||
|
}
|
||||||
|
}, [selected]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Array.isArray(query.data) && query.data.length > 0) {
|
if (Array.isArray(query.data) && query.data.length > 0) {
|
||||||
setTreeData(query.data);
|
setTreeData(query.data);
|
||||||
@@ -197,7 +205,11 @@ const AssetTree = ({selected, onSelect}: Props) => {
|
|||||||
danger: true,
|
danger: true,
|
||||||
icon: <TrashIcon className={'h-4 w-4'}/>,
|
icon: <TrashIcon className={'h-4 w-4'}/>,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
assetApi.deleteGroup(contextMenu.node.key as string).then(() => {
|
let groupKey = contextMenu.node.key as string;
|
||||||
|
if (groupKey.startsWith('group_')) {
|
||||||
|
groupKey = groupKey.substring(6);
|
||||||
|
}
|
||||||
|
assetApi.deleteGroup(groupKey).then(() => {
|
||||||
query.refetch();
|
query.refetch();
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -205,7 +217,7 @@ const AssetTree = ({selected, onSelect}: Props) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleRightClick = ({event, node}) => {
|
const handleRightClick = ({event, node}) => {
|
||||||
if (node.key === 'default') {
|
if (node.key === 'default' || node.key?.toString().startsWith('asset_')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.log(`handleRightClick`, event, node)
|
// console.log(`handleRightClick`, event, node)
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ const DbProxySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="db-proxy-enabled"
|
name="db-proxy-enabled"
|
||||||
label={t('db.proxy.enabled')}
|
label={t('db.proxy.enabled')}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
|||||||
@@ -18,14 +18,13 @@ const LogSetting = ({get, set}: SettingProps) => {
|
|||||||
style: {display: 'none'}
|
style: {display: 'none'}
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<ProFormSwitch name="recording-enabled"
|
<ProFormSwitch name="enable-recording"
|
||||||
label={t('identity.user.recording')}
|
label={t('identity.user.recording')}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProFormSelect name="session-saved-limit-days"
|
<ProFormSelect name="session-saved-limit"
|
||||||
label={t('settings.log.session.saved_limit_days')}
|
label={t('settings.log.session.saved_limit_days')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
options: [
|
options: [
|
||||||
@@ -40,7 +39,7 @@ const LogSetting = ({get, set}: SettingProps) => {
|
|||||||
}}
|
}}
|
||||||
addonAfter={t('general.days')}
|
addonAfter={t('general.days')}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect name="login-log-saved-limit-days"
|
<ProFormSelect name="login-log-saved-limit"
|
||||||
label={t('settings.log.login_log.saved_limit_days')}
|
label={t('settings.log.login_log.saved_limit_days')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
options: [
|
options: [
|
||||||
@@ -53,7 +52,7 @@ const LogSetting = ({get, set}: SettingProps) => {
|
|||||||
}}
|
}}
|
||||||
addonAfter={t('general.days')}
|
addonAfter={t('general.days')}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect name="cron-log-saved-limit-days"
|
<ProFormSelect name="cron-log-saved-limit"
|
||||||
label={t('settings.log.cron_log.saved_limit_days')}
|
label={t('settings.log.cron_log.saved_limit_days')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
options: [
|
options: [
|
||||||
@@ -66,7 +65,7 @@ const LogSetting = ({get, set}: SettingProps) => {
|
|||||||
}}
|
}}
|
||||||
addonAfter={t('general.days')}
|
addonAfter={t('general.days')}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect name="access-log-saved-limit-days"
|
<ProFormSelect name="access-log-saved-limit"
|
||||||
label={t('settings.log.access_log.saved_limit_days')}
|
label={t('settings.log.access_log.saved_limit_days')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
options: [
|
options: [
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import {Alert, App, Card, Col, Form, Row, Typography} from "antd";
|
import {Alert, App, Card, Col, Row, Typography} from "antd";
|
||||||
import {SettingProps} from "./SettingPage";
|
import {SettingProps} from "./SettingPage";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
|
||||||
import {ProForm, ProFormDigit, ProFormSwitch, ProFormText, ProFormTextArea} from "@ant-design/pro-components";
|
import {ProForm, ProFormDigit, ProFormSwitch, ProFormText, ProFormTextArea} from "@ant-design/pro-components";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import requests from "@/api/core/requests";
|
|
||||||
import propertyApi from "@/api/property-api";
|
import propertyApi from "@/api/property-api";
|
||||||
import {useMobile} from "@/hook/use-mobile";
|
import {useMobile} from "@/hook/use-mobile";
|
||||||
import {cn} from "@/lib/utils";
|
import {cn} from "@/lib/utils";
|
||||||
@@ -15,22 +13,15 @@ const MailSetting = ({get, set}: SettingProps) => {
|
|||||||
|
|
||||||
const { isMobile } = useMobile();
|
const { isMobile } = useMobile();
|
||||||
let {t} = useTranslation();
|
let {t} = useTranslation();
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
let [enabled, setEnabled] = useState(false);
|
let [enabled, setEnabled] = useState(false);
|
||||||
let {message} = App.useApp();
|
let {message} = App.useApp();
|
||||||
|
|
||||||
let query = useQuery({
|
const wrapGet = async () => {
|
||||||
queryKey: ['get-property'],
|
let values = await get();
|
||||||
queryFn: get,
|
setEnabled(values['mail-enabled']);
|
||||||
});
|
return values;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (query.data) {
|
|
||||||
form.setFieldsValue(query.data);
|
|
||||||
setEnabled(query.data['mail-enabled']);
|
|
||||||
}
|
}
|
||||||
}, [query.data]);
|
|
||||||
|
|
||||||
const handleSendTestMail = async (values: any) => {
|
const handleSendTestMail = async (values: any) => {
|
||||||
await propertyApi.sendMail(values);
|
await propertyApi.sendMail(values);
|
||||||
@@ -43,14 +34,13 @@ const MailSetting = ({get, set}: SettingProps) => {
|
|||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={isMobile ? 24 : 12}>
|
<Col span={isMobile ? 24 : 12}>
|
||||||
<Card>
|
<Card>
|
||||||
<ProForm onFinish={set} request={get} submitter={{
|
<ProForm onFinish={set} request={wrapGet} submitter={{
|
||||||
resetButtonProps: {
|
resetButtonProps: {
|
||||||
style: {display: 'none'}
|
style: {display: 'none'}
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<ProFormSwitch name="mail-enabled"
|
<ProFormSwitch name="mail-enabled"
|
||||||
label={t('settings.mail.enabled')}
|
label={t('settings.mail.enabled')}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
|||||||
@@ -19,49 +19,41 @@ const RdpSetting = ({get, set}: SettingProps) => {
|
|||||||
}}>
|
}}>
|
||||||
<ProFormSwitch name="enable-wallpaper"
|
<ProFormSwitch name="enable-wallpaper"
|
||||||
label={t('settings.rdp.enable.wallpaper')}
|
label={t('settings.rdp.enable.wallpaper')}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="enable-theming"
|
<ProFormSwitch name="enable-theming"
|
||||||
label={t("settings.rdp.enable.theming")}
|
label={t("settings.rdp.enable.theming")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="enable-font-smoothing"
|
<ProFormSwitch name="enable-font-smoothing"
|
||||||
label={t("settings.rdp.enable.font_smoothing")}
|
label={t("settings.rdp.enable.font_smoothing")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="enable-full-window-drag"
|
<ProFormSwitch name="enable-full-window-drag"
|
||||||
label={t("settings.rdp.enable.full_window_drag")}
|
label={t("settings.rdp.enable.full_window_drag")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="enable-desktop-composition"
|
<ProFormSwitch name="enable-desktop-composition"
|
||||||
label={t("settings.rdp.enable.desktop_composition")}
|
label={t("settings.rdp.enable.desktop_composition")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="enable-menu-animations"
|
<ProFormSwitch name="enable-menu-animations"
|
||||||
label={t("settings.rdp.enable.menu_animations")}
|
label={t("settings.rdp.enable.menu_animations")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="disable-bitmap-caching"
|
<ProFormSwitch name="disable-bitmap-caching"
|
||||||
label={t("settings.rdp.disable.bitmap_caching")}
|
label={t("settings.rdp.disable.bitmap_caching")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch name="disable-offscreen-caching"
|
<ProFormSwitch name="disable-offscreen-caching"
|
||||||
label={t("settings.rdp.disable.offscreen_caching")}
|
label={t("settings.rdp.disable.offscreen_caching")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -57,14 +57,12 @@ const SecuritySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="login-captcha-enabled"
|
name="login-captcha-enabled"
|
||||||
label={t("settings.security.captcha")}
|
label={t("settings.security.captcha")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="login-force-totp-enabled"
|
name="login-force-totp-enabled"
|
||||||
label={t("settings.security.force_otp")}
|
label={t("settings.security.force_otp")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
@@ -72,7 +70,6 @@ const SecuritySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="disable-password-login"
|
name="disable-password-login"
|
||||||
label={t("settings.security.disable_password_login")}
|
label={t("settings.security.disable_password_login")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
@@ -82,7 +79,6 @@ const SecuritySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="access-require-mfa"
|
name="access-require-mfa"
|
||||||
label={t("settings.security.access_require_mfa")}
|
label={t("settings.security.access_require_mfa")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
@@ -102,7 +98,6 @@ const SecuritySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="login-session-count-custom"
|
name="login-session-count-custom"
|
||||||
label={t("settings.security.session.count_custom")}
|
label={t("settings.security.session.count_custom")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
@@ -229,7 +224,6 @@ const SecuritySetting = ({get, set}: SettingProps) => {
|
|||||||
<ProFormSwitch
|
<ProFormSwitch
|
||||||
name="login-lock-enabled"
|
name="login-lock-enabled"
|
||||||
label={t("settings.security.login_lock.enabled")}
|
label={t("settings.security.login_lock.enabled")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ const SshdSetting = ({get, set}: SettingProps) => {
|
|||||||
}}>
|
}}>
|
||||||
<ProFormSwitch name="ssh-server-enabled"
|
<ProFormSwitch name="ssh-server-enabled"
|
||||||
label={t("settings.sshd.enabled")}
|
label={t("settings.sshd.enabled")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React from 'react';
|
||||||
import {Form, Typography} from "antd";
|
import {Typography} from "antd";
|
||||||
import {SettingProps} from "./SettingPage";
|
import {SettingProps} from "./SettingPage";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {ProForm, ProFormSelect, ProFormSwitch} from "@ant-design/pro-components";
|
||||||
import {ProForm, ProFormDigit, ProFormSelect, ProFormSwitch} from "@ant-design/pro-components";
|
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
const {Title} = Typography;
|
const {Title} = Typography;
|
||||||
@@ -10,18 +9,6 @@ const {Title} = Typography;
|
|||||||
const VncSetting = ({get, set}: SettingProps) => {
|
const VncSetting = ({get, set}: SettingProps) => {
|
||||||
|
|
||||||
let {t} = useTranslation();
|
let {t} = useTranslation();
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
let query = useQuery({
|
|
||||||
queryKey: ['get-property'],
|
|
||||||
queryFn: get,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (query.data) {
|
|
||||||
form.setFieldsValue(query.data);
|
|
||||||
}
|
|
||||||
}, [query.data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -55,7 +42,6 @@ const VncSetting = ({get, set}: SettingProps) => {
|
|||||||
/>
|
/>
|
||||||
<ProFormSwitch name="swap-red-blue"
|
<ProFormSwitch name="swap-red-blue"
|
||||||
label={t("settings.vnc.swap_red_blue")}
|
label={t("settings.vnc.swap_red_blue")}
|
||||||
rules={[{required: true}]}
|
|
||||||
checkedChildren={t('general.enabled')}
|
checkedChildren={t('general.enabled')}
|
||||||
unCheckedChildren={t('general.disabled')}
|
unCheckedChildren={t('general.disabled')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -198,6 +198,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
|
"title": "File Manager",
|
||||||
"operations": {
|
"operations": {
|
||||||
"batch_download": "Batch Download",
|
"batch_download": "Batch Download",
|
||||||
"create_dir": "Create Folder",
|
"create_dir": "Create Folder",
|
||||||
|
|||||||
@@ -440,6 +440,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
|
"title": "文件管理",
|
||||||
"operations": {
|
"operations": {
|
||||||
"batch_download": "批量下载",
|
"batch_download": "批量下载",
|
||||||
"create_dir": "创建文件夹",
|
"create_dir": "创建文件夹",
|
||||||
|
|||||||
Reference in New Issue
Block a user