优化接入功能&重构接入文件操作
This commit is contained in:
@ -1,50 +1,28 @@
|
||||
import React, {Component} from 'react';
|
||||
import Guacamole from 'guacamole-common-js';
|
||||
import {
|
||||
Affix,
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Drawer,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
message,
|
||||
Modal,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Tooltip,
|
||||
Tree
|
||||
} from 'antd'
|
||||
import {Affix, Button, Col, Drawer, Dropdown, Form, Input, Menu, message, Modal, Row, Space, Tooltip} from 'antd'
|
||||
import qs from "qs";
|
||||
import request from "../../common/request";
|
||||
import {server, wsServer} from "../../common/constants";
|
||||
import {wsServer} from "../../common/constants";
|
||||
import {
|
||||
AppstoreTwoTone,
|
||||
CloudDownloadOutlined,
|
||||
CloudUploadOutlined,
|
||||
CopyOutlined,
|
||||
CopyTwoTone,
|
||||
DeleteOutlined,
|
||||
DesktopOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
ExpandOutlined,
|
||||
FileZipOutlined,
|
||||
FolderAddOutlined,
|
||||
LoadingOutlined,
|
||||
ReloadOutlined,
|
||||
UploadOutlined
|
||||
ReloadOutlined
|
||||
} from '@ant-design/icons';
|
||||
import Upload from "antd/es/upload";
|
||||
import {download, exitFull, getToken, isEmpty, requestFullScreen} from "../../utils/utils";
|
||||
import {exitFull, getToken, isEmpty, requestFullScreen} from "../../utils/utils";
|
||||
import './Access.css'
|
||||
import Draggable from 'react-draggable';
|
||||
import FileSystem from "./FileSystem";
|
||||
|
||||
const {TextArea} = Input;
|
||||
const {DirectoryTree} = Tree;
|
||||
const {SubMenu} = Menu;
|
||||
|
||||
const STATE_IDLE = 0;
|
||||
const STATE_CONNECTING = 1;
|
||||
@ -53,16 +31,10 @@ const STATE_CONNECTED = 3;
|
||||
const STATE_DISCONNECTING = 4;
|
||||
const STATE_DISCONNECTED = 5;
|
||||
|
||||
const antIcon = <LoadingOutlined/>;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {span: 6},
|
||||
wrapperCol: {span: 14},
|
||||
};
|
||||
|
||||
class Access extends Component {
|
||||
|
||||
formRef = React.createRef()
|
||||
formRef = React.createRef();
|
||||
clipboardFormRef = React.createRef();
|
||||
|
||||
state = {
|
||||
sessionId: '',
|
||||
@ -73,19 +45,12 @@ class Access extends Component {
|
||||
containerOverflow: 'hidden',
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
fileSystemVisible: false,
|
||||
fileSystem: {
|
||||
loading: false,
|
||||
object: null,
|
||||
currentDirectory: '/',
|
||||
files: [],
|
||||
},
|
||||
uploadAction: '',
|
||||
uploadHeaders: {},
|
||||
keyboard: {},
|
||||
protocol: '',
|
||||
treeData: [],
|
||||
selectNode: {},
|
||||
|
||||
confirmVisible: false,
|
||||
confirmLoading: false,
|
||||
uploadVisible: false,
|
||||
@ -97,10 +62,11 @@ class Access extends Component {
|
||||
|
||||
async componentDidMount() {
|
||||
|
||||
let params = new URLSearchParams(this.props.location.search);
|
||||
let assetsId = params.get('assetsId');
|
||||
let protocol = params.get('protocol');
|
||||
let sessionId = await this.createSession(assetsId);
|
||||
let urlParams = new URLSearchParams(this.props.location.search);
|
||||
let assetId = urlParams.get('assetId');
|
||||
document.title = urlParams.get('assetName');
|
||||
let protocol = urlParams.get('protocol');
|
||||
let sessionId = await this.createSession(assetId);
|
||||
if (isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
@ -150,11 +116,11 @@ class Access extends Component {
|
||||
})
|
||||
if (this.state.protocol === 'ssh') {
|
||||
if (data.data && data.data.length > 0) {
|
||||
// message.success('您输入的内容已复制到远程服务器上,使用右键将自动粘贴。');
|
||||
message.info('您输入的内容已复制到远程服务器上,使用右键将自动粘贴。');
|
||||
}
|
||||
} else {
|
||||
if (data.data && data.data.length > 0) {
|
||||
// message.success('您输入的内容已复制到远程服务器上');
|
||||
message.info('您输入的内容已复制到远程服务器上');
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +128,7 @@ class Access extends Component {
|
||||
|
||||
onTunnelStateChange = (state) => {
|
||||
if (state === Guacamole.Tunnel.State.CLOSED) {
|
||||
this.showMessage('连接已关闭');
|
||||
console.log('web socket 已关闭');
|
||||
}
|
||||
};
|
||||
|
||||
@ -277,8 +243,14 @@ class Access extends Component {
|
||||
case 783:
|
||||
this.showMessage('错误的请求类型');
|
||||
break;
|
||||
case 797:
|
||||
this.showMessage('客户端连接数量过多');
|
||||
case 800:
|
||||
this.showMessage('会话不存在');
|
||||
break;
|
||||
case 801:
|
||||
this.showMessage('创建隧道失败');
|
||||
break;
|
||||
case 802:
|
||||
this.showMessage('管理员强制断开了此会话');
|
||||
break;
|
||||
default:
|
||||
this.showMessage('未知错误。');
|
||||
@ -320,7 +292,7 @@ class Access extends Component {
|
||||
// Set clipboard contents once stream is finished
|
||||
reader.onend = async () => {
|
||||
|
||||
// message.success('您选择的内容已复制到您的粘贴板中,在右侧的输入框中可同时查看到。');
|
||||
message.info('您选择的内容已复制到您的粘贴板中,在右侧的输入框中可同时查看到。');
|
||||
this.setState({
|
||||
clipboardText: data
|
||||
});
|
||||
@ -343,17 +315,6 @@ class Access extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
uploadChange = (info) => {
|
||||
if (info.file.status !== 'uploading') {
|
||||
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功。`, 3);
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败。`, 10);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown = (keysym) => {
|
||||
if (true === this.state.clipboardVisible || true === this.state.confirmVisible) {
|
||||
return true;
|
||||
@ -368,20 +329,6 @@ class Access extends Component {
|
||||
this.state.client.sendKeyEvent(0, keysym);
|
||||
};
|
||||
|
||||
showFileSystem = () => {
|
||||
this.setState({
|
||||
fileSystemVisible: true,
|
||||
});
|
||||
|
||||
this.loadDirData('/');
|
||||
};
|
||||
|
||||
hideFileSystem = () => {
|
||||
this.setState({
|
||||
fileSystemVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
fullScreen = () => {
|
||||
let fs = this.state.fullScreen;
|
||||
if (fs) {
|
||||
@ -400,43 +347,12 @@ class Access extends Component {
|
||||
|
||||
}
|
||||
|
||||
showClipboard = () => {
|
||||
this.setState({
|
||||
clipboardVisible: true
|
||||
}, () => {
|
||||
let element = document.getElementById('clipboard');
|
||||
if (element) {
|
||||
element.value = this.state.clipboardText;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
hideClipboard = () => {
|
||||
this.setState({
|
||||
clipboardVisible: false
|
||||
});
|
||||
};
|
||||
|
||||
updateClipboardFormTextarea = () => {
|
||||
let clipboardText = document.getElementById('clipboard').value;
|
||||
|
||||
this.setState({
|
||||
clipboardText: clipboardText
|
||||
});
|
||||
|
||||
this.sendClipboard({
|
||||
'data': clipboardText,
|
||||
'type': 'text/plain'
|
||||
});
|
||||
};
|
||||
|
||||
async createSession(assetsId) {
|
||||
let result = await request.post(`/sessions?assetId=${assetsId}`);
|
||||
let result = await request.post(`/sessions?assetId=${assetsId}&mode=guacd`);
|
||||
if (result['code'] !== 1) {
|
||||
this.showMessage(result['message']);
|
||||
return null;
|
||||
}
|
||||
document.title = result['data']['name'];
|
||||
return result['data']['id'];
|
||||
}
|
||||
|
||||
@ -516,23 +432,6 @@ class Access extends Component {
|
||||
keyboard.onkeydown = this.onKeyDown;
|
||||
keyboard.onkeyup = this.onKeyUp;
|
||||
|
||||
let stateChecker = setInterval(async () => {
|
||||
let result = await request.get(`/sessions/${sessionId}/status`);
|
||||
if (result['code'] !== 1) {
|
||||
clearInterval(stateChecker);
|
||||
} else {
|
||||
let session = result['data'];
|
||||
if (session['status'] === 'connected') {
|
||||
clearInterval(stateChecker);
|
||||
return
|
||||
}
|
||||
|
||||
if (session['status'] === 'disconnected') {
|
||||
this.showMessage(session['message']);
|
||||
clearInterval(stateChecker);
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
this.setState({
|
||||
client: client,
|
||||
containerWidth: width,
|
||||
@ -620,185 +519,6 @@ class Access extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onSelect = (keys, event) => {
|
||||
this.setState({
|
||||
selectNode: {
|
||||
key: keys[0],
|
||||
isLeaf: event.node.isLeaf
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
handleOk = async (values) => {
|
||||
let params = {
|
||||
'dir': this.state.selectNode.key + '/' + values['dir']
|
||||
}
|
||||
let paramStr = qs.stringify(params);
|
||||
|
||||
this.setState({
|
||||
confirmLoading: true
|
||||
})
|
||||
let result = await request.post(`/sessions/${this.state.sessionId}/mkdir?${paramStr}`);
|
||||
if (result.code === 1) {
|
||||
message.success('创建成功');
|
||||
let parentPath = this.state.selectNode.key;
|
||||
let items = await this.getTreeNodes(parentPath);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, parentPath, items),
|
||||
selectNode: {}
|
||||
});
|
||||
} else {
|
||||
message.error(result.message);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
confirmLoading: false,
|
||||
confirmVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirmCancel = () => {
|
||||
this.setState({
|
||||
confirmVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
handleUploadCancel = () => {
|
||||
this.setState({
|
||||
uploadVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
mkdir = () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
message.warning('请选择一个目录');
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
confirmVisible: true
|
||||
})
|
||||
}
|
||||
|
||||
upload = () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
message.warning('请选择一个目录进行上传');
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
uploadVisible: true
|
||||
})
|
||||
}
|
||||
|
||||
download = () => {
|
||||
if (!this.state.selectNode.key || !this.state.selectNode.isLeaf) {
|
||||
message.warning('当前只支持下载文件');
|
||||
return;
|
||||
}
|
||||
download(`${server}/sessions/${this.state.sessionId}/download?file=${this.state.selectNode.key}`);
|
||||
}
|
||||
|
||||
rmdir = async () => {
|
||||
if (!this.state.selectNode.key) {
|
||||
message.warning('请选择一个文件或目录');
|
||||
return;
|
||||
}
|
||||
let result;
|
||||
if (this.state.selectNode.isLeaf) {
|
||||
result = await request.delete(`/sessions/${this.state.sessionId}/rm?file=${this.state.selectNode.key}`);
|
||||
} else {
|
||||
result = await request.delete(`/sessions/${this.state.sessionId}/rmdir?dir=${this.state.selectNode.key}`);
|
||||
}
|
||||
if (result.code !== 1) {
|
||||
message.error(result.message);
|
||||
} else {
|
||||
message.success('删除成功');
|
||||
let path = this.state.selectNode.key;
|
||||
let parentPath = path.substring(0, path.lastIndexOf('/'));
|
||||
let items = await this.getTreeNodes(parentPath);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, parentPath, items),
|
||||
selectNode: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh = async () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
await this.loadDirData('/');
|
||||
} else {
|
||||
let key = this.state.selectNode.key;
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, key, items),
|
||||
});
|
||||
}
|
||||
message.success('刷新目录成功');
|
||||
}
|
||||
|
||||
onRightClick = ({event, node}) => {
|
||||
|
||||
};
|
||||
|
||||
loadDirData = async (key) => {
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: items,
|
||||
});
|
||||
}
|
||||
|
||||
getTreeNodes = async (key) => {
|
||||
const url = server + '/sessions/' + this.state.sessionId + '/ls?dir=' + key;
|
||||
|
||||
let result = await request.get(url);
|
||||
|
||||
if (result.code !== 1) {
|
||||
message.error(result['message']);
|
||||
message.error(result['message']);
|
||||
return [];
|
||||
}
|
||||
|
||||
let data = result.data;
|
||||
|
||||
data = data.sort(((a, b) => a.name.localeCompare(b.name)));
|
||||
|
||||
return data.map(item => {
|
||||
return {
|
||||
title: item['name'],
|
||||
key: item['path'],
|
||||
isLeaf: !item['isDir'] && !item['isLink'],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onLoadData = ({key, children}) => {
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
if (children) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, key, items),
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
updateTreeData = (list, key, children) => {
|
||||
return list.map((node) => {
|
||||
if (node.key === key) {
|
||||
return {...node, children};
|
||||
} else if (node.children) {
|
||||
return {...node, children: this.updateTreeData(node.children, key, children)};
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
sendCombinationKey = (keys) => {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
this.state.client.sendKeyEvent(1, keys[i]);
|
||||
@ -810,67 +530,22 @@ class Access extends Component {
|
||||
|
||||
render() {
|
||||
|
||||
const title = (
|
||||
<Row>
|
||||
<Space>
|
||||
远程文件管理
|
||||
|
||||
|
||||
<Tooltip title="创建文件夹">
|
||||
<Button type="primary" size="small" icon={<FolderAddOutlined/>}
|
||||
onClick={this.mkdir} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="上传">
|
||||
<Button type="primary" size="small" icon={<CloudUploadOutlined/>}
|
||||
onClick={this.upload} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="下载">
|
||||
<Button type="primary" size="small" icon={<CloudDownloadOutlined/>}
|
||||
onClick={this.download} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="删除文件">
|
||||
<Button type="dashed" size="small" icon={<DeleteOutlined/>} onClick={this.rmdir}
|
||||
danger/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="刷新">
|
||||
<Button type="primary" size="small" icon={<ReloadOutlined/>} onClick={this.refresh}
|
||||
ghost/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Row>
|
||||
);
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item key="1" icon={<CopyOutlined/>} onClick={this.showClipboard}>
|
||||
剪贴板
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<FileZipOutlined/>} onClick={this.showFileSystem}>
|
||||
文件管理
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<ExpandOutlined/>} onClick={this.fullScreen}>
|
||||
{this.state.fullScreenBtnText}
|
||||
</Menu.Item>
|
||||
<SubMenu title="发送快捷键" icon={<DesktopOutlined/>}>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65535'])}>Ctrl+Alt+Delete</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65288'])}>Ctrl+Alt+Backspace</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '100'])}>Windows+D</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '101'])}>Windows+E</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '114'])}>Windows+R</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '120'])}>Windows+X</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515'])}>Windows</Menu.Item>
|
||||
</SubMenu>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65535'])}>Ctrl+Alt+Delete</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65288'])}>Ctrl+Alt+Backspace</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '100'])}>Windows+D</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '101'])}>Windows+E</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '114'])}>Windows+R</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '120'])}>Windows+X</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515'])}>Windows</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@ -885,100 +560,106 @@ class Access extends Component {
|
||||
<div id="display"/>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title="创建文件夹"
|
||||
visible={this.state.confirmVisible}
|
||||
onOk={() => {
|
||||
this.formRef.current
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
this.formRef.current.resetFields();
|
||||
this.handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
|
||||
});
|
||||
}}
|
||||
confirmLoading={this.state.confirmLoading}
|
||||
onCancel={this.handleConfirmCancel}
|
||||
>
|
||||
<Form ref={this.formRef} {...formItemLayout}>
|
||||
|
||||
<Form.Item label="文件夹名称" name='dir' rules={[{required: true, message: '请输入文件夹名称'}]}>
|
||||
<Input autoComplete="off" placeholder="请输入文件夹名称"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="上传文件"
|
||||
visible={this.state.uploadVisible}
|
||||
onOk={() => {
|
||||
|
||||
}}
|
||||
confirmLoading={this.state.uploadLoading}
|
||||
onCancel={this.handleUploadCancel}
|
||||
>
|
||||
<Upload
|
||||
action={server + '/sessions/' + this.state.sessionId + '/upload?X-Auth-Token=' + getToken() + '&dir=' + this.state.selectNode.key}>
|
||||
<Button icon={<UploadOutlined/>}>上传文件</Button>
|
||||
</Upload>
|
||||
</Modal>
|
||||
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
||||
<Button icon={<ExpandOutlined/>} onClick={() => {
|
||||
this.fullScreen();
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 150}}>
|
||||
<Button icon={<CopyTwoTone/>} onClick={() => {
|
||||
this.setState({
|
||||
clipboardVisible: true
|
||||
});
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 100, right: 100}}>
|
||||
<Button icon={<AppstoreTwoTone/>} onClick={() => {
|
||||
this.setState({
|
||||
fileSystemVisible: true,
|
||||
});
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 100, right: 150}}>
|
||||
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft">
|
||||
<Button icon={<AppstoreTwoTone/>}/>
|
||||
<Button icon={<DesktopOutlined/>}/>
|
||||
</Dropdown>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Drawer
|
||||
title={title}
|
||||
title={'会话详情'}
|
||||
placement="right"
|
||||
width={window.innerWidth * 0.3}
|
||||
width={window.innerWidth * 0.5}
|
||||
closable={true}
|
||||
maskClosable={false}
|
||||
onClose={this.hideFileSystem}
|
||||
onClose={()=>{
|
||||
this.setState({
|
||||
fileSystemVisible: false
|
||||
});
|
||||
}}
|
||||
visible={this.state.fileSystemVisible}
|
||||
>
|
||||
|
||||
|
||||
<Row style={{marginTop: 10}}>
|
||||
<Col span={24}>
|
||||
<Card title={this.state.fileSystem.currentDirectory} bordered={true} size="small">
|
||||
<Spin indicator={antIcon} spinning={this.state.fileSystem.loading}>
|
||||
|
||||
<DirectoryTree
|
||||
// multiple
|
||||
onSelect={this.onSelect}
|
||||
loadData={this.onLoadData}
|
||||
treeData={this.state.treeData}
|
||||
onRightClick={this.onRightClick}
|
||||
/>
|
||||
|
||||
</Spin>
|
||||
</Card>
|
||||
<FileSystem sessionId={this.state.sessionId}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Drawer>
|
||||
|
||||
<Drawer
|
||||
title="剪贴板"
|
||||
placement="right"
|
||||
width={window.innerWidth * 0.3}
|
||||
onClose={this.hideClipboard}
|
||||
visible={this.state.clipboardVisible}
|
||||
>
|
||||
{
|
||||
this.state.clipboardVisible ?
|
||||
<Modal
|
||||
title="剪贴板"
|
||||
maskClosable={false}
|
||||
visible={this.state.clipboardVisible}
|
||||
onOk={() => {
|
||||
this.clipboardFormRef.current
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
let clipboardText = values['clipboard'];
|
||||
|
||||
<Alert message="复制/剪切的文本将出现在这里。对下面文本内容所作的修改将会影响远程电脑上的剪贴板。" type="info" showIcon closable/>
|
||||
this.sendClipboard({
|
||||
'data': clipboardText,
|
||||
'type': 'text/plain'
|
||||
});
|
||||
|
||||
<div style={{marginTop: 10, marginBottom: 10}}>
|
||||
<TextArea id='clipboard' rows={10} onBlur={this.updateClipboardFormTextarea}/>
|
||||
</div>
|
||||
this.setState({
|
||||
clipboardText: clipboardText,
|
||||
clipboardVisible: false
|
||||
});
|
||||
})
|
||||
.catch(info => {
|
||||
|
||||
});
|
||||
}}
|
||||
confirmLoading={this.state.confirmLoading}
|
||||
onCancel={() => {
|
||||
this.setState({
|
||||
clipboardVisible: false
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Form ref={this.clipboardFormRef} initialValues={{'clipboard': this.state.clipboardText}}>
|
||||
<Form.Item name='clipboard' rules={[{required: false}]}>
|
||||
<TextArea id='clipboard' rows={10}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
: undefined
|
||||
}
|
||||
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,14 +3,13 @@ import "xterm/css/xterm.css"
|
||||
import {Terminal} from "xterm";
|
||||
import qs from "qs";
|
||||
import {wsServer} from "../../common/constants";
|
||||
import "./Console.css"
|
||||
import {getToken, isEmpty} from "../../utils/utils";
|
||||
import {FitAddon} from 'xterm-addon-fit';
|
||||
import "./Access.css"
|
||||
import request from "../../common/request";
|
||||
import {message} from "antd";
|
||||
|
||||
class AccessSSH extends Component {
|
||||
class AccessNaive extends Component {
|
||||
|
||||
state = {
|
||||
width: window.innerWidth,
|
||||
@ -24,6 +23,7 @@ class AccessSSH extends Component {
|
||||
|
||||
let urlParams = new URLSearchParams(this.props.location.search);
|
||||
let assetId = urlParams.get('assetId');
|
||||
document.title = urlParams.get('assetName');
|
||||
|
||||
let sessionId = await this.createSession(assetId);
|
||||
if (isEmpty(sessionId)) {
|
||||
@ -144,12 +144,11 @@ class AccessSSH extends Component {
|
||||
}
|
||||
|
||||
async createSession(assetsId) {
|
||||
let result = await request.post(`/sessions?assetId=${assetsId}`);
|
||||
let result = await request.post(`/sessions?assetId=${assetsId}&mode=naive`);
|
||||
if (result['code'] !== 1) {
|
||||
this.showMessage(result['message']);
|
||||
return null;
|
||||
}
|
||||
document.title = result['data']['name'];
|
||||
return result['data']['id'];
|
||||
}
|
||||
|
||||
@ -203,4 +202,4 @@ class AccessSSH extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default AccessSSH;
|
||||
export default AccessNaive;
|
474
web/src/components/access/FileSystem.js
Normal file
474
web/src/components/access/FileSystem.js
Normal file
@ -0,0 +1,474 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Button, Card, Form, Input, message, Modal, Row, Space, Table, Tooltip, Tree} from "antd";
|
||||
import {
|
||||
CloudDownloadOutlined,
|
||||
CloudUploadOutlined,
|
||||
DeleteOutlined,
|
||||
FileExcelTwoTone,
|
||||
FileImageTwoTone,
|
||||
FileMarkdownTwoTone,
|
||||
FilePdfTwoTone,
|
||||
FileTextTwoTone,
|
||||
FileTwoTone,
|
||||
FileWordTwoTone,
|
||||
FileZipTwoTone,
|
||||
FolderAddOutlined,
|
||||
FolderTwoTone,
|
||||
LinkOutlined,
|
||||
LoadingOutlined,
|
||||
ReloadOutlined,
|
||||
UploadOutlined
|
||||
} from "@ant-design/icons";
|
||||
import qs from "qs";
|
||||
import request from "../../common/request";
|
||||
import {server} from "../../common/constants";
|
||||
import Upload from "antd/es/upload";
|
||||
import {download, getToken, renderSize} from "../../utils/utils";
|
||||
|
||||
const antIcon = <LoadingOutlined/>;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {span: 6},
|
||||
wrapperCol: {span: 14},
|
||||
};
|
||||
|
||||
const {DirectoryTree} = Tree;
|
||||
|
||||
class FileSystem extends Component {
|
||||
|
||||
state = {
|
||||
sessionId: undefined,
|
||||
currentDirectory: '/',
|
||||
files: [],
|
||||
loading: false,
|
||||
selectNode: {},
|
||||
selectedRowKeys: []
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let sessionId = this.props.sessionId;
|
||||
this.setState({
|
||||
sessionId: sessionId
|
||||
}, () => {
|
||||
this.loadFiles('/');
|
||||
});
|
||||
}
|
||||
|
||||
onSelect = (keys, event) => {
|
||||
this.setState({
|
||||
selectNode: {
|
||||
key: keys[0],
|
||||
isLeaf: event.node.isLeaf
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
handleOk = async (values) => {
|
||||
let params = {
|
||||
'dir': this.state.selectNode.key + '/' + values['dir']
|
||||
}
|
||||
let paramStr = qs.stringify(params);
|
||||
|
||||
this.setState({
|
||||
confirmLoading: true
|
||||
})
|
||||
let result = await request.post(`/sessions/${this.state.sessionId}/mkdir?${paramStr}`);
|
||||
if (result.code === 1) {
|
||||
message.success('创建成功');
|
||||
let parentPath = this.state.selectNode.key;
|
||||
let items = await this.getTreeNodes(parentPath);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, parentPath, items),
|
||||
selectNode: {}
|
||||
});
|
||||
} else {
|
||||
message.error(result.message);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
confirmLoading: false,
|
||||
confirmVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirmCancel = () => {
|
||||
this.setState({
|
||||
confirmVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
handleUploadCancel = () => {
|
||||
this.setState({
|
||||
uploadVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
mkdir = () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
message.warning('请选择一个目录');
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
confirmVisible: true
|
||||
})
|
||||
}
|
||||
|
||||
upload = () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
message.warning('请选择一个目录进行上传');
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
uploadVisible: true
|
||||
})
|
||||
}
|
||||
|
||||
download = () => {
|
||||
if (!this.state.selectNode.key || !this.state.selectNode.isLeaf) {
|
||||
message.warning('当前只支持下载文件');
|
||||
return;
|
||||
}
|
||||
download(`${server}/sessions/${this.state.sessionId}/download?file=${this.state.selectNode.key}`);
|
||||
}
|
||||
|
||||
rmdir = async () => {
|
||||
if (!this.state.selectNode.key) {
|
||||
message.warning('请选择一个文件或目录');
|
||||
return;
|
||||
}
|
||||
let result;
|
||||
if (this.state.selectNode.isLeaf) {
|
||||
result = await request.delete(`/sessions/${this.state.sessionId}/rm?file=${this.state.selectNode.key}`);
|
||||
} else {
|
||||
result = await request.delete(`/sessions/${this.state.sessionId}/rmdir?dir=${this.state.selectNode.key}`);
|
||||
}
|
||||
if (result.code !== 1) {
|
||||
message.error(result.message);
|
||||
} else {
|
||||
message.success('删除成功');
|
||||
let path = this.state.selectNode.key;
|
||||
let parentPath = path.substring(0, path.lastIndexOf('/'));
|
||||
let items = await this.getTreeNodes(parentPath);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, parentPath, items),
|
||||
selectNode: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh = async () => {
|
||||
if (!this.state.selectNode.key || this.state.selectNode.isLeaf) {
|
||||
await this.loadDirData('/');
|
||||
} else {
|
||||
let key = this.state.selectNode.key;
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, key, items),
|
||||
});
|
||||
}
|
||||
message.success('刷新目录成功');
|
||||
}
|
||||
|
||||
loadFiles = async (key) => {
|
||||
this.setState({
|
||||
loading: true
|
||||
})
|
||||
try {
|
||||
let result = await request.get(`${server}/sessions/${this.state.sessionId}/ls?dir=${key}`);
|
||||
if (result['code'] !== 1) {
|
||||
message.error(result['message']);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = result['data'];
|
||||
|
||||
const items = data.map(item => {
|
||||
return {'key': item['path'], ...item}
|
||||
})
|
||||
|
||||
this.setState({
|
||||
files: items
|
||||
})
|
||||
} finally {
|
||||
this.setState({
|
||||
loading: false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loadDirData = async (key) => {
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: items,
|
||||
});
|
||||
}
|
||||
|
||||
getTreeNodes = async (key) => {
|
||||
const url = server + '/sessions/' + this.state.sessionId + '/ls?dir=' + key;
|
||||
|
||||
let result = await request.get(url);
|
||||
|
||||
if (result.code !== 1) {
|
||||
message.error(result['message']);
|
||||
return [];
|
||||
}
|
||||
|
||||
let data = result.data;
|
||||
|
||||
return data.map(item => {
|
||||
return {
|
||||
title: item['name'],
|
||||
key: item['path'],
|
||||
isLeaf: !item['isDir'] && !item['isLink'],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onLoadData = ({key, children}) => {
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
if (children) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let items = await this.getTreeNodes(key);
|
||||
this.setState({
|
||||
treeData: this.updateTreeData(this.state.treeData, key, items),
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
updateTreeData = (list, key, children) => {
|
||||
return list.map((node) => {
|
||||
if (node.key === key) {
|
||||
return {...node, children};
|
||||
} else if (node.children) {
|
||||
return {...node, children: this.updateTreeData(node.children, key, children)};
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
uploadChange = (info) => {
|
||||
if (info.file.status !== 'uploading') {
|
||||
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功。`, 3);
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败。`, 10);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (value, item) => {
|
||||
let icon;
|
||||
if (item['isDir']) {
|
||||
icon = <FolderTwoTone/>;
|
||||
} else {
|
||||
if (item['isLink']) {
|
||||
icon = <LinkOutlined/>;
|
||||
} else {
|
||||
const fileExtension = item['name'].split('.').pop().toLowerCase();
|
||||
switch (fileExtension) {
|
||||
case "doc":
|
||||
case "docx":
|
||||
icon = <FileWordTwoTone/>;
|
||||
break;
|
||||
case "xls":
|
||||
case "xlsx":
|
||||
icon = <FileExcelTwoTone/>;
|
||||
break;
|
||||
case "bmp":
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "png":
|
||||
case "tif":
|
||||
case "gif":
|
||||
case "pcx":
|
||||
case "tga":
|
||||
case "exif":
|
||||
case "svg":
|
||||
case "psd":
|
||||
case "ai":
|
||||
case "webp":
|
||||
icon = <FileImageTwoTone/>;
|
||||
break;
|
||||
case "md":
|
||||
icon = <FileMarkdownTwoTone/>;
|
||||
break;
|
||||
case "pdf":
|
||||
icon = <FilePdfTwoTone/>;
|
||||
break;
|
||||
case "txt":
|
||||
icon = <FileTextTwoTone/>;
|
||||
break;
|
||||
case "zip":
|
||||
case "gz":
|
||||
case "tar":
|
||||
icon = <FileZipTwoTone/>;
|
||||
break;
|
||||
default:
|
||||
icon = <FileTwoTone/>;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <>{icon} {item['name']}</>;
|
||||
},
|
||||
sorter: (a, b) => a.name - b.name,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
render: (value, item) => {
|
||||
if (!item['isDir'] && !item['isLink']) {
|
||||
return renderSize(value)
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
sorter: (a, b) => a.size - b.size,
|
||||
},
|
||||
{
|
||||
title: '修改日期',
|
||||
dataIndex: 'modTime',
|
||||
key: 'modTime',
|
||||
sorter: (a, b) => a.modTime - b.modTime,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
}
|
||||
];
|
||||
|
||||
const {loading, selectedRowKeys} = this.state;
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: () => {
|
||||
|
||||
},
|
||||
};
|
||||
const hasSelected = selectedRowKeys.length > 0;
|
||||
|
||||
const title = (
|
||||
<Row>
|
||||
<Space>
|
||||
远程文件管理
|
||||
|
||||
|
||||
<Tooltip title="创建文件夹">
|
||||
<Button type="primary" size="small" icon={<FolderAddOutlined/>}
|
||||
onClick={this.mkdir} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="上传">
|
||||
<Button type="primary" size="small" icon={<CloudUploadOutlined/>}
|
||||
onClick={this.upload} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="下载">
|
||||
<Button type="primary" size="small" icon={<CloudDownloadOutlined/>}
|
||||
onClick={this.download} ghost/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="删除文件">
|
||||
<Button type="dashed" size="small" icon={<DeleteOutlined/>} onClick={this.rmdir}
|
||||
danger/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="刷新">
|
||||
<Button type="primary" size="small" icon={<ReloadOutlined/>} onClick={this.refresh}
|
||||
ghost/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Row>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card title={title} bordered={true} size="small">
|
||||
<Table columns={columns}
|
||||
rowSelection={rowSelection}
|
||||
dataSource={this.state.files}
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
loading={this.state.loading}
|
||||
onRow={record => {
|
||||
return {
|
||||
onClick: event => {
|
||||
|
||||
}, // 点击行
|
||||
onDoubleClick: event => {
|
||||
this.loadFiles(record['path']);
|
||||
},
|
||||
onContextMenu: event => {
|
||||
},
|
||||
onMouseEnter: event => {
|
||||
}, // 鼠标移入行
|
||||
onMouseLeave: event => {
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title="上传文件"
|
||||
visible={this.state.uploadVisible}
|
||||
onOk={() => {
|
||||
|
||||
}}
|
||||
confirmLoading={this.state.uploadLoading}
|
||||
onCancel={this.handleUploadCancel}
|
||||
>
|
||||
<Upload
|
||||
action={server + '/sessions/' + this.state.sessionId + '/upload?X-Auth-Token=' + getToken() + '&dir=' + this.state.selectNode.key}>
|
||||
<Button icon={<UploadOutlined/>}>上传文件</Button>
|
||||
</Upload>
|
||||
</Modal>
|
||||
|
||||
|
||||
<Modal
|
||||
title="创建文件夹"
|
||||
visible={this.state.confirmVisible}
|
||||
onOk={() => {
|
||||
this.formRef.current
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
this.formRef.current.resetFields();
|
||||
this.handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
|
||||
});
|
||||
}}
|
||||
confirmLoading={this.state.confirmLoading}
|
||||
onCancel={this.handleConfirmCancel}
|
||||
>
|
||||
<Form ref={this.formRef} {...formItemLayout}>
|
||||
|
||||
<Form.Item label="文件夹名称" name='dir' rules={[{required: true, message: '请输入文件夹名称'}]}>
|
||||
<Input autoComplete="off" placeholder="请输入文件夹名称"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystem;
|
@ -13,7 +13,7 @@ const STATE_CONNECTED = 3;
|
||||
const STATE_DISCONNECTING = 4;
|
||||
const STATE_DISCONNECTED = 5;
|
||||
|
||||
class Access extends Component {
|
||||
class Monitor extends Component {
|
||||
|
||||
formRef = React.createRef()
|
||||
|
||||
@ -103,76 +103,6 @@ class Access extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onError = (status) => {
|
||||
|
||||
console.log('通道异常。', status);
|
||||
|
||||
switch (status.code) {
|
||||
case 256:
|
||||
this.showMessage('未支持的访问');
|
||||
break;
|
||||
case 512:
|
||||
this.showMessage('远程服务异常');
|
||||
break;
|
||||
case 513:
|
||||
this.showMessage('服务器忙碌');
|
||||
break;
|
||||
case 514:
|
||||
this.showMessage('服务器连接超时');
|
||||
break;
|
||||
case 515:
|
||||
this.showMessage('远程服务异常');
|
||||
break;
|
||||
case 516:
|
||||
this.showMessage('资源未找到');
|
||||
break;
|
||||
case 517:
|
||||
this.showMessage('资源冲突');
|
||||
break;
|
||||
case 518:
|
||||
this.showMessage('资源已关闭');
|
||||
break;
|
||||
case 519:
|
||||
this.showMessage('远程服务未找到');
|
||||
break;
|
||||
case 520:
|
||||
this.showMessage('远程服务不可用');
|
||||
break;
|
||||
case 521:
|
||||
this.showMessage('会话冲突');
|
||||
break;
|
||||
case 522:
|
||||
this.showMessage('会话连接超时');
|
||||
break;
|
||||
case 523:
|
||||
this.showMessage('会话已关闭');
|
||||
break;
|
||||
case 768:
|
||||
this.showMessage('网络不可达');
|
||||
break;
|
||||
case 769:
|
||||
this.showMessage('服务器密码验证失败');
|
||||
break;
|
||||
case 771:
|
||||
this.showMessage('客户端被禁止');
|
||||
break;
|
||||
case 776:
|
||||
this.showMessage('客户端连接超时');
|
||||
break;
|
||||
case 781:
|
||||
this.showMessage('客户端异常');
|
||||
break;
|
||||
case 783:
|
||||
this.showMessage('错误的请求类型');
|
||||
break;
|
||||
case 797:
|
||||
this.showMessage('客户端连接数量过多');
|
||||
break;
|
||||
default:
|
||||
this.showMessage('未知错误。');
|
||||
}
|
||||
};
|
||||
|
||||
showMessage(message) {
|
||||
Modal.error({
|
||||
title: '提示',
|
||||
@ -189,7 +119,6 @@ class Access extends Component {
|
||||
|
||||
// 处理客户端的状态变化事件
|
||||
client.onstatechange = this.onClientStateChange;
|
||||
client.onerror = this.onError;
|
||||
const display = document.getElementById("display");
|
||||
|
||||
// Add client to display div
|
||||
@ -245,4 +174,4 @@ class Access extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Access;
|
||||
export default Monitor;
|
||||
|
Reference in New Issue
Block a user