/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ export * from './PluginManagerLink'; import { PageHeader } from '@ant-design/pro-layout'; import { useDebounce } from 'ahooks'; import { Button, Col, Divider, Input, List, Modal, Row, Space, Spin, Table, TableProps, 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 { useAPIClient, useRequest } from '../api-client'; import { AppNotFound } from '../common/AppNotFound'; import { useDocumentTitle } from '../document-title'; import { useToken } from '../style'; import { PluginCard } from './PluginCard'; import { PluginAddModal } from './PluginForm/modal/PluginAddModal'; import { useStyles } from './style'; import { IPluginData } from './types'; import { PluginManagerAccountAlert } from './PluginManagerAccountAlert'; 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[]; } function hasIntersection(arr1: any[], arr2: any[]) { if (!Array.isArray(arr1) || !Array.isArray(arr2)) { return false; } return arr1.some((item) => arr2.includes(item)); } function BulkEnableButton({ plugins = [] }) { const { t } = useTranslation(); const api = useAPIClient(); const [items, setItems] = useState(plugins.filter((plugin) => !plugin.enabled)); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedRowKeys, setSelectedRowKeys] = useState([]); return ( <> { await api.request({ url: 'pm:enable', params: { filterByTk: selectedRowKeys, }, }); setIsModalOpen(false); }} onCancel={() => { setSelectedRowKeys([]); setIsModalOpen(false); }} > { setItems( plugins.filter((plugin: { enabled: boolean; displayName: string; description: string }) => { const value = e.target.value; return ( !plugin.enabled && (plugin.displayName.toLowerCase().includes(value.toLowerCase()) || plugin.description?.toLowerCase().includes(value.toLowerCase())) ); }), ); }} /> item.name); setSelectedRowKeys((preSelectedRowKeys) => { if (selectedKeys.length === 0) { return preSelectedRowKeys.filter((key) => !names.includes(key)); } if (selectedKeys.length === names.length) { return _.uniq([...preSelectedRowKeys, ...selectedKeys]); } return preSelectedRowKeys; }); }, onSelect(record) { setSelectedRowKeys((preSelectedRowKeys) => { if (preSelectedRowKeys.includes(record.name)) { return preSelectedRowKeys.filter((key) => key !== record.name); } return preSelectedRowKeys.concat(record.name); }); }, }} rowKey={'name'} scroll={{ y: '60vh', }} size={'small'} pagination={false} columns={ [ { title: t('Plugin'), dataIndex: 'displayName', ellipsis: true, }, { title: t('Description'), dataIndex: 'description', ellipsis: true, width: 300, }, { title: t('Package name'), dataIndex: 'packageName', width: 300, ellipsis: true, }, ] as TableProps['columns'] } dataSource={items} /> ); } const LocalPlugins = () => { const { t } = useTranslation(); const { theme } = useStyles(); const { data, loading, refresh } = useRequest({ 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 [keyword, setKeyword] = useState(null); const debouncedSearchValue = useDebounce(searchValue, { wait: 100 }); const keyWordlists = [ 'Data model tools', 'Data sources', 'Collections', 'Collection fields', 'Blocks', 'Actions', 'Workflow', 'Users & permissions', 'Authentication', 'Notification', 'System management', 'Security', 'Logging and monitoring', 'Others', ]; const keyWordsfilterList = useMemo(() => { const list = keyWordlists.map((i) => { if (i === 'Others') { const result = data?.data.filter((v) => !hasIntersection(v.keywords, keyWordlists)); return { key: i, list: result, }; } const result = data?.data.filter((v) => v.keywords?.includes(i)); return { key: i, list: result, }; }); return list; }, [keyWordlists]); const pluginList = useMemo(() => { let list = filterList[filterIndex]?.list || []; if (!filterIndex && keyword) { list = keyWordsfilterList.find((v) => v.key === keyword).list; } else if (filterIndex && keyword) { const keyList = keyWordsfilterList.find((v) => v.key === keyword).list; list = keyList.filter((value) => list.find((k) => k.name === value.name)); } const searchLowerCaseValue = debouncedSearchValue.toLocaleLowerCase().trim(); if (searchLowerCaseValue) { list = _.filter( list, (item) => String(item.displayName || '') .toLocaleLowerCase() .includes(searchLowerCaseValue) || String(item.description || '') .toLocaleLowerCase() .includes(searchLowerCaseValue), ); } return list.sort((a, b) => a.displayName.localeCompare(b.displayName)); }, [filterIndex, filterList, debouncedSearchValue, keyword]); const handleSearch = (value: string) => { setSearchValue(value); }; if (loading) { return ; } return ( <> { setShowAddForm(false); // if (isRefresh) refresh(); }} />
{ return ( (item.key !== keyword ? setKeyword(item.key) : setKeyword(null))} > {t(item.key)} ); }} />
{pluginList.map((item) => ( ))}
); }; const MarketplacePlugins = () => { const { token } = useToken(); const { t } = useTranslation(); return
{t('Coming soon...')}
; }; export const PluginManager = () => { const params = useParams(); const navigate = useNavigate(); const { tabName = 'local' } = params; const { t } = useTranslation(); const { snippets = [] } = useACLRoleContext(); const { styles } = useStyles(); const { setTitle: setDocumentTitle } = useDocumentTitle(); useEffect(() => { const { tabName } = params; if (!tabName) { navigate(`/admin/pm/list/local/`, { replace: true }); } setDocumentTitle(t('Plugin manager')); }, []); return snippets.includes('pm') ? (
{ navigate(`/admin/pm/list/${activeKey}`); }} items={[ { key: 'local', label: t('Local'), }, { key: 'marketplace', label: t('Marketplace'), }, ]} /> } />
{React.createElement( { local: LocalPlugins, marketplace: MarketplacePlugins, }[tabName], )}
) : ( ); };