22 @license
33 Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44 */
5+ import { VFolderNodesDeploymentPresetsCheckQuery } from '../__generated__/VFolderNodesDeploymentPresetsCheckQuery.graphql' ;
56import {
67 VFolderNodesFragment$data ,
78 VFolderNodesFragment$key ,
@@ -44,7 +45,7 @@ import dayjs from 'dayjs';
4445import * as _ from 'lodash-es' ;
4546import React , { useState } from 'react' ;
4647import { useTranslation } from 'react-i18next' ;
47- import { graphql , useFragment } from 'react-relay' ;
48+ import { graphql , useFragment , useLazyLoadQuery } from 'react-relay' ;
4849
4950export const statusTagColor = {
5051 // mountable
@@ -104,6 +105,15 @@ interface VFolderNameCellProps {
104105 * additionally redirects admins (project/domain/super) to that page.
105106 */
106107 disableProjectFolderActions ?: boolean ;
108+ /**
109+ * When true, the row-level "Deploy as service" action for model folders
110+ * is rendered disabled with a tooltip explaining that no compatible
111+ * deployment presets are available. Computed once at the table level
112+ * by `VFolderNodes` (via `deploymentRevisionPresets`) so we don't fire
113+ * one query per row. The `VFolderDeployModal` retains a `null`-return
114+ * fallback for the same condition as defense in depth.
115+ */
116+ hasNoCompatiblePresets ?: boolean ;
107117}
108118
109119const VFolderNameCell : React . FC < VFolderNameCellProps > = ( {
@@ -114,6 +124,7 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
114124 onDeleteForever,
115125 onStartServiceFallback,
116126 disableProjectFolderActions = false ,
127+ hasNoCompatiblePresets = false ,
117128} ) => {
118129 'use memo' ;
119130 const { t } = useTranslation ( ) ;
@@ -149,6 +160,10 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
149160 key : 'start-service' ,
150161 title : t ( 'modelService.DeployAsService' ) ,
151162 icon : < BAIEndpointsIcon /> ,
163+ disabled : hasNoCompatiblePresets ,
164+ disabledReason : hasNoCompatiblePresets
165+ ? t ( 'data.folders.NoCompatibleDeploymentPresets' )
166+ : undefined ,
152167 onClick : ( ) => onStartServiceFallback ( vfolderId ) ,
153168 }
154169 : null ,
@@ -309,6 +324,28 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
309324
310325 const filteredVFolders = filterOutNullAndUndefined ( vfolders ) ;
311326
327+ // Project-scoped check: are any deployment presets available for this
328+ // project? Used to gate the row-level "Deploy as service" action so model
329+ // folders show a disabled tooltip ("No compatible deployment presets…")
330+ // instead of a fully interactive button that opens a modal which then
331+ // returns `null` (FR-2831). Run once per table mount, not once per row.
332+ // TODO(needs-backend): once `deploymentRevisionPresets` exposes a
333+ // per-vfolder compatibility filter, narrow this check to the rows that
334+ // actually have a model folder and respect their compatibility.
335+ const { deploymentRevisionPresets } =
336+ useLazyLoadQuery < VFolderNodesDeploymentPresetsCheckQuery > (
337+ graphql `
338+ query VFolderNodesDeploymentPresetsCheckQuery {
339+ deploymentRevisionPresets(first: 0) {
340+ count
341+ }
342+ }
343+ ` ,
344+ { } ,
345+ { fetchPolicy : 'store-or-network' } ,
346+ ) ;
347+ const hasNoCompatiblePresets = ( deploymentRevisionPresets ?. count ?? 0 ) === 0 ;
348+
312349 const deleteMutation = useTanMutation ( {
313350 mutationFn : ( id : string ) => {
314351 return baiClient . vfolder . delete_by_id ( toLocalId ( id ) ) ;
@@ -347,6 +384,7 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
347384 < VFolderNameCell
348385 vfolder = { vfolder }
349386 disableProjectFolderActions = { disableProjectFolderActions }
387+ hasNoCompatiblePresets = { hasNoCompatiblePresets }
350388 onShare = { ( ) => {
351389 vfolder ?. user === currentUser ?. uuid
352390 ? setInviteFolderId ( toLocalId ( vfolder ?. id ?? null ) )
0 commit comments