fix(gpu-service): use gpustack-{slug} namespace for org resources

The legacy ``getCurrentOrganizationId`` helper returned the raw numeric
org id from localStorage, which the GPU-service / storage call sites
then used as the K8s namespace path segment — producing requests like
``/v2/clusters/1/proxy/.../namespaces/5/instances``. The backend
creates a namespace named ``gpustack-{slug}`` (matching
``get_namespace_name``), so the request hit a namespace that doesn't
exist.

Replace the helper with ``getCurrentOrgNamespace`` that:

- prefers the Org the caller is acting under (numeric
  ``currentOrganizationId`` from localStorage, slug looked up in the
  persisted ``organizationList`` or ``allOrganizations`` cache);
- falls back to the selected cluster's owner Org slug for the admin
  "All" view, where there is no explicit Org context but the resource
  still has to land in some Org's namespace — call sites pass
  ``currentCluster?.owner_principal_id`` through;
- falls back to ``gpustack-default`` only if neither path resolves a
  slug (first load before any cache hydrates, etc.).

``ClusterListItem`` is widened with the optional ``owner_principal_id``
field so TypeScript accepts the fallback argument; the backend has
been returning it via ``ClusterPublic`` all along.
This commit is contained in:
gitlawr
2026-05-13 12:29:09 +08:00
committed by jialin
parent beb74b3c47
commit 2dae886fd1
10 changed files with 103 additions and 22 deletions
@@ -1,7 +1,9 @@
import { getCurrentOrganizationId } from '@/atoms/user';
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Button, Flex, Form, Radio } from 'antd';
import { useAtomValue } from 'jotai';
import { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { FormData as StorageFormData } from '../../storage/config/types';
@@ -29,7 +31,8 @@ const StorageVolume = () => {
const form = Form.useFormInstance<FormData>();
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
useEffect(() => {
fetchStorage({});
+4 -2
View File
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { PageAction } from '@/config';
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import type { PageActionType } from '@/config/types';
@@ -33,9 +33,11 @@ import useUpdateInstance from './services/use-update-instance';
const GPUService: React.FC = () => {
const intl = useIntl();
const namespace = getCurrentOrganizationId();
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
const clusterID = currentCluster?.id;
// In admin "All" view there's no Org context, so the helper falls
// back to the selected cluster's owner Org slug.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const deleteInstance = useCallback(
(id: number) => deleteGPUServiceInstance({ namespace, clusterID, id }),
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
@@ -12,9 +12,11 @@ interface CreateInstanceParams {
}
export default function useCreateInstance() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view has no Org context — fall back to the
// selected cluster's owner Org slug for the K8s namespace.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const fetchDetail = useCallback(
(params: CreateInstanceParams, option?: any) =>
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
@@ -13,9 +13,11 @@ interface UpdateInstanceParams {
}
export default function useUpdateInstance() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view has no Org context — fall back to the
// selected cluster's owner Org slug for the K8s namespace.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const fetchDetail = useCallback(
(params: UpdateInstanceParams, option?: any) =>
+4 -2
View File
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { PageAction } from '@/config';
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import type { PageActionType } from '@/config/types';
@@ -34,9 +34,11 @@ import useUpdateStorage from './services/use-update-storage';
const GPUServiceStorage: React.FC = () => {
const intl = useIntl();
const namespace = getCurrentOrganizationId();
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view falls back to the cluster's owner Org slug —
// see :func:`getCurrentOrgNamespace`.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const deleteStorage = useCallback(
(id: number) => deleteGPUServiceStorage({ namespace, clusterID, id }),
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
@@ -12,9 +12,11 @@ interface CreateStorageParams {
}
export default function useCreateStorage() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view has no Org context — fall back to the
// selected cluster's owner Org slug for the K8s namespace.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const fetchDetail = useCallback(
(params: CreateStorageParams, option?: any) =>
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
@@ -7,9 +7,11 @@ import { queryGPUServiceStorage } from '../apis';
import { ListItem } from '../config/types';
export default function useQueryStorage() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view has no Org context — fall back to the
// selected cluster's owner Org slug for the K8s namespace.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const fetchDetail = useCallback(
(params: Global.K8sSearchParams = {}, options?: any) =>
@@ -1,5 +1,5 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { getCurrentOrgNamespace } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
@@ -13,9 +13,11 @@ interface UpdateStorageParams {
}
export default function useUpdateStorage() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
// Admin "All" view has no Org context — fall back to the
// selected cluster's owner Org slug for the K8s namespace.
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
const fetchDetail = useCallback(
(params: UpdateStorageParams, option?: any) =>