- 修复RDP协议连接导致的任意文件读取漏洞
- RDP协议增加「域」参数 - 增加安全访问功能 - 优化代码
This commit is contained in:
		
							
								
								
									
										12
									
								
								web/src/components/devops/Job.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/src/components/devops/Job.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
.cron-log {
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    border: 0 none;
 | 
			
		||||
    line-height: 23px;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    white-space: pre-wrap;
 | 
			
		||||
    height: 500px;
 | 
			
		||||
    background-color: rgb(51, 51, 51);
 | 
			
		||||
    color: #f1f1f1;
 | 
			
		||||
    border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										612
									
								
								web/src/components/devops/Job.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										612
									
								
								web/src/components/devops/Job.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,612 @@
 | 
			
		||||
import React, {Component} from 'react';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Col,
 | 
			
		||||
    Divider,
 | 
			
		||||
    Dropdown,
 | 
			
		||||
    Input,
 | 
			
		||||
    Layout,
 | 
			
		||||
    Menu,
 | 
			
		||||
    Modal,
 | 
			
		||||
    PageHeader,
 | 
			
		||||
    Row,
 | 
			
		||||
    Space,
 | 
			
		||||
    Spin,
 | 
			
		||||
    Switch,
 | 
			
		||||
    Table,
 | 
			
		||||
    Tag,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    Typography
 | 
			
		||||
} from "antd";
 | 
			
		||||
import qs from "qs";
 | 
			
		||||
import request from "../../common/request";
 | 
			
		||||
import {message} from "antd/es";
 | 
			
		||||
import {
 | 
			
		||||
    DeleteOutlined,
 | 
			
		||||
    DownOutlined,
 | 
			
		||||
    ExclamationCircleOutlined,
 | 
			
		||||
    PlusOutlined,
 | 
			
		||||
    SyncOutlined,
 | 
			
		||||
    UndoOutlined
 | 
			
		||||
} from '@ant-design/icons';
 | 
			
		||||
import {itemRender} from "../../utils/utils";
 | 
			
		||||
 | 
			
		||||
import dayjs from "dayjs";
 | 
			
		||||
import JobModal from "./JobModal";
 | 
			
		||||
import './Job.css'
 | 
			
		||||
 | 
			
		||||
const confirm = Modal.confirm;
 | 
			
		||||
const {Content} = Layout;
 | 
			
		||||
const {Title, Text} = Typography;
 | 
			
		||||
