From bbeed5344a4328f450a7facc311720ccbf563fce Mon Sep 17 00:00:00 2001 From: dushixiang Date: Sun, 27 Dec 2020 21:44:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=8D=A2=E6=97=A5=E5=BF=97=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=BAlogrus=EF=BC=8C=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=9A=84=E8=B4=A6=E5=8F=B7=E5=92=8C=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E4=B8=BA=E5=BF=85=E5=A1=AB=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 4 +- go.sum | 2 + main.go | 26 +++++++- pkg/api/middleware.go | 3 +- pkg/api/routes.go | 3 - pkg/api/session.go | 39 ++++++++--- pkg/api/ssh.go | 6 +- pkg/api/tunnel.go | 53 ++++++++------- pkg/config/config.go | 18 ++++-- pkg/global/store.go | 10 +-- pkg/guacd/guacd.go | 20 +++--- web/src/components/asset/Asset.js | 2 +- web/src/components/asset/AssetModal.js | 68 +++++++++++++++----- web/src/components/command/BatchCommand.js | 2 +- web/src/components/command/DynamicCommand.js | 2 +- web/src/components/credential/Credential.js | 2 +- web/src/components/dashboard/Dashboard.js | 2 +- web/src/components/session/OfflineSession.js | 2 +- web/src/components/session/OnlineSession.js | 2 +- web/src/components/setting/Setting.js | 2 +- web/src/components/user/Info.js | 2 +- web/src/components/user/Logout.js | 1 + web/src/components/user/User.js | 2 +- 23 files changed, 183 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index 4ef47a9..4673389 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,18 @@ module next-terminal go 1.13 require ( + github.com/antonfisher/nested-logrus-formatter v1.3.0 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 github.com/labstack/echo/v4 v4.1.17 github.com/labstack/gommon v0.3.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/sftp v1.12.0 + github.com/sirupsen/logrus v1.2.0 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.7.1 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a gorm.io/driver/mysql v1.0.3 - gorm.io/driver/sqlite v1.1.4 // indirect + gorm.io/driver/sqlite v1.1.4 gorm.io/gorm v1.20.7 ) diff --git a/go.sum b/go.sum index c376c8a..47a23bb 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antonfisher/nested-logrus-formatter v1.3.0 h1:8zixYquU1Odk+vzAaAQPAdRh1ZjmUXNQ1T+dUBvlhVo= +github.com/antonfisher/nested-logrus-formatter v1.3.0/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= diff --git a/main.go b/main.go index 90287c2..07e0296 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,23 @@ package main import ( + "bytes" "fmt" + nested "github.com/antonfisher/nested-logrus-formatter" "github.com/labstack/gommon/log" "github.com/patrickmn/go-cache" + "github.com/sirupsen/logrus" "gorm.io/driver/mysql" "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" + "io" "next-terminal/pkg/api" "next-terminal/pkg/config" "next-terminal/pkg/global" "next-terminal/pkg/handle" "next-terminal/pkg/model" "next-terminal/pkg/utils" + "os" "strconv" "time" ) @@ -23,12 +27,29 @@ func main() { } func Run() error { + var err error + logrus.SetReportCaller(true) + logrus.SetFormatter(&nested.Formatter{ + HideKeys: true, + FieldsOrder: []string{"component", "category"}, + }) + + writer1 := &bytes.Buffer{} + writer2 := os.Stdout + writer3, err := os.OpenFile("next-terminal.log", os.O_WRONLY|os.O_CREATE, 0755) + if err != nil { + log.Fatalf("create file log.txt failed: %v", err) + } + + logrus.SetOutput(io.MultiWriter(writer1, writer2, writer3)) + global.Config, err = config.SetupConfig() if err != nil { return err } + logrus.Infof("当前数据库模式为:%v", global.Config.DB) if global.Config.DB == "mysql" { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", global.Config.Mysql.Username, @@ -38,13 +59,14 @@ func Run() error { global.Config.Mysql.Database, ) global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), + //Logger: logger.Default.LogMode(logger.Info), }) } else { global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{}) } if err != nil { + logrus.Errorf("连接数据库异常:%v", err.Error()) return err } diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index 748b9ab..25ad9dc 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -2,6 +2,7 @@ package api import ( "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" "next-terminal/pkg/global" "strings" "time" @@ -35,7 +36,7 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc { token := GetToken(c) user, found := global.Cache.Get(token) if !found { - c.Logger().Error("您的登录信息已失效,请重新登录后再试。") + logrus.Debugf("您的登录信息已失效,请重新登录后再试。") return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。") } global.Cache.Set(token, user, time.Minute*time.Duration(30)) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 6f9dec2..452a79b 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -19,9 +19,6 @@ func SetupRoutes() *echo.Echo { e.File("/favicon.ico", "web/build/favicon.ico") e.Static("/static", "web/build/static") - // Middleware - e.Use(middleware.Logger()) - //fd, _ := os.OpenFile( // "next-terminal.log", // os.O_RDWR|os.O_APPEND, diff --git a/pkg/api/session.go b/pkg/api/session.go index 0580c5e..4a36e74 100644 --- a/pkg/api/session.go +++ b/pkg/api/session.go @@ -4,8 +4,9 @@ import ( "bytes" "errors" "fmt" + "github.com/gorilla/websocket" "github.com/labstack/echo/v4" - "github.com/labstack/gommon/log" + "github.com/sirupsen/logrus" "io" "io/ioutil" "net/http" @@ -16,6 +17,7 @@ import ( "path" "strconv" "strings" + "time" ) func SessionPagingEndpoint(c echo.Context) error { @@ -38,10 +40,10 @@ func SessionPagingEndpoint(c echo.Context) error { recording := items[i].Recording + "/recording" if utils.FileExists(recording) { - log.Infof("检测到录屏文件[%v]存在", recording) + logrus.Debugf("检测到录屏文件[%v]存在", recording) items[i].Recording = "1" } else { - log.Warnf("检测到录屏文件[%v]不存在", recording) + logrus.Warnf("检测到录屏文件[%v]不存在", recording) items[i].Recording = "0" } } else { @@ -89,16 +91,18 @@ func SessionDiscontentEndpoint(c echo.Context) error { split := strings.Split(sessionIds, ",") for i := range split { - tun, ok := global.Store.Get(split[i]) - if ok { - CloseSession(split[i], tun) - } + CloseSessionById(split[i], 2001, "管理员强制关闭了此次接入。") } return Success(c, nil) } -func CloseSession(sessionId string, tun global.Tun) { - _ = tun.Tun.Close() +func CloseSessionById(sessionId string, code int, reason string) { + tun, _ := global.Store.Get(sessionId) + if tun != nil { + _ = tun.Tun.Close() + CloseSessionByWebSocket(tun.WebSocket, code, reason) + } + global.Store.Del(sessionId) session := model.Session{} @@ -109,6 +113,21 @@ func CloseSession(sessionId string, tun global.Tun) { model.UpdateSessionById(&session, sessionId) } +func CloseSessionByWebSocket(ws *websocket.Conn, c int, t string) { + if ws == nil { + return + } + ws.SetCloseHandler(func(code int, text string) error { + var message []byte + if code != websocket.CloseNoStatusReceived { + message = websocket.FormatCloseMessage(c, t) + } + _ = ws.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second)) + return nil + }) + defer ws.Close() +} + func SessionResizeEndpoint(c echo.Context) error { width := c.QueryParam("width") height := c.QueryParam("height") @@ -457,6 +476,6 @@ func SessionRecordingEndpoint(c echo.Context) error { return err } recording := path.Join(session.Recording, "recording") - log.Printf("读取录屏文件:%s", recording) + logrus.Debugf("读取录屏文件:%s", recording) return c.File(recording) } diff --git a/pkg/api/ssh.go b/pkg/api/ssh.go index 5b2ff9f..e32cb4b 100644 --- a/pkg/api/ssh.go +++ b/pkg/api/ssh.go @@ -6,8 +6,8 @@ import ( "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/pkg/sftp" + "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" - "log" "net/http" "next-terminal/pkg/model" "strconv" @@ -111,7 +111,7 @@ func SSHEndpoint(c echo.Context) error { } _, err = stdinPipe.Write(message) if err != nil { - log.Println("Tunnel write:", err) + logrus.Debugf("Tunnel write: %v", err) } } return err @@ -173,7 +173,7 @@ func WriteMessage(ws *websocket.Conn, message string) { func WriteByteMessage(ws *websocket.Conn, p []byte) { err := ws.WriteMessage(websocket.TextMessage, p) if err != nil { - log.Println("write:", err) + logrus.Debugf("write: %v", err) } } diff --git a/pkg/api/tunnel.go b/pkg/api/tunnel.go index e1975a6..cf2e9ca 100644 --- a/pkg/api/tunnel.go +++ b/pkg/api/tunnel.go @@ -1,11 +1,9 @@ package api import ( - "fmt" "github.com/gorilla/websocket" "github.com/labstack/echo/v4" - "github.com/pkg/sftp" - "log" + "github.com/sirupsen/logrus" "next-terminal/pkg/global" "next-terminal/pkg/guacd" "next-terminal/pkg/model" @@ -17,6 +15,7 @@ func TunEndpoint(c echo.Context) error { ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil) if err != nil { + logrus.Errorf("升级为WebSocket协议失败:%v", err.Error()) return err } @@ -35,7 +34,6 @@ func TunEndpoint(c echo.Context) error { propertyMap := model.FindAllPropertiesMap() var session model.Session - var sftpClient *sftp.Client if len(connectionId) > 0 { session, err = model.FindSessionByConnectionId(connectionId) @@ -95,11 +93,6 @@ func TunEndpoint(c echo.Context) error { configuration.SetParameter(guacd.FontSize, strconv.Itoa(fontSize)) configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName]) configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme]) - - sftpClient, err = CreateSftpClient(session.AssetId) - if err != nil { - return err - } break case "vnc": configuration.SetParameter("password", session.Password) @@ -117,21 +110,20 @@ func TunEndpoint(c echo.Context) error { } addr := propertyMap[guacd.Host] + ":" + propertyMap[guacd.Port] + + logrus.Infof("connect to %v with global: %+v", addr, configuration) + tunnel, err := guacd.NewTunnel(addr, configuration) if err != nil { return err } - fmt.Printf("=====================================================\n") - fmt.Printf("connect to %v with global: %+v\n", addr, configuration) - fmt.Printf("=====================================================\n") - tun := global.Tun{ - Tun: tunnel, - SftpClient: sftpClient, + Tun: tunnel, + WebSocket: ws, } - global.Store.Set(sessionId, tun) + global.Store.Set(sessionId, &tun) if len(session.ConnectionId) == 0 { session.ConnectionId = tunnel.UUID @@ -142,19 +134,30 @@ func TunEndpoint(c echo.Context) error { model.UpdateSessionById(&session, sessionId) } + go func() { + sftpClient, err := CreateSftpClient(session.AssetId) + if err != nil { + CloseSessionById(sessionId, 2002, err.Error()) + logrus.Errorf("创建sftp客户端失败:%v", err.Error()) + } + item, ok := global.Store.Get(sessionId) + if ok { + item.SftpClient = sftpClient + } + }() + go func() { for true { instruction, err := tunnel.Read() if err != nil { - CloseSession(sessionId, tun) - log.Printf("WS读取异常: %v", err) + CloseSessionById(sessionId, 523, err.Error()) + logrus.Printf("WebSocket读取错误: %v", err) break } - //fmt.Printf("<= %v \n", string(instruction)) err = ws.WriteMessage(websocket.TextMessage, instruction) if err != nil { - CloseSession(sessionId, tun) - log.Printf("WS写入异常: %v", err) + CloseSessionById(sessionId, 523, err.Error()) + logrus.Printf("WebSocket写入错误: %v", err) break } } @@ -163,14 +166,14 @@ func TunEndpoint(c echo.Context) error { for true { _, message, err := ws.ReadMessage() if err != nil { - CloseSession(sessionId, tun) - log.Printf("Tunnel读取异常: %v", err) + CloseSessionById(sessionId, 523, err.Error()) + logrus.Printf("隧道读取错误: %v", err) break } _, err = tunnel.WriteAndFlush(message) if err != nil { - CloseSession(sessionId, tun) - log.Printf("Tunnel写入异常: %v", err) + CloseSessionById(sessionId, 523, err.Error()) + logrus.Printf("隧道写入错误: %v", err) break } } diff --git a/pkg/config/config.go b/pkg/config/config.go index 701c664..311a5cc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,6 +1,7 @@ package config import ( + "github.com/spf13/pflag" "strings" "github.com/spf13/viper" @@ -39,10 +40,19 @@ func SetupConfig() (*Config, error) { viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - err := viper.ReadInConfig() - if err != nil { - return nil, err - } + pflag.String("db", "sqlite", "db mode") + pflag.String("sqlite.file", "next-terminal.db", "sqlite db file") + pflag.String("mysql.hostname", "127.0.0.1", "mysql hostname") + pflag.Int("mysql.port", 3306, "mysql port") + pflag.String("mysql.username", "mysql", "mysql username") + pflag.String("mysql.password", "mysql", "mysql password") + pflag.String("mysql.database", "next_terminal", "mysql database") + + pflag.String("server.addr", "0.0.0.0:8088", "server listen addr") + + pflag.Parse() + _ = viper.BindPFlags(pflag.CommandLine) + _ = viper.ReadInConfig() var config = &Config{ DB: viper.GetString("db"), diff --git a/pkg/global/store.go b/pkg/global/store.go index 2017f93..0b1bd34 100644 --- a/pkg/global/store.go +++ b/pkg/global/store.go @@ -1,21 +1,23 @@ package global import ( + "github.com/gorilla/websocket" "github.com/pkg/sftp" "next-terminal/pkg/guacd" "sync" ) type Tun struct { - Tun guacd.Tunnel + Tun *guacd.Tunnel SftpClient *sftp.Client + WebSocket *websocket.Conn } type TunStore struct { m sync.Map } -func (s *TunStore) Set(k string, v Tun) { +func (s *TunStore) Set(k string, v *Tun) { s.m.Store(k, v) } @@ -23,10 +25,10 @@ func (s *TunStore) Del(k string) { s.m.Delete(k) } -func (s *TunStore) Get(k string) (item Tun, ok bool) { +func (s *TunStore) Get(k string) (item *Tun, ok bool) { value, ok := s.m.Load(k) if ok { - return value.(Tun), true + return value.(*Tun), true } return item, false } diff --git a/pkg/guacd/guacd.go b/pkg/guacd/guacd.go index 1f014f9..ca9d172 100644 --- a/pkg/guacd/guacd.go +++ b/pkg/guacd/guacd.go @@ -106,13 +106,14 @@ type Tunnel struct { IsOpen bool } -func NewTunnel(address string, config Configuration) (ret Tunnel, err error) { +func NewTunnel(address string, config Configuration) (ret *Tunnel, err error) { conn, err := net.Dial("tcp", address) if err != nil { return } + ret = &Tunnel{} ret.conn = conn ret.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) ret.Config = config @@ -123,7 +124,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) { } if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil { - return Tunnel{}, err + return nil, err } args, err := ret.expect("args") @@ -135,21 +136,21 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) { height := config.GetParameter("height") // send size if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, "96")); err != nil { - return Tunnel{}, err + return nil, err } if err := ret.WriteInstructionAndFlush(NewInstruction("audio")); err != nil { - return Tunnel{}, err + return nil, err } if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil { - return Tunnel{}, err + return nil, err } if err := ret.WriteInstructionAndFlush(NewInstruction("image")); err != nil { - return Tunnel{}, err + return nil, err } if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil { - return Tunnel{}, err + return nil, err } parameters := make([]string, len(args.Args)) @@ -163,7 +164,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) { } // send connect if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil { - return Tunnel{}, err + return nil, err } ready, err := ret.expect("ready") @@ -172,7 +173,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) { } if len(ready.Args) == 0 { - return ret, errors.New("no connection id received") + return nil, errors.New("no connection id received") } ret.UUID = ready.Args[0] @@ -225,7 +226,6 @@ func (opt *Tunnel) ReadInstruction() (instruction Instruction, err error) { if err != nil { return instruction, err } - fmt.Printf("<- %v \n", msg) return instruction.Parse(msg), err } diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index bcfc889..e7280ab 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -386,7 +386,7 @@ class Asset extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="资产" > diff --git a/web/src/components/asset/AssetModal.js b/web/src/components/asset/AssetModal.js index 7696b6f..87588ad 100644 --- a/web/src/components/asset/AssetModal.js +++ b/web/src/components/asset/AssetModal.js @@ -7,12 +7,29 @@ const {Option} = Select; // 子级页面 // Ant form create 表单内置方法 +const protocolMapping = { + 'ssh': [ + {text: '自定义', value: 'custom'}, + {text: '授权凭证', value: 'credential'}, + {text: '私钥', value: 'private-key'} + ], + 'rdp': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}], + 'vnc': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}], + 'telnet': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}] +} + const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, model}) { const [form] = Form.useForm(); let [accountType, setAccountType] = useState(model.accountType); + let initAccountTypes = [] + if (model.protocol) { + initAccountTypes = protocolMapping[model.protocol]; + } + let [accountTypes, setAccountTypes] = useState(initAccountTypes); + useEffect(() => { setAccountType(model.accountType); }); @@ -35,18 +52,35 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa switch (e.target.value) { case 'ssh': port = 22; - break; - case 'rdp': - port = 3389; - break; - case 'vnc': - port = 5901; + setAccountTypes(protocolMapping['ssh']); form.setFieldsValue({ accountType: 'custom', }); + handleAccountTypeChange('custom'); + break; + case 'rdp': + port = 3389; + setAccountTypes(protocolMapping['rdp']); + form.setFieldsValue({ + accountType: 'custom', + }); + handleAccountTypeChange('custom'); + break; + case 'vnc': + port = 5901; + setAccountTypes(protocolMapping['vnc']); + form.setFieldsValue({ + accountType: 'custom', + }); + handleAccountTypeChange('custom'); break; case 'telnet': port = 23; + setAccountTypes(protocolMapping['telnet']); + form.setFieldsValue({ + accountType: 'custom', + }); + handleAccountTypeChange('custom'); break; default: port = 65535; @@ -57,6 +91,11 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa }); }; + const handleAccountTypeChange = v => { + setAccountType(v); + model.accountType = v; + } + return ( - + {accountTypes.map(item => { + return () + })} @@ -138,13 +174,11 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa { accountType === 'custom' ? <> - + - + diff --git a/web/src/components/command/BatchCommand.js b/web/src/components/command/BatchCommand.js index 07c49e5..840ed1d 100644 --- a/web/src/components/command/BatchCommand.js +++ b/web/src/components/command/BatchCommand.js @@ -57,7 +57,7 @@ class BatchCommand extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="动态指令" > diff --git a/web/src/components/command/DynamicCommand.js b/web/src/components/command/DynamicCommand.js index 605042e..de44dd8 100644 --- a/web/src/components/command/DynamicCommand.js +++ b/web/src/components/command/DynamicCommand.js @@ -361,7 +361,7 @@ class DynamicCommand extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="批量动态指令执行" > diff --git a/web/src/components/credential/Credential.js b/web/src/components/credential/Credential.js index e75c96d..a9ab041 100644 --- a/web/src/components/credential/Credential.js +++ b/web/src/components/credential/Credential.js @@ -282,7 +282,7 @@ class Credential extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="访问资产的账户、密钥等" > diff --git a/web/src/components/dashboard/Dashboard.js b/web/src/components/dashboard/Dashboard.js index f7d9633..585d96c 100644 --- a/web/src/components/dashboard/Dashboard.js +++ b/web/src/components/dashboard/Dashboard.js @@ -88,7 +88,7 @@ class Dashboard extends Component { }} subTitle="仪表盘" extra={[ - + ]} > diff --git a/web/src/components/session/OfflineSession.js b/web/src/components/session/OfflineSession.js index 404d5b2..02a8724 100644 --- a/web/src/components/session/OfflineSession.js +++ b/web/src/components/session/OfflineSession.js @@ -359,7 +359,7 @@ class OfflineSession extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="离线会话管理" > diff --git a/web/src/components/session/OnlineSession.js b/web/src/components/session/OnlineSession.js index faa387a..1d57069 100644 --- a/web/src/components/session/OnlineSession.js +++ b/web/src/components/session/OnlineSession.js @@ -350,7 +350,7 @@ class OnlineSession extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="查询实时在线会话" > diff --git a/web/src/components/setting/Setting.js b/web/src/components/setting/Setting.js index c32c807..d962536 100644 --- a/web/src/components/setting/Setting.js +++ b/web/src/components/setting/Setting.js @@ -109,7 +109,7 @@ class Setting extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="系统设置" > diff --git a/web/src/components/user/Info.js b/web/src/components/user/Info.js index 039f929..ffd3ac7 100644 --- a/web/src/components/user/Info.js +++ b/web/src/components/user/Info.js @@ -80,7 +80,7 @@ class Info extends Component { itemRender: itemRender }} extra={[ - + ]} subTitle="个人中心" > diff --git a/web/src/components/user/Logout.js b/web/src/components/user/Logout.js index a8ec264..c91c9cd 100644 --- a/web/src/components/user/Logout.js +++ b/web/src/components/user/Logout.js @@ -18,6 +18,7 @@ class Logout extends Component { return (
+ ]} subTitle="平台用户管理" >