()
| 413 | } |
| 414 | |
| 415 | function OrgApiKeysSectionBody() { |
| 416 | const result = useAtomValue(orgApiKeysAtom); |
| 417 | const refresh = useAtomRefresh(orgApiKeysAtom); |
| 418 | const doCreate = useAtomSet(createOrgApiKey, { mode: "promiseExit" }); |
| 419 | const doRevoke = useAtomSet(revokeOrgApiKey, { mode: "promiseExit" }); |
| 420 | const [createOpen, setCreateOpen] = useState(false); |
| 421 | // Same remount-per-open discipline as the personal dialog above. |
| 422 | const [openCount, setOpenCount] = useState(0); |
| 423 | const [confirmRevoke, setConfirmRevoke] = useState<ApiKeySummary | null>(null); |
| 424 | const [revokingId, setRevokingId] = useState<string | null>(null); |
| 425 | |
| 426 | const handleCreate = async (name: string): Promise<CreatedKey | null> => { |
| 427 | const exit = await doCreate({ payload: { name }, reactivityKeys: orgApiKeyWriteKeys }); |
| 428 | trackEvent("org_api_key_created", { success: Exit.isSuccess(exit) }); |
| 429 | if (Exit.isSuccess(exit)) { |
| 430 | toast.success("Organization key created"); |
| 431 | return exit.value; |
| 432 | } |
| 433 | toast.error("Failed to create organization key"); |
| 434 | return null; |
| 435 | }; |
| 436 | |
| 437 | const handleRevoke = async (key: ApiKeySummary) => { |
| 438 | setConfirmRevoke(null); |
| 439 | setRevokingId(key.id); |
| 440 | const exit = await doRevoke({ |
| 441 | params: { apiKeyId: key.id }, |
| 442 | reactivityKeys: orgApiKeyWriteKeys, |
| 443 | }); |
| 444 | setRevokingId(null); |
| 445 | trackEvent("org_api_key_revoked", { success: Exit.isSuccess(exit) }); |
| 446 | if (Exit.isSuccess(exit)) { |
| 447 | toast.success(`Revoked ${key.name}`); |
| 448 | return; |
| 449 | } |
| 450 | toast.error("Failed to revoke organization key"); |
| 451 | }; |
| 452 | |
| 453 | return ( |
| 454 | <section className="mt-10"> |
| 455 | <div className="mb-4 flex items-start justify-between gap-4"> |
| 456 | <div> |
| 457 | <h2 className="text-sm font-medium text-foreground">Organization keys</h2> |
| 458 | <p className="mt-0.5 max-w-2xl text-sm text-muted-foreground"> |
| 459 | Read-only keys owned by the organization, not a member. They authenticate the admin API |
| 460 | (who are my users, what have they connected) and cannot act as anyone or write anything. |
| 461 | Admins only. |
| 462 | </p> |
| 463 | </div> |
| 464 | <Button |
| 465 | size="sm" |
| 466 | onClick={() => { |
| 467 | setOpenCount((count) => count + 1); |
| 468 | setCreateOpen(true); |
| 469 | }} |
| 470 | > |
| 471 | <span aria-hidden="true">+</span> |
| 472 | New org key |
nothing calls this directly
no test coverage detected