diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx index e63812423..9946f244f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx @@ -1,10 +1,20 @@ "use client"; -import { Check, Copy, Info, Plus, Trash2 } from "lucide-react"; +import { Check, Copy, Info, Trash2 } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Badge } from "@/components/ui/badge"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, @@ -16,6 +26,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; +import { Spinner } from "@/components/ui/spinner"; import { usePats } from "@/hooks/use-pats"; import { copyToClipboard as copyToClipboardUtil } from "@/lib/utils"; @@ -26,6 +37,7 @@ export function ApiKeyContent() { const [label, setLabel] = useState(""); const [expiresInDays, setExpiresInDays] = useState(""); const [copiedToken, setCopiedToken] = useState(false); + const [deleteTarget, setDeleteTarget] = useState<{ id: number; label: string } | null>(null); const sortedTokens = useMemo(() => tokens, [tokens]); @@ -51,93 +63,112 @@ export function ApiKeyContent() { } }, [createdToken]); - const handleDelete = useCallback( - async (id: number, tokenLabel: string) => { - if (!window.confirm(`Delete personal access token "${tokenLabel}"? This cannot be undone.`)) { - return; - } - await deleteToken(id); - }, - [deleteToken] - ); + const handleConfirmDelete = useCallback(async () => { + if (!deleteTarget) return; + + await deleteToken(deleteTarget.id); + setDeleteTarget(null); + }, [deleteTarget, deleteToken]); return ( -
+
- Personal access tokens are long-lived credentials for extensions, Obsidian, and - programmatic API clients. Copy a token when you create it; it is shown only once. + API keys let extensions, Obsidian, and other apps connect to SurfSense.
-

Personal access tokens

+

API keys

- Expired tokens stay listed until you delete them. + Expired API keys stay listed until you delete them.

-
- {isLoading ? ( -
- - -
- ) : sortedTokens.length > 0 ? ( -
- {sortedTokens.map((token) => { - const expiresAt = token.expires_at ? new Date(token.expires_at) : null; - const isExpired = expiresAt ? expiresAt.getTime() <= Date.now() : false; - return ( -
+ {isLoading ? ( +
+ {["skeleton-a", "skeleton-b"].map((key) => ( + + + + + + + + ))} +
+ ) : sortedTokens.length > 0 ? ( +
+ {sortedTokens.map((token) => { + const expiresAt = token.expires_at ? new Date(token.expires_at) : null; + const isExpired = expiresAt ? expiresAt.getTime() <= Date.now() : false; + return ( + +
-
-

{token.label}

- {isExpired ? Expired : null} +
+
+

+ {token.label} +

+ {isExpired ? ( + + Expired + + ) : null} +
+

+ {token.prefix}... +

+

+ Expires: {expiresAt ? expiresAt.toLocaleDateString() : "Never"} · Last used:{" "} + {token.last_used_at ? new Date(token.last_used_at).toLocaleString() : "Never"} +

-

{token.prefix}...

-

- Expires: {expiresAt ? expiresAt.toLocaleDateString() : "Never"} · Last used:{" "} - {token.last_used_at ? new Date(token.last_used_at).toLocaleString() : "Never"} -

-
- ); - })} -
- ) : ( -

- No personal access tokens yet. -

- )} -
+ + + ); + })} +
+ ) : ( +

+ No API keys yet. +

+ )} - Create personal access token + Create API key - Name this token so you can recognize where it is used later. + Name this API key so you can recognize where it is used later.
- +
- - @@ -171,16 +215,21 @@ export function ApiKeyContent() { !open && setCreatedToken(null)}> - Copy your token now + Copy your API key now - This token is shown only once. Store it somewhere secure before closing this dialog. + This API key is shown only once. Store it somewhere secure before closing this dialog.
{createdToken?.token} -
@@ -189,6 +238,41 @@ export function ApiKeyContent() {
+ + !open && setDeleteTarget(null)} + > + + + Delete API key? + + {deleteTarget?.label} will be + permanently removed. This cannot be undone. + + + + Cancel + { + event.preventDefault(); + void handleConfirmDelete(); + }} + > + {isMutating ? ( + + + Deleting... + + ) : ( + "Delete" + )} + + + +
); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx index 56044de5b..f4454f343 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent.tsx @@ -38,13 +38,13 @@ export function CommunityPromptsContent() { const list = prompts ?? []; return ( -
+

Prompts shared by other users. Add any to your collection with one click.

{isLoading && ( -
+
{["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( @@ -76,7 +76,7 @@ export function CommunityPromptsContent() { )} {!isLoading && !isError && list.length > 0 && ( -
+
{list.map((prompt) => ( +

Create prompt templates triggered with in @@ -276,7 +276,7 @@ export function PromptsContent() {

{isLoading && ( -
+
{["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( @@ -308,7 +308,7 @@ export function PromptsContent() { )} {!isLoading && !isError && list.length > 0 && ( -
+
{list.map((prompt) => (
({ toast.success("Public link deleted"); }, onError: (error: Error) => { - console.error("Failed to delete public chat link:", error); + console.error("Failed to delete public chat:", error); toast.error("Failed to delete public link"); }, })); diff --git a/surfsense_web/changelog/content/2026-02-09.mdx b/surfsense_web/changelog/content/2026-02-09.mdx index 3bbc6f45e..7ffef2b4a 100644 --- a/surfsense_web/changelog/content/2026-02-09.mdx +++ b/surfsense_web/changelog/content/2026-02-09.mdx @@ -15,9 +15,9 @@ This update brings **public sharing, image generation**, a redesigned Documents #### Public Sharing -- **Public Chat Links**: Share snapshots of chats via public links. +- **Public Chats**: Share snapshots of chats via public links. - **Sharing Permissions**: Search Space owners control who can create and manage public links. -- **Link Management Page**: View and revoke all public chat links from Search Space Settings. +- **Link Management Page**: View and revoke all public chats from Search Space Settings. #### Auto (Load Balanced) Mode diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-empty-state.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-empty-state.tsx index 4e8ec5bb6..e8e8b6b12 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-empty-state.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-empty-state.tsx @@ -1,12 +1,10 @@ -import { Link2Off } from "lucide-react"; - interface PublicChatSnapshotsEmptyStateProps { title?: string; description?: string; } export function PublicChatSnapshotsEmptyState({ - title = "No public chat links", + title = "No public chats", description = "When you create public links to share chats, they will appear here.", }: PublicChatSnapshotsEmptyStateProps) { return ( diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx index 3cf07c27a..f18a0f705 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx @@ -115,7 +115,7 @@ export function PublicChatSnapshotsManager({ - Failed to load public chat links. Please try again later. + Failed to load public chats. Please try again later. ); @@ -127,7 +127,7 @@ export function PublicChatSnapshotsManager({ - You don't have permission to view public chat links in this search space. + You don't have permission to view public chats in this search space. ); @@ -140,8 +140,8 @@ export function PublicChatSnapshotsManager({ - Public chat links allow anyone with the URL to view a snapshot of a chat. These links do - not update when the original chat changes. + Public chats allow anyone with the URL to view a snapshot of a chat. They do not update + when the original chat changes. diff --git a/surfsense_web/components/settings/general-settings-manager.tsx b/surfsense_web/components/settings/general-settings-manager.tsx index 113d9d754..d0c08d881 100644 --- a/surfsense_web/components/settings/general-settings-manager.tsx +++ b/surfsense_web/components/settings/general-settings-manager.tsx @@ -208,10 +208,9 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager
- +

- Allow personal access tokens to use this search space. Web and desktop sessions are not - affected. + Allow API keys to access this search space.