Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions portals/ai-workspace/src/hooks/useGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import {
getGateways,
registerGateway,
rotateGatewayToken,
updateGateway,
deleteGateway,
HybridGateway,
Expand Down Expand Up @@ -52,7 +53,15 @@ interface UseGatewayListReturn {
isDeleting: boolean;
}

export function useGatewayList(): UseGatewayListReturn {
type UseGatewayListOptions = {
pollAlways?: boolean;
pollingIntervalMs?: number;
};

export function useGatewayList(
options: UseGatewayListOptions = {}
): UseGatewayListReturn {
const { pollAlways = false, pollingIntervalMs = 5000 } = options;
const [gateways, setGateways] = useState<HybridGateway[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
Expand Down Expand Up @@ -101,13 +110,14 @@ export function useGatewayList(): UseGatewayListReturn {
fetchGateways();
}, [fetchGateways]);

// Poll every 5 seconds while any gateway is inactive
// Poll while the page needs fresh status updates.
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);

useEffect(() => {
const hasInactive = gateways.some((gw) => !gw.isActive);
const shouldPoll = pollAlways || hasInactive;

if (hasInactive && !isLoading) {
if (shouldPoll && !isLoading) {
if (!pollingRef.current) {
pollingRef.current = setInterval(async () => {
if (!organizationId) return;
Expand All @@ -123,7 +133,7 @@ export function useGatewayList(): UseGatewayListReturn {
} catch (err) {
console.error('Polling failed:', err);
}
}, 5000);
}, pollingIntervalMs);
}
} else if (pollingRef.current) {
clearInterval(pollingRef.current);
Expand All @@ -136,7 +146,7 @@ export function useGatewayList(): UseGatewayListReturn {
pollingRef.current = null;
}
};
}, [gateways, isLoading, organizationId]);
}, [gateways, isLoading, organizationId, pollAlways, pollingIntervalMs]);

// Auto-select first gateway when gateways list changes and none is selected
useEffect(() => {
Expand All @@ -154,10 +164,26 @@ export function useGatewayList(): UseGatewayListReturn {
setIsCreating(true);
try {
const response = await registerGateway(data, organizationId);
let initialToken = response.data.token ?? null;

// The create-gateway response does not always include the one-time
// registration token, so generate it immediately for the first-view flow.
if (!initialToken) {
try {
initialToken = await rotateGatewayToken(response.data.id, organizationId);
} catch (tokenError) {
console.error('Failed to generate initial gateway registration token:', tokenError);
showSnackbar(
'Gateway created, but the initial registration token could not be generated. Use Reconfigure on the gateway page.',
'warning'
);
}
}

const newGateway: HybridGateway = {
...response.data,
status: response.data.isActive ? 'connected' : 'pending',
token: response.data.token,
token: initialToken,
};

// Add to local state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,7 @@ export default function ApplicationsList() {
</Grid>
) : (
filteredApplications.map((app) => {
const descriptionText =
app.description?.trim() ?? 'No description';
const descriptionText = app.description?.trim() || '';
const lastUpdated =
app.lastUpdated ?? app.updatedAt ?? app.createdAt;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,7 @@ export default function GenAIApplicationsSummaryCardSection({
fontSize="0.7rem"
noWrap
>
{truncateWords(
application.description || 'No description',
12
)}
{truncateWords(application.description?.trim() || '', 12)}
</Typography>
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ import { mcpProxiesApis } from '../../../../apis/MCP/mcpProxiesApis';
import type { MCPServer } from '../../../../utils/types';
import NoMCPServers from '../../../../assets/images/NoMCPServers.svg';

export default function ExternalServersList(): JSX.Element {
function getErrorDescription(error: unknown, fallbackMessage: string): string {
return (
(error as any)?.response?.data?.description ||
(error as any)?.response?.data?.message ||
(error instanceof Error ? error.message : null) ||
fallbackMessage
);
}

export default function ExternalServersList(): React.JSX.Element {
const navigate = useNavigate();
const { projectSlug } = useParams<{ projectSlug: string }>();
const {
Expand Down Expand Up @@ -162,8 +171,11 @@ export default function ExternalServersList(): JSX.Element {
await mcpProxiesApis.deleteMCPServer(serverId, apimBaseUrl);
setServers((prev) => prev.filter((s) => s.id !== serverId));
showSnackbar('MCP Proxy deleted successfully.', 'success');
} catch {
showSnackbar('Failed to delete MCP Proxy.', 'error');
} catch (error) {
showSnackbar(
getErrorDescription(error, 'Failed to delete MCP Proxy.'),
'error'
);
}
setDeleteTarget(null);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ export default function MCPProxiesSummaryCardSection({
fontSize="0.7rem"
noWrap
>
{truncateWords(server.description || 'No description', 12)}
{truncateWords(server.description?.trim() || '', 12)}
</Typography>
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default function EditGateway() {
? [normalizedVhost, ...otherEndpoints]
: undefined,
functionalityType,
description: description || undefined,
description,
});

showSnackbar('AI Gateway updated successfully', 'success');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function GatewaysList() {
} | null>(null);

const { gateways, isLoading, error, refetch, deleteGatewayById } =
useGatewayList();
useGatewayList({ pollAlways: true });

// Prefetch environments so they're available when navigating to Add/Edit views
useEnvironments();
Expand Down Expand Up @@ -185,31 +185,31 @@ export default function GatewaysList() {
sx={{ ml: 'auto', flexShrink: 0 }}
>
{isAdmin && filteredGateways.length > 0 ? (
<Tooltip
title={isGatewayQuotaReached ? gatewayQuotaTooltip : ''}
disableHoverListener={!isGatewayQuotaReached}
>
// <Tooltip
// title={isGatewayQuotaReached ? gatewayQuotaTooltip : ''}
// disableHoverListener={!isGatewayQuotaReached}
// >
<Box component="span">
<Button
variant="contained"
component={RouterLink}
to={newGatewayPath}
startIcon={<Plus size={20} />}
disabled={isGatewayQuotaReached}
sx={{
opacity: isGatewayQuotaReached ? 0.55 : 1,
'&.Mui-disabled': {
opacity: isGatewayQuotaReached ? 0.55 : 1,
},
}}
// disabled={isGatewayQuotaReached}
// sx={{
// opacity: isGatewayQuotaReached ? 0.55 : 1,
// '&.Mui-disabled': {
// opacity: isGatewayQuotaReached ? 0.55 : 1,
// },
// }}
>
<FormattedMessage
id="aiWorkspace.pages.appShell.appShellPages.gateways.GatewaysList.add.ai.gateway"
defaultMessage="Add AI Gateway"
/>
</Button>
</Box>
</Tooltip>
// </Tooltip>
) : null}
</Stack>
</Box>
Expand Down
Loading
Loading