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/11/28 01:04:49 UTC
[apisix-dashboard] 01/01: feat(plugin): added code mirror
This is an automated email from the ASF dual-hosted git repository.
juzhiyuan pushed a commit to branch juzhiyuan/feat-plugin
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
commit 09ad0c28527739231da8e9ca284681ead162358e
Author: juzhiyuan <ju...@apache.org>
AuthorDate: Sat Nov 28 09:04:30 2020 +0800
feat(plugin): added code mirror
---
web/package.json | 1 +
web/src/components/Plugin/CodeMirrorDrawer.tsx | 83 +++++++++++
web/src/components/Plugin/IconFont.tsx | 24 ++++
web/src/components/Plugin/PluginPage.tsx | 189 +++++++++++++++++++++++++
web/src/components/Plugin/data.tsx | 169 ++++++++++++++++++++++
web/src/components/Plugin/index.ts | 17 +++
web/src/components/Plugin/service.ts | 98 +++++++++++++
web/src/components/Plugin/typing.d.ts | 39 +++++
web/src/pages/Route/Create.tsx | 7 +-
web/src/pages/Route/components/Step3/index.tsx | 69 +++++----
10 files changed, 658 insertions(+), 38 deletions(-)
diff --git a/web/package.json b/web/package.json
index da5799d..05ce18f 100644
--- a/web/package.json
+++ b/web/package.json
@@ -60,6 +60,7 @@
"@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",
diff --git a/web/src/components/Plugin/CodeMirrorDrawer.tsx b/web/src/components/Plugin/CodeMirrorDrawer.tsx
new file mode 100644
index 0000000..b382d85
--- /dev/null
+++ b/web/src/components/Plugin/CodeMirrorDrawer.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 } from 'antd';
+import CodeMirror from '@uiw/react-codemirror';
+
+type Props = {
+ visible?: boolean;
+ data?: object;
+ readonly?: boolean;
+ onClose?: () => void;
+ onSubmit?: (data: object) => void;
+};
+
+const CodeMirrorDrawer: React.FC<Props> = ({
+ visible = false,
+ readonly = false,
+ data = {},
+ onClose,
+ onSubmit,
+}) => {
+ const ref = useRef<any>(null);
+ return (
+ <Drawer
+ visible={visible}
+ width={500}
+ 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>
+ )
+ }
+ >
+ <CodeMirror
+ ref={ref}
+ value={JSON.stringify(data, null, 2)}
+ options={{
+ mode: 'json-ld',
+ readOnly: readonly ? 'nocursor' : '',
+ lineWrapping: true,
+ lineNumbers: true,
+ showCursorWhenSelecting: true,
+ autofocus: true,
+ }}
+ />
+ </Drawer>
+ );
+};
+
+export default CodeMirrorDrawer;
diff --git a/web/src/components/Plugin/IconFont.tsx b/web/src/components/Plugin/IconFont.tsx
new file mode 100644
index 0000000..4889720
--- /dev/null
+++ b/web/src/components/Plugin/IconFont.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { createFromIconfontCN } from '@ant-design/icons';
+
+// NOTE: Icons from AliCDN https://www.iconfont.cn/manage/index
+const IconFont = createFromIconfontCN({
+ scriptUrl: '//at.alicdn.com/t/font_2088089_a3klmsocd15.js',
+});
+
+export default IconFont;
diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx
new file mode 100644
index 0000000..d5bfabd
--- /dev/null
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -0,0 +1,189 @@
+/*
+ * 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 { PLUGIN_MAPPER_SOURCE } from './data';
+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);
+ }, []);
+
+ 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,
+ }}
+ />
+ ),
+ <a
+ href={`https://github.com/apache/apisix/blob/master/doc/plugins/${item.name}.md`}
+ style={{ color: 'inherit' }}
+ target="_blank"
+ rel="noreferrer"
+ >
+ {item.name}
+ </a>,
+ ]}
+ style={{ height: 66 }}
+ extra={[
+ <Tooltip title="Setting" key={`plugin-card-${item.name}-extra-tooltip-2`}>
+ <Button
+ disabled={PLUGIN_MAPPER_SOURCE[item.name]?.noConfiguration}
+ 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) {
+ setName(item.name);
+ onChange({
+ ...initialData,
+ [item.name]: { ...initialData[item.name], disable: false },
+ });
+ } else {
+ onChange({
+ ...initialData,
+ [item.name]: { ...initialData[item.name], disable: true },
+ });
+ }
+ }}
+ key={Math.random().toString(36).substring(7)}
+ />,
+ ]}
+ />
+ ))}
+ </PanelSection>
+ );
+ })}
+ </Content>
+ </Layout>
+ <CodeMirrorDrawer
+ visible={name !== NEVER_EXIST_PLUGIN_FLAG}
+ data={initialData[name]}
+ readonly={readonly}
+ onClose={() => {
+ setName(NEVER_EXIST_PLUGIN_FLAG);
+ }}
+ onSubmit={(value) => {
+ fetchSchema(name, schemaType).then((schema) => {
+ const { valid, errors } = validate(value, schema);
+ if (valid) {
+ onChange({ ...initialData, [name]: { ...value, disable: false } });
+ setName(NEVER_EXIST_PLUGIN_FLAG);
+ return;
+ }
+ errors?.forEach((item) => {
+ notification.error({
+ message: 'Invalid plugin data',
+ description: item.message,
+ });
+ });
+ });
+ }}
+ />
+ </>
+ );
+};
+
+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..29762a0
--- /dev/null
+++ b/web/src/components/Plugin/data.tsx
@@ -0,0 +1,169 @@
+/*
+ * 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',
+ noConfiguration: true,
+ priority: 1,
+ avatar: <IconFont type="iconPrometheus_software_logo" />,
+ },
+ skywalking: {
+ category: 'Observability',
+ priority: 2,
+ avatar: <IconFont type="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',
+ noConfiguration: true,
+ },
+ 'jwt-auth': {
+ category: 'Authentication',
+ priority: 2,
+ avatar: <IconFont type="iconjwt-3" />,
+ },
+ 'authz-keycloak': {
+ category: 'Authentication',
+ priority: 5,
+ avatar: <IconFont type="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 type="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 type="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',
+ noConfiguration: true,
+ },
+ '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/components/Plugin/index.ts b/web/src/components/Plugin/index.ts
new file mode 100644
index 0000000..21da022
--- /dev/null
+++ b/web/src/components/Plugin/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+export { default } from './PluginPage';
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/components/Plugin/typing.d.ts b/web/src/components/Plugin/typing.d.ts
new file mode 100644
index 0000000..c9233ea
--- /dev/null
+++ b/web/src/components/Plugin/typing.d.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+declare namespace PluginComponent {
+ type Data = object;
+
+ type Schema = '' | 'route' | 'consumer';
+
+ type Category =
+ | 'Security'
+ | 'Limit traffic'
+ | 'Log'
+ | 'Observability'
+ | 'Other'
+ | 'Authentication';
+
+ type Meta = {
+ name: string;
+ category: Category;
+ hidden?: boolean;
+ noConfiguration?: 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/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx
index 396008c..379e83c 100644
--- a/web/src/pages/Route/Create.tsx
+++ b/web/src/pages/Route/Create.tsx
@@ -65,7 +65,7 @@ const Page: React.FC<Props> = (props) => {
const [form2] = Form.useForm();
const upstreamRef = useRef<any>();
- const [step, setStep] = useState(1);
+ const [step, setStep] = useState(3);
const [stepHeader, setStepHeader] = useState(STEP_HEADER_4);
const [chart, setChart] = useState(INIT_CHART);
@@ -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/Step3/index.tsx b/web/src/pages/Route/components/Step3/index.tsx
index fcc6b01..324b869 100644
--- a/web/src/pages/Route/components/Step3/index.tsx
+++ b/web/src/pages/Route/components/Step3/index.tsx
@@ -19,17 +19,18 @@ 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 { 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 +62,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}
+ />
+ )}
</>
);
};