diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx index af92a6ae5..95c769c00 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx @@ -4,256 +4,308 @@ import { useState, useEffect } from "react"; import { useRouter, useParams } from "next/navigation"; import { motion } from "framer-motion"; import { toast } from "sonner"; -import { Edit, Plus, Search, Trash2, ExternalLink, RefreshCw } from "lucide-react"; +import { + Edit, + Plus, + Search, + Trash2, + ExternalLink, + RefreshCw, +} from "lucide-react"; import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from "@/components/ui/table"; import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { getConnectorIcon } from "@/components/chat"; // Helper function to get connector type display name const getConnectorTypeDisplay = (type: string): string => { - const typeMap: Record = { - "SERPER_API": "Serper API", - "TAVILY_API": "Tavily API", - "SLACK_CONNECTOR": "Slack", - "NOTION_CONNECTOR": "Notion", - "GITHUB_CONNECTOR": "GitHub", - "LINEAR_CONNECTOR": "Linear", - "LINKUP_API": "Linkup", - // Add other connector types here as needed - }; - return typeMap[type] || type; + const typeMap: Record = { + SERPER_API: "Serper API", + TAVILY_API: "Tavily API", + SLACK_CONNECTOR: "Slack", + NOTION_CONNECTOR: "Notion", + GITHUB_CONNECTOR: "GitHub", + LINEAR_CONNECTOR: "Linear", + LINKUP_API: "Linkup", + // Add other connector types here as needed + }; + return typeMap[type] || type; }; // Helper function to format date with time const formatDateTime = (dateString: string | null): string => { - if (!dateString) return "Never"; - - const date = new Date(dateString); - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }).format(date); + if (!dateString) return "Never"; + + const date = new Date(dateString); + return new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(date); }; export default function ConnectorsPage() { - const router = useRouter(); - const params = useParams(); - const searchSpaceId = params.search_space_id as string; - - const { connectors, isLoading, error, deleteConnector, indexConnector } = useSearchSourceConnectors(); - const [connectorToDelete, setConnectorToDelete] = useState(null); - const [indexingConnectorId, setIndexingConnectorId] = useState(null); + const router = useRouter(); + const params = useParams(); + const searchSpaceId = params.search_space_id as string; - useEffect(() => { - if (error) { - toast.error("Failed to load connectors"); - console.error("Error fetching connectors:", error); - } - }, [error]); + const { connectors, isLoading, error, deleteConnector, indexConnector } = + useSearchSourceConnectors(); + const [connectorToDelete, setConnectorToDelete] = useState( + null, + ); + const [indexingConnectorId, setIndexingConnectorId] = useState( + null, + ); - // Handle connector deletion - const handleDeleteConnector = async () => { - if (connectorToDelete === null) return; - - try { - await deleteConnector(connectorToDelete); - toast.success("Connector deleted successfully"); - } catch (error) { - console.error("Error deleting connector:", error); - toast.error("Failed to delete connector"); - } finally { - setConnectorToDelete(null); - } - }; + useEffect(() => { + if (error) { + toast.error("Failed to load connectors"); + console.error("Error fetching connectors:", error); + } + }, [error]); - // Handle connector indexing - const handleIndexConnector = async (connectorId: number) => { - setIndexingConnectorId(connectorId); - try { - await indexConnector(connectorId, searchSpaceId); - toast.success("Connector content indexed successfully"); - } catch (error) { - console.error("Error indexing connector content:", error); - toast.error(error instanceof Error ? error.message : "Failed to index connector content"); - } finally { - setIndexingConnectorId(null); - } - }; + // Handle connector deletion + const handleDeleteConnector = async () => { + if (connectorToDelete === null) return; - return ( -
- -
-

Connectors

-

- Manage your connected services and data sources. -

