feat: generic configActions plugin slot for api-keys & model-routes
Introduce `pages/api-keys/plugin.tsx` and `pages/model-routes/plugin.tsx`
defining a unified `{ key, labelId, icon, priority, danger, form,
useCreate }` contract that plugins use to contribute per-row
"configure this record" actions. The host renders one dropdown entry
per registered action and mounts each entry's form, ordered by a
single numeric priority (built-ins are ranked on the same scale; rows
flagged `danger` sink to the bottom).
Drops the older `apiKeys.rowActions` / `modelRoutes.rowActions` slots
and the implicit drawer half of
`<PluginExtraFields name="APIKeysPageGlobal" />`. The model-routes
mount point stays for the page-level quota-defaults bulk-fetch and now
also accepts a `refreshToken` so per-row saves can invalidate derived
page data without changing the row set.
This commit is contained in:
@@ -3,19 +3,23 @@ import { PaginationKey } from '@/config/settings';
|
||||
import type { PageActionType } from '@/config/types';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import useQueryUserList from '@/pages/users/services/use-query-user-list';
|
||||
import { getGPUStackPlugin } from '@/plugins';
|
||||
import { useModel } from '@@/plugin-model';
|
||||
import { DeleteModal, FilterBar, IconFont, NoResult } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import { ConfigProvider, Table } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import PageBox from '../_components/page-box';
|
||||
import { deleteApisKey, queryApisKeysList } from './apis';
|
||||
import AddAPIKeyModal from './components/add-apikey-modal';
|
||||
import { ListItem } from './config/types';
|
||||
import useKeysColumns from './hooks/use-keys-columns';
|
||||
import {
|
||||
APIKeyConfigActionMount,
|
||||
getAPIKeyConfigActions,
|
||||
type APIKeyConfigActionController
|
||||
} from './plugin';
|
||||
|
||||
const APIKeys: React.FC = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
@@ -55,10 +59,27 @@ const APIKeys: React.FC = () => {
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const apiKeyIPConfig = getGPUStackPlugin()?.APIKeyIPConfig;
|
||||
const APIKeyIPConfigForm = apiKeyIPConfig?.form;
|
||||
const { openIPConfigModalStatus, openIPConfigModal, closeIPConfigModal } =
|
||||
apiKeyIPConfig?.useCreateIPConfig?.() || {};
|
||||
// Generic per-row plugin slot. Each enterprise plugin contributes a
|
||||
// `{ key, labelId, icon, priority, form, useCreate }` entry; the
|
||||
// host renders a button per entry in the dropdown and renders one
|
||||
// `APIKeyConfigActionMount` per entry — those mounts own each
|
||||
// entry's controller and register it back into `controllersRef` so
|
||||
// dropdown clicks can route to the correct `openModal`. See
|
||||
// `./plugin.tsx`.
|
||||
//
|
||||
// The action list is read once. Plugins are registered at boot and
|
||||
// never recompute, so the reference is stable for the lifetime of
|
||||
// the page and `useMemo([])` is safe.
|
||||
const configActions = useMemo(() => getAPIKeyConfigActions(), []);
|
||||
const controllersRef = useRef<Record<string, APIKeyConfigActionController>>(
|
||||
{}
|
||||
);
|
||||
const registerController = useCallback(
|
||||
(key: string, controller: APIKeyConfigActionController) => {
|
||||
controllersRef.current[key] = controller;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const [openAddModal, setOpenAddModal] = useState<{
|
||||
open: boolean;
|
||||
@@ -140,6 +161,14 @@ const APIKeys: React.FC = () => {
|
||||
}
|
||||
);
|
||||
|
||||
// Each plugin entry's button onClick routes here. The controller
|
||||
// registry is populated by each `APIKeyConfigActionMount` on mount.
|
||||
const handleConfigAction = useMemoizedFn(
|
||||
(actionKey: string, record: ListItem) => {
|
||||
controllersRef.current[actionKey]?.openModal(record);
|
||||
}
|
||||
);
|
||||
|
||||
const handleUserChange = (val: string) => {
|
||||
handleQueryChange({
|
||||
user_id: val || '*'
|
||||
@@ -170,7 +199,8 @@ const APIKeys: React.FC = () => {
|
||||
handleSelect: onSelect,
|
||||
sortOrder,
|
||||
is_admin: currentUser?.is_admin,
|
||||
onIPConfig: openIPConfigModal
|
||||
configActions,
|
||||
onConfigAction: handleConfigAction
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -225,14 +255,20 @@ const APIKeys: React.FC = () => {
|
||||
onCancel={handleModalCancel}
|
||||
onOk={handleModalOk}
|
||||
></AddAPIKeyModal>
|
||||
{APIKeyIPConfigForm && (
|
||||
<APIKeyIPConfigForm
|
||||
open={openIPConfigModalStatus.open}
|
||||
apiKey={openIPConfigModalStatus.currentData}
|
||||
onClose={closeIPConfigModal}
|
||||
/>
|
||||
)}
|
||||
<DeleteModal ref={modalRef}></DeleteModal>
|
||||
{/* One mount per registered action. Each mount calls its
|
||||
entry's `useCreate` (single hook per component, so iterating
|
||||
the plugin list doesn't violate the Rules of Hooks),
|
||||
renders the form, and registers its controller so dropdown
|
||||
clicks can dispatch to it. */}
|
||||
{configActions.map((action) => (
|
||||
<APIKeyConfigActionMount
|
||||
key={action.key}
|
||||
action={action}
|
||||
registerController={registerController}
|
||||
onOk={fetchData}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user