fix: refresh Access Settings policy from /access response
Reopening the Access Settings dialog after a save could leave the
radio on the wrong policy: the form initialized `access_policy`
from the route list's snapshot, but the list isn't refreshed when
the dialog closes, so the snapshot's value silently shadowed the
just-saved policy.
Backend now returns the route's authoritative `access_policy` on
`GET /v2/model-routes/{id}/access`. Use that value when the GET
resolves, falling back to the parent snapshot only as the initial
seed so the radio isn't briefly unselected.
While we're here, widen the `access_policy` type from a closed
union to `string` — plugins can contribute additional policy
values via the `accessControl.allowedUsersOverride` slot, so the
wire type shouldn't restrict to the OSS-side enum.
This commit is contained in:
@@ -424,9 +424,14 @@ export async function queryBackendList(params?: { cluster_id: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function queryModelAccessUserList(id: number) {
|
export async function queryModelAccessUserList(id: number) {
|
||||||
return request<{ items: UserListItem[] }>(`${MODEL_ROUTES}/${id}/access`, {
|
// The response carries `access_policy` alongside `items` so the
|
||||||
method: 'GET'
|
// 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: {
|
export async function updateModelAccessUser(params: {
|
||||||
|
|||||||
@@ -221,6 +221,10 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
const userMap = new Map(allusers.map((u) => [u.key, u]));
|
const userMap = new Map(allusers.map((u) => [u.key, u]));
|
||||||
|
|
||||||
if (currentData?.id) {
|
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({
|
form.setFieldsValue({
|
||||||
access_policy: currentData?.access_policy
|
access_policy: currentData?.access_policy
|
||||||
});
|
});
|
||||||
@@ -246,7 +250,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
setFilterInUsers(filterSet);
|
setFilterInUsers(filterSet);
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
access_policy: currentData.access_policy,
|
access_policy: res.access_policy ?? currentData.access_policy,
|
||||||
users: res.items.map((item) => ({ id: item.id }))
|
users: res.items.map((item) => ({ id: item.id }))
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const AccessControlModal: React.FC<
|
|||||||
|
|
||||||
const handleOnFinish = async (values: AccessControlFormData) => {
|
const handleOnFinish = async (values: AccessControlFormData) => {
|
||||||
try {
|
try {
|
||||||
const data: any = {
|
const data: AccessControlFormData = {
|
||||||
access_policy: values.access_policy,
|
access_policy: values.access_policy,
|
||||||
// `users` is only meaningful for the legacy `allowed_users`
|
// `users` is only meaningful for the legacy `allowed_users`
|
||||||
// policy; for the plugin override (typically the principal-
|
// policy; for the plugin override (typically the principal-
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ export interface ListItem {
|
|||||||
local_path?: string;
|
local_path?: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_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;
|
generic_proxy?: boolean;
|
||||||
gpu_selector?: {
|
gpu_selector?: {
|
||||||
gpu_ids: string[];
|
gpu_ids: string[];
|
||||||
@@ -346,7 +352,9 @@ export interface BackendOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessControlFormData {
|
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 }[];
|
users: { id: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user