- 修复RDP协议连接导致的任意文件读取漏洞
- RDP协议增加「域」参数 - 增加安全访问功能 - 优化代码
This commit is contained in:
parent
ba5bff1b38
commit
7f7edaa33c
23
main.go
23
main.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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],
|
||||||
|
@ -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 = "-"
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
114
pkg/api/security.go
Normal 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)
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
// 创建新会话
|
// 创建新会话
|
||||||
|
@ -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
33
pkg/constant/const.go
Normal 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" // 管理员
|
||||||
|
)
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
83
pkg/model/access_security.go
Normal file
83
pkg/model/access_security.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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}
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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("邮箱信息不完整,跳过发送邮件。")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"`
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
测试阿里云,ssh,10.1.1.2,22,username,password,privateKey,passphrase,description
|
资产名称,ssh,127.0.0.1,22,username,password,privateKey,passphrase,description
|
|
@ -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']}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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";
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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'
|
@ -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;
|
421
web/src/components/devops/Security.js
Normal file
421
web/src/components/devops/Security.js
Normal 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;
|
66
web/src/components/devops/SecurityModal.js
Normal file
66
web/src/components/devops/SecurityModal.js
Normal 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="支持单个IP,CIDR或使用-连接的两个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;
|
@ -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'
|
||||||
// });
|
// });
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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>;*/}
|
||||||
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user