'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ColumnDef } from '@tanstack/react-table'; import { format } from 'date-fns'; import { Check, Edit, MoreHorizontal, Plus, Shield, Trash2 } from 'lucide-react'; import { useState } from 'react'; import { DataTable } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { apiClient } from '@/lib/api-client'; import { KeygenPolicy, KeygenProduct } from '@/lib/types'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; export default function PoliciesPage() { const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedPolicy, setSelectedPolicy] = useState(null); const router = useRouter(); const [formData, setFormData] = useState({ name: '', productId: '', strict: false, floating: false, requireHeartbeat: false, maxMachines: '', maxUses: '', duration: '', scheme: 'ED25519_SIGN', }); const queryClient = useQueryClient(); const { data: policies, isLoading } = useQuery({ queryKey: ['policies'], queryFn: () => apiClient.getPolicies(1, 100), }); const { data: products } = useQuery({ queryKey: ['products'], queryFn: () => apiClient.getProducts(1, 100), }); const createMutation = useMutation({ mutationFn: (policyData: Partial) => apiClient.createPolicy(policyData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['policies'] }); setIsCreateDialogOpen(false); setFormData({ name: '', productId: '', strict: false, floating: false, requireHeartbeat: false, maxMachines: '', maxUses: '', duration: '', scheme: 'ED25519_SIGN', }); toast.success('Policy created successfully'); }, onError: (error) => { toast.error(error.message); }, }); const deleteMutation = useMutation({ mutationFn: (id: string) => apiClient.deletePolicy(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['policies'] }); setIsDeleteDialogOpen(false); setSelectedPolicy(null); toast.success('Policy deleted successfully'); }, onError: (error) => { toast.error(error.message); }, }); const handleCreate = () => { const policyData = { attributes: { name: formData.name, strict: formData.strict, floating: formData.floating, requireHeartbeat: formData.requireHeartbeat, scheme: formData.scheme, ...(formData.maxMachines && { maxMachines: parseInt(formData.maxMachines) }), ...(formData.maxUses && { maxUses: parseInt(formData.maxUses) }), ...(formData.duration && { duration: parseInt(formData.duration) }), }, relationships: { ...(formData.productId && { product: { data: { type: 'products', id: formData.productId } } }), }, }; createMutation.mutate(policyData as Partial); }; const handleDelete = () => { if (selectedPolicy) { deleteMutation.mutate(selectedPolicy.id); } }; const columns: ColumnDef[] = [ { accessorKey: 'attributes.name', header: 'Name', cell: ({ row }) => (
{row.original.attributes.name}
), }, { accessorKey: 'attributes.scheme', header: 'Scheme', cell: ({ row }) => ( {row.original.attributes.scheme} ), }, { accessorKey: 'attributes.maxMachines', header: 'Max Machines', cell: ({ row }) => { const max = row.original.attributes.maxMachines; return max ? ( {max} ) : ( Unlimited ); }, }, { accessorKey: 'attributes.maxUses', header: 'Max Uses', cell: ({ row }) => { const max = row.original.attributes.maxUses; return max ? ( {max} ) : ( Unlimited ); }, }, { accessorKey: 'attributes.duration', header: 'Duration', cell: ({ row }) => { const duration = row.original.attributes.duration; return duration ? ( {duration}s ) : ( Permanent ); }, }, { header: 'Flags', cell: ({ row }) => { const policy = row.original.attributes; return (
{policy.strict && ( Strict )} {policy.floating && ( Floating )} {policy.requireHeartbeat && ( Heartbeat )}
); }, }, { accessorKey: 'attributes.created', header: 'Created', cell: ({ row }) => format(new Date(row.original.attributes.created), 'MMM d, yyyy'), }, { id: 'actions', cell: ({ row }) => { const policy = row.original; return ( router.push(`/dashboard/policies/${policy.id}`)}> Edit { setSelectedPolicy(policy); setIsDeleteDialogOpen(true); }} className="text-red-600" > Delete ); }, }, ]; if (isLoading) { return ( <>

Loading policies...

); } return ( <>

Policies

Define license policies and validation rules

{/* Create Policy Dialog */} Create Policy Define a new license policy with validation rules
setFormData({ ...formData, name: e.target.value })} placeholder="Premium Policy" />
setFormData({ ...formData, maxMachines: e.target.value })} placeholder="Leave empty for unlimited" />
setFormData({ ...formData, maxUses: e.target.value })} placeholder="Leave empty for unlimited" />
setFormData({ ...formData, duration: e.target.value })} placeholder="Leave empty for permanent" />
Enforce strict license validation
setFormData({ ...formData, strict: checked })} />
Allow license to move between machines
setFormData({ ...formData, floating: checked })} />
Require periodic heartbeat validation
setFormData({ ...formData, requireHeartbeat: checked })} />
{/* Delete Policy Dialog */} Delete Policy Are you sure you want to delete "{selectedPolicy?.attributes.name}"? This action cannot be undone.
); }