diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py
index 4c3d691..aedab8b 100644
--- a/surfsense_backend/app/routes/search_source_connectors_routes.py
+++ b/surfsense_backend/app/routes/search_source_connectors_routes.py
@@ -36,6 +36,7 @@ from app.schemas import (
SearchSourceConnectorUpdate,
)
from app.tasks.connectors_indexing_tasks import (
+ index_confluence_pages,
index_discord_messages,
index_github_repos,
index_jira_issues,
@@ -457,6 +458,21 @@ async def index_connector_content(
)
response_message = "Jira indexing started in the background."
+ elif connector.connector_type == SearchSourceConnectorType.CONFLUENCE_CONNECTOR:
+ # Run indexing in background
+ logger.info(
+ f"Triggering Confluence indexing for connector {connector_id} into search space {search_space_id} from {indexing_from} to {indexing_to}"
+ )
+ background_tasks.add_task(
+ run_confluence_indexing_with_new_session,
+ connector_id,
+ search_space_id,
+ str(user.id),
+ indexing_from,
+ indexing_to,
+ )
+ response_message = "Confluence indexing started in the background."
+
elif connector.connector_type == SearchSourceConnectorType.DISCORD_CONNECTOR:
# Run indexing in background
logger.info(
@@ -884,3 +900,61 @@ async def run_jira_indexing(
exc_info=True,
)
# Optionally update status in DB to indicate failure
+
+
+# Add new helper functions for Confluence indexing
+async def run_confluence_indexing_with_new_session(
+ connector_id: int,
+ search_space_id: int,
+ user_id: str,
+ start_date: str,
+ end_date: str,
+):
+ """Wrapper to run Confluence indexing with its own database session."""
+ logger.info(
+ f"Background task started: Indexing Confluence connector {connector_id} into space {search_space_id} from {start_date} to {end_date}"
+ )
+ async with async_session_maker() as session:
+ await run_confluence_indexing(
+ session, connector_id, search_space_id, user_id, start_date, end_date
+ )
+ logger.info(f"Background task finished: Indexing Confluence connector {connector_id}")
+
+
+async def run_confluence_indexing(
+ session: AsyncSession,
+ connector_id: int,
+ search_space_id: int,
+ user_id: str,
+ start_date: str,
+ end_date: str,
+):
+ """Runs the Confluence indexing task and updates the timestamp."""
+ try:
+ indexed_count, error_message = await index_confluence_pages(
+ session,
+ connector_id,
+ search_space_id,
+ user_id,
+ start_date,
+ end_date,
+ update_last_indexed=False,
+ )
+ if error_message:
+ logger.error(
+ f"Confluence indexing failed for connector {connector_id}: {error_message}"
+ )
+ # Optionally update status in DB to indicate failure
+ else:
+ logger.info(
+ f"Confluence indexing successful for connector {connector_id}. Indexed {indexed_count} documents."
+ )
+ # Update the last indexed timestamp only on success
+ await update_connector_last_indexed(session, connector_id)
+ await session.commit() # Commit timestamp update
+ except Exception as e:
+ logger.error(
+ f"Critical error in run_confluence_indexing for connector {connector_id}: {e}",
+ exc_info=True,
+ )
+ # Optionally update status in DB to indicate failure
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
index 918a625..bd8b61c 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx
@@ -208,6 +208,33 @@ export default function EditConnectorPage() {
)}
+ {/* == Confluence == */}
+ {connector.connector_type === "CONFLUENCE_CONNECTOR" && (
+
+
+
+
+
+ )}
+
{/* == Linkup == */}
{connector.connector_type === "LINKUP_API" && (
{
+ return url.includes("atlassian.net") || url.includes("confluence");
+ },
+ {
+ message: "Please enter a valid Confluence instance URL",
+ },
+ ),
+ email: z.string().email({
+ message: "Please enter a valid email address.",
+ }),
+ api_token: z.string().min(10, {
+ message: "Confluence API Token is required and must be valid.",
+ }),
+});
+
+// Define the type for the form values
+type ConfluenceConnectorFormValues = z.infer;
+
+export default function ConfluenceConnectorPage() {
+ const router = useRouter();
+ const params = useParams();
+ const searchSpaceId = params.search_space_id as string;
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const { createConnector } = useSearchSourceConnectors();
+
+ // Initialize the form
+ const form = useForm({
+ resolver: zodResolver(confluenceConnectorFormSchema),
+ defaultValues: {
+ name: "Confluence Connector",
+ base_url: "",
+ email: "",
+ api_token: "",
+ },
+ });
+
+ // Handle form submission
+ const onSubmit = async (values: ConfluenceConnectorFormValues) => {
+ setIsSubmitting(true);
+ try {
+ await createConnector({
+ name: values.name,
+ connector_type: "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,
+ });
+
+ toast.success("Confluence connector created successfully!");
+
+ // Navigate back to connectors page
+ router.push(`/dashboard/${searchSpaceId}/connectors`);
+ } catch (error) {
+ console.error("Error creating connector:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Failed to create connector",
+ );
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+ router.push(`/dashboard/${searchSpaceId}/connectors/add`)
+ }
+ >
+
+ Back to Connectors
+
+
+
+
+
+ Connect
+ Documentation
+
+
+
+
+
+ Connect to Confluence
+
+ Connect your Confluence instance to index pages and comments from your spaces.
+
+
+
+
+
+
+ You'll need to create an API token from your{" "}
+
+ Atlassian Account Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Confluence Integration Guide
+
+ Learn how to set up and use the Confluence connector.
+
+
+
+
+
What gets indexed?
+
+ All pages from accessible spaces
+ Page content and metadata
+ Comments on pages (both footer and inline comments)
+ Page titles and descriptions
+
+
+
+
+
Setup Instructions
+
+ Go to your Atlassian Account Settings
+ Navigate to Security → API tokens
+ Create a new API token with appropriate permissions
+ Copy the token and paste it in the form above
+ Ensure your account has read access to the spaces you want to index
+
+
+
+
+
Permissions Required
+
+ Read access to Confluence spaces
+ View pages and comments
+ Access to space metadata
+
+
+
+
+
+
+ The connector will only index content that your account has permission to view.
+ Make sure your API token has the necessary permissions for the spaces you want to index.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx
index 3d0e59d..417036d 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
@@ -13,6 +13,7 @@ import {
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
+ IconBook,
IconBrandDiscord,
IconBrandGithub,
IconBrandNotion,
@@ -141,6 +142,14 @@ const connectorCategories: ConnectorCategory[] = [
icon: ,
status: "available",
},
+ {
+ id: "confluence-connector",
+ title: "Confluence",
+ description:
+ "Connect to Confluence to search pages, comments and documentation.",
+ icon: ,
+ status: "available",
+ },
],
},
{
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
index 1b66684..24a046f 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx
@@ -57,6 +57,7 @@ import {
IconBrandDiscord,
IconBrandGithub,
IconBrandNotion,
+ IconBrandPagekit,
IconBrandSlack,
IconBrandYoutube,
IconLayoutKanban,
@@ -180,6 +181,7 @@ const documentTypeIcons = {
LINEAR_CONNECTOR: IconLayoutKanban,
JIRA_CONNECTOR: IconTicket,
DISCORD_CONNECTOR: IconBrandDiscord,
+ CONFLUENCE_CONNECTOR: IconBrandPagekit,
} as const;
const columns: ColumnDef[] = [
diff --git a/surfsense_web/components/editConnector/types.ts b/surfsense_web/components/editConnector/types.ts
index ad16010..a54453a 100644
--- a/surfsense_web/components/editConnector/types.ts
+++ b/surfsense_web/components/editConnector/types.ts
@@ -32,5 +32,11 @@ export const editConnectorSchema = z.object({
LINEAR_API_KEY: z.string().optional(),
LINKUP_API_KEY: z.string().optional(),
DISCORD_BOT_TOKEN: z.string().optional(),
+ CONFLUENCE_BASE_URL: z.string().optional(),
+ CONFLUENCE_EMAIL: z.string().optional(),
+ CONFLUENCE_API_TOKEN: z.string().optional(),
+ JIRA_BASE_URL: z.string().optional(),
+ JIRA_EMAIL: z.string().optional(),
+ JIRA_API_TOKEN: z.string().optional(),
});
export type EditConnectorFormValues = z.infer;
diff --git a/surfsense_web/hooks/useConnectorEditPage.ts b/surfsense_web/hooks/useConnectorEditPage.ts
index ad0992a..27790ac 100644
--- a/surfsense_web/hooks/useConnectorEditPage.ts
+++ b/surfsense_web/hooks/useConnectorEditPage.ts
@@ -35,15 +35,21 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
});
const editForm = useForm({
resolver: zodResolver(editConnectorSchema),
- defaultValues: {
- name: "",
- SLACK_BOT_TOKEN: "",
- NOTION_INTEGRATION_TOKEN: "",
- SERPER_API_KEY: "",
+ defaultValues: {
+ name: "",
+ SLACK_BOT_TOKEN: "",
+ NOTION_INTEGRATION_TOKEN: "",
+ SERPER_API_KEY: "",
TAVILY_API_KEY: "",
LINEAR_API_KEY: "",
DISCORD_BOT_TOKEN: "",
- },
+ CONFLUENCE_BASE_URL: "",
+ CONFLUENCE_EMAIL: "",
+ CONFLUENCE_API_TOKEN: "",
+ JIRA_BASE_URL: "",
+ JIRA_EMAIL: "",
+ JIRA_API_TOKEN: "",
+ },
});
// Effect to load initial data