const {Search} = Input;
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        breadcrumbName: '首页',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'job',
 | 
			
		||||
        breadcrumbName: '计划任务',
 | 
			
		||||
    }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class Job extends Component {
 | 
			
		||||
 | 
			
		||||
    inputRefOfName = React.createRef();
 | 
			
		||||
 | 
			
		||||
    state = {
 | 
			
		||||
        items: [],
 | 
			
		||||
        total: 0,
 | 
			
		||||
        queryParams: {
 | 
			
		||||
            pageIndex: 1,
 | 
			
		||||
            pageSize: 10
 | 
			
		||||
        },
 | 
			
		||||
        loading: false,
 | 
			
		||||
        modalVisible: false,
 | 
			
		||||
        modalTitle: '',
 | 
			
		||||
        modalConfirmLoading: false,
 | 
			
		||||
        selectedRow: undefined,
 | 
			
		||||
        selectedRowKeys: [],
 | 
			
		||||
        logPending: false,
 | 
			
		||||
        logs: []
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        this.loadTableData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async delete(id) {
 | 
			
		||||
        const result = await request.delete('/jobs/' + id);
 | 
			
		||||
        if (result.code === 1) {
 | 
			
		||||
            message.success('删除成功');
 | 
			
		||||
            this.loadTableData(this.state.queryParams);
 | 
			
		||||
        } else {
 | 
			
		||||
            message.error('删除失败 :( ' + result.message, 10);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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('/jobs/paging?' + paramsStr);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                data = result.data;
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error(result.message);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            const items = data.items.map(item => {
 | 
			
		||||
                return {'key': item['id'], ...item}
 | 
			
		||||
            })
 | 
			
		||||
            this.setState({
 | 
			
		||||
                items: items,
 | 
			
		||||
                total: data.total,
 | 
			
		||||
                queryParams: queryParams,
 | 
			
		||||
                loading: false
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    showDeleteConfirm(id, content) {
 | 
			
		||||
        let self = this;
 | 
			
		||||
        confirm({
 | 
			
		||||
            title: '您确定要删除此任务吗?',
 | 
			
		||||
            content: content,
 | 
			
		||||
            okText: '确定',
 | 
			
		||||
            okType: 'danger',
 | 
			
		||||
            cancelText: '取消',
 | 
			
		||||
            onOk() {
 | 
			
		||||
                self.delete(id);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    showModal(title, obj = null) {
 | 
			
		||||
        if (obj['func'] === 'shell-job') {
 | 
			
		||||
            obj['shell'] = JSON.parse(obj['metadata'])['shell'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (obj['mode'] === 'custom') {
 | 
			
		||||
            obj['resourceIds'] = obj['resourceIds'].split(',');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalTitle: title,
 | 
			
		||||
            modalVisible: true,
 | 
			
		||||
            model: obj
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleCancelModal = e => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalTitle: '',
 | 
			
		||||
            modalVisible: false
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleOk = async (formData) => {
 | 
			
		||||
        // 弹窗 form 传来的数据
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalConfirmLoading: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        console.log(formData)
 | 
			
		||||
        if (formData['func'] === 'shell-job') {
 | 
			
		||||
            console.log(formData['shell'], JSON.stringify({'shell': formData['shell']}))
 | 
			
		||||
 | 
			
		||||
            formData['metadata'] = JSON.stringify({'shell': formData['shell']});
 | 
			
		||||
            formData['shell'] = undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (formData['mode'] === 'custom') {
 | 
			
		||||
            let resourceIds = formData['resourceIds'];
 | 
			
		||||
            formData['resourceIds'] = resourceIds.join(',');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (formData.id) {
 | 
			
		||||
            // 向后台提交数据
 | 
			
		||||
            const result = await request.put('/jobs/' + formData.id, formData);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('更新成功');
 | 
			
		||||
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    modalVisible: false
 | 
			
		||||
                });
 | 
			
		||||
                this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('更新失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // 向后台提交数据
 | 
			
		||||
            const result = await request.post('/jobs', formData);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('新增成功');
 | 
			
		||||
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    modalVisible: false
 | 
			
		||||
                });
 | 
			
		||||
                this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('新增失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalConfirmLoading: false
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    batchDelete = async () => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            delBtnLoading: true
 | 
			
		||||
        })
 | 
			
		||||
        try {
 | 
			
		||||
            let result = await request.delete('/jobs/' + this.state.selectedRowKeys.join(','));
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('操作成功', 3);
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    selectedRowKeys: []
 | 
			
		||||
                })
 | 
			
		||||
                await this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('删除失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                delBtnLoading: false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleTableChange = (pagination, filters, sorter) => {
 | 
			
		||||
        let query = {
 | 
			
		||||
            ...this.state.queryParams,
 | 
			
		||||
            'order': sorter.order,
 | 
			
		||||
            'field': sorter.field
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadTableData(query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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>
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
            sorter: true,
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '状态',
 | 
			
		||||
            dataIndex: 'status',
 | 
			
		||||
            key: 'status',
 | 
			
		||||
            render: (status, record) => {
 | 
			
		||||
                return <Switch checkedChildren="开启" unCheckedChildren="关闭" checked={status === 'running'}
 | 
			
		||||
                               onChange={async (checked) => {
 | 
			
		||||
                                   let jobStatus = checked ? 'running' : 'not-running';
 | 
			
		||||
                                   let result = await request.post(`/jobs/${record['id']}/change-status?status=${jobStatus}`);
 | 
			
		||||
                                   if (result['code'] === 1) {
 | 
			
		||||
                                       message.success('操作成功');
 | 
			
		||||
                                       await this.loadTableData();
 | 
			
		||||
                                   } else {
 | 
			
		||||
                                       message.error(result['message']);
 | 
			
		||||
                                   }
 | 
			
		||||
                               }}
 | 
			
		||||
                />
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '任务类型',
 | 
			
		||||
            dataIndex: 'func',
 | 
			
		||||
            key: 'func',
 | 
			
		||||
            render: (func, record) => {
 | 
			
		||||
                switch (func) {
 | 
			
		||||
                    case "check-asset-status-job":
 | 
			
		||||
                        return <Tag color="green">资产状态检测</Tag>;
 | 
			
		||||
                    case "shell-job":
 | 
			
		||||
                        return <Tag color="volcano">Shell脚本</Tag>;
 | 
			
		||||
                    default:
 | 
			
		||||
                        return '';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: 'cron表达式',
 | 
			
		||||
            dataIndex: 'cron',
 | 
			
		||||
            key: 'cron'
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '创建日期',
 | 
			
		||||
            dataIndex: 'created',
 | 
			
		||||
            key: 'created',
 | 
			
		||||
            render: (text, record) => {
 | 
			
		||||
                return (
 | 
			
		||||
                    <Tooltip title={text}>
 | 
			
		||||
                        {dayjs(text).fromNow()}
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            sorter: true,
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '最后执行日期',
 | 
			
		||||
            dataIndex: 'updated',
 | 
			
		||||
            key: 'updated',
 | 
			
		||||
            render: (text, record) => {
 | 
			
		||||
                if (text === '0001-01-01 00:00:00') {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
                return (
 | 
			
		||||
                    <Tooltip title={text}>
 | 
			
		||||
                        {dayjs(text).fromNow()}
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            sorter: true,
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '操作',
 | 
			
		||||
            key: 'action',
 | 
			
		||||
            render: (text, record, index) => {
 | 
			
		||||
 | 
			
		||||
                const menu = (
 | 
			
		||||
                    <Menu>
 | 
			
		||||
                        <Menu.Item key="0">
 | 
			
		||||
                            <Button type="text" size='small'
 | 
			
		||||
                                    onClick={() => this.showModal('更新计划任务', record)}>编辑</Button>
 | 
			
		||||
                        </Menu.Item>
 | 
			
		||||
 | 
			
		||||
                        <Menu.Item key="2">
 | 
			
		||||
                            <Button type="text" size='small'
 | 
			
		||||
                                    onClick={async () => {
 | 
			
		||||
                                        this.setState({
 | 
			
		||||
                                            logVisible: true,
 | 
			
		||||
                                            logPending: true
 | 
			
		||||
                                        })
 | 
			
		||||
 | 
			
		||||
                                        let result = await request.get(`/jobs/${record['id']}/logs`);
 | 
			
		||||
                                        if (result['code'] === 1) {
 | 
			
		||||
                                            this.setState({
 | 
			
		||||
                                                logPending: false,
 | 
			
		||||
                                                logs: result['data'],
 | 
			
		||||
                                                selectedRow: record
 | 
			
		||||
                                            })
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                    }}>日志</Button>
 | 
			
		||||
                        </Menu.Item>
 | 
			
		||||
 | 
			
		||||
                        <Menu.Divider/>
 | 
			
		||||
                        <Menu.Item key="3">
 | 
			
		||||
                            <Button type="text" size='small' danger
 | 
			
		||||
                                    onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
 | 
			
		||||
                        </Menu.Item>
 | 
			
		||||
                    </Menu>
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <Button type="link" size='small' loading={this.state.items[index]['execLoading']}
 | 
			
		||||
                                onClick={async () => {
 | 
			
		||||
                                    let items = this.state.items;
 | 
			
		||||
                                    items[index]['execLoading'] = true;
 | 
			
		||||
                                    this.setState({
 | 
			
		||||
                                        items: items
 | 
			
		||||
                                    });
 | 
			
		||||
 | 
			
		||||
                                    let result = await request.post(`/jobs/${record['id']}/exec`);
 | 
			
		||||
                                    if (result['code'] === 1) {
 | 
			
		||||
                                        message.success('执行成功');
 | 
			
		||||
                                        await this.loadTableData();
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        message.error(result['message']);
 | 
			
		||||
                                        items[index]['execLoading'] = false;
 | 
			
		||||
                                        this.setState({
 | 
			
		||||
                                            items: items
 | 
			
		||||
                                        });
 | 
			
		||||
                                    }
 | 
			
		||||
                                }}>执行</Button>
 | 
			
		||||
 | 
			
		||||
                        <Dropdown overlay={menu}>
 | 
			
		||||
                            <Button type="link" size='small'>
 | 
			
		||||
                                更多 <DownOutlined/>
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        </Dropdown>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const selectedRowKeys = this.state.selectedRowKeys;
 | 
			
		||||
        const rowSelection = {
 | 
			
		||||
            selectedRowKeys: this.state.selectedRowKeys,
 | 
			
		||||
            onChange: (selectedRowKeys, selectedRows) => {
 | 
			
		||||
                this.setState({selectedRowKeys});
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        const hasSelected = selectedRowKeys.length > 0;
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <PageHeader
 | 
			
		||||
                    className="site-page-header-ghost-wrapper"
 | 
			
		||||
                    title="计划任务"
 | 
			
		||||
                    breadcrumb={{
 | 
			
		||||
                        routes: routes,
 | 
			
		||||
                        itemRender: itemRender
 | 
			
		||||
                    }}
 | 
			
		||||
 | 
			
		||||
                    subTitle="计划任务"
 | 
			
		||||
                >
 | 
			
		||||
                </PageHeader>
 | 
			
		||||
 | 
			
		||||
                <Content className="site-layout-background page-content">
 | 
			
		||||
 | 
			
		||||
                    <div style={{marginBottom: 20}}>
 | 
			
		||||
                        <Row justify="space-around" align="middle" gutter={24}>
 | 
			
		||||
                            <Col span={12} key={1}>
 | 
			
		||||
                                <Title level={3}>任务列表</Title>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                            <Col span={12} key={2} style={{textAlign: 'right'}}>
 | 
			
		||||
                                <Space>
 | 
			
		||||
                                    <Search
 | 
			
		||||
                                        ref={this.inputRefOfName}
 | 
			
		||||
                                        placeholder="任务名称"
 | 
			
		||||
                                        allowClear
 | 
			
		||||
                                        onSearch={this.handleSearchByName}
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title='重置查询'>
 | 
			
		||||
 | 
			
		||||
                                        <Button icon={<UndoOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.inputRefOfName.current.setValue('');
 | 
			
		||||
                                            this.loadTableData({pageIndex: 1, pageSize: 10, name: '', content: ''})
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                    <Divider type="vertical"/>
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="新增">
 | 
			
		||||
                                        <Button type="dashed" icon={<PlusOutlined/>}
 | 
			
		||||
                                                onClick={() => this.showModal('新增计划任务', {})}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="刷新列表">
 | 
			
		||||
                                        <Button icon={<SyncOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.loadTableData(this.state.queryParams)
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="批量删除">
 | 
			
		||||
                                        <Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
 | 
			
		||||
                                                loading={this.state.delBtnLoading}
 | 
			
		||||
                                                onClick={() => {
 | 
			
		||||
                                                    const content = <div>
 | 
			
		||||
                                                        您确定要删除选中的<Text style={{color: '#1890FF'}}
 | 
			
		||||
                                                                       strong>{this.state.selectedRowKeys.length}</Text>条记录吗?
 | 
			
		||||
                                                    </div>;
 | 
			
		||||
                                                    confirm({
 | 
			
		||||
                                                        icon: <ExclamationCircleOutlined/>,
 | 
			
		||||
                                                        content: content,
 | 
			
		||||
                                                        onOk: () => {
 | 
			
		||||
                                                            this.batchDelete()
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        onCancel() {
 | 
			
		||||
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                    });
 | 
			
		||||
                                                }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                </Space>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                        </Row>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <Table
 | 
			
		||||
                        rowSelection={rowSelection}
 | 
			
		||||
                        dataSource={this.state.items}
 | 
			
		||||
                        columns={columns}
 | 
			
		||||
                        position={'both'}
 | 
			
		||||
                        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}
 | 
			
		||||
                        onChange={this.handleTableChange}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    {
 | 
			
		||||
                        this.state.modalVisible ?
 | 
			
		||||
                            <JobModal
 | 
			
		||||
                                visible={this.state.modalVisible}
 | 
			
		||||
                                title={this.state.modalTitle}
 | 
			
		||||
                                handleOk={this.handleOk}
 | 
			
		||||
                                handleCancel={this.handleCancelModal}
 | 
			
		||||
                                confirmLoading={this.state.modalConfirmLoading}
 | 
			
		||||
                                model={this.state.model}
 | 
			
		||||
                            >
 | 
			
		||||
                            </JobModal> : undefined
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    {
 | 
			
		||||
                        this.state.logVisible ?
 | 
			
		||||
                            <Modal
 | 
			
		||||
                                className='modal-no-padding'
 | 
			
		||||
                                width={window.innerWidth * 0.8}
 | 
			
		||||
                                title={'日志'}
 | 
			
		||||
                                visible={true}
 | 
			
		||||
                                maskClosable={false}
 | 
			
		||||
                                centered={true}
 | 
			
		||||
                                onOk={async () => {
 | 
			
		||||
                                    let result = await request.delete(`/jobs/${this.state.selectedRow['id']}/logs`);
 | 
			
		||||
                                    if (result['code'] === 1) {
 | 
			
		||||
                                        this.setState({
 | 
			
		||||
                                            logVisible: false,
 | 
			
		||||
                                            selectedRow: undefined
 | 
			
		||||
                                        })
 | 
			
		||||
                                        message.success('日志清空成功');
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        message.error(result['message'], 10);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }}
 | 
			
		||||
                                onCancel={() => {
 | 
			
		||||
                                    this.setState({
 | 
			
		||||
                                        logVisible: false,
 | 
			
		||||
                                        selectedRow: undefined
 | 
			
		||||
                                    })
 | 
			
		||||
                                }}
 | 
			
		||||
                                okText='清空'
 | 
			
		||||
                                okType={'danger'}
 | 
			
		||||
                                cancelText='取消'
 | 
			
		||||
                            >
 | 
			
		||||
                                <Spin tip='加载中...' spinning={this.state.logPending}>
 | 
			
		||||
                                    <pre className='cron-log'>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            this.state.logs.map(item => {
 | 
			
		||||
 | 
			
		||||
                                                return <><Divider
 | 
			
		||||
                                                    orientation="left"
 | 
			
		||||
                                                    style={{color: 'white'}}>{item['timestamp']}</Divider>{item['message']}</>;
 | 
			
		||||
                                            })
 | 
			
		||||
                                        }
 | 
			
		||||
                                    </pre>
 | 
			
		||||
                                </Spin>
 | 
			
		||||
                            </Modal> : undefined
 | 
			
		||||
                    }
 | 
			
		||||
                </Content>
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Job;
 | 
			
		||||
							
								
								
									
										129
									
								
								web/src/components/devops/JobModal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								web/src/components/devops/JobModal.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,129 @@
 | 
			
		||||
import React, {useEffect, useState} from 'react';
 | 
			
		||||
import {Form, Input, Modal, Radio, Select, Spin} from "antd/lib/index";
 | 
			
		||||
import TextArea from "antd/es/input/TextArea";
 | 
			
		||||
import request from "../../common/request";
 | 
			
		||||
import {message} from "antd";
 | 
			
		||||
 | 
			
		||||
const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
 | 
			
		||||
 | 
			
		||||
    const [form] = Form.useForm();
 | 
			
		||||
    if (model.func === undefined) {
 | 
			
		||||
        model.func = 'shell-job';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (model.mode === undefined) {
 | 
			
		||||
        model.mode = 'all';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let [func, setFunc] = useState(model.func);
 | 
			
		||||
    let [mode, setMode] = useState(model.mode);
 | 
			
		||||
    let [resources, setResources] = useState([]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const fetchData = async () => {
 | 
			
		||||
            setResourcesLoading(true);
 | 
			
		||||
            let result = await request.get('/assets?protocol=ssh');
 | 
			
		||||
            if (result['code'] === 1) {
 | 
			
		||||
                setResources(result['data']);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error(result['message'], 10);
 | 
			
		||||
            }
 | 
			
		||||
            setResourcesLoading(false);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        fetchData();
 | 
			
		||||
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    let [resourcesLoading, setResourcesLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const formItemLayout = {
 | 
			
		||||
        labelCol: {span: 6},
 | 
			
		||||
        wrapperCol: {span: 14},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            title={title}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            maskClosable={false}
 | 
			
		||||
            centered={true}
 | 
			
		||||
            onOk={() => {
 | 
			
		||||
                form
 | 
			
		||||
                    .validateFields()
 | 
			
		||||
                    .then(values => {
 | 
			
		||||
                        form.resetFields();
 | 
			
		||||
                        handleOk(values);
 | 
			
		||||
                    })
 | 
			
		||||
                    .catch(info => {
 | 
			
		||||
                    });
 | 
			
		||||
            }}
 | 
			
		||||
            onCancel={handleCancel}
 | 
			
		||||
            confirmLoading={confirmLoading}
 | 
			
		||||
            okText='确定'
 | 
			
		||||
            cancelText='取消'
 | 
			
		||||
        >
 | 
			
		||||
 | 
			
		||||
            <Form form={form} {...formItemLayout} initialValues={model}>
 | 
			
		||||
                <Form.Item name='id' noStyle>
 | 
			
		||||
                    <Input hidden={true}/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="任务类型" name='func' rules={[{required: true, message: '请选择任务类型'}]}>
 | 
			
		||||
                    <Select onChange={(value) => {
 | 
			
		||||
                        setFunc(value);
 | 
			
		||||
                    }}>
 | 
			
		||||
                        <Select.Option value="shell-job">Shell脚本</Select.Option>
 | 
			
		||||
                        <Select.Option value="check-asset-status-job">资产状态检测</Select.Option>
 | 
			
		||||
                    </Select>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="任务名称" name='name' rules={[{required: true, message: '请输入任务名称'}]}>
 | 
			
		||||
                    <Input autoComplete="off" placeholder="请输入任务名称"/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                {
 | 
			
		||||
                    func === 'shell-job' ?
 | 
			
		||||
                        <Form.Item label="Shell脚本" name='shell' rules={[{required: true, message: '请输入Shell脚本'}]}>
 | 
			
		||||
                            <TextArea autoSize={{minRows: 5, maxRows: 10}} placeholder="在此处填写Shell脚本内容"/>
 | 
			
		||||
                        </Form.Item> : undefined
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="cron表达式" name='cron' rules={[{required: true, message: '请输入cron表达式'}]}>
 | 
			
		||||
                    <Input placeholder="请输入cron表达式"/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="资产选择" name='mode' rules={[{required: true, message: '请选择资产'}]}>
 | 
			
		||||
                    <Radio.Group onChange={async (e) => {
 | 
			
		||||
                        setMode(e.target.value);
 | 
			
		||||
                    }}>
 | 
			
		||||
                        <Radio value={'all'}>全部资产</Radio>
 | 
			
		||||
                        <Radio value={'custom'}>自定义</Radio>
 | 
			
		||||
                    </Radio.Group>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                {
 | 
			
		||||
                    mode === 'custom' ?
 | 
			
		||||
                        <Spin tip='加载中...' spinning={resourcesLoading}>
 | 
			
		||||
                            <Form.Item label="已选择资产" name='resourceIds' rules={[{required: true}]}>
 | 
			
		||||
                                <Select
 | 
			
		||||
                                    mode="multiple"
 | 
			
		||||
                                    allowClear
 | 
			
		||||
                                    placeholder="请选择资产"
 | 
			
		||||
                                >
 | 
			
		||||
                                    {
 | 
			
		||||
                                        resources.map(item => {
 | 
			
		||||
                                            return <Select.Option key={item['id']}>{item['name']}</Select.Option>
 | 
			
		||||
                                        })
 | 
			
		||||
                                    }
 | 
			
		||||
                                </Select>
 | 
			
		||||
                            </Form.Item>
 | 
			
		||||
                        </Spin>
 | 
			
		||||
                        : undefined
 | 
			
		||||
                }
 | 
			
		||||
            </Form>
 | 
			
		||||
        </Modal>
 | 
			
		||||
    )
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default JobModal;
 | 
			
		||||
							
								
								
									
										398
									
								
								web/src/components/devops/LoginLog.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								web/src/components/devops/LoginLog.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,398 @@
 | 
			
		||||
import React, {Component} from 'react';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Col,
 | 
			
		||||
    Divider,
 | 
			
		||||
    Input,
 | 
			
		||||
    Layout,
 | 
			
		||||
    Modal,
 | 
			
		||||
    notification,
 | 
			
		||||
    PageHeader,
 | 
			
		||||
    Row,
 | 
			
		||||
    Select,
 | 
			
		||||
    Space,
 | 
			
		||||
    Table,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    Typography
 | 
			
		||||
} from "antd";
 | 
			
		||||
import qs from "qs";
 | 
			
		||||
import request from "../../common/request";
 | 
			
		||||
import {formatDate, isEmpty, itemRender} from "../../utils/utils";
 | 
			
		||||
import {message} from "antd/es";
 | 
			
		||||
import {DeleteOutlined, ExclamationCircleOutlined, SyncOutlined, UndoOutlined} from "@ant-design/icons";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const confirm = Modal.confirm;
 | 
			
		||||
const {Content} = Layout;
 | 
			
		||||
const {Search} = Input;
 | 
			
		||||
const {Title, Text} = Typography;
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        breadcrumbName: '首页',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'loginLog',
 | 
			
		||||
        breadcrumbName: '登录日志',
 | 
			
		||||
    }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class LoginLog extends Component {
 | 
			
		||||
 | 
			
		||||
    inputRefOfClientIp = React.createRef();
 | 
			
		||||
 | 
			
		||||
    state = {
 | 
			
		||||
        items: [],
 | 
			
		||||
        total: 0,
 | 
			
		||||
        queryParams: {
 | 
			
		||||
            pageIndex: 1,
 | 
			
		||||
            pageSize: 10,
 | 
			
		||||
            userId: undefined,
 | 
			
		||||
        },
 | 
			
		||||
        loading: false,
 | 
			
		||||
        selectedRowKeys: [],
 | 
			
		||||
        delBtnLoading: false,
 | 
			
		||||
        users: [],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        this.loadTableData();
 | 
			
		||||
        this.handleSearchByNickname('');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async loadTableData(queryParams) {
 | 
			
		||||
        queryParams = queryParams || this.state.queryParams;
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            queryParams: queryParams,
 | 
			
		||||
            loading: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // queryParams
 | 
			
		||||
        let paramsStr = qs.stringify(queryParams);
 | 
			
		||||
 | 
			
		||||
        let data = {
 | 
			
		||||
            items: [],
 | 
			
		||||
            total: 0
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            let result = await request.get('/login-logs/paging?' + paramsStr);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                data = result.data;
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error(result.message);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            const items = data.items.map(item => {
 | 
			
		||||
                return {'key': item['id'], ...item}
 | 
			
		||||
            })
 | 
			
		||||
            this.setState({
 | 
			
		||||
                items: items,
 | 
			
		||||
                total: data.total,
 | 
			
		||||
                queryParams: queryParams,
 | 
			
		||||
                loading: false
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleChangPage = (pageIndex, pageSize) => {
 | 
			
		||||
        let queryParams = this.state.queryParams;
 | 
			
		||||
        queryParams.pageIndex = pageIndex;
 | 
			
		||||
        queryParams.pageSize = pageSize;
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            queryParams: queryParams
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.loadTableData(queryParams)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleSearchByClientIp = clientIp => {
 | 
			
		||||
        let query = {
 | 
			
		||||
            ...this.state.queryParams,
 | 
			
		||||
            'pageIndex': 1,
 | 
			
		||||
            'pageSize': this.state.queryParams.pageSize,
 | 
			
		||||
            'clientIp': clientIp,
 | 
			
		||||
        }
 | 
			
		||||
        this.loadTableData(query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleChangeByProtocol = protocol => {
 | 
			
		||||
        let query = {
 | 
			
		||||
            ...this.state.queryParams,
 | 
			
		||||
            'pageIndex': 1,
 | 
			
		||||
            'pageSize': this.state.queryParams.pageSize,
 | 
			
		||||
            'protocol': protocol,
 | 
			
		||||
        }
 | 
			
		||||
        this.loadTableData(query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleSearchByNickname = async nickname => {
 | 
			
		||||
        const result = await request.get(`/users/paging?pageIndex=1&pageSize=1000&nickname=${nickname}`);
 | 
			
		||||
        if (result.code !== 1) {
 | 
			
		||||
            message.error(result.message, 10);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            users: result.data.items
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleChangeByUserId = userId => {
 | 
			
		||||
        let query = {
 | 
			
		||||
            ...this.state.queryParams,
 | 
			
		||||
            'pageIndex': 1,
 | 
			
		||||
            'pageSize': this.state.queryParams.pageSize,
 | 
			
		||||
            'userId': userId,
 | 
			
		||||
        }
 | 
			
		||||
        this.loadTableData(query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    batchDelete = async () => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            delBtnLoading: true
 | 
			
		||||
        })
 | 
			
		||||
        try {
 | 
			
		||||
            let result = await request.delete('/login-logs/' + this.state.selectedRowKeys.join(','));
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('操作成功', 3);
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    selectedRowKeys: []
 | 
			
		||||
                })
 | 
			
		||||
                await this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('删除失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                delBtnLoading: false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
 | 
			
		||||
        const columns = [{
 | 
			
		||||
            title: '序号',
 | 
			
		||||
            dataIndex: 'id',
 | 
			
		||||
            key: 'id',
 | 
			
		||||
            render: (id, record, index) => {
 | 
			
		||||
                return index + 1;
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '用户昵称',
 | 
			
		||||
            dataIndex: 'userName',
 | 
			
		||||
            key: 'userName'
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '来源IP',
 | 
			
		||||
            dataIndex: 'clientIp',
 | 
			
		||||
            key: 'clientIp'
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '浏览器',
 | 
			
		||||
            dataIndex: 'clientUserAgent',
 | 
			
		||||
            key: 'clientUserAgent',
 | 
			
		||||
            render: (text, record) => {
 | 
			
		||||
                if (isEmpty(text)) {
 | 
			
		||||
                    return '未知';
 | 
			
		||||
                }
 | 
			
		||||
                return (
 | 
			
		||||
                    <Tooltip placement="topLeft" title={text}>
 | 
			
		||||
                        {text.split(' ')[0]}
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '登录时间',
 | 
			
		||||
            dataIndex: 'loginTime',
 | 
			
		||||
            key: 'loginTime',
 | 
			
		||||
            render: (text, record) => {
 | 
			
		||||
 | 
			
		||||
                return formatDate(text, 'yyyy-MM-dd hh:mm:ss');
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '注销时间',
 | 
			
		||||
            dataIndex: 'logoutTime',
 | 
			
		||||
            key: 'logoutTime',
 | 
			
		||||
            render: (text, record) => {
 | 
			
		||||
                if (isEmpty(text) || text === '0001-01-01 00:00:00') {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
                return text;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
            {
 | 
			
		||||
                title: '操作',
 | 
			
		||||
                key: 'action',
 | 
			
		||||
                render: (text, record) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <Button type="link" size='small' onClick={() => {
 | 
			
		||||
                                confirm({
 | 
			
		||||
                                    title: '您确定要删除此条登录日志吗?',
 | 
			
		||||
                                    content: '删除用户未注销的登录日志将会强制用户下线',
 | 
			
		||||
                                    okText: '确定',
 | 
			
		||||
                                    okType: 'danger',
 | 
			
		||||
                                    cancelText: '取消',
 | 
			
		||||
                                    onOk() {
 | 
			
		||||
                                        del(record.id)
 | 
			
		||||
                                    }
 | 
			
		||||
                                });
 | 
			
		||||
 | 
			
		||||
                                const del = async (id) => {
 | 
			
		||||
                                    const result = await request.delete(`/login-logs/${id}`);
 | 
			
		||||
                                    if (result.code === 1) {
 | 
			
		||||
                                        notification['success']({
 | 
			
		||||
                                            message: '提示',
 | 
			
		||||
                                            description: '删除成功',
 | 
			
		||||
                                        });
 | 
			
		||||
                                        this.loadTableData();
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        notification['error']({
 | 
			
		||||
                                            message: '提示',
 | 
			
		||||
                                            description: '删除失败 :( ' + result.message,
 | 
			
		||||
                                        });
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                }
 | 
			
		||||
                            }}>删除</Button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const selectedRowKeys = this.state.selectedRowKeys;
 | 
			
		||||
        const rowSelection = {
 | 
			
		||||
            selectedRowKeys: this.state.selectedRowKeys,
 | 
			
		||||
            onChange: (selectedRowKeys, selectedRows) => {
 | 
			
		||||
                this.setState({selectedRowKeys});
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        const hasSelected = selectedRowKeys.length > 0;
 | 
			
		||||
 | 
			
		||||
        const userOptions = this.state.users.map(d => <Select.Option key={d.id}
 | 
			
		||||
                                                                     value={d.id}>{d.nickname}</Select.Option>);
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <PageHeader
 | 
			
		||||
                    className="site-page-header-ghost-wrapper"
 | 
			
		||||
                    title="登录日志"
 | 
			
		||||
                    breadcrumb={{
 | 
			
		||||
                        routes: routes,
 | 
			
		||||
                        itemRender: itemRender
 | 
			
		||||
                    }}
 | 
			
		||||
 | 
			
		||||
                    subTitle="只有登录成功的才会保存日志"
 | 
			
		||||
                >
 | 
			
		||||
                </PageHeader>
 | 
			
		||||
 | 
			
		||||
                <Content className="site-layout-background page-content">
 | 
			
		||||
                    <div style={{marginBottom: 20}}>
 | 
			
		||||
                        <Row justify="space-around" align="middle" gutter={24}>
 | 
			
		||||
                            <Col span={8} key={1}>
 | 
			
		||||
                                <Title level={3}>登录日志列表</Title>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                            <Col span={16} key={2} style={{textAlign: 'right'}}>
 | 
			
		||||
                                <Space>
 | 
			
		||||
 | 
			
		||||
                                    <Search
 | 
			
		||||
                                        ref={this.inputRefOfClientIp}
 | 
			
		||||
                                        placeholder="来源IP"
 | 
			
		||||
                                        allowClear
 | 
			
		||||
                                        onSearch={this.handleSearchByClientIp}
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
                                    <Select
 | 
			
		||||
                                        style={{width: 150}}
 | 
			
		||||
                                        showSearch
 | 
			
		||||
                                        value={this.state.queryParams.userId}
 | 
			
		||||
                                        placeholder='用户昵称'
 | 
			
		||||
                                        onSearch={this.handleSearchByNickname}
 | 
			
		||||
                                        onChange={this.handleChangeByUserId}
 | 
			
		||||
                                        filterOption={false}
 | 
			
		||||
                                        allowClear
 | 
			
		||||
                                    >
 | 
			
		||||
                                        {userOptions}
 | 
			
		||||
                                    </Select>
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title='重置查询'>
 | 
			
		||||
 | 
			
		||||
                                        <Button icon={<UndoOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.inputRefOfClientIp.current.setValue('');
 | 
			
		||||
                                            this.loadTableData({
 | 
			
		||||
                                                pageIndex: 1,
 | 
			
		||||
                                                pageSize: 10,
 | 
			
		||||
                                                protocol: '',
 | 
			
		||||
                                                userId: undefined,
 | 
			
		||||
                                                assetId: undefined
 | 
			
		||||
                                            })
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                    <Divider type="vertical"/>
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="刷新列表">
 | 
			
		||||
                                        <Button icon={<SyncOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.loadTableData(this.state.queryParams)
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="批量删除">
 | 
			
		||||
                                        <Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
 | 
			
		||||
                                                loading={this.state.delBtnLoading}
 | 
			
		||||
                                                onClick={() => {
 | 
			
		||||
                                                    const content = <div>
 | 
			
		||||
                                                        您确定要删除选中的<Text style={{color: '#1890FF'}}
 | 
			
		||||
                                                                       strong>{this.state.selectedRowKeys.length}</Text>条记录吗?
 | 
			
		||||
                                                    </div>;
 | 
			
		||||
                                                    confirm({
 | 
			
		||||
                                                        icon: <ExclamationCircleOutlined/>,
 | 
			
		||||
                                                        content: content,
 | 
			
		||||
                                                        onOk: () => {
 | 
			
		||||
                                                            this.batchDelete()
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        onCancel() {
 | 
			
		||||
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                    });
 | 
			
		||||
                                                }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                </Space>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                        </Row>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <Table rowSelection={rowSelection}
 | 
			
		||||
                           dataSource={this.state.items}
 | 
			
		||||
                           columns={columns}
 | 
			
		||||
                           position={'both'}
 | 
			
		||||
                           pagination={{
 | 
			
		||||
                               showSizeChanger: true,
 | 
			
		||||
                               current: this.state.queryParams.pageIndex,
 | 
			
		||||
                               pageSize: this.state.queryParams.pageSize,
 | 
			
		||||
                               onChange: this.handleChangPage,
 | 
			
		||||
                               total: this.state.total,
 | 
			
		||||
                               showTotal: total => `总计 ${total} 条`
 | 
			
		||||
                           }}
 | 
			
		||||
                           loading={this.state.loading}
 | 
			
		||||
                    />
 | 
			
		||||
                </Content>
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default LoginLog;
 | 
			
		||||
							
								
								
									
										421
									
								
								web/src/components/devops/Security.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								web/src/components/devops/Security.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,421 @@
 | 
			
		||||
import React, {Component} from 'react';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Col,
 | 
			
		||||
    Divider,
 | 
			
		||||
    Input,
 | 
			
		||||
    Layout,
 | 
			
		||||
    Modal,
 | 
			
		||||
    PageHeader,
 | 
			
		||||
    Row,
 | 
			
		||||
    Space,
 | 
			
		||||
    Table,
 | 
			
		||||
    Tag,
 | 
			
		||||
    Tooltip,
 | 
			
		||||
    Typography
 | 
			
		||||
} from "antd";
 | 
			
		||||
import qs from "qs";
 | 
			
		||||
import request from "../../common/request";
 | 
			
		||||
import {message} from "antd/es";
 | 
			
		||||
import {DeleteOutlined, ExclamationCircleOutlined, PlusOutlined, SyncOutlined, UndoOutlined} from '@ant-design/icons';
 | 
			
		||||
import {itemRender} from "../../utils/utils";
 | 
			
		||||
import './Job.css'
 | 
			
		||||
import SecurityModal from "./SecurityModal";
 | 
			
		||||
 | 
			
		||||
const confirm = Modal.confirm;
 | 
			
		||||
const {Content} = Layout;
 | 
			
		||||
const {Title, Text} = Typography;
 | 
			
		||||
const {Search} = Input;
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        breadcrumbName: '首页',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'security',
 | 
			
		||||
        breadcrumbName: '访问安全',
 | 
			
		||||
    }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class Security extends Component {
 | 
			
		||||
 | 
			
		||||
    inputRefOfName = React.createRef();
 | 
			
		||||
 | 
			
		||||
    state = {
 | 
			
		||||
        items: [],
 | 
			
		||||
        total: 0,
 | 
			
		||||
        queryParams: {
 | 
			
		||||
            pageIndex: 1,
 | 
			
		||||
            pageSize: 10
 | 
			
		||||
        },
 | 
			
		||||
        loading: false,
 | 
			
		||||
        modalVisible: false,
 | 
			
		||||
        modalTitle: '',
 | 
			
		||||
        modalConfirmLoading: false,
 | 
			
		||||
        selectedRow: undefined,
 | 
			
		||||
        selectedRowKeys: [],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        this.loadTableData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async delete(id) {
 | 
			
		||||
        const result = await request.delete('/securities/' + id);
 | 
			
		||||
        if (result.code === 1) {
 | 
			
		||||
            message.success('删除成功');
 | 
			
		||||
            this.loadTableData(this.state.queryParams);
 | 
			
		||||
        } else {
 | 
			
		||||
            message.error('删除失败 :( ' + result.message, 10);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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('/securities/paging?' + paramsStr);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                data = result.data;
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error(result.message);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            const items = data.items.map(item => {
 | 
			
		||||
                return {'key': item['id'], ...item}
 | 
			
		||||
            })
 | 
			
		||||
            this.setState({
 | 
			
		||||
                items: items,
 | 
			
		||||
                total: data.total,
 | 
			
		||||
                queryParams: queryParams,
 | 
			
		||||
                loading: false
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    showDeleteConfirm(id, content) {
 | 
			
		||||
        let self = this;
 | 
			
		||||
        confirm({
 | 
			
		||||
            title: '您确定要删除此任务吗?',
 | 
			
		||||
            content: content,
 | 
			
		||||
            okText: '确定',
 | 
			
		||||
            okType: 'danger',
 | 
			
		||||
            cancelText: '取消',
 | 
			
		||||
            onOk() {
 | 
			
		||||
                self.delete(id);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    showModal(title, obj = null) {
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalTitle: title,
 | 
			
		||||
            modalVisible: true,
 | 
			
		||||
            model: obj
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleCancelModal = e => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalTitle: '',
 | 
			
		||||
            modalVisible: false
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    handleOk = async (formData) => {
 | 
			
		||||
        // 弹窗 form 传来的数据
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalConfirmLoading: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (formData.id) {
 | 
			
		||||
            // 向后台提交数据
 | 
			
		||||
            const result = await request.put('/securities/' + formData.id, formData);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('更新成功');
 | 
			
		||||
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    modalVisible: false
 | 
			
		||||
                });
 | 
			
		||||
                this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('更新失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // 向后台提交数据
 | 
			
		||||
            const result = await request.post('/securities', formData);
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('新增成功');
 | 
			
		||||
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    modalVisible: false
 | 
			
		||||
                });
 | 
			
		||||
                this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('新增失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            modalConfirmLoading: false
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    batchDelete = async () => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            delBtnLoading: true
 | 
			
		||||
        })
 | 
			
		||||
        try {
 | 
			
		||||
            let result = await request.delete('/securities/' + this.state.selectedRowKeys.join(','));
 | 
			
		||||
            if (result.code === 1) {
 | 
			
		||||
                message.success('操作成功', 3);
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    selectedRowKeys: []
 | 
			
		||||
                })
 | 
			
		||||
                await this.loadTableData(this.state.queryParams);
 | 
			
		||||
            } else {
 | 
			
		||||
                message.error('删除失败 :( ' + result.message, 10);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                delBtnLoading: false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleTableChange = (pagination, filters, sorter) => {
 | 
			
		||||
        let query = {
 | 
			
		||||
            ...this.state.queryParams,
 | 
			
		||||
            'order': sorter.order,
 | 
			
		||||
            'field': sorter.field
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadTableData(query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
 | 
			
		||||
        const columns = [{
 | 
			
		||||
            title: '序号',
 | 
			
		||||
            dataIndex: 'id',
 | 
			
		||||
            key: 'id',
 | 
			
		||||
            render: (id, record, index) => {
 | 
			
		||||
                return index + 1;
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: 'IP',
 | 
			
		||||
            dataIndex: 'ip',
 | 
			
		||||
            key: 'ip',
 | 
			
		||||
            sorter: true,
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '规则',
 | 
			
		||||
            dataIndex: 'rule',
 | 
			
		||||
            key: 'rule',
 | 
			
		||||
            render: (rule) => {
 | 
			
		||||
                if (rule === 'allow') {
 | 
			
		||||
                    return <Tag color={'green'}>允许</Tag>
 | 
			
		||||
                } else {
 | 
			
		||||
                    return <Tag color={'red'}>禁止</Tag>
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '优先级',
 | 
			
		||||
            dataIndex: 'priority',
 | 
			
		||||
            key: 'priority',
 | 
			
		||||
            sorter: true,
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '来源',
 | 
			
		||||
            dataIndex: 'source',
 | 
			
		||||
            key: 'source',
 | 
			
		||||
        }, {
 | 
			
		||||
            title: '操作',
 | 
			
		||||
            key: 'action',
 | 
			
		||||
            render: (text, record, index) => {
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <Button type="link" size='small' loading={this.state.items[index]['execLoading']}
 | 
			
		||||
                                onClick={() => this.showModal('更新计划任务', record)}>编辑</Button>
 | 
			
		||||
 | 
			
		||||
                        <Button type="text" size='small' danger
 | 
			
		||||
                                onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const selectedRowKeys = this.state.selectedRowKeys;
 | 
			
		||||
        const rowSelection = {
 | 
			
		||||
            selectedRowKeys: this.state.selectedRowKeys,
 | 
			
		||||
            onChange: (selectedRowKeys, selectedRows) => {
 | 
			
		||||
                this.setState({selectedRowKeys});
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        const hasSelected = selectedRowKeys.length > 0;
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <PageHeader
 | 
			
		||||
                    className="site-page-header-ghost-wrapper"
 | 
			
		||||
                    title="访问安全"
 | 
			
		||||
                    breadcrumb={{
 | 
			
		||||
                        routes: routes,
 | 
			
		||||
                        itemRender: itemRender
 | 
			
		||||
                    }}
 | 
			
		||||
 | 
			
		||||
                    subTitle="IP访问规则限制"
 | 
			
		||||
                >
 | 
			
		||||
                </PageHeader>
 | 
			
		||||
 | 
			
		||||
                <Content className="site-layout-background page-content">
 | 
			
		||||
 | 
			
		||||
                    <div style={{marginBottom: 20}}>
 | 
			
		||||
                        <Row justify="space-around" align="middle" gutter={24}>
 | 
			
		||||
                            <Col span={12} key={1}>
 | 
			
		||||
                                <Title level={3}>IP访问规则列表</Title>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                            <Col span={12} key={2} style={{textAlign: 'right'}}>
 | 
			
		||||
                                <Space>
 | 
			
		||||
                                    <Search
 | 
			
		||||
                                        ref={this.inputRefOfName}
 | 
			
		||||
                                        placeholder="IP"
 | 
			
		||||
                                        allowClear
 | 
			
		||||
                                        onSearch={this.handleSearchByName}
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title='重置查询'>
 | 
			
		||||
 | 
			
		||||
                                        <Button icon={<UndoOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.inputRefOfName.current.setValue('');
 | 
			
		||||
                                            this.loadTableData({pageIndex: 1, pageSize: 10, name: '', content: ''})
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                    <Divider type="vertical"/>
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="新增">
 | 
			
		||||
                                        <Button type="dashed" icon={<PlusOutlined/>}
 | 
			
		||||
                                                onClick={() => this.showModal('新增安全访问规则', {})}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="刷新列表">
 | 
			
		||||
                                        <Button icon={<SyncOutlined/>} onClick={() => {
 | 
			
		||||
                                            this.loadTableData(this.state.queryParams)
 | 
			
		||||
                                        }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                    <Tooltip title="批量删除">
 | 
			
		||||
                                        <Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
 | 
			
		||||
                                                loading={this.state.delBtnLoading}
 | 
			
		||||
                                                onClick={() => {
 | 
			
		||||
                                                    const content = <div>
 | 
			
		||||
                                                        您确定要删除选中的<Text style={{color: '#1890FF'}}
 | 
			
		||||
                                                                       strong>{this.state.selectedRowKeys.length}</Text>条记录吗?
 | 
			
		||||
                                                    </div>;
 | 
			
		||||
                                                    confirm({
 | 
			
		||||
                                                        icon: <ExclamationCircleOutlined/>,
 | 
			
		||||
                                                        content: content,
 | 
			
		||||
                                                        onOk: () => {
 | 
			
		||||
                                                            this.batchDelete()
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        onCancel() {
 | 
			
		||||
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                    });
 | 
			
		||||
                                                }}>
 | 
			
		||||
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Tooltip>
 | 
			
		||||
 | 
			
		||||
                                </Space>
 | 
			
		||||
                            </Col>
 | 
			
		||||
                        </Row>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <Table
 | 
			
		||||
                        rowSelection={rowSelection}
 | 
			
		||||
                        dataSource={this.state.items}
 | 
			
		||||
                        columns={columns}
 | 
			
		||||
                        position={'both'}
 | 
			
		||||
                        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}
 | 
			
		||||
                        onChange={this.handleTableChange}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    {
 | 
			
		||||
                        this.state.modalVisible ?
 | 
			
		||||
                            <SecurityModal
 | 
			
		||||
                                visible={this.state.modalVisible}
 | 
			
		||||
                                title={this.state.modalTitle}
 | 
			
		||||
                                handleOk={this.handleOk}
 | 
			
		||||
                                handleCancel={this.handleCancelModal}
 | 
			
		||||
                                confirmLoading={this.state.modalConfirmLoading}
 | 
			
		||||
                                model={this.state.model}
 | 
			
		||||
                            >
 | 
			
		||||
                            </SecurityModal> : undefined
 | 
			
		||||
                    }
 | 
			
		||||
                </Content>
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Security;
 | 
			
		||||
							
								
								
									
										66
									
								
								web/src/components/devops/SecurityModal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								web/src/components/devops/SecurityModal.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {Form, Input, InputNumber, Modal, Radio} from "antd/lib/index";
 | 
			
		||||
 | 
			
		||||
const formItemLayout = {
 | 
			
		||||
    labelCol: {span: 6},
 | 
			
		||||
    wrapperCol: {span: 14},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SecurityModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
 | 
			
		||||
 | 
			
		||||
    const [form] = Form.useForm();
 | 
			
		||||
 | 
			
		||||
    if (model['priority'] === undefined) {
 | 
			
		||||
        model['priority'] = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            title={title}
 | 
			
		||||
            visible={visible}
 | 
			
		||||
            maskClosable={false}
 | 
			
		||||
            centered={true}
 | 
			
		||||
            onOk={() => {
 | 
			
		||||
                form
 | 
			
		||||
                    .validateFields()
 | 
			
		||||
                    .then(values => {
 | 
			
		||||
                        form.resetFields();
 | 
			
		||||
                        handleOk(values);
 | 
			
		||||
                    })
 | 
			
		||||
                    .catch(info => {
 | 
			
		||||
                    });
 | 
			
		||||
            }}
 | 
			
		||||
            onCancel={handleCancel}
 | 
			
		||||
            confirmLoading={confirmLoading}
 | 
			
		||||
            okText='确定'
 | 
			
		||||
            cancelText='取消'
 | 
			
		||||
        >
 | 
			
		||||
 | 
			
		||||
            <Form form={form} {...formItemLayout} initialValues={model}>
 | 
			
		||||
                <Form.Item name='id' noStyle>
 | 
			
		||||
                    <Input hidden={true}/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="IP地址" name='ip' rules={[{required: true, message: '请输入IP地址'}]}>
 | 
			
		||||
                    <Input autoComplete="off" placeholder="支持单个IP,CIDR或使用-连接的两个IP"/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="规则" name='rule' rules={[{required: true, message: '请选择规则'}]}>
 | 
			
		||||
                    <Radio.Group onChange={async (e) => {
 | 
			
		||||
 | 
			
		||||
                    }}>
 | 
			
		||||
                        <Radio value={'allow'}>允许</Radio>
 | 
			
		||||
                        <Radio value={'reject'}>拒绝</Radio>
 | 
			
		||||
                    </Radio.Group>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
                <Form.Item label="优先级" name='priority' rules={[{required: true, message: '请输入优先级'}]}>
 | 
			
		||||
                    <InputNumber min={0} max={100}/>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
 | 
			
		||||
            </Form>
 | 
			
		||||
        </Modal>
 | 
			
		||||
    )
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SecurityModal;
 | 
			
		||||
		Reference in New Issue
	
	Block a user