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:
dushixiang
2021-04-17 20:04:10 +08:00
36 changed files with 1116 additions and 104 deletions

View File

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

View File

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

View File

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

View 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;

View File

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

View File

@ -21,6 +21,7 @@ const DynamicCommandModal = ({title, visible, handleOk, handleCancel, confirmLoa
title={title}
visible={visible}
maskClosable={false}
onOk={() => {
form
.validateFields()

View File

@ -47,6 +47,7 @@ const CredentialModal = ({title, visible, handleOk, handleCancel, confirmLoading
title={title}
visible={visible}
maskClosable={false}
onOk={() => {
form
.validateFields()

View File

@ -47,6 +47,7 @@ const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model
title={title}
visible={visible}
maskClosable={false}
onOk={() => {
form
.validateFields()

View File

@ -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="数字越小代表优先级越高。">优先级&nbsp;
<ExclamationCircleOutlined/></Tooltip>} name='priority' rules={[{required: true, message: ''}]}>
<Form.Item label="优先级" name='priority' rules={[{required: true, message: '请输入优先级'}]}>
<InputNumber min={0} max={100}/>
</Form.Item>

View File

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

View File

@ -429,7 +429,6 @@ class UserGroup extends Component {
title='已授权资产'
visible={this.state.assetVisible}
maskClosable={false}
destroyOnClose={true}
onOk={() => {