-
- -
+ try { + await deleteConnector(connectorToDelete); + toast.success("Connector deleted successfully"); + } catch (error) { + console.error("Error deleting connector:", error); + toast.error("Failed to delete connector"); + } finally { + setConnectorToDelete(null); + } + }; - - - Your Connectors - - View and manage all your connected services. - - - - {isLoading ? ( -
-
-
-
-
-
- ) : connectors.length === 0 ? ( -
-

No connectors found

-

- You haven't added any connectors yet. Add one to enhance your search capabilities. -

- -
- ) : ( -
- - - - Name - Type - Last Indexed - Actions - - - - {connectors.map((connector) => ( - - {connector.name} - {getConnectorTypeDisplay(connector.connector_type)} - - {connector.is_indexable - ? formatDateTime(connector.last_indexed_at) - : "Not indexable"} - - -
- {connector.is_indexable && ( - - - - - - -

Index Content

-
-
-
- )} - - - - - - - - Delete Connector - - Are you sure you want to delete this connector? This action cannot be undone. - - - - setConnectorToDelete(null)}> - Cancel - - - Delete - - - - -
-
-
- ))} -
-
-
- )} -
-
-
- ); -} + // Handle connector indexing + const handleIndexConnector = async (connectorId: number) => { + setIndexingConnectorId(connectorId); + try { + await indexConnector(connectorId, searchSpaceId); + toast.success("Connector content indexed successfully"); + } catch (error) { + console.error("Error indexing connector content:", error); + toast.error( + error instanceof Error + ? error.message + : "Failed to index connector content", + ); + } finally { + setIndexingConnectorId(null); + } + }; + + return ( +
+ +
+

Connectors

+

+ Manage your connected services and data sources. +

+
+ +
+ + + + Your Connectors + + View and manage all your connected services. + + + + {isLoading ? ( +
+
+
+
+
+
+ ) : connectors.length === 0 ? ( +
+

No connectors found

+

+ You haven't added any connectors yet. Add one to enhance your + search capabilities. +

+ +
+ ) : ( +
+ + + + Name + Type + Last Indexed + Actions + + + + {connectors.map((connector) => ( + + + {connector.name} + + + {getConnectorIcon(connector.connector_type)} + + + {connector.is_indexable + ? formatDateTime(connector.last_indexed_at) + : "Not indexable"} + + +
+ {connector.is_indexable && ( + + + + + + +

Index Content

+
+
+
+ )} + + + + + + + + + Delete Connector + + + Are you sure you want to delete this + connector? This action cannot be undone. + + + + setConnectorToDelete(null)} + > + Cancel + + + Delete + + + + +
+
+
+ ))} +
+
+
+ )} +
+
+
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx index 5afea12c9..644dbc981 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect } from 'react'; +import React, { useEffect } from "react"; import { useRouter, useParams } from "next/navigation"; import { motion } from "framer-motion"; import { toast } from "sonner"; @@ -8,180 +8,208 @@ import { ArrowLeft, Check, Loader2, Github } from "lucide-react"; import { Form } from "@/components/ui/form"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; // Import Utils, Types, Hook, and Components -import { getConnectorTypeDisplay } from '@/lib/connectors/utils'; -import { useConnectorEditPage } from '@/hooks/useConnectorEditPage'; +import { getConnectorTypeDisplay } from "@/lib/connectors/utils"; +import { useConnectorEditPage } from "@/hooks/useConnectorEditPage"; import { EditConnectorLoadingSkeleton } from "@/components/editConnector/EditConnectorLoadingSkeleton"; import { EditConnectorNameForm } from "@/components/editConnector/EditConnectorNameForm"; import { EditGitHubConnectorConfig } from "@/components/editConnector/EditGitHubConnectorConfig"; import { EditSimpleTokenForm } from "@/components/editConnector/EditSimpleTokenForm"; +import { getConnectorIcon } from "@/components/chat"; export default function EditConnectorPage() { - const router = useRouter(); - const params = useParams(); - const searchSpaceId = params.search_space_id as string; - // Ensure connectorId is parsed safely - const connectorIdParam = params.connector_id as string; - const connectorId = connectorIdParam ? parseInt(connectorIdParam, 10) : NaN; + const router = useRouter(); + const params = useParams(); + const searchSpaceId = params.search_space_id as string; + // Ensure connectorId is parsed safely + const connectorIdParam = params.connector_id as string; + const connectorId = connectorIdParam ? parseInt(connectorIdParam, 10) : NaN; - // Use the custom hook to manage state and logic - const { - connectorsLoading, - connector, - isSaving, - editForm, - patForm, // Needed for GitHub child component - handleSaveChanges, - // GitHub specific props for the child component - editMode, - setEditMode, // Pass down if needed by GitHub component - originalPat, - currentSelectedRepos, - fetchedRepos, - setFetchedRepos, - newSelectedRepos, - setNewSelectedRepos, - isFetchingRepos, - handleFetchRepositories, - handleRepoSelectionChange, - } = useConnectorEditPage(connectorId, searchSpaceId); + // Use the custom hook to manage state and logic + const { + connectorsLoading, + connector, + isSaving, + editForm, + patForm, // Needed for GitHub child component + handleSaveChanges, + // GitHub specific props for the child component + editMode, + setEditMode, // Pass down if needed by GitHub component + originalPat, + currentSelectedRepos, + fetchedRepos, + setFetchedRepos, + newSelectedRepos, + setNewSelectedRepos, + isFetchingRepos, + handleFetchRepositories, + handleRepoSelectionChange, + } = useConnectorEditPage(connectorId, searchSpaceId); - // Redirect if connectorId is not a valid number after parsing - useEffect(() => { - if (isNaN(connectorId)) { - toast.error("Invalid Connector ID."); - router.push(`/dashboard/${searchSpaceId}/connectors`); - } - }, [connectorId, router, searchSpaceId]); + // Redirect if connectorId is not a valid number after parsing + useEffect(() => { + if (isNaN(connectorId)) { + toast.error("Invalid Connector ID."); + router.push(`/dashboard/${searchSpaceId}/connectors`); + } + }, [connectorId, router, searchSpaceId]); - // Loading State - if (connectorsLoading || !connector) { - // Handle NaN case before showing skeleton - if (isNaN(connectorId)) return null; - return ; - } + // Loading State + if (connectorsLoading || !connector) { + // Handle NaN case before showing skeleton + if (isNaN(connectorId)) return null; + return ; + } - // Main Render using data/handlers from the hook - return ( -
- + // Main Render using data/handlers from the hook + return ( +
+ - - - - - {/* TODO: Dynamic icon */} - Edit {getConnectorTypeDisplay(connector.connector_type)} Connector - - Modify connector name and configuration. - + + + + + {getConnectorIcon(connector.connector_type)} + Edit {getConnectorTypeDisplay(connector.connector_type)} Connector + + + Modify connector name and configuration. + + -
- {/* Pass hook's handleSaveChanges */} - - - {/* Pass form control from hook */} - + + {/* Pass hook's handleSaveChanges */} + + + {/* Pass form control from hook */} + -
+
-

