You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/03/12 10:36:56 UTC
[incubator-apisix-dashboard] branch next updated: SSL List Page
(#151)
This is an automated email from the ASF dual-hosted git repository.
juzhiyuan pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git
The following commit(s) were added to refs/heads/next by this push:
new 9019923 SSL List Page (#151)
9019923 is described below
commit 9019923f6f587b4b4f4145962d5ff82f6238b226
Author: 琚致远 <ju...@apache.org>
AuthorDate: Thu Mar 12 18:36:49 2020 +0800
SSL List Page (#151)
* feat: fake fetchCurrentUser & rewrite dev api
* feat: added SSL model
* feat: added transformFetchListData
* feat: added SSL service
* feat: added SSLModelState to connect.d
* feat: added transformFetchItemData
* feat: added SSL List page
* feat: remove ListTableList sample
* feat: remove Admin sample
* feat: transform key
* feat: added i18n for SSLModule list
* fix: i18n
* feat: added SSL page
---
config/config.ts | 20 +-
config/proxy.ts | 4 +-
public/favicon.png | Bin 2849 -> 85376 bytes
src/layouts/BasicLayout.tsx | 2 +-
src/locales/en-US/component.ts | 9 +
src/locales/en-US/menu.ts | 3 +
src/locales/zh-CN/component.ts | 9 +
src/locales/zh-CN/menu.ts | 3 +
src/models/connect.d.ts | 2 +
src/models/ssl.ts | 23 +++
src/pages/Admin.tsx | 31 ---
src/pages/ListTableList/_mock.ts | 154 --------------
src/pages/ListTableList/components/CreateForm.tsx | 25 ---
src/pages/ListTableList/components/UpdateForm.tsx | 215 -------------------
src/pages/ListTableList/data.d.ts | 35 ----
src/pages/ListTableList/index.tsx | 238 ----------------------
src/pages/ListTableList/service.ts | 38 ----
src/pages/SSLModule/list/index.tsx | 77 +++++++
src/services/ssl.ts | 20 ++
src/services/user.ts | 7 +-
src/transforms/global.ts | 68 +++++++
src/utils/request.ts | 9 +-
22 files changed, 232 insertions(+), 760 deletions(-)
diff --git a/config/config.ts b/config/config.ts
index 8ae887c..687b505 100644
--- a/config/config.ts
+++ b/config/config.ts
@@ -119,28 +119,18 @@ export default {
component: './Welcome',
},
{
- path: '/admin',
- name: 'admin',
+ name: 'ssl',
+ path: '/ssl',
icon: 'crown',
- component: './Admin',
- authority: ['admin'],
routes: [
{
- path: '/admin/sub-page',
- name: 'sub-page',
- icon: 'smile',
- component: './Welcome',
- authority: ['admin'],
+ path: '/ssl/list',
+ name: 'list',
+ component: './SSLModule/list',
},
],
},
{
- name: 'list.table-list',
- icon: 'table',
- path: '/list',
- component: './ListTableList',
- },
- {
component: './404',
},
],
diff --git a/config/proxy.ts b/config/proxy.ts
index 3fa70dd..e189b0f 100644
--- a/config/proxy.ts
+++ b/config/proxy.ts
@@ -8,9 +8,9 @@
export default {
dev: {
'/api/': {
- target: 'https://preview.pro.ant.design',
+ target: 'https://apisix.iresty.com/apisix/admin/',
changeOrigin: true,
- pathRewrite: { '^': '' },
+ pathRewrite: { '^/api': '' },
},
},
test: {
diff --git a/public/favicon.png b/public/favicon.png
index ece59ce..381ab08 100644
Binary files a/public/favicon.png and b/public/favicon.png differ
diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx
index 256570c..b1e19b7 100644
--- a/src/layouts/BasicLayout.tsx
+++ b/src/layouts/BasicLayout.tsx
@@ -156,7 +156,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = props => {
breadcrumbRender={(routers = []) => [
{
path: '/',
- breadcrumbName: '首页',
+ breadcrumbName: formatMessage({ id: 'menu.home' }),
},
...routers,
]}
diff --git a/src/locales/en-US/component.ts b/src/locales/en-US/component.ts
index 3ba7eed..b3bc39e 100644
--- a/src/locales/en-US/component.ts
+++ b/src/locales/en-US/component.ts
@@ -2,4 +2,13 @@ export default {
'component.tagSelect.expand': 'Expand',
'component.tagSelect.collapse': 'Collapse',
'component.tagSelect.all': 'All',
+ 'component.global.remove': 'Remove',
+ 'component.global.cancel': 'Cancel',
+ 'component.global.edit': 'Edit',
+ 'component.global.action': 'Action',
+ // SSL Module
+ 'component.ssl.removeSSLItemModalContent': 'You are going to remove this item!',
+ 'component.ssl.removeSSLItemModalTitle': 'SSL Remove Alert',
+ 'component.ssl.fetchSSLListSuccess': 'fetch SSL list successfully',
+ 'component.ssl.removeSSLSuccess': 'remove target SSL successfully',
};
diff --git a/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts
index a737e69..cc8862a 100644
--- a/src/locales/en-US/menu.ts
+++ b/src/locales/en-US/menu.ts
@@ -49,4 +49,7 @@ export default {
'menu.editor.flow': 'Flow Editor',
'menu.editor.mind': 'Mind Editor',
'menu.editor.koni': 'Koni Editor',
+ 'menu.ssl': 'SSL',
+ 'menu.ssl.list': 'SSL List',
+ 'menu.ssl.edit': 'Edit',
};
diff --git a/src/locales/zh-CN/component.ts b/src/locales/zh-CN/component.ts
index 1f1fead..b8c505e 100644
--- a/src/locales/zh-CN/component.ts
+++ b/src/locales/zh-CN/component.ts
@@ -2,4 +2,13 @@ export default {
'component.tagSelect.expand': '展开',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
+ 'component.global.remove': '移除',
+ 'component.global.cancel': '取消',
+ 'component.global.edit': '编辑',
+ 'component.global.action': '操作',
+ // SSL 模块
+ 'component.ssl.removeSSLItemModalContent': '确定要移除该项吗?',
+ 'component.ssl.removeSSLItemModalTitle': '移除 SSL',
+ 'component.ssl.fetchSSLListSuccess': '获取 SSL 列表成功',
+ 'component.ssl.removeSSLSuccess': '移除 SSL 成功',
};
diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts
index 985b516..1b0aa04 100644
--- a/src/locales/zh-CN/menu.ts
+++ b/src/locales/zh-CN/menu.ts
@@ -49,4 +49,7 @@ export default {
'menu.editor.flow': '流程编辑器',
'menu.editor.mind': '脑图编辑器',
'menu.editor.koni': '拓扑编辑器',
+ 'menu.ssl': 'SSL',
+ 'menu.ssl.list': 'SSL 列表',
+ 'menu.ssl.edit': '编辑',
};
diff --git a/src/models/connect.d.ts b/src/models/connect.d.ts
index 3fc53f6..6ef39fb 100644
--- a/src/models/connect.d.ts
+++ b/src/models/connect.d.ts
@@ -5,6 +5,7 @@ import { GlobalModelState } from './global';
import { DefaultSettings as SettingModelState } from '../../config/defaultSettings';
import { UserModelState } from './user';
import { StateType } from './login';
+import { ModelState as SSLModelState } from './ssl';
export { GlobalModelState, SettingModelState, UserModelState };
@@ -26,6 +27,7 @@ export interface ConnectState {
settings: SettingModelState;
user: UserModelState;
login: StateType;
+ ssl: SSLModelState;
}
export interface Route extends MenuDataItem {
diff --git a/src/models/ssl.ts b/src/models/ssl.ts
new file mode 100644
index 0000000..47d956a
--- /dev/null
+++ b/src/models/ssl.ts
@@ -0,0 +1,23 @@
+export interface SSL {
+ sni: string;
+ cert: string;
+ key: string;
+}
+
+export interface ModelState {}
+
+export interface ModelType {
+ namespace: string;
+ state: ModelState;
+ effects: {};
+ reducers: {};
+}
+
+const model: ModelType = {
+ namespace: 'ssl',
+ state: {},
+ effects: {},
+ reducers: {},
+};
+
+export default model;
diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx
deleted file mode 100644
index 9c343ad..0000000
--- a/src/pages/Admin.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
-import { Card, Typography, Alert } from 'antd';
-import { PageHeaderWrapper } from '@ant-design/pro-layout';
-
-export default (): React.ReactNode => (
- <PageHeaderWrapper content=" 这个页面只有 admin 权限才能查看">
- <Card>
- <Alert
- message="umi ui 现已发布,欢迎使用 npm run ui 启动体验。"
- type="success"
- showIcon
- banner
- style={{
- margin: -12,
- marginBottom: 48,
- }}
- />
- <Typography.Title level={2} style={{ textAlign: 'center' }}>
- <SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You
- </Typography.Title>
- </Card>
- <p style={{ textAlign: 'center', marginTop: 24 }}>
- Want to add more pages? Please refer to{' '}
- <a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
- use block
- </a>
- 。
- </p>
- </PageHeaderWrapper>
-);
diff --git a/src/pages/ListTableList/_mock.ts b/src/pages/ListTableList/_mock.ts
deleted file mode 100644
index 33b74a9..0000000
--- a/src/pages/ListTableList/_mock.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import { Request, Response } from 'express';
-import { parse } from 'url';
-import { TableListItem, TableListParams } from './data.d';
-
-// mock tableListDataSource
-const genList = (current: number, pageSize: number) => {
- const tableListDataSource: TableListItem[] = [];
-
- for (let i = 0; i < pageSize; i += 1) {
- const index = (current - 1) * 10 + i;
- tableListDataSource.push({
- key: index,
- disabled: i % 6 === 0,
- href: 'https://ant.design',
- avatar: [
- 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
- 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
- ][i % 2],
- name: `TradeCode ${index}`,
- owner: '曲丽丽',
- desc: '这是一段描述',
- callNo: Math.floor(Math.random() * 1000),
- status: Math.floor(Math.random() * 10) % 4,
- updatedAt: new Date(),
- createdAt: new Date(),
- progress: Math.ceil(Math.random() * 100),
- });
- }
- tableListDataSource.reverse();
- return tableListDataSource;
-};
-
-let tableListDataSource = genList(1, 100);
-
-function getRule(req: Request, res: Response, u: string) {
- let url = u;
- if (!url || Object.prototype.toString.call(url) !== '[object String]') {
- // eslint-disable-next-line prefer-destructuring
- url = req.url;
- }
- const { current = 1, pageSize = 10 } = req.query;
- const params = (parse(url, true).query as unknown) as TableListParams;
-
- let dataSource = [...tableListDataSource].slice((current - 1) * pageSize, current * pageSize);
- if (params.sorter) {
- const s = params.sorter.split('_');
- dataSource = dataSource.sort((prev, next) => {
- if (s[1] === 'descend') {
- return next[s[0]] - prev[s[0]];
- }
- return prev[s[0]] - next[s[0]];
- });
- }
-
- if (params.status) {
- const status = params.status.split(',');
- let filterDataSource: TableListItem[] = [];
- status.forEach((s: string) => {
- filterDataSource = filterDataSource.concat(
- dataSource.filter(item => {
- if (parseInt(`${item.status}`, 10) === parseInt(s.split('')[0], 10)) {
- return true;
- }
- return false;
- }),
- );
- });
- dataSource = filterDataSource;
- }
-
- if (params.name) {
- dataSource = dataSource.filter(data => data.name.includes(params.name || ''));
- }
- const result = {
- data: dataSource,
- total: tableListDataSource.length,
- success: true,
- pageSize,
- current: parseInt(`${params.currentPage}`, 10) || 1,
- };
-
- return res.json(result);
-}
-
-function postRule(req: Request, res: Response, u: string, b: Request) {
- let url = u;
- if (!url || Object.prototype.toString.call(url) !== '[object String]') {
- // eslint-disable-next-line prefer-destructuring
- url = req.url;
- }
-
- const body = (b && b.body) || req.body;
- const { method, name, desc, key } = body;
-
- switch (method) {
- /* eslint no-case-declarations:0 */
- case 'delete':
- tableListDataSource = tableListDataSource.filter(item => key.indexOf(item.key) === -1);
- break;
- case 'post':
- (() => {
- const i = Math.ceil(Math.random() * 10000);
- const newRule = {
- key: tableListDataSource.length,
- href: 'https://ant.design',
- avatar: [
- 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
- 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
- ][i % 2],
- name,
- owner: '曲丽丽',
- desc,
- callNo: Math.floor(Math.random() * 1000),
- status: Math.floor(Math.random() * 10) % 2,
- updatedAt: new Date(),
- createdAt: new Date(),
- progress: Math.ceil(Math.random() * 100),
- };
- tableListDataSource.unshift(newRule);
- return res.json(newRule);
- })();
- return;
-
- case 'update':
- (() => {
- let newRule = {};
- tableListDataSource = tableListDataSource.map(item => {
- if (item.key === key) {
- newRule = { ...item, desc, name };
- return { ...item, desc, name };
- }
- return item;
- });
- return res.json(newRule);
- })();
- return;
- default:
- break;
- }
-
- const result = {
- list: tableListDataSource,
- pagination: {
- total: tableListDataSource.length,
- },
- };
-
- res.json(result);
-}
-
-export default {
- 'GET /api/rule': getRule,
- 'POST /api/rule': postRule,
-};
diff --git a/src/pages/ListTableList/components/CreateForm.tsx b/src/pages/ListTableList/components/CreateForm.tsx
deleted file mode 100644
index 817922a..0000000
--- a/src/pages/ListTableList/components/CreateForm.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import { Modal } from 'antd';
-
-interface CreateFormProps {
- modalVisible: boolean;
- onCancel: () => void;
-}
-
-const CreateForm: React.FC<CreateFormProps> = props => {
- const { modalVisible, onCancel } = props;
-
- return (
- <Modal
- destroyOnClose
- title="新建规则"
- visible={modalVisible}
- onCancel={() => onCancel()}
- footer={null}
- >
- {props.children}
- </Modal>
- );
-};
-
-export default CreateForm;
diff --git a/src/pages/ListTableList/components/UpdateForm.tsx b/src/pages/ListTableList/components/UpdateForm.tsx
deleted file mode 100644
index 2c20df3..0000000
--- a/src/pages/ListTableList/components/UpdateForm.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import React, { useState } from 'react';
-import { Form, Button, DatePicker, Input, Modal, Radio, Select, Steps } from 'antd';
-
-import { TableListItem } from '../data.d';
-
-export interface FormValueType extends Partial<TableListItem> {
- target?: string;
- template?: string;
- type?: string;
- time?: string;
- frequency?: string;
-}
-
-export interface UpdateFormProps {
- onCancel: (flag?: boolean, formVals?: FormValueType) => void;
- onSubmit: (values: FormValueType) => void;
- updateModalVisible: boolean;
- values: Partial<TableListItem>;
-}
-const FormItem = Form.Item;
-const { Step } = Steps;
-const { TextArea } = Input;
-const { Option } = Select;
-const RadioGroup = Radio.Group;
-
-export interface UpdateFormState {
- formVals: FormValueType;
- currentStep: number;
-}
-
-const formLayout = {
- labelCol: { span: 7 },
- wrapperCol: { span: 13 },
-};
-
-const UpdateForm: React.FC<UpdateFormProps> = props => {
- const [formVals, setFormVals] = useState<FormValueType>({
- name: props.values.name,
- desc: props.values.desc,
- key: props.values.key,
- target: '0',
- template: '0',
- type: '1',
- time: '',
- frequency: 'month',
- });
-
- const [currentStep, setCurrentStep] = useState<number>(0);
-
- const [form] = Form.useForm();
-
- const {
- onSubmit: handleUpdate,
- onCancel: handleUpdateModalVisible,
- updateModalVisible,
- values,
- } = props;
-
- const forward = () => setCurrentStep(currentStep + 1);
-
- const backward = () => setCurrentStep(currentStep - 1);
-
- const handleNext = async () => {
- const fieldsValue = await form.validateFields();
-
- setFormVals({ ...formVals, ...fieldsValue });
-
- if (currentStep < 2) {
- forward();
- } else {
- handleUpdate(formVals);
- }
- };
-
- const renderContent = () => {
- if (currentStep === 1) {
- return (
- <>
- <FormItem name="target" label="监控对象">
- <Select style={{ width: '100%' }}>
- <Option value="0">表一</Option>
- <Option value="1">表二</Option>
- </Select>
- </FormItem>
- <FormItem name="template" label="规则模板">
- <Select style={{ width: '100%' }}>
- <Option value="0">规则模板一</Option>
- <Option value="1">规则模板二</Option>
- </Select>
- </FormItem>
- <FormItem name="type" label="规则类型">
- <RadioGroup>
- <Radio value="0">强</Radio>
- <Radio value="1">弱</Radio>
- </RadioGroup>
- </FormItem>
- </>
- );
- }
- if (currentStep === 2) {
- return (
- <>
- <FormItem
- name="time"
- label="开始时间"
- rules={[{ required: true, message: '请选择开始时间!' }]}
- >
- <DatePicker
- style={{ width: '100%' }}
- showTime
- format="YYYY-MM-DD HH:mm:ss"
- placeholder="选择开始时间"
- />
- </FormItem>
- <FormItem name="frequency" label="调度周期">
- <Select style={{ width: '100%' }}>
- <Option value="month">月</Option>
- <Option value="week">周</Option>
- </Select>
- </FormItem>
- </>
- );
- }
- return (
- <>
- <FormItem
- name="name"
- label="规则名称"
- rules={[{ required: true, message: '请输入规则名称!' }]}
- >
- <Input placeholder="请输入" />
- </FormItem>
- <FormItem
- name="desc"
- label="规则描述"
- rules={[{ required: true, message: '请输入至少五个字符的规则描述!', min: 5 }]}
- >
- <TextArea rows={4} placeholder="请输入至少五个字符" />
- </FormItem>
- </>
- );
- };
-
- const renderFooter = () => {
- if (currentStep === 1) {
- return (
- <>
- <Button style={{ float: 'left' }} onClick={backward}>
- 上一步
- </Button>
- <Button onClick={() => handleUpdateModalVisible(false, values)}>取消</Button>
- <Button type="primary" onClick={() => handleNext()}>
- 下一步
- </Button>
- </>
- );
- }
- if (currentStep === 2) {
- return (
- <>
- <Button style={{ float: 'left' }} onClick={backward}>
- 上一步
- </Button>
- <Button onClick={() => handleUpdateModalVisible(false, values)}>取消</Button>
- <Button type="primary" onClick={() => handleNext()}>
- 完成
- </Button>
- </>
- );
- }
- return (
- <>
- <Button onClick={() => handleUpdateModalVisible(false, values)}>取消</Button>
- <Button type="primary" onClick={() => handleNext()}>
- 下一步
- </Button>
- </>
- );
- };
-
- return (
- <Modal
- width={640}
- bodyStyle={{ padding: '32px 40px 48px' }}
- destroyOnClose
- title="规则配置"
- visible={updateModalVisible}
- footer={renderFooter()}
- onCancel={() => handleUpdateModalVisible(false, values)}
- afterClose={() => handleUpdateModalVisible()}
- >
- <Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
- <Step title="基本信息" />
- <Step title="配置规则属性" />
- <Step title="设定调度周期" />
- </Steps>
- <Form
- {...formLayout}
- form={form}
- initialValues={{
- target: formVals.target,
- template: formVals.template,
- type: formVals.type,
- frequency: formVals.frequency,
- name: formVals.name,
- desc: formVals.desc,
- }}
- >
- {renderContent()}
- </Form>
- </Modal>
- );
-};
-
-export default UpdateForm;
diff --git a/src/pages/ListTableList/data.d.ts b/src/pages/ListTableList/data.d.ts
deleted file mode 100644
index 04096c7..0000000
--- a/src/pages/ListTableList/data.d.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-export interface TableListItem {
- key: number;
- disabled?: boolean;
- href: string;
- avatar: string;
- name: string;
- owner: string;
- desc: string;
- callNo: number;
- status: number;
- updatedAt: Date;
- createdAt: Date;
- progress: number;
-}
-
-export interface TableListPagination {
- total: number;
- pageSize: number;
- current: number;
-}
-
-export interface TableListData {
- list: TableListItem[];
- pagination: Partial<TableListPagination>;
-}
-
-export interface TableListParams {
- sorter?: string;
- status?: string;
- name?: string;
- desc?: string;
- key?: number;
- pageSize?: number;
- currentPage?: number;
-}
diff --git a/src/pages/ListTableList/index.tsx b/src/pages/ListTableList/index.tsx
deleted file mode 100644
index 22e15b6..0000000
--- a/src/pages/ListTableList/index.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { DownOutlined, PlusOutlined } from '@ant-design/icons';
-import { Button, Divider, Dropdown, Menu, message } from 'antd';
-import React, { useState, useRef } from 'react';
-import { PageHeaderWrapper } from '@ant-design/pro-layout';
-import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
-import { SorterResult } from 'antd/es/table/interface';
-
-import CreateForm from './components/CreateForm';
-import UpdateForm, { FormValueType } from './components/UpdateForm';
-import { TableListItem } from './data.d';
-import { queryRule, updateRule, addRule, removeRule } from './service';
-
-/**
- * 添加节点
- * @param fields
- */
-const handleAdd = async (fields: TableListItem) => {
- const hide = message.loading('正在添加');
- try {
- await addRule({ ...fields });
- hide();
- message.success('添加成功');
- return true;
- } catch (error) {
- hide();
- message.error('添加失败请重试!');
- return false;
- }
-};
-
-/**
- * 更新节点
- * @param fields
- */
-const handleUpdate = async (fields: FormValueType) => {
- const hide = message.loading('正在配置');
- try {
- await updateRule({
- name: fields.name,
- desc: fields.desc,
- key: fields.key,
- });
- hide();
-
- message.success('配置成功');
- return true;
- } catch (error) {
- hide();
- message.error('配置失败请重试!');
- return false;
- }
-};
-
-/**
- * 删除节点
- * @param selectedRows
- */
-const handleRemove = async (selectedRows: TableListItem[]) => {
- const hide = message.loading('正在删除');
- if (!selectedRows) return true;
- try {
- await removeRule({
- key: selectedRows.map(row => row.key),
- });
- hide();
- message.success('删除成功,即将刷新');
- return true;
- } catch (error) {
- hide();
- message.error('删除失败,请重试');
- return false;
- }
-};
-
-const TableList: React.FC<{}> = () => {
- const [sorter, setSorter] = useState<string>('');
- const [createModalVisible, handleModalVisible] = useState<boolean>(false);
- const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
- const [stepFormValues, setStepFormValues] = useState({});
- const actionRef = useRef<ActionType>();
- const columns: ProColumns<TableListItem>[] = [
- {
- title: '规则名称',
- dataIndex: 'name',
- rules: [
- {
- required: true,
- message: '规则名称为必填项',
- },
- ],
- },
- {
- title: '描述',
- dataIndex: 'desc',
- valueType: 'textarea',
- },
- {
- title: '服务调用次数',
- dataIndex: 'callNo',
- sorter: true,
- hideInForm: true,
- renderText: (val: string) => `${val} 万`,
- },
- {
- title: '状态',
- dataIndex: 'status',
- hideInForm: true,
- valueEnum: {
- 0: { text: '关闭', status: 'Default' },
- 1: { text: '运行中', status: 'Processing' },
- 2: { text: '已上线', status: 'Success' },
- 3: { text: '异常', status: 'Error' },
- },
- },
- {
- title: '上次调度时间',
- dataIndex: 'updatedAt',
- sorter: true,
- valueType: 'dateTime',
- hideInForm: true,
- },
- {
- title: '操作',
- dataIndex: 'option',
- valueType: 'option',
- render: (_, record) => (
- <>
- <a
- onClick={() => {
- handleUpdateModalVisible(true);
- setStepFormValues(record);
- }}
- >
- 配置
- </a>
- <Divider type="vertical" />
- <a href="">订阅警报</a>
- </>
- ),
- },
- ];
-
- return (
- <PageHeaderWrapper>
- <ProTable<TableListItem>
- headerTitle="查询表格"
- actionRef={actionRef}
- rowKey="key"
- onChange={(_, _filter, _sorter) => {
- const sorterResult = _sorter as SorterResult<TableListItem>;
- if (sorterResult.field) {
- setSorter(`${sorterResult.field}_${sorterResult.order}`);
- }
- }}
- params={{
- sorter,
- }}
- toolBarRender={(action, { selectedRows }) => [
- <Button type="primary" onClick={() => handleModalVisible(true)}>
- <PlusOutlined /> 新建
- </Button>,
- selectedRows && selectedRows.length > 0 && (
- <Dropdown
- overlay={
- <Menu
- onClick={async e => {
- if (e.key === 'remove') {
- await handleRemove(selectedRows);
- action.reload();
- }
- }}
- selectedKeys={[]}
- >
- <Menu.Item key="remove">批量删除</Menu.Item>
- <Menu.Item key="approval">批量审批</Menu.Item>
- </Menu>
- }
- >
- <Button>
- 批量操作 <DownOutlined />
- </Button>
- </Dropdown>
- ),
- ]}
- tableAlertRender={(selectedRowKeys, selectedRows) => (
- <div>
- 已选择 <a style={{ fontWeight: 600 }}>{selectedRowKeys.length}</a> 项
- <span>
- 服务调用次数总计 {selectedRows.reduce((pre, item) => pre + item.callNo, 0)} 万
- </span>
- </div>
- )}
- request={params => queryRule(params)}
- columns={columns}
- rowSelection={{}}
- />
- <CreateForm onCancel={() => handleModalVisible(false)} modalVisible={createModalVisible}>
- <ProTable<TableListItem, TableListItem>
- onSubmit={async value => {
- const success = await handleAdd(value);
- if (success) {
- handleModalVisible(false);
- if (actionRef.current) {
- actionRef.current.reload();
- }
- }
- }}
- rowKey="key"
- type="form"
- columns={columns}
- rowSelection={{}}
- />
- </CreateForm>
- {stepFormValues && Object.keys(stepFormValues).length ? (
- <UpdateForm
- onSubmit={async value => {
- const success = await handleUpdate(value);
- if (success) {
- handleModalVisible(false);
- setStepFormValues({});
- if (actionRef.current) {
- actionRef.current.reload();
- }
- }
- }}
- onCancel={() => {
- handleUpdateModalVisible(false);
- setStepFormValues({});
- }}
- updateModalVisible={updateModalVisible}
- values={stepFormValues}
- />
- ) : null}
- </PageHeaderWrapper>
- );
-};
-
-export default TableList;
diff --git a/src/pages/ListTableList/service.ts b/src/pages/ListTableList/service.ts
deleted file mode 100644
index e008a6d..0000000
--- a/src/pages/ListTableList/service.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import request from '@/utils/request';
-import { TableListParams, TableListItem } from './data.d';
-
-export async function queryRule(params?: TableListParams) {
- return request('/api/rule', {
- params,
- });
-}
-
-export async function removeRule(params: { key: number[] }) {
- return request('/api/rule', {
- method: 'POST',
- data: {
- ...params,
- method: 'delete',
- },
- });
-}
-
-export async function addRule(params: TableListItem) {
- return request('/api/rule', {
- method: 'POST',
- data: {
- ...params,
- method: 'post',
- },
- });
-}
-
-export async function updateRule(params: TableListParams) {
- return request('/api/rule', {
- method: 'POST',
- data: {
- ...params,
- method: 'update',
- },
- });
-}
diff --git a/src/pages/SSLModule/list/index.tsx b/src/pages/SSLModule/list/index.tsx
new file mode 100644
index 0000000..f06f424
--- /dev/null
+++ b/src/pages/SSLModule/list/index.tsx
@@ -0,0 +1,77 @@
+import React, { useRef } from 'react';
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
+import { Button, Modal, notification } from 'antd';
+import { router } from 'umi';
+import { formatMessage } from 'umi-plugin-react/locale';
+
+import { fetchList as fetchSSLList, remove as removeSSL } from '@/services/ssl';
+import { SSL } from '@/models/ssl';
+import { ListItem } from '@/transforms/global';
+
+const List: React.FC = () => {
+ const tableRef = useRef<ActionType>();
+
+ const onRemove = (key: string) => {
+ Modal.confirm({
+ title: formatMessage({ id: 'component.ssl.removeSSLItemModalTitle' }),
+ content: formatMessage({ id: 'component.ssl.removeSSLItemModalContent' }),
+ okText: formatMessage({ id: 'component.global.remove' }),
+ cancelText: formatMessage({ id: 'component.global.cancel' }),
+ okButtonProps: {
+ type: 'danger',
+ },
+ /* eslint-disable no-unused-expressions */
+ onOk: () =>
+ removeSSL(key).then(() => {
+ notification.success({
+ message: formatMessage({ id: 'component.ssl.removeSSLSuccess' }),
+ });
+ tableRef.current?.reload();
+ }),
+ /* eslint-enable no-unused-expressions */
+ });
+ };
+
+ const columns: ProColumns<ListItem<SSL>>[] = [
+ {
+ title: 'ID',
+ dataIndex: 'displayKey',
+ },
+ {
+ title: 'SNI',
+ dataIndex: ['value', 'sni'],
+ },
+ {
+ title: formatMessage({ id: 'component.global.action' }),
+ valueType: 'option',
+ render: (_, record) => (
+ <>
+ <Button
+ type="primary"
+ style={{ marginRight: '10px' }}
+ onClick={() => router.push(`/ssl/${record.key}/edit`)}
+ >
+ {formatMessage({ id: 'component.global.edit' })}
+ </Button>
+ <Button type="danger" onClick={() => onRemove(record.key)}>
+ {formatMessage({ id: 'component.global.remove' })}
+ </Button>
+ </>
+ ),
+ },
+ ];
+
+ return (
+ <PageHeaderWrapper>
+ <ProTable<ListItem<SSL>>
+ request={() => fetchSSLList()}
+ search={false}
+ columns={columns}
+ actionRef={tableRef}
+ />
+ </PageHeaderWrapper>
+ );
+};
+
+export default List;
diff --git a/src/services/ssl.ts b/src/services/ssl.ts
new file mode 100644
index 0000000..cd51a25
--- /dev/null
+++ b/src/services/ssl.ts
@@ -0,0 +1,20 @@
+import request from '@/utils/request';
+import { transformFetchListData, transformFetchItemData } from '@/transforms/global';
+import { SSL } from '@/models/ssl';
+
+export const fetchList = () => request('/api/ssl').then(data => transformFetchListData<SSL>(data));
+
+export const fetchItem = (id: number) =>
+ request(`/api/ssl/${id}`).then(data => transformFetchItemData<SSL>(data));
+
+export const remove = (key: string) => request.delete(`/api/ssl/${key}`);
+
+export const create = (data: SSL) =>
+ request.post('/api/ssl', {
+ data,
+ });
+
+export const update = (id: number, data: SSL) =>
+ request.put(`/api/ssl/${id}`, {
+ data,
+ });
diff --git a/src/services/user.ts b/src/services/user.ts
index 1988721..1e5fec8 100644
--- a/src/services/user.ts
+++ b/src/services/user.ts
@@ -5,7 +5,12 @@ export async function query(): Promise<any> {
}
export async function queryCurrent(): Promise<any> {
- return request('/api/currentUser');
+ // NOTE: APISIX doesn‘t support user login currently, we return fake data directly.
+ return {
+ name: 'APISIX User',
+ avatar: 'favicon.png',
+ userid: '00000001',
+ };
}
export async function queryNotices(): Promise<any> {
diff --git a/src/transforms/global.ts b/src/transforms/global.ts
new file mode 100644
index 0000000..4d8e3fd
--- /dev/null
+++ b/src/transforms/global.ts
@@ -0,0 +1,68 @@
+import { RequestData } from '@ant-design/pro-table';
+
+type ActionType = 'get' | 'set' | 'delete' | 'create';
+
+interface Node<T> {
+ createdIndex: number;
+ key: string;
+ modifiedIndex: number;
+ value: T;
+}
+
+interface ListData<T> {
+ action: ActionType;
+ node: {
+ modifiedIndex: number;
+ createdIndex: number;
+ key: string;
+ dir: boolean;
+ nodes: Node<T>[];
+ };
+}
+
+export interface ListItem<T> extends Node<T> {
+ displayKey: string;
+}
+
+const key2id = (key: string) => parseInt(key.replace(/^(0+)/, ''), 10);
+
+/**
+ * Transform data from fetch list api.
+ */
+export const transformFetchListData = <T>(data: ListData<T>): RequestData<ListItem<T>> => {
+ const results = (data.node.nodes || [])
+ .map(node => {
+ const result = node.key.match(/\/([0-9]+)/);
+ let displayKey = '';
+
+ if (result) {
+ const [, key] = result;
+ displayKey = key2id(key).toString();
+ /* eslint no-param-reassign: ["error", { "props": false }] */
+ node.key = key;
+ }
+ return {
+ ...node,
+ displayKey,
+ };
+ })
+ .filter(item => item.displayKey);
+
+ return {
+ data: results,
+ total: results.length,
+ };
+};
+
+/**
+ * Transform data from fetch target item.
+ */
+export const transformFetchItemData = <T>(data: { node: Node<T>; action: ActionType }) => {
+ const result = data.node.key.match(/\/([0-9]+)/);
+ if (result) {
+ const [, key] = result;
+ /* eslint no-param-reassign: ["error", { "props": false }] */
+ data.node.key = key;
+ }
+ return data.node;
+};
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 270dfad..849bf1c 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -26,14 +26,13 @@ const codeMessage = {
/**
* 异常处理程序
*/
-const errorHandler = (error: { response: Response }): Response => {
+const errorHandler = (error: { response: Response; data: any }): Promise<Response> => {
const { response } = error;
if (response && response.status) {
- const errorText = codeMessage[response.status] || response.statusText;
- const { status, url } = response;
+ const errorText = error.data.message || codeMessage[response.status];
notification.error({
- message: `请求错误 ${status}: ${url}`,
+ message: `请求错误,错误码: ${error.data.errorCode}`,
description: errorText,
});
} else if (!response) {
@@ -42,7 +41,7 @@ const errorHandler = (error: { response: Response }): Response => {
message: '网络异常',
});
}
- return response;
+ return Promise.reject(response);
};
/**