Initial Release

This commit is contained in:
Tola Leng
2025-05-09 21:28:07 +07:00
commit 0c6cd18e6f
244 changed files with 50633 additions and 0 deletions
@@ -0,0 +1,73 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { Service } from "@/services/serviceService";
import { StatusCards } from "./StatusCards";
import { ServiceFilters } from "./ServiceFilters";
import { ServicesTable } from "./ServicesTable";
import { AddServiceDialog } from "@/components/services/AddServiceDialog";
interface DashboardContentProps {
services: Service[];
isLoading: boolean;
error: Error | null;
}
export const DashboardContent = ({ services, isLoading, error }: DashboardContentProps) => {
const [filter, setFilter] = useState<string>("all");
const [searchTerm, setSearchTerm] = useState<string>("");
const [isAddDialogOpen, setIsAddDialogOpen] = useState<boolean>(false);
// Filter services based on search term and type filter
const filteredServices = services.filter(service => {
const matchesSearch = service.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(service.url && service.url.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesFilter = filter === 'all' || service.type.toLowerCase() === filter.toLowerCase();
return matchesSearch && matchesFilter;
});
if (error) {
return (
<div className="flex flex-col items-center justify-center h-full gap-4 text-foreground">
<p>Error loading service data.</p>
<Button onClick={() => window.location.reload()}>Retry</Button>
</div>
);
}
return (
<main className="flex-1 flex flex-col overflow-auto bg-background p-6 pb-0">
<div className="flex flex-col flex-1">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-foreground">Overview</h2>
<Button
className="text-primary-foreground"
onClick={() => setIsAddDialogOpen(true)}
>
<Plus className="w-4 h-4 mr-2" /> New Service
</Button>
</div>
<StatusCards services={services} />
<ServiceFilters
filter={filter}
setFilter={setFilter}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
servicesCount={filteredServices.length}
/>
<div className="flex-1 flex flex-col pb-6">
<ServicesTable services={filteredServices} />
</div>
</div>
<AddServiceDialog
open={isAddDialogOpen}
onOpenChange={setIsAddDialogOpen}
/>
</main>
);
};
@@ -0,0 +1,190 @@
import { Button } from "@/components/ui/button";
import { AuthUser } from "@/services/authService";
import { useTheme } from "@/contexts/ThemeContext";
import { Moon, PanelLeft, PanelLeftClose, Sun, Globe, FileText, Github, Twitter, MessageSquare, Bell, User, Settings, LogOut, Grid3x3 } from "lucide-react";
import { useLanguage } from "@/contexts/LanguageContext";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { useEffect, useState } from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useNavigate } from "react-router-dom";
import { useSystemSettings } from "@/hooks/useSystemSettings";
interface HeaderProps {
currentUser: AuthUser | null;
onLogout: () => void;
sidebarCollapsed: boolean;
toggleSidebar: () => void;
}
export const Header = ({
currentUser,
onLogout,
sidebarCollapsed,
toggleSidebar
}: HeaderProps) => {
const { theme, toggleTheme } = useTheme();
const { language, setLanguage, t } = useLanguage();
const [greeting, setGreeting] = useState<string>("");
const { systemName } = useSystemSettings();
const navigate = useNavigate();
// Set greeting based on time of day
useEffect(() => {
const updateGreeting = () => {
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
setGreeting(t("goodMorning"));
} else if (hour >= 12 && hour < 18) {
setGreeting(t("goodAfternoon"));
} else {
setGreeting(t("goodEvening"));
}
};
updateGreeting();
// Update greeting if language changes
// You could add a timer to update the greeting throughout the day
// but that's typically unnecessary since most sessions aren't active across time periods
}, [language, t]);
// Log avatar data for debugging
useEffect(() => {
if (currentUser) {
console.log("Avatar URL in Header:", currentUser.avatar);
}
}, [currentUser]);
// Prepare avatar URL - ensure it displays correctly if it's a local profile image
let avatarUrl = '';
if (currentUser?.avatar) {
// If it's a relative path from the public folder, make sure it's resolved properly
if (currentUser.avatar.startsWith('/upload/profile/')) {
avatarUrl = currentUser.avatar;
} else {
avatarUrl = currentUser.avatar;
}
console.log("Final avatar URL:", avatarUrl);
}
return (
<header className="relative bg-background border-b border-border px-6 flex justify-between items-center py-[12px] overflow-hidden">
{/* Grid Pattern Overlay - Similar to StatusCards */}
<div className="absolute inset-0 z-0">
<div
className="w-full h-full"
style={{
backgroundImage: `linear-gradient(${theme === 'dark' ? '#ffffff10' : '#00000010'} 1px, transparent 1px),
linear-gradient(90deg, ${theme === 'dark' ? '#ffffff10' : '#00000010'} 1px, transparent 1px)`,
backgroundSize: '20px 20px'
}}
>
<div className="w-full h-full backdrop-blur-[1px]"></div>
</div>
</div>
{/* Header Content */}
<div className="flex items-center gap-4 z-10">
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="mr-2">
{sidebarCollapsed ? <PanelLeft className="h-5 w-5" /> : <PanelLeftClose className="h-5 w-5" />}
</Button>
<div className="flex items-center space-x-2">
<Grid3x3 className="h-5 w-5 text-green-500" />
<h1 className="text-lg font-medium">{greeting}, {currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} 👋 </h1>
</div>
</div>
<div className="flex items-center space-x-4 z-10">
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border" onClick={toggleTheme}>
<span className="sr-only">Toggle theme</span>
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">{t("language")}</span>
<Globe className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setLanguage("en")} className={language === "en" ? "bg-accent" : ""}>
{t("english")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLanguage("km")} className={language === "km" ? "bg-accent" : ""}>
{t("khmer")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* Documentation */}
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">{t("documentation")}</span>
<FileText className="w-4 h-4" />
</Button>
{/* GitHub */}
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">GitHub</span>
<Github className="w-4 h-4" />
</Button>
{/* X (Twitter) */}
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">X (Twitter)</span>
<Twitter className="w-4 h-4" />
</Button>
{/* Discord (replaced with MessageSquare) */}
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">Discord</span>
<MessageSquare className="w-4 h-4" />
</Button>
{/* Notifications */}
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border">
<span className="sr-only">{t("notifications")}</span>
<Bell className="w-4 h-4" />
</Button>
{/* User Profile Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="h-8 w-8 cursor-pointer ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground">
{avatarUrl ? <AvatarImage src={avatarUrl} alt={currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} /> : <AvatarFallback className="bg-primary/20 text-primary">
{currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'}
</AvatarFallback>}
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<div className="flex items-center gap-3 p-2">
<Avatar className="h-10 w-10">
{avatarUrl ? <AvatarImage src={avatarUrl} alt={currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} /> : <AvatarFallback className="bg-primary/20 text-primary">
{currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'}
</AvatarFallback>}
</Avatar>
<div className="flex flex-col space-y-1">
<span className="font-medium">{currentUser?.name || 'User'}</span>
<span className="text-xs text-muted-foreground truncate">{currentUser?.email}</span>
</div>
</div>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => navigate("/profile")}>
<User className="mr-2 h-4 w-4" />
<span>{t("profile")}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate("/settings")}>
<Settings className="mr-2 h-4 w-4" />
<span>{t("settings")}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onLogout} className="text-red-500 focus:text-red-500">
<LogOut className="mr-2 h-4 w-4" />
<span>{t("logout")}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
);
};
@@ -0,0 +1,54 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";
import { Plus } from "lucide-react";
interface ServiceFiltersProps {
filter: string;
setFilter: (value: string) => void;
searchTerm: string;
setSearchTerm: (value: string) => void;
servicesCount: number;
}
export const ServiceFilters = ({
filter,
setFilter,
searchTerm,
setSearchTerm,
servicesCount
}: ServiceFiltersProps) => {
return (
<div className="mb-6 flex justify-between items-center">
<div className="flex items-center">
<h3 className="text-xl font-semibold mr-2 text-foreground">Currently Monitoring</h3>
<span className="bg-secondary text-secondary-foreground px-2 py-0.5 rounded text-sm">
{servicesCount}
</span>
</div>
<div className="flex space-x-4">
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className="w-40 bg-card border-border">
<SelectValue placeholder="All Types" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="HTTP">HTTP</SelectItem>
<SelectItem value="PING">PING</SelectItem>
<SelectItem value="TCP">TCP</SelectItem>
<SelectItem value="DNS">DNS</SelectItem>
</SelectContent>
</Select>
<div className="relative">
<Input
className="w-72 bg-card border-border"
placeholder="Search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
</div>
);
};
@@ -0,0 +1,15 @@
import { Service } from "@/types/service.types";
import { ServicesTableContainer } from "@/components/services/ServicesTableContainer";
interface ServicesTableProps {
services: Service[];
}
export const ServicesTable = ({ services }: ServicesTableProps) => {
return (
<div className="flex-1 flex flex-col h-full">
<ServicesTableContainer services={services} />
</div>
);
};
@@ -0,0 +1,141 @@
import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, FileText, Settings, User, UserCog, Bell, FileClock, Database, RefreshCw, Info, ChevronDown, BookOpen } from "lucide-react";
import { useTheme } from "@/contexts/ThemeContext";
import { Link, useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useLanguage } from "@/contexts/LanguageContext";
interface SidebarProps {
collapsed: boolean;
}
export const Sidebar = ({
collapsed
}: SidebarProps) => {
const {
theme
} = useTheme();
const {
t
} = useLanguage();
const location = useLocation();
const [activeSettingsItem, setActiveSettingsItem] = useState<string | null>("general");
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false);
// Update active settings item based on URL
useEffect(() => {
if (location.pathname === '/settings') {
const params = new URLSearchParams(location.search);
const panel = params.get('panel');
if (panel) {
setActiveSettingsItem(panel);
}
}
}, [location]);
const handleSettingsItemClick = (item: string) => {
setActiveSettingsItem(item);
};
const getMenuItemClasses = (isActive: boolean) => {
return `p-2 ${isActive ? theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent'}`} rounded-lg flex items-center`;
};
// New larger icon size for the main menu
const mainIconSize = "h-6 w-6";
return <div className={`${collapsed ? 'w-16' : 'w-64'} ${theme === 'dark' ? 'bg-[#121212] border-[#1e1e1e]' : 'bg-sidebar border-sidebar-border'} border-r flex flex-col transition-all duration-300 h-full`}>
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
<div className="h-8 w-8 bg-green-500 rounded flex items-center justify-center mr-2">
<span className="text-white font-bold">C</span>
</div>
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
</div>
<nav className="my-2 mx-1 py-1 px-1">
<Link to="/dashboard" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/dashboard' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<Globe className={`${mainIconSize} text-purple-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("uptimeMonitoring")}</span>}
</Link>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<Boxes className={`${mainIconSize} text-blue-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("instanceMonitoring")}</span>}
</div>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<Radar className={`${mainIconSize} text-cyan-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("sslDomain")}</span>}
</div>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<Calendar className={`${mainIconSize} text-emerald-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("scheduleIncident")}</span>}
</div>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<BarChart2 className={`${mainIconSize} text-amber-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("operationalPage")}</span>}
</div>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<LineChart className={`${mainIconSize} text-rose-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("reports")}</span>}
</div>
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
<FileText className={`${mainIconSize} text-indigo-400`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("apiDocumentation")}</span>}
</div>
</nav>
{!collapsed && <div className={`flex-1 flex flex-col border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4`}>
<Collapsible open={settingsPanelOpen} onOpenChange={setSettingsPanelOpen} className="w-full flex flex-col flex-1">
<CollapsibleTrigger className={`flex items-center justify-between w-full mb-4 px-2 py-2 rounded-lg ${theme === 'dark' ? 'hover:bg-[#1a1a1a]' : 'hover:bg-sidebar-accent'}`}>
<div className="flex items-center">
<span className="font-medium tracking-wide">{t("settingPanel")}</span>
</div>
<div className="flex items-center">
<Settings className="h-4 w-4 mr-1" />
<ChevronDown className={`h-4 w-4 transition-transform duration-200 ${settingsPanelOpen ? 'rotate-180' : ''}`} />
</div>
</CollapsibleTrigger>
<CollapsibleContent className={`${theme === 'dark' ? 'bg-[#121212]' : 'bg-sidebar'} flex-1 flex flex-col`}>
<div className="max-h-[300px] overflow-y-auto custom-scrollbar relative pr-1">
<ScrollArea className="h-full">
<div className="space-y-2 pr-4">
<Link to={`/settings?panel=general`} className={getMenuItemClasses(activeSettingsItem === 'general')} onClick={() => handleSettingsItemClick('general')}>
<Settings className="h-4 w-4 mr-2" />
<span className="text-sm">{t("generalSettings")}</span>
</Link>
<Link to={`/settings?panel=users`} className={getMenuItemClasses(activeSettingsItem === 'users')} onClick={() => handleSettingsItemClick('users')}>
<User className="h-4 w-4 mr-2" />
<span className="text-sm">{t("userManagement")}</span>
</Link>
<Link to={`/settings?panel=notifications`} className={getMenuItemClasses(activeSettingsItem === 'notifications')} onClick={() => handleSettingsItemClick('notifications')}>
<Bell className="h-4 w-4 mr-2" />
<span className="text-sm">{t("notificationSettings")}</span>
</Link>
<Link to={`/settings?panel=templates`} className={getMenuItemClasses(activeSettingsItem === 'templates')} onClick={() => handleSettingsItemClick('templates')}>
<BookOpen className="h-4 w-4 mr-2" />
<span className="text-sm">{t("alertsTemplates")}</span>
</Link>
<div className={getMenuItemClasses(false)}>
<Database className="h-4 w-4 mr-2" />
<span className="text-sm">{t("dataRetention")}</span>
</div>
<Link to={`/settings?panel=about`} className={getMenuItemClasses(activeSettingsItem === 'about')} onClick={() => handleSettingsItemClick('about')}>
<Info className="h-4 w-4 mr-2" />
<span className="text-sm">{t("aboutSystem")}</span>
</Link>
</div>
</ScrollArea>
</div>
</CollapsibleContent>
</Collapsible>
</div>}
{collapsed && <div className={`border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4 flex justify-center`}>
<Link to="/settings">
<Settings className={`${mainIconSize} text-purple-400`} />
</Link>
</div>}
</div>;
};
@@ -0,0 +1,152 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ArrowUp, ArrowDown, Pause, AlertTriangle } from "lucide-react";
import { Service } from "@/services/serviceService";
import { useTheme } from "@/contexts/ThemeContext";
interface StatusCardsProps {
services: Service[];
}
export const StatusCards = ({ services }: StatusCardsProps) => {
// Count services by status
const upServices = services.filter(s => s.status === "up").length;
const downServices = services.filter(s => s.status === "down").length;
const pausedServices = services.filter(s => s.status === "paused").length;
const warningServices = services.filter(s => s.responseTime > 1000).length;
// Get current theme to adjust card styles
const { theme } = useTheme();
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 w-full">
{/* Up Services Card */}
<Card
className={`border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 ${
theme === 'dark' ? 'dark-card' : ''
} relative z-10`}
style={{
background: theme === 'dark'
? "linear-gradient(135deg, rgba(67, 160, 71, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
: "linear-gradient(135deg, #43a047 0%, #66bb6a 100%)"
}}
>
{/* Grid Pattern Overlay */}
<div className="absolute inset-0 z-0 opacity-10">
<div className="w-full h-full"
style={{
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
linear-gradient(90deg, #fff 1px, transparent 1px)`,
backgroundSize: '20px 20px'
}}
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">UP SERVICES</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{upServices}</span>
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
<ArrowUp className="h-6 w-6 text-white" />
</div>
</CardContent>
</Card>
{/* Down Services Card */}
<Card
className={`border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 ${
theme === 'dark' ? 'dark-card' : ''
} relative z-10`}
style={{
background: theme === 'dark'
? "linear-gradient(135deg, rgba(229, 57, 53, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
: "linear-gradient(135deg, #e53935 0%, #ef5350 100%)"
}}
>
{/* Grid Pattern Overlay */}
<div className="absolute inset-0 z-0 opacity-10">
<div className="w-full h-full"
style={{
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
linear-gradient(90deg, #fff 1px, transparent 1px)`,
backgroundSize: '20px 20px'
}}
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">DOWN SERVICES</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{downServices}</span>
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
<ArrowDown className="h-6 w-6 text-white" />
</div>
</CardContent>
</Card>
{/* Paused Services Card */}
<Card
className={`border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 ${
theme === 'dark' ? 'dark-card' : ''
} relative z-10`}
style={{
background: theme === 'dark'
? "linear-gradient(135deg, rgba(25, 118, 210, 0.8) 0%, rgba(66, 165, 245, 0.6) 100%)"
: "linear-gradient(135deg, #1976d2 0%, #42a5f5 100%)"
}}
>
{/* Grid Pattern Overlay */}
<div className="absolute inset-0 z-0 opacity-10">
<div className="w-full h-full"
style={{
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
linear-gradient(90deg, #fff 1px, transparent 1px)`,
backgroundSize: '20px 20px'
}}
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">PAUSED SERVICES</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{pausedServices}</span>
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
<Pause className="h-6 w-6 text-white" />
</div>
</CardContent>
</Card>
{/* Warning Services Card */}
<Card
className={`border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 ${
theme === 'dark' ? 'dark-card' : ''
} relative z-10`}
style={{
background: theme === 'dark'
? "linear-gradient(135deg, rgba(255, 152, 0, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
: "linear-gradient(135deg, #ff9800 0%, #ffb74d 100%)"
}}
>
{/* Grid Pattern Overlay */}
<div className="absolute inset-0 z-0 opacity-10">
<div className="w-full h-full"
style={{
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
linear-gradient(90deg, #fff 1px, transparent 1px)`,
backgroundSize: '20px 20px'
}}
></div>
</div>
<CardHeader className="pb-2 relative z-10">
<CardTitle className="text-sm font-medium text-white">WARNING SERVICES</CardTitle>
</CardHeader>
<CardContent className="flex items-center justify-between relative z-10">
<span className="text-5xl font-bold text-white">{warningServices}</span>
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
<AlertTriangle className="h-6 w-6 text-white" />
</div>
</CardContent>
</Card>
</div>
);
};