UNPKG

pulse-dashboard

Version:

A Next.js Dashboard application for real-time monitoring and historical analysis of Playwright test executions, based on playwright-pulse-report. This component provides the UI for visualizing Playwright test results and can be run as a standalone CLI too

197 lines 9.79 kB
"use client"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Server, Cpu, MemoryStick, Cog, Puzzle, Play, Laptop, Info, Github, Briefcase, Globe, Compass, Chrome, HardDrive, Layers, } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useState } from "react"; const getIcon = (key) => { const lowerKey = key.toLowerCase(); if (lowerKey.includes("os") || lowerKey.includes("operative system")) return <Laptop className="h-4 w-4 text-primary"/>; if (lowerKey.includes("cpu")) return <Cpu className="h-4 w-4 text-primary"/>; if (lowerKey.includes("mem") || lowerKey.includes("ram")) return <MemoryStick className="h-4 w-4 text-primary"/>; if (lowerKey.includes("node")) return <Cog className="h-4 w-4 text-primary"/>; if (lowerKey.includes("playwright")) return <Puzzle className="h-4 w-4 text-primary"/>; if (lowerKey.includes("y playwright")) return <Play className="h-4 w-4 text-primary"/>; // Assuming "Playwright" if "playwright" is present if (lowerKey.includes("browser")) return <Globe className="h-4 w-4 text-primary"/>; if (lowerKey.includes("chromium") || lowerKey.includes("chrome")) return <Chrome className="h-4 w-4 text-primary"/>; if (lowerKey.includes("firefox")) return <Globe className="h-4 w-4 text-primary"/>; // No specific Firefox icon in Lucide if (lowerKey.includes("webkit") || lowerKey.includes("safari")) return <Compass className="h-4 w-4 text-primary"/>; if (lowerKey.includes("provider") && (key.toLowerCase().includes("ci") || key.toLowerCase().includes("cd"))) return <Github className="h-4 w-4 text-primary"/>; if (lowerKey.includes("job") && (key.toLowerCase().includes("ci") || key.toLowerCase().includes("cd"))) return <Briefcase className="h-4 w-4 text-primary"/>; if (lowerKey.includes("disk") || lowerKey.includes("storage")) return <HardDrive className="h-4 w-4 text-primary"/>; return <Info className="h-4 w-4 text-primary"/>; }; const renderEnvironmentValue = (value, keyPrefix = "") => { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { return <span className="text-sm text-foreground">{String(value)}</span>; } if (Array.isArray(value)) { return (<ul className="list-disc list-inside pl-4"> {value.map((item, index) => (<li key={`${keyPrefix}-arr-${index}`} className="text-sm text-foreground"> {renderEnvironmentValue(item, `${keyPrefix}-arr-item-${index}`)} </li>))} </ul>); } if (typeof value === "object" && value !== null) { return (<div className="ml-4 mt-1 space-y-1 border-l pl-3 border-border"> {Object.entries(value).map(([key, val]) => (<div key={`${keyPrefix}-${key}`} className="flex items-start space-x-2"> <div className="flex-shrink-0 pt-0.5">{getIcon(key)}</div> <div className="flex-grow"> <span className="text-xs font-medium text-muted-foreground capitalize"> {key.replace(/([A-Z])/g, " $1")}:{" "} </span> {renderEnvironmentValue(val, `${keyPrefix}-${key}`)} </div> </div>))} </div>); } return <span className="text-sm text-muted-foreground italic">N/A</span>; }; const renderEnvironmentGrid = (envInfo, keyPrefix = "") => { const getDisplayValue = (val) => { if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") { return String(val); } if (Array.isArray(val)) { return val.length > 0 ? val.join(", ") : "Empty"; } if (typeof val === "object" && val !== null) { const entries = Object.entries(val); if (entries.length === 0) return "Empty"; if (entries.length <= 3) { return entries .map(([k, v]) => { const displayKey = k.replace(/([A-Z])/g, " $1").trim(); return `${displayKey}: ${String(v)}`; }) .join(", "); } return `${entries.length} items`; } return "N/A"; }; return (<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> {Object.entries(envInfo).map(([key, value]) => { const displayValue = getDisplayValue(value); const fullValue = typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value); return (<div key={`${keyPrefix}${key}`} className="flex items-center space-x-2 p-2 rounded-2xl hover:bg-muted/50 transition-all duration-200 group/item"> <div className="flex-shrink-0">{getIcon(key)}</div> <div className="flex-grow min-w-0"> <p className="text-xs font-medium text-muted-foreground truncate"> {key .replace(/([A-Z]+)/g, " $1") .replace(/([A-Z][a-z])/g, " $1") .replace(/^./, (str) => str.toUpperCase())} </p> <div className="text-xs font-semibold text-foreground truncate" title={fullValue}> {displayValue} </div> </div> </div>); })} </div>); }; export function SystemInformationWidget({ environmentInfo, loading, }) { const [activeShardTab, setActiveShardTab] = useState("0"); if (loading) { return (<Card className="shadow-lg rounded-xl mt-6"> <CardHeader> <Skeleton className="h-6 w-48 mb-1"/> <Skeleton className="h-4 w-3/4"/> </CardHeader> <CardContent className="space-y-3"> {[...Array(4)].map((_, i) => (<div key={i} className="flex items-center space-x-3"> <Skeleton className="h-5 w-5 rounded-full"/> <Skeleton className="h-4 w-1/4"/> <Skeleton className="h-4 w-1/2"/> </div>))} </CardContent> </Card>); } const isEmptyEnvironment = !environmentInfo || (Array.isArray(environmentInfo) ? environmentInfo.length === 0 : Object.keys(environmentInfo).length === 0); if (isEmptyEnvironment) { return (<Card className="shadow-lg rounded-xl mt-6"> <CardHeader> <CardTitle className="text-lg font-semibold text-primary flex items-center"> <Server className="h-5 w-5 mr-2"/> System Information </CardTitle> <CardDescription className="text-xs"> Details about the test execution environment. </CardDescription> </CardHeader> <CardContent> <Alert variant="default" className="border-muted-foreground/30"> <Info className="h-4 w-4"/> <AlertTitle>No Environment Data</AlertTitle> <AlertDescription> Environment information is not available in the current report. </AlertDescription> </Alert> </CardContent> </Card>); } const isShardedEnvironment = Array.isArray(environmentInfo); return (<Card className="shadow-lg rounded-xl mt-6 hover:shadow-2xl transition-all duration-300 border-0 bg-gradient-to-br from-card via-card to-card/95 group"> <CardHeader className="pb-3"> <div className="flex flex-row items-center justify-between"> <div> <CardTitle className="text-base font-semibold text-foreground group-hover:text-primary transition-colors flex items-center"> <Server className="h-4 w-4 mr-2"/> System Information {isShardedEnvironment && (<span className="ml-2 text-xs font-normal text-muted-foreground flex items-center gap-1"> <Layers className="h-3 w-3"/> {environmentInfo.length}{" "} {environmentInfo.length === 1 ? "Shard" : "Shards"} </span>)} </CardTitle> <CardDescription className="text-xs mt-1"> {isShardedEnvironment ? "Environment details from each test shard" : "Test execution environment details"} </CardDescription> </div> <div className="h-9 w-9 rounded-full bg-gradient-to-br from-primary/10 to-primary/5 flex items-center justify-center"> {isShardedEnvironment ? (<Layers className="h-4 w-4 text-primary"/>) : (<Laptop className="h-4 w-4 text-primary"/>)} </div> </div> </CardHeader> <CardContent className="pt-0"> {isShardedEnvironment ? (<Tabs value={activeShardTab} onValueChange={setActiveShardTab} className="w-full"> <TabsList className="w-fit max-w-full h-auto p-1 bg-muted/50 flex justify-start overflow-x-auto"> {environmentInfo.map((_, index) => (<TabsTrigger key={index} value={String(index)} className="text-xs px-3 py-1.5 data-[state=active]:bg-primary/10 flex-shrink-0"> Shard {index + 1} </TabsTrigger>))} </TabsList> {environmentInfo.map((shardEnv, index) => (<TabsContent key={index} value={String(index)} className="mt-4"> {renderEnvironmentGrid(shardEnv, `shard-${index}-`)} </TabsContent>))} </Tabs>) : (renderEnvironmentGrid(environmentInfo))} </CardContent> </Card>); } //# sourceMappingURL=SystemInformationWidget.jsx.map