mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
* refactor: plugin manager page * fix: bug * feat: addByNpm api * fix: improve the addByNpm * feat: improve applicationPlugins:list api * fix: re-download npm package when restart app * fix: plugin delete api * feat: plugin detail api * feat: zipUrl add api * fix: upload api bug * fix: plugin detail info * feat: upgrade api * fix: upload api * feat: handle plugin load error * feat: support authToken * feat: muti lang * fix: build error * fix: self review * Update plugin-manager.ts * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bugs * fix: detail click and remove isOfficial * fix: upgrade no refresh * fix: file size and type check * fix: bug * fix: upgrade error * fix: bug * fix: bug * fix: plugin card layout * fix: handling exceptional cases * fix: tgz file support * fix: macos compress file * fix: bug * fix: bug * fix: bug * fix: bug * fix: add upgrade npm type * fix: bugs * fix: bug * fix: change plugins static expose url * fix: api prefix * fix: bug * fix: add nginx `/static/plugin/` path * fix: bugs and pr docker build no dts * fix: bug * fix: build tools bug * fix: improve code * fix: build bug * feat: improve plugin info * fix: ui bug * fix: plugin document bug * feat: improve code * feat: improve code * feat: process dev deps check * feat: improve code * feat: process.env.IS_DEV_CMD * fix: do not delete the plugin package * feat: plugin symlink * fix: tsx watch --ignore=./storage/plugins/** * fix: test error * fix: improve code * fix: improve code * fix: emitStartedEvent * fix: improve code * fix: type error * fix: test error * test: console.log * fix: createStoragePluginSymLink * fix: clientStaticMiddleware rename to clientStaticUtils * feat: build tools support plugins folder * fix: 350px * fix: error * feat: client dev support plugin folder * fix: clear cli options * fix: typeError: Converting circular structure to JSON * fix: plugin name * chore: restart application after command * feat: upgrade error & docs * Update v14-changelog.md * Update v14-changelog.md * Update v14-changelog.md * fix: gateway test * refactor(plugin-workflow): add ready state for gracefully tearing down * Revert "chore: restart application after command" This reverts commit 5015274f8e4e06e506e15754b672330330e8c7f8. * chore: stop application whe restart * T 1218 change plugin folder (#2629) * feat: change folder name * feat: change `pm create` command * feat: revert plugin name change * fix: delete samples * feat: change plugins folder * fix: pm create * feat: update docs * fix: link package error * fix: docs * fix: create command * fix: pm add error * fix: create add build * fix: pm creatre + add * feat: add tar command * fix: docs * fix: bug * fix: docs --------- Co-authored-by: chenos <chenlinxh@gmail.com> * feat: docs * Update your-fisrt-plugin.md * Update your-fisrt-plugin.md * chore: application reload * chore: test * fix: pm add error * chore: preset install skip exists plugin * fix: createIfNotExists --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: chareice <chareice@live.com> Co-authored-by: Zhou <zhou.working@gmail.com> Co-authored-by: mytharcher <mytharcher@gmail.com>
223 lines
6.1 KiB
TypeScript
223 lines
6.1 KiB
TypeScript
export * from './PluginManagerLink';
|
|
import { PageHeader } from '@ant-design/pro-layout';
|
|
import { useDebounce } from 'ahooks';
|
|
import { Button, Divider, Input, Result, Space, Spin, Tabs } from 'antd';
|
|
import _ from 'lodash';
|
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
|
|
import { css } from '@emotion/css';
|
|
import { useACLRoleContext } from '../acl/ACLProvider';
|
|
import { useRequest } from '../api-client';
|
|
import { useToken } from '../style';
|
|
import { PluginCard } from './PluginCard';
|
|
import { PluginAddModal } from './PluginForm/modal/PluginAddModal';
|
|
import { useStyles } from './style';
|
|
import { IPluginData } from './types';
|
|
|
|
export interface TData {
|
|
data: IPluginData[];
|
|
meta: IMetaData;
|
|
}
|
|
|
|
export interface IMetaData {
|
|
count: number;
|
|
page: number;
|
|
pageSize: number;
|
|
totalPage: number;
|
|
allowedActions: AllowedActions;
|
|
}
|
|
|
|
export interface AllowedActions {
|
|
view: number[];
|
|
update: number[];
|
|
destroy: number[];
|
|
}
|
|
|
|
const LocalPlugins = () => {
|
|
const { t } = useTranslation();
|
|
const { theme } = useStyles();
|
|
const { data, loading, refresh } = useRequest<TData>({
|
|
url: 'pm:list',
|
|
});
|
|
const filterList = useMemo(() => {
|
|
let list = data?.data || [];
|
|
list = list.reverse();
|
|
return [
|
|
{
|
|
type: 'All',
|
|
list: list,
|
|
},
|
|
{
|
|
type: 'Built-in',
|
|
list: _.filter(list, (item) => item.builtIn),
|
|
},
|
|
{
|
|
type: 'Enabled',
|
|
list: _.filter(list, (item) => item.enabled),
|
|
},
|
|
{
|
|
type: 'Not enabled',
|
|
list: _.filter(list, (item) => !item.enabled),
|
|
},
|
|
{
|
|
type: 'Problematic',
|
|
list: _.filter(list, (item) => !item.isCompatible),
|
|
},
|
|
];
|
|
}, [data?.data]);
|
|
|
|
const [filterIndex, setFilterIndex] = useState(0);
|
|
const [isShowAddForm, setShowAddForm] = useState(false);
|
|
const [searchValue, setSearchValue] = useState('');
|
|
const debouncedSearchValue = useDebounce(searchValue, { wait: 100 });
|
|
|
|
const pluginList = useMemo(() => {
|
|
let list = filterList[filterIndex]?.list || [];
|
|
if (debouncedSearchValue) {
|
|
list = _.filter(
|
|
list,
|
|
(item) =>
|
|
item.name?.includes(debouncedSearchValue) ||
|
|
item.description?.includes(debouncedSearchValue) ||
|
|
item.displayName?.includes(debouncedSearchValue) ||
|
|
item.packageName?.includes(debouncedSearchValue),
|
|
);
|
|
}
|
|
return list;
|
|
}, [filterIndex, filterList, debouncedSearchValue]);
|
|
|
|
const handleSearch = (value: string) => {
|
|
setSearchValue(value);
|
|
};
|
|
|
|
if (loading) {
|
|
return <Spin />;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PluginAddModal
|
|
isShow={isShowAddForm}
|
|
onClose={(isRefresh) => {
|
|
setShowAddForm(false);
|
|
// if (isRefresh) refresh();
|
|
}}
|
|
/>
|
|
<div style={{ width: '100%' }}>
|
|
<div
|
|
style={{ marginBottom: theme.marginLG }}
|
|
className={css`
|
|
justify-content: space-between;
|
|
display: flex;
|
|
align-items: center;
|
|
`}
|
|
>
|
|
<div>
|
|
<Space size={theme.marginXXS} split={<Divider type="vertical" />}>
|
|
{filterList.map((item, index) => (
|
|
<a
|
|
onClick={() => setFilterIndex(index)}
|
|
key={item.type}
|
|
style={{ fontWeight: filterIndex === index ? 'bold' : 'normal' }}
|
|
>
|
|
{t(item.type)}({item.list?.length})
|
|
</a>
|
|
))}
|
|
<Input
|
|
allowClear
|
|
placeholder={t('Search plugin')}
|
|
onChange={(e) => handleSearch(e.currentTarget.value)}
|
|
/>
|
|
</Space>
|
|
</div>
|
|
<div>
|
|
<Space>
|
|
<Button onClick={() => setShowAddForm(true)} type="primary">
|
|
{t('Add new')}
|
|
</Button>
|
|
</Space>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={css`
|
|
--grid-gutter: ${theme.margin}px;
|
|
--extensions-card-width: 350px;
|
|
display: grid;
|
|
grid-column-gap: var(--grid-gutter);
|
|
grid-row-gap: var(--grid-gutter);
|
|
grid-template-columns: repeat(auto-fill, var(--extensions-card-width));
|
|
justify-content: center;
|
|
margin: auto;
|
|
`}
|
|
>
|
|
{pluginList.map((item) => (
|
|
<PluginCard key={item.name} data={item} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const MarketplacePlugins = () => {
|
|
const { token } = useToken();
|
|
const { t } = useTranslation();
|
|
return <div style={{ fontSize: token.fontSizeXL, color: token.colorText }}>{t('Coming soon...')}</div>;
|
|
};
|
|
|
|
export const PluginManager = () => {
|
|
const params = useParams();
|
|
const navigate = useNavigate();
|
|
const { tabName = 'local' } = params;
|
|
const { t } = useTranslation();
|
|
const { snippets = [] } = useACLRoleContext();
|
|
const { styles } = useStyles();
|
|
|
|
useEffect(() => {
|
|
const { tabName } = params;
|
|
if (!tabName) {
|
|
navigate(`/admin/pm/list/local/`, { replace: true });
|
|
}
|
|
}, []);
|
|
|
|
return snippets.includes('pm') ? (
|
|
<div>
|
|
<PageHeader
|
|
className={styles.pageHeader}
|
|
ghost={false}
|
|
title={t('Plugin manager')}
|
|
footer={
|
|
<Tabs
|
|
activeKey={tabName}
|
|
onChange={(activeKey) => {
|
|
navigate(`/admin/pm/list/${activeKey}`);
|
|
}}
|
|
items={[
|
|
{
|
|
key: 'local',
|
|
label: t('Local'),
|
|
},
|
|
{
|
|
key: 'marketplace',
|
|
label: t('Marketplace'),
|
|
},
|
|
]}
|
|
/>
|
|
}
|
|
/>
|
|
<div className={styles.pageContent} style={{ display: 'flex', flexFlow: 'row wrap' }}>
|
|
{React.createElement(
|
|
{
|
|
local: LocalPlugins,
|
|
marketplace: MarketplacePlugins,
|
|
}[tabName],
|
|
)}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<Result status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
|
);
|
|
};
|