mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
refactor(frontend): update settings pages fetching using react query (#10248)
### Changes 🏗️ - We have implemented some backend changes, so I have added a new, updated OpenAPI specification. - We have updated the settings and API keys page to enable us to use React Query for fetching data. ### Checklist 📋 - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Settings and api keys page is working correctly
This commit is contained in:
@@ -75,7 +75,7 @@ export const customMutator = async <T = any>(
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
response_data,
|
||||
data: response_data,
|
||||
headers: response.headers,
|
||||
} as T;
|
||||
};
|
||||
|
||||
@@ -2931,18 +2931,6 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string", "title": "Preset Id" }
|
||||
},
|
||||
{
|
||||
"name": "graph_id",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": { "type": "string", "title": "Graph Id" }
|
||||
},
|
||||
{
|
||||
"name": "graph_version",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": { "type": "integer", "title": "Graph Version" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -3128,11 +3116,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"patch": {
|
||||
"tags": ["v2", "library", "private"],
|
||||
"summary": "Update Library Agent",
|
||||
"description": "Update the library agent with the given fields.\n\nArgs:\n library_agent_id: ID of the library agent to update.\n payload: Fields to update (auto_update_version, is_favorite, etc.).\n user_id: ID of the authenticated user.\n\nReturns:\n 204 (No Content) on success.\n\nRaises:\n HTTPException(500): If a server/database error occurs.",
|
||||
"operationId": "putV2Update library agent",
|
||||
"description": "Update the library agent with the given fields.\n\nArgs:\n library_agent_id: ID of the library agent to update.\n payload: Fields to update (auto_update_version, is_favorite, etc.).\n user_id: ID of the authenticated user.\n\nRaises:\n HTTPException(500): If a server/database error occurs.",
|
||||
"operationId": "patchV2Update library agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "library_agent_id",
|
||||
@@ -3152,7 +3140,45 @@
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"204": { "description": "Agent updated successfully" },
|
||||
"200": {
|
||||
"description": "Agent updated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/LibraryAgent" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": { "description": "Server error" },
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["v2", "library", "private"],
|
||||
"summary": "Delete Library Agent",
|
||||
"description": "Soft-delete the specified library agent.\n\nArgs:\n library_agent_id: ID of the library agent to delete.\n user_id: ID of the authenticated user.\n\nReturns:\n 204 No Content if successful.\n\nRaises:\n HTTPException(404): If the agent does not exist.\n HTTPException(500): If a server/database error occurs.",
|
||||
"operationId": "deleteV2Delete library agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "library_agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string", "title": "Library Agent Id" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": { "application/json": { "schema": {} } }
|
||||
},
|
||||
"204": { "description": "Agent deleted successfully" },
|
||||
"404": { "description": "Agent not found" },
|
||||
"500": { "description": "Server error" },
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
@@ -3238,6 +3264,55 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/library/agents/{library_agent_id}/setup-trigger": {
|
||||
"post": {
|
||||
"tags": ["v2", "library", "private"],
|
||||
"summary": "Setup Trigger",
|
||||
"description": "Sets up a webhook-triggered `LibraryAgentPreset` for a `LibraryAgent`.\nReturns the correspondingly created `LibraryAgentPreset` with `webhook_id` set.",
|
||||
"operationId": "postV2SetupTrigger",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "library_agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"description": "ID of the library agent",
|
||||
"title": "Library Agent Id"
|
||||
},
|
||||
"description": "ID of the library agent"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TriggeredPresetSetupParams"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/LibraryAgentPreset" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/otto/ask": {
|
||||
"post": {
|
||||
"tags": ["v2", "otto"],
|
||||
@@ -3713,10 +3788,10 @@
|
||||
},
|
||||
"Body_postV2Execute_a_preset": {
|
||||
"properties": {
|
||||
"node_input": {
|
||||
"inputs": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Node Input"
|
||||
"title": "Inputs"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -4303,6 +4378,23 @@
|
||||
"type": "object",
|
||||
"title": "Input Schema"
|
||||
},
|
||||
"credentials_input_schema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Credentials Input Schema",
|
||||
"description": "Input schema for credentials required by the agent"
|
||||
},
|
||||
"has_external_trigger": {
|
||||
"type": "boolean",
|
||||
"title": "Has External Trigger",
|
||||
"description": "Whether the agent has an external trigger (e.g. webhook) node"
|
||||
},
|
||||
"trigger_setup_info": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/components/schemas/LibraryAgentTriggerInfo" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"new_output": { "type": "boolean", "title": "New Output" },
|
||||
"can_access_graph": {
|
||||
"type": "boolean",
|
||||
@@ -4326,6 +4418,8 @@
|
||||
"name",
|
||||
"description",
|
||||
"input_schema",
|
||||
"credentials_input_schema",
|
||||
"has_external_trigger",
|
||||
"new_output",
|
||||
"can_access_graph",
|
||||
"is_latest_version"
|
||||
@@ -4342,6 +4436,13 @@
|
||||
"type": "object",
|
||||
"title": "Inputs"
|
||||
},
|
||||
"credentials": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/CredentialsMetaInput"
|
||||
},
|
||||
"type": "object",
|
||||
"title": "Credentials"
|
||||
},
|
||||
"name": { "type": "string", "title": "Name" },
|
||||
"description": { "type": "string", "title": "Description" },
|
||||
"is_active": {
|
||||
@@ -4349,7 +4450,12 @@
|
||||
"title": "Is Active",
|
||||
"default": true
|
||||
},
|
||||
"webhook_id": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Webhook Id"
|
||||
},
|
||||
"id": { "type": "string", "title": "Id" },
|
||||
"user_id": { "type": "string", "title": "User Id" },
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@@ -4361,9 +4467,11 @@
|
||||
"graph_id",
|
||||
"graph_version",
|
||||
"inputs",
|
||||
"credentials",
|
||||
"name",
|
||||
"description",
|
||||
"id",
|
||||
"user_id",
|
||||
"updated_at"
|
||||
],
|
||||
"title": "LibraryAgentPreset",
|
||||
@@ -4378,12 +4486,23 @@
|
||||
"type": "object",
|
||||
"title": "Inputs"
|
||||
},
|
||||
"credentials": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/CredentialsMetaInput"
|
||||
},
|
||||
"type": "object",
|
||||
"title": "Credentials"
|
||||
},
|
||||
"name": { "type": "string", "title": "Name" },
|
||||
"description": { "type": "string", "title": "Description" },
|
||||
"is_active": {
|
||||
"type": "boolean",
|
||||
"title": "Is Active",
|
||||
"default": true
|
||||
},
|
||||
"webhook_id": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Webhook Id"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -4391,6 +4510,7 @@
|
||||
"graph_id",
|
||||
"graph_version",
|
||||
"inputs",
|
||||
"credentials",
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
@@ -4439,6 +4559,18 @@
|
||||
],
|
||||
"title": "Inputs"
|
||||
},
|
||||
"credentials": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/CredentialsMetaInput"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{ "type": "null" }
|
||||
],
|
||||
"title": "Credentials"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Name"
|
||||
@@ -4481,6 +4613,24 @@
|
||||
"enum": ["COMPLETED", "HEALTHY", "WAITING", "ERROR"],
|
||||
"title": "LibraryAgentStatus"
|
||||
},
|
||||
"LibraryAgentTriggerInfo": {
|
||||
"properties": {
|
||||
"provider": { "$ref": "#/components/schemas/ProviderName" },
|
||||
"config_schema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Config Schema",
|
||||
"description": "Input schema for the trigger block"
|
||||
},
|
||||
"credentials_input_name": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Credentials Input Name"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["provider", "config_schema", "credentials_input_name"],
|
||||
"title": "LibraryAgentTriggerInfo"
|
||||
},
|
||||
"LibraryAgentUpdateRequest": {
|
||||
"properties": {
|
||||
"auto_update_version": {
|
||||
@@ -4497,11 +4647,6 @@
|
||||
"anyOf": [{ "type": "boolean" }, { "type": "null" }],
|
||||
"title": "Is Archived",
|
||||
"description": "Archive the agent"
|
||||
},
|
||||
"is_deleted": {
|
||||
"anyOf": [{ "type": "boolean" }, { "type": "null" }],
|
||||
"title": "Is Deleted",
|
||||
"description": "Delete the agent"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -5773,6 +5918,31 @@
|
||||
"required": ["transactions", "next_transaction_time"],
|
||||
"title": "TransactionHistory"
|
||||
},
|
||||
"TriggeredPresetSetupParams": {
|
||||
"properties": {
|
||||
"name": { "type": "string", "title": "Name" },
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "Description",
|
||||
"default": ""
|
||||
},
|
||||
"trigger_config": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Trigger Config"
|
||||
},
|
||||
"agent_credentials": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/CredentialsMetaInput"
|
||||
},
|
||||
"type": "object",
|
||||
"title": "Agent Credentials"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["name", "trigger_config"],
|
||||
"title": "TriggeredPresetSetupParams"
|
||||
},
|
||||
"TurnstileVerifyRequest": {
|
||||
"properties": {
|
||||
"token": {
|
||||
@@ -6055,16 +6225,6 @@
|
||||
"type": "string",
|
||||
"title": "Provider Webhook Id"
|
||||
},
|
||||
"attached_nodes": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": { "$ref": "#/components/schemas/NodeModel" },
|
||||
"type": "array"
|
||||
},
|
||||
{ "type": "null" }
|
||||
],
|
||||
"title": "Attached Nodes"
|
||||
},
|
||||
"url": { "type": "string", "title": "Url", "readOnly": true }
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2, MoreVertical } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useAPISection } from "./useAPISection";
|
||||
|
||||
export function APIKeysSection() {
|
||||
const { apiKeys, isLoading, isDeleting, handleRevokeKey } = useAPISection();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center p-4">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
apiKeys &&
|
||||
apiKeys.length > 0 && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>API Key</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Last Used</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiKeys.map((key) => (
|
||||
<TableRow key={key.id}>
|
||||
<TableCell>{key.name}</TableCell>
|
||||
<TableCell>
|
||||
<div className="rounded-md border p-1 px-2 text-xs">
|
||||
{`${key.prefix}******************${key.postfix}`}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
key.status === "ACTIVE" ? "default" : "destructive"
|
||||
}
|
||||
className={
|
||||
key.status === "ACTIVE"
|
||||
? "border-green-600 bg-green-100 text-green-800"
|
||||
: "border-red-600 bg-red-100 text-red-800"
|
||||
}
|
||||
>
|
||||
{key.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(key.created_at).toLocaleDateString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{key.last_used_at
|
||||
? new Date(key.last_used_at).toLocaleDateString()
|
||||
: "Never"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => handleRevokeKey(key.id)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Revoke
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
import {
|
||||
getGetV1ListUserApiKeysQueryKey,
|
||||
useDeleteV1RevokeApiKey,
|
||||
useGetV1ListUserApiKeys,
|
||||
} from "@/api/__generated__/endpoints/api-keys/api-keys";
|
||||
import { APIKeyWithoutHash } from "@/api/__generated__/models/aPIKeyWithoutHash";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getQueryClient } from "@/lib/react-query/queryClient";
|
||||
|
||||
export const useAPISection = () => {
|
||||
const queryClient = getQueryClient();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: apiKeys, isLoading } = useGetV1ListUserApiKeys({
|
||||
query: {
|
||||
select: (res) => {
|
||||
return (res.data as APIKeyWithoutHash[]).filter(
|
||||
(key) => key.status === "ACTIVE",
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: revokeAPIKey, isPending: isDeleting } =
|
||||
useDeleteV1RevokeApiKey({
|
||||
mutation: {
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: getGetV1ListUserApiKeysQueryKey(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const handleRevokeKey = async (keyId: string) => {
|
||||
try {
|
||||
await revokeAPIKey({
|
||||
keyId: keyId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "AutoGPT Platform API key revoked successfully",
|
||||
});
|
||||
} catch {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to revoke AutoGPT Platform API key",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
apiKeys,
|
||||
isLoading,
|
||||
isDeleting,
|
||||
handleRevokeKey,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
"use client";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { LuCopy } from "react-icons/lu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { useAPIkeysModals } from "./useAPIkeysModals";
|
||||
import { APIKeyPermission } from "@/api/__generated__/models/aPIKeyPermission";
|
||||
|
||||
export const APIKeysModals = () => {
|
||||
const {
|
||||
isCreating,
|
||||
handleCreateKey,
|
||||
handleCopyKey,
|
||||
setIsCreateOpen,
|
||||
setIsKeyDialogOpen,
|
||||
isCreateOpen,
|
||||
isKeyDialogOpen,
|
||||
keyState,
|
||||
setKeyState,
|
||||
} = useAPIkeysModals();
|
||||
|
||||
return (
|
||||
<div className="mb-4 flex justify-end">
|
||||
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create Key</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New API Key</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new AutoGPT Platform API key
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={keyState.newKeyName}
|
||||
onChange={(e) =>
|
||||
setKeyState((prev) => ({
|
||||
...prev,
|
||||
newKeyName: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="My AutoGPT Platform API Key"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="description">Description (Optional)</Label>
|
||||
<Input
|
||||
id="description"
|
||||
value={keyState.newKeyDescription}
|
||||
onChange={(e) =>
|
||||
setKeyState((prev) => ({
|
||||
...prev,
|
||||
newKeyDescription: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Used for..."
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Permissions</Label>
|
||||
{Object.values(APIKeyPermission).map((permission) => (
|
||||
<div className="flex items-center space-x-2" key={permission}>
|
||||
<Checkbox
|
||||
id={permission}
|
||||
checked={keyState.selectedPermissions.includes(permission)}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
setKeyState((prev) => ({
|
||||
...prev,
|
||||
selectedPermissions: checked
|
||||
? [...prev.selectedPermissions, permission]
|
||||
: prev.selectedPermissions.filter(
|
||||
(p) => p !== permission,
|
||||
),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={permission}>{permission}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreateOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleCreateKey} disabled={isCreating}>
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isKeyDialogOpen} onOpenChange={setIsKeyDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>AutoGPT Platform API Key Created</DialogTitle>
|
||||
<DialogDescription>
|
||||
Please copy your AutoGPT API key now. You won't be able to
|
||||
see it again!
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center space-x-2">
|
||||
<code className="flex-1 rounded-md bg-secondary p-2 text-sm">
|
||||
{keyState.newApiKey}
|
||||
</code>
|
||||
<Button size="icon" variant="outline" onClick={handleCopyKey}>
|
||||
<LuCopy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setIsKeyDialogOpen(false)}>Close</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import {
|
||||
getGetV1ListUserApiKeysQueryKey,
|
||||
usePostV1CreateNewApiKey,
|
||||
} from "@/api/__generated__/endpoints/api-keys/api-keys";
|
||||
import { APIKeyPermission } from "@/api/__generated__/models/aPIKeyPermission";
|
||||
import { CreateAPIKeyResponse } from "@/api/__generated__/models/createAPIKeyResponse";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getQueryClient } from "@/lib/react-query/queryClient";
|
||||
import { useState } from "react";
|
||||
|
||||
export const useAPIkeysModals = () => {
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false);
|
||||
const [keyState, setKeyState] = useState({
|
||||
newKeyName: "",
|
||||
newKeyDescription: "",
|
||||
newApiKey: "",
|
||||
selectedPermissions: [] as APIKeyPermission[],
|
||||
});
|
||||
const queryClient = getQueryClient();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: createAPIKey, isPending: isCreating } =
|
||||
usePostV1CreateNewApiKey({
|
||||
mutation: {
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: getGetV1ListUserApiKeysQueryKey(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const handleCreateKey = async () => {
|
||||
try {
|
||||
const response = await createAPIKey({
|
||||
data: {
|
||||
name: keyState.newKeyName,
|
||||
permissions: keyState.selectedPermissions,
|
||||
description: keyState.newKeyDescription,
|
||||
},
|
||||
});
|
||||
setKeyState((prev) => ({
|
||||
...prev,
|
||||
newApiKey: (response.data as CreateAPIKeyResponse).plain_text_key,
|
||||
}));
|
||||
setIsCreateOpen(false);
|
||||
setIsKeyDialogOpen(true);
|
||||
} catch {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create AutoGPT Platform API key",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyKey = () => {
|
||||
navigator.clipboard.writeText(keyState.newApiKey);
|
||||
toast({
|
||||
title: "Copied",
|
||||
description: "AutoGPT Platform API key copied to clipboard",
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
isCreating,
|
||||
handleCreateKey,
|
||||
handleCopyKey,
|
||||
setIsCreateOpen,
|
||||
setIsKeyDialogOpen,
|
||||
isCreateOpen,
|
||||
isKeyDialogOpen,
|
||||
keyState,
|
||||
setKeyState,
|
||||
};
|
||||
};
|
||||
@@ -1,12 +1,31 @@
|
||||
import { Metadata } from "next/types";
|
||||
import { APIKeysSection } from "@/components/agptui/composite/APIKeySection";
|
||||
import { APIKeysSection } from "@/app/(platform)/profile/(user)/api_keys/components/APIKeySection/APIKeySection";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { APIKeysModals } from "./components/APIKeysModals/APIKeysModals";
|
||||
|
||||
export const metadata: Metadata = { title: "API Keys - AutoGPT Platform" };
|
||||
|
||||
const ApiKeysPage = () => {
|
||||
return (
|
||||
<div className="w-full pr-4 pt-24 md:pt-0">
|
||||
<APIKeysSection />
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>AutoGPT Platform API Keys</CardTitle>
|
||||
<CardDescription>
|
||||
Manage your AutoGPT Platform API keys for programmatic access
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<APIKeysModals />
|
||||
<APIKeysSection />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
|
||||
import BackendApi from "@/lib/autogpt-server-api";
|
||||
import { NotificationPreferenceDTO } from "@/lib/autogpt-server-api/types";
|
||||
import {
|
||||
postV1UpdateNotificationPreferences,
|
||||
postV1UpdateUserEmail,
|
||||
} from "@/api/__generated__/endpoints/auth/auth";
|
||||
|
||||
export async function updateSettings(formData: FormData) {
|
||||
const supabase = await getServerSupabase();
|
||||
@@ -29,8 +32,7 @@ export async function updateSettings(formData: FormData) {
|
||||
const { error: emailError } = await supabase.auth.updateUser({
|
||||
email,
|
||||
});
|
||||
const api = new BackendApi();
|
||||
await api.updateUserEmail(email);
|
||||
await postV1UpdateUserEmail(email);
|
||||
|
||||
if (emailError) {
|
||||
throw new Error(`${emailError.message}`);
|
||||
@@ -38,7 +40,6 @@ export async function updateSettings(formData: FormData) {
|
||||
}
|
||||
|
||||
try {
|
||||
const api = new BackendApi();
|
||||
const preferences: NotificationPreferenceDTO = {
|
||||
email: user?.email || "",
|
||||
preferences: {
|
||||
@@ -55,7 +56,7 @@ export async function updateSettings(formData: FormData) {
|
||||
},
|
||||
daily_limit: 0,
|
||||
};
|
||||
await api.updateUserPreferences(preferences);
|
||||
await postV1UpdateNotificationPreferences(preferences);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`Failed to update preferences: ${error}`);
|
||||
@@ -64,9 +65,3 @@ export async function updateSettings(formData: FormData) {
|
||||
revalidatePath("/profile/settings");
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function getUserPreferences(): Promise<NotificationPreferenceDTO> {
|
||||
const api = new BackendApi();
|
||||
const preferences = await api.getUserPreferences();
|
||||
return preferences;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
@@ -18,100 +13,22 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { updateSettings } from "@/app/(platform)/profile/(user)/settings/actions";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { NotificationPreferenceDTO } from "@/lib/autogpt-server-api";
|
||||
import { NotificationPreference } from "@/api/__generated__/models/notificationPreference";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import { useSettingsForm } from "./useSettingsForm";
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((val) => {
|
||||
// If password is provided, it must be at least 8 characters
|
||||
if (val) return val.length >= 12;
|
||||
return true;
|
||||
}, "String must contain at least 12 character(s)"),
|
||||
confirmPassword: z.string().optional(),
|
||||
notifyOnAgentRun: z.boolean(),
|
||||
notifyOnZeroBalance: z.boolean(),
|
||||
notifyOnLowBalance: z.boolean(),
|
||||
notifyOnBlockExecutionFailed: z.boolean(),
|
||||
notifyOnContinuousAgentError: z.boolean(),
|
||||
notifyOnDailySummary: z.boolean(),
|
||||
notifyOnWeeklySummary: z.boolean(),
|
||||
notifyOnMonthlySummary: z.boolean(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.password || data.confirmPassword) {
|
||||
return data.password === data.confirmPassword;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Passwords do not match",
|
||||
path: ["confirmPassword"],
|
||||
},
|
||||
);
|
||||
|
||||
interface SettingsFormProps {
|
||||
export const SettingsForm = ({
|
||||
preferences,
|
||||
user,
|
||||
}: {
|
||||
preferences: NotificationPreference;
|
||||
user: User;
|
||||
preferences: NotificationPreferenceDTO;
|
||||
}
|
||||
|
||||
export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
||||
const defaultValues = {
|
||||
email: user.email || "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
notifyOnAgentRun: preferences.preferences.AGENT_RUN,
|
||||
notifyOnZeroBalance: preferences.preferences.ZERO_BALANCE,
|
||||
notifyOnLowBalance: preferences.preferences.LOW_BALANCE,
|
||||
notifyOnBlockExecutionFailed:
|
||||
preferences.preferences.BLOCK_EXECUTION_FAILED,
|
||||
notifyOnContinuousAgentError:
|
||||
preferences.preferences.CONTINUOUS_AGENT_ERROR,
|
||||
notifyOnDailySummary: preferences.preferences.DAILY_SUMMARY,
|
||||
notifyOnWeeklySummary: preferences.preferences.WEEKLY_SUMMARY,
|
||||
notifyOnMonthlySummary: preferences.preferences.MONTHLY_SUMMARY,
|
||||
};
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues,
|
||||
}) => {
|
||||
const { form, onSubmit, onCancel } = useSettingsForm({
|
||||
preferences,
|
||||
user,
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
if (key !== "confirmPassword") {
|
||||
formData.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
await updateSettings(formData);
|
||||
|
||||
toast({
|
||||
title: "Successfully updated settings",
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
error instanceof Error ? error.message : "Something went wrong",
|
||||
variant: "destructive",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
form.reset(defaultValues);
|
||||
}
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -396,4 +313,4 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const formSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((val) => {
|
||||
if (val) return val.length >= 12;
|
||||
return true;
|
||||
}, "String must contain at least 12 character(s)"),
|
||||
confirmPassword: z.string().optional(),
|
||||
notifyOnAgentRun: z.boolean(),
|
||||
notifyOnZeroBalance: z.boolean(),
|
||||
notifyOnLowBalance: z.boolean(),
|
||||
notifyOnBlockExecutionFailed: z.boolean(),
|
||||
notifyOnContinuousAgentError: z.boolean(),
|
||||
notifyOnDailySummary: z.boolean(),
|
||||
notifyOnWeeklySummary: z.boolean(),
|
||||
notifyOnMonthlySummary: z.boolean(),
|
||||
})
|
||||
.refine((data) => {
|
||||
if (data.password || data.confirmPassword) {
|
||||
return data.password === data.confirmPassword;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
export const createDefaultValues = (
|
||||
user: { email?: string },
|
||||
preferences: { preferences?: Record<string, boolean> },
|
||||
) => {
|
||||
const defaultValues = {
|
||||
email: user.email || "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
notifyOnAgentRun: preferences.preferences?.AGENT_RUN,
|
||||
notifyOnZeroBalance: preferences.preferences?.ZERO_BALANCE,
|
||||
notifyOnLowBalance: preferences.preferences?.LOW_BALANCE,
|
||||
notifyOnBlockExecutionFailed:
|
||||
preferences.preferences?.BLOCK_EXECUTION_FAILED,
|
||||
notifyOnContinuousAgentError:
|
||||
preferences.preferences?.CONTINUOUS_AGENT_ERROR,
|
||||
notifyOnDailySummary: preferences.preferences?.DAILY_SUMMARY,
|
||||
notifyOnWeeklySummary: preferences.preferences?.WEEKLY_SUMMARY,
|
||||
notifyOnMonthlySummary: preferences.preferences?.MONTHLY_SUMMARY,
|
||||
};
|
||||
|
||||
return defaultValues;
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { createDefaultValues, formSchema } from "./helper";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { updateSettings } from "../../actions";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { NotificationPreference } from "@/api/__generated__/models/notificationPreference";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
|
||||
export const useSettingsForm = ({
|
||||
preferences,
|
||||
user,
|
||||
}: {
|
||||
preferences: NotificationPreference;
|
||||
user: User;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const defaultValues = createDefaultValues(user, preferences);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
if (key !== "confirmPassword") {
|
||||
formData.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
await updateSettings(formData);
|
||||
|
||||
toast({
|
||||
title: "Successfully updated settings",
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
error instanceof Error ? error.message : "Something went wrong",
|
||||
variant: "destructive",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
form.reset(defaultValues);
|
||||
}
|
||||
|
||||
return { form, onSubmit, onCancel };
|
||||
};
|
||||
@@ -1,23 +1,37 @@
|
||||
"use client";
|
||||
import { useGetV1GetNotificationPreferences } from "@/api/__generated__/endpoints/auth/auth";
|
||||
import { SettingsForm } from "@/app/(platform)/profile/(user)/settings/components/SettingsForm/SettingsForm";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import * as React from "react";
|
||||
import { Metadata } from "next";
|
||||
import SettingsForm from "@/components/profile/settings/SettingsForm";
|
||||
import { getServerUser } from "@/lib/supabase/server/getServerUser";
|
||||
import SettingsLoading from "./loading";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getUserPreferences } from "./actions";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Settings - AutoGPT Platform",
|
||||
description: "Manage your account settings and preferences.",
|
||||
};
|
||||
export default function SettingsPage() {
|
||||
const {
|
||||
data: preferences,
|
||||
isError,
|
||||
isLoading,
|
||||
} = useGetV1GetNotificationPreferences({
|
||||
query: {
|
||||
select: (res) => {
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const { user, error } = await getServerUser();
|
||||
const { user, isUserLoading } = useSupabase();
|
||||
|
||||
if (error || !user) {
|
||||
if (isLoading || isUserLoading) {
|
||||
return <SettingsLoading />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const preferences = await getUserPreferences();
|
||||
if (isError || !preferences || !preferences.preferences) {
|
||||
return "Errror..."; // TODO: Will use a Error reusable components from Block Menu redesign
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-2xl space-y-6 py-10">
|
||||
@@ -27,7 +41,7 @@ export default async function SettingsPage() {
|
||||
Manage your account settings and preferences.
|
||||
</p>
|
||||
</div>
|
||||
<SettingsForm user={user} preferences={preferences} />
|
||||
<SettingsForm preferences={preferences} user={user} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { APIKey, APIKeyPermission } from "@/lib/autogpt-server-api/types";
|
||||
|
||||
import { LuCopy } from "react-icons/lu";
|
||||
import { Loader2, MoreVertical } from "lucide-react";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function APIKeysSection() {
|
||||
const [apiKeys, setApiKeys] = useState<APIKey[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false);
|
||||
const [newKeyName, setNewKeyName] = useState("");
|
||||
const [newKeyDescription, setNewKeyDescription] = useState("");
|
||||
const [newApiKey, setNewApiKey] = useState("");
|
||||
const [selectedPermissions, setSelectedPermissions] = useState<
|
||||
APIKeyPermission[]
|
||||
>([]);
|
||||
const { toast } = useToast();
|
||||
const api = useBackendAPI();
|
||||
|
||||
useEffect(() => {
|
||||
loadAPIKeys();
|
||||
}, []);
|
||||
|
||||
const loadAPIKeys = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const keys = await api.listAPIKeys();
|
||||
setApiKeys(keys.filter((key) => key.status === "ACTIVE"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateKey = async () => {
|
||||
try {
|
||||
const response = await api.createAPIKey(
|
||||
newKeyName,
|
||||
selectedPermissions,
|
||||
newKeyDescription,
|
||||
);
|
||||
|
||||
setNewApiKey(response.plain_text_key);
|
||||
setIsCreateOpen(false);
|
||||
setIsKeyDialogOpen(true);
|
||||
loadAPIKeys();
|
||||
} catch {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create AutoGPT Platform API key",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyKey = () => {
|
||||
navigator.clipboard.writeText(newApiKey);
|
||||
toast({
|
||||
title: "Copied",
|
||||
description: "AutoGPT Platform API key copied to clipboard",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRevokeKey = async (keyId: string) => {
|
||||
try {
|
||||
await api.revokeAPIKey(keyId);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "AutoGPT Platform API key revoked successfully",
|
||||
});
|
||||
loadAPIKeys();
|
||||
} catch {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to revoke AutoGPT Platform API key",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>AutoGPT Platform API Keys</CardTitle>
|
||||
<CardDescription>
|
||||
Manage your AutoGPT Platform API keys for programmatic access
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-4 flex justify-end">
|
||||
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create Key</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New API Key</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new AutoGPT Platform API key
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
placeholder="My AutoGPT Platform API Key"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="description">Description (Optional)</Label>
|
||||
<Input
|
||||
id="description"
|
||||
value={newKeyDescription}
|
||||
onChange={(e) => setNewKeyDescription(e.target.value)}
|
||||
placeholder="Used for..."
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Permissions</Label>
|
||||
{Object.values(APIKeyPermission).map((permission) => (
|
||||
<div
|
||||
className="flex items-center space-x-2"
|
||||
key={permission}
|
||||
>
|
||||
<Checkbox
|
||||
id={permission}
|
||||
checked={selectedPermissions.includes(permission)}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
setSelectedPermissions(
|
||||
checked
|
||||
? [...selectedPermissions, permission]
|
||||
: selectedPermissions.filter(
|
||||
(p) => p !== permission,
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={permission}>{permission}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsCreateOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleCreateKey}>Create</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isKeyDialogOpen} onOpenChange={setIsKeyDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>AutoGPT Platform API Key Created</DialogTitle>
|
||||
<DialogDescription>
|
||||
Please copy your AutoGPT API key now. You won't be able
|
||||
to see it again!
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center space-x-2">
|
||||
<code className="flex-1 rounded-md bg-secondary p-2 text-sm">
|
||||
{newApiKey}
|
||||
</code>
|
||||
<Button size="icon" variant="outline" onClick={handleCopyKey}>
|
||||
<LuCopy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setIsKeyDialogOpen(false)}>Close</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center p-4">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
apiKeys.length > 0 && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>API Key</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Last Used</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiKeys.map((key) => (
|
||||
<TableRow key={key.id}>
|
||||
<TableCell>{key.name}</TableCell>
|
||||
<TableCell>
|
||||
<div className="rounded-md border p-1 px-2 text-xs">
|
||||
{`${key.prefix}******************${key.postfix}`}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
key.status === "ACTIVE" ? "default" : "destructive"
|
||||
}
|
||||
className={
|
||||
key.status === "ACTIVE"
|
||||
? "border-green-600 bg-green-100 text-green-800"
|
||||
: "border-red-600 bg-red-100 text-red-800"
|
||||
}
|
||||
>
|
||||
{key.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(key.created_at).toLocaleDateString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{key.last_used_at
|
||||
? new Date(key.last_used_at).toLocaleDateString()
|
||||
: "Never"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => handleRevokeKey(key.id)}
|
||||
>
|
||||
Revoke
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user