(props: { readonly orgKeysSection?: ReactNode })
| 223 | } |
| 224 | |
| 225 | export function ApiKeysPage(props: { readonly orgKeysSection?: ReactNode }) { |
| 226 | useExecutorDocumentTitle("API keys"); |
| 227 | const result = useAtomValue(apiKeysAtom); |
| 228 | const refreshApiKeys = useAtomRefresh(apiKeysAtom); |
| 229 | const doCreate = useAtomSet(createApiKey, { mode: "promiseExit" }); |
| 230 | const doRevoke = useAtomSet(revokeApiKey, { mode: "promiseExit" }); |
| 231 | const [createOpen, setCreateOpen] = useState(false); |
| 232 | // Bumped per open so the dialog body remounts with fresh state; the shell |
| 233 | // stays mounted for Radix's exit animation (see CreateKeyDialogBody). |
| 234 | const [openCount, setOpenCount] = useState(0); |
| 235 | const [revokingId, setRevokingId] = useState<string | null>(null); |
| 236 | |
| 237 | const handleCreate = async (name: string): Promise<CreatedKey | null> => { |
| 238 | const exit = await doCreate({ payload: { name }, reactivityKeys: apiKeyWriteKeys }); |
| 239 | trackEvent("api_key_created", { success: Exit.isSuccess(exit) }); |
| 240 | if (Exit.isSuccess(exit)) { |
| 241 | toast.success("API key created"); |
| 242 | return exit.value; |
| 243 | } |
| 244 | toast.error("Failed to create API key"); |
| 245 | return null; |
| 246 | }; |
| 247 | |
| 248 | const handleRevoke = async (key: ApiKeySummary) => { |
| 249 | setRevokingId(key.id); |
| 250 | const exit = await doRevoke({ params: { apiKeyId: key.id }, reactivityKeys: apiKeyWriteKeys }); |
| 251 | setRevokingId(null); |
| 252 | trackEvent("api_key_revoked", { success: Exit.isSuccess(exit) }); |
| 253 | if (Exit.isSuccess(exit)) { |
| 254 | toast.success(`Revoked ${key.name}`); |
| 255 | return; |
| 256 | } |
| 257 | toast.error("Failed to revoke API key"); |
| 258 | }; |
| 259 | |
| 260 | return ( |
| 261 | <PageContainer> |
| 262 | <PageHeader |
| 263 | title="API keys" |
| 264 | description="Keys for accessing the Executor API and MCP endpoint from scripts and tools." |
| 265 | actions={ |
| 266 | <Button |
| 267 | onClick={() => { |
| 268 | setOpenCount((count) => count + 1); |
| 269 | setCreateOpen(true); |
| 270 | }} |
| 271 | > |
| 272 | <span aria-hidden="true">+</span> |
| 273 | New key |
| 274 | </Button> |
| 275 | } |
| 276 | > |
| 277 | <div className="mt-4 flex max-w-2xl items-center gap-2 rounded-md border border-border bg-card px-3 py-2"> |
| 278 | <code className="min-w-0 flex-1 truncate font-mono text-xs text-foreground"> |
| 279 | Authorization: Bearer <api-key> |
| 280 | </code> |
| 281 | <CopyButton value="Authorization: Bearer <api-key>" /> |
| 282 | </div> |
nothing calls this directly
no test coverage detected