fix: source owner_principal_id from current organization

OwnerPrincipalIdField was reading currentClusterAtom, which no callers
ever wrote — so every form submission ended up with
owner_principal_id=null and the backend rejected non-admins with
"Only platform admin can create global ...". Pin the field to
currentOrganizationId instead (the Org the caller is acting under).
Cluster ownership is the wrong source: a cluster_access grant lets one
Org schedule on another Org's cluster, but the new resource is still
owned by the caller's Org, and the backend enforces
owner_principal_id == ctx.current_principal_id.

Also drops the dead currentClusterAtom.
This commit is contained in:
gitlawr
2026-05-27 13:41:32 +08:00
committed by jialin
parent e343ec0a1e
commit 099b419e34
3 changed files with 19 additions and 12 deletions
-6
View File
@@ -1,6 +0,0 @@
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { atom } from 'jotai';
export const currentClusterAtom = atom<
(Partial<ClusterListItem> & { label?: string; value?: number }) | null
>(null);
+9
View File
@@ -3,6 +3,15 @@ import { atomWithStorage } from 'jotai/utils';
export const userAtom = atomWithStorage<any>('userInfo', null);
// Backs the `currentOrganizationId` localStorage key. Stays null in
// builds with no Org context (single-tenant), and is shared with any
// extension that persists the same key so both sides stay in sync
// without one side having to import from the other.
export const currentOrganizationIdAtom = atomWithStorage<number | null>(
'currentOrganizationId',
null
);
export const GPUStackVersionAtom = atom<{
version: string;
git_commit: string;
@@ -1,4 +1,4 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { currentOrganizationIdAtom } from '@/atoms/user';
import { Input as CInput } from '@gpustack/core-ui';
import { Form } from 'antd';
import type { NamePath } from 'antd/es/form/interface';
@@ -9,18 +9,22 @@ interface OwnerPrincipalIdFieldProps {
name?: NamePath;
}
// Pins `owner_principal_id` to the Org the caller is currently acting
// under. Cluster ownership is irrelevant here: cluster_access grants
// let one Org schedule on another Org's cluster, but the resource the
// caller creates still belongs to *their* Org, and the backend enforces
// `owner_principal_id == ctx.current_principal_id`.
const OwnerPrincipalIdField: React.FC<OwnerPrincipalIdFieldProps> = ({
name = 'owner_principal_id'
}) => {
const currentCluster = useAtomValue(currentClusterAtom);
const ownerPrincipalId = currentCluster?.owner_principal_id;
const currentOrgId = useAtomValue(currentOrganizationIdAtom);
const form = Form.useFormInstance();
useEffect(() => {
if (ownerPrincipalId != null) {
form.setFieldValue(name, ownerPrincipalId);
if (currentOrgId != null) {
form.setFieldValue(name, currentOrgId);
}
}, [ownerPrincipalId, name, form]);
}, [currentOrgId, name, form]);
return (
<Form.Item name={name} hidden>