You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by su...@apache.org on 2021/04/09 04:33:07 UTC
[apisix-dashboard] branch master updated: feat: add basic-auth UI
Form (#1718)
This is an automated email from the ASF dual-hosted git repository.
sunyi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 82ab89e feat: add basic-auth UI Form (#1718)
82ab89e is described below
commit 82ab89e2f208eeca1e6f03d9dae885ee7e4b19d3
Author: litesun <su...@apache.org>
AuthorDate: Fri Apr 9 12:31:36 2021 +0800
feat: add basic-auth UI Form (#1718)
---
web/cypress/fixtures/selector.json | 5 +-
.../plugin/create-delete-in-drawer-plugin.spec.js | 24 +++++-
.../create-edit-delete-plugin-template.spec.js | 13 +--
.../create-edit-duplicate-delete-route.spec.js | 4 +
.../service/create-edit-delete-service.spec.js | 3 +
web/cypress/support/commands.js | 36 ++++++---
web/src/components/Plugin/PluginDetail.tsx | 93 +++++++++++++++-------
.../Plugin/{typing.d.ts => UI/basic-auth.tsx} | 53 ++++++++----
.../components/Plugin/{typing.d.ts => UI/index.ts} | 19 +----
.../Plugin/{typing.d.ts => UI/plugin.tsx} | 36 ++++++---
web/src/components/Plugin/typing.d.ts | 2 +-
web/src/locales/en-US/component.ts | 4 +-
web/src/locales/zh-CN/component.ts | 4 +-
13 files changed, 198 insertions(+), 98 deletions(-)
diff --git a/web/cypress/fixtures/selector.json b/web/cypress/fixtures/selector.json
index 71d4b58..0236d8e 100644
--- a/web/cypress/fixtures/selector.json
+++ b/web/cypress/fixtures/selector.json
@@ -82,5 +82,8 @@
"twentyPerPage": "[title=\"20 / page\"]",
"pageList": ".ant-table-pagination-right",
"pageTwo": ".ant-pagination-item-2",
- "pageTwoActived": ".ant-pagination-item-2.ant-pagination-item-active"
+ "pageTwoActived": ".ant-pagination-item-2.ant-pagination-item-active",
+ "selectDropdown": ".ant-select-dropdown",
+ "codeMirrorMode": "[data-cy='code-mirror-mode']",
+ "selectJSON":".ant-select-dropdown [label=JSON]"
}
diff --git a/web/cypress/integration/plugin/create-delete-in-drawer-plugin.spec.js b/web/cypress/integration/plugin/create-delete-in-drawer-plugin.spec.js
index a6e7b4c..272b06b 100644
--- a/web/cypress/integration/plugin/create-delete-in-drawer-plugin.spec.js
+++ b/web/cypress/integration/plugin/create-delete-in-drawer-plugin.spec.js
@@ -32,7 +32,17 @@ context('Delete Plugin List with the Drawer', () => {
cy.contains('Create').click();
cy.contains(this.data.basicAuthPlugin).parents(this.domSelector.pluginCardBordered).within(() => {
- cy.get('button').click({ force: true });
+ cy.get('button').click({
+ force: true
+ });
+ });
+
+ cy.get(this.domSelector.codeMirrorMode).invoke('text').then(text => {
+ if (text === 'Form') {
+ cy.get(this.domSelector.codeMirrorMode).click();
+ cy.get(this.domSelector.selectDropdown).should('be.visible');
+ cy.get(this.domSelector.selectJSON).click();
+ }
});
cy.get(this.domSelector.drawer).should('be.visible').within(() => {
@@ -41,15 +51,21 @@ context('Delete Plugin List with the Drawer', () => {
});
cy.contains('button', 'Submit').click();
- cy.get(this.domSelector.drawer, { timeout }).should('not.exist');
+ cy.get(this.domSelector.drawer, {
+ timeout
+ }).should('not.exist');
});
it('should delete the plugin with the drawer', function () {
cy.visit('/plugin/list');
cy.get(this.domSelector.refresh).click();
cy.contains('button', 'Configure').click();
- cy.get(this.domSelector.drawerFooter).contains('button', 'Delete').click({ force: true });
- cy.contains('button', 'Confirm').click({ force: true });
+ cy.get(this.domSelector.drawerFooter).contains('button', 'Delete').click({
+ force: true
+ });
+ cy.contains('button', 'Confirm').click({
+ force: true
+ });
cy.get(this.domSelector.empty).should('be.visible');
});
});
diff --git a/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js b/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js
index d3ea3c4..f41f54a 100644
--- a/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js
+++ b/web/cypress/integration/pluginTemplate/create-edit-delete-plugin-template.spec.js
@@ -39,13 +39,14 @@ context('Create Configure and Delete PluginTemplate', () => {
force: true
});
cy.focused(this.domSelector.drawer).should('exist');
- cy.get(this.domSelector.drawer, {
- timeout
- }).within(() => {
- cy.get(this.domSelector.disabledSwitcher).click({
- force: true,
- });
+
+ cy.get(this.domSelector.codeMirrorMode).click();
+ cy.get(this.domSelector.selectDropdown).should('be.visible');
+ cy.get(this.domSelector.selectJSON).click();
+ cy.get(this.domSelector.disabledSwitcher).click({
+ force: true,
});
+
cy.contains('Submit').click();
cy.contains('Next').click();
cy.contains('Submit').click();
diff --git a/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js
index 85aad6f..048d489 100644
--- a/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js
+++ b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js
@@ -84,6 +84,10 @@ context('Create and Delete Route', () => {
cy.get(this.domSelector.checkedSwitcher).should('exist');
});
+ cy.get(this.domSelector.codeMirrorMode).click();
+ cy.get(this.domSelector.selectDropdown).should('be.visible');
+ cy.get(this.domSelector.selectJSON).click();
+
cy.contains('button', 'Submit').click();
cy.get(this.domSelector.drawer, { timeout }).should('not.exist');
diff --git a/web/cypress/integration/service/create-edit-delete-service.spec.js b/web/cypress/integration/service/create-edit-delete-service.spec.js
index 52b63c3..8c7fa0a 100644
--- a/web/cypress/integration/service/create-edit-delete-service.spec.js
+++ b/web/cypress/integration/service/create-edit-delete-service.spec.js
@@ -47,6 +47,9 @@ context('Create and Delete Service ', () => {
cy.get(this.domSelector.checkedSwitcher).should('exist');
});
+ cy.get(this.domSelector.codeMirrorMode).click();
+ cy.get(this.domSelector.selectDropdown).should('be.visible');
+ cy.get(this.domSelector.selectJSON).click();
cy.contains('button', 'Submit').click();
cy.get(this.domSelector.drawer, { timeout }).should('not.exist');
diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js
index 59e8ad5..d443339 100644
--- a/web/cypress/support/commands.js
+++ b/web/cypress/support/commands.js
@@ -34,16 +34,19 @@ Cypress.Commands.add('login', () => {
Cypress.Commands.add('configurePlugins', (cases) => {
const timeout = 300;
- const domSelectors = {
+ const domSelector = {
name: '[data-cy-plugin-name]',
parents: '.ant-card-bordered',
drawer_wrap: '.ant-drawer-content-wrapper',
drawer: '.ant-drawer-content',
switch: '#disable',
close: '.anticon-close',
+ selectDropdown: '.ant-select-dropdown',
+ codeMirrorMode: '[data-cy="code-mirror-mode"]',
+ selectJSON: '.ant-select-dropdown [label=JSON]'
};
- cy.get(domSelectors.name, { timeout }).then(function (cards) {
+ cy.get(domSelector.name, { timeout }).then(function (cards) {
[...cards].forEach((card) => {
const name = card.innerText;
const pluginCases = cases[name] || [];
@@ -54,7 +57,7 @@ Cypress.Commands.add('configurePlugins', (cases) => {
}
cy.contains(name)
- .parents(domSelectors.parents)
+ .parents(domSelector.parents)
.within(() => {
cy.contains('Enable').click({
force: true,
@@ -62,9 +65,9 @@ Cypress.Commands.add('configurePlugins', (cases) => {
});
// NOTE: wait for the Drawer to appear on the DOM
- cy.focused(domSelectors.drawer).should('exist');
- cy.get(domSelectors.drawer, { timeout }).within(() => {
- cy.get(domSelectors.switch).click({
+ cy.focused(domSelector.drawer).should('exist');
+ cy.get(domSelector.drawer, { timeout }).within(() => {
+ cy.get(domSelector.switch).click({
force: true,
});
});
@@ -73,26 +76,35 @@ Cypress.Commands.add('configurePlugins', (cases) => {
if (codemirror) {
codemirror.setValue(JSON.stringify(data));
}
- cy.get(domSelectors.drawer).should('exist');
- cy.get(domSelectors.drawer, { timeout }).within(() => {
+ cy.get(domSelector.drawer).should('exist');
+
+ cy.get(domSelector.codeMirrorMode).invoke('text').then(text => {
+ if (text === 'Form') {
+ cy.get(domSelector.codeMirrorMode).click();
+ cy.get(domSelector.selectDropdown).should('be.visible');
+ cy.get(domSelector.selectJSON).click();
+ }
+ });
+
+ cy.get(domSelector.drawer, { timeout }).within(() => {
cy.contains('Submit').click({
force: true,
});
- cy.get(domSelectors.drawer).should('not.exist');
+ cy.get(domSelector.drawer).should('not.exist');
});
});
if (shouldValid === true) {
- cy.get(domSelectors.drawer).should('not.exist');
+ cy.get(domSelector.drawer).should('not.exist');
} else if (shouldValid === false) {
cy.get(this.domSelector.notification).should('contain', 'Invalid plugin data');
- cy.get(domSelectors.close).should('be.visible').click({
+ cy.get(domSelector.close).should('be.visible').click({
force: true,
multiple: true,
});
- cy.get(domSelectors.drawer, { timeout })
+ cy.get(domSelector.drawer, { timeout })
.invoke('show')
.within(() => {
cy.contains('Cancel').click({
diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx
index 8c3eac7..8dbdfe8 100644
--- a/web/src/components/Plugin/PluginDetail.tsx
+++ b/web/src/components/Plugin/PluginDetail.tsx
@@ -38,6 +38,7 @@ import addFormats from 'ajv-formats';
import { fetchSchema } from './service';
import { json2yaml, yaml2json } from '../../helpers';
+import { PluginForm, PLUGIN_UI_LIST } from './UI';
type Props = {
name: string;
@@ -94,8 +95,10 @@ const PluginDetail: React.FC<Props> = ({
enum codeMirrorModeList {
JSON = 'JSON',
YAML = 'YAML',
+ UIForm = 'Form'
}
const [form] = Form.useForm();
+ const [UIForm] = Form.useForm();
const ref = useRef<any>(null);
const data = initialData[name] || {};
const pluginType = pluginList.find((item) => item.name === name)?.type;
@@ -107,11 +110,19 @@ const PluginDetail: React.FC<Props> = ({
{ label: codeMirrorModeList.YAML, value: codeMirrorModeList.YAML },
];
+ if (PLUGIN_UI_LIST.includes(name)) {
+ modeOptions.push({ label: codeMirrorModeList.UIForm, value: codeMirrorModeList.UIForm });
+ }
+
useEffect(() => {
form.setFieldsValue({
disable: initialData[name] && !initialData[name].disable,
scope: 'global',
});
+ if (PLUGIN_UI_LIST.includes(name)) {
+ setCodeMirrorMode(codeMirrorModeList.UIForm);
+ UIForm.setFieldsValue(initialData[name]);
+ };
}, []);
const validateData = (pluginName: string, value: PluginComponent.Data) => {
@@ -161,23 +172,30 @@ const PluginDetail: React.FC<Props> = ({
const handleModeChange = (value: PluginComponent.CodeMirrorMode) => {
switch (value) {
case codeMirrorModeList.JSON: {
- const { data: yamlData, error } = yaml2json(ref.current.editor.getValue(), true);
-
- if (error) {
- notification.error({
- message: 'Invalid Yaml data',
- });
- return;
+ if (codeMirrorMode === codeMirrorModeList.YAML) {
+ const { data: yamlData, error } = yaml2json(ref.current.editor.getValue(), true);
+ if (error) {
+ notification.error({
+ message: 'Invalid Yaml data',
+ });
+ return;
+ }
+ ref.current.editor.setValue(
+ js_beautify(yamlData, {
+ indent_size: 2,
+ }),
+ );
+ } else {
+ ref.current.editor.setValue(
+ js_beautify(JSON.stringify(UIForm.getFieldsValue()), {
+ indent_size: 2,
+ }),
+ );
}
- ref.current.editor.setValue(
- js_beautify(yamlData, {
- indent_size: 2,
- }),
- );
break;
}
case codeMirrorModeList.YAML: {
- const { data: jsonData, error } = json2yaml(ref.current.editor.getValue());
+ const { data: jsonData, error } = json2yaml(codeMirrorMode === codeMirrorModeList.JSON ? ref.current.editor.getValue() : JSON.stringify(UIForm.getFieldsValue()));
if (error) {
notification.error({
@@ -188,11 +206,28 @@ const PluginDetail: React.FC<Props> = ({
ref.current.editor.setValue(jsonData);
break;
}
+
+ case codeMirrorModeList.UIForm: {
+ if (codeMirrorMode === codeMirrorModeList.JSON) {
+ UIForm.setFieldsValue(JSON.parse(ref.current.editor.getValue()));
+ } else {
+ const { data: yamlData, error } = yaml2json(ref.current.editor.getValue(), true);
+ if (error) {
+ notification.error({
+ message: 'Invalid Yaml data',
+ });
+ return;
+ }
+ UIForm.setFieldsValue(JSON.parse(yamlData));
+ }
+ break;
+ }
default:
break;
}
setCodeMirrorMode(value);
};
+
const formatCodes = () => {
try {
if (ref.current) {
@@ -249,10 +284,15 @@ const PluginDetail: React.FC<Props> = ({
type="primary"
onClick={() => {
try {
- const editorData =
- codeMirrorMode === codeMirrorModeList.JSON
- ? JSON.parse(ref.current?.editor.getValue())
- : yaml2json(ref.current?.editor.getValue(), false).data;
+ let editorData;
+ if (codeMirrorMode === codeMirrorModeList.JSON) {
+ editorData = JSON.parse(ref.current?.editor.getValue());
+ } else if (codeMirrorMode === codeMirrorModeList.YAML) {
+ editorData = yaml2json(ref.current?.editor.getValue(), false).data;
+ } else {
+ editorData = UIForm.getFieldsValue();
+ }
+
validateData(name, editorData).then((value) => {
onChange({ formData: form.getFieldsValue(), codemirrorData: value });
});
@@ -297,11 +337,9 @@ const PluginDetail: React.FC<Props> = ({
<PageHeader
title=""
subTitle={
- pluginType === 'auth' && schemaType !== 'consumer' ? (
- <Alert message={`${name} does not require configuration`} type="warning" />
- ) : (
- <>Current plugin: {name}</>
- )
+ pluginType === 'auth' && schemaType !== 'consumer' && (codeMirrorMode !== codeMirrorModeList.UIForm) ? (
+ <Alert message={formatMessage({ id: 'component.global.noConfigurationRequired' })} type="warning" />
+ ) : null
}
ghost={false}
extra={[
@@ -328,12 +366,13 @@ const PluginDetail: React.FC<Props> = ({
}}
data-cy='code-mirror-mode'
></Select>,
- <Button type="primary" onClick={formatCodes} key={3}>
+ <Button type="primary" onClick={formatCodes} key={3} disabled={codeMirrorMode === codeMirrorModeList.UIForm}>
{formatMessage({ id: 'component.global.format' })}
- </Button>,
+ </Button>
]}
/>
- <CodeMirror
+ {Boolean(codeMirrorMode === codeMirrorModeList.UIForm) && <PluginForm name={name} form={UIForm} renderForm={!(pluginType === 'auth' && schemaType !== 'consumer')} />}
+ <div style={{ display: codeMirrorMode === codeMirrorModeList.UIForm ? 'none' : 'unset' }}><CodeMirror
ref={(codemirror) => {
ref.current = codemirror;
if (codemirror) {
@@ -350,8 +389,8 @@ const PluginDetail: React.FC<Props> = ({
lineNumbers: true,
showCursorWhenSelecting: true,
autofocus: true,
- }}
- />
+ }} />
+ </div>
</Drawer>
</>
);
diff --git a/web/src/components/Plugin/typing.d.ts b/web/src/components/Plugin/UI/basic-auth.tsx
similarity index 53%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/Plugin/UI/basic-auth.tsx
index 79dda49..8d95aa0 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/Plugin/UI/basic-auth.tsx
@@ -14,21 +14,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = Record<string, any>;
+import React from 'react';
+import type { FormInstance } from 'antd/es/form';
+import { Form, Input } from 'antd';
- type Schema = '' | 'route' | 'consumer';
+type Props = {
+ form: FormInstance;
+ ref?: any;
+};
- type Meta = {
- name: string;
- priority: number;
- schema: Record<string, any>;
- type: string;
- version: number;
- consumer_schema?: Record<string, any>;
- };
+export const FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 4,
+ },
+ wrapperCol: {
+ span: 8
+ },
+};
- type ReferPage = '' | 'route' | 'consumer' | 'service' | 'plugin';
-
- type CodeMirrorMode = 'JSON' | 'YAML';
+const BasicAuth: React.FC<Props> = ({ form }) => {
+ return (
+ <Form
+ form={form}
+ {...FORM_ITEM_LAYOUT}
+ >
+ <Form.Item
+ label="username"
+ name="username"
+ required
+ >
+ <Input></Input>
+ </Form.Item>
+ <Form.Item
+ label="password"
+ name="password"
+ required
+ >
+ <Input></Input>
+ </Form.Item>
+ </Form>
+ );
}
+
+export default BasicAuth;
diff --git a/web/src/components/Plugin/typing.d.ts b/web/src/components/Plugin/UI/index.ts
similarity index 66%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/Plugin/UI/index.ts
index 79dda49..8da266b 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/Plugin/UI/index.ts
@@ -14,21 +14,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = Record<string, any>;
-
- type Schema = '' | 'route' | 'consumer';
-
- type Meta = {
- name: string;
- priority: number;
- schema: Record<string, any>;
- type: string;
- version: number;
- consumer_schema?: Record<string, any>;
- };
-
- type ReferPage = '' | 'route' | 'consumer' | 'service' | 'plugin';
-
- type CodeMirrorMode = 'JSON' | 'YAML';
-}
+export { PLUGIN_UI_LIST, PluginForm } from './plugin';
diff --git a/web/src/components/Plugin/typing.d.ts b/web/src/components/Plugin/UI/plugin.tsx
similarity index 55%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/Plugin/UI/plugin.tsx
index 79dda49..f13b31a 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/Plugin/UI/plugin.tsx
@@ -14,21 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = Record<string, any>;
+import React from 'react';
+import { FormInstance } from 'antd/es/form';
+import { Empty } from 'antd';
+import { useIntl } from 'umi';
- type Schema = '' | 'route' | 'consumer';
+import BasicAuth from './basic-auth'
- type Meta = {
- name: string;
- priority: number;
- schema: Record<string, any>;
- type: string;
- version: number;
- consumer_schema?: Record<string, any>;
- };
+type Props = {
+ name: string,
+ form: FormInstance,
+ renderForm: boolean
+}
+
+export const PLUGIN_UI_LIST = ['basic-auth',];
+
+export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
+
+ const { formatMessage } = useIntl();
- type ReferPage = '' | 'route' | 'consumer' | 'service' | 'plugin';
+ if (!renderForm) { return <Empty description={formatMessage({ id: 'component.global.noConfigurationRequired' })} /> };
- type CodeMirrorMode = 'JSON' | 'YAML';
+ switch (name) {
+ case 'basic-auth':
+ return <BasicAuth form={form} />
+ default:
+ return null;
+ }
}
diff --git a/web/src/components/Plugin/typing.d.ts b/web/src/components/Plugin/typing.d.ts
index 79dda49..c92b882 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/Plugin/typing.d.ts
@@ -30,5 +30,5 @@ declare namespace PluginComponent {
type ReferPage = '' | 'route' | 'consumer' | 'service' | 'plugin';
- type CodeMirrorMode = 'JSON' | 'YAML';
+ type CodeMirrorMode = 'JSON' | 'YAML'| 'Form';
}
diff --git a/web/src/locales/en-US/component.ts b/web/src/locales/en-US/component.ts
index 55b6a8a..53e9496 100644
--- a/web/src/locales/en-US/component.ts
+++ b/web/src/locales/en-US/component.ts
@@ -72,5 +72,7 @@ export default {
'component.user.loginByPassword': 'Username & Password',
'component.user.login': 'Login',
- 'component.document': 'Document'
+ 'component.document': 'Document',
+
+ 'component.global.noConfigurationRequired': 'No configuration required',
};
diff --git a/web/src/locales/zh-CN/component.ts b/web/src/locales/zh-CN/component.ts
index f4171ec..12b29e1 100644
--- a/web/src/locales/zh-CN/component.ts
+++ b/web/src/locales/zh-CN/component.ts
@@ -68,5 +68,7 @@ export default {
'component.user.loginByPassword': '账号密码登录',
'component.user.login': '登录',
- 'component.document': '操作手册'
+ 'component.document': '操作手册',
+
+ 'component.global.noConfigurationRequired': '无需配置',
};