Merge remote-tracking branch 'origin/dev'
# Conflicts: # web/src/components/command/DynamicCommand.js # web/src/components/command/DynamicCommandModal.js # web/src/components/credential/CredentialModal.js # web/src/components/devops/JobModal.js # web/src/components/devops/SecurityModal.js # web/src/components/user/User.js # web/src/components/user/UserGroup.js
This commit is contained in:
@ -132,7 +132,7 @@ class LoginForm extends Component {
|
||||
|
||||
<Modal title="双因素认证" visible={this.state.totpModalVisible} confirmLoading={this.state.confirmLoading}
|
||||
maskClosable={false}
|
||||
centered={true}
|
||||
|
||||
okButtonProps={{form:'totp-form', key: 'submit', htmlType: 'submit'}}
|
||||
onOk={() => {
|
||||
this.formRef.current
|
||||
|
@ -20,7 +20,8 @@ class Term extends Component {
|
||||
term: undefined,
|
||||
webSocket: undefined,
|
||||
fitAddon: undefined,
|
||||
sessionId: undefined
|
||||
sessionId: undefined,
|
||||
enterBtnIndex: 1001
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
@ -215,17 +216,17 @@ class Term extends Component {
|
||||
}}/>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 50, zIndex: 9999}}>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 50, zIndex: this.state.enterBtnIndex}}>
|
||||
<Button icon={<AppstoreTwoTone/>} onClick={() => {
|
||||
this.setState({
|
||||
fileSystemVisible: true,
|
||||
enterBtnIndex: 999, // xterm.js 输入框的zIndex是1000,在弹出文件管理页面后要隐藏此按钮
|
||||
});
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Drawer
|
||||
style={{zIndex: 10000}}
|
||||
title={'会话详情'}
|
||||
placement="right"
|
||||
width={window.innerWidth * 0.8}
|
||||
@ -233,7 +234,8 @@ class Term extends Component {
|
||||
// maskClosable={false}
|
||||
onClose={() => {
|
||||
this.setState({
|
||||
fileSystemVisible: false
|
||||
fileSystemVisible: false,
|
||||
enterBtnIndex: 1001, // xterm.js 输入框的zIndex是1000,在隐藏文件管理页面后要显示此按钮
|
||||
});
|
||||
}}
|
||||
visible={this.state.fileSystemVisible}
|
||||
|
@ -355,10 +355,10 @@ class Asset extends Component {
|
||||
window.open(`#/access?assetId=${id}&assetName=${name}&protocol=${protocol}`);
|
||||
}
|
||||
} else {
|
||||
message.warn('您访问的资产未在线,请确认网络状态。', 10);
|
||||
message.warn({content: '您访问的资产未在线,请确认网络状态。', key: id, duration: 10});
|
||||
}
|
||||
} else {
|
||||
message.error('操作失败 :( ' + result.message, 10);
|
||||
message.error({content: result.message, key: id, duration: 10});
|
||||
}
|
||||
|
||||
}
|
||||
|
427
web/src/components/command/ChooseAsset.js
Normal file
427
web/src/components/command/ChooseAsset.js
Normal file
@ -0,0 +1,427 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "antd";
|
||||
import qs from "qs";
|
||||
import request from "../../common/request";
|
||||
import {message} from "antd/es";
|
||||
|
||||
|
||||
import {PlusOutlined, SyncOutlined, UndoOutlined} from '@ant-design/icons';
|
||||
import {PROTOCOL_COLORS} from "../../common/constants";
|
||||
import {isEmpty} from "../../utils/utils";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const confirm = Modal.confirm;
|
||||
const {Search} = Input;
|
||||
const {Content} = Layout;
|
||||
const {Title, Text} = Typography;
|
||||
|
||||
class ChooseAsset extends Component {
|
||||
|
||||
inputRefOfName = React.createRef();
|
||||
inputRefOfIp = React.createRef();
|
||||
changeOwnerFormRef = React.createRef();
|
||||
|
||||
state = {
|
||||
items: [],
|
||||
total: 0,
|
||||
queryParams: {
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
protocol: 'ssh'
|
||||
},
|
||||
loading: false,
|
||||
tags: [],
|
||||
model: {},
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
delBtnLoading: false,
|
||||
changeOwnerModalVisible: false,
|
||||
changeSharerModalVisible: false,
|
||||
changeOwnerConfirmLoading: false,
|
||||
changeSharerConfirmLoading: false,
|
||||
users: [],
|
||||
selected: {},
|
||||
totalSelectedRows: [],
|
||||
};
|
||||
|
||||
checkedAssets = undefined
|
||||
|
||||
async componentDidMount() {
|
||||
this.checkedAssets = this.props.setCheckedAssets;
|
||||
this.loadTableData();
|
||||
let result = await request.get('/tags');
|
||||
if (result['code'] === 1) {
|
||||
this.setState({
|
||||
tags: result['data']
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async loadTableData(queryParams) {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
queryParams = queryParams || this.state.queryParams;
|
||||
|
||||
// queryParams
|
||||
let paramsStr = qs.stringify(queryParams);
|
||||
|
||||
let data = {
|
||||
items: [],
|
||||
total: 0
|
||||
};
|
||||
|
||||
try {
|
||||
let result = await request.get('/assets/paging?' + paramsStr);
|
||||
if (result['code'] === 1) {
|
||||
data = result['data'];
|
||||
} else {
|
||||
message.error(result['message']);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
let sharer = this.state.sharer;
|
||||
const items = data.items.map(item => {
|
||||
let disabled = false;
|
||||
if (sharer === item['owner']) {
|
||||
disabled = true;
|
||||
}
|
||||
return {...item, 'key': item['id'], 'disabled': disabled}
|
||||
})
|
||||
let totalSelectedRows = this.state.totalSelectedRows;
|
||||
let selectedRowKeys = totalSelectedRows.map(item => item['id']);
|
||||
this.setState({
|
||||
items: items,
|
||||
total: data.total,
|
||||
queryParams: queryParams,
|
||||
loading: false,
|
||||
selectedRowKeys: selectedRowKeys
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleChangPage = async (pageIndex, pageSize) => {
|
||||
let queryParams = this.state.queryParams;
|
||||
queryParams.pageIndex = pageIndex;
|
||||
queryParams.pageSize = pageSize;
|
||||
|
||||
this.setState({
|
||||
queryParams: queryParams
|
||||
});
|
||||
|
||||
await this.loadTableData(queryParams)
|
||||
};
|
||||
|
||||
handleSearchByName = name => {
|
||||
let query = {
|
||||
...this.state.queryParams,
|
||||
'pageIndex': 1,
|
||||
'pageSize': this.state.queryParams.pageSize,
|
||||
'name': name,
|
||||
}
|
||||
|
||||
this.loadTableData(query);
|
||||
};
|
||||
|
||||
handleSearchByIp = ip => {
|
||||
let query = {
|
||||
...this.state.queryParams,
|
||||
'pageIndex': 1,
|
||||
'pageSize': this.state.queryParams.pageSize,
|
||||
'ip': ip,
|
||||
}
|
||||
|
||||
this.loadTableData(query);
|
||||
};
|
||||
|
||||
handleTagsChange = tags => {
|
||||
console.log(tags)
|
||||
// this.setState({
|
||||
// tags: tags
|
||||
// })
|
||||
let query = {
|
||||
...this.state.queryParams,
|
||||
'pageIndex': 1,
|
||||
'pageSize': this.state.queryParams.pageSize,
|
||||
'tags': tags.join(','),
|
||||
}
|
||||
|
||||
this.loadTableData(query);
|
||||
}
|
||||
|
||||
unSelectRow = async (assetId) => {
|
||||
const selectedRowKeys = this.state.selectedRowKeys.filter(key => key !== assetId);
|
||||
const totalSelectedRows = this.state.totalSelectedRows.filter(item => item['id'] !== assetId);
|
||||
this.setState({
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
totalSelectedRows: totalSelectedRows
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const columns = [{
|
||||
title: '序号',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
render: (id, record, index) => {
|
||||
return index + 1;
|
||||
}
|
||||
}, {
|
||||
title: '资产名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (name, record) => {
|
||||
let short = name;
|
||||
if (short && short.length > 20) {
|
||||
short = short.substring(0, 20) + " ...";
|
||||
}
|
||||
return (
|
||||
<Tooltip placement="topLeft" title={name}>
|
||||
{short}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}, {
|
||||
title: '连接协议',
|
||||
dataIndex: 'protocol',
|
||||
key: 'protocol',
|
||||
render: (text, record) => {
|
||||
const title = `${record['ip'] + ':' + record['port']}`
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
<Tag color={PROTOCOL_COLORS[text]}>{text}</Tag>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
}, {
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
key: 'tags',
|
||||
render: tags => {
|
||||
if (!isEmpty(tags)) {
|
||||
let tagDocuments = []
|
||||
let tagArr = tags.split(',');
|
||||
for (let i = 0; i < tagArr.length; i++) {
|
||||
if (tags[i] === '-') {
|
||||
continue;
|
||||
}
|
||||
tagDocuments.push(<Tag>{tagArr[i]}</Tag>)
|
||||
}
|
||||
return tagDocuments;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '状态',
|
||||
dataIndex: 'active',
|
||||
key: 'active',
|
||||
render: text => {
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
<Tooltip title='运行中'>
|
||||
<Badge status="processing"/>
|
||||
</Tooltip>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Tooltip title='不可用'>
|
||||
<Badge status="error"/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '所有者',
|
||||
dataIndex: 'ownerName',
|
||||
key: 'ownerName'
|
||||
}, {
|
||||
title: '创建日期',
|
||||
dataIndex: 'created',
|
||||
key: 'created',
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<Tooltip title={text}>
|
||||
{dayjs(text).fromNow()}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const selectedRowKeys = this.state.selectedRowKeys;
|
||||
const rowSelection = {
|
||||
selectedRowKeys: this.state.selectedRowKeys,
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState({selectedRowKeys, selectedRows});
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
disabled: record['disabled'],
|
||||
}),
|
||||
};
|
||||
let hasSelected = false;
|
||||
if (selectedRowKeys.length > 0) {
|
||||
let totalSelectedRows = this.state.totalSelectedRows;
|
||||
let allSelectedRowKeys = totalSelectedRows.map(item => item['id']);
|
||||
for (let i = 0; i < selectedRowKeys.length; i++) {
|
||||
let selectedRowKey = selectedRowKeys[i];
|
||||
if (!allSelectedRowKeys.includes(selectedRowKey)) {
|
||||
hasSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title level={3}>已选择资产列表</Title>
|
||||
<div>
|
||||
{
|
||||
this.state.totalSelectedRows.map(item => {
|
||||
return <Tag color={PROTOCOL_COLORS[item['protocol']]} closable
|
||||
onClose={() => this.unSelectRow(item['id'])}
|
||||
key={item['id']}>{item['name']}</Tag>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<Divider/>
|
||||
<Content key='page-content' className="site-layout-background">
|
||||
<div style={{marginBottom: 20}}>
|
||||
<Row justify="space-around" align="middle" gutter={24}>
|
||||
<Col span={4} key={1}>
|
||||
<Title level={3}>全部资产列表</Title>
|
||||
</Col>
|
||||
<Col span={20} key={2} style={{textAlign: 'right'}}>
|
||||
<Space>
|
||||
|
||||
<Search
|
||||
ref={this.inputRefOfName}
|
||||
placeholder="资产名称"
|
||||
allowClear
|
||||
onSearch={this.handleSearchByName}
|
||||
style={{width: 200}}
|
||||
/>
|
||||
|
||||
<Search
|
||||
ref={this.inputRefOfIp}
|
||||
placeholder="资产IP"
|
||||
allowClear
|
||||
onSearch={this.handleSearchByIp}
|
||||
style={{width: 200}}
|
||||
/>
|
||||
|
||||
<Select mode="multiple"
|
||||
allowClear
|
||||
placeholder="资产标签" onChange={this.handleTagsChange}
|
||||
style={{minWidth: 150}}>
|
||||
{this.state.tags.map(tag => {
|
||||
if (tag === '-') {
|
||||
return undefined;
|
||||
}
|
||||
return (<Select.Option key={tag}>{tag}</Select.Option>)
|
||||
})}
|
||||
</Select>
|
||||
|
||||
<Tooltip title='重置查询'>
|
||||
|
||||
<Button icon={<UndoOutlined/>} onClick={() => {
|
||||
this.inputRefOfName.current.setValue('');
|
||||
this.inputRefOfIp.current.setValue('');
|
||||
this.loadTableData({
|
||||
...this.state.queryParams,
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
protocol: 'ssh',
|
||||
name: '',
|
||||
ip: ''
|
||||
})
|
||||
}}>
|
||||
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Divider type="vertical"/>
|
||||
|
||||
<Tooltip title="刷新列表">
|
||||
<Button icon={<SyncOutlined/>} onClick={() => {
|
||||
this.loadTableData(this.state.queryParams)
|
||||
}}>
|
||||
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="添加选择">
|
||||
<Button type="primary" disabled={!hasSelected} icon={<PlusOutlined/>}
|
||||
onClick={async () => {
|
||||
console.log(this.state.selectedRows)
|
||||
let totalSelectedRows = this.state.totalSelectedRows;
|
||||
let totalSelectedRowKeys = totalSelectedRows.map(item => item['id']);
|
||||
|
||||
let selectedRows = this.state.selectedRows;
|
||||
let newRowKeys = []
|
||||
for (let i = 0; i < selectedRows.length; i++) {
|
||||
let selectedRow = selectedRows[i];
|
||||
if (totalSelectedRowKeys.includes(selectedRow['id'])) {
|
||||
continue;
|
||||
}
|
||||
totalSelectedRows.push(selectedRow);
|
||||
newRowKeys.push(selectedRow['id']);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
totalSelectedRows: totalSelectedRows
|
||||
})
|
||||
if (this.checkedAssets) {
|
||||
this.checkedAssets(totalSelectedRows);
|
||||
}
|
||||
}}>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<Table key='assets-table'
|
||||
rowSelection={rowSelection}
|
||||
dataSource={this.state.items}
|
||||
columns={columns}
|
||||
position={'both'}
|
||||
size="middle"
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
current: this.state.queryParams.pageIndex,
|
||||
pageSize: this.state.queryParams.pageSize,
|
||||
onChange: this.handleChangPage,
|
||||
onShowSizeChange: this.handleChangPage,
|
||||
total: this.state.total,
|
||||
showTotal: total => `总计 ${total} 条`
|
||||
}}
|
||||
loading={this.state.loading}
|
||||
/>
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChooseAsset;
|
@ -32,10 +32,10 @@ import {
|
||||
SyncOutlined,
|
||||
UndoOutlined
|
||||
} from '@ant-design/icons';
|
||||
import {compare} from "../../utils/utils";
|
||||
|
||||
import {hasPermission, isAdmin} from "../../service/permission";
|
||||
import dayjs from "dayjs";
|
||||
import ChooseAsset from "./ChooseAsset";
|
||||
|
||||
const confirm = Modal.confirm;
|
||||
const {Content} = Layout;
|
||||
@ -191,6 +191,12 @@ class DynamicCommand extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
setCheckedAssets = (checkedAssets) => {
|
||||
this.setState({
|
||||
checkedAssets: checkedAssets
|
||||
})
|
||||
}
|
||||
|
||||
executeCommand = e => {
|
||||
let checkedAssets = this.state.checkedAssets;
|
||||
if (checkedAssets.length === 0) {
|
||||
@ -198,18 +204,10 @@ class DynamicCommand extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
let assets = this.state.assets;
|
||||
let cAssets = checkedAssets.map(item => {
|
||||
let name = '';
|
||||
for (let i = 0; i < assets.length; i++) {
|
||||
if (assets[i]['id'] === item) {
|
||||
name = assets[i]['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: item,
|
||||
name: name
|
||||
id: item['id'],
|
||||
name: item['name']
|
||||
}
|
||||
});
|
||||
|
||||
@ -474,17 +472,6 @@ class DynamicCommand extends Component {
|
||||
assetsVisible: true,
|
||||
commandId: record['id']
|
||||
});
|
||||
|
||||
let result = await request.get('/assets?protocol=ssh');
|
||||
if (result.code === 1) {
|
||||
let assets = result.data;
|
||||
assets.sort(compare('name'));
|
||||
this.setState({
|
||||
assets: assets
|
||||
});
|
||||
} else {
|
||||
message.error(result.message);
|
||||
}
|
||||
}}>执行</Button>
|
||||
|
||||
<Dropdown overlay={menu}>
|
||||
@ -640,7 +627,8 @@ class DynamicCommand extends Component {
|
||||
<Modal
|
||||
title="选择资产"
|
||||
visible={this.state.assetsVisible}
|
||||
|
||||
width={window.innerWidth * 0.8}
|
||||
centered={true}
|
||||
onOk={this.executeCommand}
|
||||
onCancel={() => {
|
||||
this.setState({
|
||||
@ -648,19 +636,11 @@ class DynamicCommand extends Component {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Checkbox indeterminate={this.state.indeterminate} onChange={this.onCheckAllChange}
|
||||
checked={this.state.checkAllChecked}>
|
||||
全选
|
||||
</Checkbox>
|
||||
<Divider/>
|
||||
<ChooseAsset
|
||||
setCheckedAssets={this.setCheckedAssets}
|
||||
>
|
||||
|
||||
<CheckboxGroup options={this.state.assets.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
key: item.id,
|
||||
}
|
||||
})} value={this.state.checkedAssets} onChange={this.onChange}/>
|
||||
</ChooseAsset>
|
||||
</Modal>
|
||||
|
||||
|
||||
|
@ -21,6 +21,7 @@ const DynamicCommandModal = ({title, visible, handleOk, handleCancel, confirmLoa
|
||||
title={title}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
|
@ -47,6 +47,7 @@ const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading
|
||||
title={title}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
|
@ -47,6 +47,7 @@ const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model
|
||||
title={title}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Form, Input, InputNumber, Modal, Radio, Tooltip} from "antd/lib/index";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {Form, Input, InputNumber, Modal, Radio} from "antd/lib/index";
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {span: 6},
|
||||
@ -20,6 +19,7 @@ const SecurityModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
title={title}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
@ -54,9 +54,7 @@ const SecurityModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={<Tooltip
|
||||
title="数字越小代表优先级越高。">优先级
|
||||
<ExclamationCircleOutlined/></Tooltip>} name='priority' rules={[{required: true, message: '请输入优先级'}]}>
|
||||
<Form.Item label="优先级" name='priority' rules={[{required: true, message: '请输入优先级'}]}>
|
||||
<InputNumber min={0} max={100}/>
|
||||
</Form.Item>
|
||||
|
||||
|
@ -628,7 +628,6 @@ class User extends Component {
|
||||
width={window.innerWidth * 0.8}
|
||||
title='已授权资产'
|
||||
visible={this.state.assetVisible}
|
||||
|
||||
maskClosable={false}
|
||||
destroyOnClose={true}
|
||||
onOk={() => {
|
||||
@ -649,7 +648,6 @@ class User extends Component {
|
||||
<Modal title="修改密码" visible={this.state.changePasswordVisible}
|
||||
confirmLoading={this.state.changePasswordConfirmLoading}
|
||||
maskClosable={false}
|
||||
|
||||
onOk={() => {
|
||||
this.changePasswordFormRef.current
|
||||
.validateFields()
|
||||
|
@ -429,7 +429,6 @@ class UserGroup extends Component {
|
||||
title='已授权资产'
|
||||
visible={this.state.assetVisible}
|
||||
maskClosable={false}
|
||||
|
||||
destroyOnClose={true}
|
||||
onOk={() => {
|
||||
|
||||
|
Reference in New Issue
Block a user