import { useState, useEffect, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { useLocation } from "wouter"; import { DashboardLayout, DashboardConfig } from "@/components/dashboard/dashboard-layout"; import { ShareDashboardDialog } from "@/components/dashboard/share-dashboard-dialog"; import { DashboardExport } from "@/components/dashboard/dashboard-export"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Plus, RefreshCw, Share2, Trash2, ArrowLeft } from "lucide-react"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { v4 as uuidv4 } from 'uuid'; import { apiRequest, getQueryFn, queryClient } from "@/lib/queryClient"; // Schema for form validation const dashboardSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), }); type DashboardFormValues = z.infer; export default function CustomDashboardsPage() { const { toast } = useToast(); const [, setLocation] = useLocation(); const [dashboards, setDashboards] = useState([]); const [activeTab, setActiveTab] = useState("dashboard-overview"); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [dashboardToDelete, setDashboardToDelete] = useState(null); const [dataSourcesLoading, setDataSourcesLoading] = useState(true); const dashboardRef = useRef(null); const [dataSources, setDataSources] = useState; }>>([]); // Form for creating a new dashboard const form = useForm({ resolver: zodResolver(dashboardSchema), defaultValues: { name: "", }, }); // Fetch LDAP connections to use as data sources const { data: connections = [], isLoading: isLoadingConnections } = useQuery({ queryKey: ['/api/ldap-connections'], }); // Data source queries const { data: userData } = useQuery<{ data: any[] }>({ queryKey: ['/api/user-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: computerData } = useQuery<{ data: any[] }>({ queryKey: ['/api/computer-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: groupData } = useQuery<{ data: any[] }>({ queryKey: ['/api/group-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: ouData } = useQuery<{ data: any[] }>({ queryKey: ['/api/ou-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: domainData } = useQuery<{ data: any[] }>({ queryKey: ['/api/domain-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: siteData } = useQuery<{ data: any[] }>({ queryKey: ['/api/site-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); const { data: subnetData } = useQuery<{ data: any[] }>({ queryKey: ['/api/subnet-dashboard-data'], enabled: !isLoadingConnections && connections.length > 0, }); // Load saved dashboards useEffect(() => { const savedDashboards = localStorage.getItem('dashboards'); if (savedDashboards) { try { setDashboards(JSON.parse(savedDashboards)); } catch (error) { console.error('Error parsing saved dashboards:', error); setDashboards([]); } } }, []); // Set initial active tab useEffect(() => { if (dashboards.length > 0 && activeTab === "dashboard-overview") { setActiveTab(dashboards[0].id); } }, [dashboards, activeTab]); // Prepare data sources when data is loaded useEffect(() => { if (userData || computerData || groupData || ouData || domainData || siteData || subnetData) { const sources = []; if (userData?.data) { sources.push({ id: 'users', name: 'Users', data: userData.data, fields: getFieldTypes(userData.data), }); } if (computerData?.data) { sources.push({ id: 'computers', name: 'Computers', data: computerData.data, fields: getFieldTypes(computerData.data), }); } if (groupData?.data) { sources.push({ id: 'groups', name: 'Groups', data: groupData.data, fields: getFieldTypes(groupData.data), }); } if (ouData?.data) { sources.push({ id: 'ous', name: 'Organizational Units', data: ouData.data, fields: getFieldTypes(ouData.data), }); } if (domainData?.data) { sources.push({ id: 'domains', name: 'Domains', data: domainData.data, fields: getFieldTypes(domainData.data), }); } if (siteData?.data) { sources.push({ id: 'sites', name: 'Sites', data: siteData.data, fields: getFieldTypes(siteData.data), }); } if (subnetData?.data) { sources.push({ id: 'subnets', name: 'Subnets', data: subnetData.data, fields: getFieldTypes(subnetData.data), }); } setDataSources(sources); setDataSourcesLoading(false); } }, [userData, computerData, groupData, ouData, domainData, siteData, subnetData]); function getFieldTypes(data: any[]): Array<{ name: string; type: string }> { if (!data || data.length === 0) return []; const sample = data[0]; return Object.keys(sample).map(key => { const value = sample[key]; let type = 'string'; if (typeof value === 'number') { type = 'number'; } else if (typeof value === 'boolean') { type = 'boolean'; } else if (value instanceof Date) { type = 'date'; } else if (Array.isArray(value)) { type = 'array'; } else if (value !== null && typeof value === 'object') { type = 'object'; } return { name: key, type }; }); } const handleCreateDashboard = (values: DashboardFormValues) => { const newDashboard: DashboardConfig = { id: uuidv4(), name: values.name, layouts: { lg: [] }, widgets: [], branding: { title: values.name, showHeader: true, logo: null, primaryColor: '#3b82f6', secondaryColor: '#f59e0b', showFooter: true, footerText: `Created by AD Management API - ${new Date().toLocaleDateString()}` } }; const updatedDashboards = [...dashboards, newDashboard]; setDashboards(updatedDashboards); setActiveTab(newDashboard.id); setIsCreateDialogOpen(false); form.reset(); // Save to localStorage localStorage.setItem('dashboards', JSON.stringify(updatedDashboards)); toast({ title: "Dashboard Created", description: `New dashboard "${values.name}" has been created.`, }); }; const handleSaveDashboard = (updatedConfig: DashboardConfig) => { const updatedDashboards = dashboards.map(dashboard => dashboard.id === updatedConfig.id ? updatedConfig : dashboard ); setDashboards(updatedDashboards); // Save to localStorage localStorage.setItem('dashboards', JSON.stringify(updatedDashboards)); toast({ title: "Dashboard Saved", description: "Your changes have been saved.", }); }; const confirmDelete = (dashboardId: string) => { setDashboardToDelete(dashboardId); setDeleteDialogOpen(true); }; const executeDelete = () => { if (!dashboardToDelete) return; const updatedDashboards = dashboards.filter(dashboard => dashboard.id !== dashboardToDelete); setDashboards(updatedDashboards); setDeleteDialogOpen(false); if (updatedDashboards.length > 0) { setActiveTab(updatedDashboards[0].id); } else { setActiveTab("dashboard-overview"); } setDashboardToDelete(null); // Save to localStorage localStorage.setItem('dashboards', JSON.stringify(updatedDashboards)); toast({ title: "Dashboard Deleted", description: "The dashboard has been deleted.", }); }; return (

Custom Dashboards

{dashboards.length > 0 && activeTab !== "dashboard-overview" && ( <> d.id === activeTab)?.name || "Custom Dashboard"} /> )}
{dashboards.map((dashboard) => ( {dashboard.name} ))} {dashboards.length === 0 ? (

No Dashboards Created

Create your first dashboard to start visualizing data

) : ( dashboards.map((dashboard) => ( {dataSourcesLoading ? (
Loading data sources...
) : (
)}
)) )}
{/* Create Dashboard Dialog */} Create New Dashboard
( Dashboard Name Give your dashboard a descriptive name. )} />
{/* Delete Dashboard Confirmation */} Are you sure? This action cannot be undone. This will permanently delete the dashboard and all its widgets. setDashboardToDelete(null)}> Cancel Delete
); }