Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(FE): add service module #1089

Merged
merged 34 commits into from
Dec 25, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3fd5b25
feat: init service
LiteSun Dec 21, 2020
464a9dd
feat: add service list page
LiteSun Dec 21, 2020
5c1aa61
feat: add service step header
LiteSun Dec 21, 2020
e52da90
feat: add basic information page
LiteSun Dec 21, 2020
b9e4f57
feat: add plugin page
LiteSun Dec 21, 2020
1e50509
feat: update license
LiteSun Dec 21, 2020
52fd0d9
feat: add create service
LiteSun Dec 21, 2020
d3439c0
feat: add list operation
LiteSun Dec 21, 2020
6a91c20
feat: add edit mode
LiteSun Dec 21, 2020
0cebe35
Merge branch 'master' into feat-service
juzhiyuan Dec 22, 2020
e4a88c7
feat: add service edit
LiteSun Dec 22, 2020
b614b9a
Merge branch 'feat-service' of https://github.com/LiteSun/incubator-a…
LiteSun Dec 22, 2020
6bdfc10
feat: remove useless file
LiteSun Dec 22, 2020
0159ac4
feat: clean code
LiteSun Dec 22, 2020
4a81f61
feat: validate upstream form
LiteSun Dec 22, 2020
48ac69e
Merge branch 'master' into feat-service
juzhiyuan Dec 22, 2020
d86e3b3
feat: update codes
LiteSun Dec 22, 2020
dab89a4
Merge branch 'feat-service' of https://github.com/LiteSun/incubator-a…
LiteSun Dec 22, 2020
9d108b5
Merge branch 'master' into feat-service
juzhiyuan Dec 22, 2020
5d3cf44
feat: update UpstreamForm
LiteSun Dec 22, 2020
e417c1b
Merge branch 'feat-service' of https://github.com/LiteSun/incubator-a…
LiteSun Dec 22, 2020
63b3b14
Merge branch 'master' into feat-service
juzhiyuan Dec 23, 2020
a7d355d
feat: add service_id in route step1 page
LiteSun Dec 24, 2020
978d382
feat: update code
LiteSun Dec 24, 2020
ba1a54e
feat: update comment
LiteSun Dec 24, 2020
4745035
feat: update route transform
LiteSun Dec 24, 2020
1d53577
Merge branch 'master' into feat-service
juzhiyuan Dec 24, 2020
4cddac6
Merge branch 'master' into feat-service
juzhiyuan Dec 25, 2020
4a506a2
fix: upstreamForm not update when upstream_id change
LiteSun Dec 25, 2020
caeb4e4
Merge branch 'feat-service' of https://github.com/LiteSun/incubator-a…
LiteSun Dec 25, 2020
769258d
feat: add init upstreamFrom default value
LiteSun Dec 25, 2020
cc97cc9
Merge branch 'master' into feat-service
LiteSun Dec 25, 2020
3e11605
Merge branch 'master' into feat-service
juzhiyuan Dec 25, 2020
31e8bf9
Merge branch 'master' into feat-service
LiteSun Dec 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions web/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ const routes = [
path: '/consumer/:username/edit',
component: './Consumer/Create',
},
{
path: '/service/list',
component: './Service/List',
},
{
path: '/service/create',
component: './Service/Create',
},
{
path: '/service/:serviceId/edit',
component: './Service/Create',
},
{
path: '/settings',
component: './Setting',
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/Plugin/typing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
declare namespace PluginComponent {
type Data = object;

type Schema = '' | 'route' | 'consumer';
type Schema = '' | 'route' | 'consumer' | 'service';

type Category =
| 'Security'
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/Upstream/UpstreamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ const UpstreamForm: React.FC<Props> = forwardRef(
useEffect(() => {
const id = form.getFieldValue('upstream_id');
if (id) {
form.setFieldsValue(list.find((item) => item.id === id));
setReadonly(true);
requestAnimationFrame(() => {
form.setFieldsValue(list.find((item) => item.id === id));
})
}
}, [list]);

Expand Down
5 changes: 5 additions & 0 deletions web/src/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export const getMenuData = (): MenuDataItem[] => {
path: '/consumer/list',
icon: <IconFont name="iconconsumer" />,
},
{
name: 'service',
path: '/service/list',
icon: <IconFont name="iconconsumer" />,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to add a new icon for service?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes, please select an icon here[1], then I will help to generate a new icon file.

[1] https://www.iconfont.cn/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine, if there doesn't have a more proper icon, we could use it. I will file a new PR to do this.

},
{
name: 'setting',
path: '/settings',
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/en-US/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default {
'menu.ssl': 'SSL',
'menu.upstream': 'Upstream',
'menu.consumer': 'Consumer',
'menu.service': 'Service',
'menu.setting': 'Settings',
'menu.serverinfo': 'Server Info',
};
1 change: 1 addition & 0 deletions web/src/locales/zh-CN/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default {
'menu.ssl': '证书',
'menu.upstream': '上游',
'menu.consumer': 'Consumer',
'menu.service': '服务',
'menu.setting': '设置',
'menu.serverinfo': '服务端信息',
};
27 changes: 24 additions & 3 deletions web/src/pages/Route/components/Step1/RequestConfigView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import React, { useEffect, useState } from 'react';
import Form from 'antd/es/form';
import { Button, Input, Select, Row, Col, InputNumber, Switch } from 'antd';
import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
Expand All @@ -26,13 +26,20 @@ import {
FORM_ITEM_LAYOUT,
FORM_ITEM_WITHOUT_LABEL,
} from '@/pages/Route/constants';
import { fetchServiceList } from '../../service';

const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
form,
disabled,
onChange = () => {},
onChange = () => { },
}) => {
const { formatMessage } = useIntl();
const [serviceList, setServiceList] = useState<ServiceModule.ResponseBody[]>([]);

useEffect(() => {
fetchServiceList().then(({ data }) => setServiceList(data));
}, []);

const HostList = () => (
<Form.List name="hosts">
{(fields, { add, remove }) => {
Expand Down Expand Up @@ -376,7 +383,21 @@ const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
return null;
}}
</Form.Item>
</PanelSection>
<Form.Item
label={formatMessage({ id: 'page.route.service' })}
name='service_id'
>
<Select disabled={disabled}>
{/* NOTE value = '' mean no service_id select, need to find a better way */}
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
<Select.Option value='' key={Math.random().toString(36).substring(7)} >{null}</Select.Option >
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
{serviceList.map(item => {
return <Select.Option value={item.id} key={item.id}>
{item.name}
</Select.Option>
})}
</Select>
</Form.Item>
</PanelSection >
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
);
};

Expand Down
1 change: 1 addition & 0 deletions web/src/pages/Route/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default {
'page.route.regexMatch': 'Regex Match',
'page.route.rule': 'Rule',
'page.route.httpHeaderName': 'HTTP Request Header Name',
'page.route.service': 'Service',

'page.route.input.placeholder.parameterNameHttpHeader': 'Request header name, for example: HOST',
'page.route.input.placeholder.parameterNameRequestParameter': 'Parameter name, for example: id',
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/Route/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default {
'page.route.published': '已发布',
'page.route.unpublished': '未发布',
'page.route.onlineDebug': '在线调试',
'page.route.service': '服务',
liuxiran marked this conversation as resolved.
Show resolved Hide resolved

// button
'page.route.button.returnList': '返回路由列表',
Expand Down
9 changes: 8 additions & 1 deletion web/src/pages/Route/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const checkHostWithSSL = (hosts: string[]) =>
export const updateRouteStatus = (rid: string, status: RouteModule.RouteStatus) =>
request(`/routes/${rid}`, {
method: 'PATCH',
data: {status}
data: { status }
});

export const debugRoute = (data: RouteModule.debugRequest) => {
Expand All @@ -99,3 +99,10 @@ export const debugRoute = (data: RouteModule.debugRequest) => {
data,
});
};

export const fetchServiceList = () =>
request('/services').then(({ data }) => ({
data: data.rows,
total: data.total_size,
}));

juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 5 additions & 1 deletion web/src/pages/Route/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const transformStepData = ({
'redirectURI',
'ret_code',
'redirectOption',
form1Data.service_id.length === 0 ? 'service_id' : '',
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
!Object.keys(step3DataCloned.plugins || {}).length ? 'plugins' : '',
!Object.keys(step3DataCloned.script || {}).length ? 'script' : '',
form1Data.hosts.filter(Boolean).length === 0 ? 'hosts' : '',
Expand All @@ -103,6 +104,7 @@ export const transformStepData = ({
'redirect',
'vars',
'plugins',
form1Data.service_id.length !== 0 ? 'service_id' : '',
form1Data.hosts.filter(Boolean).length !== 0 ? 'hosts' : '',
data.remote_addrs?.filter(Boolean).length !== 0 ? 'remote_addrs' : '',
]);
Expand Down Expand Up @@ -150,6 +152,7 @@ export const transformRouteData = (data: RouteModule.Body) => {
status,
upstream,
upstream_id,
service_id = '',
priority = 0,
enable_websocket
} = data;
Expand All @@ -163,7 +166,8 @@ export const transformRouteData = (data: RouteModule.Body) => {
// @ts-ignore
methods: methods.length ? methods : ["ALL"],
priority,
enable_websocket
enable_websocket,
service_id
};

const redirect = data.plugins?.redirect || {};
Expand Down
2 changes: 2 additions & 0 deletions web/src/pages/Route/typing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ declare namespace RouteModule {
script: Record<string, any>;
url?: string;
enable_websocket?: boolean;
service_id?: string;
};

// step1
Expand Down Expand Up @@ -142,6 +143,7 @@ declare namespace RouteModule {
ret_code?: number;
status: number;
enable_websocket?: boolean;
service_id: string;
};

type AdvancedMatchingRules = {
Expand Down
138 changes: 138 additions & 0 deletions web/src/pages/Service/Create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useRef, useEffect } from 'react'
import { useIntl, history } from 'umi';
import { Card, Steps, Form, notification } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { omit } from 'lodash';

import ActionBar from '@/components/ActionBar';
import PluginPage from '@/components/Plugin';
import Preview from './components/Preview';
import Step1 from "./components/Step1";
import { create, update, fetchItem } from './service';

const { Step } = Steps;

const Page: React.FC = (props) => {
const { formatMessage } = useIntl();
const [form] = Form.useForm();
const [upstreamForm] = Form.useForm();
const upstreamRef = useRef<any>();
const [plugins, setPlugins] = useState<PluginComponent.Data>({});

const STEP_HEADER = [
formatMessage({ id: 'page.service.steps.stepTitle.basicInformation' }),
formatMessage({ id: 'page.service.steps.stepTitle.pluginConfig' }),
formatMessage({ id: 'component.global.steps.stepTitle.preview' }),
]

const [stepHeader] = useState(STEP_HEADER);
const [step, setStep] = useState(1);

useEffect(() => {
const { serviceId } = (props as any).match.params;
if (serviceId) {
fetchItem(serviceId).then(({ data }) => {
if (data.upstream_id && data.upstream_id !== '') {
upstreamForm.setFieldsValue({ upstream_id: data.upstream_id });
}
if (data.upstream) {
upstreamForm.setFieldsValue(data.upstream);
}
form.setFieldsValue(omit(data, ['upstream_id', 'upstream', 'plugins']));
setPlugins(data.plugins || {});
});
}
}, []);

const onSubmit = () => {
const data = {
...form.getFieldsValue(),
plugins,
};

const upstreamFormData = upstreamForm.getFieldsValue();
if (upstreamFormData.upstream_id === '') {
data.upstream = omit(upstreamFormData, ['upstream_id']);
} else {
data.upstream_id = upstreamFormData.upstream_id;
}

const { serviceId } = (props as any).match.params;
(serviceId ? update(serviceId, data) : create(data))
.then(() => {
notification.success({
message: `${serviceId
? formatMessage({ id: 'component.global.edit' })
: formatMessage({ id: 'component.global.create' })
} ${formatMessage({ id: 'menu.service' })} ${formatMessage({
id: 'component.status.success',
})}`,
});
history.push('/service/list');
})
.catch(() => {
setStep(3);
});
};

const onStepChange = (nextStep: number) => {
if (step === 1 && nextStep === 2) {
form.validateFields().then(() => {
upstreamForm.validateFields().then(() => {
setStep(nextStep);
})
})
return;
}
if (nextStep === 4) {
onSubmit();
return;
};
setStep(nextStep);
}

return (<>
<PageHeaderWrapper
title={`${(props as any).match.params.rid
? formatMessage({ id: 'component.global.edit' })
: formatMessage({ id: 'component.global.create' })
} ${formatMessage({ id: 'menu.service' })}`}
>
<Card bordered={false}>
<Steps current={step - 1} style={{ marginBottom: "25px" }}>
{stepHeader.map((item) => (
<Step title={item} key={item} />
))}
</Steps>
{step === 1 && <Step1
form={form}
upstreamForm={upstreamForm}
upstreamRef={upstreamRef}
/>}
{step === 2 && (
<PluginPage initialData={plugins} onChange={setPlugins} schemaType="route" />
liuxiran marked this conversation as resolved.
Show resolved Hide resolved
)}
{step === 3 && <Preview upstreamForm={upstreamForm} form={form} plugins={plugins} />}
</Card>
</PageHeaderWrapper>
<ActionBar step={step} lastStep={3} onChange={onStepChange} withResultView />
</>)
}

export default Page;
Loading