diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx index 7c7ad7e..eacb2c6 100644 --- a/frontend/src/app/agents/page.tsx +++ b/frontend/src/app/agents/page.tsx @@ -9,9 +9,25 @@ import { useAuth } from "@/auth/clerk"; import { useQueryClient } from "@tanstack/react-query"; import { AgentsTable } from "@/components/agents/AgentsTable"; +import { GatewayAgentImportDialog } from "@/components/gateways/GatewayAgentImportDialog"; import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout"; import { Button } from "@/components/ui/button"; import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { ApiError } from "@/api/mutator"; import { @@ -25,7 +41,12 @@ import { getListBoardsApiV1BoardsGetQueryKey, useListBoardsApiV1BoardsGet, } from "@/api/generated/boards/boards"; -import { type AgentRead } from "@/api/generated/model"; +import { + type listGatewaysApiV1GatewaysGetResponse, + getListGatewaysApiV1GatewaysGetQueryKey, + useListGatewaysApiV1GatewaysGet, +} from "@/api/generated/gateways/gateways"; +import { type AgentRead, type GatewayRead } from "@/api/generated/model"; import { createOptimisticListDeleteMutation } from "@/lib/list-delete"; import { useOrganizationMembership } from "@/lib/use-organization-membership"; import { useUrlSorting } from "@/lib/use-url-sorting"; @@ -52,9 +73,13 @@ export default function AgentsPage() { }); const [deleteTarget, setDeleteTarget] = useState(null); + const [importGatewayPickerOpen, setImportGatewayPickerOpen] = useState(false); + const [selectedGatewayId, setSelectedGatewayId] = useState(""); + const [importTarget, setImportTarget] = useState(null); const boardsKey = getListBoardsApiV1BoardsGetQueryKey(); const agentsKey = getListAgentsApiV1AgentsGetQueryKey(); + const gatewaysKey = getListGatewaysApiV1GatewaysGetQueryKey(); const boardsQuery = useListBoardsApiV1BoardsGet< listBoardsApiV1BoardsGetResponse, @@ -77,6 +102,16 @@ export default function AgentsPage() { refetchOnMount: "always", }, }); + const gatewaysQuery = useListGatewaysApiV1GatewaysGet< + listGatewaysApiV1GatewaysGetResponse, + ApiError + >(undefined, { + query: { + enabled: Boolean(isSignedIn && isAdmin), + refetchInterval: 30_000, + refetchOnMount: "always", + }, + }); const boards = useMemo( () => @@ -92,6 +127,13 @@ export default function AgentsPage() { : [], [agentsQuery.data], ); + const gateways = useMemo( + () => + gatewaysQuery.data?.status === 200 + ? (gatewaysQuery.data.data.items ?? []) + : [], + [gatewaysQuery.data], + ); const deleteMutation = useDeleteAgentApiV1AgentsAgentIdDelete< ApiError, @@ -121,6 +163,26 @@ export default function AgentsPage() { deleteMutation.mutate({ agentId: deleteTarget.id }); }; + const handleImportAgentsClick = () => { + if (gateways.length === 1) { + setImportTarget(gateways[0]); + return; + } + setImportGatewayPickerOpen(true); + }; + + const handleOpenImportForSelectedGateway = () => { + const gateway = gateways.find((item) => item.id === selectedGatewayId); + if (!gateway) return; + setImportGatewayPickerOpen(false); + setImportTarget(gateway); + }; + + const handleImported = async () => { + await queryClient.invalidateQueries({ queryKey: agentsKey }); + await queryClient.invalidateQueries({ queryKey: gatewaysKey }); + }; + return ( <> 0 ? ( - +
+ + +
) : null } isAdmin={isAdmin} @@ -187,6 +258,65 @@ export default function AgentsPage() { onConfirm={handleDelete} isConfirming={deleteMutation.isPending} /> + + { + setImportGatewayPickerOpen(open); + if (!open) { + setSelectedGatewayId(""); + } + }} + > + + + Import Agents + + Select a gateway to open the agent import screen. + + +
+

Gateway

+ +
+ + + + +
+
+ + { + if (!open) { + setImportTarget(null); + } + }} + gateway={importTarget} + onImported={handleImported} + /> ); }