diff --git a/src/pages/gpu-service/components/owner-tag.tsx b/src/pages/gpu-service/components/owner-tag.tsx index 0f9383c7..19245d32 100644 --- a/src/pages/gpu-service/components/owner-tag.tsx +++ b/src/pages/gpu-service/components/owner-tag.tsx @@ -6,8 +6,8 @@ import useUserDirectory from '../hooks/use-user-directory'; /** * Owner tag for template cards, disambiguating same-name templates in * the admin's cross-tenant view. Renders nothing for non-admin callers - * (the management page is `mine`-scoped for them) and when a plugin - * provides its own `OwnerScopeTag` slot. + * (gated on `canSeeAdmin`) and when a plugin provides its own + * `OwnerScopeTag` slot. */ const OwnerTag: React.FC<{ ownerId?: number | null }> = ({ ownerId }) => { const intl = useIntl(); diff --git a/src/pages/gpu-service/templates/components/template-card.tsx b/src/pages/gpu-service/templates/components/template-card.tsx index 4ea4a610..5a24cca4 100644 --- a/src/pages/gpu-service/templates/components/template-card.tsx +++ b/src/pages/gpu-service/templates/components/template-card.tsx @@ -28,7 +28,7 @@ import { TemplateCard, ThemeTag } from '@gpustack/core-ui'; -import { useIntl } from '@umijs/max'; +import { useAccess, useIntl } from '@umijs/max'; import { Button, Tag } from 'antd'; import { useMemo } from 'react'; import styled from 'styled-components'; @@ -144,8 +144,15 @@ interface TemplateCardProps { const TemplateCardItem: React.FC = ({ data, onSelect }) => { const intl = useIntl(); + const access = useAccess(); const { isDarkTheme } = useUserSettings(); + // Global templates (owner_principal_id NULL) are admin-curated and + // only editable by an admin. Principal-owned rows only reach a + // non-admin's list when they own them, so those are always + // manageable by the caller. + const canManage = !!access.canSeeAdmin || data.owner_principal_id != null; + const manufacturerLabelMap: Record = useMemo(() => { return Object.values(GPUsConfigs).reduce( (acc, item) => { @@ -234,6 +241,9 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { }; const renderActions = () => { + if (!canManage) { + return null; + } return ( { isInfiniteScroll: true, contentForDelete: intl.formatMessage({ id: 'gpuservice.template' }), defaultQueryParams: { - perPage: 24, - // Management view: drop Global rows for non-admin callers — the - // page is a CRUD surface, and admin-curated Global templates - // they can't edit only add visual noise. The instance-create - // picker (uses ``useQueryTemplates`` separately) doesn't set - // this and so still sees Global presets. - mine: true + // Default (non-``mine``) scope: Global rows plus rows owned by the + // caller's current principal, matching the Inference Backend page. + // Global rows a non-admin can't edit render read-only — the card + // gates Edit/Delete on ownership. + perPage: 24 } }); const { openTemplateModalStatus, openTemplateModal, closeTemplateModal } =