- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx
deleted file mode 100644
index 252a8e1d2..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { updateConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import type { EnumConnectorName } from "@/contracts/enums/connector";
-import type { SearchSourceConnector } from "@/hooks/use-search-source-connectors";
-
-// Define the form schema with Zod
-const apiConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z.string().min(10, {
- message: "API key is required and must be valid.",
- }),
-});
-
-// Helper function to get connector type display name
-const getConnectorTypeDisplay = (type: string): string => {
- const typeMap: Record = {
- TAVILY_API: "Tavily API",
- SLACK_CONNECTOR: "Slack Connector",
- NOTION_CONNECTOR: "Notion Connector",
- GITHUB_CONNECTOR: "GitHub Connector",
- LINEAR_CONNECTOR: "Linear Connector",
- JIRA_CONNECTOR: "Jira Connector",
- DISCORD_CONNECTOR: "Discord Connector",
- LINKUP_API: "Linkup",
- CONFLUENCE_CONNECTOR: "Confluence Connector",
- CLICKUP_CONNECTOR: "ClickUp Connector",
- GOOGLE_CALENDAR_CONNECTOR: "Google Calendar Connector",
- GOOGLE_GMAIL_CONNECTOR: "Google Gmail Connector",
- AIRTABLE_CONNECTOR: "Airtable Connector",
- LUMA_CONNECTOR: "Luma Connector",
- ELASTICSEARCH_CONNECTOR: "Elasticsearch Connector",
- WEBCRAWLER_CONNECTOR: "Web Page Connector",
- // Add other connector types here as needed
- };
- return typeMap[type] || type;
-};
-
-// Define the type for the form values
-type ApiConnectorFormValues = z.infer;
-
-// Get API key field name based on connector type
-const getApiKeyFieldName = (connectorType: string): string => {
- const fieldMap: Record = {
- TAVILY_API: "TAVILY_API_KEY",
- SLACK_CONNECTOR: "SLACK_BOT_TOKEN",
- NOTION_CONNECTOR: "NOTION_INTEGRATION_TOKEN",
- GITHUB_CONNECTOR: "GITHUB_PAT",
- DISCORD_CONNECTOR: "DISCORD_BOT_TOKEN",
- LINKUP_API: "LINKUP_API_KEY",
- LUMA_CONNECTOR: "LUMA_API_KEY",
- ELASTICSEARCH_CONNECTOR: "ELASTICSEARCH_API_KEY",
- WEBCRAWLER_CONNECTOR: "FIRECRAWL_API_KEY",
- };
- return fieldMap[connectorType] || "";
-};
-
-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);
-
- const { data: connectors = [] } = useAtomValue(connectorsAtom);
- const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
- const [connector, setConnector] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const [isSubmitting, setIsSubmitting] = useState(false);
- // console.log("connector", connector);
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(apiConnectorFormSchema),
- defaultValues: {
- name: "",
- api_key: "",
- },
- });
-
- useEffect(() => {
- const currentConnector = connectors.find((c) => c.id === connectorId);
-
- if (currentConnector) {
- setConnector(currentConnector);
-
- const apiKeyField = getApiKeyFieldName(currentConnector.connector_type);
- if (apiKeyField) {
- form.reset({
- name: currentConnector.name,
- api_key: currentConnector.config[apiKeyField] || "",
- });
- } else {
- toast.error("This connector type is not supported for editing");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- }
-
- setIsLoading(false);
- } else if (!isLoading && connectors.length > 0) {
- toast.error("Connector not found");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- }
- }, [connectors, connectorId, form, router, searchSpaceId, isLoading]);
-
- // Handle form submission
- const onSubmit = async (values: ApiConnectorFormValues) => {
- if (!connector) return;
-
- setIsSubmitting(true);
- try {
- const apiKeyField = getApiKeyFieldName(connector.connector_type);
-
- const updatedConfig = { ...connector.config };
- if (values.api_key) {
- updatedConfig[apiKeyField] = values.api_key;
- }
-
- await updateConnector({
- id: connectorId,
- data: {
- name: values.name,
- connector_type: connector.connector_type as EnumConnectorName,
- config: updatedConfig,
- is_indexable: connector.is_indexable,
- last_indexed_at: connector.last_indexed_at,
- },
- });
-
- toast.success("Connector updated successfully!");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error updating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to update connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- if (isLoading) {
- return (
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- Edit {connector ? getConnectorTypeDisplay(connector.connector_type) : ""} Connector
-
- Update your connector settings.
-
-
-
-
- API Key Security
-
- Your API key is stored securely. For security reasons, we don't display your
- existing API key. If you don't update the API key field, your existing key will be
- preserved.
-
-
-
-
-
-
-
-
-
-
- {/* OAuth Connection Card */}
- {!doesConnectorExist ? (
-
-
- Connect Your Airtable Account
-
- Connect your Airtable account to access your records. We'll only request read-only
- access to your records.
-
-
-
-
-
- Read-only access to your records
-
-
-
- Access works even when you're offline
-
-
-
- You can disconnect anytime
-
-
-
-
-
-
-
- ) : (
- /* Configuration Form Card */
-
-
- ✅ Your Airtable is successfully connected!
-
-
- )}
-
- {/* Help Section */}
- {!doesConnectorExist && (
-
-
- How It Works
-
-
-
-
1. Connect Your Account
-
- Click "Connect Your Airtable Account" to start the secure OAuth process. You'll be
- redirected to Airtable to sign in.
-
-
-
-
2. Grant Permissions
-
- Airtable will ask for permission to read your records. We only request read-only
- access to keep your data safe.
-
-
-
-
- )}
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx
deleted file mode 100644
index 204925d26..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx
+++ /dev/null
@@ -1,323 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { Switch } from "@/components/ui/switch";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const baiduSearchApiFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z.string().min(10, {
- message: "API key is required and must be valid.",
- }),
- model: z.string().optional(),
- search_source: z.enum(["baidu_search_v1", "baidu_search_v2"]).optional(),
- enable_deep_search: z.boolean().default(false),
-});
-
-// Define the type for the form values
-type BaiduSearchApiFormValues = z.infer;
-
-export default function BaiduSearchApiPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(baiduSearchApiFormSchema),
- defaultValues: {
- name: "Baidu Search Connector",
- api_key: "",
- model: "ernie-3.5-8k",
- search_source: "baidu_search_v2",
- enable_deep_search: false,
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: BaiduSearchApiFormValues) => {
- setIsSubmitting(true);
- try {
- // Build config object
- const config: Record = {
- BAIDU_API_KEY: values.api_key,
- };
-
- // Add optional parameters if provided
- if (values.model) {
- config.BAIDU_MODEL = values.model;
- }
- if (values.search_source) {
- config.BAIDU_SEARCH_SOURCE = values.search_source;
- }
- if (values.enable_deep_search !== undefined) {
- config.BAIDU_ENABLE_DEEP_SEARCH = values.enable_deep_search;
- }
-
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.BAIDU_SEARCH_API,
- config,
- is_indexable: false,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Baidu Search connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect Baidu AI Search for intelligent Chinese web search capabilities.
-
-
-
-
-
-
-
-
- Connect Baidu Search
-
- Integrate with Baidu AI Search to enhance your search capabilities with intelligent
- Chinese web search results.
-
-
-
-
-
- API Key Required
-
- You'll need a Baidu AppBuilder API key to use this connector. You can get one by
- signing up at{" "}
-
- qianfan.cloud.baidu.com
-
-
-
-
-
-
-
-
-
What you get with Baidu Search:
-
-
Intelligent search tailored for Chinese web content
-
Real-time information from Baidu's search index
-
AI-powered summarization with source references
-
Support for web, video, and image search results
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/bookstack-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/bookstack-connector/page.tsx
deleted file mode 100644
index 3fa634238..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/bookstack-connector/page.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { Alert, AlertDescription } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const bookstackConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- base_url: z.string().url({
- message: "Please enter a valid BookStack URL (e.g., https://docs.example.com)",
- }),
- token_id: z.string().min(10, {
- message: "BookStack Token ID is required.",
- }),
- token_secret: z.string().min(10, {
- message: "BookStack Token Secret is required.",
- }),
-});
-
-// Define the type for the form values
-type BookStackConnectorFormValues = z.infer;
-
-export default function BookStackConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(bookstackConnectorFormSchema),
- defaultValues: {
- name: "BookStack Connector",
- base_url: "",
- token_id: "",
- token_secret: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: BookStackConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.BOOKSTACK_CONNECTOR,
- config: {
- BOOKSTACK_BASE_URL: values.base_url,
- BOOKSTACK_TOKEN_ID: values.token_id,
- BOOKSTACK_TOKEN_SECRET: values.token_secret,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("BookStack connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your BookStack instance to search wiki pages.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect to BookStack
-
- Connect your BookStack instance to index pages from your wiki.
-
-
-
-
-
-
- You'll need to create an API token from your BookStack instance. Go to{" "}
- Edit Profile → API Tokens → Create Token
-
-
-
-
-
-
-
-
-
-
-
-
- BookStack Integration Guide
-
- Learn how to set up and use the BookStack connector.
-
-
-
-
-
What gets indexed?
-
-
All pages from your BookStack instance
-
Page content in Markdown format
-
Page titles and metadata
-
Book and chapter hierarchy information
-
-
-
-
-
Setup Instructions
-
-
Log in to your BookStack instance
-
Click on your profile icon → Edit Profile
-
Navigate to the "API Tokens" tab
-
Click "Create Token" and give it a name
-
Copy both the Token ID and Token Secret
-
Paste them in the form above
-
-
-
-
-
Permissions Required
-
-
Your user account must have "Access System API" permission
-
Read access to books and pages you want to index
-
The connector will only index content your account can view
-
-
-
-
-
-
- BookStack API has a rate limit of 180 requests per minute. The connector
- automatically handles rate limiting to ensure reliable indexing.
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/circleback-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/circleback-connector/page.tsx
deleted file mode 100644
index ce28de5be..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/circleback-connector/page.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Copy, ExternalLink, Loader2, Webhook } from "lucide-react";
-import { motion } from "motion/react";
-import Link from "next/link";
-import { useParams, useRouter } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import type { SearchSourceConnector } from "@/contracts/types/connector.types";
-
-// Define the form schema with Zod
-const circlebackConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
-});
-
-// Define the type for the form values
-type CirclebackConnectorFormValues = z.infer;
-
-export default function CirclebackConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [doesConnectorExist, setDoesConnectorExist] = useState(false);
- const [copied, setCopied] = useState(false);
-
- const { data: connectors } = useAtomValue(connectorsAtom);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Construct the webhook URL
- const apiBaseUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000";
- const webhookUrl = `${apiBaseUrl}/api/v1/webhooks/circleback/${searchSpaceId}`;
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(circlebackConnectorFormSchema),
- defaultValues: {
- name: "Circleback Meetings",
- },
- });
-
- const { refetch: fetchConnectors } = useAtomValue(connectorsAtom);
-
- useEffect(() => {
- fetchConnectors().then((data) => {
- const connectors = data.data || [];
- const connector = connectors.find(
- (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.CIRCLEBACK_CONNECTOR
- );
- if (connector) {
- setDoesConnectorExist(true);
- }
- });
- }, []);
-
- // Copy webhook URL to clipboard
- const copyToClipboard = async () => {
- try {
- await navigator.clipboard.writeText(webhookUrl);
- setCopied(true);
- toast.success("Webhook URL copied to clipboard!");
- setTimeout(() => setCopied(false), 2000);
- } catch {
- toast.error("Failed to copy to clipboard");
- }
- };
-
- // Handle form submission
- const onSubmit = async (values: CirclebackConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.CIRCLEBACK_CONNECTOR,
- config: {
- webhook_url: webhookUrl,
- },
- is_indexable: false, // Webhooks push data, not indexed
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Circleback connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Receive meeting notes and transcripts via webhook.
-
-
-
-
-
- {/* Connection Card */}
- {!doesConnectorExist ? (
- <>
-
-
- Webhook Configuration
-
- Use this webhook URL in your Circleback automation to send meeting data to
- SurfSense.
-
-
-
-
-
-
-
-
-
-
- Copy this URL and paste it in your Circleback automation settings.
-
-
-
-
-
- How it works
-
- When you configure this webhook in Circleback, it will automatically send
- meeting notes, transcripts, and action items to SurfSense after each meeting.
-
-
-
-
-
-
-
- Create Connector
-
- Register the Circleback connector to track incoming meeting data.
-
-
-
-
-
- >
- ) : (
- /* Success Card */
-
-
- ✅ Circleback connector is active!
-
- Your Circleback meetings will be automatically imported to this search space.
-
-
-
-
- Connect your ClickUp workspace to search tasks and projects.
-
-
-
-
-
-
-
- ClickUp Configuration
-
- Enter your ClickUp API token to connect your workspace. You can generate a personal API
- token from your ClickUp settings.
-
-
-
-
-
-
-
-
-
-
- How to get your ClickUp API Token
-
-
-
-
1. Log in to your ClickUp account
-
- 2. Click your avatar in the upper-right corner and select "Settings"
-
-
3. In the sidebar, click "Apps"
-
- 4. Under "API Token", click "Generate" or "Regenerate"
-
-
- 5. Copy the generated token and paste it above
-
-
-
-
- Go to ClickUp API Settings
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx
deleted file mode 100644
index 7fcd03062..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx
+++ /dev/null
@@ -1,322 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { Alert, AlertDescription } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const confluenceConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- base_url: z
- .string()
- .url({
- message: "Please enter a valid Confluence URL (e.g., https://yourcompany.atlassian.net)",
- })
- .refine(
- (url) => {
- return url.includes("atlassian.net") || url.includes("confluence");
- },
- {
- message: "Please enter a valid Confluence instance URL",
- }
- ),
- email: z.string().email({
- message: "Please enter a valid email address.",
- }),
- api_token: z.string().min(10, {
- message: "Confluence API Token is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type ConfluenceConnectorFormValues = z.infer;
-
-export default function ConfluenceConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(confluenceConnectorFormSchema),
- defaultValues: {
- name: "Confluence Connector",
- base_url: "",
- email: "",
- api_token: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: ConfluenceConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.CONFLUENCE_CONNECTOR,
- config: {
- CONFLUENCE_BASE_URL: values.base_url,
- CONFLUENCE_EMAIL: values.email,
- CONFLUENCE_API_TOKEN: values.api_token,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Confluence connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Confluence instance to search pages and spaces.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect to Confluence
-
- Connect your Confluence instance to index pages and comments from your spaces.
-
-
-
-
-
-
- You'll need to create an API token from your{" "}
-
- Atlassian Account Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
- Confluence Integration Guide
-
- Learn how to set up and use the Confluence connector.
-
-
-
-
-
What gets indexed?
-
-
All pages from accessible spaces
-
Page content and metadata
-
Comments on pages (both footer and inline comments)
-
Page titles and descriptions
-
-
-
-
-
Setup Instructions
-
-
Go to your Atlassian Account Settings
-
Navigate to Security → API tokens
-
Create a new API token with appropriate permissions
-
Copy the token and paste it in the form above
-
Ensure your account has read access to the spaces you want to index
-
-
-
-
-
Permissions Required
-
-
Read access to Confluence spaces
-
View pages and comments
-
Access to space metadata
-
-
-
-
-
-
- The connector will only index content that your account has permission to view.
- Make sure your API token has the necessary permissions for the spaces you want
- to index.
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx
deleted file mode 100644
index 94ef849e2..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx
+++ /dev/null
@@ -1,371 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const discordConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- bot_token: z
- .string()
- .min(50, { message: "Discord Bot Token appears to be too short." })
- .regex(/^[A-Za-z0-9._-]+$/, { message: "Discord Bot Token contains invalid characters." }),
-});
-
-// Define the type for the form values
-type DiscordConnectorFormValues = z.infer;
-
-export default function DiscordConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(discordConnectorFormSchema),
- defaultValues: {
- name: "Discord Connector",
- bot_token: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: DiscordConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.DISCORD_CONNECTOR,
- config: {
- DISCORD_BOT_TOKEN: values.bot_token,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Discord connector created successfully!");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Discord server to search messages and channels.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Discord Server
-
- Integrate with Discord to search and retrieve information from your servers and
- channels. This connector can index your Discord messages for search.
-
-
-
-
-
- Bot Token Required
-
- You'll need a Discord Bot Token to use this connector. You can create a Discord
- bot and get the token from the{" "}
-
- Discord Developer Portal
-
- .
-
-
-
-
-
-
-
-
What you get with Discord integration:
-
-
Search through your Discord servers and channels
-
Access historical messages and shared files
-
Connect your team's knowledge directly to your search space
-
Keep your search results up-to-date with latest communications
-
Index your Discord messages for enhanced search capabilities
-
-
-
-
-
-
-
-
-
- Discord Connector Documentation
-
-
- Learn how to set up and use the Discord connector to index your server data.
-
-
-
-
-
How it works
-
- The Discord connector indexes all accessible channels for a given bot in your
- servers.
-
-
-
Upcoming: Support for private channels by granting the bot access.
-
-
-
-
-
-
- Authorization
-
-
-
-
- Bot Setup Required
-
- You must create a Discord bot and add it to your server with the correct
- permissions.
-
-
-
-
-
- Navigate to the Connector Dashboard and select the{" "}
- Discord Connector.
-
-
- Place the Bot Token under{" "}
- Step 1 Provide Credentials.
-
-
- Click Connect to establish the connection.
-
-
-
-
-
- Important: Bot Channel Access
-
- After connecting, ensure the bot has access to all channels you want to
- index. You may need to adjust channel permissions in Discord.
-
-
-
-
-
- First Indexing
-
- The first indexing pulls all accessible channels and may take longer than
- future updates. Only channels where the bot has access will be indexed.
-
-
-
-
-
Troubleshooting:
-
-
- Missing messages: If you don't see messages from a
- channel, check the bot's permissions for that channel.
-
-
- Bot not responding: Make sure the bot is online and the
- token is correct.
-
-
- Private channels: The bot must be explicitly granted
- access to private channels.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/elasticsearch-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/elasticsearch-connector/page.tsx
deleted file mode 100644
index 15e03f3aa..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/elasticsearch-connector/page.tsx
+++ /dev/null
@@ -1,755 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import * as RadioGroup from "@radix-ui/react-radio-group";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter, useSearchParams } from "next/navigation";
-import { useId, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Separator } from "@/components/ui/separator";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const elasticsearchConnectorFormSchema = z
- .object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- endpoint_url: z.string().url({ message: "Please enter a valid Elasticsearch endpoint URL." }),
- auth_method: z.enum(["basic", "api_key"]).default("api_key"),
- username: z.string().optional(),
- password: z.string().optional(),
- ELASTICSEARCH_API_KEY: z.string().optional(),
- indices: z.string().optional(),
- query: z.string().default("*"),
- search_fields: z.string().optional(),
- max_documents: z.number().min(1).max(10000).optional(),
- })
- .refine(
- (data) => {
- if (data.auth_method === "basic") {
- return Boolean(data.username?.trim() && data.password?.trim());
- }
- if (data.auth_method === "api_key") {
- return Boolean(data.ELASTICSEARCH_API_KEY?.trim());
- }
- return true;
- },
- {
- message: "Authentication credentials are required for the selected method.",
- path: ["auth_method"],
- }
- );
-
-// Define the type for the form values
-type ElasticsearchConnectorFormValues = z.infer;
-
-export default function ElasticsearchConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchParams = useSearchParams();
- // match pattern used in other connector pages: prefer route param, fallback to query param
- const searchSpaceId = (params.search_space_id ?? searchParams?.get("search_space_id")) as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- const authBasicId = useId();
- const authApiKeyId = useId();
-
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(elasticsearchConnectorFormSchema),
- defaultValues: {
- name: "Elasticsearch Connector",
- endpoint_url: "",
- auth_method: "api_key",
- username: "",
- password: "",
- ELASTICSEARCH_API_KEY: "",
- indices: "",
- query: "*",
- search_fields: "",
- max_documents: undefined,
- },
- });
-
- const stringToArray = (str: string): string[] => {
- const items = str
- .split(",")
- .map((item) => item.trim())
- .filter((item) => item.length > 0);
- return Array.from(new Set(items));
- };
-
- // Handle form submission
- const onSubmit = async (values: ElasticsearchConnectorFormValues) => {
- setIsSubmitting(true);
- if (!searchSpaceId) {
- toast.error(
- "Missing search_space_id (route or ?search_space_id=). Provide it in the URL or pick a search space."
- );
- setIsSubmitting(false);
- return;
- }
- const searchSpaceIdNum = Number(searchSpaceId);
- if (!Number.isInteger(searchSpaceIdNum) || searchSpaceIdNum <= 0) {
- toast.error("Invalid search_space_id. It must be a positive integer.");
- setIsSubmitting(false);
- return;
- }
- try {
- // Send full URL to backend (backend expects ELASTICSEARCH_URL)
- const config: Record = {
- ELASTICSEARCH_URL: values.endpoint_url,
- // default to verifying certs; expose fields for CA/verify if UI added later
- ELASTICSEARCH_VERIFY_CERTS: true,
- };
-
- if (values.auth_method === "basic") {
- if (values.username) config.ELASTICSEARCH_USERNAME = values.username;
- if (values.password) config.ELASTICSEARCH_PASSWORD = values.password;
- } else if (values.auth_method === "api_key") {
- if (values.ELASTICSEARCH_API_KEY)
- config.ELASTICSEARCH_API_KEY = values.ELASTICSEARCH_API_KEY;
- }
-
- const indicesInput = values.indices?.trim() ?? "";
- const indicesArr = stringToArray(indicesInput);
- config.ELASTICSEARCH_INDEX =
- indicesArr.length === 0 ? "*" : indicesArr.length === 1 ? indicesArr[0] : indicesArr;
-
- if (values.query && values.query !== "*") {
- config.ELASTICSEARCH_QUERY = values.query;
- }
-
- if (values.search_fields?.trim()) {
- // config.ELASTICSEARCH_FIELDS = stringToArray(values.search_fields);
- const fields = stringToArray(values.search_fields);
- config.ELASTICSEARCH_FIELDS = fields;
- config.ELASTICSEARCH_CONTENT_FIELDS = fields;
- if (fields.includes("title")) {
- config.ELASTICSEARCH_TITLE_FIELD = "title";
- }
- }
-
- if (values.max_documents !== undefined && values.max_documents > 0) {
- config.ELASTICSEARCH_MAX_DOCUMENTS = values.max_documents;
- }
-
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.ELASTICSEARCH_CONNECTOR,
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- config,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Elasticsearch connector created successfully!");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect to your Elasticsearch cluster to search and index documents.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Elasticsearch Cluster
-
- Connect to your Elasticsearch instance to search and index documents for enhanced
- search capabilities.
-
-
-
-
-
-
-
-
- What you get with Elasticsearch integration:
-
-
-
Search across your indexed documents and logs
-
Access structured and unstructured data from your cluster
-
Leverage existing Elasticsearch indices for enhanced search
-
Real-time search capabilities with powerful query features
-
Integration with your existing Elasticsearch infrastructure
-
-
-
-
-
-
-
-
-
- Elasticsearch Connector Documentation
-
-
- Learn how to set up and use the Elasticsearch connector to search your data.
-
-
-
-
-
How it works
-
- The Elasticsearch connector allows you to search and retrieve documents from
- your Elasticsearch cluster. Configure connection details, select specific
- indices, and set search parameters to make your existing data searchable within
- SurfSense.
-
-
-
-
-
-
- Connection Setup
-
-
-
-
- Endpoint URL: Enter the complete Elasticsearch endpoint
- URL (e.g., https://your-cluster.es.region.aws.com:443). We'll
- automatically extract hostname, port, and SSL settings.
-
-
- Authentication: Choose the appropriate method:
-
-
- API Key: Base64 encoded API key (recommended for
- security)
-
- Index Selection: Specify which indices to search using
- comma-separated patterns (e.g., "logs-*, documents-*")
-
-
-
-
-
-
-
- Advanced Configuration
-
-
-
- Fine-tune your Elasticsearch connector with these optional settings:
-
-
-
- Search Fields: Limit searches to specific fields (e.g.,
- "title, content") for better relevance
-
-
- Default Query: Set a default Elasticsearch query pattern
-
-
- Max Documents: Limit the number of documents returned per
- search (1-10,000)
-
-
-
-
-
-
-
- Troubleshooting
-
-
-
-
-
Common Connection Issues:
-
-
- Connection Refused: Check hostname and port. Ensure
- Elasticsearch is running.
-
-
- Authentication Failed: Verify credentials. For API
- keys, ensure they have proper permissions.
-
-
- SSL Errors: Try disabling SSL for local development
- or check certificate validity.
-
-
- No Indices Found: Ensure your credentials have
- permission to list and read indices.
-
-
-
-
-
-
- Security Note
-
- For production environments, use API keys with minimal required
- permissions: cluster monitoring and read access to specific indices.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx
deleted file mode 100644
index a6c0c147b..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx
+++ /dev/null
@@ -1,531 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, CircleAlert, Github, Info, ListChecks, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Checkbox } from "@/components/ui/checkbox";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import { authenticatedFetch, redirectToLogin } from "@/lib/auth-utils";
-
-// Define the form schema with Zod for GitHub PAT entry step
-const githubPatFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- github_pat: z
- .string()
- .min(20, {
- // Apply min length first
- message: "GitHub Personal Access Token seems too short.",
- })
- .refine((pat) => pat.startsWith("ghp_") || pat.startsWith("github_pat_"), {
- // Then refine the pattern
- message: "GitHub PAT should start with 'ghp_' or 'github_pat_'",
- }),
-});
-
-// Define the type for the form values
-type GithubPatFormValues = z.infer;
-
-// Type for fetched GitHub repositories
-interface GithubRepo {
- id: number;
- name: string;
- full_name: string;
- private: boolean;
- url: string;
- description: string | null;
- last_updated: string | null;
-}
-
-export default function GithubConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [step, setStep] = useState<"enter_pat" | "select_repos">("enter_pat");
- const [isFetchingRepos, setIsFetchingRepos] = useState(false);
- const [isCreatingConnector, setIsCreatingConnector] = useState(false);
- const [repositories, setRepositories] = useState([]);
- const [selectedRepos, setSelectedRepos] = useState([]);
- const [connectorName, setConnectorName] = useState("GitHub Connector");
- const [validatedPat, setValidatedPat] = useState(""); // Store the validated PAT
-
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form for PAT entry
- const form = useForm({
- resolver: zodResolver(githubPatFormSchema),
- defaultValues: {
- name: connectorName,
- github_pat: "",
- },
- });
-
- // Function to fetch repositories using the new backend endpoint
- const fetchRepositories = async (values: GithubPatFormValues) => {
- setIsFetchingRepos(true);
- setConnectorName(values.name); // Store the name
- setValidatedPat(values.github_pat); // Store the PAT temporarily
- try {
- const response = await authenticatedFetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- 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();
- setRepositories(data);
- setStep("select_repos"); // Move to the next step
- toast.success(`Found ${data.length} repositories.`);
- } catch (error) {
- console.error("Error fetching GitHub repositories:", error);
- const errorMessage =
- error instanceof Error
- ? error.message
- : "Failed to fetch repositories. Please check the PAT and try again.";
- toast.error(errorMessage);
- } finally {
- setIsFetchingRepos(false);
- }
- };
-
- // Handle final connector creation
- const handleCreateConnector = async () => {
- if (selectedRepos.length === 0) {
- toast.warning("Please select at least one repository to index.");
- return;
- }
-
- setIsCreatingConnector(true);
- try {
- await createConnector({
- data: {
- name: connectorName, // Use the stored name
- connector_type: EnumConnectorName.GITHUB_CONNECTOR,
- config: {
- GITHUB_PAT: validatedPat, // Use the stored validated PAT
- repo_full_names: selectedRepos, // Add the selected repo names
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("GitHub connector created successfully!");
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating GitHub connector:", error);
- const errorMessage =
- error instanceof Error ? error.message : "Failed to create GitHub connector.";
- toast.error(errorMessage);
- } finally {
- setIsCreatingConnector(false);
- }
- };
-
- // Handle checkbox changes
- const handleRepoSelection = (repoFullName: string, checked: boolean) => {
- setSelectedRepos((prev) =>
- checked ? [...prev, repoFullName] : prev.filter((name) => name !== repoFullName)
- );
- };
-
- return (
-
-
-
-
-
-
- Connect GitHub
- Setup Guide
-
-
-
-
-
-
- {step === "enter_pat" ? (
- getConnectorIcon(EnumConnectorName.GITHUB_CONNECTOR, "h-6 w-6")
- ) : (
-
- )}
- {step === "enter_pat" ? "Connect GitHub Account" : "Select Repositories to Index"}
-
-
- {step === "enter_pat"
- ? "Provide a name and GitHub Personal Access Token (PAT) to fetch accessible repositories."
- : `Select which repositories you want SurfSense to index for search. Found ${repositories.length} repositories accessible via your PAT.`}
-
-
-
-
-
-
-
What you get with GitHub integration:
-
-
Search through code and documentation in your selected repositories
-
Access READMEs, Markdown files, and common code files
-
Connect your project knowledge directly to your search space
-
Index your selected repositories for enhanced search capabilities
-
-
-
-
-
-
-
-
- GitHub Connector Setup Guide
-
- Learn how to generate a Personal Access Token (PAT) and connect your GitHub
- account.
-
-
-
-
-
How it works
-
- The GitHub connector uses a Personal Access Token (PAT) to authenticate with the
- GitHub API. First, it fetches a list of repositories accessible to the token.
- You then select which repositories you want to index. The connector indexes
- relevant files (code, markdown, text) from only the selected repositories.
-
-
-
- The connector indexes files based on common code and documentation extensions.
-
-
Large files (over 1MB) are skipped during indexing.
-
Only selected repositories are indexed.
-
- Indexing runs periodically (check connector settings for frequency) to keep
- content up-to-date.
-
- Click on Personal access tokens, then choose{" "}
- Tokens (classic) or{" "}
- Fine-grained tokens (recommended if available and
- suitable).
-
-
- Click Generate new token (and choose the appropriate
- type).
-
-
- Give your token a descriptive name (e.g., "SurfSense Connector").
-
-
- Set an expiration date for the token (recommended for security).
-
-
- Under Select scopes (for classic tokens) or{" "}
- Repository access (for fine-grained), grant the
- necessary permissions. At minimum, the `repo` scope
- (or equivalent read access to repositories for fine-grained tokens) is
- required to read repository content.
-
-
- Click Generate token.
-
-
- Important: Copy your new PAT immediately. You won't
- be able to see it again after leaving the page.
-
-
-
-
-
-
-
-
-
- Step 2: Connect in SurfSense
-
-
-
-
Navigate to the "Connect GitHub" tab.
-
Enter a name for your connector.
-
- Paste the copied GitHub PAT into the "GitHub Personal Access Token (PAT)"
- field.
-
-
- Click Fetch Repositories.
-
-
- If the PAT is valid, you'll see a list of your accessible repositories.
-
-
- Select the repositories you want SurfSense to index using the checkboxes.
-
-
- Click the Create Connector button.
-
-
- If the connection is successful, you will be redirected and can start
- indexing from the Connectors page.
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx
deleted file mode 100644
index d208b1659..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import Link from "next/link";
-import { useParams, useRouter, useSearchParams } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import type { SearchSourceConnector } from "@/contracts/types/connector.types";
-import { authenticatedFetch } from "@/lib/auth-utils";
-
-export default function GoogleCalendarConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isConnecting, setIsConnecting] = useState(false);
- const [doesConnectorExist, setDoesConnectorExist] = useState(false);
-
- const { refetch: fetchConnectors } = useAtomValue(connectorsAtom);
-
- useEffect(() => {
- fetchConnectors().then((data) => {
- const connectors = data.data || [];
- const connector = connectors.find(
- (c: SearchSourceConnector) =>
- c.connector_type === EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR
- );
- if (connector) {
- setDoesConnectorExist(true);
- }
- });
- }, []);
-
- // Handle Google OAuth connection
- const handleConnectGoogle = async () => {
- try {
- setIsConnecting(true);
- // Call backend to initiate authorization flow
- const response = await authenticatedFetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/calendar/connector/add/?space_id=${searchSpaceId}`,
- { method: "GET" }
- );
-
- if (!response.ok) {
- throw new Error("Failed to initiate Google OAuth");
- }
-
- const data = await response.json();
-
- // Redirect to Google for authentication
- window.location.href = data.auth_url;
- } catch (error) {
- console.error("Error connecting to Google:", error);
- toast.error("Failed to connect to Google Calendar");
- } finally {
- setIsConnecting(false);
- }
- };
-
- return (
-
- Connect your Google Calendar to search events.
-
-
-
-
-
- {/* OAuth Connection Card */}
- {!doesConnectorExist ? (
-
-
- Connect Your Google Account
-
- Connect your Google account to access your calendar events. We'll only request
- read-only access to your calendars.
-
-
-
-
-
- Read-only access to your calendar events
-
-
-
- Access works even when you're offline
-
-
-
- You can disconnect anytime
-
-
-
-
-
-
-
- ) : (
- /* Configuration Form Card */
-
-
- ✅ Your Google calendar is successfully connected!
-
-
- )}
-
- {/* Help Section */}
- {!doesConnectorExist && (
-
-
- How It Works
-
-
-
-
1. Connect Your Account
-
- Click "Connect Your Google Account" to start the secure OAuth process. You'll be
- redirected to Google to sign in.
-
-
-
-
2. Grant Permissions
-
- Google will ask for permission to read your calendar events. We only request
- read-only access to keep your data safe.
-
-
- {/* Connection Card */}
- {!doesConnectorExist ? (
-
-
- Connect Your Google Account
-
- Authorize read-only access to your Google Drive. You'll select which folder to
- index when you start indexing.
-
-
-
-
-
- Read-only access to your Drive files
-
-
-
- Index documents, spreadsheets, presentations, PDFs & more
-
-
-
- Automatic updates with change tracking
-
-
-
- Secure OAuth 2.0 authentication
-
-
-
-
-
-
-
- ) : (
-
-
- ✅ Already Connected
-
- Your Google Drive connector is already set up. Go to the connectors page to
- start indexing.
-
-
-
-
-
-
- )}
-
- {/* Information Card */}
-
-
- How Google Drive Integration Works
-
-
-
-
1️⃣ Connect Your Account
-
- First, securely connect your Google Drive account using OAuth 2.0. We only
- request read-only access.
-
-
-
-
2️⃣ Select Folder to Index
-
- When you're ready to index, go to the connectors page and click "Index". You'll
- choose which folder to process.
-
-
-
-
3️⃣ Automatic Change Detection
-
- We use Google Drive's change tracking API to detect when files are modified,
- added, or deleted. Only changed files are re-indexed.
-
-
-
-
📄 Comprehensive File Support
-
- Supports Google Workspace files (Docs, Sheets, Slides), Microsoft Office
- documents, PDFs, text files, images (with OCR), and more.
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx
deleted file mode 100644
index 5ca8874d1..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import Link from "next/link";
-import { useParams, useRouter, useSearchParams } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import type { SearchSourceConnector } from "@/contracts/types/connector.types";
-import { authenticatedFetch } from "@/lib/auth-utils";
-
-export default function GoogleGmailConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isConnecting, setIsConnecting] = useState(false);
- const [doesConnectorExist, setDoesConnectorExist] = useState(false);
-
- const { refetch: fetchConnectors } = useAtomValue(connectorsAtom);
-
- useEffect(() => {
- fetchConnectors().then((data) => {
- const connectors = data.data || [];
- const connector = connectors.find(
- (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.GOOGLE_GMAIL_CONNECTOR
- );
- if (connector) {
- setDoesConnectorExist(true);
- }
- });
- }, []);
-
- // Handle Google OAuth connection
- const handleConnectGoogle = async () => {
- try {
- setIsConnecting(true);
- // Call backend to initiate authorization flow
- const response = await authenticatedFetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/gmail/connector/add/?space_id=${searchSpaceId}`,
- { method: "GET" }
- );
-
- if (!response.ok) {
- throw new Error("Failed to initiate Google OAuth");
- }
-
- const data = await response.json();
-
- // Redirect to Google for authentication
- window.location.href = data.auth_url;
- } catch (error) {
- console.error("Error connecting to Google:", error);
- toast.error("Failed to connect to Google Gmail");
- } finally {
- setIsConnecting(false);
- }
- };
-
- return (
-
- Connect your Gmail account to search through your emails
-
-
-
-
-
- {/* Connection Card */}
- {!doesConnectorExist ? (
-
-
- Connect Your Gmail Account
-
- Securely connect your Gmail account to enable email search within SurfSense. We'll
- only access your emails with read-only permissions.
-
-
-
-
-
- Read-only access to your emails
-
-
-
- Search through email content and metadata
-
-
-
- Secure OAuth 2.0 authentication
-
-
-
- You can disconnect anytime
-
-
-
-
-
-
-
- ) : (
- /* Configuration Form Card */
-
-
- ✅ Your Gmail is successfully connected!
-
-
- )}
-
- {/* Information Card */}
-
-
- What data will be indexed?
-
-
-
-
Email Content
-
- We'll index the content of your emails including subject lines, sender information,
- and message body text to make them searchable.
-
-
-
-
Email Metadata
-
- Information like sender, recipient, date, and labels will be indexed to provide
- better search context and filtering options.
-
-
-
-
Privacy & Security
-
- Your emails are processed securely and stored with encryption. We only access emails
- with read-only permissions and never modify or send emails on your behalf.
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
deleted file mode 100644
index a0c22582b..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
+++ /dev/null
@@ -1,427 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const jiraConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- base_url: z
- .string()
- .url({
- message: "Please enter a valid Jira URL (e.g., https://yourcompany.atlassian.net)",
- })
- .refine(
- (url) => {
- return url.includes("atlassian.net") || url.includes("jira");
- },
- {
- message: "Please enter a valid Jira instance URL",
- }
- ),
- email: z.string().email({
- message: "Please enter a valid email address.",
- }),
- api_token: z.string().min(10, {
- message: "Jira API Token is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type JiraConnectorFormValues = z.infer;
-
-export default function JiraConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(jiraConnectorFormSchema),
- defaultValues: {
- name: "Jira Connector",
- base_url: "",
- email: "",
- api_token: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: JiraConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.JIRA_CONNECTOR,
- config: {
- JIRA_BASE_URL: values.base_url,
- JIRA_EMAIL: values.email,
- JIRA_API_TOKEN: values.api_token,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Jira connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Jira instance to search issues and tickets.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Jira Instance
-
- Integrate with Jira to search and retrieve information from your issues, tickets,
- and comments. This connector can index your Jira content for search.
-
-
-
-
-
- Jira Personal Access Token Required
-
- You'll need a Jira Personal Access Token to use this connector. You can create
- one from{" "}
-
- Atlassian Account Settings
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
-
- A friendly name to identify this connector.
-
-
-
- )}
- />
-
- (
-
- Jira Instance URL
-
-
-
-
- Your Jira instance URL. For Atlassian Cloud, this is typically
- https://yourcompany.atlassian.net
-
-
-
- )}
- />
-
- (
-
- Email Address
-
-
-
- Your Atlassian account email address.
-
-
- )}
- />
-
- (
-
- API Token
-
-
-
-
- Your Jira API Token will be encrypted and stored securely.
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Jira integration:
-
-
Search through all your Jira issues and tickets
-
Access issue descriptions, comments, and full discussion threads
-
Connect your team's project management directly to your search space
-
Keep your search results up-to-date with latest Jira content
-
Index your Jira issues for enhanced search capabilities
-
Search by issue keys, status, priority, and assignee information
-
-
-
-
-
-
-
-
- Jira Connector Documentation
-
- Learn how to set up and use the Jira connector to index your project management
- data.
-
-
-
-
-
How it works
-
- The Jira connector uses the Jira REST API with Basic Authentication to fetch all
- issues and comments that your account has access to within your Jira instance.
-
-
-
- For follow up indexing runs, the connector retrieves issues and comments that
- have been updated since the last indexing attempt.
-
-
- Indexing is configured to run periodically, so updates should appear in your
- search results within minutes.
-
-
-
-
-
-
-
- Authorization
-
-
-
-
- Read-Only Access is Sufficient
-
- You only need read access for this connector to work. The API Token will
- only be used to read your Jira data.
-
-
-
-
Enter a label for your token (like "SurfSense Connector")
-
- Click Create
-
-
Copy the generated token as it will only be shown once
-
-
-
-
-
Step 2: Grant necessary access
-
- The API Token will have access to all projects and issues that your user
- account can see. Make sure your account has appropriate permissions for
- the projects you want to index.
-
-
-
- Data Privacy
-
- Only issues, comments, and basic metadata will be indexed. Jira
- attachments and linked files are not indexed by this connector.
-
-
-
-
-
-
-
-
- Indexing
-
-
-
- Navigate to the Connector Dashboard and select the Jira{" "}
- Connector.
-
-
- Enter your Jira Instance URL (e.g.,
- https://yourcompany.atlassian.net)
-
-
- Place your Personal Access Token in the form field.
-
-
- Click Connect to establish the connection.
-
-
Once connected, your Jira issues will be indexed automatically.
-
-
-
-
- What Gets Indexed
-
-
The Jira connector indexes the following data:
-
-
Issue keys and summaries (e.g., PROJ-123)
-
Issue descriptions
-
Issue comments and discussion threads
-
Issue status, priority, and type information
-
Assignee and reporter information
-
Project information
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx
deleted file mode 100644
index ba747bf6e..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx
+++ /dev/null
@@ -1,379 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const linearConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z
- .string()
- .min(10, {
- message: "Linear API Key is required and must be valid.",
- })
- .regex(/^lin_api_/, {
- message: "Linear API Key should start with 'lin_api_'",
- }),
-});
-
-// Define the type for the form values
-type LinearConnectorFormValues = z.infer;
-
-export default function LinearConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(linearConnectorFormSchema),
- defaultValues: {
- name: "Linear Connector",
- api_key: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: LinearConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.LINEAR_CONNECTOR,
- config: {
- LINEAR_API_KEY: values.api_key,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Linear connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Linear workspace to search issues and projects.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Linear Workspace
-
- Integrate with Linear to search and retrieve information from your issues and
- comments. This connector can index your Linear content for search.
-
-
-
-
-
- Linear API Key Required
-
- You'll need a Linear API Key to use this connector. You can create a Linear API
- key from{" "}
-
- Linear API Settings
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
-
- A friendly name to identify this connector.
-
-
-
- )}
- />
-
- (
-
- Linear API Key
-
-
-
-
- Your Linear API Key will be encrypted and stored securely. It typically
- starts with "lin_api_".
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Linear integration:
-
-
Search through all your Linear issues and comments
-
Access issue titles, descriptions, and full discussion threads
-
Connect your team's project management directly to your search space
-
Keep your search results up-to-date with latest Linear content
-
Index your Linear issues for enhanced search capabilities
-
-
-
-
-
-
-
-
- Linear Connector Documentation
-
- Learn how to set up and use the Linear connector to index your project management
- data.
-
-
-
-
-
How it works
-
- The Linear connector uses the Linear GraphQL API to fetch all issues and
- comments that the API key has access to within a workspace.
-
-
-
- For follow up indexing runs, the connector retrieves issues and comments that
- have been updated since the last indexing attempt.
-
-
- Indexing is configured to run periodically, so updates should appear in your
- search results within minutes.
-
-
-
-
-
-
-
- Authorization
-
-
-
-
- Read-Only Access is Sufficient
-
- You only need a read-only API key for this connector to work. This limits
- the permissions to just reading your Linear data.
-
-
-
-
Alternatively, click on your profile picture → Settings → API
-
- Click the + New API key button.
-
-
Enter a description for your key (like "Search Connector").
-
Select "Read-only" as the permission.
-
- Click Create to generate the API key.
-
-
- Copy the generated API key that starts with 'lin_api_' as it will only
- be shown once.
-
-
-
-
-
-
Step 2: Grant necessary access
-
- The API key will have access to all issues and comments that your user
- account can see. If you're creating the key as an admin, it will have
- access to all issues in the workspace.
-
-
-
- Data Privacy
-
- Only issues and comments will be indexed. Linear attachments and
- linked files are not indexed by this connector.
-
-
-
-
-
-
-
-
- Indexing
-
-
-
- Navigate to the Connector Dashboard and select the Linear{" "}
- Connector.
-
-
- Place the API Key in the form field.
-
-
- Click Connect to establish the connection.
-
-
Once connected, your Linear issues will be indexed automatically.
-
-
-
-
- What Gets Indexed
-
-
The Linear connector indexes the following data:
-
-
Issue titles and identifiers (e.g., PROJ-123)
-
Issue descriptions
-
Issue comments
-
Issue status and metadata
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx
deleted file mode 100644
index 16c0700d7..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const linkupApiFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z.string().min(10, {
- message: "API key is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type LinkupApiFormValues = z.infer;
-
-export default function LinkupApiPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(linkupApiFormSchema),
- defaultValues: {
- name: "Linkup API Connector",
- api_key: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: LinkupApiFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.LINKUP_API,
- config: {
- LINKUP_API_KEY: values.api_key,
- },
- is_indexable: false,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Linkup API connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect Linkup API for enhanced search capabilities.
-
-
-
-
-
-
-
-
- Connect Linkup API
-
- Integrate with Linkup API to enhance your search capabilities with AI-powered search
- results.
-
-
-
-
-
- API Key Required
-
- You'll need a Linkup API key to use this connector. You can get one by signing up at{" "}
-
- linkup.so
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
- A friendly name to identify this connector.
-
-
- )}
- />
-
- (
-
- Linkup API Key
-
-
-
-
- Your API key will be encrypted and stored securely.
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Linkup API:
-
-
AI-powered search results tailored to your queries
-
Real-time information from the web
-
Enhanced search capabilities for your projects
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx
deleted file mode 100644
index 0b78cf40c..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Key, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import Link from "next/link";
-import { useParams, useRouter } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import type { SearchSourceConnector } from "@/contracts/types/connector.types";
-
-// Define the form schema with Zod
-const lumaConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z.string().min(10, {
- message: "API key is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type LumaConnectorFormValues = z.infer;
-
-export default function LumaConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [doesConnectorExist, setDoesConnectorExist] = useState(false);
-
- const { data: connectors } = useAtomValue(connectorsAtom);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(lumaConnectorFormSchema),
- defaultValues: {
- name: "Luma Events",
- api_key: "",
- },
- });
-
- const { refetch: fetchConnectors } = useAtomValue(connectorsAtom);
-
- useEffect(() => {
- fetchConnectors().then((data) => {
- const connectors = data.data || [];
- const connector = connectors.find(
- (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.LUMA_CONNECTOR
- );
- if (connector) {
- setDoesConnectorExist(true);
- }
- });
- }, []);
-
- // Handle form submission
- const onSubmit = async (values: LumaConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.LUMA_CONNECTOR,
- config: {
- LUMA_API_KEY: values.api_key,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Luma connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
-
- {/* Connection Card */}
- {!doesConnectorExist ? (
-
-
- Connect Your Luma Account
-
- Enter your Luma API key to connect your account. We'll use this to access your
- events in read-only mode.
-
-
-
-
-
- (
-
- Connector Name
-
-
-
-
- A friendly name to identify this connector.
-
-
-
- )}
- />
-
- (
-
- API Key
-
-
-
-
- Your API key will be encrypted and stored securely.
-
-
-
- )}
- />
-
-
-
-
- Read-only access to your Luma events
-
-
-
- Access works even when you're offline
-
-
-
- You can disconnect anytime
-
-
-
-
-
-
-
-
-
-
- ) : (
- /* Success Card */
-
-
- ✅ Your Luma account is successfully connected!
-
-
- )}
-
- {/* Help Section */}
- {!doesConnectorExist && (
-
-
- How It Works
-
-
-
-
1. Get Your API Key
-
- Log into your Luma account and navigate to your account settings to generate an
- API key.
-
-
-
-
2. Enter Your API Key
-
- Paste your API key in the field above. We'll use this to securely access your
- events with read-only permissions.
-
-
-
-
- )}
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx
deleted file mode 100644
index 160808e21..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const notionConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- integration_token: z.string().min(10, {
- message: "Notion Integration Token is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type NotionConnectorFormValues = z.infer;
-
-export default function NotionConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(notionConnectorFormSchema),
- defaultValues: {
- name: "Notion Connector",
- integration_token: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: NotionConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.NOTION_CONNECTOR,
- config: {
- NOTION_INTEGRATION_TOKEN: values.integration_token,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Notion connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Notion workspace to search pages and databases.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Notion Workspace
-
- Integrate with Notion to search and retrieve information from your workspace pages
- and databases. This connector can index your Notion content for search.
-
-
-
-
-
- Notion Integration Token Required
-
- You'll need a Notion Integration Token to use this connector. You can create a
- Notion integration and get the token from{" "}
-
- Notion Integrations Dashboard
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
-
- A friendly name to identify this connector.
-
-
-
- )}
- />
-
- (
-
- Notion Integration Token
-
-
-
-
- Your Notion Integration Token will be encrypted and stored securely. It
- typically starts with "ntn_".
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Notion integration:
-
-
Search through your Notion pages and databases
-
Access documents, wikis, and knowledge bases
-
Connect your team's knowledge directly to your search space
-
Keep your search results up-to-date with latest Notion content
-
Index your Notion documents for enhanced search capabilities
-
-
-
-
-
-
-
-
- Notion Connector Documentation
-
- Learn how to set up and use the Notion connector to index your workspace data.
-
-
-
-
-
How it works
-
- The Notion connector uses the Notion search API to fetch all pages that the
- connector has access to within a workspace.
-
-
-
- For follow up indexing runs, the connector only retrieves pages that have been
- updated since the last indexing attempt.
-
-
- Indexing is configured to run every 10 minutes, so page
- updates should appear within 10 minutes.
-
-
-
-
-
-
-
- Authorization
-
-
-
-
- No Admin Access Required
-
- There's no requirement to be an Admin to share information with an
- integration. Any member can share pages and databases with it.
-
-
-
-
- Name the integration (something like "Search Connector" could work).
-
-
Select "Read content" as the only capability required.
-
- Click Submit to create the integration.
-
-
- On the next page, you'll find your Notion integration token. Make a
- copy of it as you'll need it to configure the connector.
-
-
-
-
-
-
- Step 2: Share pages/databases with your integration
-
-
- To keep your information secure, integrations don't have access to any
- pages or databases in the workspace at first. You must share specific
- pages with an integration in order for the connector to access those
- pages.
-
-
-
Go to the page/database in your workspace.
-
- Click the ••• on the top right corner of the page.
-
-
- Scroll to the bottom of the pop-up and click{" "}
- Add connections.
-
-
- Search for and select the new integration in the{" "}
- Search for connections... menu.
-
-
- Important:
-
-
- If you've added a page, all child pages also become accessible.
-
-
- If you've added a database, all rows (and their children) become
- accessible.
-
-
-
-
-
-
-
-
-
-
- Indexing
-
-
-
- Navigate to the Connector Dashboard and select the Notion{" "}
- Connector.
-
-
- Place the Integration Token under{" "}
- Step 1 Provide Credentials.
-
-
- Click Connect to establish the connection.
-
-
-
-
-
- Indexing Behavior
-
- The Notion connector currently indexes everything it has access to. If you
- want to limit specific content being indexed, simply unshare the database
- from Notion with the integration.
-
-
-
-
-
-
-
-
-
-
-
- Bring your self-hosted SearxNG meta-search engine into SurfSense.
-
-
-
-
-
-
-
-
- Connect SearxNG
-
- Integrate SurfSense with any SearxNG instance to broaden your search coverage while
- preserving privacy and control.
-
-
-
-
-
- SearxNG Instance Required
-
- You need access to a running SearxNG instance. Refer to the{" "}
-
- SearxNG installation guide
- {" "}
- for setup instructions. If your instance requires an API key, include it below.
-
-
-
-
-
- (
-
- Connector Name
-
-
-
- A friendly name to identify this connector.
-
-
- )}
- />
-
- (
-
- SearxNG Host
-
-
-
-
- Provide the full base URL to your SearxNG instance. Include the protocol
- (http/https).
-
-
-
- )}
- />
-
- (
-
- API Key (optional)
-
-
-
-
- Leave empty if your SearxNG instance does not enforce API keys.
-
-
-
- )}
- />
-
-
- (
-
- Engines (optional)
-
-
-
-
- Comma-separated list to target specific engines.
-
-
-
- )}
- />
-
- (
-
- Categories (optional)
-
-
-
-
- Comma-separated list of SearxNG categories.
-
-
-
- )}
- />
-
-
-
- (
-
- Preferred Language (optional)
-
-
-
-
- IETF language tag (e.g. en, en-US). Leave blank to inherit defaults.
-
-
-
- )}
- />
-
- (
-
- SafeSearch Level (optional)
-
-
-
-
- Set 0, 1, or 2 to adjust SafeSearch filtering. Leave blank to use the
- instance default.
-
-
-
- )}
- />
-
-
- (
-
-
- Verify SSL Certificates
-
- Disable only when connecting to instances with self-signed certificates.
-
-
-
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx
deleted file mode 100644
index f3cf2ca6c..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx
+++ /dev/null
@@ -1,421 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "@/components/ui/accordion";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const slackConnectorFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- bot_token: z.string().min(10, {
- message: "Bot User OAuth Token is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type SlackConnectorFormValues = z.infer;
-
-export default function SlackConnectorPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(slackConnectorFormSchema),
- defaultValues: {
- name: "Slack Connector",
- bot_token: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: SlackConnectorFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.SLACK_CONNECTOR,
- config: {
- SLACK_BOT_TOKEN: values.bot_token,
- },
- is_indexable: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Slack connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect your Slack workspace to search messages and channels.
-
-
-
-
-
-
-
-
- Connect
- Documentation
-
-
-
-
-
- Connect Slack Workspace
-
- Integrate with Slack to search and retrieve information from your workspace
- channels and conversations. This connector can index your Slack messages for
- search.
-
-
-
-
-
- Bot User OAuth Token Required
-
- You'll need a Slack Bot User OAuth Token to use this connector. You can create a
- Slack app and get the token from{" "}
-
- Slack API Dashboard
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
-
- A friendly name to identify this connector.
-
-
-
- )}
- />
-
- (
-
- Slack Bot User OAuth Token
-
-
-
-
- Your Bot User OAuth Token will be encrypted and stored securely. It
- typically starts with "xoxb-".
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Slack integration:
-
-
Search through your Slack channels and conversations
-
Access historical messages and shared files
-
Connect your team's knowledge directly to your search space
-
Keep your search results up-to-date with latest communications
-
Index your Slack messages for enhanced search capabilities
-
-
-
-
-
-
-
-
- Slack Connector Documentation
-
- Learn how to set up and use the Slack connector to index your workspace data.
-
-
-
-
-
How it works
-
- The Slack connector indexes all public channels for a given workspace.
-
-
-
- Upcoming: Support for private channels by tagging/adding the Slack Bot to
- private channels.
-
-
-
-
-
-
-
- Authorization
-
-
-
-
- Admin Access Required
-
- You must be an admin of the Slack workspace to set up the connector.
-
-
-
-
-
- In the app page, navigate to the OAuth & Permissions tab
- under the Features header.
-
-
- Copy the Bot User OAuth Token, this will be used to
- access Slack.
-
-
-
-
-
-
- Indexing
-
-
-
- Navigate to the Connector Dashboard and select the Slack{" "}
- Connector.
-
-
- Place the Bot User OAuth Token under{" "}
- Step 1 Provide Credentials.
-
-
- Click Connect to establish the connection.
-
-
-
-
-
- Important: Invite Bot to Channels
-
- After connecting, you must invite the bot to each channel you want to
- index. In each Slack channel, type:
-
- /invite @YourBotName
-
-
- Without this step, you'll get a "not_in_channel" error when the
- connector tries to access channel messages.
-
-
-
-
-
-
- First Indexing
-
- The first indexing pulls all of the public channels and takes longer than
- future updates. Only channels where the bot has been invited will be fully
- indexed.
-
-
-
-
-
Troubleshooting:
-
-
- not_in_channel error: If you see this error in logs, it
- means the bot hasn't been invited to a channel it's trying to access.
- Use the /invite @YourBotName command in that channel.
-
-
- Alternative approach: You can add the{" "}
- chat:write.public scope to your Slack app to allow it to
- access public channels without an explicit invitation.
-
-
- For private channels: The bot must always be invited
- using the /invite command.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx
deleted file mode 100644
index a9ddd5a41..000000000
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useAtomValue } from "jotai";
-import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { motion } from "motion/react";
-import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { EnumConnectorName } from "@/contracts/enums/connector";
-import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-
-// Define the form schema with Zod
-const tavilyApiFormSchema = z.object({
- name: z.string().min(3, {
- message: "Connector name must be at least 3 characters.",
- }),
- api_key: z.string().min(10, {
- message: "API key is required and must be valid.",
- }),
-});
-
-// Define the type for the form values
-type TavilyApiFormValues = z.infer;
-
-export default function TavilyApiPage() {
- const router = useRouter();
- const params = useParams();
- const searchSpaceId = params.search_space_id as string;
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
-
- // Initialize the form
- const form = useForm({
- resolver: zodResolver(tavilyApiFormSchema),
- defaultValues: {
- name: "Tavily API Connector",
- api_key: "",
- },
- });
-
- // Handle form submission
- const onSubmit = async (values: TavilyApiFormValues) => {
- setIsSubmitting(true);
- try {
- await createConnector({
- data: {
- name: values.name,
- connector_type: EnumConnectorName.TAVILY_API,
- config: {
- TAVILY_API_KEY: values.api_key,
- },
- is_indexable: false,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- },
- queryParams: {
- search_space_id: searchSpaceId,
- },
- });
-
- toast.success("Tavily API connector created successfully!");
-
- // Navigate back to connectors page
- router.push(`/dashboard/${searchSpaceId}/connectors`);
- } catch (error) {
- console.error("Error creating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to create connector");
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- Connect Tavily API for AI-powered search capabilities.
-
-
-
-
-
-
-
-
- Connect Tavily API
-
- Integrate with Tavily API to enhance your search capabilities with AI-powered search
- results.
-
-
-
-
-
- API Key Required
-
- You'll need a Tavily API key to use this connector. You can get one by signing up at{" "}
-
- tavily.com
-
-
-
-
-
-
- (
-
- Connector Name
-
-
-
- A friendly name to identify this connector.
-
-
- )}
- />
-
- (
-
- Tavily API Key
-
-
-
-
- Your API key will be encrypted and stored securely.
-
-
-
- )}
- />
-
-
-
-
-
-
-
-
-
What you get with Tavily API:
-
-
AI-powered search results tailored to your queries