feat: plugin extension slots for access, request interceptors, Users actions, and form fields

Four purely-additive seams that let build-time tooling extend host
behaviour without forking files:

* `src/access.extensions.ts` — identity `applyAccessExtensions`
  (mirrors `config/routes.extensions.ts`). `src/access.ts` runs the
  resolved predicate object through it. Adds two new predicates,
  `canSeeOrgAdmin` and `canManageCurrentOrg`, alongside the existing
  `canSeeAdmin`. Resources, Models children, Cluster Management, and
  Resources/Backends are retagged from `canSeeAdmin` to
  `canSeeOrgAdmin`. Users and Dashboard stay strict.
* `src/request.extensions.ts` — identity-empty
  `extraRequestInterceptors`. `src/request-config.tsx` spreads it
  into the existing `requestInterceptors` list so extensions can
  inject context-aware headers without forking the request config.
* Users page action column — renders
  `getGPUStackPlugin()?.components?.UserRowActions` next to the
  existing DropdownButtons inside a Space when a plugin component
  is registered. If absent, the cell renders exactly as before.
* `src/components/plugin-extra-fields.tsx` — generic component-slot
  helper. Renders `pluginManager.components.<name>` if registered,
  forwarding a `context` payload. Used by create/edit forms to let
  plugins inject extra `Form.Item` fields. Mounted in the relevant
  create forms — API Keys, Cloud Credentials, Clusters, Model
  Routes, Model Providers, and Inference Backends — under the slot
  name `CreateOrgScopeField`. Resources whose org is implicit from a
  parent (Models / Workers / Benchmarks / Worker Pools / Model Files
  inherit from the chosen Cluster) deliberately don't mount the slot.
This commit is contained in:
gitlawr
2026-05-08 18:29:52 +08:00
committed by jialin
parent 896b8ef326
commit 2889ba7c78
18 changed files with 211 additions and 67 deletions
+31 -13
View File
@@ -1,8 +1,13 @@
import { history, useIntl } from '@umijs/max';
import { Button, Result } from 'antd';
import React from 'react';
import React, { useEffect } from 'react';
import { PageContainerInner } from '../pages/_components/page-box';
// On a 403, auto-redirect to root so context changes (e.g. an org
// switch into a context that can't see the current route) don't
// dead-end the user on a permission page. The 404 path stays as a
// classic Result with a Back button — those are usually genuine
// "wrong URL" landings the user should see and acknowledge.
const Exception: React.FC<{
children: React.ReactNode;
route?: any;
@@ -12,23 +17,36 @@ const Exception: React.FC<{
noFound?: React.ReactNode;
}> = (props) => {
const intl = useIntl();
// render custom 404
console.log('exception====', props);
const unaccessible = !!props.route?.unaccessible;
const customNoAccessible = props.unAccessible || props.noAccessible;
const willAutoRedirect = unaccessible && !customNoAccessible;
useEffect(() => {
if (willAutoRedirect) {
// `replace` instead of `push` so the user's Back button doesn't
// bounce them back into the unauthorized page (which would just
// redirect forward again).
history.replace('/');
}
}, [willAutoRedirect]);
// Render `null` during the auto-redirect path so the default 403
// Result doesn't flash for one frame before useEffect fires.
if (willAutoRedirect) {
return null;
}
return (
(!props.route && (props.noFound || props.notFound)) ||
// render custom 403
(props.route?.unaccessible && (props.unAccessible || props.noAccessible)) ||
// render default exception
((!props.route || props.route?.unaccessible) && (
(unaccessible && customNoAccessible) ||
// render default 404
(!props.route && (
<PageContainerInner>
<Result
status={props.route ? '403' : '404'}
title={props.route ? '403' : '404'}
subTitle={
props.route
? intl.formatMessage({ id: 'common.permission.403' })
: intl.formatMessage({ id: 'common.permission.404' })
}
status="404"
title="404"
subTitle={intl.formatMessage({ id: 'common.permission.404' })}
extra={
<Button type="primary" onClick={() => history.push('/')}>
{intl.formatMessage({ id: 'common.button.back' })}
+1 -1
View File
@@ -342,7 +342,7 @@ export default (props: any) => {
config={{
apiBaseUrl: GPUSTACK_API_BASE_URL,
theme: userSettings.theme,
iconUrl: '//at.alicdn.com/t/c/font_4613488_ueevrwt2v9.js',
iconUrl: '//at.alicdn.com/t/c/font_4613488_tzcsatubq4f.js',
isDarkTheme: userSettings.isDarkTheme,
defaultColorPrimary: COLOR_PRIMARY
}}