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 (
+
+ );
+ },
+ },
+ {
+ 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;