mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-23 23:48:56 +01:00
Create client API keys section on settings page
- Add table shadcn component to use in API keys settings section - In dev mode, route requests to auth to khoj server at localhost:42110
This commit is contained in:
parent
00fa4fa0fa
commit
2e165a0e0a
3 changed files with 246 additions and 1 deletions
|
@ -20,8 +20,16 @@ import {
|
|||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
|
||||
import { ArrowRight, ChatCircleText, Key, Palette, SpeakerHigh, UserCircle, FileMagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { ArrowRight, ChatCircleText, Key, Palette, SpeakerHigh, UserCircle, FileMagnifyingGlass, Trash, Copy, PlusCircle } from "@phosphor-icons/react";
|
||||
|
||||
import NavMenu from "../components/navMenu/navMenu";
|
||||
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
|
||||
|
@ -62,9 +70,82 @@ const DropdownComponent: React.FC<DropdownComponentProps> = ({ items, selected,
|
|||
);
|
||||
}
|
||||
|
||||
interface TokenObject {
|
||||
token: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const useApiKeys = () => {
|
||||
const [apiKeys, setApiKeys] = useState<TokenObject[]>([]);
|
||||
const { toast } = useToast();
|
||||
|
||||
const generateAPIKey = async () => {
|
||||
try {
|
||||
const response = await fetch(`/auth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const tokenObj = await response.json();
|
||||
setApiKeys(prevKeys => [...prevKeys, tokenObj]);
|
||||
} catch (error) {
|
||||
console.error('Error generating API key:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const copyAPIKey = async (token: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(token);
|
||||
toast({
|
||||
title: "🔑 API Key",
|
||||
description: "Copied to clipboard",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error copying API key:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAPIKey = async (token: string) => {
|
||||
try {
|
||||
const response = await fetch(`/auth/token?token=${token}`, { method: 'DELETE' });
|
||||
if (response.ok) {
|
||||
setApiKeys(prevKeys => prevKeys.filter(key => key.token !== token));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting API key:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const listApiKeys = async () => {
|
||||
try {
|
||||
const response = await fetch(`/auth/token`);
|
||||
const tokens = await response.json();
|
||||
if (tokens?.length > 0) {
|
||||
setApiKeys(tokens);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error listing API keys:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
listApiKeys();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
apiKeys,
|
||||
generateAPIKey,
|
||||
copyAPIKey,
|
||||
deleteAPIKey,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export default function SettingsView() {
|
||||
const [title, setTitle] = useState("Settings");
|
||||
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
||||
const { apiKeys, generateAPIKey, copyAPIKey, deleteAPIKey } = useApiKeys();
|
||||
const userConfig = useUserConfig(true);
|
||||
const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg";
|
||||
const { toast } = useToast();
|
||||
|
@ -219,6 +300,49 @@ export default function SettingsView() {
|
|||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section grid gap-8">
|
||||
<div className="text-4xl">Clients</div>
|
||||
<div className="cards flex flex-wrap gap-16">
|
||||
<Card className="grid grid-flow-column border border-gray-300 shadow-md rounded-lg">
|
||||
<CardHeader className="text-xl flex flex-row"><Key className="h-7 w-7 mr-2"/>API Keys</CardHeader>
|
||||
<CardContent className="overflow-hidden">
|
||||
<p className="text-md text-gray-400">
|
||||
Access Khoj from your Desktop App, Obsidian plugin, and more.
|
||||
</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Token</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiKeys.map((key) => (
|
||||
<TableRow key={key.token}>
|
||||
<TableCell><b>{key.name}</b></TableCell>
|
||||
<TableCell>{`${key.token.slice(0, 4)}...${key.token.slice(-4)}`}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="outline" className="border border-green-400" onClick={() => copyAPIKey(key.token)}>
|
||||
<Copy className="h-4 w-4 mr-2" /><span className="hidden md:inline">Copy</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="ml-4 border border-red-400" onClick={() => deleteAPIKey(key.token)}>
|
||||
<Trash className='h-4 w-4 mr-2' /><span className="hidden md:inline">Delete</span>
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-4">
|
||||
<Button variant="outline" className="border border-green-300" onClick={generateAPIKey}>
|
||||
<PlusCircle className='h-4 w-4 mr-2' />Generate Key
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
|
117
src/interface/web/components/ui/table.tsx
Normal file
117
src/interface/web/components/ui/table.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
|
@ -10,6 +10,10 @@ const nextConfig = {
|
|||
source: '/api/:path*',
|
||||
destination: 'http://localhost:42110/api/:path*',
|
||||
},
|
||||
{
|
||||
source: '/auth/:path*',
|
||||
destination: 'http://localhost:42110/auth/:path*',
|
||||
},
|
||||
];
|
||||
},
|
||||
trailingSlash: true,
|
||||
|
|
Loading…
Reference in a new issue