diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 528cfbe9e..a5b67ae7d 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -5,6 +5,7 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { FolderInput, FolderPlus, + FolderSync, ListFilter, Plus, Settings2, @@ -32,9 +33,12 @@ import type { FolderDisplay } from "@/components/documents/FolderNode"; import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog"; import { FolderTreeView } from "@/components/documents/FolderTreeView"; import { VersionHistoryDialog } from "@/components/documents/version-history"; -import { useIsSelfHosted, useRuntimeConfig } from "@/components/providers/runtime-config"; +import { useOptionalRuntimeConfig, useRuntimeConfig } from "@/components/providers/runtime-config"; import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems"; -import { DEFAULT_EXCLUDE_PATTERNS } from "@/components/sources/FolderWatchDialog"; +import { + DEFAULT_EXCLUDE_PATTERNS, + FolderWatchDialog, +} from "@/components/sources/FolderWatchDialog"; import { AlertDialog, AlertDialogAction, @@ -187,13 +191,27 @@ export function EmbeddedDocumentsMenu({ * OAuth or open the existing account's config. In anonymous mode, `gate` * intercepts every item to trigger the login flow. */ -export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void }) { +export function EmbeddedImportMenu({ + gate, + onFolderWatched, +}: { + gate?: (feature: string) => void; + onFolderWatched?: () => void; +}) { const { openDialog } = useDocumentUploadDialog(); const setImportRequest = useSetAtom(importConnectorRequestAtom); - const selfHosted = useIsSelfHosted(); + // Provider is absent on anonymous /free pages, where every item is login-gated + // anyway — defaulting to hosted (Composio) there is cosmetic. + const selfHosted = useOptionalRuntimeConfig()?.deploymentMode === "self-hosted"; const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus(); const { data: connectors } = useAtomValue(connectorsAtom); + // Watch Local Folder is a desktop-app feature (needs the Electron folder watcher). + const { isDesktop } = usePlatform(); + const params = useParams(); + const workspaceId = getWorkspaceIdNumber(params) ?? 0; + const [folderWatchOpen, setFolderWatchOpen] = useState(false); + // Native Google Drive connector self-hosted only; hosted deployments use Composio. const driveType = selfHosted ? EnumConnectorName.GOOGLE_DRIVE_CONNECTOR @@ -223,6 +241,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void Upload Files + {isDesktop && ( + (gate ? gate("watch local folders") : setFolderWatchOpen(true))} + > + + Watch Local Folder + + )} {cloudItems.map((item) => { const enabled = gate ? true : isConnectorEnabled(item.type); @@ -297,6 +323,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void ); })} + {isDesktop && !gate && ( + + )} ); } @@ -1180,7 +1214,7 @@ function AuthenticatedDocumentsSidebarBase({ contentClassName="px-0" persistentAction={
- + { + if (session.status === "unauthenticated") handleUnauthorized(); + }, [session.status]); + if (session.status !== "authenticated") { return null; } diff --git a/surfsense_web/components/providers/runtime-config.tsx b/surfsense_web/components/providers/runtime-config.tsx index 560acd597..574573f6f 100644 --- a/surfsense_web/components/providers/runtime-config.tsx +++ b/surfsense_web/components/providers/runtime-config.tsx @@ -31,6 +31,11 @@ export function useRuntimeConfig() { return context; } +/** For components that may render outside RuntimeConfigProvider (e.g. anonymous /free pages). */ +export function useOptionalRuntimeConfig(): RuntimeConfigValue | null { + return useContext(RuntimeConfigContext); +} + export function useIsLocalAuth() { return useRuntimeConfig().authType === "LOCAL"; } diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts index 0cc5224e2..31341a9e6 100644 --- a/surfsense_web/lib/apis/base-api.service.ts +++ b/surfsense_web/lib/apis/base-api.service.ts @@ -108,6 +108,9 @@ class BaseApiService { const refreshRetryKey = getRefreshRetryKey(mergedOptions.method, url); if (this.isDesktopClient && !desktopAccessToken && !isNoAuthEndpoint) { + // Desktop refresh token is gone/revoked — send the user to /desktop/login + // (same treatment as a server 401 below) instead of erroring in place. + handleUnauthorized(); throw new AuthenticationError("You are not authenticated. Please login again."); }