Fix responsive breakpoints and flex layout for sidebar menu
This commit is contained in:
@@ -1,71 +1,59 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { AuthUser } from "@/services/authService";
|
import { AuthUser } from "@/services/authService";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { Moon, PanelLeft, PanelLeftClose, Sun, Globe, FileText, Github, Twitter, MessageSquare, Bell, User, Settings, LogOut } from "lucide-react";
|
import {
|
||||||
|
Moon, PanelLeft, PanelLeftClose, Sun, Globe, FileText,
|
||||||
|
Github, Twitter, MessageSquare, Bell, User, Settings,
|
||||||
|
LogOut, Menu, X
|
||||||
|
} from "lucide-react";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
|
import {
|
||||||
|
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger, DropdownMenuSeparator
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useSystemSettings } from "@/hooks/useSystemSettings";
|
import { useSystemSettings } from "@/hooks/useSystemSettings";
|
||||||
|
import { useSidebar } from "@/contexts/SidebarContext";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
currentUser: AuthUser | null;
|
currentUser: AuthUser | null;
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
sidebarCollapsed: boolean;
|
|
||||||
toggleSidebar: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({
|
export const Header = ({
|
||||||
currentUser,
|
currentUser,
|
||||||
onLogout,
|
onLogout,
|
||||||
sidebarCollapsed,
|
|
||||||
toggleSidebar
|
|
||||||
}: HeaderProps) => {
|
}: HeaderProps) => {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
const { language, setLanguage, t } = useLanguage();
|
const { language, setLanguage, t } = useLanguage();
|
||||||
|
const { sidebarCollapsed, toggleSidebar, isMobileOpen, toggleMobileMenu } = useSidebar();
|
||||||
const [greeting, setGreeting] = useState<string>("");
|
const [greeting, setGreeting] = useState<string>("");
|
||||||
const { systemName } = useSystemSettings();
|
const { systemName } = useSystemSettings();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Set greeting based on time of day
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateGreeting = () => {
|
const updateGreeting = () => {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
if (hour >= 5 && hour < 12) {
|
if (hour >= 5 && hour < 12) setGreeting(t("goodMorning"));
|
||||||
setGreeting(t("goodMorning"));
|
else if (hour >= 12 && hour < 18) setGreeting(t("goodAfternoon"));
|
||||||
} else if (hour >= 12 && hour < 18) {
|
else setGreeting(t("goodEvening"));
|
||||||
setGreeting(t("goodAfternoon"));
|
|
||||||
} else {
|
|
||||||
setGreeting(t("goodEvening"));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
updateGreeting();
|
updateGreeting();
|
||||||
|
|
||||||
}, [language, t]);
|
}, [language, t]);
|
||||||
|
|
||||||
// Log avatar data for debugging
|
const avatarUrl = currentUser?.avatar || '';
|
||||||
useEffect(() => {
|
|
||||||
if (currentUser) {
|
|
||||||
|
|
||||||
}
|
const openExternalLink = (url: string) => {
|
||||||
}, [currentUser]);
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
let avatarUrl = '';
|
|
||||||
if (currentUser?.avatar) {
|
|
||||||
|
|
||||||
if (currentUser.avatar.startsWith('/upload/profile/')) {
|
|
||||||
avatarUrl = currentUser.avatar;
|
|
||||||
} else {
|
|
||||||
avatarUrl = currentUser.avatar;
|
|
||||||
}
|
|
||||||
console.log("Final avatar URL:", avatarUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="relative bg-background border-b border-border px-6 flex justify-between items-center py-[12px] overflow-hidden">
|
<header className="relative bg-background border-b border-border px-4 lg:px-6 flex justify-between items-center h-16 shrink-0 z-30 overflow-hidden">
|
||||||
{/* Grid Pattern Overlay - Similar to StatusCards */}
|
{/* Grid Pattern Overlay */}
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0 pointer-events-none opacity-20">
|
||||||
<div
|
<div
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
@@ -78,19 +66,74 @@ export const Header = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Header Content */}
|
<div className="flex items-center gap-2 lg:gap-4 z-10">
|
||||||
<div className="flex items-center gap-4 z-10">
|
{/* Mobile Toggle */}
|
||||||
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="mr-2">
|
<Button variant="ghost" size="icon" onClick={toggleMobileMenu} className="lg:hidden h-9 w-9">
|
||||||
|
{isMobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Desktop Toggle */}
|
||||||
|
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="hidden lg:flex h-9 w-9">
|
||||||
{sidebarCollapsed ? <PanelLeft className="h-5 w-5" /> : <PanelLeftClose className="h-5 w-5" />}
|
{sidebarCollapsed ? <PanelLeft className="h-5 w-5" /> : <PanelLeftClose className="h-5 w-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center">
|
||||||
<h1 className="text-lg font-medium">{greeting}, {currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} 👋 ✨</h1>
|
<h1 className="text-sm lg:text-lg font-medium truncate max-w-[150px] lg:max-w-none">
|
||||||
|
{greeting}, {currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} 👋 ✨
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4 z-10">
|
<div className="flex items-center space-x-1 lg:space-x-3 z-10">
|
||||||
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border" onClick={toggleTheme}>
|
{/* External Links - Hidden on small screens */}
|
||||||
|
<div className="hidden md:flex items-center space-x-1 mr-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full w-8 h-8 border-border"
|
||||||
|
onClick={() => openExternalLink('https://docs.checkcle.io')}
|
||||||
|
title={t("documentation")}
|
||||||
|
>
|
||||||
|
<FileText className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full w-8 h-8 border-border"
|
||||||
|
onClick={() => openExternalLink('https://github.com/operacle/checkcle')}
|
||||||
|
title="GitHub"
|
||||||
|
>
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full w-8 h-8 border-border"
|
||||||
|
onClick={() => openExternalLink('https://x.com/checkcle_oss')}
|
||||||
|
title="X (Twitter)"
|
||||||
|
>
|
||||||
|
<Twitter className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full w-8 h-8 border-border"
|
||||||
|
onClick={() => openExternalLink('https://discord.gg/xs9gbubGwX')}
|
||||||
|
title="Discord"
|
||||||
|
>
|
||||||
|
<MessageSquare className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full w-8 h-8 border-border"
|
||||||
|
title={t("notifications")}
|
||||||
|
>
|
||||||
|
<Bell className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="outline" size="icon" className="rounded-full w-8 h-8 border-border hidden sm:flex" onClick={toggleTheme}>
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -102,7 +145,7 @@ export const Header = ({
|
|||||||
<Globe className="w-4 h-4" />
|
<Globe className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end" className="w-40">
|
||||||
<DropdownMenuItem onClick={() => setLanguage("en")} className={language === "en" ? "bg-accent" : ""}>
|
<DropdownMenuItem onClick={() => setLanguage("en")} className={language === "en" ? "bg-accent" : ""}>
|
||||||
{t("english")}
|
{t("english")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -110,7 +153,7 @@ export const Header = ({
|
|||||||
{t("khmer")}
|
{t("khmer")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setLanguage("de")} className={language === "de" ? "bg-accent" : ""}>
|
<DropdownMenuItem onClick={() => setLanguage("de")} className={language === "de" ? "bg-accent" : ""}>
|
||||||
{t("Deutsch")}
|
{t("german")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setLanguage("ko")} className={language === "ko" ? "bg-accent" : ""}>
|
<DropdownMenuItem onClick={() => setLanguage("ko")} className={language === "ko" ? "bg-accent" : ""}>
|
||||||
{t("korean")}
|
{t("korean")}
|
||||||
@@ -124,74 +167,21 @@ export const Header = ({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
{/* Documentation */}
|
<div className="h-8 w-px bg-border mx-1 hidden sm:block" />
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
className="rounded-full w-8 h-8 border-border"
|
|
||||||
onClick={() => window.open("https://docs.checkcle.io", "_blank")}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
onClick={() => window.open("https://github.com/operacle/checkcle", "_blank")}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
onClick={() => window.open("https://x.com/checkcle_oss", "_blank")}
|
|
||||||
>
|
|
||||||
<span className="sr-only">X (Twitter)</span>
|
|
||||||
<Twitter className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* Discord */}
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
className="rounded-full w-8 h-8 border-border"
|
|
||||||
onClick={() => window.open("https://discord.gg/xs9gbubGwX", "_blank")}
|
|
||||||
>
|
|
||||||
<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>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Avatar className="h-8 w-8 cursor-pointer ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground">
|
<Avatar className="h-8 w-8 cursor-pointer border hover:ring-2 hover:ring-primary/20 transition-all">
|
||||||
{avatarUrl ? <AvatarImage src={avatarUrl} alt={currentUser?.name || currentUser?.email?.split('@')[0] || 'User'} /> : <AvatarFallback className="bg-primary/20 text-primary">
|
{avatarUrl ? <AvatarImage src={avatarUrl} alt="User" /> : <AvatarFallback className="bg-primary/20 text-primary">{currentUser?.name?.[0] || 'U'}</AvatarFallback>}
|
||||||
{currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'}
|
|
||||||
</AvatarFallback>}
|
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-56">
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
<div className="flex items-center gap-3 p-2">
|
<div className="flex items-center gap-3 p-2">
|
||||||
<Avatar className="h-10 w-10">
|
<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">
|
{avatarUrl ? <AvatarImage src={avatarUrl} alt="User" /> : <AvatarFallback className="bg-primary/20 text-primary">{currentUser?.name?.[0] || 'U'}</AvatarFallback>}
|
||||||
{currentUser?.name?.[0] || currentUser?.email?.[0] || 'U'}
|
|
||||||
</AvatarFallback>}
|
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-0.5">
|
||||||
<span className="font-medium">{currentUser?.name || 'User'}</span>
|
<span className="text-sm font-medium truncate">{currentUser?.name || 'User'}</span>
|
||||||
<span className="text-xs text-muted-foreground truncate">{currentUser?.email}</span>
|
<span className="text-xs text-muted-foreground truncate">{currentUser?.email}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,4 +204,4 @@ export const Header = ({
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,28 +1,44 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { SidebarHeader } from "./sidebar/SidebarHeader";
|
import { SidebarHeader } from "./sidebar/SidebarHeader";
|
||||||
import { MainNavigation } from "./sidebar/MainNavigation";
|
import { MainNavigation } from "./sidebar/MainNavigation";
|
||||||
import { SettingsPanel } from "./sidebar/SettingsPanel";
|
import { SettingsPanel } from "./sidebar/SettingsPanel";
|
||||||
|
import { useSidebar } from "@/contexts/SidebarContext";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface SidebarProps {
|
export const Sidebar = () => {
|
||||||
collapsed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Sidebar = ({ collapsed }: SidebarProps) => {
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { sidebarCollapsed, isMobileOpen, toggleMobileMenu } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`
|
{/* Mobile Overlay */}
|
||||||
${theme === 'dark' ? 'bg-[#121212] border-[#1e1e1e]' : 'bg-sidebar border-sidebar-border'}
|
{isMobileOpen && (
|
||||||
border-r flex flex-col h-full
|
<div
|
||||||
${collapsed ? 'w-16' : 'w-64'}
|
className="fixed inset-0 bg-black/60 z-40 lg:hidden backdrop-blur-sm transition-opacity animate-in fade-in"
|
||||||
`}
|
onClick={toggleMobileMenu}
|
||||||
>
|
/>
|
||||||
<SidebarHeader collapsed={collapsed} />
|
)}
|
||||||
<MainNavigation collapsed={collapsed} />
|
|
||||||
<SettingsPanel collapsed={collapsed} />
|
{/* Sidebar Container */}
|
||||||
</div>
|
<aside
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-y-0 left-0 z-50 lg:static lg:block transition-all duration-300 ease-in-out border-r flex flex-col h-full",
|
||||||
|
theme === 'dark' ? 'bg-[#121212] border-[#1e1e1e]' : 'bg-sidebar border-sidebar-border',
|
||||||
|
// Mobile state
|
||||||
|
isMobileOpen ? "translate-x-0 w-64" : "-translate-x-full lg:translate-x-0",
|
||||||
|
// Desktop state (collapsed/expanded)
|
||||||
|
!isMobileOpen && sidebarCollapsed ? "lg:w-16" : "lg:w-64"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SidebarHeader collapsed={!isMobileOpen && sidebarCollapsed} />
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto overflow-x-hidden custom-scrollbar">
|
||||||
|
<MainNavigation collapsed={!isMobileOpen && sidebarCollapsed} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SettingsPanel collapsed={!isMobileOpen && sidebarCollapsed} />
|
||||||
|
</aside>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,8 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { LucideIcon } from "lucide-react";
|
import { LucideIcon } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface MenuItemProps {
|
interface MenuItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
path: string | null;
|
path: string | null;
|
||||||
@@ -12,8 +14,8 @@ interface MenuItemProps {
|
|||||||
hasNavigation: boolean;
|
hasNavigation: boolean;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuItem: React.FC<MenuItemProps> = ({
|
export const MenuItem: React.FC<MenuItemProps> = ({
|
||||||
id,
|
|
||||||
path,
|
path,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
translationKey,
|
translationKey,
|
||||||
@@ -21,36 +23,44 @@ export const MenuItem: React.FC<MenuItemProps> = ({
|
|||||||
hasNavigation,
|
hasNavigation,
|
||||||
collapsed
|
collapsed
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { theme } = useTheme();
|
||||||
theme
|
const { t } = useLanguage();
|
||||||
} = useTheme();
|
|
||||||
const {
|
|
||||||
t
|
|
||||||
} = useLanguage();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const isActive = path && location.pathname === path;
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
|
||||||
if (hasNavigation && path) {
|
if (hasNavigation && path) {
|
||||||
navigate(path, {
|
navigate(path);
|
||||||
replace: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const isActive = path && location.pathname === path;
|
|
||||||
const mainIconSize = "h-5 w-5";
|
return (
|
||||||
return <div className={`
|
<div
|
||||||
${collapsed ? 'p-3' : 'py-3 px-3'}
|
className={cn(
|
||||||
mb-1 rounded-lg
|
"group relative flex items-center mb-1 rounded-lg cursor-pointer transition-all duration-200",
|
||||||
${isActive ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`}
|
collapsed ? "p-3 justify-center" : "p-2.5 px-4",
|
||||||
flex items-center gap-3
|
isActive
|
||||||
${collapsed ? 'justify-center' : ''}
|
? (theme === 'dark' ? 'bg-zinc-800 text-white' : 'bg-primary/10 text-primary')
|
||||||
cursor-pointer transition-colors
|
: (theme === 'dark' ? 'text-zinc-400 hover:bg-zinc-800/50 hover:text-white' : 'text-zinc-600 hover:bg-zinc-100')
|
||||||
`} onClick={handleClick}>
|
)}
|
||||||
<Icon className={`${mainIconSize} flex-shrink-0 ${theme === 'dark' ? 'text-gray-400' : 'text-muted-foreground'}`} />
|
onClick={handleClick}
|
||||||
{!collapsed && <span className="text-sidebar-foreground font-inter text-[14px] font-medium tracking-tight leading-tight whitespace-nowrap">
|
title={collapsed ? t(translationKey) : ""}
|
||||||
|
>
|
||||||
|
<Icon className={cn("h-5 w-5 shrink-0 transition-colors", color, isActive ? "opacity-100" : "opacity-80 group-hover:opacity-100")} />
|
||||||
|
|
||||||
|
{!collapsed && (
|
||||||
|
<span className="ml-3 font-medium tracking-wide text-sm truncate">
|
||||||
{t(translationKey)}
|
{t(translationKey)}
|
||||||
</span>}
|
</span>
|
||||||
</div>;
|
)}
|
||||||
|
|
||||||
|
{/* Small Indicator for active state */}
|
||||||
|
{isActive && !collapsed && (
|
||||||
|
<div className="absolute left-0 w-1 h-6 bg-primary rounded-r-full" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
+3
-10
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Sidebar } from "@/components/dashboard/Sidebar";
|
import { Sidebar } from "@/components/dashboard/Sidebar";
|
||||||
import { Header } from "@/components/dashboard/Header";
|
import { Header } from "@/components/dashboard/Header";
|
||||||
@@ -7,11 +6,9 @@ import { ServiceNotFound } from "@/components/services/ServiceNotFound";
|
|||||||
import { Service } from "@/types/service.types";
|
import { Service } from "@/types/service.types";
|
||||||
|
|
||||||
interface ServiceDetailWrapperProps {
|
interface ServiceDetailWrapperProps {
|
||||||
children: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
service: Service | null;
|
service: Service | null;
|
||||||
sidebarCollapsed: boolean;
|
|
||||||
toggleSidebar: () => void;
|
|
||||||
currentUser: any;
|
currentUser: any;
|
||||||
handleLogout: () => void;
|
handleLogout: () => void;
|
||||||
}
|
}
|
||||||
@@ -20,20 +17,16 @@ export const ServiceDetailWrapper = ({
|
|||||||
children,
|
children,
|
||||||
isLoading,
|
isLoading,
|
||||||
service,
|
service,
|
||||||
sidebarCollapsed,
|
|
||||||
toggleSidebar,
|
|
||||||
currentUser,
|
currentUser,
|
||||||
handleLogout
|
handleLogout
|
||||||
}: ServiceDetailWrapperProps) => {
|
}: ServiceDetailWrapperProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full overflow-hidden bg-background text-foreground">
|
<div className="flex h-screen w-full overflow-hidden bg-background text-foreground">
|
||||||
<Sidebar collapsed={sidebarCollapsed} />
|
<Sidebar />
|
||||||
<div className="flex flex-col flex-1 min-w-0">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<Header
|
<Header
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onLogout={handleLogout}
|
onLogout={handleLogout}
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
|
||||||
toggleSidebar={toggleSidebar}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -48,4 +41,4 @@ export const ServiceDetailWrapper = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,78 +1,34 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import React, { useState, useCallback } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { DateRangeOption } from "../DateRangeFilter";
|
import { DateRangeOption } from "../DateRangeFilter";
|
||||||
import { authService } from "@/services/authService";
|
import { authService } from "@/services/authService";
|
||||||
import { ServiceDetailContent } from "../ServiceDetailContent";
|
import { ServiceDetailContent } from "../ServiceDetailContent";
|
||||||
import { ServiceDetailWrapper } from "./ServiceDetailWrapper";
|
import { ServiceDetailWrapper } from "./ServiceDetailWrapper";
|
||||||
import { useServiceData, useRealTimeUpdates } from "./hooks";
|
import { useServiceData, useRealTimeUpdates } from "./hooks";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
|
||||||
|
|
||||||
export const ServiceDetailContainer = () => {
|
export const ServiceDetailContainer = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Set default to 24h
|
|
||||||
const [startDate, setStartDate] = useState<Date>(() => {
|
const [startDate, setStartDate] = useState<Date>(() => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
date.setHours(date.getHours() - 24); // Go back 24 hours
|
date.setHours(date.getHours() - 24);
|
||||||
return date;
|
return date;
|
||||||
});
|
});
|
||||||
const [endDate, setEndDate] = useState<Date>(() => {
|
const [endDate, setEndDate] = useState<Date>(() => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future
|
date.setMinutes(date.getMinutes() + 5);
|
||||||
return date;
|
return date;
|
||||||
});
|
});
|
||||||
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');
|
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');
|
||||||
|
|
||||||
// State for sidebar collapse functionality (shared with Dashboard)
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
|
||||||
// Check if there's a saved preference in localStorage
|
|
||||||
const saved = localStorage.getItem("sidebarCollapsed");
|
|
||||||
return saved ? JSON.parse(saved) : window.innerWidth < 768;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle sidebar and save preference
|
|
||||||
const toggleSidebar = useCallback(() => {
|
|
||||||
setSidebarCollapsed(prev => {
|
|
||||||
const newState = !prev;
|
|
||||||
localStorage.setItem("sidebarCollapsed", JSON.stringify(newState));
|
|
||||||
return newState;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Get current user for header
|
|
||||||
const currentUser = authService.getCurrentUser();
|
const currentUser = authService.getCurrentUser();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Verify user is authenticated
|
|
||||||
if (!authService.isAuthenticated()) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Authentication required",
|
|
||||||
description: "Please log in to view service details",
|
|
||||||
});
|
|
||||||
navigate("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-collapse sidebar on small screens
|
|
||||||
const handleResize = () => {
|
|
||||||
if (window.innerWidth < 768 && !sidebarCollapsed) {
|
|
||||||
setSidebarCollapsed(true);
|
|
||||||
localStorage.setItem("sidebarCollapsed", JSON.stringify(true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
|
||||||
}, [navigate, sidebarCollapsed]);
|
|
||||||
|
|
||||||
// Handler for logout (same as Dashboard)
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
authService.logout();
|
authService.logout();
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use our custom hooks
|
|
||||||
const {
|
const {
|
||||||
service,
|
service,
|
||||||
uptimeData,
|
uptimeData,
|
||||||
@@ -85,7 +41,6 @@ export const ServiceDetailContainer = () => {
|
|||||||
handleRegionalAgentChange
|
handleRegionalAgentChange
|
||||||
} = useServiceData(id, startDate, endDate);
|
} = useServiceData(id, startDate, endDate);
|
||||||
|
|
||||||
// Set up real-time updates
|
|
||||||
useRealTimeUpdates({
|
useRealTimeUpdates({
|
||||||
serviceId: id,
|
serviceId: id,
|
||||||
startDate,
|
startDate,
|
||||||
@@ -94,18 +49,12 @@ export const ServiceDetailContainer = () => {
|
|||||||
setUptimeData
|
setUptimeData
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle date range filter changes
|
|
||||||
const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => {
|
const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => {
|
||||||
console.log(`ServiceDetailContainer: Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`);
|
|
||||||
|
|
||||||
// Update state which will trigger the useEffect in useServiceData
|
|
||||||
setStartDate(start);
|
setStartDate(start);
|
||||||
setEndDate(end);
|
setEndDate(end);
|
||||||
setSelectedRange(option);
|
setSelectedRange(option);
|
||||||
|
|
||||||
// Also explicitly fetch data with the new range to ensure immediate update
|
|
||||||
if (id) {
|
if (id) {
|
||||||
console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`);
|
|
||||||
fetchUptimeData(id, start, end, option, selectedRegionalAgent);
|
fetchUptimeData(id, start, end, option, selectedRegionalAgent);
|
||||||
}
|
}
|
||||||
}, [id, fetchUptimeData, selectedRegionalAgent]);
|
}, [id, fetchUptimeData, selectedRegionalAgent]);
|
||||||
@@ -114,12 +63,10 @@ export const ServiceDetailContainer = () => {
|
|||||||
<ServiceDetailWrapper
|
<ServiceDetailWrapper
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
service={service}
|
service={service}
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
|
||||||
toggleSidebar={toggleSidebar}
|
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
handleLogout={handleLogout}
|
handleLogout={handleLogout}
|
||||||
>
|
>
|
||||||
{service && (
|
{service ? (
|
||||||
<ServiceDetailContent
|
<ServiceDetailContent
|
||||||
service={service}
|
service={service}
|
||||||
uptimeData={uptimeData}
|
uptimeData={uptimeData}
|
||||||
@@ -129,7 +76,7 @@ export const ServiceDetailContainer = () => {
|
|||||||
selectedRegionalAgent={selectedRegionalAgent}
|
selectedRegionalAgent={selectedRegionalAgent}
|
||||||
onRegionalAgentChange={handleRegionalAgentChange}
|
onRegionalAgentChange={handleRegionalAgentChange}
|
||||||
/>
|
/>
|
||||||
)}
|
) : null}
|
||||||
</ServiceDetailWrapper>
|
</ServiceDetailWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,25 +1,64 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
import React, { createContext, useContext, useState } from 'react';
|
|
||||||
|
|
||||||
interface SidebarContextType {
|
interface SidebarContextType {
|
||||||
sidebarCollapsed: boolean;
|
sidebarCollapsed: boolean;
|
||||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
|
isMobileOpen: boolean;
|
||||||
|
setIsMobileOpen: (open: boolean) => void;
|
||||||
|
toggleMobileMenu: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
|
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const SidebarProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
// Initialize sidebar collapsed state from localStorage with window check for safety
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const saved = localStorage.getItem("sidebarCollapsed");
|
||||||
|
return saved ? JSON.parse(saved) : false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fix: added isMobileOpen state for responsive mobile menu
|
||||||
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
setSidebarCollapsed(prev => !prev);
|
setSidebarCollapsed(prev => {
|
||||||
|
const newState = !prev;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem("sidebarCollapsed", JSON.stringify(newState));
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fix: added toggleMobileMenu function to manage mobile drawer state
|
||||||
|
const toggleMobileMenu = () => {
|
||||||
|
setIsMobileOpen(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fix: added resize listener to close mobile menu when switching to desktop view
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (window.innerWidth >= 1024) {
|
||||||
|
setIsMobileOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
sidebarCollapsed,
|
sidebarCollapsed,
|
||||||
setSidebarCollapsed,
|
setSidebarCollapsed,
|
||||||
toggleSidebar
|
toggleSidebar,
|
||||||
|
isMobileOpen,
|
||||||
|
setIsMobileOpen,
|
||||||
|
toggleMobileMenu
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -35,4 +74,4 @@ export const useSidebar = () => {
|
|||||||
throw new Error('useSidebar must be used within a SidebarProvider');
|
throw new Error('useSidebar must be used within a SidebarProvider');
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Header } from "@/components/dashboard/Header";
|
import { Header } from "@/components/dashboard/Header";
|
||||||
@@ -11,72 +10,39 @@ import { LoadingState } from "@/components/services/LoadingState";
|
|||||||
import { useSidebar } from "@/contexts/SidebarContext";
|
import { useSidebar } from "@/contexts/SidebarContext";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
// Use shared sidebar state
|
|
||||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
|
||||||
|
|
||||||
// Get current user
|
|
||||||
const currentUser = authService.getCurrentUser();
|
const currentUser = authService.getCurrentUser();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// For debugging user data
|
|
||||||
useEffect(() => {
|
|
||||||
// console.log("Current user data:", currentUser);
|
|
||||||
}, [currentUser]);
|
|
||||||
|
|
||||||
// Handle logout
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
authService.logout();
|
authService.logout();
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch all services with 1-minute polling for real-time updates
|
|
||||||
const { data: services = [], isLoading, error } = useQuery({
|
const { data: services = [], isLoading, error } = useQuery({
|
||||||
queryKey: ['services'],
|
queryKey: ['services'],
|
||||||
queryFn: serviceService.getServices,
|
queryFn: serviceService.getServices,
|
||||||
refetchInterval: 60000, // 1 minute as requested
|
refetchInterval: 60000,
|
||||||
staleTime: 30000, // Data is fresh for 30 seconds
|
|
||||||
gcTime: 120000, // Keep in cache for 2 minutes
|
|
||||||
refetchOnWindowFocus: true, // Refetch when window gains focus
|
|
||||||
refetchOnMount: true, // Refetch on mount
|
|
||||||
refetchOnReconnect: true, // Refetch on reconnect
|
|
||||||
retry: 2,
|
|
||||||
retryDelay: 3000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start monitoring all active services when the dashboard loads - only once
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let hasStarted = false;
|
|
||||||
|
|
||||||
const startActiveServices = async () => {
|
const startActiveServices = async () => {
|
||||||
if (hasStarted) return;
|
|
||||||
hasStarted = true;
|
|
||||||
|
|
||||||
await serviceService.startAllActiveServices();
|
await serviceService.startAllActiveServices();
|
||||||
// console.log("Active services monitoring started");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only start once and add a delay to prevent immediate execution
|
|
||||||
const timeoutId = setTimeout(startActiveServices, 2000);
|
const timeoutId = setTimeout(startActiveServices, 2000);
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
return () => {
|
}, []);
|
||||||
clearTimeout(timeoutId);
|
|
||||||
};
|
|
||||||
}, []); // Remove services dependency to prevent re-runs
|
|
||||||
|
|
||||||
// Show the loading state while fetching data
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LoadingState />;
|
return <LoadingState />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
||||||
<Sidebar collapsed={sidebarCollapsed} />
|
<Sidebar />
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<Header
|
<Header
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onLogout={handleLogout}
|
onLogout={handleLogout}
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
|
||||||
toggleSidebar={toggleSidebar}
|
|
||||||
/>
|
/>
|
||||||
<DashboardContent
|
<DashboardContent
|
||||||
services={services}
|
services={services}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
@@ -10,16 +9,13 @@ import { ServerTable } from "@/components/servers/ServerTable";
|
|||||||
import { AddServerAgentDialog } from "@/components/servers/AddServerAgentDialog";
|
import { AddServerAgentDialog } from "@/components/servers/AddServerAgentDialog";
|
||||||
import { serverService } from "@/services/serverService";
|
import { serverService } from "@/services/serverService";
|
||||||
import { Server, ServerStats } from "@/types/server.types";
|
import { Server, ServerStats } from "@/types/server.types";
|
||||||
import { useSidebar } from "@/contexts/SidebarContext";
|
|
||||||
import { authService } from "@/services/authService";
|
import { authService } from "@/services/authService";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
|
||||||
const InstanceMonitoring = () => {
|
const InstanceMonitoring = () => {
|
||||||
const { theme } = useTheme();
|
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const { sidebarCollapsed, toggleSidebar } = useSidebar();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [stats, setStats] = useState<ServerStats>({
|
const [stats, setStats] = useState<ServerStats>({
|
||||||
@@ -29,13 +25,13 @@ const InstanceMonitoring = () => {
|
|||||||
warning: 0
|
warning: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
|
const [currentUser] = useState(authService.getCurrentUser());
|
||||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||||
|
|
||||||
const { data: servers = [], isLoading, error, refetch } = useQuery({
|
const { data: servers = [], isLoading, error, refetch } = useQuery<Server[]>({
|
||||||
queryKey: ['servers'],
|
queryKey: ['servers'],
|
||||||
queryFn: serverService.getServers,
|
queryFn: serverService.getServers,
|
||||||
refetchInterval: 30000 // Refetch every 30 seconds
|
refetchInterval: 30000
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,88 +40,38 @@ const InstanceMonitoring = () => {
|
|||||||
}
|
}
|
||||||
}, [servers]);
|
}, [servers]);
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
authService.logout();
|
authService.logout();
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAgentAdded = () => {
|
|
||||||
refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
|
||||||
<Sidebar collapsed={sidebarCollapsed} />
|
|
||||||
<div className="flex flex-col flex-1 min-w-0">
|
|
||||||
<Header
|
|
||||||
currentUser={currentUser}
|
|
||||||
onLogout={handleLogout}
|
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
|
||||||
toggleSidebar={toggleSidebar}
|
|
||||||
/>
|
|
||||||
<main className="flex-1 flex flex-col items-center justify-center p-4 sm:p-6">
|
|
||||||
<div className="text-center max-w-md w-full">
|
|
||||||
<h2 className="text-xl sm:text-2xl font-bold mb-4">{t('errorLoadingServers')}</h2>
|
|
||||||
<p className="text-muted-foreground mb-4 text-sm sm:text-base">
|
|
||||||
{t('unableToFetchServerData')}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={handleRefresh}
|
|
||||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 text-sm sm:text-base"
|
|
||||||
>
|
|
||||||
{t('retry')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
||||||
<Sidebar collapsed={sidebarCollapsed} />
|
<Sidebar />
|
||||||
<div className="flex flex-col flex-1 min-w-0">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<Header
|
<Header
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
onLogout={handleLogout}
|
onLogout={handleLogout}
|
||||||
sidebarCollapsed={sidebarCollapsed}
|
|
||||||
toggleSidebar={toggleSidebar}
|
|
||||||
/>
|
/>
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
<div className="p-4 sm:p-6 lg:p-8 space-y-6">
|
<div className="p-4 lg:p-8 space-y-6">
|
||||||
{/* Header Section */}
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
<div className="min-w-0 flex-1">
|
{t('instanceMonitoring')}
|
||||||
<h1 className="text-2xl lg:text-2xl font-bold text-foreground">
|
</h1>
|
||||||
{t('instanceMonitoring')}
|
<p className="text-muted-foreground mt-1 text-sm">
|
||||||
</h1>
|
{t('describeMonitorInstance')}
|
||||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
|
</p>
|
||||||
{t('describeMonitorInstance')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={() => setAddDialogOpen(true)} className="flex-shrink-0">
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
{t('addServerAgent')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Button onClick={() => setAddDialogOpen(true)} className="flex-shrink-0">
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
{t('addServerAgent')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Cards Section */}
|
<ServerStatsCards stats={stats} />
|
||||||
<div>
|
<ServerTable servers={servers} isLoading={isLoading} onRefresh={refetch} />
|
||||||
<ServerStatsCards stats={stats} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Server Table Section */}
|
|
||||||
<div>
|
|
||||||
<ServerTable servers={servers} isLoading={isLoading} onRefresh={handleRefresh} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +79,7 @@ const InstanceMonitoring = () => {
|
|||||||
<AddServerAgentDialog
|
<AddServerAgentDialog
|
||||||
open={addDialogOpen}
|
open={addDialogOpen}
|
||||||
onOpenChange={setAddDialogOpen}
|
onOpenChange={setAddDialogOpen}
|
||||||
onAgentAdded={handleAgentAdded}
|
onAgentAdded={refetch}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user