From 21146485e6f71c35efc0231bc3f93fd696bc731c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:27:30 +0530 Subject: [PATCH] feat: Add support for Confluence, BookStack, GitHub, Jira, ClickUp, and Luma connectors, including configuration forms and benefits display, enhance connector dialog for new connectors, and improve overall connector management functionality. --- .../components/bookstack-connect-form.tsx | 379 ++++++++++++++++ .../components/clickup-connect-form.tsx | 344 ++++++++++++++ .../components/confluence-connect-form.tsx | 405 +++++++++++++++++ .../components/discord-connect-form.tsx | 4 +- .../components/github-connect-form.tsx | 419 ++++++++++++++++++ .../components/jira-connect-form.tsx | 407 +++++++++++++++++ .../components/linear-connect-form.tsx | 4 +- .../components/luma-connect-form.tsx | 342 ++++++++++++++ .../components/notion-connect-form.tsx | 4 +- .../components/slack-connect-form.tsx | 4 +- .../connect-forms/connector-benefits.ts | 44 +- .../connector-popup/connect-forms/index.tsx | 18 + .../components/bookstack-config.tsx | 151 +++++++ .../components/clickup-config.tsx | 92 ++++ .../components/confluence-config.tsx | 152 +++++++ .../components/github-config.tsx | 150 +++++++ .../components/jira-config.tsx | 152 +++++++ .../components/luma-config.tsx | 92 ++++ .../connector-configs/index.tsx | 18 + .../views/connector-connect-view.tsx | 6 + .../tabs/all-connectors-tab.tsx | 8 +- surfsense_web/lib/connectors/utils.ts | 1 + 22 files changed, 3185 insertions(+), 11 deletions(-) create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/bookstack-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/clickup-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/confluence-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/github-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/jira-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/luma-connect-form.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/bookstack-config.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/clickup-config.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/confluence-config.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/github-config.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/jira-config.tsx create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/luma-config.tsx diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/bookstack-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/bookstack-connect-form.tsx new file mode 100644 index 000000000..ff3dc07b5 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/bookstack-connect-form.tsx @@ -0,0 +1,379 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +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 base URL." }), + token_id: z.string().min(1, { + message: "BookStack Token ID is required.", + }), + token_secret: z.string().min(1, { + message: "BookStack Token Secret is required.", + }), +}); + +type BookStackConnectorFormValues = z.infer; + +export const BookStackConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(bookstackConnectorFormSchema), + defaultValues: { + name: "BookStack Connector", + base_url: "", + token_id: "", + token_secret: "", + }, + }); + + const handleSubmit = async (values: BookStackConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + 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: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ API Token Required + + You'll need a BookStack API Token to use this connector. You can create one from your BookStack instance settings. + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + BookStack Base URL + + + + + The base URL of your BookStack instance (e.g., https://your-bookstack-instance.com). + + + + )} + /> + + ( + + Token ID + + + + + Your BookStack API Token ID. + + + + )} + /> + + ( + + Token Secret + + + + + Your BookStack API Token Secret will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR) && ( +
+

What you get with BookStack integration:

