mirror of
				https://github.com/csehviktor/status-monitor.git
				synced 2025-08-08 18:06:14 +02:00 
			
		
		
		
	improve home page
This commit is contained in:
		| @@ -1,4 +1,9 @@ | ||||
| import { UptimeMessage } from "@/services/types"; | ||||
| import { formatRelativeTime, isAgentOnline } from "@/services/utils"; | ||||
| import { DottedProgressBar } from "./Progressbar"; | ||||
|  | ||||
| export type AgentOverviewCardProps = { | ||||
|     count: number; | ||||
|     title: string; | ||||
|     icon: { | ||||
|         ref: React.ReactNode; | ||||
| @@ -19,7 +24,9 @@ export function AgentOverviewCard({ | ||||
|                     <p className="text-sm text-gray-400 uppercase tracking-wider"> | ||||
|                         {props.title} | ||||
|                     </p> | ||||
|                     <p className="text-2xl font-bold text-white">0</p> | ||||
|                     <p className="text-2xl font-bold text-white"> | ||||
|                         {props.count} | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 <div | ||||
|                     className="w-12 h-12 rounded-lg flex items-center justify-center" | ||||
| @@ -34,3 +41,62 @@ export function AgentOverviewCard({ | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export type AgentCardProps = { | ||||
|     data: UptimeMessage; | ||||
|     onClick?: () => void; | ||||
| }; | ||||
|  | ||||
| export function AgentCard({ props }: { props: AgentCardProps }) { | ||||
|     const status = isAgentOnline(props.data) | ||||
|         ? { | ||||
|               name: "online", | ||||
|               color: "bg-green-500", | ||||
|               textColor: "text-green-400", | ||||
|               borderColor: "border-green-500/20", | ||||
|               bgColor: "bg-green-500/10", | ||||
|           } | ||||
|         : { | ||||
|               name: "offline", | ||||
|               color: "bg-red-500", | ||||
|               textColor: "text-red-400", | ||||
|               borderColor: "border-red-500/20", | ||||
|               bgColor: "bg-red-500/10", | ||||
|           }; | ||||
|  | ||||
|     return ( | ||||
|         <div | ||||
|             onClick={() => props.onClick?.()} | ||||
|             className="bg-[#111111] border border-[#262626] rounded-lg p-6 hover:bg-[#151515] transition-all cursor-pointer group" | ||||
|         > | ||||
|             <div className="flex items-center justify-between"> | ||||
|                 <div className="flex items-center space-x-3"> | ||||
|                     <div | ||||
|                         className={`px-2 py-1 rounded-md text-xs font-medium ${status.bgColor} ${status.textColor} border ${status.borderColor}`} | ||||
|                     > | ||||
|                         {status.name.toUpperCase()} | ||||
|                     </div> | ||||
|                     <h3 className="text-lg font-semibold text-white transition-colors"> | ||||
|                         {props.data.agent} | ||||
|                     </h3> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="flex items-center space-x-3 flex-1 justify-end"> | ||||
|                     <div className="flex flex-col max-w-lg gap-y-2"> | ||||
|                         <DottedProgressBar | ||||
|                             color={status.color} | ||||
|                             percentage={props.data.uptime} | ||||
|                             totalDots={20} | ||||
|                         /> | ||||
|                         <div className="flex justify-between items-center text-xs"> | ||||
|                             <span> | ||||
|                                 {formatRelativeTime(props.data.last_seen)} | ||||
|                             </span> | ||||
|                             <span>{props.data.uptime.toFixed(2)}%</span> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
|   | ||||
							
								
								
									
										24
									
								
								ui/src/components/Progressbar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								ui/src/components/Progressbar.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| export function DottedProgressBar({ | ||||
|     color, | ||||
|     percentage, | ||||
|     totalDots = 10, | ||||
| }: { | ||||
|     color: string; | ||||
|     percentage: number; | ||||
|     totalDots?: number; | ||||
| }) { | ||||
|     const filledDots = Math.round((percentage / 100) * totalDots); | ||||
|  | ||||
|     return ( | ||||
|         <div className="flex items-center justify-center space-x-2"> | ||||
|             {Array.from({ length: totalDots }).map((_, index) => ( | ||||
|                 <div | ||||
|                     key={index} | ||||
|                     className={`w-[14px] h-[14px] rounded-full transition-all duration-300 ${ | ||||
|                         index < filledDots ? color : "bg-[#232323]" | ||||
|                     }`} | ||||
|                 /> | ||||
|             ))} | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| @@ -1,8 +1,27 @@ | ||||
| import { AgentOverviewCard } from "@/components/AgentCard"; | ||||
| "use client"; | ||||
|  | ||||
| import { AgentCard, AgentOverviewCard } from "@/components/AgentCard"; | ||||
| import { Header } from "@/components/Header"; | ||||
| import { UptimeMessage } from "@/services/types"; | ||||
| import { isAgentOnline } from "@/services/utils"; | ||||
| import { Box } from "lucide-react"; | ||||
| import { useEffect, useState } from "react"; | ||||
|  | ||||
| export function HomePage() { | ||||
|     const [agents, setAgents] = useState<UptimeMessage[]>([]); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         fetch("http://localhost:3000/agents") | ||||
|             .then((res) => res.json()) | ||||
|             .then((data) => { | ||||
|                 setAgents(data); | ||||
|             }); | ||||
|     }, []); | ||||
|  | ||||
|     const totalAgents = agents.length; | ||||
|     const onlineAgents = agents.filter((agent) => isAgentOnline(agent)).length; | ||||
|     const offlineAgents = totalAgents - onlineAgents; | ||||
|  | ||||
|     return ( | ||||
|         <div> | ||||
|             <Header | ||||
| @@ -13,9 +32,12 @@ export function HomePage() { | ||||
|                 }} | ||||
|             /> | ||||
|             <main className="max-w-7xl mx-auto py-8"> | ||||
|                 <h2 className="text-xl font-semibold mb-4">Overview</h2> | ||||
|  | ||||
|                 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8"> | ||||
|                     <AgentOverviewCard | ||||
|                         props={{ | ||||
|                             count: totalAgents, | ||||
|                             title: "total agents", | ||||
|                             icon: { | ||||
|                                 ref: <Box />, | ||||
| @@ -26,6 +48,7 @@ export function HomePage() { | ||||
|                     /> | ||||
|                     <AgentOverviewCard | ||||
|                         props={{ | ||||
|                             count: onlineAgents, | ||||
|                             title: "online", | ||||
|                             icon: { | ||||
|                                 ref: ( | ||||
| @@ -38,6 +61,7 @@ export function HomePage() { | ||||
|                     /> | ||||
|                     <AgentOverviewCard | ||||
|                         props={{ | ||||
|                             count: offlineAgents, | ||||
|                             title: "offline", | ||||
|                             icon: { | ||||
|                                 ref: ( | ||||
| @@ -53,7 +77,11 @@ export function HomePage() { | ||||
|                 <div className="mb-6"> | ||||
|                     <h2 className="text-xl font-semibold mb-4">Agents</h2> | ||||
|  | ||||
|                     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div> | ||||
|                     <div className="space-y-6"> | ||||
|                         {agents.map((agent, index) => ( | ||||
|                             <AgentCard key={index} props={{ data: agent }} /> | ||||
|                         ))} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </main> | ||||
|         </div> | ||||
|   | ||||
| @@ -54,5 +54,5 @@ export interface StatusMessage { | ||||
| export interface UptimeMessage { | ||||
|     agent: string; | ||||
|     uptime: number; | ||||
|     last_seen: Date; | ||||
|     last_seen: string; | ||||
| } | ||||
|   | ||||
							
								
								
									
										28
									
								
								ui/src/services/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								ui/src/services/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import { UptimeMessage } from "@/services/types"; | ||||
|  | ||||
| export function isAgentOnline(data: UptimeMessage): boolean { | ||||
|     const timeDiff = new Date().getTime() - new Date(data.last_seen).getTime(); | ||||
|  | ||||
|     return timeDiff < 10000; | ||||
| } | ||||
|  | ||||
| export const formatRelativeTime = (timestamp: string): string => { | ||||
|     const now = new Date(); | ||||
|     const past = new Date(timestamp); | ||||
|     const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000); | ||||
|  | ||||
|     if (diffInSeconds < 5) { | ||||
|         return "now"; | ||||
|     } else if (diffInSeconds < 60) { | ||||
|         return `${diffInSeconds}s ago`; | ||||
|     } else if (diffInSeconds < 3600) { | ||||
|         const minutes = Math.floor(diffInSeconds / 60); | ||||
|         return `${minutes}m ago`; | ||||
|     } else if (diffInSeconds < 86400) { | ||||
|         const hours = Math.floor(diffInSeconds / 3600); | ||||
|         return `${hours}h ago`; | ||||
|     } else { | ||||
|         const days = Math.floor(diffInSeconds / 86400); | ||||
|         return `${days}d ago`; | ||||
|     } | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user