/** * Component: API Token Management Tab (Admin) * Documentation: documentation/backend/services/api-tokens.md */ 'use client'; import { useState, useEffect, useCallback } from 'react'; import { fetchWithAuth } from '@/lib/utils/api'; import { ConfirmDialog } from '@/app/admin/components/ConfirmDialog'; import { useApiTokens } from '@/lib/hooks/useApiTokens'; import { getInstanceUrl } from '@/lib/utils/client-url'; import Link from 'next/link'; import type { AdminApiToken } from '@/lib/types/api-tokens'; interface UserOption { id: string; plexUsername: string; role: string; } export function ApiTab() { const api = useApiTokens({ basePath: '/api/admin/api-tokens' }); // Admin-specific state const [users, setUsers] = useState([]); const [newTokenUserId, setNewTokenUserId] = useState(''); const fetchUsers = useCallback(async () => { try { const response = await fetchWithAuth('/api/admin/users'); if (response.ok) { const data = await response.json(); setUsers(data.users.map((u: any) => ({ id: u.id, plexUsername: u.plexUsername, role: u.role }))); } } catch { // Non-critical, user selector just won't populate } }, []); useEffect(() => { fetchUsers(); }, [fetchUsers]); const handleCreate = async () => { const extraBody: Record = {}; if (newTokenUserId) extraBody.userId = newTokenUserId; const created = await api.handleCreate(extraBody); // Reset admin-specific fields only when create succeeds if (created) { setNewTokenUserId(''); } }; const handleCancel = () => { api.resetForm(); setNewTokenUserId(''); }; if (api.loading) { return (
); } return (

API Tokens

Manage API tokens for all users. Create tokens for any user for programmatic access.{' '} View API documentation

{/* Error display */} {api.error && (
{api.error}
)} {/* Newly created token banner */} {api.createdToken && (

Token created successfully! Copy it now — it won't be shown again.

{api.createdToken}
)} {/* Create token form */} {api.showCreateForm ? (

Create New Token

api.setNewTokenName(e.target.value)} placeholder="e.g., Home Assistant, Webhook" className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:border-blue-500 focus:ring-1 focus:ring-blue-500" onKeyDown={(e) => e.key === 'Enter' && handleCreate()} />

Token will inherit the selected user's role

) : ( )} {/* Token list */} {api.tokens.length === 0 ? (

No API tokens yet

Create a token to enable programmatic API access

) : (
{api.tokens.map((token) => ( ))}
Name Token Acts As Role Created By Last Used Expires Actions
{token.name} {token.tokenPrefix}... {token.tokenUser} {token.role} {token.createdBy} {api.formatDate(token.lastUsedAt)} {token.expiresAt ? ( {api.formatDate(token.expiresAt)} {new Date(token.expiresAt) < new Date() && ' (expired)'} ) : ( 'Never' )}
)} {/* Usage instructions */}

Usage

Include the token in the Authorization header:

{`curl -H "Authorization: Bearer rmab_your_token_here" \\
  ${getInstanceUrl()}/api/requests`}
        
{/* Revoke confirmation dialog */} Are you sure you want to revoke{' '} “{api.tokens.find((t) => t.id === api.confirmRevokeId)?.name ?? 'this token'}” ? Any integrations using this token will immediately lose access. This cannot be undone. } confirmLabel="Revoke token" cancelLabel="Cancel" confirmVariant="danger" onConfirm={api.handleDeleteConfirmed} onCancel={() => api.setConfirmRevokeId(null)} />
); }