mirror of
https://github.com/khoj-ai/khoj.git
synced 2025-02-17 08:04:21 +00: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,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} 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 NavMenu from "../components/navMenu/navMenu";
|
||||||
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
|
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() {
|
export default function SettingsView() {
|
||||||
const [title, setTitle] = useState("Settings");
|
const [title, setTitle] = useState("Settings");
|
||||||
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
||||||
|
const { apiKeys, generateAPIKey, copyAPIKey, deleteAPIKey } = useApiKeys();
|
||||||
const userConfig = useUserConfig(true);
|
const userConfig = useUserConfig(true);
|
||||||
const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg";
|
const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg";
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
@ -219,6 +300,49 @@ export default function SettingsView() {
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</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*',
|
source: '/api/:path*',
|
||||||
destination: 'http://localhost:42110/api/:path*',
|
destination: 'http://localhost:42110/api/:path*',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/auth/:path*',
|
||||||
|
destination: 'http://localhost:42110/auth/:path*',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
trailingSlash: true,
|
trailingSlash: true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue