- 修复RDP协议连接导致的任意文件读取漏洞

- RDP协议增加「域」参数
- 增加安全访问功能
- 优化代码
This commit is contained in:
dushixiang 2021-03-11 21:16:29 +08:00
parent ba5bff1b38
commit 7f7edaa33c
45 changed files with 1020 additions and 158 deletions

23
main.go
View File

@ -15,6 +15,7 @@ import (
"io" "io"
"next-terminal/pkg/api" "next-terminal/pkg/api"
"next-terminal/pkg/config" "next-terminal/pkg/config"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/handle" "next-terminal/pkg/handle"
"next-terminal/pkg/model" "next-terminal/pkg/model"
@ -132,7 +133,7 @@ func Run() error {
Username: "admin", Username: "admin",
Password: string(pass), Password: string(pass),
Nickname: "超级管理员", Nickname: "超级管理员",
Type: model.TypeAdmin, Type: constant.TypeAdmin,
Created: utils.NowJsonTime(), Created: utils.NowJsonTime(),
} }
if err := model.CreateNewUser(&user); err != nil { if err := model.CreateNewUser(&user); err != nil {
@ -144,7 +145,7 @@ func Run() error {
// 修正默认用户类型为管理员 // 修正默认用户类型为管理员
if users[i].Type == "" { if users[i].Type == "" {
user := model.User{ user := model.User{
Type: model.TypeAdmin, Type: constant.TypeAdmin,
} }
model.UpdateUserById(&user, users[i].ID) model.UpdateUserById(&user, users[i].ID)
logrus.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID) logrus.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID)
@ -191,6 +192,12 @@ func Run() error {
if err := global.DB.AutoMigrate(&model.JobLog{}); err != nil { if err := global.DB.AutoMigrate(&model.JobLog{}); err != nil {
return err return err
} }
if err := global.DB.AutoMigrate(&model.AccessSecurity{}); err != nil {
return err
}
if err := api.ReloadAccessSecurity(); err != nil {
return err
}
if len(model.FindAllTemp()) == 0 { if len(model.FindAllTemp()) == 0 {
for i := 0; i <= 30; i++ { for i := 0; i <= 30; i++ {
@ -216,7 +223,7 @@ func Run() error {
global.Cron = cron.New(cron.WithSeconds()) //精确到秒 global.Cron = cron.New(cron.WithSeconds()) //精确到秒
global.Cron.Start() global.Cron.Start()
jobs, err := model.FindJobByFunc(model.FuncCheckAssetStatusJob) jobs, err := model.FindJobByFunc(constant.FuncCheckAssetStatusJob)
if err != nil { if err != nil {
return err return err
} }
@ -224,10 +231,10 @@ func Run() error {
job := model.Job{ job := model.Job{
ID: utils.UUID(), ID: utils.UUID(),
Name: "资产状态检测", Name: "资产状态检测",
Func: model.FuncCheckAssetStatusJob, Func: constant.FuncCheckAssetStatusJob,
Cron: "0 0 0/1 * * ?", Cron: "0 0 0/1 * * ?",
Mode: model.JobModeAll, Mode: constant.JobModeAll,
Status: model.JobStatusRunning, Status: constant.JobStatusRunning,
Created: utils.NowJsonTime(), Created: utils.NowJsonTime(),
Updated: utils.NowJsonTime(), Updated: utils.NowJsonTime(),
} }
@ -237,8 +244,8 @@ func Run() error {
logrus.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron) logrus.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron)
} else { } else {
for i := range jobs { for i := range jobs {
if jobs[i].Status == model.JobStatusRunning { if jobs[i].Status == constant.JobStatusRunning {
err := model.ChangeJobStatusById(jobs[i].ID, model.JobStatusRunning) err := model.ChangeJobStatusById(jobs[i].ID, constant.JobStatusRunning)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"next-terminal/pkg/constant"
"next-terminal/pkg/model" "next-terminal/pkg/model"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"strconv" "strconv"
@ -85,7 +86,7 @@ func AssetImportEndpoint(c echo.Context) error {
Protocol: record[1], Protocol: record[1],
IP: record[2], IP: record[2],
Port: port, Port: port,
AccountType: model.Custom, AccountType: constant.Custom,
Username: record[4], Username: record[4],
Password: record[5], Password: record[5],
PrivateKey: record[6], PrivateKey: record[6],

View File

@ -3,6 +3,7 @@ package api
import ( import (
"errors" "errors"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"next-terminal/pkg/constant"
"next-terminal/pkg/model" "next-terminal/pkg/model"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"strconv" "strconv"
@ -26,7 +27,7 @@ func CredentialCreateEndpoint(c echo.Context) error {
item.Created = utils.NowJsonTime() item.Created = utils.NowJsonTime()
switch item.Type { switch item.Type {
case model.Custom: case constant.Custom:
item.PrivateKey = "-" item.PrivateKey = "-"
item.Passphrase = "-" item.Passphrase = "-"
if len(item.Username) == 0 { if len(item.Username) == 0 {
@ -35,7 +36,7 @@ func CredentialCreateEndpoint(c echo.Context) error {
if len(item.Password) == 0 { if len(item.Password) == 0 {
item.Password = "-" item.Password = "-"
} }
case model.PrivateKey: case constant.PrivateKey:
item.Password = "-" item.Password = "-"
if len(item.Username) == 0 { if len(item.Username) == 0 {
item.Username = "-" item.Username = "-"
@ -90,7 +91,7 @@ func CredentialUpdateEndpoint(c echo.Context) error {
} }
switch item.Type { switch item.Type {
case model.Custom: case constant.Custom:
item.PrivateKey = "-" item.PrivateKey = "-"
item.Passphrase = "-" item.Passphrase = "-"
if len(item.Username) == 0 { if len(item.Username) == 0 {
@ -99,7 +100,7 @@ func CredentialUpdateEndpoint(c echo.Context) error {
if len(item.Password) == 0 { if len(item.Password) == 0 {
item.Password = "-" item.Password = "-"
} }
case model.PrivateKey: case constant.PrivateKey:
item.Password = "-" item.Password = "-"
if len(item.Username) == 0 { if len(item.Username) == 0 {
item.Username = "-" item.Username = "-"

View File

@ -3,8 +3,10 @@ package api
import ( import (
"fmt" "fmt"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"net"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/model" "next-terminal/pkg/utils"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -26,6 +28,62 @@ func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
} }
} }
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if global.Securities == nil {
return next(c)
}
ip := c.RealIP()
for i := 0; i < len(global.Securities); i++ {
security := global.Securities[i]
if strings.Contains(security.IP, "/") {
// CIDR
_, ipNet, err := net.ParseCIDR(security.IP)
if err != nil {
continue
}
if !ipNet.Contains(net.ParseIP(ip)) {
continue
}
} else if strings.Contains(security.IP, "-") {
// 范围段
split := strings.Split(security.IP, "-")
if len(split) < 2 {
continue
}
start := split[0]
end := split[1]
intReqIP := utils.IpToInt(ip)
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
continue
}
} else {
// IP
if security.IP != ip {
continue
}
}
if security.Rule == constant.AccessRuleAllow {
return next(c)
}
if security.Rule == constant.AccessRuleReject {
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(Token) != "" {
return Fail(c, 0, "您的访问请求被拒绝 :(")
} else {
return c.HTML(666, "您的访问请求被拒绝 :(")
}
}
}
return next(c)
}
}
func Auth(next echo.HandlerFunc) echo.HandlerFunc { func Auth(next echo.HandlerFunc) echo.HandlerFunc {
startWithUrls := []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"} startWithUrls := []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"}
@ -80,7 +138,7 @@ func Admin(next echo.HandlerFunc) echo.HandlerFunc {
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。") return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
} }
if account.Type != model.TypeAdmin { if account.Type != constant.TypeAdmin {
return Fail(c, 403, "permission denied") return Fail(c, 403, "permission denied")
} }

View File

@ -2,6 +2,7 @@ package api
import ( import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"next-terminal/pkg/constant"
"next-terminal/pkg/model" "next-terminal/pkg/model"
) )
@ -21,7 +22,7 @@ func OverviewCounterEndPoint(c echo.Context) error {
credential int64 credential int64
asset int64 asset int64
) )
if model.TypeUser == account.Type { if constant.TypeUser == account.Type {
countUser, _ = model.CountOnlineUser() countUser, _ = model.CountOnlineUser()
countOnlineSession, _ = model.CountOnlineSession() countOnlineSession, _ = model.CountOnlineSession()
credential, _ = model.CountCredentialByUserId(account.ID) credential, _ = model.CountCredentialByUserId(account.ID)

View File

@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/log" "next-terminal/pkg/log"
"next-terminal/pkg/model" "next-terminal/pkg/model"
@ -34,6 +35,7 @@ func SetupRoutes() *echo.Echo {
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
})) }))
e.Use(ErrorHandler) e.Use(ErrorHandler)
e.Use(TcpWall)
e.Use(Auth) e.Use(Auth)
e.POST("/login", LoginEndpoint) e.POST("/login", LoginEndpoint)
@ -121,7 +123,7 @@ func SetupRoutes() *echo.Echo {
sessions.POST("/:id/mkdir", SessionMkDirEndpoint) sessions.POST("/:id/mkdir", SessionMkDirEndpoint)
sessions.POST("/:id/rm", SessionRmEndpoint) sessions.POST("/:id/rm", SessionRmEndpoint)
sessions.POST("/:id/rename", SessionRenameEndpoint) sessions.POST("/:id/rename", SessionRenameEndpoint)
sessions.DELETE("/:id", SessionDeleteEndpoint) sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
sessions.GET("/:id/recording", SessionRecordingEndpoint) sessions.GET("/:id/recording", SessionRecordingEndpoint)
} }
@ -158,6 +160,15 @@ func SetupRoutes() *echo.Echo {
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint) jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
} }
securities := e.Group("/securities", Admin)
{
securities.POST("", SecurityCreateEndpoint)
securities.GET("/paging", SecurityPagingEndpoint)
securities.PUT("/:id", SecurityUpdateEndpoint)
securities.DELETE("/:id", SecurityDeleteEndpoint)
securities.GET("/:id", SecurityGetEndpoint)
}
return e return e
} }
@ -218,7 +229,7 @@ func HasPermission(c echo.Context, owner string) bool {
return false return false
} }
// 检测是否为管理人员 // 检测是否为管理人员
if model.TypeAdmin == account.Type { if constant.TypeAdmin == account.Type {
return true return true
} }
// 检测是否为所有者 // 检测是否为所有者

114
pkg/api/security.go Normal file
View File

@ -0,0 +1,114 @@
package api
import (
"github.com/labstack/echo/v4"
"next-terminal/pkg/global"
"next-terminal/pkg/model"
"next-terminal/pkg/utils"
"strconv"
"strings"
)
func SecurityCreateEndpoint(c echo.Context) error {
var item model.AccessSecurity
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Source = "管理员添加"
if err := model.CreateNewSecurity(&item); err != nil {
return err
}
// 更新内存中的安全规则
if err := ReloadAccessSecurity(); err != nil {
return err
}
return Success(c, "")
}
func ReloadAccessSecurity() error {
rules, err := model.FindAllAccessSecurities()
if err != nil {
return err
}
if rules != nil && len(rules) > 0 {
var securities []*global.Security
for i := 0; i < len(rules); i++ {
rule := global.Security{
IP: rules[i].IP,
Rule: rules[i].Rule,
}
securities = append(securities, &rule)
}
global.Securities = securities
}
return nil
}
func SecurityPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
ip := c.QueryParam("ip")
rule := c.QueryParam("rule")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := model.FindPageSecurity(pageIndex, pageSize, ip, rule, order, field)
if err != nil {
return err
}
return Success(c, H{
"total": total,
"items": items,
})
}
func SecurityUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.AccessSecurity
if err := c.Bind(&item); err != nil {
return err
}
if err := model.UpdateSecurityById(&item, id); err != nil {
return err
}
// 更新内存中的安全规则
if err := ReloadAccessSecurity(); err != nil {
return err
}
return Success(c, nil)
}
func SecurityDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
jobId := split[i]
if err := model.DeleteSecurityById(jobId); err != nil {
return err
}
}
// 更新内存中的安全规则
if err := ReloadAccessSecurity(); err != nil {
return err
}
return Success(c, nil)
}
func SecurityGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := model.FindSecurityById(id)
if err != nil {
return err
}
return Success(c, item)
}

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/model" "next-terminal/pkg/model"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
@ -36,10 +37,10 @@ func SessionPagingEndpoint(c echo.Context) error {
} }
for i := 0; i < len(items); i++ { for i := 0; i < len(items); i++ {
if status == model.Disconnected && len(items[i].Recording) > 0 { if status == constant.Disconnected && len(items[i].Recording) > 0 {
var recording string var recording string
if items[i].Mode == model.Naive { if items[i].Mode == constant.Naive {
recording = items[i].Recording recording = items[i].Recording
} else { } else {
recording = items[i].Recording + "/recording" recording = items[i].Recording + "/recording"
@ -77,7 +78,7 @@ func SessionConnectEndpoint(c echo.Context) error {
session := model.Session{} session := model.Session{}
session.ID = sessionId session.ID = sessionId
session.Status = model.Connected session.Status = constant.Connected
session.ConnectedTime = utils.NowJsonTime() session.ConnectedTime = utils.NowJsonTime()
if err := model.UpdateSessionById(&session, sessionId); err != nil { if err := model.UpdateSessionById(&session, sessionId); err != nil {
@ -118,11 +119,11 @@ func CloseSessionById(sessionId string, code int, reason string) {
return return
} }
if s.Status == model.Disconnected { if s.Status == constant.Disconnected {
return return
} }
if s.Status == model.Connecting { if s.Status == constant.Connecting {
// 会话还未建立成功,无需保留数据 // 会话还未建立成功,无需保留数据
_ = model.DeleteSessionById(sessionId) _ = model.DeleteSessionById(sessionId)
return return
@ -130,7 +131,7 @@ func CloseSessionById(sessionId string, code int, reason string) {
session := model.Session{} session := model.Session{}
session.ID = sessionId session.ID = sessionId
session.Status = model.Disconnected session.Status = constant.Disconnected
session.DisconnectedTime = utils.NowJsonTime() session.DisconnectedTime = utils.NowJsonTime()
session.Code = code session.Code = code
session.Message = reason session.Message = reason
@ -161,15 +162,15 @@ func SessionCreateEndpoint(c echo.Context) error {
assetId := c.QueryParam("assetId") assetId := c.QueryParam("assetId")
mode := c.QueryParam("mode") mode := c.QueryParam("mode")
if mode == model.Naive { if mode == constant.Naive {
mode = model.Naive mode = constant.Naive
} else { } else {
mode = model.Guacd mode = constant.Guacd
} }
user, _ := GetCurrentAccount(c) user, _ := GetCurrentAccount(c)
if model.TypeUser == user.Type { if constant.TypeUser == user.Type {
// 检测是否有访问权限 // 检测是否有访问权限
assetIds, err := model.FindAssetIdsByUserId(user.ID) assetIds, err := model.FindAssetIdsByUserId(user.ID)
if err != nil { if err != nil {
@ -196,7 +197,7 @@ func SessionCreateEndpoint(c echo.Context) error {
Protocol: asset.Protocol, Protocol: asset.Protocol,
IP: asset.IP, IP: asset.IP,
Port: asset.Port, Port: asset.Port,
Status: model.NoConnect, Status: constant.NoConnect,
Creator: user.ID, Creator: user.ID,
ClientIP: c.RealIP(), ClientIP: c.RealIP(),
Mode: mode, Mode: mode,
@ -208,7 +209,7 @@ func SessionCreateEndpoint(c echo.Context) error {
return err return err
} }
if credential.Type == model.Custom { if credential.Type == constant.Custom {
session.Username = credential.Username session.Username = credential.Username
session.Password = credential.Password session.Password = credential.Password
} else { } else {
@ -273,7 +274,7 @@ func SessionUploadEndpoint(c echo.Context) error {
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(remoteFile, "../") { if strings.Contains(remoteFile, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
@ -331,7 +332,7 @@ func SessionDownloadEndpoint(c echo.Context) error {
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes())) return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes()))
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(remoteFile, "../") { if strings.Contains(remoteFile, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
drivePath, err := model.GetDrivePath() drivePath, err := model.GetDrivePath()
@ -413,7 +414,7 @@ func SessionLsEndpoint(c echo.Context) error {
return Success(c, files) return Success(c, files)
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(remoteDir, "../") { if strings.Contains(remoteDir, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
drivePath, err := model.GetDrivePath() drivePath, err := model.GetDrivePath()
@ -446,6 +447,18 @@ func SessionLsEndpoint(c echo.Context) error {
return errors.New("当前协议不支持此操作") return errors.New("当前协议不支持此操作")
} }
func SafetyRuleTrigger(c echo.Context) {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP())
security := model.AccessSecurity{
ID: utils.UUID(),
Source: "安全规则触发",
IP: c.RealIP(),
Rule: constant.AccessRuleReject,
}
_ = model.CreateNewSecurity(&security)
}
func SessionMkDirEndpoint(c echo.Context) error { func SessionMkDirEndpoint(c echo.Context) error {
sessionId := c.Param("id") sessionId := c.Param("id")
session, err := model.FindSessionById(sessionId) session, err := model.FindSessionById(sessionId)
@ -464,7 +477,7 @@ func SessionMkDirEndpoint(c echo.Context) error {
return Success(c, nil) return Success(c, nil)
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(remoteDir, "../") { if strings.Contains(remoteDir, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
drivePath, err := model.GetDrivePath() drivePath, err := model.GetDrivePath()
@ -525,7 +538,7 @@ func SessionRmEndpoint(c echo.Context) error {
return Success(c, nil) return Success(c, nil)
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(key, "../") { if strings.Contains(key, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
drivePath, err := model.GetDrivePath() drivePath, err := model.GetDrivePath()
@ -566,7 +579,7 @@ func SessionRenameEndpoint(c echo.Context) error {
return Success(c, nil) return Success(c, nil)
} else if "rdp" == session.Protocol { } else if "rdp" == session.Protocol {
if strings.Contains(oldName, "../") { if strings.Contains(oldName, "../") {
logrus.Warnf("IP %v 尝试进行攻击请ban掉此IP", c.RealIP()) SafetyRuleTrigger(c)
return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。") return Fail(c, -1, ":) 您的IP已被记录请去向管理员自首。")
} }
drivePath, err := model.GetDrivePath() drivePath, err := model.GetDrivePath()
@ -591,7 +604,7 @@ func SessionRecordingEndpoint(c echo.Context) error {
} }
var recording string var recording string
if session.Mode == model.Naive { if session.Mode == constant.Naive {
recording = session.Recording recording = session.Recording
} else { } else {
recording = session.Recording + "/recording" recording = session.Recording + "/recording"

View File

@ -6,6 +6,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"net/http" "net/http"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
"next-terminal/pkg/model" "next-terminal/pkg/model"
@ -62,7 +63,7 @@ func SSHEndpoint(c echo.Context) (err error) {
} }
user, _ := GetCurrentAccount(c) user, _ := GetCurrentAccount(c)
if model.TypeUser == user.Type { if constant.TypeUser == user.Type {
// 检测是否有访问权限 // 检测是否有访问权限
assetIds, err := model.FindAssetIdsByUserId(user.ID) assetIds, err := model.FindAssetIdsByUserId(user.ID)
if err != nil { if err != nil {
@ -137,7 +138,7 @@ func SSHEndpoint(c echo.Context) (err error) {
ConnectionId: sessionId, ConnectionId: sessionId,
Width: cols, Width: cols,
Height: rows, Height: rows,
Status: model.Connecting, Status: constant.Connecting,
Recording: recording, Recording: recording,
} }
// 创建新会话 // 创建新会话

View File

@ -5,6 +5,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
"next-terminal/pkg/model" "next-terminal/pkg/model"
@ -49,7 +50,7 @@ func TunEndpoint(c echo.Context) error {
logrus.Warnf("会话不存在") logrus.Warnf("会话不存在")
return err return err
} }
if session.Status != model.Connected { if session.Status != constant.Connected {
logrus.Warnf("会话未在线") logrus.Warnf("会话未在线")
return errors.New("会话未在线") return errors.New("会话未在线")
} }
@ -188,7 +189,7 @@ func TunEndpoint(c echo.Context) error {
ConnectionId: tunnel.UUID, ConnectionId: tunnel.UUID,
Width: intWidth, Width: intWidth,
Height: intHeight, Height: intHeight,
Status: model.Connecting, Status: constant.Connecting,
Recording: configuration.GetParameter(guacd.RecordingPath), Recording: configuration.GetParameter(guacd.RecordingPath),
} }
// 创建新会话 // 创建新会话

33
pkg/constant/const.go Normal file
View File

@ -0,0 +1,33 @@
package constant
const (
AccessRuleAllow = "allow" // 允许访问
AccessRuleReject = "reject" // 拒绝访问
Custom = "custom" // 密码
PrivateKey = "private-key" // 密钥
JobStatusRunning = "running" // 计划任务运行状态
JobStatusNotRunning = "not-running" // 计划任务未运行状态
FuncCheckAssetStatusJob = "check-asset-status-job" // 检测资产是否在线
FuncShellJob = "shell-job" // 执行Shell脚本
JobModeAll = "all" // 全部资产
JobModeCustom = "custom" // 自定义选择资产
SshMode = "ssh-mode" // ssh模式
MailHost = "mail-host" // 邮件服务器地址
MailPort = "mail-port" // 邮件服务器端口
MailUsername = "mail-username" // 邮件服务账号
MailPassword = "mail-password" // 邮件服务密码
NoConnect = "no_connect" // 会话状态:未连接
Connecting = "connecting" // 会话状态:连接中
Connected = "connected" // 会话状态:已连接
Disconnected = "disconnected" // 会话状态:已断开连接
Guacd = "guacd" // 接入模式guacd
Naive = "naive" // 接入模式:原生
TypeUser = "user" // 普通用户
TypeAdmin = "admin" // 管理员
)

View File

@ -16,3 +16,10 @@ var Config *config.Config
var Store *TunStore var Store *TunStore
var Cron *cron.Cron var Cron *cron.Cron
type Security struct {
Rule string
IP string
}
var Securities []*Security

View File

@ -34,6 +34,7 @@ const (
DisableOffscreenCaching = "disable-offscreen-caching" DisableOffscreenCaching = "disable-offscreen-caching"
DisableGlyphCaching = "disable-glyph-caching" DisableGlyphCaching = "disable-glyph-caching"
Domain = "domain"
RemoteApp = "remote-app" RemoteApp = "remote-app"
RemoteAppDir = "remote-app-dir" RemoteAppDir = "remote-app-dir"
RemoteAppArgs = "remote-app-args" RemoteAppArgs = "remote-app-args"

View File

@ -2,6 +2,7 @@ package handle
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"next-terminal/pkg/constant"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
"next-terminal/pkg/model" "next-terminal/pkg/model"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
@ -16,7 +17,7 @@ func RunTicker() {
unUsedSessionTicker := time.NewTicker(time.Minute * 60) unUsedSessionTicker := time.NewTicker(time.Minute * 60)
go func() { go func() {
for range unUsedSessionTicker.C { for range unUsedSessionTicker.C {
sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting}) sessions, _ := model.FindSessionByStatusIn([]string{constant.NoConnect, constant.Connecting})
if sessions != nil && len(sessions) > 0 { if sessions != nil && len(sessions) > 0 {
now := time.Now() now := time.Now()
for i := range sessions { for i := range sessions {
@ -65,14 +66,14 @@ func RunTicker() {
} }
func RunDataFix() { func RunDataFix() {
sessions, _ := model.FindSessionByStatus(model.Connected) sessions, _ := model.FindSessionByStatus(constant.Connected)
if sessions == nil { if sessions == nil {
return return
} }
for i := range sessions { for i := range sessions {
session := model.Session{ session := model.Session{
Status: model.Disconnected, Status: constant.Disconnected,
DisconnectedTime: utils.NowJsonTime(), DisconnectedTime: utils.NowJsonTime(),
} }

View File

@ -0,0 +1,83 @@
package model
import (
"next-terminal/pkg/global"
)
type AccessSecurity struct {
ID string `json:"id"`
Rule string `json:"rule"`
IP string `json:"ip"`
Source string `json:"source"`
Priority int64 `json:"priority"` // 越小优先级越高
}
func (r *AccessSecurity) TableName() string {
return "access_securities"
}
func FindAllAccessSecurities() (o []AccessSecurity, err error) {
db := global.DB
err = db.Order("priority asc").Find(&o).Error
return
}
func FindPageSecurity(pageIndex, pageSize int, ip, rule, order, field string) (o []AccessSecurity, total int64, err error) {
t := AccessSecurity{}
db := global.DB.Table(t.TableName())
dbCounter := global.DB.Table(t.TableName())
if len(ip) > 0 {
db = db.Where("ip like ?", "%"+ip+"%")
dbCounter = dbCounter.Where("ip like ?", "%"+ip+"%")
}
if len(rule) > 0 {
db = db.Where("rule = ?", rule)
dbCounter = dbCounter.Where("rule = ?", rule)
}
err = dbCounter.Count(&total).Error
if err != nil {
return nil, 0, err
}
if order == "descend" {
order = "desc"
} else {
order = "asc"
}
if field == "ip" {
field = "ip"
} else if field == "rule" {
field = "rule"
} else {
field = "priority"
}
err = db.Order(field + " " + order).Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Error
if o == nil {
o = make([]AccessSecurity, 0)
}
return
}
func CreateNewSecurity(o *AccessSecurity) error {
return global.DB.Create(o).Error
}
func UpdateSecurityById(o *AccessSecurity, id string) error {
o.ID = id
return global.DB.Updates(o).Error
}
func DeleteSecurityById(id string) error {
return global.DB.Where("id = ?", id).Delete(AccessSecurity{}).Error
}
func FindSecurityById(id string) (o *AccessSecurity, err error) {
err = global.DB.Where("id = ?", id).First(&o).Error
return
}

View File

@ -1,6 +1,7 @@
package model package model
import ( import (
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"strings" "strings"
@ -66,7 +67,7 @@ func FindAssetByProtocolAndIds(protocol string, assetIds []string) (o []Asset, e
func FindAssetByConditions(protocol string, account User) (o []Asset, err error) { func FindAssetByConditions(protocol string, account User) (o []Asset, err error) {
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
if TypeUser == account.Type { if constant.TypeUser == account.Type {
owner := account.ID owner := account.ID
db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)
} }
@ -82,7 +83,7 @@ func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string, account
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
dbCounter := global.DB.Table("assets").Select("DISTINCT assets.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") dbCounter := global.DB.Table("assets").Select("DISTINCT assets.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
if TypeUser == account.Type { if constant.TypeUser == account.Type {
owner := account.ID owner := account.ID
db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)
dbCounter = dbCounter.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"gorm.io/gorm" "gorm.io/gorm"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
@ -20,8 +21,8 @@ func (r *AssetAttribute) TableName() string {
return "asset_attributes" return "asset_attributes"
} }
var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode} var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, constant.SshMode}
var RDPParameterNames = []string{guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs} var RDPParameterNames = []string{guacd.Domain, guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs}
var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort} var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort}
var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex} var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex}
var KubernetesParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.Namespace, guacd.Pod, guacd.Container, guacd.UesSSL, guacd.ClientCert, guacd.ClientKey, guacd.CaCert, guacd.IgnoreCert} var KubernetesParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.Namespace, guacd.Pod, guacd.Container, guacd.UesSSL, guacd.ClientCert, guacd.ClientKey, guacd.CaCert, guacd.IgnoreCert}

View File

@ -1,6 +1,7 @@
package model package model
import ( import (
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
) )
@ -32,7 +33,7 @@ func FindPageCommand(pageIndex, pageSize int, name, content, order, field string
db := global.DB.Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on commands.owner = users.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id") db := global.DB.Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on commands.owner = users.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id")
dbCounter := global.DB.Table("commands").Select("DISTINCT commands.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id") dbCounter := global.DB.Table("commands").Select("DISTINCT commands.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id")
if TypeUser == account.Type { if constant.TypeUser == account.Type {
owner := account.ID owner := account.ID
db = db.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner) db = db.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner)
dbCounter = dbCounter.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner)

View File

@ -1,16 +1,11 @@
package model package model
import ( import (
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
) )
// 密码
const Custom = "custom"
// 密钥
const PrivateKey = "private-key"
type Credential struct { type Credential struct {
ID string `gorm:"primary_key" json:"id"` ID string `gorm:"primary_key" json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -45,7 +40,7 @@ type CredentialSimpleVo struct {
func FindAllCredential(account User) (o []CredentialSimpleVo, err error) { func FindAllCredential(account User) (o []CredentialSimpleVo, err error) {
db := global.DB.Table("credentials").Select("DISTINCT credentials.id,credentials.name").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id") db := global.DB.Table("credentials").Select("DISTINCT credentials.id,credentials.name").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id")
if account.Type == TypeUser { if account.Type == constant.TypeUser {
db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", account.ID, account.ID) db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", account.ID, account.ID)
} }
err = db.Find(&o).Error err = db.Find(&o).Error
@ -56,7 +51,7 @@ func FindPageCredential(pageIndex, pageSize int, name, order, field string, acco
db := global.DB.Table("credentials").Select("credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on credentials.owner = users.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id") db := global.DB.Table("credentials").Select("credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on credentials.owner = users.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id")
dbCounter := global.DB.Table("credentials").Select("DISTINCT credentials.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id") dbCounter := global.DB.Table("credentials").Select("DISTINCT credentials.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id")
if TypeUser == account.Type { if constant.TypeUser == account.Type {
owner := account.ID owner := account.ID
db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner) db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner)
dbCounter = dbCounter.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner)

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/term" "next-terminal/pkg/term"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
@ -13,17 +14,6 @@ import (
"time" "time"
) )
const (
JobStatusRunning = "running"
JobStatusNotRunning = "not-running"
FuncCheckAssetStatusJob = "check-asset-status-job"
FuncShellJob = "shell-job"
JobModeAll = "all"
JobModeCustom = "custom"
)
type Job struct { type Job struct {
ID string `gorm:"primary_key" json:"id"` ID string `gorm:"primary_key" json:"id"`
CronJobId int `json:"cronJobId"` CronJobId int `json:"cronJobId"`
@ -91,7 +81,7 @@ func FindJobByFunc(function string) (o []Job, err error) {
func CreateNewJob(o *Job) (err error) { func CreateNewJob(o *Job) (err error) {
if o.Status == JobStatusRunning { if o.Status == constant.JobStatusRunning {
j, err := getJob(o) j, err := getJob(o)
if err != nil { if err != nil {
return err return err
@ -107,11 +97,12 @@ func CreateNewJob(o *Job) (err error) {
} }
func UpdateJobById(o *Job, id string) (err error) { func UpdateJobById(o *Job, id string) (err error) {
if o.Status == JobStatusRunning { if o.Status == constant.JobStatusRunning {
return errors.New("请先停止定时任务后再修改") return errors.New("请先停止定时任务后再修改")
} }
return global.DB.Where("id = ?", id).Updates(o).Error o.ID = id
return global.DB.Updates(o).Error
} }
func UpdateJonUpdatedById(id string) (err error) { func UpdateJonUpdatedById(id string) (err error) {
@ -125,7 +116,7 @@ func ChangeJobStatusById(id, status string) (err error) {
if err != nil { if err != nil {
return err return err
} }
if status == JobStatusRunning { if status == constant.JobStatusRunning {
j, err := getJob(&job) j, err := getJob(&job)
if err != nil { if err != nil {
return err return err
@ -136,11 +127,11 @@ func ChangeJobStatusById(id, status string) (err error) {
} }
logrus.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries())) logrus.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning, CronJobId: int(entryID)}).Error return global.DB.Updates(Job{ID: id, Status: constant.JobStatusRunning, CronJobId: int(entryID)}).Error
} else { } else {
global.Cron.Remove(cron.EntryID(job.CronJobId)) global.Cron.Remove(cron.EntryID(job.CronJobId))
logrus.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries())) logrus.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error return global.DB.Updates(Job{ID: id, Status: constant.JobStatusNotRunning}).Error
} }
} }
@ -167,8 +158,8 @@ func DeleteJobById(id string) error {
if err != nil { if err != nil {
return err return err
} }
if job.Status == JobStatusRunning { if job.Status == constant.JobStatusRunning {
if err := ChangeJobStatusById(id, JobStatusNotRunning); err != nil { if err := ChangeJobStatusById(id, constant.JobStatusNotRunning); err != nil {
return err return err
} }
} }
@ -177,9 +168,9 @@ func DeleteJobById(id string) error {
func getJob(j *Job) (job cron.Job, err error) { func getJob(j *Job) (job cron.Job, err error) {
switch j.Func { switch j.Func {
case FuncCheckAssetStatusJob: case constant.FuncCheckAssetStatusJob:
job = CheckAssetStatusJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata} job = CheckAssetStatusJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
case FuncShellJob: case constant.FuncShellJob:
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata} job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
default: default:
return nil, errors.New("未识别的任务") return nil, errors.New("未识别的任务")
@ -200,7 +191,7 @@ func (r CheckAssetStatusJob) Run() {
} }
var assets []Asset var assets []Asset
if r.Mode == JobModeAll { if r.Mode == constant.JobModeAll {
assets, _ = FindAllAsset() assets, _ = FindAllAsset()
} else { } else {
assets, _ = FindAssetByIds(strings.Split(r.ResourceIds, ",")) assets, _ = FindAssetByIds(strings.Split(r.ResourceIds, ","))
@ -258,7 +249,7 @@ func (r ShellJob) Run() {
} }
var assets []Asset var assets []Asset
if r.Mode == JobModeAll { if r.Mode == constant.JobModeAll {
assets, _ = FindAssetByProtocol("ssh") assets, _ = FindAssetByProtocol("ssh")
} else { } else {
assets, _ = FindAssetByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ",")) assets, _ = FindAssetByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ","))
@ -299,7 +290,7 @@ func (r ShellJob) Run() {
return return
} }
if credential.Type == Custom { if credential.Type == constant.Custom {
username = credential.Username username = credential.Username
password = credential.Password password = credential.Password
} else { } else {

View File

@ -4,18 +4,11 @@ import (
"github.com/jordan-wright/email" "github.com/jordan-wright/email"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"net/smtp" "net/smtp"
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
) )
const (
SshMode = "ssh-mode"
MailHost = "mail-host"
MailPort = "mail-port"
MailUsername = "mail-username"
MailPassword = "mail-password"
)
type Property struct { type Property struct {
Name string `gorm:"primary_key" json:"name"` Name string `gorm:"primary_key" json:"name"`
Value string `json:"value"` Value string `json:"value"`
@ -74,10 +67,10 @@ func GetRecordingPath() (string, error) {
func SendMail(to, subject, text string) { func SendMail(to, subject, text string) {
propertiesMap := FindAllPropertiesMap() propertiesMap := FindAllPropertiesMap()
host := propertiesMap[MailHost] host := propertiesMap[constant.MailHost]
port := propertiesMap[MailPort] port := propertiesMap[constant.MailPort]
username := propertiesMap[MailUsername] username := propertiesMap[constant.MailUsername]
password := propertiesMap[MailPassword] password := propertiesMap[constant.MailPassword]
if host == "" || port == "" || username == "" || password == "" { if host == "" || port == "" || username == "" || password == "" {
logrus.Debugf("邮箱信息不完整,跳过发送邮件。") logrus.Debugf("邮箱信息不完整,跳过发送邮件。")

View File

@ -1,6 +1,7 @@
package model package model
import ( import (
"next-terminal/pkg/constant"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"os" "os"
@ -8,18 +9,6 @@ import (
"time" "time"
) )
const (
NoConnect = "no_connect"
Connecting = "connecting"
Connected = "connected"
Disconnected = "disconnected"
)
const (
Guacd = "guacd"
Naive = "naive"
)
type Session struct { type Session struct {
ID string `gorm:"primary_key" json:"id"` ID string `gorm:"primary_key" json:"id"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
@ -130,7 +119,7 @@ func FindSessionByStatusIn(statuses []string) (o []Session, err error) {
func FindOutTimeSessions(dayLimit int) (o []Session, err error) { func FindOutTimeSessions(dayLimit int) (o []Session, err error) {
limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour) limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour)
err = global.DB.Where("status = ? and connected_time < ?", Disconnected, limitTime).Find(&o).Error err = global.DB.Where("status = ? and connected_time < ?", constant.Disconnected, limitTime).Find(&o).Error
return return
} }
@ -187,7 +176,7 @@ func DeleteSessionByStatus(status string) {
} }
func CountOnlineSession() (total int64, err error) { func CountOnlineSession() (total int64, err error) {
err = global.DB.Where("status = ?", Connected).Find(&Session{}).Count(&total).Error err = global.DB.Where("status = ?", constant.Connected).Find(&Session{}).Count(&total).Error
return return
} }

View File

@ -2,10 +2,6 @@ package model
import "next-terminal/pkg/global" import "next-terminal/pkg/global"
const (
FontSize = "font-size"
)
type UserAttribute struct { type UserAttribute struct {
Id string `gorm:"index" json:"id"` Id string `gorm:"index" json:"id"`
UserId string `gorm:"index" json:"userId"` UserId string `gorm:"index" json:"userId"`

View File

@ -6,11 +6,6 @@ import (
"reflect" "reflect"
) )
const (
TypeUser = "user"
TypeAdmin = "admin"
)
type User struct { type User struct {
ID string `gorm:"primary_key" json:"id"` ID string `gorm:"primary_key" json:"id"`
Username string `gorm:"index" json:"username"` Username string `gorm:"index" json:"username"`

View File

@ -182,3 +182,30 @@ func StructToMap(obj interface{}) map[string]interface{} {
} }
return data return data
} }
func IpToInt(ip string) int64 {
if len(ip) == 0 {
return 0
}
bits := strings.Split(ip, ".")
if len(bits) < 4 {
return 0
}
b0 := StringToInt(bits[0])
b1 := StringToInt(bits[1])
b2 := StringToInt(bits[2])
b3 := StringToInt(bits[3])
var sum int64
sum += int64(b0) << 24
sum += int64(b1) << 16
sum += int64(b2) << 8
sum += int64(b3)
return sum
}
func StringToInt(in string) (out int) {
out, _ = strconv.Atoi(in)
return
}

View File

@ -1 +1 @@
测试阿里云,ssh,10.1.1.2,22,username,password,privateKey,passphrase,description 资产名称,ssh,127.0.0.1,22,username,password,privateKey,passphrase,description
1 测试阿里云 资产名称 ssh 10.1.1.2 127.0.0.1 22 username password privateKey passphrase description

View File

@ -24,6 +24,7 @@ import {
IdcardOutlined, IdcardOutlined,
LinkOutlined, LinkOutlined,
LoginOutlined, LoginOutlined,
SafetyCertificateOutlined,
SettingOutlined, SettingOutlined,
SolutionOutlined, SolutionOutlined,
TeamOutlined, TeamOutlined,
@ -38,11 +39,12 @@ import BatchCommand from "./components/command/BatchCommand";
import {isEmpty, NT_PACKAGE} from "./utils/utils"; import {isEmpty, NT_PACKAGE} from "./utils/utils";
import {isAdmin} from "./service/permission"; import {isAdmin} from "./service/permission";
import UserGroup from "./components/user/UserGroup"; import UserGroup from "./components/user/UserGroup";
import LoginLog from "./components/session/LoginLog"; import LoginLog from "./components/devops/LoginLog";
import Term from "./components/access/Term"; import Term from "./components/access/Term";
import Job from "./components/job/Job"; import Job from "./components/devops/Job";
import {Header} from "antd/es/layout/layout"; import {Header} from "antd/es/layout/layout";
import LayoutHeader from "./components/user/LayoutHeader"; import LayoutHeader from "./components/user/LayoutHeader";
import Security from "./components/devops/Security";
const {Footer, Sider} = Layout; const {Footer, Sider} = Layout;
@ -203,6 +205,12 @@ class App extends Component {
计划任务 计划任务
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="access-security" icon={<SafetyCertificateOutlined/>}>
<Link to={'/access-security'}>
访问安全
</Link>
</Menu.Item>
</SubMenu> </SubMenu>
<SubMenu key='user-group' title='用户管理' icon={<UserSwitchOutlined/>}> <SubMenu key='user-group' title='用户管理' icon={<UserSwitchOutlined/>}>
@ -260,6 +268,7 @@ class App extends Component {
<Route path="/info" component={Info}/> <Route path="/info" component={Info}/>
<Route path="/setting" component={Setting}/> <Route path="/setting" component={Setting}/>
<Route path="/job" component={Job}/> <Route path="/job" component={Job}/>
<Route path="/access-security" component={Security}/>
<Footer style={{textAlign: 'center'}}> <Footer style={{textAlign: 'center'}}>
Next Terminal ©2021 dushixiang Version:{this.state.package['version']} Next Terminal ©2021 dushixiang Version:{this.state.package['version']}

View File

@ -40,7 +40,7 @@ import {
UploadOutlined UploadOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import {PROTOCOL_COLORS} from "../../common/constants"; import {PROTOCOL_COLORS} from "../../common/constants";
import LayoutHeader from "../user/LayoutHeader";
import {hasPermission, isAdmin} from "../../service/permission"; import {hasPermission, isAdmin} from "../../service/permission";
import Upload from "antd/es/upload"; import Upload from "antd/es/upload";
import axios from "axios"; import axios from "axios";
@ -880,28 +880,44 @@ class Asset extends Component {
disabled: this.state.fileList.length === 0 disabled: this.state.fileList.length === 0
}} }}
> >
<Upload <Space>
maxCount={1} <Upload
onRemove={file => { maxCount={1}
this.setState(state => { onRemove={file => {
const index = state.fileList.indexOf(file); this.setState(state => {
const newFileList = state.fileList.slice(); const index = state.fileList.indexOf(file);
newFileList.splice(index, 1); const newFileList = state.fileList.slice();
return { newFileList.splice(index, 1);
fileList: newFileList, return {
}; fileList: newFileList,
}); };
}} });
beforeUpload={(file) => { }}
this.setState(state => ({ beforeUpload={(file) => {
fileList: [file], this.setState(state => ({
})); fileList: [file],
return false; }));
}} return false;
fileList={this.state.fileList} }}
> fileList={this.state.fileList}
<Button icon={<UploadOutlined/>}>选择csv文件</Button> >
</Upload> <Button icon={<UploadOutlined/>}>选择csv文件</Button>
</Upload>
<Button type="primary" onClick={() => {
let csvString= 'name,ssh,127.0.0.1,22,username,password,privateKey,passphrase,description';
//前置的"\uFEFF"为“零宽不换行空格”,可处理中文乱码问题
const blob = new Blob(["\uFEFF" + csvString], {type: 'text/csv;charset=gb2312;'});
let a = document.createElement('a');
a.download = 'sample.csv';
a.href = URL.createObjectURL(blob);
a.click();
}}>
下载样本文件
</Button>
</Space>
</Modal> </Modal>
: undefined : undefined
} }

View File

@ -287,6 +287,14 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
{ {
protocol === 'rdp' ? protocol === 'rdp' ?
<> <>
<Panel header={<Text strong>认证</Text>} key="">
<Form.Item
name="domain"
label='域'
>
<Input type='text' placeholder="身份验证时使用的域"/>
</Form.Item>
</Panel>
<Panel header={<Text strong>Remote App</Text>} key="remote-app"> <Panel header={<Text strong>Remote App</Text>} key="remote-app">
<Form.Item <Form.Item
name="remote-app" name="remote-app"

View File

@ -2,7 +2,7 @@ import React, {Component} from 'react';
import {Card, Input, List, PageHeader, Spin} from "antd"; import {Card, Input, List, PageHeader, Spin} from "antd";
import Console from "../access/Console"; import Console from "../access/Console";
import {itemRender} from "../../utils/utils"; import {itemRender} from "../../utils/utils";
import LayoutHeader from "../user/LayoutHeader";
import './Command.css' import './Command.css'
import request from "../../common/request"; import request from "../../common/request";
import {message} from "antd/es"; import {message} from "antd/es";

View File

@ -34,7 +34,7 @@ import {
UndoOutlined UndoOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import {compare, itemRender} from "../../utils/utils"; import {compare, itemRender} from "../../utils/utils";
import LayoutHeader from "../user/LayoutHeader";
import {hasPermission, isAdmin} from "../../service/permission"; import {hasPermission, isAdmin} from "../../service/permission";
import dayjs from "dayjs"; import dayjs from "dayjs";

View File

@ -33,7 +33,7 @@ import {
UndoOutlined UndoOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import {itemRender} from "../../utils/utils"; import {itemRender} from "../../utils/utils";
import LayoutHeader from "../user/LayoutHeader";
import {hasPermission, isAdmin} from "../../service/permission"; import {hasPermission, isAdmin} from "../../service/permission";
import dayjs from "dayjs"; import dayjs from "dayjs";

View File

@ -6,7 +6,7 @@ import request from "../../common/request";
import './Dashboard.css' import './Dashboard.css'
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Area} from '@ant-design/charts'; import {Area} from '@ant-design/charts';
import LayoutHeader from "../user/LayoutHeader";
import {isAdmin} from "../../service/permission"; import {isAdmin} from "../../service/permission";
@ -87,9 +87,7 @@ class Dashboard extends Component {
itemRender: itemRender itemRender: itemRender
}} }}
subTitle="仪表盘" subTitle="仪表盘"
extra={[ extra={[]}
]}
> >
</PageHeader> </PageHeader>

View File

@ -31,7 +31,7 @@ import {
UndoOutlined UndoOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import {itemRender} from "../../utils/utils"; import {itemRender} from "../../utils/utils";
import LayoutHeader from "../user/LayoutHeader";
import dayjs from "dayjs"; import dayjs from "dayjs";
import JobModal from "./JobModal"; import JobModal from "./JobModal";
import './Job.css' import './Job.css'

View File

@ -21,7 +21,7 @@ import request from "../../common/request";
import {formatDate, isEmpty, itemRender} from "../../utils/utils"; import {formatDate, isEmpty, itemRender} from "../../utils/utils";
import {message} from "antd/es"; import {message} from "antd/es";
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons"; import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
import LayoutHeader from "../user/LayoutHeader";
const confirm = Modal.confirm; const confirm = Modal.confirm;
const {Content} = Layout; const {Content} = Layout;

View File

@ -0,0 +1,421 @@
import React, {Component} from 'react';
import {
Button,
Col,
Divider,
Input,
Layout,
Modal,
PageHeader,
Row,
Space,
Table,
Tag,
Tooltip,
Typography
} from "antd";
import qs from "qs";
import request from "../../common/request";
import {message} from "antd/es";
import {DeleteOutlined, ExclamationCircleOutlined, PlusOutlined, SyncOutlined, UndoOutlined} from '@ant-design/icons';
import {itemRender} from "../../utils/utils";
import './Job.css'
import SecurityModal from "./SecurityModal";
const confirm = Modal.confirm;
const {Content} = Layout;
const {Title, Text} = Typography;
const {Search} = Input;
const routes = [
{
path: '',
breadcrumbName: '首页',
},
{
path: 'security',
breadcrumbName: '访问安全',
}
];
class Security extends Component {
inputRefOfName = React.createRef();
state = {
items: [],
total: 0,
queryParams: {
pageIndex: 1,
pageSize: 10
},
loading: false,
modalVisible: false,
modalTitle: '',
modalConfirmLoading: false,
selectedRow: undefined,
selectedRowKeys: [],
};
componentDidMount() {
this.loadTableData();
}
async delete(id) {
const result = await request.delete('/securities/' + id);
if (result.code === 1) {
message.success('删除成功');
this.loadTableData(this.state.queryParams);
} else {
message.error('删除失败 :( ' + result.message, 10);
}
}
async loadTableData(queryParams) {
this.setState({
loading: true
});
queryParams = queryParams || this.state.queryParams;
// queryParams
let paramsStr = qs.stringify(queryParams);
let data = {
items: [],
total: 0
};
try {
let result = await request.get('/securities/paging?' + paramsStr);
if (result.code === 1) {
data = result.data;
} else {
message.error(result.message);
}
} catch (e) {
} finally {
const items = data.items.map(item => {
return {'key': item['id'], ...item}
})
this.setState({
items: items,
total: data.total,
queryParams: queryParams,
loading: false
});
}
}
handleChangPage = async (pageIndex, pageSize) => {
let queryParams = this.state.queryParams;
queryParams.pageIndex = pageIndex;
queryParams.pageSize = pageSize;
this.setState({
queryParams: queryParams
});
await this.loadTableData(queryParams)
};
handleSearchByName = name => {
let query = {
...this.state.queryParams,
'pageIndex': 1,
'pageSize': this.state.queryParams.pageSize,
'name': name,
}
this.loadTableData(query);
};
showDeleteConfirm(id, content) {
let self = this;
confirm({
title: '您确定要删除此任务吗?',
content: content,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
self.delete(id);
}
});
};
showModal(title, obj = null) {
this.setState({
modalTitle: title,
modalVisible: true,
model: obj
});
};
handleCancelModal = e => {
this.setState({
modalTitle: '',
modalVisible: false
});
};
handleOk = async (formData) => {
// 弹窗 form 传来的数据
this.setState({
modalConfirmLoading: true
});
if (formData.id) {
// 向后台提交数据
const result = await request.put('/securities/' + formData.id, formData);
if (result.code === 1) {
message.success('更新成功');
this.setState({
modalVisible: false
});
this.loadTableData(this.state.queryParams);
} else {
message.error('更新失败 :( ' + result.message, 10);
}
} else {
// 向后台提交数据
const result = await request.post('/securities', formData);
if (result.code === 1) {
message.success('新增成功');
this.setState({
modalVisible: false
});
this.loadTableData(this.state.queryParams);
} else {
message.error('新增失败 :( ' + result.message, 10);
}
}
this.setState({
modalConfirmLoading: false
});
};
batchDelete = async () => {
this.setState({
delBtnLoading: true
})
try {
let result = await request.delete('/securities/' + this.state.selectedRowKeys.join(','));
if (result.code === 1) {
message.success('操作成功', 3);
this.setState({
selectedRowKeys: []
})
await this.loadTableData(this.state.queryParams);
} else {
message.error('删除失败 :( ' + result.message, 10);
}
} finally {
this.setState({
delBtnLoading: false
})
}
}
handleTableChange = (pagination, filters, sorter) => {
let query = {
...this.state.queryParams,
'order': sorter.order,
'field': sorter.field
}
this.loadTableData(query);
}
render() {
const columns = [{
title: '序号',
dataIndex: 'id',
key: 'id',
render: (id, record, index) => {
return index + 1;
}
}, {
title: 'IP',
dataIndex: 'ip',
key: 'ip',
sorter: true,
}, {
title: '规则',
dataIndex: 'rule',
key: 'rule',
render: (rule) => {
if (rule === 'allow') {
return <Tag color={'green'}>允许</Tag>
} else {
return <Tag color={'red'}>禁止</Tag>
}
}
}, {
title: '优先级',
dataIndex: 'priority',
key: 'priority',
sorter: true,
}, {
title: '来源',
dataIndex: 'source',
key: 'source',
}, {
title: '操作',
key: 'action',
render: (text, record, index) => {
return (
<div>
<Button type="link" size='small' loading={this.state.items[index]['execLoading']}
onClick={() => this.showModal('更新计划任务', record)}>编辑</Button>
<Button type="text" size='small' danger
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
</div>
)
},
}
];
const selectedRowKeys = this.state.selectedRowKeys;
const rowSelection = {
selectedRowKeys: this.state.selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
this.setState({selectedRowKeys});
},
};
const hasSelected = selectedRowKeys.length > 0;
return (
<>
<PageHeader
className="site-page-header-ghost-wrapper"
title="访问安全"
breadcrumb={{
routes: routes,
itemRender: itemRender
}}
subTitle="IP访问规则限制"
>
</PageHeader>
<Content className="site-layout-background page-content">
<div style={{marginBottom: 20}}>
<Row justify="space-around" align="middle" gutter={24}>
<Col span={12} key={1}>
<Title level={3}>IP访问规则列表</Title>
</Col>
<Col span={12} key={2} style={{textAlign: 'right'}}>
<Space>
<Search
ref={this.inputRefOfName}
placeholder="IP"
allowClear
onSearch={this.handleSearchByName}
/>
<Tooltip title='重置查询'>
<Button icon={<UndoOutlined/>} onClick={() => {
this.inputRefOfName.current.setValue('');
this.loadTableData({pageIndex: 1, pageSize: 10, name: '', content: ''})
}}>
</Button>
</Tooltip>
<Divider type="vertical"/>
<Tooltip title="新增">
<Button type="dashed" icon={<PlusOutlined/>}
onClick={() => this.showModal('新增安全访问规则', {})}>
</Button>
</Tooltip>
<Tooltip title="刷新列表">
<Button icon={<SyncOutlined/>} onClick={() => {
this.loadTableData(this.state.queryParams)
}}>
</Button>
</Tooltip>
<Tooltip title="批量删除">
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
loading={this.state.delBtnLoading}
onClick={() => {
const content = <div>
您确定要删除选中的<Text style={{color: '#1890FF'}}
strong>{this.state.selectedRowKeys.length}</Text>
</div>;
confirm({
icon: <ExclamationCircleOutlined/>,
content: content,
onOk: () => {
this.batchDelete()
},
onCancel() {
},
});
}}>
</Button>
</Tooltip>
</Space>
</Col>
</Row>
</div>
<Table
rowSelection={rowSelection}
dataSource={this.state.items}
columns={columns}
position={'both'}
pagination={{
showSizeChanger: true,
current: this.state.queryParams.pageIndex,
pageSize: this.state.queryParams.pageSize,
onChange: this.handleChangPage,
onShowSizeChange: this.handleChangPage,
total: this.state.total,
showTotal: total => `总计 ${total}`
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
{
this.state.modalVisible ?
<SecurityModal
visible={this.state.modalVisible}
title={this.state.modalTitle}
handleOk={this.handleOk}
handleCancel={this.handleCancelModal}
confirmLoading={this.state.modalConfirmLoading}
model={this.state.model}
>
</SecurityModal> : undefined
}
</Content>
</>
);
}
}
export default Security;

View File

@ -0,0 +1,66 @@
import React from 'react';
import {Form, Input, InputNumber, Modal, Radio} from "antd/lib/index";
const formItemLayout = {
labelCol: {span: 6},
wrapperCol: {span: 14},
};
const SecurityModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
const [form] = Form.useForm();
if (model['priority'] === undefined) {
model['priority'] = 0;
}
return (
<Modal
title={title}
visible={visible}
maskClosable={false}
centered={true}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
handleOk(values);
})
.catch(info => {
});
}}
onCancel={handleCancel}
confirmLoading={confirmLoading}
okText='确定'
cancelText='取消'
>
<Form form={form} {...formItemLayout} initialValues={model}>
<Form.Item name='id' noStyle>
<Input hidden={true}/>
</Form.Item>
<Form.Item label="IP地址" name='ip' rules={[{required: true, message: '请输入IP地址'}]}>
<Input autoComplete="off" placeholder="支持单个IPCIDR或使用-连接的两个IP"/>
</Form.Item>
<Form.Item label="规则" name='rule' rules={[{required: true, message: '请选择规则'}]}>
<Radio.Group onChange={async (e) => {
}}>
<Radio value={'allow'}>允许</Radio>
<Radio value={'reject'}>拒绝</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="优先级" name='priority' rules={[{required: true, message: '请输入优先级'}]}>
<InputNumber min={0} max={100}/>
</Form.Item>
</Form>
</Modal>
)
};
export default SecurityModal;

View File

@ -23,7 +23,7 @@ import Playback from "./Playback";
import {message} from "antd/es"; import {message} from "antd/es";
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons"; import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
import {PROTOCOL_COLORS} from "../../common/constants"; import {PROTOCOL_COLORS} from "../../common/constants";
import LayoutHeader from "../user/LayoutHeader";
import dayjs from "dayjs"; import dayjs from "dayjs";
const confirm = Modal.confirm; const confirm = Modal.confirm;
@ -290,6 +290,30 @@ class OfflineSession extends Component {
<Button type="link" size='small' <Button type="link" size='small'
disabled={disabled} disabled={disabled}
onClick={() => this.showPlayback(record)}>回放</Button> onClick={() => this.showPlayback(record)}>回放</Button>
<Button type="link" size='small'
onClick={() => {
confirm({
title: '您确定要禁止该IP访问本系统吗?',
content: '',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
// 向后台提交数据
let formData = {
ip: record['clientIp'],
rule: 'reject',
priority: 99,
}
const result = await request.post('/securities', formData);
if (result.code === 1) {
message.success('禁用成功');
} else {
message.error('禁用失败 :( ' + result.message, 10);
}
}
});
}}>禁用IP</Button>
<Button type="link" size='small' onClick={() => { <Button type="link" size='small' onClick={() => {
confirm({ confirm({
title: '您确定要删除此会话吗?', title: '您确定要删除此会话吗?',
@ -496,7 +520,7 @@ class OfflineSession extends Component {
overflow: 'visible' overflow: 'visible'
}} }}
onLoad={() => { onLoad={() => {
// const obj = ReactDOM.findDOMNode(this); // constant obj = ReactDOM.findDOMNode(this);
// this.setState({ // this.setState({
// "iFrameHeight": obj.contentWindow.document.body.scrollHeight + 'px' // "iFrameHeight": obj.contentWindow.document.body.scrollHeight + 'px'
// }); // });

View File

@ -24,7 +24,7 @@ import {message} from "antd/es";
import {PROTOCOL_COLORS} from "../../common/constants"; import {PROTOCOL_COLORS} from "../../common/constants";
import {DisconnectOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons"; import {DisconnectOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
import Monitor from "../access/Monitor"; import Monitor from "../access/Monitor";
import LayoutHeader from "../user/LayoutHeader";
import dayjs from "dayjs"; import dayjs from "dayjs";
const confirm = Modal.confirm; const confirm = Modal.confirm;

View File

@ -3,7 +3,6 @@ import {Button, Card, Form, Image, Input, Layout, Modal, PageHeader, Result, Spa
import {itemRender} from '../../utils/utils' import {itemRender} from '../../utils/utils'
import request from "../../common/request"; import request from "../../common/request";
import {message} from "antd/es"; import {message} from "antd/es";
import LayoutHeader from "./LayoutHeader";
import {ExclamationCircleOutlined, ReloadOutlined} from "@ant-design/icons"; import {ExclamationCircleOutlined, ReloadOutlined} from "@ant-design/icons";
const {Content} = Layout; const {Content} = Layout;

View File

@ -549,7 +549,7 @@ class User extends Component {
{/* icon={<IssuesCloseOutlined/>}*/} {/* icon={<IssuesCloseOutlined/>}*/}
{/* loading={this.state.delBtnLoading}*/} {/* loading={this.state.delBtnLoading}*/}
{/* onClick={() => {*/} {/* onClick={() => {*/}
{/* const content = <div>*/} {/* constant content = <div>*/}
{/* 您确定要启用选中的<Text style={{color: '#1890FF'}}*/} {/* 您确定要启用选中的<Text style={{color: '#1890FF'}}*/}
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/} {/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
{/* </div>;*/} {/* </div>;*/}
@ -572,7 +572,7 @@ class User extends Component {
{/* <Button type="default" danger disabled={!hasSelected} icon={<StopOutlined/>}*/} {/* <Button type="default" danger disabled={!hasSelected} icon={<StopOutlined/>}*/}
{/* loading={this.state.delBtnLoading}*/} {/* loading={this.state.delBtnLoading}*/}
{/* onClick={() => {*/} {/* onClick={() => {*/}
{/* const content = <div>*/} {/* constant content = <div>*/}
{/* 您确定要禁用选中的<Text style={{color: '#1890FF'}}*/} {/* 您确定要禁用选中的<Text style={{color: '#1890FF'}}*/}
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/} {/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
{/* </div>;*/} {/* </div>;*/}

View File

@ -6,7 +6,6 @@ import qs from "qs";
import request from "../../common/request"; import request from "../../common/request";
import {message} from "antd/es"; import {message} from "antd/es";
import {DeleteOutlined, ExclamationCircleOutlined, PlusOutlined, SyncOutlined, UndoOutlined} from '@ant-design/icons'; import {DeleteOutlined, ExclamationCircleOutlined, PlusOutlined, SyncOutlined, UndoOutlined} from '@ant-design/icons';
import LayoutHeader from "./LayoutHeader";
import UserGroupModal from "./UserGroupModal"; import UserGroupModal from "./UserGroupModal";
import UserShareAsset from "./UserShareAsset"; import UserShareAsset from "./UserShareAsset";
import dayjs from "dayjs"; import dayjs from "dayjs";