diff --git a/packages/core/client/src/api-client/APIClient.ts b/packages/core/client/src/api-client/APIClient.ts index 93777c64fb..78ba5bbc6f 100644 --- a/packages/core/client/src/api-client/APIClient.ts +++ b/packages/core/client/src/api-client/APIClient.ts @@ -120,7 +120,9 @@ export class APIClient extends APIClientSDK { toErrMessages(error) { if (typeof error?.response?.data === 'string') { - return [{ message: error?.response?.data }]; + const tempElement = document.createElement('div'); + tempElement.innerHTML = error?.response?.data; + return [{ message: tempElement.textContent || tempElement.innerText }]; } return ( error?.response?.data?.errors || diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index d190ee577a..feadcd2737 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -284,7 +284,9 @@ export class Application { loadFailed = true; const toError = (error) => { if (typeof error?.response?.data === 'string') { - return { message: error?.response?.data }; + const tempElement = document.createElement('div'); + tempElement.innerHTML = error?.response?.data; + return { message: tempElement.textContent || tempElement.innerText }; } if (error?.response?.data?.error) { return error?.response?.data?.error; diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index d6172ea7d5..d48e3590e2 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1027,5 +1027,9 @@ "Add & Update": "添加 & 更新", "Table size":"表格大小", "Hide column": "隐藏列", - "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整个列会变为透明色。在非配置模式下,整个列将被隐藏。即使整个列被隐藏了,其配置的默认值和其他设置仍然有效。" + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整个列会变为透明色。在非配置模式下,整个列将被隐藏。即使整个列被隐藏了,其配置的默认值和其他设置仍然有效。", + "Plugin": "插件", + "Bulk enable": "批量激活", + "Search plugin...": "搜索插件...", + "Package name": "包名" } diff --git a/packages/core/client/src/pm/PluginManager.tsx b/packages/core/client/src/pm/PluginManager.tsx index 112f41a56a..9295167010 100644 --- a/packages/core/client/src/pm/PluginManager.tsx +++ b/packages/core/client/src/pm/PluginManager.tsx @@ -10,7 +10,7 @@ export * from './PluginManagerLink'; import { PageHeader } from '@ant-design/pro-layout'; import { useDebounce } from 'ahooks'; -import { Button, Col, Divider, Input, List, Result, Row, Space, Spin, Tabs } from 'antd'; +import { Button, Col, Divider, Input, List, Modal, Result, Row, Space, Spin, Table, Tabs } from 'antd'; import _ from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,7 +18,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { css } from '@emotion/css'; import { useACLRoleContext } from '../acl/ACLProvider'; -import { useRequest } from '../api-client'; +import { useAPIClient, useRequest } from '../api-client'; import { useToken } from '../style'; import { PluginCard } from './PluginCard'; import { PluginAddModal } from './PluginForm/modal/PluginAddModal'; @@ -51,6 +51,90 @@ function hasIntersection(arr1: any[], arr2: any[]) { 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 ( + <> + + { + console.log(selectedRowKeys); + 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())) + ); + }), + ); + }} + /> + + + + ); +} + const LocalPlugins = () => { const { t } = useTranslation(); const { theme } = useStyles(); @@ -197,6 +281,7 @@ const LocalPlugins = () => {
+ diff --git a/packages/core/server/src/commands/pm.ts b/packages/core/server/src/commands/pm.ts index fc00100be9..de8c2045ae 100644 --- a/packages/core/server/src/commands/pm.ts +++ b/packages/core/server/src/commands/pm.ts @@ -78,6 +78,9 @@ export default (app: Application) => { try { await app.pm.enable(plugins); } catch (error) { + await app.tryReloadOrRestart({ + recover: true, + }); throw new PluginCommandError(`Failed to enable plugin`, { cause: error }); } }); diff --git a/packages/core/server/src/plugin-manager/options/resource.ts b/packages/core/server/src/plugin-manager/options/resource.ts index 902117c672..6cbc10dfe0 100644 --- a/packages/core/server/src/plugin-manager/options/resource.ts +++ b/packages/core/server/src/plugin-manager/options/resource.ts @@ -96,7 +96,8 @@ export default { if (!filterByTk) { ctx.throw(400, 'plugin name invalid'); } - app.runAsCLI(['pm', 'enable', filterByTk], { from: 'user' }); + const keys = Array.isArray(filterByTk) ? filterByTk : [filterByTk]; + app.runAsCLI(['pm', 'enable', ...keys], { from: 'user' }); ctx.body = filterByTk; await next(); },