diff --git a/src/layouts/defaultProps.tsx b/src/layouts/defaultProps.tsx index c747b11e..ddc5a3de 100644 --- a/src/layouts/defaultProps.tsx +++ b/src/layouts/defaultProps.tsx @@ -42,6 +42,12 @@ export default { icon: , component: '@/pages/env/index', }, + { + path: '/sshKey', + name: 'SSH密钥', + icon: , + component: '@/pages/sshKey/index', + }, { path: '/config', name: intl.get('配置文件'), diff --git a/src/pages/sshKey/index.tsx b/src/pages/sshKey/index.tsx new file mode 100644 index 00000000..131c699b --- /dev/null +++ b/src/pages/sshKey/index.tsx @@ -0,0 +1,300 @@ +import useTableScrollHeight from '@/hooks/useTableScrollHeight'; +import { SharedContext } from '@/layouts'; +import config from '@/utils/config'; +import { request } from '@/utils/http'; +import { + CheckCircleOutlined, + DeleteOutlined, + EditOutlined, + StopOutlined, +} from '@ant-design/icons'; +import { PageContainer } from '@ant-design/pro-layout'; +import { useOutletContext } from '@umijs/max'; +import { + Button, + Input, + Modal, + Space, + Table, + Tag, + Tooltip, + Typography, + message, +} from 'antd'; +import dayjs from 'dayjs'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import intl from 'react-intl-universal'; +import { useVT } from 'virtualizedtableforantd4'; +import Copy from '../../components/copy'; +import SshKeyModal from './modal'; + +const { Paragraph, Text } = Typography; +const { Search } = Input; + +enum Status { + '已启用', + '已禁用', +} + +enum StatusColor { + 'success', + 'error', +} + +enum OperationName { + '启用', + '禁用', +} + +enum OperationPath { + 'enable', + 'disable', +} + +const SshKey = () => { + const { headerStyle, isPhone, theme } = useOutletContext(); + const columns: any = [ + { + title: '序号', + width: 80, + render: (text: string, record: any, index: number) => { + return {index + 1} ; + }, + }, + { + title: '别名', + dataIndex: 'alias', + key: 'alias', + sorter: (a: any, b: any) => a.alias.localeCompare(b.alias), + render: (text: string, record: any) => { + return ( +
+ +
{text}
+
+ +
+ ); + }, + }, + { + title: '备注', + dataIndex: 'remarks', + key: 'remarks', + width: '35%', + sorter: (a: any, b: any) => (a.remarks || '').localeCompare(b.remarks || ''), + render: (text: string, record: any) => { + return ( + +
{text}
+
+ ); + }, + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + width: 90, + filters: [ + { + text: '已启用', + value: 0, + }, + { + text: '已禁用', + value: 1, + }, + ], + onFilter: (value: number, record: any) => record.status === value, + render: (value: number, record: any) => { + return {Status[value]}; + }, + }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + width: 185, + sorter: (a: any, b: any) => { + return ( + dayjs(a.createdAt || 0).valueOf() - dayjs(b.createdAt || 0).valueOf() + ); + }, + render: (text: string, record: any) => { + const d = dayjs(text); + return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : '-'; + }, + }, + { + title: '操作', + key: 'action', + width: 120, + render: (text: string, record: any, index: number) => { + return ( + + + { + operateSSHKey( + [record.id], + record.status === 0 ? OperationPath[1] : OperationPath[0], + ); + }} + > + {OperationName[record.status]} + + + + { + editSSHKey(record); + }} + > + + + + + { + deleteSSHKey(record); + }} + > + + + + + ); + }, + }, + ]; + const [value, setValue] = useState(); + const [loading, setLoading] = useState(true); + const [isModalVisible, setIsModalVisible] = useState(false); + const [searchValue, setSearchValue] = useState(''); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const tableRef = useRef(null); + const [data, setData] = useState([]); + const scrollHeight = useTableScrollHeight(tableRef, data, [ + headerStyle.marginTop, + ]); + const [vt] = useVT(() => ({ scroll: { y: scrollHeight } }), [scrollHeight]); + + const getSSHKeys = (needLoading = true) => { + setLoading(needLoading); + request + .get(`${config.apiPrefix}sshKeys?searchValue=${searchValue}`) + .then(({ code, data }) => { + if (code === 200) { + setData(data); + } + }) + .finally(() => setLoading(false)); + }; + + const editSSHKey = (record: any) => { + setValue(record); + setIsModalVisible(true); + }; + + const deleteSSHKey = (record: any) => { + Modal.confirm({ + title: '确认删除', + content: ( + <> + 确认删除SSH密钥 + + {record.alias} + + 吗 + + ), + onOk() { + request + .delete(`${config.apiPrefix}sshKeys`, [record.id]) + .then(({ code, data }) => { + if (code === 200) { + message.success('删除成功'); + getSSHKeys(); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const operateSSHKey = (ids: any[], operationPath: string) => { + request + .put(`${config.apiPrefix}sshKeys/${operationPath}`, ids) + .then(({ code, data }) => { + if (code === 200) { + message.success(`批量${OperationName[OperationPath[operationPath]]}成功`); + getSSHKeys(false); + } + }); + }; + + const onSearch = (value: string) => { + setSearchValue(value); + }; + + const handleCancel = (keys?: any[]) => { + setIsModalVisible(false); + if (keys) { + getSSHKeys(); + } + }; + + const addSSHKey = () => { + setValue(null); + setIsModalVisible(true); + }; + + useEffect(() => { + getSSHKeys(); + }, [searchValue]); + + const rowSelection = { + selectedRowKeys, + onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => { + setSelectedRowKeys(selectedRowKeys); + }, + }; + + return ( + + 新建 + , + ]} + header={{ + style: headerStyle, + }} + > +
+ + + {isModalVisible && ( + + )} + + ); +}; + +export default SshKey; diff --git a/src/pages/sshKey/modal.tsx b/src/pages/sshKey/modal.tsx new file mode 100644 index 00000000..a8a42ebf --- /dev/null +++ b/src/pages/sshKey/modal.tsx @@ -0,0 +1,97 @@ +import intl from 'react-intl-universal'; +import React, { useEffect, useState } from 'react'; +import { Modal, message, Input, Form } from 'antd'; +import { request } from '@/utils/http'; +import config from '@/utils/config'; + +const SshKeyModal = ({ + sshKey, + handleCancel, +}: { + sshKey?: any; + handleCancel: (keys?: any[]) => void; +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + const handleOk = async (values: any) => { + setLoading(true); + const method = sshKey ? 'put' : 'post'; + const payload = sshKey ? { ...values, id: sshKey.id } : [values]; + try { + const { code, data } = await request[method]( + `${config.apiPrefix}sshKeys`, + payload, + ); + + if (code === 200) { + message.success( + sshKey ? '更新SSH密钥成功' : '创建SSH密钥成功', + ); + handleCancel(data); + } + setLoading(false); + } catch (error: any) { + setLoading(false); + } + }; + + return ( + { + form + .validateFields() + .then((values) => { + handleOk(values); + }) + .catch((info) => { + console.log('Validate Failed:', info); + }); + }} + onCancel={() => handleCancel()} + confirmLoading={loading} + > +
+ + + + + + + + + + +
+ ); +}; + +export default SshKeyModal;