New Integrations page in Wild Central UI for managing API tokens. Provides token display, copy, and rotation so Wild Cloud and other services can easily retrieve their authentication credentials. - GET /api/v1/integrations returns current API token - POST /api/v1/integrations/api-token/rotate generates a new token - Frontend with show/hide toggle, copy button, and usage instructions - Added to sidebar navigation under Central section
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
import { useState } from 'react';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { Key, RotateCw, Loader2, AlertCircle, CheckCircle, Eye, EyeOff } from 'lucide-react';
|
|
import { Card } from './ui/card';
|
|
import { Button } from './ui/button';
|
|
import { Badge } from './ui/badge';
|
|
import { Alert, AlertDescription } from './ui/alert';
|
|
import { CopyButton } from './CopyButton';
|
|
import { integrationsApi } from '../services/api/integrations';
|
|
|
|
export function IntegrationsComponent() {
|
|
const queryClient = useQueryClient();
|
|
const [showToken, setShowToken] = useState(false);
|
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
|
|
const { data, isLoading, error } = useQuery({
|
|
queryKey: ['integrations'],
|
|
queryFn: () => integrationsApi.get(),
|
|
});
|
|
|
|
const rotateMutation = useMutation({
|
|
mutationFn: () => integrationsApi.rotateApiToken(),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['integrations'] });
|
|
setSuccessMessage('API token rotated. Update the token in any connected services.');
|
|
setTimeout(() => setSuccessMessage(null), 8000);
|
|
},
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className="p-6">
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Card className="p-8 text-center">
|
|
<AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium mb-2">Error Loading Integrations</h3>
|
|
<p className="text-muted-foreground">{(error as Error)?.message || 'An error occurred'}</p>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const token = data?.apiToken?.token || '';
|
|
const maskedToken = token ? `${token.slice(0, 6)}${'*'.repeat(Math.max(0, token.length - 6))}` : '';
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-2 bg-primary/10 rounded-lg">
|
|
<Key className="h-6 w-6 text-primary" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-semibold">Integrations</h2>
|
|
<p className="text-muted-foreground">
|
|
Manage API tokens and service connections
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{successMessage && (
|
|
<Alert className="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/20">
|
|
<CheckCircle className="h-4 w-4 text-green-600" />
|
|
<AlertDescription className="text-green-700 dark:text-green-300">{successMessage}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{rotateMutation.error && (
|
|
<Alert variant="error">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertDescription>{(rotateMutation.error as Error)?.message || 'Failed to rotate token'}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<Card className="p-6 border-l-4 border-l-blue-500">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div>
|
|
<h3 className="text-lg font-semibold">API Token</h3>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
Bearer token for authenticating with the Wild Central API
|
|
</p>
|
|
</div>
|
|
<Badge variant="outline" className="gap-1">
|
|
{token ? (
|
|
<><CheckCircle className="h-3 w-3 text-green-500" /> Configured</>
|
|
) : (
|
|
<><AlertCircle className="h-3 w-3 text-red-500" /> Not Set</>
|
|
)}
|
|
</Badge>
|
|
</div>
|
|
|
|
{token && (
|
|
<>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<code className="flex-1 bg-muted rounded-md p-3 font-mono text-sm break-all">
|
|
{showToken ? token : maskedToken}
|
|
</code>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => setShowToken(!showToken)}
|
|
title={showToken ? 'Hide token' : 'Show token'}
|
|
>
|
|
{showToken ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</Button>
|
|
<CopyButton content={token} label="Copy" />
|
|
</div>
|
|
|
|
<div className="bg-muted/50 rounded-md p-4 mb-4">
|
|
<p className="text-sm font-medium mb-2">Usage</p>
|
|
<p className="text-sm text-muted-foreground mb-3">
|
|
Services like Wild Cloud use this token to authenticate with Wild Central's API.
|
|
Set it as the <code className="font-mono bg-muted px-1 rounded">WILD_CENTRAL_TOKEN</code> environment variable.
|
|
</p>
|
|
<code className="block bg-muted rounded-md p-2 font-mono text-xs break-all">
|
|
WILD_CENTRAL_TOKEN={showToken ? token : maskedToken}
|
|
</code>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => rotateMutation.mutate()}
|
|
disabled={rotateMutation.isPending}
|
|
>
|
|
{rotateMutation.isPending ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<RotateCw className="h-4 w-4" />
|
|
)}
|
|
{token ? 'Rotate Token' : 'Generate Token'}
|
|
</Button>
|
|
{token && (
|
|
<p className="text-xs text-muted-foreground">
|
|
Rotating the token will invalidate the current one. Connected services will need to be updated.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|