修改docker默认时区为上海
修复了记住登录无效的问题 修复了ssh下载文件名称不正确的问题 授权凭证增加了密钥类型
This commit is contained in:
parent
636b91e0f7
commit
bc9daf2b01
@ -25,6 +25,8 @@ ENV DB sqlite
|
|||||||
ENV SQLITE_FILE 'next-terminal.db'
|
ENV SQLITE_FILE 'next-terminal.db'
|
||||||
ENV SERVER_PORT 8088
|
ENV SERVER_PORT 8088
|
||||||
ENV SERVER_ADDR 0.0.0.0:$SERVER_PORT
|
ENV SERVER_ADDR 0.0.0.0:$SERVER_PORT
|
||||||
|
ENV TIME_ZONE=Asia/Shanghai
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime && echo $TIME_ZONE > /etc/timezone
|
||||||
|
|
||||||
WORKDIR /usr/local/next-terminal
|
WORKDIR /usr/local/next-terminal
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# Next Terminal
|
# Next Terminal
|
||||||
你的下一个终端。
|
你的下一个终端。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 快速了解
|
## 快速了解
|
||||||
|
|
||||||
Next Terminal是使用Golang和React开发的一款HTML5的远程桌面网关,具有小巧、易安装、易使用、资源占用小的特点,支持RDP、SSH、VNC和Telnet协议的连接和管理。
|
Next Terminal是使用Golang和React开发的一款HTML5的远程桌面网关,具有小巧、易安装、易使用、资源占用小的特点,支持RDP、SSH、VNC和Telnet协议的连接和管理。
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
"next-terminal/pkg/totp"
|
"next-terminal/pkg/totp"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
@ -15,6 +13,7 @@ import (
|
|||||||
type LoginAccount struct {
|
type LoginAccount struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Remember bool `json:"remember"`
|
||||||
TOTP string `json:"totp"`
|
TOTP string `json:"totp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +27,12 @@ type ChangePassword struct {
|
|||||||
OldPassword string `json:"oldPassword"`
|
OldPassword string `json:"oldPassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Authorization struct {
|
||||||
|
Token string
|
||||||
|
Remember bool
|
||||||
|
User model.User
|
||||||
|
}
|
||||||
|
|
||||||
func LoginEndpoint(c echo.Context) error {
|
func LoginEndpoint(c echo.Context) error {
|
||||||
var loginAccount LoginAccount
|
var loginAccount LoginAccount
|
||||||
if err := c.Bind(&loginAccount); err != nil {
|
if err := c.Bind(&loginAccount); err != nil {
|
||||||
@ -43,15 +48,24 @@ func LoginEndpoint(c echo.Context) error {
|
|||||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
return Fail(c, -1, "您输入的账号或密码不正确")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(user, loginAccount)
|
|
||||||
|
|
||||||
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
||||||
return Fail(c, -2, "您的TOTP不匹配")
|
return Fail(c, -2, "您的TOTP不匹配")
|
||||||
}
|
}
|
||||||
|
|
||||||
token := utils.UUID()
|
token := utils.UUID()
|
||||||
|
|
||||||
global.Cache.Set(token, user, time.Minute*time.Duration(30))
|
authorization := Authorization{
|
||||||
|
Token: token,
|
||||||
|
Remember: loginAccount.Remember,
|
||||||
|
User: user,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
model.UpdateUserById(&model.User{Online: true}, user.ID)
|
||||||
|
|
||||||
|
@ -112,11 +112,8 @@ func AssetTcpingEndpoint(c echo.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
active := utils.Tcping(item.IP, item.Port)
|
active := utils.Tcping(item.IP, item.Port)
|
||||||
asset := model.Asset{
|
|
||||||
Active: active,
|
|
||||||
}
|
|
||||||
|
|
||||||
model.UpdateAssetById(&asset, item.ID)
|
model.UpdateAssetActiveById(active, item.ID)
|
||||||
return Success(c, active)
|
return Success(c, active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,31 @@ func CredentialCreateEndpoint(c echo.Context) error {
|
|||||||
item.ID = utils.UUID()
|
item.ID = utils.UUID()
|
||||||
item.Created = utils.NowJsonTime()
|
item.Created = utils.NowJsonTime()
|
||||||
|
|
||||||
|
switch item.Type {
|
||||||
|
case model.Custom:
|
||||||
|
item.PrivateKey = "-"
|
||||||
|
item.Passphrase = "-"
|
||||||
|
if len(item.Username) == 0 {
|
||||||
|
item.Username = "-"
|
||||||
|
}
|
||||||
|
if len(item.Password) == 0 {
|
||||||
|
item.Password = "-"
|
||||||
|
}
|
||||||
|
case model.PrivateKey:
|
||||||
|
item.Password = "-"
|
||||||
|
if len(item.Username) == 0 {
|
||||||
|
item.Username = "-"
|
||||||
|
}
|
||||||
|
if len(item.PrivateKey) == 0 {
|
||||||
|
item.PrivateKey = "-"
|
||||||
|
}
|
||||||
|
if len(item.Passphrase) == 0 {
|
||||||
|
item.Passphrase = "-"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Fail(c, -1, "类型错误")
|
||||||
|
}
|
||||||
|
|
||||||
if err := model.CreateNewCredential(&item); err != nil {
|
if err := model.CreateNewCredential(&item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -49,6 +74,31 @@ func CredentialUpdateEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch item.Type {
|
||||||
|
case model.Custom:
|
||||||
|
item.PrivateKey = "-"
|
||||||
|
item.Passphrase = "-"
|
||||||
|
if len(item.Username) == 0 {
|
||||||
|
item.Username = "-"
|
||||||
|
}
|
||||||
|
if len(item.Password) == 0 {
|
||||||
|
item.Password = "-"
|
||||||
|
}
|
||||||
|
case model.PrivateKey:
|
||||||
|
item.Password = "-"
|
||||||
|
if len(item.Username) == 0 {
|
||||||
|
item.Username = "-"
|
||||||
|
}
|
||||||
|
if len(item.PrivateKey) == 0 {
|
||||||
|
item.PrivateKey = "-"
|
||||||
|
}
|
||||||
|
if len(item.Passphrase) == 0 {
|
||||||
|
item.Passphrase = "-"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Fail(c, -1, "类型错误")
|
||||||
|
}
|
||||||
|
|
||||||
model.UpdateCredentialById(&item, id)
|
model.UpdateCredentialById(&item, id)
|
||||||
|
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
|
@ -34,12 +34,19 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
token := GetToken(c)
|
token := GetToken(c)
|
||||||
user, found := global.Cache.Get(token)
|
authorization, found := global.Cache.Get(token)
|
||||||
if !found {
|
if !found {
|
||||||
logrus.Debugf("您的登录信息已失效,请重新登录后再试。")
|
logrus.Debugf("您的登录信息已失效,请重新登录后再试。")
|
||||||
return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。")
|
return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。")
|
||||||
}
|
}
|
||||||
global.Cache.Set(token, user, time.Minute*time.Duration(30))
|
|
||||||
|
if authorization.(Authorization).Remember {
|
||||||
|
// 记住登录有效期两周
|
||||||
|
global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14))
|
||||||
|
} else {
|
||||||
|
global.Cache.Set(token, authorization, time.Hour*time.Duration(2))
|
||||||
|
}
|
||||||
|
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ func GetCurrentAccount(c echo.Context) (model.User, bool) {
|
|||||||
token := GetToken(c)
|
token := GetToken(c)
|
||||||
get, b := global.Cache.Get(token)
|
get, b := global.Cache.Get(token)
|
||||||
if b {
|
if b {
|
||||||
return get.(model.User), true
|
return get.(Authorization).User, true
|
||||||
}
|
}
|
||||||
return model.User{}, false
|
return model.User{}, false
|
||||||
}
|
}
|
||||||
|
@ -185,8 +185,14 @@ func SessionCreateEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Username = credential.Username
|
if credential.Type == model.Custom {
|
||||||
session.Password = credential.Password
|
session.Username = credential.Username
|
||||||
|
session.Password = credential.Password
|
||||||
|
} else {
|
||||||
|
session.Username = credential.Username
|
||||||
|
session.PrivateKey = credential.PrivateKey
|
||||||
|
session.Passphrase = credential.Passphrase
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := model.CreateNewSession(session); err != nil {
|
if err := model.CreateNewSession(session); err != nil {
|
||||||
@ -223,12 +229,11 @@ func SessionUploadEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dstFile, err := tun.SftpClient.Create(remoteFile)
|
dstFile, err := tun.SftpClient.Create(remoteFile)
|
||||||
|
defer dstFile.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer dstFile.Close()
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
n, _ := src.Read(buf)
|
n, _ := src.Read(buf)
|
||||||
@ -282,7 +287,9 @@ func SessionDownloadEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer dstFile.Close()
|
defer dstFile.Close()
|
||||||
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", remoteFile))
|
// 获取带后缀的文件名称
|
||||||
|
filenameWithSuffix := path.Base(remoteFile)
|
||||||
|
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filenameWithSuffix))
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
if _, err := dstFile.WriteTo(&buff); err != nil {
|
if _, err := dstFile.WriteTo(&buff); err != nil {
|
||||||
|
@ -49,6 +49,7 @@ func (w *NextWriter) Read() ([]byte, int, error) {
|
|||||||
func SSHEndpoint(c echo.Context) error {
|
func SSHEndpoint(c echo.Context) error {
|
||||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +59,13 @@ func SSHEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
sshClient, err := CreateSshClient(assetId)
|
sshClient, err := CreateSshClient(assetId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Errorf("创建SSH客户端失败:%v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := sshClient.NewSession()
|
session, err := sshClient.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Errorf("创建SSH会话失败:%v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
@ -123,36 +126,62 @@ func CreateSshClient(assetId string) (*ssh.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
accountType = asset.AccountType
|
||||||
|
username = asset.Username
|
||||||
|
password = asset.Password
|
||||||
|
privateKey = asset.PrivateKey
|
||||||
|
passphrase = asset.Passphrase
|
||||||
|
)
|
||||||
|
|
||||||
var authMethod ssh.AuthMethod
|
var authMethod ssh.AuthMethod
|
||||||
if asset.AccountType == "credential" {
|
if accountType == "credential" {
|
||||||
|
|
||||||
credential, err := model.FindCredentialById(asset.CredentialId)
|
credential, err := model.FindCredentialById(asset.CredentialId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
asset.Username = credential.Username
|
accountType = credential.Type
|
||||||
asset.Password = credential.Password
|
username = credential.Username
|
||||||
authMethod = ssh.Password(asset.Password)
|
password = credential.Password
|
||||||
} else if asset.AccountType == "private-key" {
|
privateKey = credential.PrivateKey
|
||||||
|
passphrase = credential.Passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "-" {
|
||||||
|
username = ""
|
||||||
|
}
|
||||||
|
if password == "-" {
|
||||||
|
password = ""
|
||||||
|
}
|
||||||
|
if privateKey == "-" {
|
||||||
|
privateKey = ""
|
||||||
|
}
|
||||||
|
if passphrase == "-" {
|
||||||
|
passphrase = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountType == model.PrivateKey {
|
||||||
var key ssh.Signer
|
var key ssh.Signer
|
||||||
if len(asset.Passphrase) > 0 {
|
if len(passphrase) > 0 {
|
||||||
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(asset.PrivateKey), []byte(asset.Passphrase))
|
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
key, err = ssh.ParsePrivateKey([]byte(asset.PrivateKey))
|
key, err = ssh.ParsePrivateKey([]byte(privateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authMethod = ssh.PublicKeys(key)
|
authMethod = ssh.PublicKeys(key)
|
||||||
} else {
|
} else {
|
||||||
authMethod = ssh.Password(asset.Password)
|
authMethod = ssh.Password(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
config := &ssh.ClientConfig{
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
User: asset.Username,
|
User: username,
|
||||||
Auth: []ssh.AuthMethod{authMethod},
|
Auth: []ssh.AuthMethod{authMethod},
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
width := c.QueryParam("width")
|
width := c.QueryParam("width")
|
||||||
height := c.QueryParam("height")
|
height := c.QueryParam("height")
|
||||||
|
dpi := c.QueryParam("dpi")
|
||||||
sessionId := c.QueryParam("sessionId")
|
sessionId := c.QueryParam("sessionId")
|
||||||
connectionId := c.QueryParam("connectionId")
|
connectionId := c.QueryParam("connectionId")
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
configuration := guacd.NewConfiguration()
|
configuration := guacd.NewConfiguration()
|
||||||
configuration.SetParameter("width", width)
|
configuration.SetParameter("width", width)
|
||||||
configuration.SetParameter("height", height)
|
configuration.SetParameter("height", height)
|
||||||
|
configuration.SetParameter("dpi", dpi)
|
||||||
|
|
||||||
propertyMap := model.FindAllPropertiesMap()
|
propertyMap := model.FindAllPropertiesMap()
|
||||||
|
|
||||||
@ -73,8 +75,6 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
configuration.SetParameter("security", "any")
|
configuration.SetParameter("security", "any")
|
||||||
configuration.SetParameter("ignore-cert", "true")
|
configuration.SetParameter("ignore-cert", "true")
|
||||||
configuration.SetParameter("create-drive-path", "true")
|
configuration.SetParameter("create-drive-path", "true")
|
||||||
|
|
||||||
configuration.SetParameter("dpi", "96")
|
|
||||||
configuration.SetParameter("resize-method", "reconnect")
|
configuration.SetParameter("resize-method", "reconnect")
|
||||||
configuration.SetParameter(guacd.EnableDrive, propertyMap[guacd.EnableDrive])
|
configuration.SetParameter(guacd.EnableDrive, propertyMap[guacd.EnableDrive])
|
||||||
configuration.SetParameter(guacd.DriveName, propertyMap[guacd.DriveName])
|
configuration.SetParameter(guacd.DriveName, propertyMap[guacd.DriveName])
|
||||||
@ -99,9 +99,7 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
configuration.SetParameter("password", session.Password)
|
configuration.SetParameter("password", session.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
fontSize, _ := strconv.Atoi(propertyMap[guacd.FontSize])
|
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
|
||||||
fontSize = fontSize * 2
|
|
||||||
configuration.SetParameter(guacd.FontSize, strconv.Itoa(fontSize))
|
|
||||||
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
|
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
|
||||||
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
|
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
|
||||||
break
|
break
|
||||||
|
@ -134,8 +134,10 @@ func NewTunnel(address string, config Configuration) (ret *Tunnel, err error) {
|
|||||||
|
|
||||||
width := config.GetParameter("width")
|
width := config.GetParameter("width")
|
||||||
height := config.GetParameter("height")
|
height := config.GetParameter("height")
|
||||||
|
dpi := config.GetParameter("dpi")
|
||||||
|
|
||||||
// send size
|
// send size
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, "96")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, dpi)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func RunTicker() {
|
func RunTicker() {
|
||||||
var ch chan int
|
var ch chan int
|
||||||
//定时任务
|
// 定时任务,每隔一小时删除一次未使用的会话信息
|
||||||
ticker := time.NewTicker(time.Minute * 60)
|
ticker := time.NewTicker(time.Minute * 60)
|
||||||
go func() {
|
go func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
@ -85,6 +85,11 @@ func UpdateAssetById(o *Asset, id string) {
|
|||||||
global.DB.Updates(o)
|
global.DB.Updates(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateAssetActiveById(active bool, id string) {
|
||||||
|
sql := "update assets set active = ? where id = ?"
|
||||||
|
global.DB.Exec(sql, active, id)
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteAssetById(id string) {
|
func DeleteAssetById(id string) {
|
||||||
global.DB.Where("id = ?", id).Delete(&Asset{})
|
global.DB.Where("id = ?", id).Delete(&Asset{})
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,21 @@ import (
|
|||||||
"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"`
|
||||||
Username string `json:"username"`
|
Type string `json:"type"`
|
||||||
Password string `json:"password"`
|
Username string `json:"username"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Password string `json:"password"`
|
||||||
|
PrivateKey string `json:"privateKey"`
|
||||||
|
Passphrase string `json:"passphrase"`
|
||||||
|
Created utils.JsonTime `json:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Credential) TableName() string {
|
func (r *Credential) TableName() string {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-terminal",
|
"name": "next-terminal",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.3.0",
|
"@ant-design/icons": "^4.3.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// prod
|
// prod
|
||||||
export const server = '';
|
export const server = '/';
|
||||||
export const wsServer = '';
|
export const wsServer = '/';
|
||||||
export const prefix = '';
|
export const prefix = '';
|
||||||
|
|
||||||
// dev
|
// dev
|
||||||
|
@ -41,13 +41,6 @@ class LoginForm extends Component {
|
|||||||
localStorage.setItem('X-Auth-Token', result['data']);
|
localStorage.setItem('X-Auth-Token', result['data']);
|
||||||
// this.props.history.push();
|
// this.props.history.push();
|
||||||
window.location.href = "/"
|
window.location.href = "/"
|
||||||
|
|
||||||
let r = await request.get('/info');
|
|
||||||
if (r.code === 1) {
|
|
||||||
this.props.updateUser(r.data);
|
|
||||||
} else {
|
|
||||||
message.error(r.message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error(e.message);
|
message.error(e.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -308,7 +308,6 @@ class Access extends Component {
|
|||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
await navigator.clipboard.writeText(data);
|
await navigator.clipboard.writeText(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -433,6 +432,10 @@ class Access extends Component {
|
|||||||
|
|
||||||
let width = window.innerWidth;
|
let width = window.innerWidth;
|
||||||
let height = window.innerHeight;
|
let height = window.innerHeight;
|
||||||
|
let dpi = 96;
|
||||||
|
if (protocol === 'ssh' || protocol === 'telnet') {
|
||||||
|
dpi = dpi * 2;
|
||||||
|
}
|
||||||
|
|
||||||
let token = getToken();
|
let token = getToken();
|
||||||
|
|
||||||
@ -440,6 +443,7 @@ class Access extends Component {
|
|||||||
'sessionId': sessionId,
|
'sessionId': sessionId,
|
||||||
'width': width,
|
'width': width,
|
||||||
'height': height,
|
'height': height,
|
||||||
|
'dpi': dpi,
|
||||||
'X-Auth-Token': token
|
'X-Auth-Token': token
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -848,6 +852,7 @@ class Access extends Component {
|
|||||||
|
|
||||||
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
||||||
<Button
|
<Button
|
||||||
|
shape="circle"
|
||||||
icon={<CopyOutlined/>}
|
icon={<CopyOutlined/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.showClipboard();
|
this.showClipboard();
|
||||||
@ -861,6 +866,7 @@ class Access extends Component {
|
|||||||
this.state.protocol === 'ssh' || this.state.protocol === 'rdp' ?
|
this.state.protocol === 'ssh' || this.state.protocol === 'rdp' ?
|
||||||
<Affix style={{position: 'absolute', top: 50, right: 50}}>
|
<Affix style={{position: 'absolute', top: 50, right: 50}}>
|
||||||
<Button
|
<Button
|
||||||
|
shape="circle"
|
||||||
icon={<FolderOpenOutlined/>}
|
icon={<FolderOpenOutlined/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.showFileSystem();
|
this.showFileSystem();
|
||||||
|
@ -468,7 +468,7 @@ class Asset extends Component {
|
|||||||
|
|
||||||
<Select mode="multiple"
|
<Select mode="multiple"
|
||||||
allowClear
|
allowClear
|
||||||
placeholder="请选择标签" onChange={this.handleTagsChange}
|
placeholder="资产标签" onChange={this.handleTagsChange}
|
||||||
style={{minWidth: 150}}>
|
style={{minWidth: 150}}>
|
||||||
{this.state.tags.map(tag => {
|
{this.state.tags.map(tag => {
|
||||||
if (tag === '-') {
|
if (tag === '-') {
|
||||||
|
@ -10,13 +10,13 @@ const {Option} = Select;
|
|||||||
|
|
||||||
const protocolMapping = {
|
const protocolMapping = {
|
||||||
'ssh': [
|
'ssh': [
|
||||||
{text: '自定义', value: 'custom'},
|
{text: '密码', value: 'custom'},
|
||||||
|
{text: '密钥', value: 'private-key'},
|
||||||
{text: '授权凭证', value: 'credential'},
|
{text: '授权凭证', value: 'credential'},
|
||||||
{text: '私钥', value: 'private-key'}
|
|
||||||
],
|
],
|
||||||
'rdp': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
'rdp': [{text: '密码', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
||||||
'vnc': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
'vnc': [{text: '密码', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
||||||
'telnet': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}]
|
'telnet': [{text: '密码', value: 'custom'}, {text: '授权凭证', value: 'credential'}]
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, tags, model}) {
|
const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, tags, model}) {
|
||||||
|
@ -6,9 +6,11 @@ import CredentialModal from "./CredentialModal";
|
|||||||
import request from "../../common/request";
|
import request from "../../common/request";
|
||||||
import {message} from "antd/es";
|
import {message} from "antd/es";
|
||||||
import {
|
import {
|
||||||
DeleteOutlined, DeleteTwoTone,
|
DeleteOutlined,
|
||||||
|
DeleteTwoTone,
|
||||||
EditTwoTone,
|
EditTwoTone,
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined,
|
||||||
|
EyeTwoTone,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
UndoOutlined
|
UndoOutlined
|
||||||
@ -18,7 +20,7 @@ import Logout from "../user/Logout";
|
|||||||
|
|
||||||
const confirm = Modal.confirm;
|
const confirm = Modal.confirm;
|
||||||
const {Search} = Input;
|
const {Search} = Input;
|
||||||
const {Title, Text} = Typography;
|
const {Title, Text, Paragraph} = Typography;
|
||||||
const {Content} = Layout;
|
const {Content} = Layout;
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -135,7 +137,8 @@ class Credential extends Component {
|
|||||||
okType: 'danger',
|
okType: 'danger',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk() {
|
onOk() {
|
||||||
self.delete(id).then(r => {});
|
self.delete(id).then(r => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -239,14 +242,27 @@ class Credential extends Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
title: '凭证类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
render: (type, record) => {
|
||||||
|
|
||||||
|
if (type === 'private-key') {
|
||||||
|
return (
|
||||||
|
<Text strong type="success">密钥</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text strong type="warning">密码</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
title: '授权账户',
|
title: '授权账户',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
key: 'username',
|
key: 'username',
|
||||||
}, {
|
|
||||||
title: '授权密码',
|
|
||||||
dataIndex: 'password',
|
|
||||||
key: 'password',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@ -255,8 +271,12 @@ class Credential extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="link" size='small' icon={<EditTwoTone/>} onClick={() => this.showModal('更新凭证', record)}>编辑</Button>
|
<Button type="link" size='small' icon={<EyeTwoTone/>}
|
||||||
<Button type="link" size='small' icon={<DeleteTwoTone />} onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
onClick={() => this.showModal('查看凭证', record)}>查看</Button>
|
||||||
|
<Button type="link" size='small' icon={<EditTwoTone/>}
|
||||||
|
onClick={() => this.showModal('更新凭证', record)}>编辑</Button>
|
||||||
|
<Button type="link" size='small' icon={<DeleteTwoTone/>}
|
||||||
|
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -388,6 +408,7 @@ class Credential extends Component {
|
|||||||
handleCancel={this.handleCancelModal}
|
handleCancel={this.handleCancelModal}
|
||||||
confirmLoading={this.state.modalConfirmLoading}
|
confirmLoading={this.state.modalConfirmLoading}
|
||||||
model={this.state.model}
|
model={this.state.model}
|
||||||
|
footer={this.state.modalTitle.indexOf('查看') > -1 ? null : undefined}
|
||||||
>
|
>
|
||||||
|
|
||||||
</CredentialModal>
|
</CredentialModal>
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import {Form, Input, Modal} from "antd/lib/index";
|
import {Form, Input, Modal, Select} from "antd/lib/index";
|
||||||
|
import {isEmpty} from "../../utils/utils";
|
||||||
|
|
||||||
const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading,model}) => {
|
const {TextArea} = Input;
|
||||||
|
|
||||||
|
const accountTypes = [
|
||||||
|
{text: '密码', value: 'custom'},
|
||||||
|
{text: '密钥', value: 'private-key'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@ -10,6 +18,29 @@ const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading
|
|||||||
wrapperCol: {span: 14},
|
wrapperCol: {span: 14},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (model === null || model === undefined) {
|
||||||
|
model = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty(model.type)) {
|
||||||
|
model.type = 'custom';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in model) {
|
||||||
|
if (model.hasOwnProperty(key)) {
|
||||||
|
if (model[key] === '-') {
|
||||||
|
model[key] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let [type, setType] = useState(model.type);
|
||||||
|
|
||||||
|
const handleAccountTypeChange = v => {
|
||||||
|
setType(v);
|
||||||
|
model.type = v;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@ -43,13 +74,40 @@ const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading
|
|||||||
<Input placeholder="请输入凭证名称"/>
|
<Input placeholder="请输入凭证名称"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="授权账户" name='username' rules={[{required: true, message: '请输入授权账户'}]}>
|
<Form.Item label="账户类型" name='type' rules={[{required: true, message: '请选择接账户类型'}]}>
|
||||||
<Input placeholder="输入授权账户"/>
|
<Select onChange={handleAccountTypeChange}>
|
||||||
|
{accountTypes.map(item => {
|
||||||
|
return (<Select.Option key={item.value} value={item.value}>{item.text}</Select.Option>)
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="授权密码" name='password' rules={[{required: true, message: '请输入授权密码',}]}>
|
{
|
||||||
<Input placeholder="输入授权密码"/>
|
type === 'private-key' ?
|
||||||
</Form.Item>
|
<>
|
||||||
|
<Form.Item label="授权账户" name='username'>
|
||||||
|
<Input placeholder="输入授权账户"/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="私钥" name='privateKey' rules={[{required: true, message: '请输入私钥'}]}>
|
||||||
|
<TextArea rows={4}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="私钥密码" name='passphrase'>
|
||||||
|
<TextArea rows={1}/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Form.Item label="授权账户" name='username'>
|
||||||
|
<Input placeholder="输入授权账户"/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="授权密码" name='password'>
|
||||||
|
<Input placeholder="输入授权密码"/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
Loading…
Reference in New Issue
Block a user