mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-28 21:49:40 +02:00
feat: add BookStack connector for wiki documentation indexing
This commit is contained in:
parent
e0725741c9
commit
6b1b8d0f2e
18 changed files with 1362 additions and 1 deletions
|
|
@ -0,0 +1,305 @@
|
|||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import * as z from "zod";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
||||
|
||||
// Define the form schema with Zod
|
||||
const bookstackConnectorFormSchema = z.object({
|
||||
name: z.string().min(3, {
|
||||
message: "Connector name must be at least 3 characters.",
|
||||
}),
|
||||
base_url: z
|
||||
.string()
|
||||
.url({
|
||||
message: "Please enter a valid BookStack URL (e.g., https://docs.example.com)",
|
||||
}),
|
||||
token_id: z.string().min(10, {
|
||||
message: "BookStack Token ID is required.",
|
||||
}),
|
||||
token_secret: z.string().min(10, {
|
||||
message: "BookStack Token Secret is required.",
|
||||
}),
|
||||
});
|
||||
|
||||
// Define the type for the form values
|
||||
type BookStackConnectorFormValues = z.infer<typeof bookstackConnectorFormSchema>;
|
||||
|
||||
export default function BookStackConnectorPage() {
|
||||
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<BookStackConnectorFormValues>({
|
||||
resolver: zodResolver(bookstackConnectorFormSchema),
|
||||
defaultValues: {
|
||||
name: "BookStack Connector",
|
||||
base_url: "",
|
||||
token_id: "",
|
||||
token_secret: "",
|
||||
},
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (values: BookStackConnectorFormValues) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await createConnector(
|
||||
{
|
||||
name: values.name,
|
||||
connector_type: EnumConnectorName.BOOKSTACK_CONNECTOR,
|
||||
config: {
|
||||
BOOKSTACK_BASE_URL: values.base_url,
|
||||
BOOKSTACK_TOKEN_ID: values.token_id,
|
||||
BOOKSTACK_TOKEN_SECRET: values.token_secret,
|
||||
},
|
||||
is_indexable: true,
|
||||
last_indexed_at: null,
|
||||
periodic_indexing_enabled: false,
|
||||
indexing_frequency_minutes: null,
|
||||
next_scheduled_at: null,
|
||||
},
|
||||
parseInt(searchSpaceId)
|
||||
);
|
||||
|
||||
toast.success("BookStack connector created successfully!");
|
||||
|
||||
// Navigate back to connectors page
|
||||
router.push(`/dashboard/${searchSpaceId}/connectors`);
|
||||
} catch (error) {
|
||||
console.error("Error creating connector:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to create connector");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8 max-w-3xl">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="mb-6"
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/add`)}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Connectors
|
||||
</Button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
{getConnectorIcon(EnumConnectorName.BOOKSTACK_CONNECTOR, "h-6 w-6")}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Connect BookStack</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Connect your BookStack instance to search wiki pages.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Tabs defaultValue="connect" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2 mb-6">
|
||||
<TabsTrigger value="connect">Connect</TabsTrigger>
|
||||
<TabsTrigger value="documentation">Documentation</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="connect">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Connect to BookStack</CardTitle>
|
||||
<CardDescription>
|
||||
Connect your BookStack instance to index pages from your wiki.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
You'll need to create an API token from your BookStack instance.
|
||||
Go to <strong>Edit Profile → API Tokens → Create Token</strong>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Connector Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="My BookStack Connector" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A friendly name to identify this connector.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>BookStack Instance URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://docs.example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Your BookStack instance URL (e.g., https://wiki.yourcompany.com)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="token_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Token ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your BookStack Token ID" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The Token ID from your BookStack API token.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="token_secret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Token Secret</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Your BookStack Token Secret"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Your Token Secret will be encrypted and stored securely.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={isSubmitting} className="w-full sm:w-auto">
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
Connect BookStack
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="documentation">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>BookStack Integration Guide</CardTitle>
|
||||
<CardDescription>
|
||||
Learn how to set up and use the BookStack connector.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-3">What gets indexed?</h3>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm text-muted-foreground">
|
||||
<li>All pages from your BookStack instance</li>
|
||||
<li>Page content in Markdown format</li>
|
||||
<li>Page titles and metadata</li>
|
||||
<li>Book and chapter hierarchy information</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-3">Setup Instructions</h3>
|
||||
<ol className="list-decimal list-inside space-y-2 text-sm text-muted-foreground">
|
||||
<li>Log in to your BookStack instance</li>
|
||||
<li>Click on your profile icon → Edit Profile</li>
|
||||
<li>Navigate to the "API Tokens" tab</li>
|
||||
<li>Click "Create Token" and give it a name</li>
|
||||
<li>Copy both the Token ID and Token Secret</li>
|
||||
<li>Paste them in the form above</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-3">Permissions Required</h3>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm text-muted-foreground">
|
||||
<li>Your user account must have "Access System API" permission</li>
|
||||
<li>Read access to books and pages you want to index</li>
|
||||
<li>The connector will only index content your account can view</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
BookStack API has a rate limit of 180 requests per minute. The connector
|
||||
automatically handles rate limiting to ensure reliable indexing.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -128,6 +128,7 @@ export function DashboardBreadcrumb() {
|
|||
"github-connector": "GitHub",
|
||||
"jira-connector": "Jira",
|
||||
"confluence-connector": "Confluence",
|
||||
"bookstack-connector": "BookStack",
|
||||
"discord-connector": "Discord",
|
||||
"linear-connector": "Linear",
|
||||
"clickup-connector": "ClickUp",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ export const editConnectorSchema = z.object({
|
|||
CONFLUENCE_BASE_URL: z.string().optional(),
|
||||
CONFLUENCE_EMAIL: z.string().optional(),
|
||||
CONFLUENCE_API_TOKEN: z.string().optional(),
|
||||
BOOKSTACK_BASE_URL: z.string().optional(),
|
||||
BOOKSTACK_TOKEN_ID: z.string().optional(),
|
||||
BOOKSTACK_TOKEN_SECRET: z.string().optional(),
|
||||
JIRA_BASE_URL: z.string().optional(),
|
||||
JIRA_EMAIL: z.string().optional(),
|
||||
JIRA_API_TOKEN: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -123,6 +123,13 @@ export const connectorCategories: ConnectorCategory[] = [
|
|||
icon: getConnectorIcon(EnumConnectorName.CONFLUENCE_CONNECTOR, "h-6 w-6"),
|
||||
status: "available",
|
||||
},
|
||||
{
|
||||
id: "bookstack-connector",
|
||||
title: "BookStack",
|
||||
description: "bookstack_desc",
|
||||
icon: getConnectorIcon(EnumConnectorName.BOOKSTACK_CONNECTOR, "h-6 w-6"),
|
||||
status: "available",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export enum EnumConnectorName {
|
|||
JIRA_CONNECTOR = "JIRA_CONNECTOR",
|
||||
DISCORD_CONNECTOR = "DISCORD_CONNECTOR",
|
||||
CONFLUENCE_CONNECTOR = "CONFLUENCE_CONNECTOR",
|
||||
BOOKSTACK_CONNECTOR = "BOOKSTACK_CONNECTOR",
|
||||
CLICKUP_CONNECTOR = "CLICKUP_CONNECTOR",
|
||||
GOOGLE_CALENDAR_CONNECTOR = "GOOGLE_CALENDAR_CONNECTOR",
|
||||
GOOGLE_GMAIL_CONNECTOR = "GOOGLE_GMAIL_CONNECTOR",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
IconBook,
|
||||
IconBooks,
|
||||
IconBrandDiscord,
|
||||
IconBrandElastic,
|
||||
IconBrandGithub,
|
||||
|
|
@ -53,6 +54,8 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas
|
|||
return <IconTable {...iconProps} />;
|
||||
case EnumConnectorName.CONFLUENCE_CONNECTOR:
|
||||
return <IconBook {...iconProps} />;
|
||||
case EnumConnectorName.BOOKSTACK_CONNECTOR:
|
||||
return <IconBooks {...iconProps} />;
|
||||
case EnumConnectorName.CLICKUP_CONNECTOR:
|
||||
return <IconChecklist {...iconProps} />;
|
||||
case EnumConnectorName.LUMA_CONNECTOR:
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
|||
CONFLUENCE_BASE_URL: "",
|
||||
CONFLUENCE_EMAIL: "",
|
||||
CONFLUENCE_API_TOKEN: "",
|
||||
BOOKSTACK_BASE_URL: "",
|
||||
BOOKSTACK_TOKEN_ID: "",
|
||||
BOOKSTACK_TOKEN_SECRET: "",
|
||||
JIRA_BASE_URL: "",
|
||||
JIRA_EMAIL: "",
|
||||
JIRA_API_TOKEN: "",
|
||||
|
|
@ -139,6 +142,9 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
|||
CONFLUENCE_BASE_URL: config.CONFLUENCE_BASE_URL || "",
|
||||
CONFLUENCE_EMAIL: config.CONFLUENCE_EMAIL || "",
|
||||
CONFLUENCE_API_TOKEN: config.CONFLUENCE_API_TOKEN || "",
|
||||
BOOKSTACK_BASE_URL: config.BOOKSTACK_BASE_URL || "",
|
||||
BOOKSTACK_TOKEN_ID: config.BOOKSTACK_TOKEN_ID || "",
|
||||
BOOKSTACK_TOKEN_SECRET: config.BOOKSTACK_TOKEN_SECRET || "",
|
||||
JIRA_BASE_URL: config.JIRA_BASE_URL || "",
|
||||
JIRA_EMAIL: config.JIRA_EMAIL || "",
|
||||
JIRA_API_TOKEN: config.JIRA_API_TOKEN || "",
|
||||
|
|
@ -435,6 +441,28 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
|||
};
|
||||
}
|
||||
break;
|
||||
case "BOOKSTACK_CONNECTOR":
|
||||
if (
|
||||
formData.BOOKSTACK_BASE_URL !== originalConfig.BOOKSTACK_BASE_URL ||
|
||||
formData.BOOKSTACK_TOKEN_ID !== originalConfig.BOOKSTACK_TOKEN_ID ||
|
||||
formData.BOOKSTACK_TOKEN_SECRET !== originalConfig.BOOKSTACK_TOKEN_SECRET
|
||||
) {
|
||||
if (
|
||||
!formData.BOOKSTACK_BASE_URL ||
|
||||
!formData.BOOKSTACK_TOKEN_ID ||
|
||||
!formData.BOOKSTACK_TOKEN_SECRET
|
||||
) {
|
||||
toast.error("All BookStack fields are required.");
|
||||
setIsSaving(false);
|
||||
return;
|
||||
}
|
||||
newConfig = {
|
||||
BOOKSTACK_BASE_URL: formData.BOOKSTACK_BASE_URL,
|
||||
BOOKSTACK_TOKEN_ID: formData.BOOKSTACK_TOKEN_ID,
|
||||
BOOKSTACK_TOKEN_SECRET: formData.BOOKSTACK_TOKEN_SECRET,
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "JIRA_CONNECTOR":
|
||||
if (
|
||||
formData.JIRA_BASE_URL !== originalConfig.JIRA_BASE_URL ||
|
||||
|
|
@ -584,6 +612,10 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
|||
editForm.setValue("CONFLUENCE_BASE_URL", newlySavedConfig.CONFLUENCE_BASE_URL || "");
|
||||
editForm.setValue("CONFLUENCE_EMAIL", newlySavedConfig.CONFLUENCE_EMAIL || "");
|
||||
editForm.setValue("CONFLUENCE_API_TOKEN", newlySavedConfig.CONFLUENCE_API_TOKEN || "");
|
||||
} else if (connector.connector_type === "BOOKSTACK_CONNECTOR") {
|
||||
editForm.setValue("BOOKSTACK_BASE_URL", newlySavedConfig.BOOKSTACK_BASE_URL || "");
|
||||
editForm.setValue("BOOKSTACK_TOKEN_ID", newlySavedConfig.BOOKSTACK_TOKEN_ID || "");
|
||||
editForm.setValue("BOOKSTACK_TOKEN_SECRET", newlySavedConfig.BOOKSTACK_TOKEN_SECRET || "");
|
||||
} else if (connector.connector_type === "JIRA_CONNECTOR") {
|
||||
editForm.setValue("JIRA_BASE_URL", newlySavedConfig.JIRA_BASE_URL || "");
|
||||
editForm.setValue("JIRA_EMAIL", newlySavedConfig.JIRA_EMAIL || "");
|
||||
|
|
|
|||
|
|
@ -330,6 +330,7 @@
|
|||
"notion_desc": "Connect to your Notion workspace to access pages and databases.",
|
||||
"github_desc": "Connect a GitHub PAT to index code and docs from accessible repositories.",
|
||||
"confluence_desc": "Connect to Confluence to search pages, comments and documentation.",
|
||||
"bookstack_desc": "Connect to BookStack to search wiki pages and documentation.",
|
||||
"airtable_desc": "Connect to Airtable to search records, tables and database content.",
|
||||
"luma_desc": "Connect to Luma to search events, meetups and gatherings.",
|
||||
"calendar_desc": "Connect to Google Calendar to search events, meetings and schedules.",
|
||||
|
|
|
|||
|
|
@ -330,6 +330,7 @@
|
|||
"notion_desc": "连接到您的 Notion 工作区以访问页面和数据库。",
|
||||
"github_desc": "连接 GitHub PAT 以索引可访问存储库的代码和文档。",
|
||||
"confluence_desc": "连接到 Confluence 以搜索页面、评论和文档。",
|
||||
"bookstack_desc": "连接到 BookStack 以搜索 Wiki 页面和文档。",
|
||||
"airtable_desc": "连接到 Airtable 以搜索记录、表格和数据库内容。",
|
||||
"luma_desc": "连接到 Luma 以搜索活动、聚会和集会。",
|
||||
"calendar_desc": "连接到 Google 日历以搜索活动、会议和日程。",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue