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 2032aab05..00824bc56 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,372 +1,90 @@ "use client"; -import { useState, useEffect } from "react"; +import React, { useEffect } from 'react'; import { useRouter, useParams } from "next/navigation"; import { motion } from "framer-motion"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; import { toast } from "sonner"; -import { ArrowLeft, Check, Loader2, Github, } from "lucide-react"; +import { ArrowLeft, Check, Loader2, Github } from "lucide-react"; -import { useSearchSourceConnectors, SearchSourceConnector } from "@/hooks/useSearchSourceConnectors"; -import { - Form, -} from "@/components/ui/form"; +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 { EditConnectorLoadingSkeleton } from "@/components/editConnector/EditConnectorLoadingSkeleton"; import { EditConnectorNameForm } from "@/components/editConnector/EditConnectorNameForm"; import { EditGitHubConnectorConfig } from "@/components/editConnector/EditGitHubConnectorConfig"; import { EditSimpleTokenForm } from "@/components/editConnector/EditSimpleTokenForm"; - -// Helper function to get connector type display name (copied from manage page) -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", - }; - return typeMap[type] || type; -}; - -// Schema for PAT input when editing GitHub repos (remains separate) -const githubPatSchema = z.object({ - github_pat: z.string() - .min(20, { message: "GitHub Personal Access Token seems too short." }) - .refine(pat => pat.startsWith('ghp_') || pat.startsWith('github_pat_'), { - message: "GitHub PAT should start with 'ghp_' or 'github_pat_'", - }), -}); -type GithubPatFormValues = z.infer; - -// Updated schema for main edit form - includes optional fields for other connector configs -const editConnectorSchema = z.object({ - name: z.string().min(3, { message: "Connector name must be at least 3 characters." }), - // Add optional fields for other connector types' configs - SLACK_BOT_TOKEN: z.string().optional(), - NOTION_INTEGRATION_TOKEN: z.string().optional(), - SERPER_API_KEY: z.string().optional(), - TAVILY_API_KEY: z.string().optional(), - // GITHUB_PAT is handled separately via patForm for repo editing flow -}); -type EditConnectorFormValues = z.infer; - -interface GithubRepo { - id: number; - name: string; - full_name: string; - private: boolean; - url: string; - description: string | null; - last_updated: string | null; -} - -type EditMode = 'viewing' | 'editing_repos'; // Only relevant for GitHub - -export default function EditConnectorPage() { // Renamed for clarity +export default function EditConnectorPage() { const router = useRouter(); const params = useParams(); const searchSpaceId = params.search_space_id as string; - const connectorId = parseInt(params.connector_id as string, 10); + // Ensure connectorId is parsed safely + const connectorIdParam = params.connector_id as string; + const connectorId = connectorIdParam ? parseInt(connectorIdParam, 10) : NaN; - const { connectors, updateConnector, isLoading: connectorsLoading } = useSearchSourceConnectors(); + // 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); - const [connector, setConnector] = useState(null); - const [originalConfig, setOriginalConfig] = useState | null>(null); // Store original config object - - // GitHub specific state (only used if connector type is GitHub) - const [currentSelectedRepos, setCurrentSelectedRepos] = useState([]); - const [originalPat, setOriginalPat] = useState(""); - const [editMode, setEditMode] = useState('viewing'); - const [fetchedRepos, setFetchedRepos] = useState(null); - const [newSelectedRepos, setNewSelectedRepos] = useState([]); - const [isFetchingRepos, setIsFetchingRepos] = useState(false); - - const [isSaving, setIsSaving] = useState(false); - - // Form for GitHub PAT input (only used for GitHub repo editing) - const patForm = useForm({ - resolver: zodResolver(githubPatSchema), - defaultValues: { github_pat: "" }, - }); - - // Main form for connector details (name + simple config fields) - const editForm = useForm({ - resolver: zodResolver(editConnectorSchema), - defaultValues: { - name: "", - SLACK_BOT_TOKEN: "", - NOTION_INTEGRATION_TOKEN: "", - SERPER_API_KEY: "", - TAVILY_API_KEY: "", - }, - }); - - // Effect to load connector data + // Redirect if connectorId is not a valid number after parsing useEffect(() => { - if (!connectorsLoading && connectors.length > 0 && !connector) { - const currentConnector = connectors.find(c => c.id === connectorId); - if (currentConnector) { - setConnector(currentConnector); - setOriginalConfig(currentConnector.config || {}); // Store original config - - // Reset main form with common and type-specific fields - editForm.reset({ - name: currentConnector.name, - SLACK_BOT_TOKEN: currentConnector.config?.SLACK_BOT_TOKEN || "", - NOTION_INTEGRATION_TOKEN: currentConnector.config?.NOTION_INTEGRATION_TOKEN || "", - SERPER_API_KEY: currentConnector.config?.SERPER_API_KEY || "", - TAVILY_API_KEY: currentConnector.config?.TAVILY_API_KEY || "", - }); - - // If GitHub, set up GitHub-specific state - if (currentConnector.connector_type === 'GITHUB_CONNECTOR') { - const savedRepos = currentConnector.config?.repo_full_names || []; - const savedPat = currentConnector.config?.GITHUB_PAT || ""; - setCurrentSelectedRepos(savedRepos); - setNewSelectedRepos(savedRepos); - setOriginalPat(savedPat); - patForm.reset({ github_pat: savedPat }); - setEditMode('viewing'); // Start in viewing mode for repos - } - } else { - toast.error("Connector not found."); - router.push(`/dashboard/${searchSpaceId}/connectors`); - } + if (isNaN(connectorId)) { + toast.error("Invalid Connector ID."); + router.push(`/dashboard/${searchSpaceId}/connectors`); } - }, [connectorId, connectors, connectorsLoading, router, searchSpaceId, connector, editForm, patForm]); - - // Fetch repositories using the entered PAT - const handleFetchRepositories = async (values: GithubPatFormValues) => { - setIsFetchingRepos(true); - setFetchedRepos(null); - try { - const token = localStorage.getItem('surfsense_bearer_token'); - if (!token) throw new Error('No authentication token found'); - - const response = await fetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories/`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify({ github_pat: values.github_pat }) - } - ); - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.detail || `Failed to fetch repositories: ${response.statusText}`); - } - const data: GithubRepo[] = await response.json(); - setFetchedRepos(data); - setNewSelectedRepos(currentSelectedRepos); - toast.success(`Found ${data.length} repositories. Select which ones to index.`); - } catch (error) { - console.error("Error fetching GitHub repositories:", error); - toast.error(error instanceof Error ? error.message : "Failed to fetch repositories."); - } finally { - setIsFetchingRepos(false); - } - }; - - // Handle checkbox changes during editing - const handleRepoSelectionChange = (repoFullName: string, checked: boolean) => { - setNewSelectedRepos(prev => - checked - ? [...prev, repoFullName] - : prev.filter(name => name !== repoFullName) - ); - }; - - // Save changes - updated to handle different connector types - const handleSaveChanges = async (formData: EditConnectorFormValues) => { - if (!connector || !originalConfig) return; - - setIsSaving(true); - const updatePayload: Partial = {}; - let configChanged = false; - let newConfig: Record | null = null; - - // 1. Check if name changed - if (formData.name !== connector.name) { - updatePayload.name = formData.name; - } - - // 2. Check for config changes based on connector type - switch (connector.connector_type) { - case 'GITHUB_CONNECTOR': - const currentPatInForm = patForm.getValues('github_pat'); - const patChanged = currentPatInForm !== originalPat; - const initialRepoSet = new Set(currentSelectedRepos); - const newRepoSet = new Set(newSelectedRepos); - const reposChanged = initialRepoSet.size !== newRepoSet.size || ![...initialRepoSet].every(repo => newRepoSet.has(repo)); - - if (patChanged || (editMode === 'editing_repos' && reposChanged && fetchedRepos !== null)) { - if (!currentPatInForm || !(currentPatInForm.startsWith('ghp_') || currentPatInForm.startsWith('github_pat_'))) { - toast.error("Invalid GitHub PAT format. Cannot save."); - setIsSaving(false); return; - } - newConfig = { - GITHUB_PAT: currentPatInForm, - repo_full_names: newSelectedRepos, - }; - if (reposChanged && newSelectedRepos.length === 0) { - toast.warning("Warning: No repositories selected."); - } - } - break; - - case 'SLACK_CONNECTOR': - if (formData.SLACK_BOT_TOKEN !== originalConfig.SLACK_BOT_TOKEN) { - if (!formData.SLACK_BOT_TOKEN) { - toast.error("Slack Bot Token cannot be empty."); setIsSaving(false); return; - } - newConfig = { SLACK_BOT_TOKEN: formData.SLACK_BOT_TOKEN }; - } - break; - - case 'NOTION_CONNECTOR': - if (formData.NOTION_INTEGRATION_TOKEN !== originalConfig.NOTION_INTEGRATION_TOKEN) { - if (!formData.NOTION_INTEGRATION_TOKEN) { - toast.error("Notion Integration Token cannot be empty."); setIsSaving(false); return; - } - newConfig = { NOTION_INTEGRATION_TOKEN: formData.NOTION_INTEGRATION_TOKEN }; - } - break; - - case 'SERPER_API': - if (formData.SERPER_API_KEY !== originalConfig.SERPER_API_KEY) { - if (!formData.SERPER_API_KEY) { - toast.error("Serper API Key cannot be empty."); setIsSaving(false); return; - } - newConfig = { SERPER_API_KEY: formData.SERPER_API_KEY }; - } - break; - - case 'TAVILY_API': - if (formData.TAVILY_API_KEY !== originalConfig.TAVILY_API_KEY) { - if (!formData.TAVILY_API_KEY) { - toast.error("Tavily API Key cannot be empty."); setIsSaving(false); return; - } - newConfig = { TAVILY_API_KEY: formData.TAVILY_API_KEY }; - } - break; - - // Add cases for other connector types if necessary - } - - // If config was determined to have changed, add it to the payload - if (newConfig !== null) { - updatePayload.config = newConfig; - configChanged = true; - } - - // 3. Check if there are actual changes to save - if (Object.keys(updatePayload).length === 0) { - toast.info("No changes detected."); - setIsSaving(false); - if (connector.connector_type === 'GITHUB_CONNECTOR') { - setEditMode('viewing'); - patForm.reset({ github_pat: originalPat }); - } - return; - } - - // 4. Proceed with update API call - try { - await updateConnector(connectorId, updatePayload); - toast.success("Connector updated successfully!"); - - // Update local state after successful save - const newlySavedConfig = updatePayload.config || originalConfig; - setOriginalConfig(newlySavedConfig); - if (updatePayload.name) { - setConnector(prev => prev ? { ...prev, name: updatePayload.name!, config: newlySavedConfig } : null); - editForm.setValue('name', updatePayload.name); - } else { - setConnector(prev => prev ? { ...prev, config: newlySavedConfig } : null); - } - - if (connector.connector_type === 'GITHUB_CONNECTOR' && configChanged) { - const savedGitHubConfig = newlySavedConfig as { GITHUB_PAT?: string; repo_full_names?: string[] }; - setCurrentSelectedRepos(savedGitHubConfig.repo_full_names || []); - setOriginalPat(savedGitHubConfig.GITHUB_PAT || ""); - setNewSelectedRepos(savedGitHubConfig.repo_full_names || []); - patForm.reset({ github_pat: savedGitHubConfig.GITHUB_PAT || "" }); - } else if (connector.connector_type === 'SLACK_CONNECTOR' && configChanged) { - editForm.setValue('SLACK_BOT_TOKEN', newlySavedConfig.SLACK_BOT_TOKEN || ""); - } // Add similar blocks for Notion, Serper, Tavily - else if (connector.connector_type === 'NOTION_CONNECTOR' && configChanged) { - editForm.setValue('NOTION_INTEGRATION_TOKEN', newlySavedConfig.NOTION_INTEGRATION_TOKEN || ""); - } else if (connector.connector_type === 'SERPER_API' && configChanged) { - editForm.setValue('SERPER_API_KEY', newlySavedConfig.SERPER_API_KEY || ""); - } else if (connector.connector_type === 'TAVILY_API' && configChanged) { - editForm.setValue('TAVILY_API_KEY', newlySavedConfig.TAVILY_API_KEY || ""); - } - - // Reset GitHub specific edit state - if (connector.connector_type === 'GITHUB_CONNECTOR') { - setEditMode('viewing'); - setFetchedRepos(null); - } - - } catch (error) { - console.error("Error updating connector:", error); - toast.error(error instanceof Error ? error.message : "Failed to update connector."); - } finally { - setIsSaving(false); - } - }; + }, [connectorId, router, searchSpaceId]); + // 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 (
- - + - {/* Title can be dynamic based on type */} - {/* TODO: Make icon dynamic */} + {/* TODO: Dynamic icon */} Edit {getConnectorTypeDisplay(connector.connector_type)} Connector - - Modify the connector name and configuration. - + Modify connector name and configuration. -
+ + {/* Pass hook's handleSaveChanges */} - {/* Name Component */} + {/* Pass form control from hook */}
@@ -376,14 +94,15 @@ export default function EditConnectorPage() { // Renamed for clarity {/* == GitHub == */} {connector.connector_type === 'GITHUB_CONNECTOR' && ( )} - {/* == Notion == */} {connector.connector_type === 'NOTION_CONNECTOR' && ( )} - - {/* == Serper API == */} + {/* == Serper == */} {connector.connector_type === 'SERPER_API' && ( )} - - {/* == Tavily API == */} + {/* == Tavily == */} {connector.connector_type === 'TAVILY_API' && ( pat.startsWith('ghp_') || pat.startsWith('github_pat_'), { + message: "GitHub PAT should start with 'ghp_' or 'github_pat_'", + }), +}); +export type GithubPatFormValues = z.infer; + +export const editConnectorSchema = z.object({ + name: z.string().min(3, { message: "Connector name must be at least 3 characters." }), + SLACK_BOT_TOKEN: z.string().optional(), + NOTION_INTEGRATION_TOKEN: z.string().optional(), + SERPER_API_KEY: z.string().optional(), + TAVILY_API_KEY: z.string().optional(), +}); +export type EditConnectorFormValues = z.infer; diff --git a/surfsense_web/hooks/useConnectorEditPage.ts b/surfsense_web/hooks/useConnectorEditPage.ts new file mode 100644 index 000000000..e75998ef9 --- /dev/null +++ b/surfsense_web/hooks/useConnectorEditPage.ts @@ -0,0 +1,210 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { toast } from 'sonner'; +import { useSearchSourceConnectors, SearchSourceConnector } from '@/hooks/useSearchSourceConnectors'; +import { + GithubRepo, + EditMode, + githubPatSchema, + editConnectorSchema, + GithubPatFormValues, + EditConnectorFormValues +} from '@/components/editConnector/types'; + +export function useConnectorEditPage(connectorId: number, searchSpaceId: string) { + const router = useRouter(); + const { connectors, updateConnector, isLoading: connectorsLoading } = useSearchSourceConnectors(); + + // State managed by the hook + const [connector, setConnector] = useState(null); + const [originalConfig, setOriginalConfig] = useState | null>(null); + const [isSaving, setIsSaving] = useState(false); + const [currentSelectedRepos, setCurrentSelectedRepos] = useState([]); + const [originalPat, setOriginalPat] = useState(""); + const [editMode, setEditMode] = useState('viewing'); + const [fetchedRepos, setFetchedRepos] = useState(null); + const [newSelectedRepos, setNewSelectedRepos] = useState([]); + const [isFetchingRepos, setIsFetchingRepos] = useState(false); + + // Forms managed by the hook + const patForm = useForm({ + resolver: zodResolver(githubPatSchema), + defaultValues: { github_pat: "" }, + }); + const editForm = useForm({ + resolver: zodResolver(editConnectorSchema), + defaultValues: { name: "", SLACK_BOT_TOKEN: "", NOTION_INTEGRATION_TOKEN: "", SERPER_API_KEY: "", TAVILY_API_KEY: "" }, + }); + + // Effect to load initial data + useEffect(() => { + if (!connectorsLoading && connectors.length > 0 && !connector) { + const currentConnector = connectors.find(c => c.id === connectorId); + if (currentConnector) { + setConnector(currentConnector); + const config = currentConnector.config || {}; + setOriginalConfig(config); + editForm.reset({ + name: currentConnector.name, + SLACK_BOT_TOKEN: config.SLACK_BOT_TOKEN || "", + NOTION_INTEGRATION_TOKEN: config.NOTION_INTEGRATION_TOKEN || "", + SERPER_API_KEY: config.SERPER_API_KEY || "", + TAVILY_API_KEY: config.TAVILY_API_KEY || "", + }); + if (currentConnector.connector_type === 'GITHUB_CONNECTOR') { + const savedRepos = config.repo_full_names || []; + const savedPat = config.GITHUB_PAT || ""; + setCurrentSelectedRepos(savedRepos); + setNewSelectedRepos(savedRepos); + setOriginalPat(savedPat); + patForm.reset({ github_pat: savedPat }); + setEditMode('viewing'); + } + } else { + toast.error("Connector not found."); + router.push(`/dashboard/${searchSpaceId}/connectors`); + } + } + }, [connectorId, connectors, connectorsLoading, router, searchSpaceId, connector, editForm, patForm]); + + // Handlers managed by the hook + const handleFetchRepositories = useCallback(async (values: GithubPatFormValues) => { + setIsFetchingRepos(true); + setFetchedRepos(null); + try { + const token = localStorage.getItem('surfsense_bearer_token'); + if (!token) throw new Error('No auth token'); + const response = await fetch( + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories/`, + { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ github_pat: values.github_pat }) } + ); + if (!response.ok) { const err = await response.json(); throw new Error(err.detail || 'Fetch failed'); } + const data: GithubRepo[] = await response.json(); + setFetchedRepos(data); + setNewSelectedRepos(currentSelectedRepos); + toast.success(`Found ${data.length} repos.`); + } catch (error) { + console.error("Error fetching GitHub repositories:", error); + toast.error(error instanceof Error ? error.message : "Failed to fetch repositories."); + } finally { setIsFetchingRepos(false); } + }, [currentSelectedRepos]); // Added dependency + + const handleRepoSelectionChange = useCallback((repoFullName: string, checked: boolean) => { + setNewSelectedRepos(prev => checked ? [...prev, repoFullName] : prev.filter(name => name !== repoFullName)); + }, []); + + const handleSaveChanges = useCallback(async (formData: EditConnectorFormValues) => { + if (!connector || !originalConfig) return; + setIsSaving(true); + const updatePayload: Partial = {}; + let configChanged = false; + let newConfig: Record | null = null; + + if (formData.name !== connector.name) { + updatePayload.name = formData.name; + } + + switch (connector.connector_type) { + case 'GITHUB_CONNECTOR': + const currentPatInForm = patForm.getValues('github_pat'); + const patChanged = currentPatInForm !== originalPat; + const initialRepoSet = new Set(currentSelectedRepos); + const newRepoSet = new Set(newSelectedRepos); + const reposChanged = initialRepoSet.size !== newRepoSet.size || ![...initialRepoSet].every(repo => newRepoSet.has(repo)); + if (patChanged || (editMode === 'editing_repos' && reposChanged && fetchedRepos !== null)) { + if (!currentPatInForm || !(currentPatInForm.startsWith('ghp_') || currentPatInForm.startsWith('github_pat_'))) { + toast.error("Invalid GitHub PAT format. Cannot save."); setIsSaving(false); return; + } + newConfig = { GITHUB_PAT: currentPatInForm, repo_full_names: newSelectedRepos }; + if (reposChanged && newSelectedRepos.length === 0) { toast.warning("Warning: No repositories selected."); } + } + break; + case 'SLACK_CONNECTOR': + if (formData.SLACK_BOT_TOKEN !== originalConfig.SLACK_BOT_TOKEN) { + if (!formData.SLACK_BOT_TOKEN) { toast.error("Slack Token empty."); setIsSaving(false); return; } + newConfig = { SLACK_BOT_TOKEN: formData.SLACK_BOT_TOKEN }; + } + break; + // ... other cases ... + case 'NOTION_CONNECTOR': + if (formData.NOTION_INTEGRATION_TOKEN !== originalConfig.NOTION_INTEGRATION_TOKEN) { + if (!formData.NOTION_INTEGRATION_TOKEN) { toast.error("Notion Token empty."); setIsSaving(false); return; } + newConfig = { NOTION_INTEGRATION_TOKEN: formData.NOTION_INTEGRATION_TOKEN }; + } + break; + case 'SERPER_API': + if (formData.SERPER_API_KEY !== originalConfig.SERPER_API_KEY) { + if (!formData.SERPER_API_KEY) { toast.error("Serper Key empty."); setIsSaving(false); return; } + newConfig = { SERPER_API_KEY: formData.SERPER_API_KEY }; + } + break; + case 'TAVILY_API': + if (formData.TAVILY_API_KEY !== originalConfig.TAVILY_API_KEY) { + if (!formData.TAVILY_API_KEY) { toast.error("Tavily Key empty."); setIsSaving(false); return; } + newConfig = { TAVILY_API_KEY: formData.TAVILY_API_KEY }; + } + break; + } + + if (newConfig !== null) { + updatePayload.config = newConfig; + configChanged = true; + } + + if (Object.keys(updatePayload).length === 0) { + toast.info("No changes detected."); + setIsSaving(false); + if (connector.connector_type === 'GITHUB_CONNECTOR') { setEditMode('viewing'); patForm.reset({ github_pat: originalPat }); } + return; + } + + try { + await updateConnector(connectorId, updatePayload); + toast.success("Connector updated!"); + const newlySavedConfig = updatePayload.config || originalConfig; + setOriginalConfig(newlySavedConfig); + if (updatePayload.name) { + setConnector(prev => prev ? { ...prev, name: updatePayload.name!, config: newlySavedConfig } : null); + } + if (connector.connector_type === 'GITHUB_CONNECTOR' && configChanged) { + const savedGitHubConfig = newlySavedConfig as { GITHUB_PAT?: string; repo_full_names?: string[] }; + setCurrentSelectedRepos(savedGitHubConfig.repo_full_names || []); + setOriginalPat(savedGitHubConfig.GITHUB_PAT || ""); + setNewSelectedRepos(savedGitHubConfig.repo_full_names || []); + patForm.reset({ github_pat: savedGitHubConfig.GITHUB_PAT || "" }); + } + if (connector.connector_type === 'GITHUB_CONNECTOR') { + setEditMode('viewing'); + setFetchedRepos(null); + } + // Resetting simple form values is handled by useEffect if connector state updates + } catch (error) { + console.error("Error updating connector:", error); + toast.error(error instanceof Error ? error.message : "Failed to update connector."); + } finally { setIsSaving(false); } + }, [connector, originalConfig, updateConnector, connectorId, patForm, originalPat, currentSelectedRepos, newSelectedRepos, editMode, fetchedRepos]); // Added dependencies + + // Return values needed by the component + return { + connectorsLoading, + connector, + isSaving, + editForm, + patForm, + handleSaveChanges, + // GitHub specific props + editMode, + setEditMode, + originalPat, + currentSelectedRepos, + fetchedRepos, + setFetchedRepos, + newSelectedRepos, + setNewSelectedRepos, + isFetchingRepos, + handleFetchRepositories, + handleRepoSelectionChange, + }; +} diff --git a/surfsense_web/lib/connectors/utils.ts b/surfsense_web/lib/connectors/utils.ts new file mode 100644 index 000000000..f0a6dab45 --- /dev/null +++ b/surfsense_web/lib/connectors/utils.ts @@ -0,0 +1,11 @@ +// Helper function to get connector type display name +export 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", + }; + return typeMap[type] || type; +};