This is an automated email from the ASF dual-hosted git repository.
juzhiyuan 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 053721c fix(Plugin): use codemirror instead of form in plugin module
(#898)
053721c is described below
commit 053721cc483c6ffc21981b1d6a70cfce07aa808b
Author: 琚致远 <[email protected]>
AuthorDate: Thu Dec 10 13:55:27 2020 +0800
fix(Plugin): use codemirror instead of form in plugin module (#898)
* feat(plugin): added code mirror
* chore: format codes
* feat(Plugin): remove a7 plugin
* feat(Plugin): remove @api7-dashboard/plugin
* feat: added codes format
* feat: use local icon files
* feat: update ASF Release cfg
* feat: update ASF Release cfg
---
.actions/ASF-Release.cfg | 1 +
web/config/config.ts | 2 +-
web/package.json | 4 +-
web/src/app.tsx | 2 +
.../IconFont/IconFont.tsx} | 19 ++-
.../{iconfont.ts => components/IconFont/index.ts} | 9 +-
web/src/components/Plugin/CodeMirrorDrawer.tsx | 142 ++++++++++++++++
web/src/components/Plugin/PluginPage.tsx | 182 +++++++++++++++++++++
web/src/components/Plugin/data.tsx | 166 +++++++++++++++++++
.../{iconfont.ts => components/Plugin/index.ts} | 10 +-
web/src/components/Plugin/service.ts | 98 +++++++++++
.../Preview.tsx => components/Plugin/typing.d.ts} | 37 +++--
web/src/e2e/Login.e2e.js | 7 +-
web/src/e2e/Logout.e2e.js | 9 +-
web/src/e2e/service.js | 2 +-
web/src/global.less | 12 ++
web/src/helpers.tsx | 20 +--
web/src/libs/iconfont.js | 85 ++++++++++
web/src/pages/Consumer/Create.tsx | 21 +--
web/src/pages/Consumer/components/Preview.tsx | 4 +-
web/src/pages/Route/Create.tsx | 5 +-
.../Route/components/CreateStep4/CreateStep4.tsx | 2 +-
.../Route/components/Step1/MatchingRulesView.tsx | 2 +-
.../Route/components/Step1/RequestConfigView.tsx | 2 +-
web/src/pages/Route/components/Step3/index.tsx | 68 ++++----
web/yarn.lock | 90 ++++++++--
26 files changed, 872 insertions(+), 129 deletions(-)
diff --git a/.actions/ASF-Release.cfg b/.actions/ASF-Release.cfg
index bed65bf..e2828f7 100644
--- a/.actions/ASF-Release.cfg
+++ b/.actions/ASF-Release.cfg
@@ -78,6 +78,7 @@ web/src/e2e/__mocks__/antd-pro-merge-less.js
web/src/e2e/baseLayout.e2e.js
web/src/pages/404.tsx
web/src/service-worker.js
+web/src/libs/iconfont.js
api/build-tools/json.lua
# Skip files containing Apache 2.0 License
diff --git a/web/config/config.ts b/web/config/config.ts
index f71d512..024b223 100644
--- a/web/config/config.ts
+++ b/web/config/config.ts
@@ -61,5 +61,5 @@ export default defineConfig({
manifest: {
basePath: '/',
},
- outputPath: '../output/html'
+ outputPath: '../output/html',
});
diff --git a/web/package.json b/web/package.json
index d2b53bf..3f088fd 100644
--- a/web/package.json
+++ b/web/package.json
@@ -55,14 +55,15 @@
"@ant-design/icons": "^4.0.0",
"@ant-design/pro-layout": "^6.0.0",
"@ant-design/pro-table": "2.6.3",
- "@api7-dashboard/plugin": "^1.0.15",
"@api7-dashboard/pluginchart": "^1.0.14",
"@api7-dashboard/ui": "^1.0.3",
"@rjsf/antd": "2.2.0",
"@rjsf/core": "2.2.0",
+ "@uiw/react-codemirror": "^3.0.1",
"antd": "^4.4.0",
"classnames": "^2.2.6",
"dayjs": "1.8.28",
+ "js-beautify": "^1.13.0",
"json-schema": "0.2.5",
"lodash": "^4.17.11",
"moment": "^2.25.3",
@@ -87,6 +88,7 @@
"@types/express": "^4.17.0",
"@types/history": "^4.7.2",
"@types/jest": "^26.0.0",
+ "@types/js-beautify": "^1.13.1",
"@types/lodash": "^4.14.144",
"@types/node-forge": "0.9.3",
"@types/qs": "^6.5.3",
diff --git a/web/src/app.tsx b/web/src/app.tsx
index 41921f1..8e67c87 100644
--- a/web/src/app.tsx
+++ b/web/src/app.tsx
@@ -26,6 +26,8 @@ import RightContent from '@/components/RightContent';
import Footer from '@/components/Footer';
import { queryCurrent } from '@/services/user';
import { getMenuData, errorHandler } from '@/helpers';
+
+import './libs/iconfont';
import defaultSettings from '../config/defaultSettings';
export async function getInitialState(): Promise<{
diff --git a/web/src/iconfont.ts b/web/src/components/IconFont/IconFont.tsx
similarity index 74%
copy from web/src/iconfont.ts
copy to web/src/components/IconFont/IconFont.tsx
index df1b507..540ddfc 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/IconFont/IconFont.tsx
@@ -14,11 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { createFromIconfontCN } from '@ant-design/icons';
+import React from 'react';
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
- scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
+type Props = {
+ name: string;
+};
+
+/**
+ * Icon Font
+ * https://www.iconfont.cn/help/detail?helptype=code
+ */
+const IconFont: React.FC<Props> = ({ name }) => (
+ <svg className="icon" aria-hidden="true">
+ <use xlinkHref={`#${name}`} />
+ </svg>
+);
export default IconFont;
diff --git a/web/src/iconfont.ts b/web/src/components/IconFont/index.ts
similarity index 73%
copy from web/src/iconfont.ts
copy to web/src/components/IconFont/index.ts
index df1b507..a020929 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/IconFont/index.ts
@@ -14,11 +14,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { createFromIconfontCN } from '@ant-design/icons';
-
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
- scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
-
-export default IconFont;
+export { default } from './IconFont';
diff --git a/web/src/components/Plugin/CodeMirrorDrawer.tsx
b/web/src/components/Plugin/CodeMirrorDrawer.tsx
new file mode 100644
index 0000000..95ef951
--- /dev/null
+++ b/web/src/components/Plugin/CodeMirrorDrawer.tsx
@@ -0,0 +1,142 @@
+/*
+ * 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, { useRef } from 'react';
+import { Drawer, Button, notification, PageHeader } from 'antd';
+import CodeMirror from '@uiw/react-codemirror';
+import { js_beautify } from 'js-beautify';
+import { LinkOutlined } from '@ant-design/icons';
+
+type Props = {
+ name: string;
+ visible?: boolean;
+ data?: object;
+ readonly?: boolean;
+ onClose?: () => void;
+ onSubmit?: (data: object) => void;
+};
+
+const CodeMirrorDrawer: React.FC<Props> = ({
+ name,
+ visible = false,
+ readonly = false,
+ data = {},
+ onClose,
+ onSubmit,
+}) => {
+ const ref = useRef<any>(null);
+
+ const formatCodes = () => {
+ try {
+ if (ref.current) {
+ ref.current.editor.setValue(
+ js_beautify(ref.current.editor.getValue(), {
+ indent_size: 2,
+ }),
+ );
+ }
+ } catch (error) {
+ notification.error({
+ message: 'Format failed',
+ });
+ }
+ };
+
+ return (
+ <>
+ <style>
+ {`
+ .ant-drawer-body {
+ padding: 0;
+ }
+ .ant-page-header.ant-page-header-compact {
+ height: 100%;
+ }
+ .ant-page-header-content {
+ height: 95%;
+ }
+ `}
+ </style>
+ <Drawer
+ title="Plugin Data Editor"
+ visible={visible}
+ width={500}
+ maskClosable={false}
+ destroyOnClose
+ onClose={onClose}
+ footer={
+ !readonly && (
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <Button onClick={onClose}>Cancel</Button>
+ <Button
+ type="primary"
+ style={{ marginRight: 8, marginLeft: 8 }}
+ onClick={() => {
+ try {
+ if (onSubmit) {
+ onSubmit(JSON.parse(ref.current?.editor.getValue()));
+ }
+ } catch (error) {
+ notification.error({
+ message: 'Invalid JSON data',
+ });
+ }
+ }}
+ >
+ Submit
+ </Button>
+ </div>
+ )
+ }
+ >
+ <PageHeader
+ title=""
+ subTitle={`Current Plugin: ${name}`}
+ ghost={false}
+ extra={[
+ <Button
+ type="default"
+ icon={<LinkOutlined />}
+ onClick={() => {
+
window.open(`https://github.com/apache/apisix/blob/master/doc/plugins/${name}.md`);
+ }}
+ >
+ Document
+ </Button>,
+ <Button type="primary" onClick={formatCodes}>
+ Format
+ </Button>,
+ ]}
+ >
+ <CodeMirror
+ ref={ref}
+ value={JSON.stringify(data, null, 2)}
+ options={{
+ mode: 'json-ld',
+ readOnly: readonly ? 'nocursor' : '',
+ lineWrapping: true,
+ lineNumbers: true,
+ showCursorWhenSelecting: true,
+ autofocus: true,
+ }}
+ />
+ </PageHeader>
+ </Drawer>
+ </>
+ );
+};
+
+export default CodeMirrorDrawer;
diff --git a/web/src/components/Plugin/PluginPage.tsx
b/web/src/components/Plugin/PluginPage.tsx
new file mode 100644
index 0000000..8c5ab79
--- /dev/null
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -0,0 +1,182 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import { Anchor, Layout, Switch, Card, Tooltip, Button, notification, Avatar }
from 'antd';
+import { SettingFilled } from '@ant-design/icons';
+import { PanelSection } from '@api7-dashboard/ui';
+import { validate } from 'json-schema';
+
+import { fetchSchema, getList } from './service';
+import CodeMirrorDrawer from './CodeMirrorDrawer';
+
+type Props = {
+ readonly?: boolean;
+ initialData?: PluginComponent.Data;
+ schemaType?: PluginComponent.Schema;
+ onChange?: (data: PluginComponent.Data) => void;
+};
+
+const PanelSectionStyle = {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(3, 33.333333%)',
+ gridRowGap: 15,
+ gridColumnGap: 10,
+ width: 'calc(100% - 20px)',
+};
+
+const { Sider, Content } = Layout;
+
+// NOTE: use this flag as plugin's name to hide drawer
+const NEVER_EXIST_PLUGIN_FLAG = 'NEVER_EXIST_PLUGIN_FLAG';
+
+const PluginPage: React.FC<Props> = ({
+ readonly = false,
+ initialData = {},
+ schemaType = '',
+ onChange = () => {},
+}) => {
+ const [pluginList, setPlugin] = useState<PluginComponent.Meta[][]>([]);
+ const [name, setName] = useState<string>(NEVER_EXIST_PLUGIN_FLAG);
+
+ useEffect(() => {
+ getList().then(setPlugin);
+ }, []);
+
+ const validateData = (pluginName: string, value: PluginComponent.Data) => {
+ fetchSchema(pluginName, schemaType).then((schema) => {
+ const { valid, errors } = validate(value, schema);
+ if (valid) {
+ setName(NEVER_EXIST_PLUGIN_FLAG);
+ onChange({ ...initialData, [pluginName]: { ...value, disable: false }
});
+ return;
+ }
+ errors?.forEach((item) => {
+ notification.error({
+ message: 'Invalid plugin data',
+ description: item.message,
+ });
+ });
+ setName(pluginName);
+ });
+ };
+
+ return (
+ <>
+ <style>{`
+ .ant-avatar > img {
+ object-fit: contain;
+ }
+ .ant-avatar {
+ background-color: transparent;
+ }
+ .ant-avatar.ant-avatar-icon {
+ font-size: 32px;
+ }
+ `}</style>
+ <Layout>
+ <Sider theme="light">
+ <Anchor offsetTop={150}>
+ {pluginList.map((plugins) => {
+ const { category } = plugins[0];
+ return (
+ <Anchor.Link
+ href={`#plugin-category-${category}`}
+ title={category}
+ key={category}
+ />
+ );
+ })}
+ </Anchor>
+ </Sider>
+ <Content style={{ padding: '0 10px', backgroundColor: '#fff',
minHeight: 1400 }}>
+ {pluginList.map((plugins) => {
+ const { category } = plugins[0];
+ return (
+ <PanelSection
+ title={category}
+ key={category}
+ style={PanelSectionStyle}
+ id={`plugin-category-${category}`}
+ >
+ {plugins.map((item) => (
+ <Card
+ key={item.name}
+ title={[
+ item.avatar && (
+ <Avatar
+ icon={item.avatar}
+ className="plugin-avatar"
+ style={{
+ marginRight: 5,
+ }}
+ />
+ ),
+ <span>{item.name}</span>,
+ ]}
+ style={{ height: 66 }}
+ extra={[
+ <Tooltip title="Setting"
key={`plugin-card-${item.name}-extra-tooltip-2`}>
+ <Button
+ shape="circle"
+ icon={<SettingFilled />}
+ style={{ marginRight: 10, marginLeft: 10 }}
+ size="middle"
+ onClick={() => {
+ setName(item.name);
+ }}
+ />
+ </Tooltip>,
+ <Switch
+ defaultChecked={initialData[item.name] &&
!initialData[item.name].disable}
+ disabled={readonly}
+ onChange={(isChecked) => {
+ if (isChecked) {
+ validateData(item.name, initialData[item.name]);
+ } else {
+ onChange({
+ ...initialData,
+ [item.name]: { ...initialData[item.name],
disable: true },
+ });
+ }
+ }}
+ key={Math.random().toString(36).substring(7)}
+ />,
+ ]}
+ />
+ ))}
+ </PanelSection>
+ );
+ })}
+ </Content>
+ </Layout>
+ <CodeMirrorDrawer
+ name={name}
+ visible={name !== NEVER_EXIST_PLUGIN_FLAG}
+ data={initialData[name]}
+ readonly={readonly}
+ onClose={() => {
+ setName(NEVER_EXIST_PLUGIN_FLAG);
+ }}
+ onSubmit={(value) => {
+ validateData(name, value);
+ }}
+ />
+ </>
+ );
+};
+
+export default PluginPage;
diff --git a/web/src/components/Plugin/data.tsx
b/web/src/components/Plugin/data.tsx
new file mode 100644
index 0000000..40b1d60
--- /dev/null
+++ b/web/src/components/Plugin/data.tsx
@@ -0,0 +1,166 @@
+/*
+ * 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 from 'react';
+
+import IconFont from '../IconFont';
+
+export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginComponent.Meta,
'name'>> = {
+ 'limit-req': {
+ category: 'Limit traffic',
+ priority: 1,
+ },
+ 'limit-count': {
+ category: 'Limit traffic',
+ priority: 2,
+ },
+ 'limit-conn': {
+ category: 'Limit traffic',
+ priority: 3,
+ },
+ prometheus: {
+ category: 'Observability',
+ priority: 1,
+ avatar: <IconFont name="iconPrometheus_software_logo" />,
+ },
+ skywalking: {
+ category: 'Observability',
+ priority: 2,
+ avatar: <IconFont name="iconskywalking" />,
+ },
+ zipkin: {
+ category: 'Observability',
+ priority: 3,
+ },
+ 'request-id': {
+ category: 'Observability',
+ priority: 4,
+ },
+ 'key-auth': {
+ category: 'Authentication',
+ priority: 1,
+ },
+ 'basic-auth': {
+ category: 'Authentication',
+ priority: 3,
+ },
+ 'node-status': {
+ category: 'Other',
+ },
+ 'jwt-auth': {
+ category: 'Authentication',
+ priority: 2,
+ avatar: <IconFont name="iconjwt-3" />,
+ },
+ 'authz-keycloak': {
+ category: 'Authentication',
+ priority: 5,
+ avatar: <IconFont name="iconkeycloak_icon_32px" />,
+ },
+ 'ip-restriction': {
+ category: 'Security',
+ priority: 1,
+ },
+ 'grpc-transcode': {
+ category: 'Other',
+ },
+ 'serverless-pre-function': {
+ category: 'Other',
+ },
+ 'serverless-post-function': {
+ category: 'Other',
+ },
+ 'openid-connect': {
+ category: 'Authentication',
+ priority: 4,
+ avatar: <IconFont name="iconicons8-openid" />,
+ },
+ 'proxy-rewrite': {
+ category: 'Other',
+ },
+ redirect: {
+ category: 'Other',
+ hidden: true,
+ },
+ 'response-rewrite': {
+ category: 'Other',
+ },
+ 'fault-injection': {
+ category: 'Security',
+ priority: 4,
+ },
+ 'udp-logger': {
+ category: 'Log',
+ priority: 4,
+ },
+ 'wolf-rbac': {
+ category: 'Other',
+ },
+ 'proxy-cache': {
+ category: 'Other',
+ priority: 1,
+ },
+ 'tcp-logger': {
+ category: 'Log',
+ priority: 3,
+ },
+ 'proxy-mirror': {
+ category: 'Other',
+ priority: 2,
+ },
+ 'kafka-logger': {
+ category: 'Log',
+ priority: 1,
+ avatar: <IconFont name="iconApache_kafka" />,
+ },
+ cors: {
+ category: 'Security',
+ priority: 2,
+ },
+ 'uri-blocker': {
+ category: 'Security',
+ priority: 3,
+ },
+ 'request-validator': {
+ category: 'Security',
+ priority: 5,
+ },
+ heartbeat: {
+ category: 'Other',
+ hidden: true,
+ },
+ 'batch-requests': {
+ category: 'Other',
+ },
+ 'http-logger': {
+ category: 'Log',
+ priority: 2,
+ },
+ 'mqtt-proxy': {
+ category: 'Other',
+ },
+ oauth: {
+ category: 'Security',
+ },
+ syslog: {
+ category: 'Log',
+ priority: 5,
+ },
+ echo: {
+ category: 'Other',
+ priority: 3,
+ },
+};
diff --git a/web/src/iconfont.ts b/web/src/components/Plugin/index.ts
similarity index 73%
rename from web/src/iconfont.ts
rename to web/src/components/Plugin/index.ts
index df1b507..940c9f0 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/Plugin/index.ts
@@ -14,11 +14,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { createFromIconfontCN } from '@ant-design/icons';
-
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
- scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
-
-export default IconFont;
+export { default } from './PluginPage';
+export { PLUGIN_MAPPER_SOURCE } from './data';
diff --git a/web/src/components/Plugin/service.ts
b/web/src/components/Plugin/service.ts
new file mode 100644
index 0000000..01f171b
--- /dev/null
+++ b/web/src/components/Plugin/service.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { JSONSchema7 } from 'json-schema';
+import { omit } from 'lodash';
+import { request } from 'umi';
+import { PLUGIN_MAPPER_SOURCE } from './data';
+
+enum Category {
+ 'Limit traffic',
+ 'Observability',
+ 'Security',
+ 'Authentication',
+ 'Log',
+ 'Other',
+}
+
+export const fetchList = () => request<Res<string[]>>('/plugins');
+
+let cachedPluginNameList: string[] = [];
+export const getList = async () => {
+ if (!cachedPluginNameList.length) {
+ cachedPluginNameList = (await fetchList()).data;
+ }
+ const names = cachedPluginNameList;
+ const data: Record<string, PluginComponent.Meta[]> = {};
+
+ names.forEach((name) => {
+ const plugin = PLUGIN_MAPPER_SOURCE[name] || {};
+ const { category = 'Other', hidden = false } = plugin;
+
+ // NOTE: assign it to Authentication plugin
+ if (name.includes('auth')) {
+ plugin.category = 'Authentication';
+ }
+
+ if (!data[category]) {
+ data[category] = [];
+ }
+
+ if (!hidden) {
+ data[category] = data[category].concat({
+ ...plugin,
+ name,
+ });
+ }
+ });
+
+ return Object.keys(data)
+ .sort((a, b) => Category[a] - Category[b])
+ .map((category) => {
+ return data[category].sort((a, b) => {
+ return (a.priority || 9999) - (b.priority || 9999);
+ });
+ });
+};
+
+/**
+ * cache pulgin schema by schemaType
+ * default schema is route for plugins in route
+ * support schema: consumer for plugins in consumer
+ */
+const cachedPluginSchema: Record<string, object> = {
+ route: {},
+ consumer: {},
+};
+export const fetchSchema = async (
+ name: string,
+ schemaType: PluginComponent.Schema,
+): Promise<JSONSchema7> => {
+ if (!cachedPluginSchema[schemaType][name]) {
+ const queryString = schemaType !== 'route' ? `?schema_type=${schemaType}`
: '';
+ cachedPluginSchema[schemaType][name] = (
+ await request(`/schema/plugins/${name}${queryString}`)
+ ).data;
+ // for plugins schema returned with properties: [], which will cause parse
error
+ if (JSON.stringify(cachedPluginSchema[schemaType][name].properties) ===
'[]') {
+ cachedPluginSchema[schemaType][name] = omit(
+ cachedPluginSchema[schemaType][name],
+ 'properties',
+ );
+ }
+ }
+ return cachedPluginSchema[schemaType][name];
+};
diff --git a/web/src/pages/Consumer/components/Preview.tsx
b/web/src/components/Plugin/typing.d.ts
similarity index 60%
copy from web/src/pages/Consumer/components/Preview.tsx
copy to web/src/components/Plugin/typing.d.ts
index 92e4b01..3a899aa 100644
--- a/web/src/pages/Consumer/components/Preview.tsx
+++ b/web/src/components/Plugin/typing.d.ts
@@ -14,24 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
-import { FormInstance } from 'antd/lib/form';
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
+declare namespace PluginComponent {
+ type Data = object;
-import Step1 from './Step1';
+ type Schema = '' | 'route' | 'consumer';
-type Props = {
- form1: FormInstance;
- plugins: PluginPageType.FinalData;
-};
+ type Category =
+ | 'Security'
+ | 'Limit traffic'
+ | 'Log'
+ | 'Observability'
+ | 'Other'
+ | 'Authentication';
-const Page: React.FC<Props> = ({ form1, plugins }) => {
- return (
- <>
- <Step1 form={form1} disabled />
- <PluginPage initialData={plugins} readonly />
- </>
- );
-};
-
-export default Page;
+ type Meta = {
+ name: string;
+ category: Category;
+ hidden?: boolean;
+ // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
+ priority?: number;
+ avatar?: React.ReactNode;
+ };
+}
diff --git a/web/src/e2e/Login.e2e.js b/web/src/e2e/Login.e2e.js
index 1829daa..5ea3ac9 100644
--- a/web/src/e2e/Login.e2e.js
+++ b/web/src/e2e/Login.e2e.js
@@ -18,10 +18,7 @@
/* eslint-disable import/no-extraneous-dependencies */
const puppeteer = require('puppeteer');
-const {
- setupLogin,
- BASE_URL
-} = require('./service')
+const { setupLogin, BASE_URL } = require('./service');
let browser;
const domSelectors = {
@@ -38,7 +35,7 @@ const loginFailedData = {
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
- slowMo: 100
+ slowMo: 100,
});
});
diff --git a/web/src/e2e/Logout.e2e.js b/web/src/e2e/Logout.e2e.js
index cef92cf..c10083c 100644
--- a/web/src/e2e/Logout.e2e.js
+++ b/web/src/e2e/Logout.e2e.js
@@ -18,24 +18,21 @@
/* eslint-disable import/no-extraneous-dependencies */
const puppeteer = require('puppeteer');
-const {
- setupLogin
-} = require('./service')
+const { setupLogin } = require('./service');
let browser;
const domSelectors = {
userProfile: '.ant-space-horizontal div:nth-child(2)',
dropdownMenuItem: '.ant-dropdown-menu-item',
- buttonLogin: ".ant-btn-lg",
+ buttonLogin: '.ant-btn-lg',
logoutButton: '.ant-dropdown > ul > li:nth-child(3)',
};
describe('Logout', () => {
-
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
- slowMo: 100
+ slowMo: 100,
});
});
diff --git a/web/src/e2e/service.js b/web/src/e2e/service.js
index 84bfcb0..49279cd 100644
--- a/web/src/e2e/service.js
+++ b/web/src/e2e/service.js
@@ -36,4 +36,4 @@ export const setupLogin = async (page) => {
await page.click(domSelectors.buttonLogin);
await page.waitForSelector(domSelectors.loginSuccessIcon);
await page.waitForNavigation();
-}
+};
diff --git a/web/src/global.less b/web/src/global.less
index b86beab..aa17671 100644
--- a/web/src/global.less
+++ b/web/src/global.less
@@ -49,11 +49,13 @@ ol {
.ant-table {
width: 100%;
overflow-x: auto;
+
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
+
> span {
display: block;
}
@@ -88,3 +90,13 @@ ol {
.ant-layout.ant-layout-has-sider > .ant-layout-content {
overflow-x: unset;
}
+
+// NOTE: compatible with IconFont
+.icon {
+ width: 1.2em;
+ height: 1.2em;
+ margin-right: 1em;
+ overflow: hidden;
+ vertical-align: -0.15em;
+ fill: currentColor;
+}
diff --git a/web/src/helpers.tsx b/web/src/helpers.tsx
index ac8eb84..a0ffe74 100644
--- a/web/src/helpers.tsx
+++ b/web/src/helpers.tsx
@@ -21,39 +21,39 @@ import { history } from 'umi';
import moment from 'moment';
import { codeMessage } from './constants';
-import IconFont from './iconfont';
+import IconFont from './components/IconFont';
export const getMenuData = (): MenuDataItem[] => {
return [
{
name: 'metrics',
path: '/metrics',
- icon: <IconFont type="icondashboard" />,
+ icon: <IconFont name="icondashboard" />,
},
{
name: 'routes',
path: '/routes/list',
- icon: <IconFont type="iconroute" />,
+ icon: <IconFont name="iconroute" />,
},
{
name: 'ssl',
path: '/ssl/list',
- icon: <IconFont type="iconSSLshuzizhengshu" />,
+ icon: <IconFont name="iconssl" />,
},
{
name: 'upstream',
path: '/upstream/list',
- icon: <IconFont type="iconupstream" />,
+ icon: <IconFont name="iconserver" />,
},
{
name: 'consumer',
path: '/consumer/list',
- icon: <IconFont type="iconfuwuliebiao" />,
+ icon: <IconFont name="iconconsumer" />,
},
{
name: 'setting',
path: '/settings',
- icon: <IconFont type="iconsetting" />,
+ icon: <IconFont name="iconsetting" />,
},
];
};
@@ -101,8 +101,8 @@ export const getUrlQuery: (key: string) => string | false =
(key: string) => {
export const timestampToLocaleString = (timestamp: number) => {
if (!timestamp) {
// TODO: i18n
- return "None"
+ return 'None';
}
- return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss')
-}
+ return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
+};
diff --git a/web/src/libs/iconfont.js b/web/src/libs/iconfont.js
new file mode 100644
index 0000000..0c8d3d6
--- /dev/null
+++ b/web/src/libs/iconfont.js
@@ -0,0 +1,85 @@
+/*
+* MIT License
+
+* Copyright (c) 2019 Alipay.inc
+
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+
+* The above copyright notice and this permission notice shall be included in
all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+/* eslint-disable */
+!(function (l) {
+ var c,
+ h,
+ t,
+ a,
+ i,
+ e,
+ z =
+ '<svg><symbol id="icondashboard" viewBox="0 0 1024 1024"><path
d="M662.87 343.467l60.33 60.33-128.768 128.768a85.333 85.333 0 1
1-60.33-60.33l128.767-128.768zM85.332 512C85.333 276.352 276.352 85.333 512
85.333S938.667 276.352 938.667 512a425.813 425.813 0 0 1-156.758
330.453l-70.826-53.162a341.333 341.333 0 1 0-398.123 0l-70.87 53.162A425.813
425.813 0 0 1 85.334 512zM384 725.333h256v85.334H384v-85.334z"
></path></symbol><symbol id="iconssl" viewBox="0 0 1024 1024"><path d="M167.
[...]
+ p = (p = document.getElementsByTagName('script'))[p.length -
1].getAttribute('data-injectcss');
+ if (p && !l.__iconfont__svg__cssinject__) {
+ l.__iconfont__svg__cssinject__ = !0;
+ try {
+ document.write(
+ '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill:
currentColor;vertical-align: -0.1em;font-size:16px;}</style>',
+ );
+ } catch (l) {
+ console && console.log(l);
+ }
+ }
+ function o() {
+ i || ((i = !0), t());
+ }
+ (c = function () {
+ var l, c, h, t;
+ ((t = document.createElement('div')).innerHTML = z),
+ (z = null),
+ (h = t.getElementsByTagName('svg')[0]) &&
+ (h.setAttribute('aria-hidden', 'true'),
+ (h.style.position = 'absolute'),
+ (h.style.width = 0),
+ (h.style.height = 0),
+ (h.style.overflow = 'hidden'),
+ (l = h),
+ (c = document.body).firstChild
+ ? ((t = l), (h = c.firstChild).parentNode.insertBefore(t, h))
+ : c.appendChild(l));
+ }),
+ document.addEventListener
+ ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState)
+ ? setTimeout(c, 0)
+ : ((h = function () {
+ document.removeEventListener('DOMContentLoaded', h, !1), c();
+ }),
+ document.addEventListener('DOMContentLoaded', h, !1))
+ : document.attachEvent &&
+ ((t = c),
+ (a = l.document),
+ (i = !1),
+ (e = function () {
+ try {
+ a.documentElement.doScroll('left');
+ } catch (l) {
+ return void setTimeout(e, 50);
+ }
+ o();
+ })(),
+ (a.onreadystatechange = function () {
+ 'complete' == a.readyState && ((a.onreadystatechange = null), o());
+ }));
+})(window);
diff --git a/web/src/pages/Consumer/Create.tsx
b/web/src/pages/Consumer/Create.tsx
index d866076..cab68b9 100644
--- a/web/src/pages/Consumer/Create.tsx
+++ b/web/src/pages/Consumer/Create.tsx
@@ -18,9 +18,9 @@ import React, { useState, useEffect } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Card, Steps, notification, Form } from 'antd';
import { history, useIntl } from 'umi';
-import { PluginPage, PluginPageType, PLUGIN_MAPPER_SOURCE } from
'@api7-dashboard/plugin';
import ActionBar from '@/components/ActionBar';
+import PluginPage, { PLUGIN_MAPPER_SOURCE } from '@/components/Plugin';
import Step1 from './components/Step1';
import Preview from './components/Preview';
@@ -28,7 +28,7 @@ import { fetchItem, create, update } from './service';
const Page: React.FC = (props) => {
const [step, setStep] = useState(1);
- const [plugins, setPlugins] = useState<PluginPageType.FinalData>({});
+ const [plugins, setPlugins] = useState<PluginComponent.Data>({});
const [form1] = Form.useForm();
const { formatMessage } = useIntl();
@@ -70,14 +70,15 @@ const Page: React.FC = (props) => {
setStep(nextStep);
});
} else if (nextStep === 3) {
- const authPluginNames = Object.keys(PLUGIN_MAPPER_SOURCE).filter(
- (pluginName) => PLUGIN_MAPPER_SOURCE[pluginName].category ===
'Authentication',
- );
- const currentAuthPlugin = Object.keys(plugins).filter((plugin) =>
- authPluginNames.includes(plugin),
- );
- const currentAuthPluginLen = currentAuthPlugin.length;
- if (currentAuthPluginLen > 1 || currentAuthPluginLen === 0) {
+ // TRICK: waiting for
https://github.com/apache/apisix-dashboard/issues/532
+ if (
+ !Object.keys(plugins).filter(
+ (name) =>
+ (name.indexOf('auth') !== -1 ||
+ PLUGIN_MAPPER_SOURCE[name]?.category === 'Authentication') &&
+ !plugins[name].disable,
+ ).length
+ ) {
notification.warning({
message: formatMessage({
id:
'page.consumer.notification.warning.enableAuthenticationPlugin',
diff --git a/web/src/pages/Consumer/components/Preview.tsx
b/web/src/pages/Consumer/components/Preview.tsx
index 92e4b01..a1d1daf 100644
--- a/web/src/pages/Consumer/components/Preview.tsx
+++ b/web/src/pages/Consumer/components/Preview.tsx
@@ -16,13 +16,13 @@
*/
import React from 'react';
import { FormInstance } from 'antd/lib/form';
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
+import PluginPage from '@/components/Plugin';
import Step1 from './Step1';
type Props = {
form1: FormInstance;
- plugins: PluginPageType.FinalData;
+ plugins: PluginComponent.Data;
};
const Page: React.FC<Props> = ({ form1, plugins }) => {
diff --git a/web/src/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx
index 396008c..73e3e9e 100644
--- a/web/src/pages/Route/Create.tsx
+++ b/web/src/pages/Route/Create.tsx
@@ -252,10 +252,11 @@ const Page: React.FC<Props> = (props) => {
return (
<>
<PageHeaderWrapper
- title={`${(props as any).match.params.rid
+ title={`${
+ (props as any).match.params.rid
? formatMessage({ id: 'component.global.edit' })
: formatMessage({ id: 'component.global.create' })
- } ${formatMessage({ id: 'menu.routes' })}`}
+ } ${formatMessage({ id: 'menu.routes' })}`}
>
<Card bordered={false}>
<Steps current={step - 1} className={styles.steps}>
diff --git a/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
b/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
index 5e153e3..f0c668a 100644
--- a/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
+++ b/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
@@ -17,9 +17,9 @@
import React from 'react';
import { FormInstance } from 'antd/lib/form';
import { useIntl } from 'umi';
-import { PluginPage } from '@api7-dashboard/plugin';
import PluginOrchestration from '@api7-dashboard/pluginchart';
+import PluginPage from '@/components/Plugin';
import Step1 from '../Step1';
import Step2 from '../Step2';
diff --git a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
index 9ba0b80..48260e7 100644
--- a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
+++ b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
@@ -149,7 +149,7 @@ const MatchingRulesView:
React.FC<RouteModule.Step1PassProps> = ({
</Space>
),
},
- ].filter(item => Object.keys(item).length)
+ ].filter((item) => Object.keys(item).length);
const renderModal = () => (
<Modal
diff --git a/web/src/pages/Route/components/Step1/RequestConfigView.tsx
b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
index 0d41815..20d8457 100644
--- a/web/src/pages/Route/components/Step1/RequestConfigView.tsx
+++ b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
@@ -30,7 +30,7 @@ import {
const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
form,
disabled,
- onChange = () => { },
+ onChange = () => {},
}) => {
const { formatMessage } = useIntl();
const HostList = () => (
diff --git a/web/src/pages/Route/components/Step3/index.tsx
b/web/src/pages/Route/components/Step3/index.tsx
index fcc6b01..3eb30b8 100644
--- a/web/src/pages/Route/components/Step3/index.tsx
+++ b/web/src/pages/Route/components/Step3/index.tsx
@@ -19,17 +19,17 @@ import { Radio, Tooltip } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { isChrome } from 'react-device-detect';
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
import PluginOrchestration from '@api7-dashboard/pluginchart';
+import PluginPage from '@/components/Plugin';
type Props = {
data: {
- plugins: PluginPageType.FinalData;
+ plugins: PluginComponent.Data;
script: Record<string, any>;
};
- onChange(data: { plugins: PluginPageType.FinalData; script: any }): void;
+ onChange(data: { plugins: PluginComponent.Data; script: any }): void;
readonly?: boolean;
- isForceHttps: boolean
+ isForceHttps: boolean;
};
type Mode = 'NORMAL' | 'DRAW';
@@ -61,42 +61,40 @@ const Page: React.FC<Props> = ({ data, onChange, readonly =
false, isForceHttps
</Radio.Group>
{Boolean(disableDraw) && (
<div style={{ marginLeft: '10px' }}>
- <Tooltip placement="right" title={() => {
- // NOTE: forceHttps do not support DRAW mode
- // TODO: i18n
- const titleArr: string[] = [];
- if (!isChrome) {
- titleArr.push('插件编排仅支持 Chrome 浏览器。');
- }
- if (isForceHttps) {
- titleArr.push('当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。');
- }
- return (
- titleArr.map((item, index) => `${index + 1}.${item}`).join("")
- )
- }}>
+ <Tooltip
+ placement="right"
+ title={() => {
+ // NOTE: forceHttps do not support DRAW mode
+ // TODO: i18n
+ const titleArr: string[] = [];
+ if (!isChrome) {
+ titleArr.push('插件编排仅支持 Chrome 浏览器。');
+ }
+ if (isForceHttps) {
+ titleArr.push('当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。');
+ }
+ return titleArr.map((item, index) => `${index +
1}.${item}`).join('');
+ }}
+ >
<QuestionCircleOutlined />
</Tooltip>
</div>
)}
</div>
- {
- Boolean(mode === 'NORMAL') && (
- <PluginPage
- initialData={plugins}
- onChange={(pluginsData) => onChange({ plugins: pluginsData,
script: {} })}
- />
- )
- }
- {
- Boolean(mode === 'DRAW') && (
- <PluginOrchestration
- data={script?.chart}
- onChange={(scriptData) => onChange({ plugins: {}, script:
scriptData })}
- readonly={readonly}
- />
- )
- }
+ {Boolean(mode === 'NORMAL') && (
+ <PluginPage
+ initialData={plugins}
+ schemaType="route"
+ onChange={(pluginsData) => onChange({ plugins: pluginsData, script:
{} })}
+ />
+ )}
+ {Boolean(mode === 'DRAW') && (
+ <PluginOrchestration
+ data={script?.chart}
+ onChange={(scriptData) => onChange({ plugins: {}, script: scriptData
})}
+ readonly={readonly}
+ />
+ )}
</>
);
};
diff --git a/web/yarn.lock b/web/yarn.lock
index ec89ce3..754e9a9 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -187,18 +187,6 @@
lodash "^4.17.15"
resize-observer-polyfill "^1.5.0"
-"@api7-dashboard/plugin@^1.0.15":
- version "1.0.15"
- resolved
"https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.15.tgz#690ab5666125ef7e45c6841fa348e9f3870b890b"
- integrity
sha512-B7B0zmRxWQwNjdqm6GI91E5QLaJ3Dzs6VE3OP6zVz5Mlgx3KuyfYGwB8E0jr+ApeB59RvgHAGgRExN6YDMTzFA==
- dependencies:
- "@rjsf/antd" "^2.3.0"
- "@rjsf/core" "^2.3.0"
- "@uiw/react-codemirror" "^3.0.1"
- ajv "^6.12.5"
- json-schema "^0.2.5"
- set-value "^3.0.2"
-
"@api7-dashboard/plugin@^1.0.6":
version "1.0.6"
resolved
"https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.6.tgz#d17bd53da9151c0fee2d5591965b642bc7e1005d"
@@ -2395,6 +2383,11 @@
jest-diff "^25.2.1"
pretty-format "^25.2.1"
+"@types/js-beautify@^1.13.1":
+ version "1.13.1"
+ resolved
"https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.13.1.tgz#d4739266c5dcad561226cd1ec5407fa0542d863d"
+ integrity
sha512-F3YCoZS//n74Wu+hxoVrxX1H8qaWo+WAgQ+ObmFH4ZFwI0fIwiJTW7pvkCRShw8ST7+ej7sB68K+ZHAZgK4S4Q==
+
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4",
"@types/json-schema@^7.0.5":
version "7.0.5"
resolved
"https://registry.npm.taobao.org/@types/json-schema/download/@types/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
@@ -3756,6 +3749,11 @@ abab@^2.0.0:
resolved
"https://registry.npm.taobao.org/abab/download/abab-2.0.4.tgz?cache=0&sync_timestamp=1596258082074&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fabab%2Fdownload%2Fabab-2.0.4.tgz#6dfa57b417ca06d21b2478f0e638302f99c2405c"
integrity sha1-bfpXtBfKBtIbJHjw5jgwL5nCQFw=
+abbrev@1:
+ version "1.1.1"
+ resolved
"https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+ integrity
sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
accepts@~1.3.5, accepts@~1.3.7:
version "1.3.7"
resolved
"https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -5541,6 +5539,14 @@ concat-stream@^1.5.0, concat-stream@^1.5.2,
concat-stream@^1.6.2:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+config-chain@^1.1.12:
+ version "1.1.12"
+ resolved
"https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
+ integrity
sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
confusing-browser-globals@^1.0.5, confusing-browser-globals@^1.0.9:
version "1.0.9"
resolved
"https://registry.npm.taobao.org/confusing-browser-globals/download/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd"
@@ -6596,6 +6602,16 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
+editorconfig@^0.15.3:
+ version "0.15.3"
+ resolved
"https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
+ integrity
sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
+ dependencies:
+ commander "^2.19.0"
+ lru-cache "^4.1.5"
+ semver "^5.6.0"
+ sigmund "^1.0.1"
+
[email protected]:
version "1.1.1"
resolved
"https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -9059,7 +9075,7 @@ [email protected]:
resolved
"https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-ini@^1.3.5:
+ini@^1.3.4, ini@^1.3.5:
version "1.3.5"
resolved
"https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=
@@ -10214,6 +10230,17 @@ joi@^17.1.1:
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
+js-beautify@^1.13.0:
+ version "1.13.0"
+ resolved
"https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.13.0.tgz#a056d5d3acfd4918549aae3ab039f9f3c51eebb2"
+ integrity
sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==
+ dependencies:
+ config-chain "^1.1.12"
+ editorconfig "^0.15.3"
+ glob "^7.1.3"
+ mkdirp "^1.0.4"
+ nopt "^5.0.0"
+
js-file-download@^0.4.1:
version "0.4.12"
resolved
"https://registry.npm.taobao.org/js-file-download/download/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821"
@@ -10976,6 +11003,14 @@ lowlight@^1.14.0:
fault "^1.0.0"
highlight.js "~10.1.0"
+lru-cache@^4.1.5:
+ version "4.1.5"
+ resolved
"https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity
sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
lru-cache@^5.1.1:
version "5.1.1"
resolved
"https://registry.npm.taobao.org/lru-cache/download/lru-cache-5.1.1.tgz?cache=0&sync_timestamp=1594427569171&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -11499,7 +11534,7 @@ [email protected], mkdirp@^0.5.1, mkdirp@^0.5.3,
mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp
dependencies:
minimist "^1.2.5"
[email protected], mkdirp@^1.0.0:
[email protected], mkdirp@^1.0.0, mkdirp@^1.0.4:
version "1.0.4"
resolved
"https://registry.npm.taobao.org/mkdirp/download/mkdirp-1.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha1-PrXtYmInVteaXw4qIh3+utdcL34=
@@ -11789,6 +11824,13 @@ node-releases@^1.1.13, node-releases@^1.1.60:
resolved
"https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.60.tgz?cache=0&sync_timestamp=1595485348533&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084"
integrity sha1-aUi9/OgobwtdDlqI6DhOlU3+cIQ=
+nopt@^5.0.0:
+ version "5.0.0"
+ resolved
"https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+ integrity
sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+ dependencies:
+ abbrev "1"
+
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4,
normalize-package-data@^2.5.0:
version "2.5.0"
resolved
"https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -13494,6 +13536,11 @@ property-information@^5.0.0:
dependencies:
xtend "^4.0.0"
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved
"https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+ integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+
protocols@^1.1.0, protocols@^1.4.0:
version "1.4.8"
resolved
"https://registry.npm.taobao.org/protocols/download/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"
@@ -13524,6 +13571,11 @@ [email protected]:
dependencies:
event-stream "=3.3.4"
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved
"https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+
psl@^1.1.28:
version "1.8.0"
resolved
"https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@@ -15701,6 +15753,11 @@ side-channel@^1.0.2:
es-abstract "^1.18.0-next.0"
object-inspect "^1.8.0"
+sigmund@^1.0.1:
+ version "1.0.1"
+ resolved
"https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
+ integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
+
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved
"https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -18279,6 +18336,11 @@ y18n@^4.0.0:
resolved
"https://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha1-le+U+F7MgdAHwmThkKEg8KPIVms=
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved
"https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+
yallist@^3.0.2:
version "3.1.1"
resolved
"https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"