Configuration

+

Configuration

- {/* == GitHub == */} - {connector.connector_type === 'GITHUB_CONNECTOR' && ( - - )} + {/* == GitHub == */} + {connector.connector_type === "GITHUB_CONNECTOR" && ( + + )} - {/* == Slack == */} - {connector.connector_type === 'SLACK_CONNECTOR' && ( - - )} - {/* == Notion == */} - {connector.connector_type === 'NOTION_CONNECTOR' && ( - - )} - {/* == Serper == */} - {connector.connector_type === 'SERPER_API' && ( - - )} - {/* == Tavily == */} - {connector.connector_type === 'TAVILY_API' && ( - - )} + {/* == Slack == */} + {connector.connector_type === "SLACK_CONNECTOR" && ( + + )} + {/* == Notion == */} + {connector.connector_type === "NOTION_CONNECTOR" && ( + + )} + {/* == Serper == */} + {connector.connector_type === "SERPER_API" && ( + + )} + {/* == Tavily == */} + {connector.connector_type === "TAVILY_API" && ( + + )} - {/* == Linear == */} - {connector.connector_type === 'LINEAR_CONNECTOR' && ( - - )} + {/* == Linear == */} + {connector.connector_type === "LINEAR_CONNECTOR" && ( + + )} - {/* == Linkup == */} - {connector.connector_type === 'LINKUP_API' && ( - - )} - -
- - - - - -
-
-
- ); -} + {/* == Linkup == */} + {connector.connector_type === "LINKUP_API" && ( + + )} + + + + + + + + +
+ ); +}