feat: access-control seam exposes prepended policies + create default

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.
This commit is contained in:
gitlawr
2026-05-11 19:30:21 +08:00
committed by jialin
parent 68235e188a
commit df53774628
@@ -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<TransferKey[]>([]);
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
}}
>
<Label>
@@ -324,7 +361,10 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
<Tooltip
title={
<TooltipList
list={buildAccessScopeTips(allowedUsersOverride)}
list={buildAccessScopeTips(
allowedUsersOverride,
prependedPolicies
)}
></TooltipList>
}
>
@@ -337,6 +377,10 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
onChange={handleOnPolicyChange}
style={{ marginBottom: 12 }}
options={[
...prependedPolicies.map((p) => ({
label: intl.formatMessage({ id: p.labelId }),
value: p.policyValue
})),
{
label: intl.formatMessage({ id: 'models.accessSettings.authed' }),
value: 'authed'
@@ -373,6 +417,13 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
></AlertBlockInfo>
</div>
)}
{activePrependedPolicy?.Field && (
<activePrependedPolicy.Field
form={form}
routeId={currentData?.id}
action={action}
/>
)}
{allowedUsersOverride &&
accessPolicy === overridePolicyValue && (
<allowedUsersOverride.Field