From f0157dbaeb638b36f8354db00a06ec7fe861c5dc Mon Sep 17 00:00:00 2001 From: dushixiang <798148596@qq.com> Date: Wed, 20 Jan 2021 22:57:26 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=97=A5=E5=BF=97=20-=20=E5=A2=9E=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=B4=A2=E5=BC=95=20-=20=E4=BF=AE=E6=94=B9=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=AE=89=E8=A3=85=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install-naive.MD | 2 +- main.go | 51 ++- pkg/api/account.go | 57 ++- pkg/api/login-log.go | 42 ++ pkg/api/middleware.go | 7 +- pkg/api/routes.go | 18 +- pkg/model/asset.go | 4 +- pkg/model/command.go | 2 +- pkg/model/credential.go | 2 +- pkg/model/login-log.go | 102 +++++ pkg/model/resource-sharer.go | 9 +- pkg/model/session.go | 6 +- pkg/model/user-group-member.go | 4 +- pkg/model/user-group.go | 2 +- pkg/model/user.go | 4 +- web/package.json | 2 +- web/src/App.js | 30 +- web/src/components/session/LoginLog.js | 399 +++++++++++++++++++ web/src/components/session/OfflineSession.js | 30 +- web/src/components/session/Playback.js | 5 +- 20 files changed, 700 insertions(+), 78 deletions(-) create mode 100644 pkg/api/login-log.go create mode 100644 pkg/model/login-log.go create mode 100644 web/src/components/session/LoginLog.js diff --git a/docs/install-naive.MD b/docs/install-naive.MD index 27fd1cf..1da3aec 100644 --- a/docs/install-naive.MD +++ b/docs/install-naive.MD @@ -126,7 +126,7 @@ mkdir ~/next-terminal & cd ~/next-terminal 下载 ```shell -wget https://github.com/dushixiang/next-terminal/releases/download/v0.0.4/next-terminal.tgz +wget https://github.com/dushixiang/next-terminal/releases/latest/download/next-terminal.tgz ``` 解压 diff --git a/main.go b/main.go index acf08ba..33b7ff3 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( "gorm.io/driver/mysql" "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" "io" "next-terminal/pkg/api" "next-terminal/pkg/config" @@ -23,6 +22,8 @@ import ( "time" ) +const Version = "v0.0.9" + func main() { log.Fatal(Run()) } @@ -35,12 +36,11 @@ func Run() error { / | \_/ __ \\ \/ /\ __\ | |_/ __ \_ __ \/ \| |/ \\__ \ | | / | \ ___/ > < | | | |\ ___/| | \/ Y Y \ | | \/ __ \| |__ \____|__ /\___ >__/\_ \ |__| |____| \___ >__| |__|_| /__|___| (____ /____/ - \/ \/ \/ \/ \/ \/ \/ - -`) + \/ \/ \/ \/ \/ \/ \/ ` + Version + "\n") var err error //logrus.SetReportCaller(true) + logrus.SetLevel(logrus.DebugLevel) logrus.SetFormatter(&nested.Formatter{ HideKeys: true, FieldsOrder: []string{"component", "category"}, @@ -70,7 +70,7 @@ 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{}) @@ -88,8 +88,9 @@ func Run() error { users := model.FindAllUser() if len(users) == 0 { + initPassword := "admin" var pass []byte - if pass, err = utils.Encoder.Encode([]byte("admin")); err != nil { + if pass, err = utils.Encoder.Encode([]byte(initPassword)); err != nil { return err } @@ -104,6 +105,7 @@ func Run() error { if err := model.CreateNewUser(&user); err != nil { return err } + logrus.Infof("初始用户创建成功,账号:「%v」密码:「%v」", user.Username, initPassword) } else { for i := range users { // 修正默认用户类型为管理员 @@ -141,6 +143,9 @@ func Run() error { if err := global.DB.AutoMigrate(&model.UserGroupMember{}); err != nil { return err } + if err := global.DB.AutoMigrate(&model.LoginLog{}); err != nil { + return err + } if err := global.DB.AutoMigrate(&model.Num{}); err != nil { return err } @@ -153,9 +158,43 @@ func Run() error { } } + // 配置缓存器 global.Cache = cache.New(5*time.Minute, 10*time.Minute) + global.Cache.OnEvicted(func(key string, value interface{}) { + logrus.Debugf("用户Token「%v」过期", key) + model.Logout(key) + }) global.Store = global.NewStore() + loginLogs, err := model.FindAliveLoginLogs() + if err != nil { + return err + } + + for i := range loginLogs { + loginLog := loginLogs[i] + token := loginLog.ID + user, err := model.FindUserById(loginLog.UserId) + if err != nil { + logrus.Debugf("用户「%v」获取失败,忽略", loginLog.UserId) + continue + } + + authorization := api.Authorization{ + Token: token, + Remember: loginLog.Remember, + User: user, + } + + if authorization.Remember { + // 记住登录有效期两周 + global.Cache.Set(token, authorization, api.RememberEffectiveTime) + } else { + global.Cache.Set(token, authorization, api.NotRememberEffectiveTime) + } + logrus.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token) + } + e := api.SetupRoutes() if err := handle.InitProperties(); err != nil { return err diff --git a/pkg/api/account.go b/pkg/api/account.go index b499d73..489bf62 100644 --- a/pkg/api/account.go +++ b/pkg/api/account.go @@ -11,6 +11,11 @@ import ( "github.com/labstack/echo/v4" ) +const ( + RememberEffectiveTime = time.Hour * time.Duration(24*14) + NotRememberEffectiveTime = time.Minute * time.Duration(2) +) + type LoginAccount struct { Username string `json:"username"` Password string `json:"password"` @@ -53,7 +58,16 @@ func LoginEndpoint(c echo.Context) error { return Fail(c, 0, "") } - token := strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "") + token, err := Login(c, loginAccount, user) + if err != nil { + return err + } + + return Success(c, token) +} + +func Login(c echo.Context, loginAccount LoginAccount, user model.User) (token string, err error) { + token = strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "") authorization := Authorization{ Token: token, @@ -63,14 +77,28 @@ func LoginEndpoint(c echo.Context) error { if authorization.Remember { // 记住登录有效期两周 - global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14)) + global.Cache.Set(token, authorization, RememberEffectiveTime) } else { - global.Cache.Set(token, authorization, time.Hour*time.Duration(2)) + global.Cache.Set(token, authorization, NotRememberEffectiveTime) } - model.UpdateUserById(&model.User{Online: true}, user.ID) + // 保存登录日志 + loginLog := model.LoginLog{ + ID: token, + UserId: user.ID, + ClientIP: c.RealIP(), + ClientUserAgent: c.Request().UserAgent(), + LoginTime: utils.NowJsonTime(), + Remember: authorization.Remember, + } - return Success(c, token) + if model.CreateNewLoginLog(&loginLog) != nil { + return "", err + } + + // 修改登录状态 + model.UpdateUserById(&model.User{Online: true}, user.ID) + return token, nil } func loginWithTotpEndpoint(c echo.Context) error { @@ -92,29 +120,18 @@ func loginWithTotpEndpoint(c echo.Context) error { return Fail(c, -2, "您的TOTP不匹配") } - token := strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "") - - authorization := Authorization{ - Token: token, - Remember: loginAccount.Remember, - User: user, + token, err := Login(c, loginAccount, user) + if err != nil { + return err } - if authorization.Remember { - // 记住登录有效期两周 - global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14)) - } else { - global.Cache.Set(token, authorization, time.Hour*time.Duration(2)) - } - - model.UpdateUserById(&model.User{Online: true}, user.ID) - return Success(c, token) } func LogoutEndpoint(c echo.Context) error { token := GetToken(c) global.Cache.Delete(token) + model.Logout(token) return Success(c, nil) } diff --git a/pkg/api/login-log.go b/pkg/api/login-log.go new file mode 100644 index 0000000..a50c39e --- /dev/null +++ b/pkg/api/login-log.go @@ -0,0 +1,42 @@ +package api + +import ( + "github.com/labstack/echo/v4" + "next-terminal/pkg/global" + "next-terminal/pkg/model" + "strconv" + "strings" +) + +func LoginLogPagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + userId := c.QueryParam("userId") + clientIp := c.QueryParam("clientIp") + + items, total, err := model.FindPageLoginLog(pageIndex, pageSize, userId, clientIp) + + if err != nil { + return err + } + + return Success(c, H{ + "total": total, + "items": items, + }) +} + +func LoginLogDeleteEndpoint(c echo.Context) error { + ids := c.Param("id") + split := strings.Split(ids, ",") + for i := range split { + token := split[i] + global.Cache.Delete(token) + model.Logout(token) + } + if err := model.DeleteLoginLogByIdIn(split); err != nil { + return err + } + + return Success(c, nil) +} diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index c1b6c41..533998e 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -3,7 +3,6 @@ package api import ( "fmt" "github.com/labstack/echo/v4" - "github.com/sirupsen/logrus" "next-terminal/pkg/global" "next-terminal/pkg/model" "strings" @@ -44,7 +43,6 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc { token := GetToken(c) authorization, found := global.Cache.Get(token) if !found { - logrus.Debugf("您的登录信息已失效,请重新登录后再试。") return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。") } @@ -62,7 +60,10 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc { func Admin(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - account, _ := GetCurrentAccount(c) + account, found := GetCurrentAccount(c) + if !found { + return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。") + } if account.Type != model.TypeAdmin { return Fail(c, 403, "permission denied") diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 9689a62..bcf75f7 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -53,13 +53,13 @@ func SetupRoutes() *echo.Echo { users.POST("/:id/reset-totp", Admin(UserResetTotpEndpoint)) } - userGroups := e.Group("/user-groups") + userGroups := e.Group("/user-groups", Admin) { - userGroups.POST("", Admin(UserGroupCreateEndpoint)) - userGroups.GET("/paging", Admin(UserGroupPagingEndpoint)) - userGroups.PUT("/:id", Admin(UserGroupUpdateEndpoint)) - userGroups.DELETE("/:id", Admin(UserGroupDeleteEndpoint)) - userGroups.GET("/:id", Admin(UserGroupGetEndpoint)) + userGroups.POST("", UserGroupCreateEndpoint) + userGroups.GET("/paging", UserGroupPagingEndpoint) + userGroups.PUT("/:id", UserGroupUpdateEndpoint) + userGroups.DELETE("/:id", UserGroupDeleteEndpoint) + userGroups.GET("/:id", UserGroupGetEndpoint) //userGroups.POST("/:id/members", UserGroupAddMembersEndpoint) //userGroups.DELETE("/:id/members/:memberId", UserGroupDelMembersEndpoint) } @@ -125,6 +125,12 @@ func SetupRoutes() *echo.Echo { resourceSharers.POST("/add-resources", Admin(ResourceAddByUserIdAssignEndPoint)) } + loginLogs := e.Group("login-logs", Admin) + { + loginLogs.GET("/paging", LoginLogPagingEndpoint) + loginLogs.DELETE("/:id", LoginLogDeleteEndpoint) + } + e.GET("/properties", PropertyGetEndpoint) e.PUT("/properties", Admin(PropertyUpdateEndpoint)) diff --git a/pkg/model/asset.go b/pkg/model/asset.go index 6c9ce3c..df6fa67 100644 --- a/pkg/model/asset.go +++ b/pkg/model/asset.go @@ -15,14 +15,14 @@ type Asset struct { AccountType string `json:"accountType"` Username string `json:"username"` Password string `json:"password"` - CredentialId string `json:"credentialId"` + CredentialId string `gorm:"index" json:"credentialId"` PrivateKey string `json:"privateKey"` Passphrase string `json:"passphrase"` Description string `json:"description"` Active bool `json:"active"` Created utils.JsonTime `json:"created"` Tags string `json:"tags"` - Owner string `json:"owner"` + Owner string `gorm:"index" json:"owner"` } type AssetVo struct { diff --git a/pkg/model/command.go b/pkg/model/command.go index 7f86056..a93e204 100644 --- a/pkg/model/command.go +++ b/pkg/model/command.go @@ -10,7 +10,7 @@ type Command struct { Name string `json:"name"` Content string `json:"content"` Created utils.JsonTime `json:"created"` - Owner string `json:"owner"` + Owner string `gorm:"index" json:"owner"` } type CommandVo struct { diff --git a/pkg/model/credential.go b/pkg/model/credential.go index db0a585..31094e0 100644 --- a/pkg/model/credential.go +++ b/pkg/model/credential.go @@ -20,7 +20,7 @@ type Credential struct { PrivateKey string `json:"privateKey"` Passphrase string `json:"passphrase"` Created utils.JsonTime `json:"created"` - Owner string `json:"owner"` + Owner string `gorm:"index" json:"owner"` } func (r *Credential) TableName() string { diff --git a/pkg/model/login-log.go b/pkg/model/login-log.go new file mode 100644 index 0000000..2830eba --- /dev/null +++ b/pkg/model/login-log.go @@ -0,0 +1,102 @@ +package model + +import ( + "github.com/sirupsen/logrus" + "next-terminal/pkg/global" + "next-terminal/pkg/utils" +) + +type LoginLog struct { + ID string `gorm:"primary_key" json:"id"` + UserId string `gorm:"index" json:"userId"` + ClientIP string `json:"clientIp"` + ClientUserAgent string `json:"clientUserAgent"` + LoginTime utils.JsonTime `json:"loginTime"` + LogoutTime utils.JsonTime `json:"logoutTime"` + Remember bool `json:"remember"` +} + +type LoginLogVo struct { + ID string `json:"id"` + UserId string `json:"userId"` + UserName string `json:"userName"` + ClientIP string `json:"clientIp"` + ClientUserAgent string `json:"clientUserAgent"` + LoginTime utils.JsonTime `json:"loginTime"` + LogoutTime utils.JsonTime `json:"logoutTime"` + Remember bool `json:"remember"` +} + +func (r *LoginLog) TableName() string { + return "login_logs" +} + +func FindPageLoginLog(pageIndex, pageSize int, userId, clientIp string) (o []LoginLogVo, total int64, err error) { + + db := global.DB.Table("login_logs").Select("login_logs.id,login_logs.user_id,login_logs.client_ip,login_logs.client_user_agent,login_logs.login_time, login_logs.logout_time, users.nickname as user_name").Joins("left join users on login_logs.user_id = users.id") + dbCounter := global.DB.Table("login_logs").Select("DISTINCT login_logs.id") + + if userId != "" { + db = db.Where("login_logs.user_id = ?", userId) + dbCounter = dbCounter.Where("login_logs.user_id = ?", userId) + } + + if clientIp != "" { + db = db.Where("login_logs.client_ip like ?", "%"+clientIp+"%") + dbCounter = dbCounter.Where("login_logs.client_ip like ?", "%"+clientIp+"%") + } + + err = dbCounter.Count(&total).Error + if err != nil { + return nil, 0, err + } + + err = db.Order("login_logs.login_time desc").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&o).Error + if o == nil { + o = make([]LoginLogVo, 0) + } + return +} + +func FindAliveLoginLogs() (o []LoginLog, err error) { + err = global.DB.Where("logout_time is null").Find(&o).Error + return +} + +func FindAliveLoginLogsByUserId(userId string) (o []LoginLog, err error) { + err = global.DB.Where("logout_time is null and user_id = ?", userId).Find(&o).Error + return +} + +func CreateNewLoginLog(o *LoginLog) (err error) { + return global.DB.Create(o).Error +} + +func DeleteLoginLogByIdIn(ids []string) (err error) { + return global.DB.Where("id in ?", ids).Delete(&LoginLog{}).Error +} + +func FindLoginLogById(id string) (o LoginLog, err error) { + err = global.DB.Where("id = ?", id).First(&o).Error + return +} + +func Logout(id string) { + + loginLog, err := FindLoginLogById(id) + if err != nil { + logrus.Warnf("登录日志「%v」获取失败", id) + return + } + + global.DB.Table("login_logs").Where("id = ?", id).Update("logout_time", utils.NowJsonTime()) + + loginLogs, err := FindAliveLoginLogsByUserId(loginLog.UserId) + if err != nil { + return + } + + if len(loginLogs) == 0 { + UpdateUserById(&User{Online: false}, loginLog.UserId) + } +} diff --git a/pkg/model/resource-sharer.go b/pkg/model/resource-sharer.go index 096803d..7d7362c 100644 --- a/pkg/model/resource-sharer.go +++ b/pkg/model/resource-sharer.go @@ -8,10 +8,11 @@ import ( ) type ResourceSharer struct { - ID string `gorm:"primary_key" json:"name"` - ResourceId string `json:"resourceId"` - ResourceType string `json:"resourceType"` - UserId string `json:"userId"` + ID string `gorm:"primary_key" json:"id"` + ResourceId string `gorm:"index" json:"resourceId"` + ResourceType string `gorm:"index" json:"resourceType"` + UserId string `gorm:"index" json:"userId"` + UserGroupId string `gorm:"index" json:"userGroupId"` } func (r *ResourceSharer) TableName() string { diff --git a/pkg/model/session.go b/pkg/model/session.go index 08fcf34..6e970f0 100644 --- a/pkg/model/session.go +++ b/pkg/model/session.go @@ -19,14 +19,14 @@ type Session struct { IP string `json:"ip"` Port int `json:"port"` ConnectionId string `json:"connectionId"` - AssetId string `json:"assetId"` + AssetId string `gorm:"index" json:"assetId"` Username string `json:"username"` Password string `json:"password"` - Creator string `json:"creator"` + Creator string `gorm:"index" json:"creator"` ClientIP string `json:"clientIp"` Width int `json:"width"` Height int `json:"height"` - Status string `json:"status"` + Status string `gorm:"index" json:"status"` Recording string `json:"recording"` PrivateKey string `json:"privateKey"` Passphrase string `json:"passphrase"` diff --git a/pkg/model/user-group-member.go b/pkg/model/user-group-member.go index ca05e08..88ea276 100644 --- a/pkg/model/user-group-member.go +++ b/pkg/model/user-group-member.go @@ -4,8 +4,8 @@ import "next-terminal/pkg/global" type UserGroupMember struct { ID string `gorm:"primary_key" json:"name"` - UserId string `json:"userId"` - UserGroupId string `json:"userGroupId"` + UserId string `gorm:"index" json:"userId"` + UserGroupId string `gorm:"index" json:"userGroupId"` } func (r *UserGroupMember) TableName() string { diff --git a/pkg/model/user-group.go b/pkg/model/user-group.go index 21a77fa..30ae658 100644 --- a/pkg/model/user-group.go +++ b/pkg/model/user-group.go @@ -13,7 +13,7 @@ type UserGroup struct { } type UserGroupVo struct { - ID string `gorm:"primary_key" json:"id"` + ID string `json:"id"` Name string `json:"name"` Created utils.JsonTime `json:"created"` MemberCount int64 `json:"memberCount"` diff --git a/pkg/model/user.go b/pkg/model/user.go index 83f5f0d..963c83c 100644 --- a/pkg/model/user.go +++ b/pkg/model/user.go @@ -13,7 +13,7 @@ const ( type User struct { ID string `gorm:"primary_key" json:"id"` - Username string `json:"username"` + Username string `gorm:"index:unique" json:"username"` Password string `json:"password"` Nickname string `json:"nickname"` TOTPSecret string `json:"-"` @@ -24,7 +24,7 @@ type User struct { } type UserVo struct { - ID string `gorm:"primary_key" json:"id"` + ID string `json:"id"` Username string `json:"username"` Nickname string `json:"nickname"` Online bool `json:"online"` diff --git a/web/package.json b/web/package.json index d06af16..a51ee93 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "next-terminal", - "version": "0.0.8", + "version": "0.0.9", "private": true, "dependencies": { "@ant-design/icons": "^4.3.0", diff --git a/web/src/App.js b/web/src/App.js index 625b419..0795536 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -22,11 +22,14 @@ import { DisconnectOutlined, IdcardOutlined, LinkOutlined, + LoginOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SettingOutlined, - SolutionOutlined, TeamOutlined, - UserOutlined, UserSwitchOutlined + SolutionOutlined, + TeamOutlined, + UserOutlined, + UserSwitchOutlined } from '@ant-design/icons'; import Info from "./components/user/Info"; import request from "./common/request"; @@ -36,6 +39,7 @@ import BatchCommand from "./components/command/BatchCommand"; import {NT_PACKAGE} from "./utils/utils"; import {isAdmin} from "./service/permission"; import UserGroup from "./components/user/UserGroup"; +import LoginLog from "./components/session/LoginLog"; const {Footer, Sider} = Layout; @@ -142,17 +146,16 @@ class App extends Component { }> - }> - - 授权凭证 - - - }> 资产列表 + }> + + 授权凭证 + + }> @@ -183,15 +186,21 @@ class App extends Component { 历史会话 + + }> + + 登录日志 + + - }> + }> }> 用户管理 - }> + }> 用户组管理 @@ -243,6 +252,7 @@ class App extends Component { + diff --git a/web/src/components/session/LoginLog.js b/web/src/components/session/LoginLog.js new file mode 100644 index 0000000..021bf3d --- /dev/null +++ b/web/src/components/session/LoginLog.js @@ -0,0 +1,399 @@ +import React, {Component} from 'react'; + +import { + Button, + Col, + Divider, + Input, + Layout, + Modal, + notification, + PageHeader, + Row, + Select, + Space, + Table, + Tooltip, + Typography +} from "antd"; +import qs from "qs"; +import request from "../../common/request"; +import {formatDate, isEmpty, itemRender} from "../../utils/utils"; +import {message} from "antd/es"; +import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons"; +import Logout from "../user/Logout"; + +const confirm = Modal.confirm; +const {Content} = Layout; +const {Search} = Input; +const {Title, Text} = Typography; +const routes = [ + { + path: '', + breadcrumbName: '首页', + }, + { + path: 'loginLog', + breadcrumbName: '登录日志', + } +]; + +class LoginLog extends Component { + + inputRefOfClientIp = React.createRef(); + + state = { + items: [], + total: 0, + queryParams: { + pageIndex: 1, + pageSize: 10, + userId: undefined, + }, + loading: false, + selectedRowKeys: [], + delBtnLoading: false, + users: [], + }; + + componentDidMount() { + this.loadTableData(); + this.handleSearchByNickname(''); + } + + async loadTableData(queryParams) { + queryParams = queryParams || this.state.queryParams; + + this.setState({ + queryParams: queryParams, + loading: true + }); + + // queryParams + let paramsStr = qs.stringify(queryParams); + + let data = { + items: [], + total: 0 + }; + + try { + let result = await request.get('/login-logs/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 = (pageIndex, pageSize) => { + let queryParams = this.state.queryParams; + queryParams.pageIndex = pageIndex; + queryParams.pageSize = pageSize; + + this.setState({ + queryParams: queryParams + }); + + this.loadTableData(queryParams) + }; + + handleSearchByClientIp = clientIp => { + let query = { + ...this.state.queryParams, + 'pageIndex': 1, + 'pageSize': this.state.queryParams.pageSize, + 'clientIp': clientIp, + } + this.loadTableData(query); + } + + handleChangeByProtocol = protocol => { + let query = { + ...this.state.queryParams, + 'pageIndex': 1, + 'pageSize': this.state.queryParams.pageSize, + 'protocol': protocol, + } + this.loadTableData(query); + } + + handleSearchByNickname = async nickname => { + const result = await request.get(`/users/paging?pageIndex=1&pageSize=1000&nickname=${nickname}`); + if (result.code !== 1) { + message.error(result.message, 10); + return; + } + + this.setState({ + users: result.data.items + }) + } + + handleChangeByUserId = userId => { + let query = { + ...this.state.queryParams, + 'pageIndex': 1, + 'pageSize': this.state.queryParams.pageSize, + 'userId': userId, + } + this.loadTableData(query); + } + + batchDelete = async () => { + this.setState({ + delBtnLoading: true + }) + try { + let result = await request.delete('/login-logs/' + 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 + }) + } + } + + render() { + + const columns = [{ + title: '序号', + dataIndex: 'id', + key: 'id', + render: (id, record, index) => { + return index + 1; + } + }, { + title: '用户昵称', + dataIndex: 'userName', + key: 'userName' + }, { + title: '来源IP', + dataIndex: 'clientIp', + key: 'clientIp' + }, { + title: '浏览器', + dataIndex: 'clientUserAgent', + key: 'clientUserAgent', + render: (text, record) => { + if (isEmpty(text)) { + return '未知'; + } + return ( + + {text.split(' ')[0]} + + ) + } + }, { + title: '登录时间', + dataIndex: 'loginTime', + key: 'loginTime', + render: (text, record) => { + + return formatDate(text, 'yyyy-MM-dd hh:mm:ss'); + } + }, { + title: '注销时间', + dataIndex: 'logoutTime', + key: 'logoutTime', + render: (text, record) => { + if (isEmpty(text) || text === '0001-01-01 00:00:00') { + return ''; + } + return text; + } + }, + { + title: '操作', + key: 'action', + render: (text, record) => { + return ( +
+ +
+ ) + }, + } + ]; + + const selectedRowKeys = this.state.selectedRowKeys; + const rowSelection = { + selectedRowKeys: this.state.selectedRowKeys, + onChange: (selectedRowKeys, selectedRows) => { + this.setState({selectedRowKeys}); + }, + }; + const hasSelected = selectedRowKeys.length > 0; + + const userOptions = this.state.users.map(d => {d.nickname}); + + return ( + <> + + ]} + subTitle="只有登录成功的才会保存日志" + > + + + +
+ + + 登录日志列表 + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + `总计 ${total} 条` + }} + loading={this.state.loading} + /> + + + ); + } +} + +export default LoginLog; diff --git a/web/src/components/session/OfflineSession.js b/web/src/components/session/OfflineSession.js index 9b88168..35b6912 100644 --- a/web/src/components/session/OfflineSession.js +++ b/web/src/components/session/OfflineSession.js @@ -474,20 +474,24 @@ class OfflineSession extends Component { loading={this.state.loading} /> - + + : undefined + } - width={window.innerWidth * 0.8} - footer={null} - destroyOnClose - maskClosable={false} - > - - ); diff --git a/web/src/components/session/Playback.js b/web/src/components/session/Playback.js index bb82e42..fc7899b 100644 --- a/web/src/components/session/Playback.js +++ b/web/src/components/session/Playback.js @@ -142,7 +142,9 @@ class Playback extends Component { this.setState({ recording: recording - }) + }, () => { + this.handlePlayPause(); + }); } handlePlayPause = () => { @@ -150,7 +152,6 @@ class Playback extends Component { if (recording) { if (this.state.percent === this.state.max) { // 重播 - console.log('重新播放') this.setState({ percent: 0 }, () => {