修改docker默认时区为上海

修复了记住登录无效的问题
修复了ssh下载文件名称不正确的问题
授权凭证增加了密钥类型
This commit is contained in:
dushixiang 2021-01-08 23:01:41 +08:00
parent 636b91e0f7
commit bc9daf2b01
22 changed files with 274 additions and 74 deletions

View File

@ -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

View File

@ -1,6 +1,8 @@
# Next Terminal # Next Terminal
你的下一个终端。 你的下一个终端。
![Docker image](https://github.com/dushixiang/next-terminal/workflows/Docker%20image/badge.svg?branch=master)
## 快速了解 ## 快速了解
Next Terminal是使用Golang和React开发的一款HTML5的远程桌面网关具有小巧、易安装、易使用、资源占用小的特点支持RDP、SSH、VNC和Telnet协议的连接和管理。 Next Terminal是使用Golang和React开发的一款HTML5的远程桌面网关具有小巧、易安装、易使用、资源占用小的特点支持RDP、SSH、VNC和Telnet协议的连接和管理。

View File

@ -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)

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)
} }
} }

View File

@ -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
} }

View File

@ -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 {

View File

@ -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(),
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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 {

View File

@ -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{})
} }

View File

@ -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 {

View File

@ -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",

View File

@ -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

View File

@ -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 {

View File

@ -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();

View File

@ -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 === '-') {

View File

@ -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}) {

View File

@ -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>

View File

@ -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>