94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
|
|
import { DashboardSection } from "./DashboardSection";
|
||
|
|
import { DashboardEmptyState } from "./DashboardEmptyState";
|
||
|
|
|
||
|
|
type SessionSummary = {
|
||
|
|
key: string;
|
||
|
|
title: string;
|
||
|
|
subtitle: string;
|
||
|
|
usage: string;
|
||
|
|
lastSeenAt: string | null;
|
||
|
|
isMain: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
interface SessionsSectionProps {
|
||
|
|
sessions: SessionSummary[];
|
||
|
|
activeSessions: number;
|
||
|
|
hasConfiguredGateways: boolean;
|
||
|
|
isLoading: boolean;
|
||
|
|
gatewayUnavailableCount: number;
|
||
|
|
gatewayTargetsCount: number;
|
||
|
|
formatCount: (n: number) => string;
|
||
|
|
formatRelative: (ts: string) => string;
|
||
|
|
dash: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function SessionsSection({
|
||
|
|
sessions,
|
||
|
|
activeSessions,
|
||
|
|
hasConfiguredGateways,
|
||
|
|
isLoading,
|
||
|
|
gatewayUnavailableCount,
|
||
|
|
gatewayTargetsCount,
|
||
|
|
formatCount,
|
||
|
|
formatRelative,
|
||
|
|
dash,
|
||
|
|
}: SessionsSectionProps) {
|
||
|
|
return (
|
||
|
|
<DashboardSection title="Sessions" action={{ label: formatCount(activeSessions), href: "#" }}>
|
||
|
|
<div className="max-h-[310px] space-y-2 overflow-x-hidden overflow-y-auto pr-1">
|
||
|
|
{!hasConfiguredGateways ? (
|
||
|
|
<DashboardEmptyState message="No gateways are configured for any board yet." />
|
||
|
|
) : isLoading ? (
|
||
|
|
<DashboardEmptyState message="Loading sessions..." />
|
||
|
|
) : sessions.length > 0 ? (
|
||
|
|
<>
|
||
|
|
{gatewayUnavailableCount > 0 && (
|
||
|
|
<DashboardEmptyState
|
||
|
|
tone="warning"
|
||
|
|
message={`${formatCount(gatewayUnavailableCount)} gateway${gatewayUnavailableCount === 1 ? "" : "s"} unavailable; showing sessions from reachable gateways.`}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
{sessions.map((session) => (
|
||
|
|
<div
|
||
|
|
key={session.key}
|
||
|
|
className="overflow-hidden rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] px-3 py-2"
|
||
|
|
>
|
||
|
|
<div className="flex items-center justify-between gap-3">
|
||
|
|
<div className="min-w-0 flex-1">
|
||
|
|
<p className="truncate text-sm font-medium text-strong">
|
||
|
|
<span
|
||
|
|
className={`mr-2 inline-block h-2 w-2 rounded-full ${
|
||
|
|
session.isMain
|
||
|
|
? "bg-[color:var(--success)]"
|
||
|
|
: "bg-[color:var(--border-strong)]"
|
||
|
|
}`}
|
||
|
|
/>
|
||
|
|
{session.title}
|
||
|
|
</p>
|
||
|
|
<p className="mt-0.5 truncate text-xs text-muted">{session.subtitle}</p>
|
||
|
|
</div>
|
||
|
|
<div className="min-w-0 max-w-[45%] text-right">
|
||
|
|
<p className="truncate text-xs font-medium text-strong">
|
||
|
|
{session.usage === dash ? "Usage unavailable" : session.usage}
|
||
|
|
</p>
|
||
|
|
<p className="text-[11px] text-muted">
|
||
|
|
{session.lastSeenAt ? formatRelative(session.lastSeenAt) : "Activity unavailable"}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</>
|
||
|
|
) : gatewayUnavailableCount === gatewayTargetsCount ? (
|
||
|
|
<DashboardEmptyState
|
||
|
|
tone="danger"
|
||
|
|
message="Session data is unavailable for all configured gateways."
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<DashboardEmptyState message="No active sessions detected." />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</DashboardSection>
|
||
|
|
);
|
||
|
|
}
|