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 = { | export type AgentOverviewCardProps = { | ||||||
|  |     count: number; | ||||||
|     title: string; |     title: string; | ||||||
|     icon: { |     icon: { | ||||||
|         ref: React.ReactNode; |         ref: React.ReactNode; | ||||||
| @@ -19,7 +24,9 @@ export function AgentOverviewCard({ | |||||||
|                     <p className="text-sm text-gray-400 uppercase tracking-wider"> |                     <p className="text-sm text-gray-400 uppercase tracking-wider"> | ||||||
|                         {props.title} |                         {props.title} | ||||||
|                     </p> |                     </p> | ||||||
|                     <p className="text-2xl font-bold text-white">0</p> |                     <p className="text-2xl font-bold text-white"> | ||||||
|  |                         {props.count} | ||||||
|  |                     </p> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div |                 <div | ||||||
|                     className="w-12 h-12 rounded-lg flex items-center justify-center" |                     className="w-12 h-12 rounded-lg flex items-center justify-center" | ||||||
| @@ -34,3 +41,62 @@ export function AgentOverviewCard({ | |||||||
|         </div> |         </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 { Header } from "@/components/Header"; | ||||||
|  | import { UptimeMessage } from "@/services/types"; | ||||||
|  | import { isAgentOnline } from "@/services/utils"; | ||||||
| import { Box } from "lucide-react"; | import { Box } from "lucide-react"; | ||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  |  | ||||||
| export function HomePage() { | 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 ( |     return ( | ||||||
|         <div> |         <div> | ||||||
|             <Header |             <Header | ||||||
| @@ -13,9 +32,12 @@ export function HomePage() { | |||||||
|                 }} |                 }} | ||||||
|             /> |             /> | ||||||
|             <main className="max-w-7xl mx-auto py-8"> |             <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"> |                 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8"> | ||||||
|                     <AgentOverviewCard |                     <AgentOverviewCard | ||||||
|                         props={{ |                         props={{ | ||||||
|  |                             count: totalAgents, | ||||||
|                             title: "total agents", |                             title: "total agents", | ||||||
|                             icon: { |                             icon: { | ||||||
|                                 ref: <Box />, |                                 ref: <Box />, | ||||||
| @@ -26,6 +48,7 @@ export function HomePage() { | |||||||
|                     /> |                     /> | ||||||
|                     <AgentOverviewCard |                     <AgentOverviewCard | ||||||
|                         props={{ |                         props={{ | ||||||
|  |                             count: onlineAgents, | ||||||
|                             title: "online", |                             title: "online", | ||||||
|                             icon: { |                             icon: { | ||||||
|                                 ref: ( |                                 ref: ( | ||||||
| @@ -38,6 +61,7 @@ export function HomePage() { | |||||||
|                     /> |                     /> | ||||||
|                     <AgentOverviewCard |                     <AgentOverviewCard | ||||||
|                         props={{ |                         props={{ | ||||||
|  |                             count: offlineAgents, | ||||||
|                             title: "offline", |                             title: "offline", | ||||||
|                             icon: { |                             icon: { | ||||||
|                                 ref: ( |                                 ref: ( | ||||||
| @@ -53,7 +77,11 @@ export function HomePage() { | |||||||
|                 <div className="mb-6"> |                 <div className="mb-6"> | ||||||
|                     <h2 className="text-xl font-semibold mb-4">Agents</h2> |                     <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> |                 </div> | ||||||
|             </main> |             </main> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
| @@ -54,5 +54,5 @@ export interface StatusMessage { | |||||||
| export interface UptimeMessage { | export interface UptimeMessage { | ||||||
|     agent: string; |     agent: string; | ||||||
|     uptime: number; |     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