diff --git a/src/config/getInstanceClient.ts b/src/config/getInstanceClient.ts index d1fd64f22..951ea3900 100644 --- a/src/config/getInstanceClient.ts +++ b/src/config/getInstanceClient.ts @@ -12,8 +12,16 @@ interface InstanceClient { } export function getInstanceClient( - { id = OverallAppSignIn, operationsUrl, port, secure, forceFabricConnect }: InstanceClient & { + { + id = OverallAppSignIn, + operationsUrl, + port, + secure, + forceFabricConnect, + disableFabricConnect, + }: InstanceClient & { forceFabricConnect?: boolean; + disableFabricConnect?: boolean; } = {}, ) { let baseURL = operationsUrl || authStore.getOperationsUrl(id); @@ -30,7 +38,7 @@ export function getInstanceClient( } } - const fabricConnect = forceFabricConnect || authStore.checkForFabricConnect(id); + const fabricConnect = !disableFabricConnect && (forceFabricConnect || authStore.checkForFabricConnect(id)); if (fabricConnect) { if (id.startsWith('clu-')) { baseURL = apiClient.defaults.baseURL + `/Cluster/${id}/operation`; diff --git a/src/config/useInstanceClient.tsx b/src/config/useInstanceClient.tsx index f5f0c0c08..c7cce79e5 100644 --- a/src/config/useInstanceClient.tsx +++ b/src/config/useInstanceClient.tsx @@ -5,37 +5,49 @@ import { OverallAppSignIn } from '@/features/auth/store/authStore'; import { useParams } from '@tanstack/react-router'; import { useMemo } from 'react'; -export function useInstanceClient(operationsUrl?: string | null, port?: number, secure?: boolean) { +interface UseParams { + operationsUrl?: string | null; + port?: number; + secure?: boolean; + disableFabricConnect?: boolean; + instanceId?: string; + clusterId?: string; +} + +export function useInstanceClient( + params: UseParams = {}, +) { const { instanceId, clusterId }: { instanceId?: string; clusterId?: string } = useParams({ strict: false }); - const id = isLocalStudio ? OverallAppSignIn : instanceId ?? clusterId; - return getInstanceClient({ id, operationsUrl, port, secure }); + const id = isLocalStudio ? OverallAppSignIn : params.instanceId ?? instanceId ?? params.clusterId ?? clusterId; + return getInstanceClient({ id, ...params }); } export function useInstanceClientParams( - operationsUrl?: string | null, - port?: number, - secure?: boolean, + params: UseParams = {}, ): InstanceClientConfig & InstanceTypeConfig { const { instanceId, clusterId }: { instanceId?: string; clusterId?: string } = useParams({ strict: false }); - const id = isLocalStudio ? OverallAppSignIn : instanceId ?? clusterId; + const id = isLocalStudio ? OverallAppSignIn : params.instanceId ?? instanceId ?? params.clusterId ?? clusterId; return { - instanceClient: getInstanceClient({ id, operationsUrl, port, secure }), + instanceClient: getInstanceClient({ id, ...params }), entityType: (isLocalStudio || instanceId) ? 'instance' : 'cluster', }; } export function useInstanceClientIdParams( - operationsUrl?: string | null, - port?: number, - secure?: boolean, + params: UseParams = {}, ): InstanceClientIdConfig & InstanceTypeConfig { - const params: { instanceId?: string; clusterId?: string } = useParams({ strict: false }); - return useMemo(() => getInstanceClientIdFromParams({ ...params, operationsUrl, port, secure }), [ - params, - operationsUrl, - port, - secure, - ]); + const { instanceId, clusterId }: { instanceId?: string; clusterId?: string } = useParams({ strict: false }); + return useMemo( + () => getInstanceClientIdFromParams({ instanceId, clusterId, ...params }), + [ + params.instanceId ?? instanceId, + params.clusterId ?? clusterId, + params.operationsUrl, + params.port, + params.secure, + params.disableFabricConnect, + ], + ); } export function getInstanceClientIdFromParams({ @@ -44,19 +56,21 @@ export function getInstanceClientIdFromParams({ operationsUrl, port, secure, + disableFabricConnect, }: { instanceId?: string; clusterId?: string; operationsUrl?: string | null; port?: number; secure?: boolean; + disableFabricConnect?: boolean; }): InstanceClientIdConfig & InstanceTypeConfig { const id = isLocalStudio ? OverallAppSignIn : instanceId ?? clusterId; if (!id) { throw new Error('id could not be automatically calculated in useInstanceClientIdParams'); } return { - instanceClient: getInstanceClient({ id, operationsUrl, port, secure }), + instanceClient: getInstanceClient({ id, operationsUrl, port, secure, disableFabricConnect }), entityId: id, entityType: (isLocalStudio || instanceId) ? 'instance' : 'cluster', }; diff --git a/src/features/auth/ClusterInstanceSignIn.tsx b/src/features/auth/ClusterInstanceSignIn.tsx index cdb01cf25..b2f9ff2dd 100644 --- a/src/features/auth/ClusterInstanceSignIn.tsx +++ b/src/features/auth/ClusterInstanceSignIn.tsx @@ -54,7 +54,7 @@ export function ClusterInstanceSignIn() { return null; }, [cluster, instance]); - const instanceParams = useInstanceClientIdParams(operationsUrl); + const instanceParams = useInstanceClientIdParams({ operationsUrl }); const warnAboutLocalDeviceAccess = useMemo( () => operationsUrl?.includes('localhost') || operationsUrl?.includes('127.0.0.1'), [operationsUrl], diff --git a/src/features/cluster/FinishSetup.tsx b/src/features/cluster/FinishSetup.tsx index 0ef5f68af..53746a76a 100644 --- a/src/features/cluster/FinishSetup.tsx +++ b/src/features/cluster/FinishSetup.tsx @@ -33,7 +33,7 @@ export function FinishSetup() { const navigate = useNavigate(); const operationsUrl = useMemo(() => getOperationsUrlForCluster(cluster), [cluster]); - const instanceClient = useInstanceClient(operationsUrl); + const instanceClient = useInstanceClient({ operationsUrl }); const { redirect } = useSearch({ strict: false }); const router = useRouter(); diff --git a/src/features/cluster/InstanceLogInCell.tsx b/src/features/cluster/InstanceLogInCell.tsx index 6e7ca2c9a..d8755efe9 100644 --- a/src/features/cluster/InstanceLogInCell.tsx +++ b/src/features/cluster/InstanceLogInCell.tsx @@ -16,7 +16,7 @@ export function InstanceLogInCell( ) { const { user: instanceUser, isLoading: instanceAuthIsLoading } = useInstanceAuth(instance.id); const operationsUrl = useMemo(() => getOperationsUrlForInstance(instance), [instance]); - const instanceClient = useInstanceClient(operationsUrl); + const instanceClient = useInstanceClient({ operationsUrl }); const { update } = useOrganizationClusterInstancePermissions(); const isFabricConnect = authStore.checkForFabricConnect(instance.id); diff --git a/src/features/cluster/InstanceStatusCell.tsx b/src/features/cluster/InstanceStatusCell.tsx new file mode 100644 index 000000000..554a3dab4 --- /dev/null +++ b/src/features/cluster/InstanceStatusCell.tsx @@ -0,0 +1,100 @@ +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { useInstanceClientIdParams } from '@/config/useInstanceClient'; +import { useOrganizationClusterInstancePermissions } from '@/hooks/usePermissions'; +import { Instance } from '@/integrations/api/api.patch'; +import { getStatusQueryOptions, getSystemStatusById } from '@/integrations/api/instance/status/getStatus'; +import { useSetStatus } from '@/integrations/api/instance/status/setStatus'; +import { getOperationsUrlForInstance } from '@/lib/urls/getOperationsUrlForInstance'; +import { useQuery } from '@tanstack/react-query'; +import { LoaderCircleIcon, ShieldCheckIcon, ShieldXIcon } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; + +export function InstanceStatusCell( + { instance }: { readonly instance: Instance }, +) { + const operationsUrl = useMemo(() => getOperationsUrlForInstance(instance), [instance]); + const instanceParams = useInstanceClientIdParams({ operationsUrl, instanceId: instance.id }); + const { update: canManage } = useOrganizationClusterInstancePermissions(); + const { mutate: setStatus, isPending: isSettingStatus } = useSetStatus(); + + // We want to spread the initial requests across 5 seconds. + const [randomOffset] = useState(() => Math.floor(Math.random() * 5_000)); + const [ready, setReady] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => setReady(true), randomOffset); + return () => clearTimeout(timer); + }, [randomOffset]); + + const { data: statusResponse, isLoading, isFetching } = useQuery(getStatusQueryOptions(instanceParams, ready)); + + const systemStatus = getSystemStatusById(statusResponse, 'availability') || 'Unknown'; + const isAvailable = systemStatus === 'Available'; + const isUnavailable = systemStatus === 'Unavailable'; + + return ( +