- 增加一定时间内登录失败次数的限制
- 增加离线会话保存时间限制 - 完成需求「使会话详情的路径栏可写」close 69 - 修复bug「Ubuntu系统RDP不能正常显示」close 66 - 修复bug「使用guacd接入ssh时,第一个按键被忽略」 close 70
This commit is contained in:
parent
6e2f9e9d90
commit
cc9ed70822
@ -45,7 +45,10 @@ test/test
|
|||||||
|
|
||||||
如果您觉得 next-terminal 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
如果您觉得 next-terminal 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||||
|
|
||||||

|
<figure class="half">
|
||||||
|
<img src="./screenshot/donate_wx.png" alt="微信"/>
|
||||||
|
<img src="./screenshot/donate_zfb.png" alt="支付宝"/>
|
||||||
|
</figure>
|
||||||
|
|
||||||
## 联系方式
|
## 联系方式
|
||||||
|
|
||||||
@ -53,8 +56,6 @@ test/test
|
|||||||
|
|
||||||
- QQ群 938145268
|
- QQ群 938145268
|
||||||
|
|
||||||
<img src="screenshot/qq.png" width="300" height="auto"/>
|
|
||||||
|
|
||||||
- Telegram
|
- Telegram
|
||||||
|
|
||||||
https://t.me/next_terminal
|
https://t.me/next_terminal
|
14
main.go
14
main.go
@ -19,6 +19,7 @@ import (
|
|||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,8 +167,11 @@ func Run() error {
|
|||||||
// 配置缓存器
|
// 配置缓存器
|
||||||
global.Cache = cache.New(5*time.Minute, 10*time.Minute)
|
global.Cache = cache.New(5*time.Minute, 10*time.Minute)
|
||||||
global.Cache.OnEvicted(func(key string, value interface{}) {
|
global.Cache.OnEvicted(func(key string, value interface{}) {
|
||||||
logrus.Debugf("用户Token「%v」过期", key)
|
if strings.HasPrefix(key, api.Token) {
|
||||||
model.Logout(key)
|
token := strings.Split(key, ":")[0]
|
||||||
|
logrus.Debugf("用户Token「%v」过期", token)
|
||||||
|
model.Logout(token)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
global.Store = global.NewStore()
|
global.Store = global.NewStore()
|
||||||
|
|
||||||
@ -191,11 +195,13 @@ func Run() error {
|
|||||||
User: user,
|
User: user,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheKey := strings.Join([]string{api.Token, token}, ":")
|
||||||
|
|
||||||
if authorization.Remember {
|
if authorization.Remember {
|
||||||
// 记住登录有效期两周
|
// 记住登录有效期两周
|
||||||
global.Cache.Set(token, authorization, api.RememberEffectiveTime)
|
global.Cache.Set(cacheKey, authorization, api.RememberEffectiveTime)
|
||||||
} else {
|
} else {
|
||||||
global.Cache.Set(token, authorization, api.NotRememberEffectiveTime)
|
global.Cache.Set(cacheKey, authorization, api.NotRememberEffectiveTime)
|
||||||
}
|
}
|
||||||
logrus.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token)
|
logrus.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token)
|
||||||
}
|
}
|
||||||
|
@ -46,19 +46,35 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user, err := model.FindUserByUsername(loginAccount.Username)
|
user, err := model.FindUserByUsername(loginAccount.Username)
|
||||||
|
|
||||||
|
// 存储登录失败次数信息
|
||||||
|
loginFailCountKey := loginAccount.Username
|
||||||
|
v, ok := global.Cache.Get(loginFailCountKey)
|
||||||
|
if !ok {
|
||||||
|
v = 1
|
||||||
|
}
|
||||||
|
count := v.(int)
|
||||||
|
if count >= 5 {
|
||||||
|
return Fail(c, -1, "登录失败次数过多,请稍后再试")
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
count++
|
||||||
|
global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
count++
|
||||||
|
global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
|
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
|
||||||
return Fail(c, 0, "")
|
return Fail(c, 0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := Login(c, loginAccount, user)
|
token, err := LoginSuccess(c, loginAccount, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -66,7 +82,7 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
return Success(c, token)
|
return Success(c, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(c echo.Context, loginAccount LoginAccount, user model.User) (token string, err error) {
|
func LoginSuccess(c echo.Context, loginAccount LoginAccount, user model.User) (token string, err error) {
|
||||||
token = strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "")
|
token = strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "")
|
||||||
|
|
||||||
authorization := Authorization{
|
authorization := Authorization{
|
||||||
@ -75,11 +91,13 @@ func Login(c echo.Context, loginAccount LoginAccount, user model.User) (token st
|
|||||||
User: user,
|
User: user,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheKey := strings.Join([]string{Token, token}, ":")
|
||||||
|
|
||||||
if authorization.Remember {
|
if authorization.Remember {
|
||||||
// 记住登录有效期两周
|
// 记住登录有效期两周
|
||||||
global.Cache.Set(token, authorization, RememberEffectiveTime)
|
global.Cache.Set(cacheKey, authorization, RememberEffectiveTime)
|
||||||
} else {
|
} else {
|
||||||
global.Cache.Set(token, authorization, NotRememberEffectiveTime)
|
global.Cache.Set(cacheKey, authorization, NotRememberEffectiveTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存登录日志
|
// 保存登录日志
|
||||||
@ -107,20 +125,37 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 存储登录失败次数信息
|
||||||
|
loginFailCountKey := loginAccount.Username
|
||||||
|
v, ok := global.Cache.Get(loginFailCountKey)
|
||||||
|
if !ok {
|
||||||
|
v = 1
|
||||||
|
}
|
||||||
|
count := v.(int)
|
||||||
|
if count >= 5 {
|
||||||
|
return Fail(c, -1, "登录失败次数过多,请稍后再试")
|
||||||
|
}
|
||||||
|
|
||||||
user, err := model.FindUserByUsername(loginAccount.Username)
|
user, err := model.FindUserByUsername(loginAccount.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
count++
|
||||||
|
global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
count++
|
||||||
|
global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
|
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
||||||
return Fail(c, -2, "您的TOTP不匹配")
|
count++
|
||||||
|
global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||||
|
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := Login(c, loginAccount, user)
|
token, err := LoginSuccess(c, loginAccount, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
token := GetToken(c)
|
token := GetToken(c)
|
||||||
authorization, found := global.Cache.Get(token)
|
cacheKey := strings.Join([]string{Token, token}, ":")
|
||||||
|
authorization, found := global.Cache.Get(cacheKey)
|
||||||
if !found {
|
if !found {
|
||||||
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,14 @@ func Fail(c echo.Context, code int, message string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FailWithData(c echo.Context, code int, message string, data interface{}) error {
|
||||||
|
return c.JSON(200, H{
|
||||||
|
"code": code,
|
||||||
|
"message": message,
|
||||||
|
"data": data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Success(c echo.Context, data interface{}) error {
|
func Success(c echo.Context, data interface{}) error {
|
||||||
return c.JSON(200, H{
|
return c.JSON(200, H{
|
||||||
"code": 1,
|
"code": 1,
|
||||||
|
@ -66,15 +66,9 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
func SessionDeleteEndpoint(c echo.Context) error {
|
func SessionDeleteEndpoint(c echo.Context) error {
|
||||||
sessionIds := c.Param("id")
|
sessionIds := c.Param("id")
|
||||||
split := strings.Split(sessionIds, ",")
|
split := strings.Split(sessionIds, ",")
|
||||||
for i := range split {
|
err := model.DeleteSessionByIds(split)
|
||||||
drivePath, err := model.GetRecordingPath()
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(path.Join(drivePath, split[i])); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
model.DeleteSessionById(split[i])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
@ -132,7 +126,7 @@ func CloseSessionById(sessionId string, code int, reason string) {
|
|||||||
|
|
||||||
if s.Status == model.Connecting {
|
if s.Status == model.Connecting {
|
||||||
// 会话还未建立成功,无需保留数据
|
// 会话还未建立成功,无需保留数据
|
||||||
model.DeleteSessionById(sessionId)
|
_ = model.DeleteSessionById(sessionId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +254,9 @@ func (opt *Tunnel) Read() (p []byte, err error) {
|
|||||||
if s == "rate=44100,channels=2;" {
|
if s == "rate=44100,channels=2;" {
|
||||||
return make([]byte, 0), nil
|
return make([]byte, 0), nil
|
||||||
}
|
}
|
||||||
|
if s == "rate=22050,channels=2;" {
|
||||||
|
return make([]byte, 0), nil
|
||||||
|
}
|
||||||
if s == "5.audio,1.1,31.audio/L16;" {
|
if s == "5.audio,1.1,31.audio/L16;" {
|
||||||
s += "rate=44100,channels=2;"
|
s += "rate=44100,channels=2;"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package handle
|
|||||||
import (
|
import (
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"log"
|
||||||
"next-terminal/pkg/guacd"
|
"next-terminal/pkg/guacd"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
@ -22,7 +23,7 @@ func RunTicker() {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
for i := range sessions {
|
for i := range sessions {
|
||||||
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
|
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
|
||||||
model.DeleteSessionById(sessions[i].ID)
|
_ = model.DeleteSessionById(sessions[i].ID)
|
||||||
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
|
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
|
||||||
logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID)
|
logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID)
|
||||||
}
|
}
|
||||||
@ -40,6 +41,40 @@ func RunTicker() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
_, err := c.AddFunc("0 0 0 * * ?", func() {
|
||||||
|
// 定时任务 每日凌晨检查超过时长限制的会话
|
||||||
|
property, err := model.FindPropertyByName("session-saved-limit")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if property.Value == "" || property.Value == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(property.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessions, err := model.FindOutTimeSessions(limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessions != nil && len(sessions) > 0 {
|
||||||
|
var sessionIds []string
|
||||||
|
for i := range sessions {
|
||||||
|
sessionIds = append(sessionIds, sessions[i].ID)
|
||||||
|
}
|
||||||
|
err := model.DeleteSessionByIds(sessionIds)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("删除离线会话失败 %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,6 +128,12 @@ func FindSessionByStatusIn(statuses []string) (o []Session, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindOutTimeSessions(dayLimit int) (o []Session, err error) {
|
||||||
|
limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour)
|
||||||
|
err = global.DB.Where("status = ? and connected_time < ?", Disconnected, limitTime).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func CreateNewSession(o *Session) (err error) {
|
func CreateNewSession(o *Session) (err error) {
|
||||||
err = global.DB.Create(o).Error
|
err = global.DB.Create(o).Error
|
||||||
return
|
return
|
||||||
@ -154,8 +162,24 @@ func UpdateSessionWindowSizeById(width, height int, id string) error {
|
|||||||
return UpdateSessionById(&session, id)
|
return UpdateSessionById(&session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteSessionById(id string) {
|
func DeleteSessionById(id string) error {
|
||||||
global.DB.Where("id = ?", id).Delete(&Session{})
|
return global.DB.Where("id = ?", id).Delete(&Session{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSessionByIds(sessionIds []string) error {
|
||||||
|
drivePath, err := GetRecordingPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range sessionIds {
|
||||||
|
if err := os.RemoveAll(path.Join(drivePath, sessionIds[i])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := DeleteSessionById(sessionIds[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteSessionByStatus(status string) {
|
func DeleteSessionByStatus(status string) {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 286 KiB |
BIN
screenshot/donate_wx.png
Normal file
BIN
screenshot/donate_wx.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
screenshot/donate_zfb.png
Normal file
BIN
screenshot/donate_zfb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
@ -314,9 +314,6 @@ class Access extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onKeyDown = (keysym) => {
|
onKeyDown = (keysym) => {
|
||||||
// if (this.state.clipboardVisible || this.state.fileSystemVisible) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
this.state.client.sendKeyEvent(1, keysym);
|
this.state.client.sendKeyEvent(1, keysym);
|
||||||
if (keysym === 65288) {
|
if (keysym === 65288) {
|
||||||
return false;
|
return false;
|
||||||
@ -426,6 +423,7 @@ class Access extends Component {
|
|||||||
|
|
||||||
const sink = new Guacamole.InputSink();
|
const sink = new Guacamole.InputSink();
|
||||||
display.appendChild(sink.getElement());
|
display.appendChild(sink.getElement());
|
||||||
|
sink.focus();
|
||||||
|
|
||||||
// Keyboard
|
// Keyboard
|
||||||
const keyboard = new Guacamole.Keyboard(sink.getElement());
|
const keyboard = new Guacamole.Keyboard(sink.getElement());
|
||||||
@ -582,7 +580,7 @@ class Access extends Component {
|
|||||||
|
|
||||||
<Draggable>
|
<Draggable>
|
||||||
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
||||||
<Button icon={<ExpandOutlined/>} onClick={() => {
|
<Button icon={<ExpandOutlined/>} disabled={this.state.clientState !== STATE_CONNECTED} onClick={() => {
|
||||||
this.fullScreen();
|
this.fullScreen();
|
||||||
}}/>
|
}}/>
|
||||||
</Affix>
|
</Affix>
|
||||||
@ -590,7 +588,7 @@ class Access extends Component {
|
|||||||
|
|
||||||
<Draggable>
|
<Draggable>
|
||||||
<Affix style={{position: 'absolute', top: 50, right: 150}}>
|
<Affix style={{position: 'absolute', top: 50, right: 150}}>
|
||||||
<Button icon={<CopyTwoTone/>} onClick={() => {
|
<Button icon={<CopyTwoTone/>} disabled={this.state.clientState !== STATE_CONNECTED} onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
clipboardVisible: true
|
clipboardVisible: true
|
||||||
});
|
});
|
||||||
@ -603,7 +601,7 @@ class Access extends Component {
|
|||||||
<>
|
<>
|
||||||
<Draggable>
|
<Draggable>
|
||||||
<Affix style={{position: 'absolute', top: 100, right: 100}}>
|
<Affix style={{position: 'absolute', top: 100, right: 100}}>
|
||||||
<Button icon={<AppstoreTwoTone/>} onClick={() => {
|
<Button icon={<AppstoreTwoTone/>} disabled={this.state.clientState !== STATE_CONNECTED} onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
fileSystemVisible: true,
|
fileSystemVisible: true,
|
||||||
});
|
});
|
||||||
@ -613,8 +611,8 @@ class Access extends Component {
|
|||||||
|
|
||||||
<Draggable>
|
<Draggable>
|
||||||
<Affix style={{position: 'absolute', top: 100, right: 150}}>
|
<Affix style={{position: 'absolute', top: 100, right: 150}}>
|
||||||
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft">
|
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft">
|
||||||
<Button icon={<DesktopOutlined/>}/>
|
<Button icon={<DesktopOutlined/>} disabled={this.state.clientState !== STATE_CONNECTED}/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Affix>
|
</Affix>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
@ -626,7 +624,7 @@ class Access extends Component {
|
|||||||
<>
|
<>
|
||||||
<Draggable>
|
<Draggable>
|
||||||
<Affix style={{position: 'absolute', top: 100, right: 100}}>
|
<Affix style={{position: 'absolute', top: 100, right: 100}}>
|
||||||
<Button icon={<AppstoreTwoTone/>} onClick={() => {
|
<Button icon={<AppstoreTwoTone/>} disabled={this.state.clientState !== STATE_CONNECTED} onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
fileSystemVisible: true,
|
fileSystemVisible: true,
|
||||||
});
|
});
|
||||||
|
@ -37,6 +37,7 @@ class FileSystem extends Component {
|
|||||||
state = {
|
state = {
|
||||||
sessionId: undefined,
|
sessionId: undefined,
|
||||||
currentDirectory: '/',
|
currentDirectory: '/',
|
||||||
|
currentDirectoryInput: '/',
|
||||||
files: [],
|
files: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedRowKeys: [],
|
selectedRowKeys: [],
|
||||||
@ -126,6 +127,7 @@ class FileSystem extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
files: items,
|
files: items,
|
||||||
currentDirectory: key,
|
currentDirectory: key,
|
||||||
|
currentDirectoryInput: key,
|
||||||
selectedRow: {},
|
selectedRow: {},
|
||||||
selectedRowKeys: []
|
selectedRowKeys: []
|
||||||
})
|
})
|
||||||
@ -189,6 +191,16 @@ class FileSystem extends Component {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleCurrentDirectoryInputChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
currentDirectoryInput: event.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCurrentDirectoryInputPressEnter = (event) => {
|
||||||
|
this.loadFiles(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -315,10 +327,11 @@ class FileSystem extends Component {
|
|||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<Row justify="space-around" align="middle" gutter={24}>
|
<Row justify="space-around" align="middle" gutter={24}>
|
||||||
<Col span={16} key={1}>
|
<Col span={20} key={1}>
|
||||||
{this.state.currentDirectory}
|
<Input value={this.state.currentDirectoryInput} onChange={this.handleCurrentDirectoryInputChange}
|
||||||
|
onPressEnter={this.handleCurrentDirectoryInputPressEnter}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8} key={2} style={{textAlign: 'right'}}>
|
<Col span={4} key={2} style={{textAlign: 'right'}}>
|
||||||
<Space>
|
<Space>
|
||||||
<Tooltip title="创建文件夹">
|
<Tooltip title="创建文件夹">
|
||||||
<Button type="primary" size="small" icon={<FolderAddOutlined/>}
|
<Button type="primary" size="small" icon={<FolderAddOutlined/>}
|
||||||
@ -383,7 +396,6 @@ class FileSystem extends Component {
|
|||||||
if (record['path'] === '..') {
|
if (record['path'] === '..') {
|
||||||
// 获取当前目录的上级目录
|
// 获取当前目录的上级目录
|
||||||
let currentDirectory = this.state.currentDirectory;
|
let currentDirectory = this.state.currentDirectory;
|
||||||
console.log(currentDirectory)
|
|
||||||
let parentDirectory = currentDirectory.substring(0, currentDirectory.lastIndexOf('/'));
|
let parentDirectory = currentDirectory.substring(0, currentDirectory.lastIndexOf('/'));
|
||||||
this.loadFiles(parentDirectory);
|
this.loadFiles(parentDirectory);
|
||||||
} else {
|
} else {
|
||||||
|
@ -546,6 +546,21 @@ class Setting extends Component {
|
|||||||
</> : null
|
</> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
{...formItemLayout}
|
||||||
|
name="session-saved-limit"
|
||||||
|
label="会话录屏保存时长"
|
||||||
|
initialValue=""
|
||||||
|
>
|
||||||
|
<Select onChange={null}>
|
||||||
|
<Option value="">永久</Option>
|
||||||
|
<Option value="30">30天</Option>
|
||||||
|
<Option value="60">60天</Option>
|
||||||
|
<Option value="180">180天</Option>
|
||||||
|
<Option value="360">360天</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item {...formTailLayout}>
|
<Form.Item {...formTailLayout}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
更新
|
更新
|
||||||
|
Loading…
Reference in New Issue
Block a user