nocobase/packages/core/client/src/pm/PluginCard.tsx
jack zhang 705b7449f0
feat: new plugin manager, supports adding plugins through UI (#2430)
* 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>
2023-09-12 22:39:23 +08:00

220 lines
6.7 KiB
TypeScript

import { App, Card, Divider, Popconfirm, Space, Switch, Typography, message } from 'antd';
import classnames from 'classnames';
import React, { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { DeleteOutlined, ReadOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { useAPIClient } from '../api-client';
import { PluginDetail } from './PluginDetail';
import { PluginUpgradeModal } from './PluginForm/modal/PluginUpgradeModal';
import { useStyles } from './style';
import type { IPluginData } from './types';
interface IPluginInfo extends IPluginCard {
onClick: () => void;
}
function PluginInfo(props: IPluginInfo) {
const { data, onClick } = props;
const { name, displayName, isCompatible, packageName, updatable, builtIn, enabled, description, type, error } = data;
const { styles, theme } = useStyles();
const navigate = useNavigate();
const { t } = useTranslation();
const api = useAPIClient();
const { modal } = App.useApp();
const [showUploadForm, setShowUploadForm] = useState(false);
const [enabledVal, setEnabledVal] = useState(enabled);
const reload = () => window.location.reload();
return (
<>
{showUploadForm && (
<PluginUpgradeModal
isShow={showUploadForm}
pluginData={data}
onClose={(isRefresh) => {
setShowUploadForm(false);
}}
/>
)}
<Card
size={'small'}
bordered={false}
onClick={() => {
!error && onClick();
}}
headStyle={{ border: 'none', minHeight: 'inherit', paddingTop: 14 }}
bodyStyle={{ paddingTop: 10 }}
// style={{ marginBottom: theme.marginLG }}
title={<div>{displayName || name || packageName}</div>}
hoverable
className={css`
.ant-card-actions {
li .ant-space {
gap: 2px !important;
}
li a {
.anticon {
margin-right: 3px;
/* display: none; */
}
}
li:last-child {
width: 20% !important;
}
li:first-child {
width: 80% !important;
border-inline-end: 0;
text-align: left;
padding-left: 16px;
}
}
`}
actions={[
<Space split={<Divider type="vertical" />} key={'1'}>
<a key={'5'}>
<ReadOutlined /> {t('Docs')}
</a>
{updatable && (
<a
key={'3'}
onClick={(e) => {
e.stopPropagation();
setShowUploadForm(true);
}}
>
<ReloadOutlined /> {t('Update')}
</a>
)}
{enabled ? (
<a
onClick={(e) => {
e.stopPropagation();
navigate(`/admin/settings/${name}`);
}}
>
<SettingOutlined /> {t('Setting')}
</a>
) : (
<Popconfirm
key={'delete'}
disabled={builtIn}
title={t('Are you sure to delete this plugin?')}
onConfirm={async (e) => {
e.stopPropagation();
api.request({
url: `pm:remove`,
params: {
filterByTk: name,
},
});
}}
onCancel={(e) => e.stopPropagation()}
okText={t('Yes')}
cancelText={t('No')}
>
<a
onClick={(e) => {
e.stopPropagation();
}}
className={classnames({ [styles.cardActionDisabled]: builtIn })}
>
<DeleteOutlined /> {t('Remove')}
</a>
</Popconfirm>
)}
</Space>,
<Switch
key={'enable'}
size={'small'}
disabled={builtIn || error}
onChange={async (checked, e) => {
e.stopPropagation();
if (!isCompatible && checked) {
message.error(t("Dependencies check failed, can't enable."));
return;
}
await api.request({
url: `pm:${checked ? 'enable' : 'disable'}`,
params: {
filterByTk: name,
},
});
}}
checked={enabledVal}
></Switch>,
].filter(Boolean)}
>
<Card.Meta
description={
!error ? (
<Typography.Paragraph
style={{ height: theme.fontSize * theme.lineHeight * 3 }}
type={isCompatible ? 'secondary' : 'danger'}
ellipsis={{ rows: 3 }}
>
{isCompatible ? description : t('Plugin dependencies check failed')}
</Typography.Paragraph>
) : (
<Typography.Text type="danger">
{t('Plugin loading failed. Please check the server logs.')}
</Typography.Text>
)
}
/>
{/* {!isCompatible && !error && (
<Button style={{ padding: 0 }} type="link">
<Typography.Text type="danger">{t('Dependencies check failed')}</Typography.Text>
</Button>
)} */}
{/*
<Col span={8}>
<Space direction="vertical" align="end" style={{ display: 'flex', marginTop: -10 }}>
{type && (
<Button
onClick={(e) => {
e.stopPropagation();
setShowUploadForm(true);
}}
ghost
type="primary"
>
{t('Update plugin')}
</Button>
)}
{!error && (
<Button style={{ padding: 0 }} type="link">
{t('More details')}
</Button>
)}
</Space>
</Col> */}
</Card>
</>
);
}
export interface IPluginCard {
data: IPluginData;
}
export const PluginCard: FC<IPluginCard> = (props) => {
const { data } = props;
const [plugin, setPlugin] = useState<IPluginData>(undefined);
return (
<>
{plugin && <PluginDetail plugin={plugin} onCancel={() => setPlugin(undefined)} />}
<PluginInfo
onClick={() => {
setPlugin(data);
}}
data={data}
/>
</>
);
};