+
    + {getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The BookStack connector uses the BookStack REST API to fetch all pages from your BookStack instance that your account has access to. +

+
    +
  • + For follow up indexing runs, the connector retrieves pages 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

+ + + API Token Required + + You need to create an API token from your BookStack instance. The token requires "Access System API" permission. + + + +
+
+

Step 1: Create an API Token

+
    +
  1. Log in to your BookStack instance
  2. +
  3. Click on your profile icon → Edit Profile
  4. +
  5. Navigate to the "API Tokens" tab
  6. +
  7. Click "Create Token" and give it a name
  8. +
  9. Copy both the Token ID and Token Secret
  10. +
  11. Paste them in the form above
  12. +
+
+ +
+

Step 2: Grant necessary access

+

+ Your user account must have "Access System API" permission. The connector will only index content your account can view. +

+ + + Rate Limiting + + BookStack API has a rate limit of 180 requests per minute. The connector automatically handles rate limiting to ensure reliable indexing. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the BookStack Connector. +
  2. +
  3. + Enter your BookStack Instance URL (e.g., https://docs.example.com) +
  4. +
  5. + Enter your Token ID and Token Secret from your BookStack API token. +
  6. +
  7. + Click Connect to establish the connection. +
  8. +
  9. Once connected, your BookStack pages will be indexed automatically.
  10. +
+ + + + What Gets Indexed + +

The BookStack connector indexes the following data:

+
    +
  • All pages from your BookStack instance
  • +
  • Page content in Markdown format
  • +
  • Page titles and metadata
  • +
  • Book and chapter hierarchy information
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/clickup-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/clickup-connect-form.tsx new file mode 100644 index 000000000..b4e844a8c --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/clickup-connect-form.tsx @@ -0,0 +1,344 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +const clickupConnectorFormSchema = z.object({ + name: z.string().min(3, { + message: "Connector name must be at least 3 characters.", + }), + api_token: z.string().min(10, { + message: "ClickUp API Token is required and must be valid.", + }), +}); + +type ClickUpConnectorFormValues = z.infer; + +export const ClickUpConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(clickupConnectorFormSchema), + defaultValues: { + name: "ClickUp Connector", + api_token: "", + }, + }); + + const handleSubmit = async (values: ClickUpConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + name: values.name, + connector_type: EnumConnectorName.CLICKUP_CONNECTOR, + config: { + CLICKUP_API_TOKEN: values.api_token, + }, + is_indexable: true, + last_indexed_at: null, + periodic_indexing_enabled: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ API Token Required + + You'll need a ClickUp API Token to use this connector. You can create one from{" "} + + ClickUp Settings + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + ClickUp API Token + + + + + Your ClickUp API Token will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.CLICKUP_CONNECTOR) && ( +
+

What you get with ClickUp integration:

+
    + {getConnectorBenefits(EnumConnectorName.CLICKUP_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The ClickUp connector uses the ClickUp API to fetch all tasks and projects that your API token has access to within your workspace. +

+
    +
  • + For follow up indexing runs, the connector retrieves tasks 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

+ + + API Token Required + + You need a ClickUp personal API token to use this connector. The token will be used to read your ClickUp data. + + + +
+
+

Step 1: Get Your API Token

+
    +
  1. Log in to your ClickUp account
  2. +
  3. Click your avatar in the upper-right corner and select "Settings"
  4. +
  5. In the sidebar, click "Apps"
  6. +
  7. + Under "API Token", click Generate or Regenerate +
  8. +
  9. Copy the generated token (it typically starts with "pk_")
  10. +
  11. + Paste it in the form above. You can also visit{" "} + + ClickUp API Settings + {" "} + directly. +
  12. +
+
+ +
+

Step 2: Grant necessary access

+

+ The API Token will have access to all tasks and projects that your user account can see. Make sure your account has appropriate permissions for the workspaces you want to index. +

+ + + Data Privacy + + Only tasks, comments, and basic metadata will be indexed. ClickUp attachments and linked files are not indexed by this connector. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the ClickUp Connector. +
  2. +
  3. + Place your API Token in the form field. +
  4. +
  5. + Click Connect to establish the connection. +
  6. +
  7. Once connected, your ClickUp tasks will be indexed automatically.
  8. +
+ + + + What Gets Indexed + +

The ClickUp connector indexes the following data:

+
    +
  • Task names and descriptions
  • +
  • Task comments and discussion threads
  • +
  • Task status, priority, and assignee information
  • +
  • Project and workspace information
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/confluence-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/confluence-connect-form.tsx new file mode 100644 index 000000000..f4a5f0431 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/confluence-connect-form.tsx @@ -0,0 +1,405 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +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 base 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.", + }), +}); + +type ConfluenceConnectorFormValues = z.infer; + +export const ConfluenceConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(confluenceConnectorFormSchema), + defaultValues: { + name: "Confluence Connector", + base_url: "", + email: "", + api_token: "", + }, + }); + + const handleSubmit = async (values: ConfluenceConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + 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: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ API Token Required + + You'll need a Confluence API Token to use this connector. You can create one from{" "} + + Atlassian Account Settings + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Confluence Base URL + + + + + The base URL of your Confluence instance (e.g., https://your-domain.atlassian.net). + + + + )} + /> + + ( + + Email Address + + + + + The email address associated with your Atlassian account. + + + + )} + /> + + ( + + API Token + + + + + Your Confluence API Token will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR) && ( +
+

What you get with Confluence integration:

+
    + {getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The Confluence connector uses the Confluence REST API to fetch all pages and comments that your account has access to within your Confluence instance. +

+
    +
  • + For follow up indexing runs, the connector retrieves pages 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 Confluence data. + + + +
+
+

Step 1: Create an API Token

+
    +
  1. Log in to your Atlassian account
  2. +
  3. + Navigate to{" "} + + https://id.atlassian.com/manage-profile/security/api-tokens + {" "} + in your browser. +
  4. +
  5. + Click Create API token +
  6. +
  7. Enter a label for your token (like "SurfSense Connector")
  8. +
  9. + Click Create +
  10. +
  11. Copy the generated token as it will only be shown once
  12. +
+
+ +
+

Step 2: Grant necessary access

+

+ The API Token will have access to all spaces and pages that your user account can see. Make sure your account has appropriate permissions for the spaces you want to index. +

+ + + Data Privacy + + Only pages, comments, and basic metadata will be indexed. Confluence attachments and linked files are not indexed by this connector. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the Confluence Connector. +
  2. +
  3. + Enter your Confluence Instance URL (e.g., https://yourcompany.atlassian.net) +
  4. +
  5. + Enter your Email Address associated with your Atlassian account +
  6. +
  7. + Place your API Token in the form field. +
  8. +
  9. + Click Connect to establish the connection. +
  10. +
  11. Once connected, your Confluence pages will be indexed automatically.
  12. +
+ + + + What Gets Indexed + +

The Confluence connector indexes the following data:

+
    +
  • All pages from accessible spaces
  • +
  • Page content and metadata
  • +
  • Comments on pages (both footer and inline comments)
  • +
  • Page titles and descriptions
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx index 4ab416aa1..c7ede6181 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx @@ -223,8 +223,8 @@ export const DiscordConnectForm: FC = ({

What you get with Discord integration:

    - {getConnectorBenefits(EnumConnectorName.DISCORD_CONNECTOR)?.map((benefit, i) => ( -
  • {benefit}
  • + {getConnectorBenefits(EnumConnectorName.DISCORD_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • ))}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/github-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/github-connect-form.tsx new file mode 100644 index 000000000..c36034f8f --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/github-connect-form.tsx @@ -0,0 +1,419 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; + +const githubConnectorFormSchema = z.object({ + name: z.string().min(3, { + message: "Connector name must be at least 3 characters.", + }), + github_pat: z + .string() + .min(20, { + message: "GitHub Personal Access Token seems too short.", + }) + .refine((pat) => pat.startsWith("ghp_") || pat.startsWith("github_pat_"), { + message: "GitHub PAT should start with 'ghp_' or 'github_pat_'", + }), + repo_full_names: z.string().min(1, { + message: "At least one repository is required.", + }), +}); + +type GithubConnectorFormValues = z.infer; + +export const GithubConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(githubConnectorFormSchema), + defaultValues: { + name: "GitHub Connector", + github_pat: "", + repo_full_names: "", + }, + }); + + const stringToArray = (str: string): string[] => { + const items = str + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0); + return Array.from(new Set(items)); + }; + + const handleSubmit = async (values: GithubConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + const repoList = stringToArray(values.repo_full_names); + + await onSubmit({ + name: values.name, + connector_type: EnumConnectorName.GITHUB_CONNECTOR, + config: { + GITHUB_PAT: values.github_pat, + repo_full_names: repoList, + }, + is_indexable: true, + last_indexed_at: null, + periodic_indexing_enabled: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ Personal Access Token Required + + You'll need a GitHub Personal Access Token to use this connector. You can create one from{" "} + + GitHub Settings + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + GitHub Personal Access Token + + + + + Your GitHub PAT will be encrypted and stored securely. It typically starts with "ghp_" or "github_pat_". + + + + )} + /> + + ( + + Repository Names + + + + + Comma-separated list of repository full names (e.g., "owner/repo1, owner/repo2"). + + + + )} + /> + + {/* Show parsed repositories as badges */} + {form.watch("repo_full_names")?.trim() && ( +
+

Selected Repositories:

+
+ {stringToArray(form.watch("repo_full_names") ?? "").map((repo) => ( + + {repo} + + ))} +
+
+ )} + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.GITHUB_CONNECTOR) && ( +
+

What you get with GitHub integration:

+
    + {getConnectorBenefits(EnumConnectorName.GITHUB_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The GitHub connector uses a Personal Access Token (PAT) to authenticate with the GitHub API. You provide a comma-separated list of repository full names (e.g., "owner/repo1, owner/repo2") that you want to index. The connector indexes relevant files (code, markdown, text) from the selected repositories. +

+
    +
  • + The connector indexes files based on common code and documentation extensions. +
  • +
  • Large files (over 1MB) are skipped during indexing.
  • +
  • Only specified repositories are indexed.
  • +
  • + Indexing runs periodically (check connector settings for frequency) to keep content up-to-date. +
  • +
+
+ +
+
+

Authorization

+ + + Personal Access Token Required + + You'll need a GitHub PAT with the appropriate scopes (e.g., 'repo') to fetch repositories. The PAT will be stored securely to enable indexing. + + + +
+
+

Step 1: Generate GitHub PAT

+
    +
  1. + Go to your GitHub{" "} + + Developer settings + +
  2. +
  3. + Click on Personal access tokens, then choose{" "} + Tokens (classic) or Fine-grained tokens (recommended if available). +
  4. +
  5. + Click Generate new token (and choose the appropriate type). +
  6. +
  7. + Give your token a descriptive name (e.g., "SurfSense Connector"). +
  8. +
  9. + Set an expiration date for the token (recommended for security). +
  10. +
  11. + 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. +
  12. +
  13. + Click Generate token. +
  14. +
  15. + Important: Copy your new PAT immediately. You won't be able to see it again after leaving the page. +
  16. +
+
+ +
+

Step 2: Specify repositories

+

+ Enter a comma-separated list of repository full names in the format "owner/repo1, owner/repo2". The connector will index files from only the specified repositories. +

+ + + Repository Access + + Make sure your PAT has access to all repositories you want to index. Private repositories require appropriate permissions. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the GitHub Connector. +
  2. +
  3. + Enter your GitHub Personal Access Token in the form field. +
  4. +
  5. + Enter a comma-separated list of Repository Names (e.g., "owner/repo1, owner/repo2"). +
  6. +
  7. + Click Connect to establish the connection. +
  8. +
  9. Once connected, your GitHub repositories will be indexed automatically.
  10. +
+ + + + What Gets Indexed + +

The GitHub connector indexes the following data:

+
    +
  • Code files from selected repositories
  • +
  • README files and Markdown documentation
  • +
  • Common text-based file formats
  • +
  • Repository metadata and structure
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/jira-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/jira-connect-form.tsx new file mode 100644 index 000000000..c87010eb5 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/jira-connect-form.tsx @@ -0,0 +1,407 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +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 base 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.", + }), +}); + +type JiraConnectorFormValues = z.infer; + +export const JiraConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(jiraConnectorFormSchema), + defaultValues: { + name: "Jira Connector", + base_url: "", + email: "", + api_token: "", + }, + }); + + const handleSubmit = async (values: JiraConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + 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: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ API Token Required + + You'll need a Jira API Token to use this connector. You can create one from{" "} + + Atlassian Account Settings + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Jira Base URL + + + + + The base URL of your Jira instance (e.g., https://your-domain.atlassian.net). + + + + )} + /> + + ( + + Email Address + + + + + The email address associated with your Atlassian account. + + + + )} + /> + + ( + + API Token + + + + + Your Jira API Token will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.JIRA_CONNECTOR) && ( +
+

What you get with Jira integration:

+
    + {getConnectorBenefits(EnumConnectorName.JIRA_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

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. + + + +
+
+

Step 1: Create an API Token

+
    +
  1. Log in to your Atlassian account
  2. +
  3. + Navigate to{" "} + + https://id.atlassian.com/manage-profile/security/api-tokens + {" "} + in your browser. +
  4. +
  5. + Click Create API token +
  6. +
  7. Enter a label for your token (like "SurfSense Connector")
  8. +
  9. + Click Create +
  10. +
  11. Copy the generated token as it will only be shown once
  12. +
+
+ +
+

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

+
    +
  1. + Navigate to the Connector Dashboard and select the Jira Connector. +
  2. +
  3. + Enter your Jira Instance URL (e.g., https://yourcompany.atlassian.net) +
  4. +
  5. + Enter your Email Address associated with your Atlassian account +
  6. +
  7. + Place your API Token in the form field. +
  8. +
  9. + Click Connect to establish the connection. +
  10. +
  11. Once connected, your Jira issues will be indexed automatically.
  12. +
+ + + + 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/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx index cc80b5dd3..1514e023f 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx @@ -226,8 +226,8 @@ export const LinearConnectForm: FC = ({

What you get with Linear integration:

    - {getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR)?.map((benefit, i) => ( -
  • {benefit}
  • + {getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • ))}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/luma-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/luma-connect-form.tsx new file mode 100644 index 000000000..f72ba3812 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/luma-connect-form.tsx @@ -0,0 +1,342 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +import type { FC } from "react"; +import { useRef } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +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 { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +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: "Luma API Key is required and must be valid.", + }), +}); + +type LumaConnectorFormValues = z.infer; + +export const LumaConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(lumaConnectorFormSchema), + defaultValues: { + name: "Luma Connector", + api_key: "", + }, + }); + + const handleSubmit = async (values: LumaConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + 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: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ API Key Required + + You'll need a Luma API Key to use this connector. You can create one from{" "} + + Luma API Settings + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Luma API Key + + + + + Your Luma API Key will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.LUMA_CONNECTOR) && ( +
+

What you get with Luma integration:

+
    + {getConnectorBenefits(EnumConnectorName.LUMA_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The Luma connector uses the Luma API to fetch all events that your API key has access to. +

+
    +
  • + For follow up indexing runs, the connector retrieves events 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

+ + + API Key Required + + You need a Luma API key to use this connector. The key will be used to read your Luma events with read-only permissions. + + + +
+
+

Step 1: Get Your API Key

+
    +
  1. Log into your Luma account
  2. +
  3. Navigate to your account settings
  4. +
  5. Go to API settings or Developer settings
  6. +
  7. Generate a new API key
  8. +
  9. Copy the generated API key
  10. +
  11. + You can also visit{" "} + + Luma API Settings + {" "} + for more information. +
  12. +
+
+ +
+

Step 2: Grant necessary access

+

+ The API key will have access to all events that your user account can see. Make sure your account has appropriate permissions for the events you want to index. +

+ + + Data Privacy + + Only event details, descriptions, and attendee information will be indexed. Event attachments and linked files are not indexed by this connector. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the Luma Connector. +
  2. +
  3. + Place your API Key in the form field. +
  4. +
  5. + Click Connect to establish the connection. +
  6. +
  7. Once connected, your Luma events will be indexed automatically.
  8. +
+ + + + What Gets Indexed + +

The Luma connector indexes the following data:

+
    +
  • Event titles and descriptions
  • +
  • Event details and metadata
  • +
  • Attendee information
  • +
  • Event dates and locations
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx index 8a1ee740d..ca6735afd 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx @@ -223,8 +223,8 @@ export const NotionConnectForm: FC = ({

What you get with Notion integration:

    - {getConnectorBenefits(EnumConnectorName.NOTION_CONNECTOR)?.map((benefit, i) => ( -
  • {benefit}
  • + {getConnectorBenefits(EnumConnectorName.NOTION_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • ))}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx index 2e4424bc2..2d4f8b7c8 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx @@ -223,8 +223,8 @@ export const SlackConnectForm: FC = ({

What you get with Slack integration:

    - {getConnectorBenefits(EnumConnectorName.SLACK_CONNECTOR)?.map((benefit, i) => ( -
  • {benefit}
  • + {getConnectorBenefits(EnumConnectorName.SLACK_CONNECTOR)?.map((benefit) => ( +
  • {benefit}
  • ))}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts index ab70d7835..664fe78b8 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts @@ -59,8 +59,48 @@ export function getConnectorBenefits(connectorType: string): string[] | null { "Keep your search results up-to-date with latest Notion content", "Index your Notion workspace for enhanced search capabilities", ], - // Add other connectors as needed - // GITHUB_CONNECTOR: [...], + CONFLUENCE_CONNECTOR: [ + "Search through all your Confluence pages and spaces", + "Access page content, comments, and attachments", + "Connect your team's documentation directly to your search space", + "Keep your search results up-to-date with latest Confluence content", + "Index your Confluence workspace for enhanced search capabilities", + ], + BOOKSTACK_CONNECTOR: [ + "Search through all your BookStack pages and books", + "Access page content, chapters, and documentation", + "Connect your documentation directly to your search space", + "Keep your search results up-to-date with latest BookStack content", + "Index your BookStack instance for enhanced search capabilities", + ], + GITHUB_CONNECTOR: [ + "Search through code, issues, and documentation from GitHub repositories", + "Access repository content, pull requests, and discussions", + "Connect your codebase directly to your search space", + "Keep your search results up-to-date with latest GitHub content", + "Index your GitHub repositories for enhanced search capabilities", + ], + JIRA_CONNECTOR: [ + "Search through all your Jira issues and tickets", + "Access issue descriptions, comments, and project data", + "Connect your project management directly to your search space", + "Keep your search results up-to-date with latest Jira content", + "Index your Jira projects for enhanced search capabilities", + ], + CLICKUP_CONNECTOR: [ + "Search through all your ClickUp tasks and projects", + "Access task descriptions, comments, and project data", + "Connect your task management directly to your search space", + "Keep your search results up-to-date with latest ClickUp content", + "Index your ClickUp workspace for enhanced search capabilities", + ], + LUMA_CONNECTOR: [ + "Search through all your Luma events", + "Access event details, descriptions, and attendee information", + "Connect your events directly to your search space", + "Keep your search results up-to-date with latest Luma content", + "Index your Luma events for enhanced search capabilities", + ], }; return benefits[connectorType] || null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx index d06fd7125..46ed58272 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx @@ -1,9 +1,15 @@ import type { FC } from "react"; import { BaiduSearchApiConnectForm } from "./components/baidu-search-api-connect-form"; +import { BookStackConnectForm } from "./components/bookstack-connect-form"; +import { ClickUpConnectForm } from "./components/clickup-connect-form"; +import { ConfluenceConnectForm } from "./components/confluence-connect-form"; import { DiscordConnectForm } from "./components/discord-connect-form"; import { ElasticsearchConnectForm } from "./components/elasticsearch-connect-form"; +import { GithubConnectForm } from "./components/github-connect-form"; +import { JiraConnectForm } from "./components/jira-connect-form"; import { LinearConnectForm } from "./components/linear-connect-form"; import { LinkupApiConnectForm } from "./components/linkup-api-connect-form"; +import { LumaConnectForm } from "./components/luma-connect-form"; import { NotionConnectForm } from "./components/notion-connect-form"; import { SearxngConnectForm } from "./components/searxng-connect-form"; import { SlackConnectForm } from "./components/slack-connect-form"; @@ -56,6 +62,18 @@ export function getConnectFormComponent( return DiscordConnectForm; case "NOTION_CONNECTOR": return NotionConnectForm; + case "CONFLUENCE_CONNECTOR": + return ConfluenceConnectForm; + case "BOOKSTACK_CONNECTOR": + return BookStackConnectForm; + case "GITHUB_CONNECTOR": + return GithubConnectForm; + case "JIRA_CONNECTOR": + return JiraConnectForm; + case "CLICKUP_CONNECTOR": + return ClickUpConnectForm; + case "LUMA_CONNECTOR": + return LumaConnectForm; // Add other connector types here as needed default: return null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/bookstack-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/bookstack-config.tsx new file mode 100644 index 000000000..facfc0941 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/bookstack-config.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface BookStackConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const BookStackConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [baseUrl, setBaseUrl] = useState( + (connector.config?.BOOKSTACK_BASE_URL as string) || "" + ); + const [tokenId, setTokenId] = useState( + (connector.config?.BOOKSTACK_TOKEN_ID as string) || "" + ); + const [tokenSecret, setTokenSecret] = useState( + (connector.config?.BOOKSTACK_TOKEN_SECRET as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update values when connector changes + useEffect(() => { + const url = (connector.config?.BOOKSTACK_BASE_URL as string) || ""; + const id = (connector.config?.BOOKSTACK_TOKEN_ID as string) || ""; + const secret = (connector.config?.BOOKSTACK_TOKEN_SECRET as string) || ""; + setBaseUrl(url); + setTokenId(id); + setTokenSecret(secret); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleBaseUrlChange = (value: string) => { + setBaseUrl(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + BOOKSTACK_BASE_URL: value, + }); + } + }; + + const handleTokenIdChange = (value: string) => { + setTokenId(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + BOOKSTACK_TOKEN_ID: value, + }); + } + }; + + const handleTokenSecretChange = (value: string) => { + setTokenSecret(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + BOOKSTACK_TOKEN_SECRET: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My BookStack Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+
+ + handleBaseUrlChange(e.target.value)} + placeholder="https://your-bookstack-instance.com" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ The base URL of your BookStack instance. +

+
+ +
+ + handleTokenIdChange(e.target.value)} + placeholder="Your Token ID" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Your BookStack API Token ID. +

+
+ +
+ + handleTokenSecretChange(e.target.value)} + placeholder="Your Token Secret" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your BookStack Token Secret if needed. +

+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/clickup-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/clickup-config.tsx new file mode 100644 index 000000000..b2cbed7d7 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/clickup-config.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface ClickUpConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const ClickUpConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [apiToken, setApiToken] = useState( + (connector.config?.CLICKUP_API_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update API token and name when connector changes + useEffect(() => { + const token = (connector.config?.CLICKUP_API_TOKEN as string) || ""; + setApiToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleApiTokenChange = (value: string) => { + setApiToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + CLICKUP_API_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My ClickUp Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+ + handleApiTokenChange(e.target.value)} + placeholder="pk_..." + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your ClickUp API Token if needed. +

+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/confluence-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/confluence-config.tsx new file mode 100644 index 000000000..796428083 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/confluence-config.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface ConfluenceConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const ConfluenceConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [baseUrl, setBaseUrl] = useState( + (connector.config?.CONFLUENCE_BASE_URL as string) || "" + ); + const [email, setEmail] = useState( + (connector.config?.CONFLUENCE_EMAIL as string) || "" + ); + const [apiToken, setApiToken] = useState( + (connector.config?.CONFLUENCE_API_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update values when connector changes + useEffect(() => { + const url = (connector.config?.CONFLUENCE_BASE_URL as string) || ""; + const emailVal = (connector.config?.CONFLUENCE_EMAIL as string) || ""; + const token = (connector.config?.CONFLUENCE_API_TOKEN as string) || ""; + setBaseUrl(url); + setEmail(emailVal); + setApiToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleBaseUrlChange = (value: string) => { + setBaseUrl(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + CONFLUENCE_BASE_URL: value, + }); + } + }; + + const handleEmailChange = (value: string) => { + setEmail(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + CONFLUENCE_EMAIL: value, + }); + } + }; + + const handleApiTokenChange = (value: string) => { + setApiToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + CONFLUENCE_API_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Confluence Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+
+ + handleBaseUrlChange(e.target.value)} + placeholder="https://your-domain.atlassian.net" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ The base URL of your Confluence instance. +

+
+ +
+ + handleEmailChange(e.target.value)} + placeholder="your-email@example.com" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ The email address associated with your Atlassian account. +

+
+ +
+ + handleApiTokenChange(e.target.value)} + placeholder="Your API Token" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Confluence API Token if needed. +

+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/github-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/github-config.tsx new file mode 100644 index 000000000..acf32e322 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/github-config.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface GithubConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const GithubConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const stringToArray = (arr: string[] | string | undefined): string[] => { + if (Array.isArray(arr)) return arr; + if (typeof arr === "string") { + return arr.split(",").map((item) => item.trim()).filter((item) => item.length > 0); + } + return []; + }; + + const arrayToString = (arr: string[]): string => { + return arr.join(", "); + }; + + const [githubPat, setGithubPat] = useState( + (connector.config?.GITHUB_PAT as string) || "" + ); + const [repoFullNames, setRepoFullNames] = useState( + arrayToString(stringToArray(connector.config?.repo_full_names)) + ); + const [name, setName] = useState(connector.name || ""); + + // Update values when connector changes + useEffect(() => { + const pat = (connector.config?.GITHUB_PAT as string) || ""; + const repos = arrayToString(stringToArray(connector.config?.repo_full_names)); + setGithubPat(pat); + setRepoFullNames(repos); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleGithubPatChange = (value: string) => { + setGithubPat(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + GITHUB_PAT: value, + }); + } + }; + + const handleRepoFullNamesChange = (value: string) => { + setRepoFullNames(value); + const repoList = stringToArray(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + repo_full_names: repoList, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My GitHub Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+
+ + handleGithubPatChange(e.target.value)} + placeholder="ghp_..." + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your GitHub PAT if needed. +

+
+ +
+ + handleRepoFullNamesChange(e.target.value)} + placeholder="owner/repo1, owner/repo2" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Comma-separated list of repository full names. +

+
+ + {/* Show parsed repositories as badges */} + {repoFullNames.trim() && ( +
+

Repositories:

+
+ {stringToArray(repoFullNames).map((repo) => ( + + {repo} + + ))} +
+
+ )} +
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/jira-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/jira-config.tsx new file mode 100644 index 000000000..cd73b0fd8 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/jira-config.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface JiraConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const JiraConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [baseUrl, setBaseUrl] = useState( + (connector.config?.JIRA_BASE_URL as string) || "" + ); + const [email, setEmail] = useState( + (connector.config?.JIRA_EMAIL as string) || "" + ); + const [apiToken, setApiToken] = useState( + (connector.config?.JIRA_API_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update values when connector changes + useEffect(() => { + const url = (connector.config?.JIRA_BASE_URL as string) || ""; + const emailVal = (connector.config?.JIRA_EMAIL as string) || ""; + const token = (connector.config?.JIRA_API_TOKEN as string) || ""; + setBaseUrl(url); + setEmail(emailVal); + setApiToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleBaseUrlChange = (value: string) => { + setBaseUrl(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + JIRA_BASE_URL: value, + }); + } + }; + + const handleEmailChange = (value: string) => { + setEmail(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + JIRA_EMAIL: value, + }); + } + }; + + const handleApiTokenChange = (value: string) => { + setApiToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + JIRA_API_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Jira Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+
+ + handleBaseUrlChange(e.target.value)} + placeholder="https://your-domain.atlassian.net" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ The base URL of your Jira instance. +

+
+ +
+ + handleEmailChange(e.target.value)} + placeholder="your-email@example.com" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ The email address associated with your Atlassian account. +

+
+ +
+ + handleApiTokenChange(e.target.value)} + placeholder="Your API Token" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Jira API Token if needed. +

+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/luma-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/luma-config.tsx new file mode 100644 index 000000000..ec4ffe9ef --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/luma-config.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface LumaConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const LumaConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [apiKey, setApiKey] = useState( + (connector.config?.LUMA_API_KEY as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update API key and name when connector changes + useEffect(() => { + const key = (connector.config?.LUMA_API_KEY as string) || ""; + setApiKey(key); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleApiKeyChange = (value: string) => { + setApiKey(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + LUMA_API_KEY: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Luma Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+ + handleApiKeyChange(e.target.value)} + placeholder="Your API Key" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Luma API Key if needed. +

+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx index 6f5afaec0..59919d0cd 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx @@ -3,11 +3,17 @@ import type { FC } from "react"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { BaiduSearchApiConfig } from "./components/baidu-search-api-config"; +import { BookStackConfig } from "./components/bookstack-config"; +import { ClickUpConfig } from "./components/clickup-config"; +import { ConfluenceConfig } from "./components/confluence-config"; import { DiscordConfig } from "./components/discord-config"; import { ElasticsearchConfig } from "./components/elasticsearch-config"; +import { GithubConfig } from "./components/github-config"; import { GoogleDriveConfig } from "./components/google-drive-config"; +import { JiraConfig } from "./components/jira-config"; import { LinearConfig } from "./components/linear-config"; import { LinkupApiConfig } from "./components/linkup-api-config"; +import { LumaConfig } from "./components/luma-config"; import { NotionConfig } from "./components/notion-config"; import { SearxngConfig } from "./components/searxng-config"; import { SlackConfig } from "./components/slack-config"; @@ -51,6 +57,18 @@ export function getConnectorConfigComponent( return DiscordConfig; case "NOTION_CONNECTOR": return NotionConfig; + case "CONFLUENCE_CONNECTOR": + return ConfluenceConfig; + case "BOOKSTACK_CONNECTOR": + return BookStackConfig; + case "GITHUB_CONNECTOR": + return GithubConfig; + case "JIRA_CONNECTOR": + return JiraConfig; + case "CLICKUP_CONNECTOR": + return ClickUpConfig; + case "LUMA_CONNECTOR": + return LumaConfig; // OAuth connectors (Gmail, Calendar, Airtable) and others don't need special config UI default: return null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx index 9965f721b..81605c44f 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx @@ -52,6 +52,12 @@ export const ConnectorConnectView: FC = ({ SLACK_CONNECTOR: "slack-connect-form", DISCORD_CONNECTOR: "discord-connect-form", NOTION_CONNECTOR: "notion-connect-form", + CONFLUENCE_CONNECTOR: "confluence-connect-form", + BOOKSTACK_CONNECTOR: "bookstack-connect-form", + GITHUB_CONNECTOR: "github-connect-form", + JIRA_CONNECTOR: "jira-connect-form", + CLICKUP_CONNECTOR: "clickup-connect-form", + LUMA_CONNECTOR: "luma-connect-form", }; const formId = formIdMap[connectorType]; if (formId) { diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx index 76b93b930..30884e6d3 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx @@ -102,6 +102,12 @@ export const AllConnectorsTab: FC = ({ const isSlack = connector.id === "slack-connector"; const isDiscord = connector.id === "discord-connector"; const isNotion = connector.id === "notion-connector"; + const isConfluence = connector.id === "confluence-connector"; + const isBookStack = connector.id === "bookstack-connector"; + const isGithub = connector.id === "github-connector"; + const isJira = connector.id === "jira-connector"; + const isClickUp = connector.id === "clickup-connector"; + const isLuma = connector.id === "luma-connector"; const isConnected = connectedTypes.has(connector.connectorType); const isConnecting = connectingId === connector.id; @@ -113,7 +119,7 @@ export const AllConnectorsTab: FC = ({ const handleConnect = isWebcrawler && onCreateWebcrawler ? onCreateWebcrawler - : (isTavily || isSearxng || isLinkup || isBaidu || isLinear || isElasticsearch || isSlack || isDiscord || isNotion) && onConnectNonOAuth + : (isTavily || isSearxng || isLinkup || isBaidu || isLinear || isElasticsearch || isSlack || isDiscord || isNotion || isConfluence || isBookStack || isGithub || isJira || isClickUp || isLuma) && onConnectNonOAuth ? () => onConnectNonOAuth(connector.connectorType) : () => router.push(`/dashboard/${searchSpaceId}/connectors/add/${connector.id}`); diff --git a/surfsense_web/lib/connectors/utils.ts b/surfsense_web/lib/connectors/utils.ts index 648175332..de791be7f 100644 --- a/surfsense_web/lib/connectors/utils.ts +++ b/surfsense_web/lib/connectors/utils.ts @@ -11,6 +11,7 @@ export const getConnectorTypeDisplay = (type: string): string => { DISCORD_CONNECTOR: "Discord", LINKUP_API: "Linkup", CONFLUENCE_CONNECTOR: "Confluence", + BOOKSTACK_CONNECTOR: "BookStack", CLICKUP_CONNECTOR: "ClickUp", GOOGLE_CALENDAR_CONNECTOR: "Google Calendar", GOOGLE_GMAIL_CONNECTOR: "Google Gmail",