From df53774628a1871a27960d8375f7a5149f9c0946 Mon Sep 17 00:00:00 2001 From: gitlawr Date: Mon, 11 May 2026 18:24:17 +0800 Subject: [PATCH] feat: access-control seam exposes prepended policies + create default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OSS access-control form currently lets a plugin replace the `allowed_users` policy entry. Multi-tenancy also wants: - an extra radio option for "Org-scoped" sitting in front of the built-ins, and - a context-sensitive create-time default (routes inside a non- platform Org should land on the Org-scoped policy rather than `authed`). Extend the `accessControl` slot with two more hooks: - `prependedPolicies?: { policyValue, labelId, tipsId?, Field? }[]` — entries prepended to the radio group; each may carry an optional content `Field` rendered when selected. - `resolveCreateDefault?: () => string | undefined` — overrides the initial `access_policy` for the create flow; the OSS fallback is still `authed`. The host's tooltip list mirrors the same order. Existing behaviour without a plugin is unchanged. --- .../components/access-control-modal/form.tsx | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/pages/llmodels/components/access-control-modal/form.tsx b/src/pages/llmodels/components/access-control-modal/form.tsx index 91b386f5..756863d2 100644 --- a/src/pages/llmodels/components/access-control-modal/form.tsx +++ b/src/pages/llmodels/components/access-control-modal/form.tsx @@ -34,7 +34,14 @@ import { AccessControlFormData } from '../../config/types'; type TransferKey = string | number | bigint; -const buildAccessScopeTips = (override?: AllowedUsersOverride) => [ +const buildAccessScopeTips = ( + override?: AllowedUsersOverride, + prepended: PrependedPolicy[] = [] +) => [ + ...prepended.map((p) => ({ + title: { text: p.labelId, locale: true }, + tips: p.tipsId ?? p.labelId + })), { title: { text: 'models.accessSettings.authed', @@ -92,14 +99,37 @@ type AllowedUsersOverride = { }>; }; +// Extra policy entries prepended to the radio list (rendered before +// `authed`). Each entry can optionally bring a `Field` that renders +// when its policy is selected — leave it out for plain "scope" +// policies that need no extra config (e.g. ORG). +type PrependedPolicy = { + policyValue: string; + labelId: string; + tipsId?: string; + Field?: React.ComponentType<{ + form: any; + routeId?: number; + action: PageActionType; + }>; +}; + const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => { const { action, currentData, onFinish, onValuesChange } = props; const intl = useIntl(); const [form] = Form.useForm(); const accessPolicy = Form.useWatch('access_policy', form); + const pluginAccessControl = getGPUStackPlugin()?.accessControl; const allowedUsersOverride: AllowedUsersOverride | undefined = - getGPUStackPlugin()?.accessControl?.allowedUsersOverride; + pluginAccessControl?.allowedUsersOverride; const overridePolicyValue = allowedUsersOverride?.policyValue; + const prependedPolicies: PrependedPolicy[] = + pluginAccessControl?.prependedPolicies ?? []; + const resolveCreateDefault: (() => string | undefined) | undefined = + pluginAccessControl?.resolveCreateDefault; + const activePrependedPolicy = prependedPolicies.find( + (p) => p.policyValue === accessPolicy + ); const [targetKeys, setTargetKeys] = useState([]); const [totalPages, setTotalPages] = useState(0); const [userList, setUserList] = useState< @@ -316,7 +346,14 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => { scrollToFirstError={true} initialValues={{ users: [], - access_policy: action === PageAction.CREATE ? 'authed' : undefined + // For create the plugin (if registered) may supply a context- + // sensitive default — e.g. routes in a non-platform Org + // default to the Org-scoped policy. Fall back to `authed` + // (the OSS default) if the plugin doesn't return a value. + access_policy: + action === PageAction.CREATE + ? (resolveCreateDefault?.() ?? 'authed') + : undefined }} >