initial commit
This commit is contained in:
commit
8a67761c9a
83 changed files with 15510 additions and 0 deletions
237
components/domains-list.tsx
Normal file
237
components/domains-list.tsx
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Globe, Plus, Settings, Search, X } from 'lucide-react';
|
||||
import { AddDomainDialog } from './add-domain-dialog';
|
||||
import { DomainSettingsDialog } from './domain-settings-dialog';
|
||||
|
||||
interface Domain {
|
||||
domain: string;
|
||||
date_created: string;
|
||||
dnssec?: "enabled" | "disabled";
|
||||
}
|
||||
|
||||
interface DomainsListProps {
|
||||
apiKey: string;
|
||||
onSelectDomain: (domain: string) => void;
|
||||
selectedDomain: string | null;
|
||||
}
|
||||
|
||||
export function DomainsList({ apiKey, onSelectDomain, selectedDomain }: DomainsListProps) {
|
||||
const [domains, setDomains] = useState<Domain[]>([]);
|
||||
const [filteredDomains, setFilteredDomains] = useState<Domain[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAddDomainOpen, setIsAddDomainOpen] = useState(false);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const fetchDomains = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch('/api/vultr/domains', {
|
||||
headers: {
|
||||
'X-API-Key': apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch domains');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setDomains(data.domains || []);
|
||||
setFilteredDomains(data.domains || []);
|
||||
} catch (error) {
|
||||
console.error('Error fetching domains:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to fetch domains. Please check your API key.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchDomains();
|
||||
}, [apiKey, toast]);
|
||||
|
||||
// Filter domains when search query changes
|
||||
useEffect(() => {
|
||||
if (searchQuery.trim() === '') {
|
||||
setFilteredDomains(domains);
|
||||
} else {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const filtered = domains.filter(domain =>
|
||||
domain.domain.toLowerCase().includes(query)
|
||||
);
|
||||
setFilteredDomains(filtered);
|
||||
}
|
||||
}, [searchQuery, domains]);
|
||||
|
||||
const handleDomainAdded = (newDomain: Domain) => {
|
||||
const updatedDomains = [newDomain, ...domains];
|
||||
setDomains(updatedDomains);
|
||||
setFilteredDomains(updatedDomains);
|
||||
toast({
|
||||
title: 'Domain Added',
|
||||
description: `${newDomain.domain} has been added successfully.`,
|
||||
});
|
||||
setIsAddDomainOpen(false);
|
||||
};
|
||||
|
||||
const handleDomainUpdated = (updatedDomain: Domain) => {
|
||||
const updatedDomains = domains.map(domain =>
|
||||
domain.domain === updatedDomain.domain ? updatedDomain : domain
|
||||
);
|
||||
setDomains(updatedDomains);
|
||||
setFilteredDomains(updatedDomains);
|
||||
toast({
|
||||
title: 'Domain Updated',
|
||||
description: `${updatedDomain.domain} settings have been updated successfully.`,
|
||||
});
|
||||
setIsSettingsOpen(false);
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
setSearchQuery('');
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-10 w-full mb-4" />
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-12 w-full" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const selectedDomainData = selectedDomain
|
||||
? domains.find(d => d.domain === selectedDomain)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 space-y-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search domains..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 pr-9"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 p-0"
|
||||
onClick={clearSearch}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Clear search</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => setIsAddDomainOpen(true)}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Domain
|
||||
</Button>
|
||||
|
||||
{selectedDomain && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => setIsSettingsOpen(true)}
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Domain Settings
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{domains.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Globe className="mx-auto h-12 w-12 text-muted-foreground opacity-50" />
|
||||
<h3 className="mt-4 text-lg font-semibold">No domains found</h3>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
You don't have any domains configured in your Vultr account.
|
||||
</p>
|
||||
</div>
|
||||
) : filteredDomains.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Search className="mx-auto h-12 w-12 text-muted-foreground opacity-50" />
|
||||
<h3 className="mt-4 text-lg font-semibold">No matching domains</h3>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
No domains match your search query "{searchQuery}"
|
||||
</p>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={clearSearch}
|
||||
className="mt-2"
|
||||
>
|
||||
Clear search
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="h-[calc(100vh-350px)]">
|
||||
<div className="space-y-2 pr-4">
|
||||
{filteredDomains.map((domain) => (
|
||||
<Button
|
||||
key={domain.domain}
|
||||
variant={selectedDomain === domain.domain ? "default" : "outline"}
|
||||
className="w-full justify-start text-left font-normal h-auto py-3"
|
||||
onClick={() => onSelectDomain(domain.domain)}
|
||||
>
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="font-medium">{domain.domain}</span>
|
||||
<span className="text-xs text-muted-foreground mt-1">
|
||||
Created: {new Date(domain.date_created).toLocaleDateString()}
|
||||
</span>
|
||||
{domain.dnssec && (
|
||||
<span className="text-xs mt-1">
|
||||
DNSSEC: <span className={domain.dnssec === "enabled" ? "text-green-500" : "text-muted-foreground"}>
|
||||
{domain.dnssec}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
|
||||
<AddDomainDialog
|
||||
open={isAddDomainOpen}
|
||||
onOpenChange={setIsAddDomainOpen}
|
||||
apiKey={apiKey}
|
||||
onDomainAdded={handleDomainAdded}
|
||||
/>
|
||||
|
||||
{selectedDomainData && (
|
||||
<DomainSettingsDialog
|
||||
open={isSettingsOpen}
|
||||
onOpenChange={setIsSettingsOpen}
|
||||
apiKey={apiKey}
|
||||
domain={selectedDomainData}
|
||||
onDomainUpdated={handleDomainUpdated}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue