mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-28 19:59:46 -05:00
Enhanced Server Stats Dashboard with Performance Optimizations (#746)
* Improve the layout of server mini stats in the dashboard. - Server stats and tags made siblings for clearer responsibilities - Changed margin to padding - Unreachable indicator made into an overlay of the stats * feat: optimize dashboard server stats with lazy loading and smart server availability checks - Add enabled prop to ServerStatsMini for conditional data fetching - Implement server availability check (only fetch stats for Ok servers, not NotOk/Disabled) - Prevent 500 errors by avoiding API calls to offline servers - Increase polling interval from 10s to 15s and add 5s stale time - Add useMemo for expensive calculations to reduce re-renders - Add conditional overlay rendering for unreachable servers - Only render stats when showServerStats preference is enabled * fix: show disabled servers with overlay instead of hiding component - Maintain consistent layout by showing disabled state overlay - Prevent UX inconsistency where disabled servers disappeared entirely * fix: show button height * feat: add enhance card animations * cleanup
This commit is contained in:
@@ -3,10 +3,12 @@ import { cn } from "@lib/utils";
|
||||
import { Progress } from "@ui/progress";
|
||||
import { ServerState } from "komodo_client/dist/types";
|
||||
import { Cpu, Database, MemoryStick, LucideIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface ServerStatsMiniProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
interface StatItemProps {
|
||||
@@ -22,7 +24,7 @@ const StatItem = ({ icon: Icon, label, percentage, type, isUnreachable, getTextC
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="w-3 h-3 text-muted-foreground" aria-hidden="true" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center justify-between pb-1">
|
||||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
<span
|
||||
className={cn(
|
||||
@@ -41,12 +43,20 @@ const StatItem = ({ icon: Icon, label, percentage, type, isUnreachable, getTextC
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ServerStatsMini = ({ id, className }: ServerStatsMiniProps) => {
|
||||
export const ServerStatsMini = ({ id, className, enabled = true }: ServerStatsMiniProps) => {
|
||||
const calculatePercentage = (value: number) =>
|
||||
Number((value ?? 0).toFixed(2));
|
||||
|
||||
const server = useRead("ListServers", {}).data?.find((s) => s.id === id);
|
||||
const serverDetails = useRead("GetServer", { server: id }).data;
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const server = servers?.find((s) => s.id === id);
|
||||
|
||||
const isServerAvailable = server &&
|
||||
server.info.state !== ServerState.Disabled &&
|
||||
server.info.state !== ServerState.NotOk;
|
||||
|
||||
const serverDetails = useRead("GetServer", { server: id }, {
|
||||
enabled: enabled && isServerAvailable
|
||||
}).data;
|
||||
|
||||
const cpuWarning = serverDetails?.config?.cpu_warning ?? 75;
|
||||
const cpuCritical = serverDetails?.config?.cpu_critical ?? 90;
|
||||
@@ -63,42 +73,52 @@ export const ServerStatsMini = ({ id, className }: ServerStatsMiniProps) => {
|
||||
if (percentage >= warning) return "text-yellow-600";
|
||||
return "text-green-600";
|
||||
};
|
||||
|
||||
const stats = useRead(
|
||||
"GetSystemStats",
|
||||
{ server: id },
|
||||
{
|
||||
enabled: server ? server.info.state !== "Disabled" : false,
|
||||
refetchInterval: 10_000,
|
||||
enabled: enabled && isServerAvailable,
|
||||
refetchInterval: 15_000,
|
||||
staleTime: 5_000,
|
||||
},
|
||||
).data;
|
||||
|
||||
if (!server || server.info.state === "Disabled") {
|
||||
if (!server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cpuPercentage = stats ? calculatePercentage(stats.cpu_perc) : 0;
|
||||
const memoryPercentage = stats && stats.mem_total_gb > 0 ? calculatePercentage((stats.mem_used_gb / stats.mem_total_gb) * 100) : 0;
|
||||
const calculations = useMemo(() => {
|
||||
const cpuPercentage = stats ? calculatePercentage(stats.cpu_perc) : 0;
|
||||
const memoryPercentage = stats && stats.mem_total_gb > 0 ? calculatePercentage((stats.mem_used_gb / stats.mem_total_gb) * 100) : 0;
|
||||
|
||||
const diskUsed = stats ? stats.disks.reduce((acc, disk) => acc + disk.used_gb, 0) : 0;
|
||||
const diskTotal = stats ? stats.disks.reduce((acc, disk) => acc + disk.total_gb, 0) : 0;
|
||||
const diskPercentage = diskTotal > 0? calculatePercentage((diskUsed / diskTotal) * 100) : 0;
|
||||
const diskUsed = stats ? stats.disks.reduce((acc, disk) => acc + disk.used_gb, 0) : 0;
|
||||
const diskTotal = stats ? stats.disks.reduce((acc, disk) => acc + disk.total_gb, 0) : 0;
|
||||
const diskPercentage = diskTotal > 0 ? calculatePercentage((diskUsed / diskTotal) * 100) : 0;
|
||||
|
||||
const isUnreachable = !stats || server.info.state === ServerState.NotOk;
|
||||
const isDisabled = server.info.state === ServerState.Disabled;
|
||||
|
||||
const isUnreachable = !stats || server.info.state === ServerState.NotOk;
|
||||
const unreachableClass = isUnreachable ? "opacity-50" : "";
|
||||
return {
|
||||
cpuPercentage,
|
||||
memoryPercentage,
|
||||
diskPercentage,
|
||||
isUnreachable,
|
||||
isDisabled
|
||||
};
|
||||
}, [stats, server.info.state]);
|
||||
|
||||
const statItems = [
|
||||
const { cpuPercentage, memoryPercentage, diskPercentage, isUnreachable, isDisabled } = calculations;
|
||||
const overlayClass = (isUnreachable || isDisabled) ? "opacity-50" : "";
|
||||
|
||||
const statItems = useMemo(() => [
|
||||
{ icon: Cpu, label: "CPU", percentage: cpuPercentage, type: "cpu" as const },
|
||||
{ icon: MemoryStick, label: "Memory", percentage: memoryPercentage, type: "memory" as const },
|
||||
{ icon: Database, label: "Disk", percentage: diskPercentage, type: "disk" as const },
|
||||
];
|
||||
], [cpuPercentage, memoryPercentage, diskPercentage]);
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", unreachableClass, className)}>
|
||||
{isUnreachable && (
|
||||
<div className="text-xs text-muted-foreground italic text-center">
|
||||
Unreachable
|
||||
</div>
|
||||
)}
|
||||
<div className={cn("relative flex flex-col gap-2", overlayClass, className)}>
|
||||
{statItems.map((item) => (
|
||||
<StatItem
|
||||
key={item.label}
|
||||
@@ -106,10 +126,20 @@ export const ServerStatsMini = ({ id, className }: ServerStatsMiniProps) => {
|
||||
label={item.label}
|
||||
percentage={item.percentage}
|
||||
type={item.type}
|
||||
isUnreachable={isUnreachable}
|
||||
isUnreachable={isUnreachable || isDisabled}
|
||||
getTextColor={getTextColor}
|
||||
/>
|
||||
))}
|
||||
{isDisabled && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-black/60 z-10">
|
||||
<span className="text-xs text-foreground font-bold italic text-center">Disabled</span>
|
||||
</div>
|
||||
)}
|
||||
{isUnreachable && !isDisabled && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-black/60 z-10">
|
||||
<span className="text-xs text-foreground font-bold italic text-center">Unreachable</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -43,7 +43,6 @@ export default function Dashboard() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
updatePreference(
|
||||
"showServerStats",
|
||||
@@ -170,7 +169,7 @@ const RecentCard = ({
|
||||
<Link
|
||||
to={`${usableResourcePath(type)}/${id}`}
|
||||
className={cn(
|
||||
"w-full px-3 py-2 border rounded-md hover:bg-accent/25 hover:-translate-y-1 transition-all flex flex-col justify-between",
|
||||
"w-full px-3 py-2 border rounded-md hover:bg-accent/25 hover:-translate-y-1 transition-all duration-1000 linear flex flex-col justify-between",
|
||||
showServerStats ? "min-h-32" : "h-20",
|
||||
className,
|
||||
)}
|
||||
@@ -185,17 +184,22 @@ const RecentCard = ({
|
||||
{type === "Stack" && <StackUpdateAvailable id={id} small />}
|
||||
</div>
|
||||
|
||||
<div
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-2 w-full",
|
||||
showServerStats ? "mt-2 flex-1" : "mt-auto",
|
||||
"overflow-hidden w-full transition-opacity transition-all duration-1000 linear",
|
||||
showServerStats
|
||||
? "max-h-40 opacity-100 py-2"
|
||||
: "max-h-0 opacity-0 py-0"
|
||||
)}
|
||||
>
|
||||
{showServerStats && <ServerStatsMini id={id} />}
|
||||
<div className="flex gap-2 w-full py-1">
|
||||
<TagsWithBadge className="flex-row" tag_ids={tags} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<ServerStatsMini id={id} enabled={showServerStats} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 w-full py-1">
|
||||
<TagsWithBadge tag_ids={tags} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user