From 205585814f4435618d795ce524333c024f3f531c Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Thu, 10 Apr 2025 01:36:04 +0000 Subject: [PATCH] Update main dashboard to display correct metrics and information. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/6e2e879c-f85c-413f-b67d-151c3c3cc987.jpg --- client/src/App.tsx | 7 +- client/src/pages/custom-dashboards-page.tsx | 445 ++++++++++++++++++++ client/src/pages/main-dashboard-page.tsx | 277 ++++++++++++ 3 files changed, 726 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/custom-dashboards-page.tsx create mode 100644 client/src/pages/main-dashboard-page.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 3972b9d..5f233a7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -7,7 +7,8 @@ import { queryClient } from "./lib/queryClient"; import ProtectedRoute from "./lib/protected-route"; import NotFound from "@/pages/not-found"; import AuthPage from "@/pages/auth-page"; -import DashboardPage from "@/pages/dashboard-page"; +import MainDashboardPage from "@/pages/main-dashboard-page"; +import CustomDashboardsPage from "@/pages/custom-dashboards-page"; import UsersPage from "@/pages/users-page"; import GroupsPage from "@/pages/groups-page"; import OUsPage from "@/pages/ous-page"; @@ -31,7 +32,7 @@ export default function App() { - + @@ -44,7 +45,7 @@ export default function App() { - + diff --git a/client/src/pages/custom-dashboards-page.tsx b/client/src/pages/custom-dashboards-page.tsx new file mode 100644 index 0000000..18e4e77 --- /dev/null +++ b/client/src/pages/custom-dashboards-page.tsx @@ -0,0 +1,445 @@ +import { useState, useEffect } 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 { 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 [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, + layout: { 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 ( + + + + window.history.back()} + className="mr-2" + > + + Back + + Custom Dashboards + + + {dashboards.length > 0 && activeTab !== "dashboard-overview" && ( + confirmDelete(activeTab)} + className="text-destructive hover:bg-destructive/10" + > + + Delete Dashboard + + )} + setIsCreateDialogOpen(true)}> + + New Dashboard + + + + + + + {dashboards.map((dashboard) => ( + + {dashboard.name} + + ))} + + + {dashboards.length === 0 ? ( + + No Dashboards Created + Create your first dashboard to start visualizing data + setIsCreateDialogOpen(true)}> + + Create Dashboard + + + ) : ( + dashboards.map((dashboard) => ( + + {dataSourcesLoading ? ( + + + Loading data sources... + + ) : ( + + )} + + )) + )} + + + {/* Create Dashboard Dialog */} + + + + Create New Dashboard + + + + ( + + Dashboard Name + + + + + Give your dashboard a descriptive name. + + + + )} + /> + + setIsCreateDialogOpen(false)} + > + Cancel + + Create Dashboard + + + + + + + {/* 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 + + + + + + ); +} \ No newline at end of file diff --git a/client/src/pages/main-dashboard-page.tsx b/client/src/pages/main-dashboard-page.tsx new file mode 100644 index 0000000..45a145a --- /dev/null +++ b/client/src/pages/main-dashboard-page.tsx @@ -0,0 +1,277 @@ +import React from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + BarChart, + Bar, + PieChart, + Pie, + Cell, + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from "recharts"; +import { + Users, + Monitor, + UserPlus, + Globe, + Building2, + Network, + AlertCircle, + Activity, + Loader2 +} from "lucide-react"; +import { DashboardLayout } from "@/layouts/dashboard-layout"; + +interface DashboardSummary { + userCount: number; + computerCount: number; + groupCount: number; + sitesCount: number; + domainsCount: number; + subnetsCount: number; + message?: string; +} + +export default function MainDashboardPage() { + // Fetch summary data + const { data: summary, isLoading: summaryLoading } = useQuery({ + queryKey: ['/api/dashboard-summary'], + retry: false + }); + + // Fetch Users Data + const { data: usersData, isLoading: usersLoading } = useQuery<{data: any[]}>({ + queryKey: ['/api/user-dashboard-data'], + retry: false + }); + + // Fetch Computers Data + const { data: computersData, isLoading: computersLoading } = useQuery<{data: any[]}>({ + queryKey: ['/api/computer-dashboard-data'], + retry: false + }); + + // Fetch Groups Data + const { data: groupsData, isLoading: groupsLoading } = useQuery<{data: any[]}>({ + queryKey: ['/api/group-dashboard-data'], + retry: false + }); + + const isLoading = summaryLoading || usersLoading || computersLoading || groupsLoading; + + const usersByOU = React.useMemo(() => { + if (!usersData?.data) return []; + + // Group users by organizational unit + const ouGroups: Record = {}; + + usersData.data.forEach(user => { + const ou = user.organizationalUnit || 'None'; + ouGroups[ou] = (ouGroups[ou] || 0) + 1; + }); + + // Convert to array format for charts + return Object.entries(ouGroups).map(([name, value]) => ({ name, value })); + }, [usersData]); + + const computersByOS = React.useMemo(() => { + if (!computersData?.data) return []; + + // Group computers by operating system + const osGroups: Record = {}; + + computersData.data.forEach(computer => { + const os = computer.operatingSystem || 'Unknown'; + osGroups[os] = (osGroups[os] || 0) + 1; + }); + + // Convert to array format for charts + return Object.entries(osGroups).map(([name, value]) => ({ name, value })); + }, [computersData]); + + const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#A569BD', '#5DADE2', '#F4D03F']; + + return ( + + {isLoading ? ( + + + Loading dashboard data... + + ) : ( + <> + {/* Summary Cards */} + + + + Users + + + + {summary?.userCount || 0} + Total active users + + + + + + Computers + + + + {summary?.computerCount || 0} + Total computers + + + + + + Groups + + + + {summary?.groupCount || 0} + Total groups + + + + + + Domains + + + + {summary?.domainsCount || 0} + Total domains + + + + + + Sites + + + + {summary?.sitesCount || 0} + Total sites + + + + + + Subnets + + + + {summary?.subnetsCount || 0} + Total subnets + + + + + {/* Charts */} + + + + Users by Organizational Unit + + Distribution of users across organizational units + + + + {usersByOU.length > 0 ? ( + + + `${name}: ${(percent * 100).toFixed(0)}%`} + outerRadius={80} + fill="#8884d8" + dataKey="value" + > + {usersByOU.map((entry, index) => ( + + ))} + + + + + + ) : ( + + + No data available + + )} + + + + + + Computers by Operating System + + Distribution of computer operating systems + + + + {computersByOS.length > 0 ? ( + + + + + + + + + + + ) : ( + + + No data available + + )} + + + + + {/* Recent Activity */} + + + + + Recent Activity + + + Most recent changes in your Active Directory environment + + + + + {/* This would show recent audit logs */} + + View detailed logs in the Audit Logs section + + + + + > + )} + + ); +} \ No newline at end of file
Create your first dashboard to start visualizing data
Total active users
Total computers
Total groups
Total domains
Total sites
Total subnets