mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
refactor: streamline authentication handling and UI feedback for connectors showing re-authentication button in the config edit area as well
This commit is contained in:
parent
283b4194cc
commit
f938c4018e
3 changed files with 111 additions and 150 deletions
|
|
@ -1,7 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
|
@ -11,15 +9,11 @@ import {
|
||||||
FolderClosed,
|
FolderClosed,
|
||||||
Image,
|
Image,
|
||||||
Presentation,
|
Presentation,
|
||||||
RefreshCw,
|
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
|
||||||
import { ComposioDriveFolderTree } from "@/components/connectors/composio-drive-folder-tree";
|
import { ComposioDriveFolderTree } from "@/components/connectors/composio-drive-folder-tree";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -29,7 +23,6 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
|
||||||
import type { ConnectorConfigProps } from "../index";
|
import type { ConnectorConfigProps } from "../index";
|
||||||
|
|
||||||
interface SelectedFolder {
|
interface SelectedFolder {
|
||||||
|
|
@ -89,13 +82,10 @@ function getFileIconFromName(fileName: string, className: string = "size-3.5 shr
|
||||||
return <File className={`${className} text-gray-500`} />;
|
return <File className={`${className} text-gray-500`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMPOSIO_REAUTH_ENDPOINT = "/api/v1/auth/composio/connector/reauth";
|
|
||||||
|
|
||||||
export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({
|
export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({
|
||||||
connector,
|
connector,
|
||||||
onConfigChange,
|
onConfigChange,
|
||||||
}) => {
|
}) => {
|
||||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
|
||||||
const isIndexable = connector.config?.is_indexable as boolean;
|
const isIndexable = connector.config?.is_indexable as boolean;
|
||||||
|
|
||||||
const existingFolders =
|
const existingFolders =
|
||||||
|
|
@ -107,40 +97,10 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({
|
||||||
const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders);
|
const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles);
|
const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles);
|
||||||
const [indexingOptions, setIndexingOptions] = useState<IndexingOptions>(existingIndexingOptions);
|
const [indexingOptions, setIndexingOptions] = useState<IndexingOptions>(existingIndexingOptions);
|
||||||
const [reauthing, setReauthing] = useState(false);
|
|
||||||
const [authError, setAuthError] = useState(false);
|
const [authError, setAuthError] = useState(false);
|
||||||
|
|
||||||
const isAuthExpired = connector.config?.auth_expired === true || authError;
|
const isAuthExpired = connector.config?.auth_expired === true || authError;
|
||||||
|
|
||||||
const handleReauth = useCallback(async () => {
|
|
||||||
if (!searchSpaceId) return;
|
|
||||||
setReauthing(true);
|
|
||||||
try {
|
|
||||||
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
|
||||||
const url = new URL(`${backendUrl}${COMPOSIO_REAUTH_ENDPOINT}`);
|
|
||||||
url.searchParams.set("connector_id", String(connector.id));
|
|
||||||
url.searchParams.set("space_id", String(searchSpaceId));
|
|
||||||
url.searchParams.set("return_url", window.location.pathname);
|
|
||||||
const response = await authenticatedFetch(url.toString());
|
|
||||||
if (!response.ok) {
|
|
||||||
const data = await response.json().catch(() => ({}));
|
|
||||||
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.auth_url) {
|
|
||||||
window.location.href = data.auth_url;
|
|
||||||
} else if (data.success) {
|
|
||||||
toast.success("Authentication refreshed successfully.");
|
|
||||||
setAuthError(false);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.error("Failed to initiate re-authentication.");
|
|
||||||
} finally {
|
|
||||||
setReauthing(false);
|
|
||||||
}
|
|
||||||
}, [searchSpaceId, connector.id]);
|
|
||||||
|
|
||||||
const handleAuthError = useCallback(() => {
|
const handleAuthError = useCallback(() => {
|
||||||
setAuthError(true);
|
setAuthError(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -276,24 +236,13 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isAuthExpired && (
|
{isAuthExpired && (
|
||||||
<div className="flex items-center gap-2">
|
<p className="text-xs text-amber-600 dark:text-amber-500">
|
||||||
<p className="text-xs text-amber-600 dark:text-amber-500">
|
Your Google Drive authentication has expired. Please re-authenticate using the button below.
|
||||||
Your Google Drive authentication has expired.
|
</p>
|
||||||
</p>
|
)}
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="h-7 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
|
||||||
onClick={handleReauth}
|
|
||||||
disabled={reauthing}
|
|
||||||
>
|
|
||||||
<RefreshCw className={cn("size-3.5", reauthing && "animate-spin")} />
|
|
||||||
Re-authenticate
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isEditMode ? (
|
{isEditMode ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import {
|
import {
|
||||||
|
|
||||||
File,
|
File,
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
FileText,
|
FileText,
|
||||||
FolderClosed,
|
FolderClosed,
|
||||||
Image,
|
Image,
|
||||||
Presentation,
|
Presentation,
|
||||||
RefreshCw,
|
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
|
|
@ -28,7 +22,6 @@ import {
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
|
||||||
import { type PickerResult, useGooglePicker } from "@/hooks/use-google-picker";
|
import { type PickerResult, useGooglePicker } from "@/hooks/use-google-picker";
|
||||||
import type { ConnectorConfigProps } from "../index";
|
import type { ConnectorConfigProps } from "../index";
|
||||||
|
|
||||||
|
|
@ -89,10 +82,7 @@ function getFileIconFromName(fileName: string, className: string = "size-3.5 shr
|
||||||
return <File className={`${className} text-gray-500`} />;
|
return <File className={`${className} text-gray-500`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DRIVE_REAUTH_ENDPOINT = "/api/v1/auth/google/drive/connector/reauth";
|
|
||||||
|
|
||||||
export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
|
export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
|
||||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
|
||||||
const existingFolders = (connector.config?.selected_folders as SelectedItem[] | undefined) || [];
|
const existingFolders = (connector.config?.selected_folders as SelectedItem[] | undefined) || [];
|
||||||
const existingFiles = (connector.config?.selected_files as SelectedItem[] | undefined) || [];
|
const existingFiles = (connector.config?.selected_files as SelectedItem[] | undefined) || [];
|
||||||
const existingIndexingOptions =
|
const existingIndexingOptions =
|
||||||
|
|
@ -101,33 +91,6 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
|
||||||
const [selectedFolders, setSelectedFolders] = useState<SelectedItem[]>(existingFolders);
|
const [selectedFolders, setSelectedFolders] = useState<SelectedItem[]>(existingFolders);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<SelectedItem[]>(existingFiles);
|
const [selectedFiles, setSelectedFiles] = useState<SelectedItem[]>(existingFiles);
|
||||||
const [indexingOptions, setIndexingOptions] = useState<IndexingOptions>(existingIndexingOptions);
|
const [indexingOptions, setIndexingOptions] = useState<IndexingOptions>(existingIndexingOptions);
|
||||||
const [reauthing, setReauthing] = useState(false);
|
|
||||||
|
|
||||||
const handleReauth = useCallback(async () => {
|
|
||||||
if (!searchSpaceId) return;
|
|
||||||
setReauthing(true);
|
|
||||||
try {
|
|
||||||
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
|
||||||
const url = new URL(`${backendUrl}${DRIVE_REAUTH_ENDPOINT}`);
|
|
||||||
url.searchParams.set("connector_id", String(connector.id));
|
|
||||||
url.searchParams.set("space_id", String(searchSpaceId));
|
|
||||||
url.searchParams.set("return_url", window.location.pathname);
|
|
||||||
const response = await authenticatedFetch(url.toString());
|
|
||||||
if (!response.ok) {
|
|
||||||
const data = await response.json().catch(() => ({}));
|
|
||||||
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.auth_url) {
|
|
||||||
window.location.href = data.auth_url;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.error("Failed to initiate re-authentication.");
|
|
||||||
} finally {
|
|
||||||
setReauthing(false);
|
|
||||||
}
|
|
||||||
}, [searchSpaceId, connector.id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const folders = (connector.config?.selected_folders as SelectedItem[] | undefined) || [];
|
const folders = (connector.config?.selected_folders as SelectedItem[] | undefined) || [];
|
||||||
|
|
@ -268,40 +231,26 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={openPicker}
|
onClick={openPicker}
|
||||||
disabled={pickerLoading || isAuthExpired}
|
disabled={pickerLoading || isAuthExpired}
|
||||||
className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-white/10 text-xs sm:text-sm h-8 sm:h-9"
|
className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-white/10 text-xs sm:text-sm h-8 sm:h-9"
|
||||||
>
|
>
|
||||||
{pickerLoading && <Spinner size="xs" className="mr-1.5" />}
|
{pickerLoading && <Spinner size="xs" className="mr-1.5" />}
|
||||||
{totalSelected > 0 ? "Change Selection" : "Select from Google Drive"}
|
{totalSelected > 0 ? "Change Selection" : "Select from Google Drive"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{(pickerError || isAuthExpired) && (
|
{pickerError && !isAuthExpired && (
|
||||||
<div className="flex flex-col gap-2">
|
<p className="text-xs text-destructive">{pickerError}</p>
|
||||||
{pickerError && !isAuthExpired && (
|
)}
|
||||||
<p className="text-xs text-destructive">{pickerError}</p>
|
|
||||||
)}
|
{isAuthExpired && (
|
||||||
{isAuthExpired && (
|
<p className="text-xs text-amber-600 dark:text-amber-500">
|
||||||
<div className="flex items-center gap-2">
|
Your Google Drive authentication has expired. Please re-authenticate using the button below.
|
||||||
<p className="text-xs text-amber-600 dark:text-amber-500">
|
</p>
|
||||||
Your Google Drive authentication has expired.
|
)}
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="h-7 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
|
||||||
onClick={handleReauth}
|
|
||||||
disabled={reauthing}
|
|
||||||
>
|
|
||||||
<RefreshCw className={cn("size-3.5", reauthing && "animate-spin")} />
|
|
||||||
Re-authenticate
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Indexing Options */}
|
{/* Indexing Options */}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
import { ArrowLeft, Info, RefreshCw, Trash2 } from "lucide-react";
|
import { ArrowLeft, Info, RefreshCw, Trash2 } from "lucide-react";
|
||||||
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { DateRangeSelector } from "../../components/date-range-selector";
|
import { DateRangeSelector } from "../../components/date-range-selector";
|
||||||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||||
|
|
@ -13,6 +18,17 @@ import { SummaryConfig } from "../../components/summary-config";
|
||||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||||
import { getConnectorConfigComponent } from "../index";
|
import { getConnectorConfigComponent } from "../index";
|
||||||
|
|
||||||
|
const REAUTH_ENDPOINTS: Partial<Record<string, string>> = {
|
||||||
|
[EnumConnectorName.LINEAR_CONNECTOR]: "/api/v1/auth/linear/connector/reauth",
|
||||||
|
[EnumConnectorName.NOTION_CONNECTOR]: "/api/v1/auth/notion/connector/reauth",
|
||||||
|
[EnumConnectorName.GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/google/drive/connector/reauth",
|
||||||
|
[EnumConnectorName.GOOGLE_GMAIL_CONNECTOR]: "/api/v1/auth/google/gmail/connector/reauth",
|
||||||
|
[EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/google/calendar/connector/reauth",
|
||||||
|
[EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/composio/connector/reauth",
|
||||||
|
[EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR]: "/api/v1/auth/composio/connector/reauth",
|
||||||
|
[EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/composio/connector/reauth",
|
||||||
|
};
|
||||||
|
|
||||||
interface ConnectorEditViewProps {
|
interface ConnectorEditViewProps {
|
||||||
connector: SearchSourceConnector;
|
connector: SearchSourceConnector;
|
||||||
startDate: Date | undefined;
|
startDate: Date | undefined;
|
||||||
|
|
@ -60,6 +76,41 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||||
onConfigChange,
|
onConfigChange,
|
||||||
onNameChange,
|
onNameChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const searchSpaceIdAtom = useAtomValue(activeSearchSpaceIdAtom);
|
||||||
|
const isAuthExpired = connector.config?.auth_expired === true;
|
||||||
|
const reauthEndpoint = REAUTH_ENDPOINTS[connector.connector_type];
|
||||||
|
const [reauthing, setReauthing] = useState(false);
|
||||||
|
|
||||||
|
const handleReauth = useCallback(async () => {
|
||||||
|
const spaceId = searchSpaceId ?? searchSpaceIdAtom;
|
||||||
|
if (!spaceId || !reauthEndpoint) return;
|
||||||
|
setReauthing(true);
|
||||||
|
try {
|
||||||
|
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||||
|
const url = new URL(`${backendUrl}${reauthEndpoint}`);
|
||||||
|
url.searchParams.set("connector_id", String(connector.id));
|
||||||
|
url.searchParams.set("space_id", String(spaceId));
|
||||||
|
url.searchParams.set("return_url", window.location.pathname);
|
||||||
|
const response = await authenticatedFetch(url.toString());
|
||||||
|
if (!response.ok) {
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.auth_url) {
|
||||||
|
window.location.href = data.auth_url;
|
||||||
|
} else if (data.success) {
|
||||||
|
toast.success(data.message ?? "Authentication refreshed successfully.");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
toast.error("Failed to initiate re-authentication.");
|
||||||
|
} finally {
|
||||||
|
setReauthing(false);
|
||||||
|
}
|
||||||
|
}, [searchSpaceId, searchSpaceIdAtom, reauthEndpoint, connector.id]);
|
||||||
|
|
||||||
// Get connector-specific config component
|
// Get connector-specific config component
|
||||||
const ConnectorConfigComponent = useMemo(
|
const ConnectorConfigComponent = useMemo(
|
||||||
() => getConnectorConfigComponent(connector.connector_type),
|
() => getConnectorConfigComponent(connector.connector_type),
|
||||||
|
|
@ -169,29 +220,30 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Quick Index Button - shown for indexable connectors that have been configured */}
|
{/* Quick Index Button - hidden when auth is expired */}
|
||||||
{connector.is_indexable &&
|
{connector.is_indexable &&
|
||||||
onQuickIndex && (
|
onQuickIndex &&
|
||||||
<Button
|
!isAuthExpired && (
|
||||||
variant="secondary"
|
<Button
|
||||||
size="sm"
|
variant="secondary"
|
||||||
onClick={handleQuickIndex}
|
size="sm"
|
||||||
disabled={isQuickIndexing || isIndexing || isSaving || isDisconnecting}
|
onClick={handleQuickIndex}
|
||||||
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
|
disabled={isQuickIndexing || isIndexing || isSaving || isDisconnecting}
|
||||||
>
|
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
|
||||||
{isQuickIndexing || isIndexing ? (
|
>
|
||||||
<>
|
{isQuickIndexing || isIndexing ? (
|
||||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
<>
|
||||||
Syncing
|
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||||
</>
|
Syncing
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
<>
|
||||||
Quick Index
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
</>
|
Quick Index
|
||||||
)}
|
</>
|
||||||
</Button>
|
)}
|
||||||
)}
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -349,6 +401,16 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||||
Disconnect
|
Disconnect
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{isAuthExpired && reauthEndpoint ? (
|
||||||
|
<Button
|
||||||
|
onClick={handleReauth}
|
||||||
|
disabled={reauthing || isDisconnecting}
|
||||||
|
className="text-xs sm:text-sm flex-1 sm:flex-initial h-12 sm:h-auto py-3 sm:py-2 bg-amber-600 hover:bg-amber-700 text-white"
|
||||||
|
>
|
||||||
|
<RefreshCw className={cn("size-3.5", reauthing && "animate-spin")} />
|
||||||
|
Re-authenticate
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
disabled={isSaving || isDisconnecting}
|
disabled={isSaving || isDisconnecting}
|
||||||
|
|
@ -363,6 +425,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||||
"Save Changes"
|
"Save Changes"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue