import { css } from '@emotion/css'; import { Layout, Menu, message, Modal, PageHeader, Popconfirm, Result, Space, Spin, Table, TableProps, Tabs, TabsProps, Tag, Typography, } from 'antd'; import { sortBy } from 'lodash'; import React, { createContext, useContext, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Redirect, useHistory, useRouteMatch } from 'react-router-dom'; import { ACLPane } from '../acl'; import { useACLRoleContext } from '../acl/ACLProvider'; import { useAPIClient, useRequest } from '../api-client'; import { CollectionManagerPane } from '../collection-manager'; import { useDocumentTitle } from '../document-title'; import { Icon } from '../icon'; import { RouteSwitchContext } from '../route-switch'; import { useCompile, useTableSize } from '../schema-component'; import { useParseMarkdown } from '../schema-component/antd/markdown/util'; import { BlockTemplatesPane } from '../schema-templates'; import { SystemSettingsPane } from '../system-settings'; const { Link } = Typography; export const SettingsCenterContext = createContext({}); interface PluginTableProps { filter: any; builtIn?: boolean; } interface PluginDocumentProps { path: string; name: string; } const PluginDocument: React.FC = (props) => { const { data, loading, error } = useRequest( { url: '/plugins:getTabInfo', params: { filterByTk: props.name, path: props.path, }, }, { refreshDeps: [props.name, props.path], }, ); const { html, loading: parseLoading } = useParseMarkdown(data?.data?.content); return (
{loading || parseLoading ? ( ) : (
)}
); }; const PluginTable: React.FC = (props) => { const { builtIn } = props; const history = useHistory(); const api = useAPIClient(); const [plugin, setPlugin] = useState(null); const { t, i18n } = useTranslation(); const settingItems = useContext(SettingsCenterContext); const { data, loading } = useRequest({ url: 'applicationPlugins:list', params: { filter: props.filter, sort: 'name', paginate: false, }, }); const { data: tabsData, run } = useRequest( { url: '/plugins:getTabs', }, { manual: true, }, ); const columns = useMemo['columns']>(() => { return [ { title: t('Plugin name'), dataIndex: 'displayName', width: 300, render: (displayName, record) => displayName || record.name, }, { title: t('Description'), dataIndex: 'description', ellipsis: true, }, { title: t('Version'), dataIndex: 'version', width: 300, }, // { // title: t('Author'), // dataIndex: 'author', // width: 200, // }, { title: t('Actions'), width: 300, render(data) { return ( { setPlugin(data); run({ params: { filterByTk: data.name, }, }); }} > {t('View')} {data.enabled && settingItems[data.name] ? ( { history.push(`/admin/settings/${data.name}`); }} > {t('Setting')} ) : null} {!builtIn ? ( <> { const checked = !data.enabled; Modal.warn({ title: checked ? t('Plugin staring') : t('Plugin stopping'), content: t('The application is reloading, please do not close the page.'), okButtonProps: { style: { display: 'none', }, }, }); await api.request({ url: `pm:${checked ? 'enable' : 'disable'}/${data.name}`, }); window.location.reload(); // message.success(checked ? t('插件激活成功') : t('插件禁用成功')); }} > {t(data.enabled ? 'Disable' : 'Enable')} { await api.request({ url: `pm:remove/${data.name}`, }); message.success(t('插件删除成功')); window.location.reload(); }} onCancel={() => {}} okText={t('Yes')} cancelText={t('No')} > {t('Delete')} ) : null} ); }, }, ]; }, [t, builtIn]); const items = useMemo(() => { return tabsData?.data?.tabs.map((item) => { return { label: item.title, key: item.path, children: React.createElement(PluginDocument, { name: tabsData?.data.filterByTk, path: item.path, }), }; }); }, [tabsData?.data]); const { height, tableSizeRefCallback } = useTableSize(); return (
{plugin?.displayName || plugin?.name} v{plugin?.version} } open={!!plugin} onCancel={() => setPlugin(null)} > {plugin?.description &&
{plugin?.description}
}
); }; const LocalPlugins = () => { return ( ); }; const BuiltinPlugins = () => { return ( ); }; const MarketplacePlugins = () => { const { t } = useTranslation(); return
{t('Coming soon...')}
; }; const PluginList = (props) => { const match = useRouteMatch(); const history = useHistory(); const { tabName = 'local' } = match.params || {}; const { setTitle } = useDocumentTitle(); const { t } = useTranslation(); const { snippets = [] } = useACLRoleContext(); return snippets.includes('pm') ? (
{ history.push(`/admin/pm/list/${activeKey}`); }} > } />
{React.createElement( { local: LocalPlugins, 'built-in': BuiltinPlugins, marketplace: MarketplacePlugins, }[tabName], )}
) : ( ); }; const settings = { acl: { title: '{{t("ACL")}}', icon: 'LockOutlined', tabs: { roles: { isBookmark: true, title: '{{t("Roles & Permissions")}}', component: ACLPane, }, }, }, 'ui-schema-storage': { title: '{{t("Block templates")}}', icon: 'LayoutOutlined', tabs: { 'block-templates': { title: '{{t("Block templates")}}', component: BlockTemplatesPane, }, }, }, 'collection-manager': { icon: 'DatabaseOutlined', title: '{{t("Collection manager")}}', tabs: { collections: { isBookmark: true, title: '{{t("Collections & Fields")}}', component: CollectionManagerPane, }, }, }, 'system-settings': { icon: 'SettingOutlined', title: '{{t("System settings")}}', tabs: { 'system-settings': { isBookmark: true, title: '{{t("System settings")}}', component: SystemSettingsPane, }, }, }, }; export const getPluginsTabs = (items, snippets) => { const pluginsTabs = Object.keys(items).map((plugin) => { const tabsObj = items[plugin].tabs; const tabs = sortBy( Object.keys(tabsObj).map((tab) => { return { key: tab, ...tabsObj[tab], isAllow: snippets.includes('pm.*') && !snippets?.includes(`!pm.${plugin}.${tab}`), }; }), (o) => !o.isAllow, ); return { ...items[plugin], key: plugin, tabs, isAllow: !tabs.every((v) => !v.isAllow), }; }); return sortBy(pluginsTabs, (o) => !o.isAllow); }; const SettingsCenter = (props) => { const { snippets = [] } = useACLRoleContext(); const match = useRouteMatch(); const history = useHistory(); const items = useContext(SettingsCenterContext); const pluginsTabs = getPluginsTabs(items, snippets); const compile = useCompile(); const firstUri = useMemo(() => { const pluginName = pluginsTabs[0].key; const tabName = pluginsTabs[0].tabs[0].key; return `/admin/settings/${pluginName}/${tabName}`; }, [pluginsTabs]); const { pluginName, tabName } = match.params || {}; const activePlugin = pluginsTabs.find((v) => v.key === pluginName); const aclPluginTabCheck = activePlugin?.isAllow && activePlugin.tabs.find((v) => v.key === tabName)?.isAllow; if (!pluginName) { return ; } if (!items[pluginName]) { return ; } if (!tabName) { const firstTabName = Object.keys(items[pluginName]?.tabs).shift(); return ; } const component = items[pluginName]?.tabs?.[tabName]?.component; const plugin: any = pluginsTabs.find((v) => v.key === pluginName); const menuItems: any = pluginsTabs .filter((plugin) => plugin.isAllow) .map((plugin) => { return { label: compile(plugin.title), key: plugin.key, icon: plugin.icon ? : null, }; }); return (
} className={css` width: var(--side-menu-width); overflow: hidden; flex: 0 0 var(--side-menu-width); max-width: var(--side-menu-width); min-width: var(--side-menu-width); pointer-events: none; `} >
{ const item = items[e.key]; const tabKey = Object.keys(item.tabs).shift(); history.push(`/admin/settings/${e.key}/${tabKey}`); }} items={menuItems as any} /> {aclPluginTabCheck && ( { history.push(`/admin/settings/${pluginName}/${activeKey}`); }} > {plugin.tabs?.map((tab) => { return tab.isAllow && ; })} } /> )}
{aclPluginTabCheck ? ( component && React.createElement(component) ) : ( )}
); }; export const SettingsCenterProvider = (props) => { const { settings = {} } = props; const items = useContext(SettingsCenterContext); return ( {props.children} ); }; export const PMProvider = (props) => { const { routes, ...others } = useContext(RouteSwitchContext); routes[1].routes.unshift( { type: 'route', path: '/admin/pm/list/:tabName?', component: PluginList, }, { type: 'route', path: '/admin/settings/:pluginName?/:tabName?', component: SettingsCenter, uiSchemaUid: routes[1].uiSchemaUid, }, ); return ( {props.children} ); }; export default PMProvider; export * from './PluginManagerLink';