Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion packages/plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@api7-dashboard/plugin",
"version": "1.0.4",
"version": "1.0.5",
"description": "@api7-dashboard/plugin",
"repository": {
"type": "git",
Expand All @@ -26,6 +26,7 @@
"@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"
},
Expand Down
107 changes: 61 additions & 46 deletions packages/plugin/src/PluginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import { SettingOutlined, InfoOutlined } from '@ant-design/icons';
import { EyeFilled, SettingFilled } from '@ant-design/icons';
import { JSONSchema7 } from 'json-schema';
import { Anchor, Layout, Switch, Card, Tooltip, Button, notification } from 'antd';
import { omit } from 'lodash';
import Ajv from 'ajv';

// @ts-ignore
import { PanelSection } from '@api7-dashboard/ui';
Expand All @@ -20,12 +21,21 @@ type Props = {
onChange?(data: PluginPage.FinalData): void;
};

enum Category {
'Limit traffic',
'Observability',
'Security',
'Authentication',
'Log',
'Other',
}

const PanelSectionStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(4, 25%)',
gridTemplateColumns: 'repeat(3, 33.333333%)',
gridRowGap: 15,
gridColumnGap: 10,
width: 'calc(100% - 40px)',
width: 'calc(100% - 20px)',
};

const { Sider, Content } = Layout;
Expand All @@ -38,7 +48,9 @@ const PluginPageApp: React.FC<Props> = ({ initialData = {}, readonly, onChange =

useEffect(() => {
getList(initialData).then(setAllPlugins);
}, []);
}, [initialData]);

const ajv = new Ajv();

