diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 5231ed22..f5a11dc8 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -424,9 +424,14 @@ export async function queryBackendList(params?: { cluster_id: number }) { } export async function queryModelAccessUserList(id: number) { - return request<{ items: UserListItem[] }>(`${MODEL_ROUTES}/${id}/access`, { - method: 'GET' - }); + // The response carries `access_policy` alongside `items` so the + // Access Settings dialog can refresh both halves from a single + // GET (the calling list snapshot may be stale after a prior + // save). + return request<{ items: UserListItem[]; access_policy?: string }>( + `${MODEL_ROUTES}/${id}/access`, + { method: 'GET' } + ); } export async function updateModelAccessUser(params: { diff --git a/src/pages/llmodels/components/access-control-modal/form.tsx b/src/pages/llmodels/components/access-control-modal/form.tsx index 5fff220a..91b386f5 100644 --- a/src/pages/llmodels/components/access-control-modal/form.tsx +++ b/src/pages/llmodels/components/access-control-modal/form.tsx @@ -221,6 +221,10 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => { const userMap = new Map(allusers.map((u) => [u.key, u])); if (currentData?.id) { + // Seed the radio from the parent's snapshot so the form isn't + // momentarily unselected; the GET below replaces it with the + // server's authoritative value, which is what survives a save + // when the parent list hasn't been refreshed. form.setFieldsValue({ access_policy: currentData?.access_policy }); @@ -246,7 +250,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => { setFilterInUsers(filterSet); form.setFieldsValue({ - access_policy: currentData.access_policy, + access_policy: res.access_policy ?? currentData.access_policy, users: res.items.map((item) => ({ id: item.id })) }); }); diff --git a/src/pages/llmodels/components/access-control-modal/index.tsx b/src/pages/llmodels/components/access-control-modal/index.tsx index 02e37106..9cd57028 100644 --- a/src/pages/llmodels/components/access-control-modal/index.tsx +++ b/src/pages/llmodels/components/access-control-modal/index.tsx @@ -23,7 +23,7 @@ const AccessControlModal: React.FC< const handleOnFinish = async (values: AccessControlFormData) => { try { - const data: any = { + const data: AccessControlFormData = { access_policy: values.access_policy, // `users` is only meaningful for the legacy `allowed_users` // policy; for the plugin override (typically the principal- diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index c70d2e99..93b20a19 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -25,7 +25,13 @@ export interface ListItem { local_path?: string; created_at: string; updated_at: string; - access_policy: 'public' | 'authed' | 'allowed_users'; + // Built-in values are 'public' | 'authed' | 'allowed_users'; + // additional values (e.g. 'allowed_principals') may be contributed + // by plugins via `accessControl.prependedPolicies` or by + // overriding the default via `accessControl.allowedUsersOverride`. + // The `(string & {})` tail keeps literal autocomplete for the + // built-ins while still accepting plugin-defined values. + access_policy: 'public' | 'authed' | 'allowed_users' | (string & {}); generic_proxy?: boolean; gpu_selector?: { gpu_ids: string[]; @@ -346,7 +352,9 @@ export interface BackendOption { } export interface AccessControlFormData { - access_policy: 'public' | 'authed' | 'allowed_users'; + // See `RouteItem.access_policy` for why plugin-defined values are + // accepted alongside the built-ins. + access_policy: 'public' | 'authed' | 'allowed_users' | (string & {}); users: { id: number }[]; }