+ );
}
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
index 8986444..9ed3f94 100644
--- 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
@@ -9,7 +9,10 @@ import * as z from "zod";
import { toast } from "sonner";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
-import { useSearchSourceConnectors, SearchSourceConnector } from "@/hooks/useSearchSourceConnectors";
+import {
+ useSearchSourceConnectors,
+ SearchSourceConnector,
+} from "@/hooks/useSearchSourceConnectors";
import {
Form,
FormControl,
@@ -28,11 +31,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import {
- Alert,
- AlertDescription,
- AlertTitle,
-} from "@/components/ui/alert";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
// Define the form schema with Zod
const apiConnectorFormSchema = z.object({
@@ -47,13 +46,15 @@ const apiConnectorFormSchema = z.object({
// Helper function to get connector type display name
const getConnectorTypeDisplay = (type: string): string => {
const typeMap: Record = {
- "SERPER_API": "Serper API",
- "TAVILY_API": "Tavily API",
- "SLACK_CONNECTOR": "Slack Connector",
- "NOTION_CONNECTOR": "Notion Connector",
- "GITHUB_CONNECTOR": "GitHub Connector",
- "DISCORD_CONNECTOR": "Discord Connector",
- "LINKUP_API": "Linkup",
+ SERPER_API: "Serper API",
+ 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",
// Add other connector types here as needed
};
return typeMap[type] || type;
@@ -67,9 +68,11 @@ export default function EditConnectorPage() {
const params = useParams();
const searchSpaceId = params.search_space_id as string;
const connectorId = parseInt(params.connector_id as string, 10);
-
+
const { connectors, updateConnector } = useSearchSourceConnectors();
- const [connector, setConnector] = useState(null);
+ const [connector, setConnector] = useState(
+ null,
+ );
const [isLoading, setIsLoading] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
// console.log("connector", connector);
@@ -85,24 +88,24 @@ export default function EditConnectorPage() {
// Get API key field name based on connector type
const getApiKeyFieldName = (connectorType: string): string => {
const fieldMap: Record = {
- "SERPER_API": "SERPER_API_KEY",
- "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"
+ SERPER_API: "SERPER_API_KEY",
+ 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",
};
return fieldMap[connectorType] || "";
};
// Find connector in the list
useEffect(() => {
- const currentConnector = connectors.find(c => c.id === connectorId);
-
+ const currentConnector = connectors.find((c) => c.id === connectorId);
+
if (currentConnector) {
setConnector(currentConnector);
-
+
// Check if connector type is supported
const apiKeyField = getApiKeyFieldName(currentConnector.connector_type);
if (apiKeyField) {
@@ -115,7 +118,7 @@ export default function EditConnectorPage() {
toast.error("This connector type is not supported for editing");
router.push(`/dashboard/${searchSpaceId}/connectors`);
}
-
+
setIsLoading(false);
} else if (!isLoading && connectors.length > 0) {
// If connectors are loaded but this one isn't found
@@ -127,11 +130,11 @@ export default function EditConnectorPage() {
// Handle form submission
const onSubmit = async (values: ApiConnectorFormValues) => {
if (!connector) return;
-
+
setIsSubmitting(true);
try {
const apiKeyField = getApiKeyFieldName(connector.connector_type);
-
+
// Only update the API key if a new one was provided
const updatedConfig = { ...connector.config };
if (values.api_key) {
@@ -150,7 +153,9 @@ export default function EditConnectorPage() {
router.push(`/dashboard/${searchSpaceId}/connectors`);
} catch (error) {
console.error("Error updating connector:", error);
- toast.error(error instanceof Error ? error.message : "Failed to update connector");
+ toast.error(
+ error instanceof Error ? error.message : "Failed to update connector",
+ );
} finally {
setIsSubmitting(false);
}
@@ -186,24 +191,30 @@ export default function EditConnectorPage() {
- Edit {connector ? getConnectorTypeDisplay(connector.connector_type) : ""} Connector
+ Edit{" "}
+ {connector
+ ? getConnectorTypeDisplay(connector.connector_type)
+ : ""}{" "}
+ Connector
-
- Update your connector settings.
-
+ 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.
+ 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.
-
+
(
- {connector?.connector_type === "SLACK_CONNECTOR"
- ? "Slack Bot Token"
- : connector?.connector_type === "NOTION_CONNECTOR"
- ? "Notion Integration Token"
+ {connector?.connector_type === "SLACK_CONNECTOR"
+ ? "Slack Bot Token"
+ : connector?.connector_type === "NOTION_CONNECTOR"
+ ? "Notion Integration Token"
: connector?.connector_type === "GITHUB_CONNECTOR"
? "GitHub Personal Access Token (PAT)"
: connector?.connector_type === "LINKUP_API"
@@ -238,27 +249,28 @@ export default function EditConnectorPage() {
: "API Key"}
-
- {connector?.connector_type === "SLACK_CONNECTOR"
- ? "Enter a new Slack Bot Token or leave blank to keep your existing token."
- : connector?.connector_type === "NOTION_CONNECTOR"
- ? "Enter a new Notion Integration Token or leave blank to keep your existing token."
+ {connector?.connector_type === "SLACK_CONNECTOR"
+ ? "Enter a new Slack Bot Token or leave blank to keep your existing token."
+ : connector?.connector_type === "NOTION_CONNECTOR"
+ ? "Enter a new Notion Integration Token or leave blank to keep your existing token."
: connector?.connector_type === "GITHUB_CONNECTOR"
? "Enter a new GitHub PAT or leave blank to keep your existing token."
: connector?.connector_type === "LINKUP_API"
@@ -271,8 +283,8 @@ export default function EditConnectorPage() {
/>
-
);
-}
+}
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
new file mode 100644
index 0000000..625adfa
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx
@@ -0,0 +1,448 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter, useParams } from "next/navigation";
+import { motion } from "framer-motion";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import * as z from "zod";
+import { toast } from "sonner";
+import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
+
+import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+
+// 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",
+ },
+ ),
+ personal_access_token: z.string().min(10, {
+ message: "Jira Personal Access 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 { createConnector } = useSearchSourceConnectors();
+
+ // Initialize the form
+ const form = useForm({
+ resolver: zodResolver(jiraConnectorFormSchema),
+ defaultValues: {
+ name: "Jira Connector",
+ base_url: "",
+ personal_access_token: "",
+ },
+ });
+
+ // Handle form submission
+ const onSubmit = async (values: JiraConnectorFormValues) => {
+ setIsSubmitting(true);
+ try {
+ await createConnector({
+ name: values.name,
+ connector_type: "JIRA_CONNECTOR",
+ config: {
+ JIRA_BASE_URL: values.base_url,
+ JIRA_PERSONAL_ACCESS_TOKEN: values.personal_access_token,
+ },
+ is_indexable: true,
+ last_indexed_at: null,
+ });
+
+ 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
+ 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
+
+
+
+ )}
+ />
+
+ (
+
+ Personal Access Token
+
+
+
+
+ Your Jira Personal Access 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 to fetch all
+ issues and comments that the Personal Access Token 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 Personal Access 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 Personal Access 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/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
index afcc0af..3d0e59d 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
@@ -1,8 +1,17 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+} from "@/components/ui/card";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
import {
IconBrandDiscord,
IconBrandGithub,
@@ -67,23 +76,26 @@ const connectorCategories: ConnectorCategory[] = [
{
id: "slack-connector",
title: "Slack",
- description: "Connect to your Slack workspace to access messages and channels.",
+ description:
+ "Connect to your Slack workspace to access messages and channels.",
icon: ,
status: "available",
},
{
id: "ms-teams",
title: "Microsoft Teams",
- description: "Connect to Microsoft Teams to access your team's conversations.",
+ description:
+ "Connect to Microsoft Teams to access your team's conversations.",
icon: ,
status: "coming-soon",
},
{
id: "discord-connector",
title: "Discord",
- description: "Connect to Discord servers to access messages and channels.",
+ description:
+ "Connect to Discord servers to access messages and channels.",
icon: ,
- status: "available"
+ status: "available",
},
],
},
@@ -94,16 +106,18 @@ const connectorCategories: ConnectorCategory[] = [
{
id: "linear-connector",
title: "Linear",
- description: "Connect to Linear to search issues, comments and project data.",
+ description:
+ "Connect to Linear to search issues, comments and project data.",
icon: ,
status: "available",
},
{
id: "jira-connector",
title: "Jira",
- description: "Connect to Jira to search issues, tickets and project data.",
+ description:
+ "Connect to Jira to search issues, tickets and project data.",
icon: ,
- status: "coming-soon",
+ status: "available",
},
],
},
@@ -114,14 +128,16 @@ const connectorCategories: ConnectorCategory[] = [
{
id: "notion-connector",
title: "Notion",
- description: "Connect to your Notion workspace to access pages and databases.",
+ description:
+ "Connect to your Notion workspace to access pages and databases.",
icon: ,
status: "available",
},
{
id: "github-connector",
title: "GitHub",
- description: "Connect a GitHub PAT to index code and docs from accessible repositories.",
+ description:
+ "Connect a GitHub PAT to index code and docs from accessible repositories.",
icon: ,
status: "available",
},
@@ -141,7 +157,8 @@ const connectorCategories: ConnectorCategory[] = [
{
id: "zoom",
title: "Zoom",
- description: "Connect to Zoom to access meeting recordings and transcripts.",
+ description:
+ "Connect to Zoom to access meeting recordings and transcripts.",
icon: ,
status: "coming-soon",
},
@@ -152,7 +169,7 @@ const connectorCategories: ConnectorCategory[] = [
// Animation variants
const fadeIn = {
hidden: { opacity: 0 },
- visible: { opacity: 1, transition: { duration: 0.4 } }
+ visible: { opacity: 1, transition: { duration: 0.4 } },
};
const staggerContainer = {
@@ -160,43 +177,49 @@ const staggerContainer = {
visible: {
opacity: 1,
transition: {
- staggerChildren: 0.1
- }
- }
+ staggerChildren: 0.1,
+ },
+ },
};
const cardVariants = {
hidden: { opacity: 0, y: 20 },
- visible: {
- opacity: 1,
+ visible: {
+ opacity: 1,
y: 0,
- transition: {
+ transition: {
type: "spring",
stiffness: 260,
- damping: 20
- }
+ damping: 20,
+ },
},
- hover: {
+ hover: {
scale: 1.02,
- boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
- transition: {
+ boxShadow:
+ "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
+ transition: {
type: "spring",
stiffness: 400,
- damping: 10
- }
- }
+ damping: 10,
+ },
+ },
};
export default function ConnectorsPage() {
const params = useParams();
const searchSpaceId = params.search_space_id as string;
- const [expandedCategories, setExpandedCategories] = useState(["search-engines", "knowledge-bases", "project-management", "team-chats"]);
+ const [expandedCategories, setExpandedCategories] = useState([
+ "search-engines",
+ "knowledge-bases",
+ "project-management",
+ "team-chats",
+ ]);
const toggleCategory = (categoryId: string) => {
- setExpandedCategories(prev =>
- prev.includes(categoryId)
- ? prev.filter(id => id !== categoryId)
- : [...prev, categoryId]
+ setExpandedCategories((prev) =>
+ prev.includes(categoryId)
+ ? prev.filter((id) => id !== categoryId)
+ : [...prev, categoryId],
);
};
@@ -205,9 +228,9 @@ export default function ConnectorsPage() {
@@ -215,18 +238,19 @@ export default function ConnectorsPage() {
Connect Your Tools
- Integrate with your favorite services to enhance your research capabilities.
+ Integrate with your favorite services to enhance your research
+ capabilities.