return (
<>
Expand All @@ -56,16 +68,17 @@ const PluginPageApp: React.FC<Props> = ({ initialData = {}, readonly, onChange =
})}
</Anchor>
</Sider>
<Content style={{ padding: '0 10px', backgroundColor: '#fff' }}>
{Object.entries(allPlugins).map(([category, plugins]) => {
return (
<Content style={{ padding: '0 10px', backgroundColor: '#fff', minHeight: 1300 }}>
{Object.keys(allPlugins)
.sort((a, b) => Category[a] - Category[b])
.map((category) => (
<PanelSection
title={category}
key={category}
style={PanelSectionStyle}
id={`plugin-category-${category}`}
>
{plugins.map(({ name, enabled }) => (
{allPlugins[category].map(({ name, enabled }) => (
<Card
key={name}
title={
Expand All @@ -77,54 +90,65 @@ const PluginPageApp: React.FC<Props> = ({ initialData = {}, readonly, onChange =
{name}
</a>
}
style={{ height: 66 }}
extra={[
<Tooltip title="View Raw" key={`plugin-card-${name}-extra-tooltip`}>
<Button
shape="circle"
icon={<InfoOutlined />}
size="small"
style={{ marginRight: 10 }}
icon={<EyeFilled />}
size="middle"
onClick={() => {
setCodeMirrorCodes(initialData[name]);
}}
/>
</Tooltip>,
<Tooltip title="Setting" key={`plugin-card-${name}-extra-tooltip-2`}>
<Button
disabled={PLUGIN_MAPPER_SOURCE[name]?.noConfiguration}
shape="circle"
icon={<SettingFilled />}
style={{ marginRight: 10, marginLeft: 10 }}
size="middle"
onClick={() => {
fetchPluginSchema(name!).then((schemaData) => {
setSchema(schemaData);
setTimeout(() => {
setPluginName(name);
}, 300);
});
}}
/>
</Tooltip>,
<Switch
defaultChecked={enabled}
disabled={readonly}
onChange={(isChecked) => {
// NOTE: 当前生命周期为:若关闭插件,则移除数据状态;再启用时,该插件一定是没有状态的。
const data = { ...initialData, [name]: initialData[name] || {} };
if (isChecked) {
notification.info({ message: `请配置插件 ${name}` });
onChange(data);
const data = initialData[name] || {};
fetchPluginSchema(name!).then((schemaData) => {
const validate = ajv.validate(schemaData, data);
if (validate) {
onChange({ ...initialData, [name]: data });
} else {
notification.warning({ message: `请配置插件 ${name}` });
setSchema(schemaData);
setTimeout(() => {
setPluginName(name);
}, 300);
}
});
} else {
onChange(omit(data, [name!]));
onChange(omit({ ...initialData }, [name!]));
}
}}
key={`plugin-card-${name}-extra-switch-${enabled}`}
key={Math.random().toString(36).substring(7)}
/>,
]}
actions={[
<SettingOutlined
onClick={() => {
fetchPluginSchema(name!).then((schemaData) => {
setSchema(schemaData);
setTimeout(() => {
setPluginName(name);
}, 300);
});
}}
/>,
]}
>
{/* TODO: https://github.com/ant-design/pro-components/pull/379/files#diff-9b2c55deb25c2f8fec0e59c7bf59ce4aR75 */}
<Card.Meta description="暂无简介" />
</Card>
></Card>
))}
</PanelSection>
);
})}
))}
</Content>
</Layout>
<PluginDrawer
Expand All @@ -134,22 +158,13 @@ const PluginPageApp: React.FC<Props> = ({ initialData = {}, readonly, onChange =
}
readonly={readonly}
schema={schema!}
onClose={() => setPluginName(undefined)}
onClose={() => {
setPluginName(undefined);
}}
onFinish={(value) => {
if (!pluginName) {
return;
}

const { category = 'Other' } = PLUGIN_MAPPER_SOURCE[pluginName] || {};
const newAllPlugins = { ...allPlugins };
newAllPlugins[category] = newAllPlugins[category].map((item) => {
if (item.name === pluginName) {
return { ...item, enabled: true };
}
return item;
});
setAllPlugins(newAllPlugins);

onChange({
...initialData,
[pluginName]: transformPlugin(pluginName, value, 'request'),
Expand Down
48 changes: 36 additions & 12 deletions packages/plugin/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,42 @@ import { PluginPage } from './typing.d';

export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginPage.PluginMapperItem, 'name'>> = {
'limit-req': {
category: 'Limit',
category: 'Limit traffic',
},
'limit-count': {
category: 'Limit',
category: 'Limit traffic',
},
'limit-conn': {
category: 'Limit',
category: 'Limit traffic',
},
prometheus: {
category: 'Observability',
noConfiguration: true
},
skywalking: {
category: 'Observability'
},
zipkin: {
category: 'Observability',
},
'request-id': {
category: 'Observability',
},
'key-auth': {
category: 'Security',
category: 'Authentication',
},
'basic-auth': {
category: 'Security',
},
prometheus: {
category: 'Metric',
category: 'Authentication',
},
'node-status': {
category: 'Other',
noConfiguration: true
},
'jwt-auth': {
category: 'Security',
category: 'Authentication',
},
zipkin: {
category: 'Metric',
'authz-keycloak': {
category: 'Authentication',
},
'ip-restriction': {
category: 'Security',
Expand All @@ -42,7 +53,7 @@ export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginPage.PluginMapperIt
category: 'Other',
},
'openid-connect': {
category: 'Security',
category: 'Authentication',
},
'proxy-rewrite': {
category: 'Other',
Expand Down Expand Up @@ -80,12 +91,19 @@ export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginPage.PluginMapperIt
cors: {
category: 'Security',
},
'uri-blocker': {
category: 'Security',
},
'request-validator': {
category: 'Security',
},
heartbeat: {
category: 'Other',
hidden: true,
},
'batch-requests': {
category: 'Other',
noConfiguration: true
},
'http-logger': {
category: 'Log',
Expand All @@ -96,4 +114,10 @@ export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginPage.PluginMapperIt
oauth: {
category: 'Security',
},
syslog: {
category: 'Log',
},
echo: {
category: 'Log',
}
};
15 changes: 12 additions & 3 deletions packages/plugin/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import request from './request';

export const fetchPluginList = (): Promise<string[]> => request<string[]>('/plugins');

let cachedPluginNameList: string[] = []
export const getList = async (plugins: Record<string, object>) => {
const names = await fetchPluginList();
if (!cachedPluginNameList.length) {
cachedPluginNameList = await fetchPluginList()
}
const names = cachedPluginNameList;
const data: Record<string, PluginPage.PluginMapperItem[]> = {};
const enabledPluginNames = Object.keys(plugins);
names.forEach((name) => {
Expand All @@ -30,5 +34,10 @@ export const getList = async (plugins: Record<string, object>) => {
return data;
};

export const fetchPluginSchema = (name: string): Promise<JSONSchema7> =>
request(`/schema/plugins/${name}`).then((data: any) => transformPlugin(name, data, 'schema'));
const cachedPluginSchema: Record<string, object> = {}
export const fetchPluginSchema = async (name: string): Promise<JSONSchema7> => {
if (!cachedPluginSchema[name]) {
cachedPluginSchema[name] = await request(`/schema/plugins/${name}`)
}
return transformPlugin(name, cachedPluginSchema[name], 'schema')
}
4 changes: 3 additions & 1 deletion packages/plugin/src/typing.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export declare namespace PluginPage {
type PluginCategory = 'Security' | 'Limit' | 'Log' | 'Metric' | 'Other';
// NOTE: 需要与 PluginPage 文件中的 Category 枚举值同步
type PluginCategory = 'Security' | 'Limit traffic' | 'Log' | 'Observability' | 'Other' | 'Authentication';

type PluginMapperItem = {
category: PluginCategory;
hidden?: boolean;
name: string;
enabled?: boolean;
noConfiguration?: boolean;
};

type PluginProps = PluginMapperItem & {
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4834,6 +4834,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"

ajv@^6.12.5:
version "6.12.5"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"

align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
Expand Down