Redesign StatsPage header with improved UI/UX

- Replace old header design with modern card-based layout
- Add color-coded metric cards with FontAwesome icons
- Improve time period selector with pill-style buttons
- Replace custom SVG icons with FontAwesome icons (fa-chart-bar, fa-bolt, fa-database, fa-download, fa-arrow-left)
- Enhance visual hierarchy with proper spacing and gradients
- Fix mobile responsiveness for duration selector (flex-wrap support)
- Simplify CSS by removing redundant properties
- Merge summary and time selector sections for better flow
This commit is contained in:
Dylan Knutson
2025-07-23 07:07:52 +00:00
parent e027dc9bc4
commit c96b1d9cc1

View File

@@ -61,49 +61,111 @@ export const StatsPage: React.FC<StatsPageProps> = ({
}));
return (
<div className="mx-auto max-w-7xl px-4 mt-6 sm:mt-8">
<h1 className="text-2xl font-bold text-slate-900 text-center">HTTP Request Log Stats</h1>
<div className="mt-2 text-center">
<a
href="/log_entries"
className="text-blue-600 hover:text-blue-800 transition-colors"
>
Back to Index
</a>
</div>
<div className="mt-6 text-center">
{/* Time Window Selector */}
<div className="space-x-2">
{availableTimeWindows.map((timeWindowOption) => (
<React.Fragment key={timeWindowOption.seconds}>
{timeWindowOption.active ? (
<span className="rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800">
{timeWindowOption.label}
</span>
) : (
<a
href={timeWindowOption.path}
className="rounded-full px-3 py-1 text-sm text-blue-600 hover:text-blue-800 hover:bg-blue-50 transition-colors"
>
{timeWindowOption.label}
</a>
)}
</React.Fragment>
))}
<div className="mx-auto max-w-7xl px-4 mt-8">
{/* Header Section */}
<div className="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
{/* Top Bar */}
<div className="bg-slate-50 px-6 py-4 border-b border-slate-200">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-slate-900">HTTP Request Analytics</h1>
<a
href="/log_entries"
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 hover:bg-white border border-slate-300 rounded-lg transition-colors hover:shadow-sm"
>
<i className="fas fa-arrow-left" />
Back to Log Entries
</a>
</div>
</div>
<StatsCard
requestCount={lastWindowCount}
timeWindow={timeWindowFormatted}
requestsPerSecond={requestsPerSecond}
totalBytes={totalBytesFormatted}
bytesPerSecond={bytesPerSecondFormatted}
/>
{/* Stats Summary */}
<div className="px-6 py-6 bg-gradient-to-br from-blue-50 to-indigo-50 border-t border-slate-200">
<div className="text-center mb-4">
<h3 className="text-lg font-semibold text-slate-900 mb-1">Summary for {timeWindowFormatted}</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Total Requests */}
<div className="bg-white rounded-lg p-4 shadow-sm border border-slate-200">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide">Total Requests</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{lastWindowCount.toLocaleString()}</p>
</div>
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<i className="fas fa-chart-bar text-blue-600" />
</div>
</div>
</div>
{/* Requests per Second */}
<div className="bg-white rounded-lg p-4 shadow-sm border border-slate-200">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide">Requests/sec</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{requestsPerSecond}</p>
</div>
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
<i className="fas fa-bolt text-green-600" />
</div>
</div>
</div>
{/* Total Data */}
<div className="bg-white rounded-lg p-4 shadow-sm border border-slate-200">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide">Total Data</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{totalBytesFormatted}</p>
</div>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<i className="fas fa-database text-purple-600" />
</div>
</div>
</div>
{/* Data per Second */}
<div className="bg-white rounded-lg p-4 shadow-sm border border-slate-200">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-medium text-slate-500 uppercase tracking-wide">Data/sec</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{bytesPerSecondFormatted}</p>
</div>
<div className="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
<i className="fas fa-download text-orange-600" />
</div>
</div>
</div>
</div>
</div>
{/* Time Window Selector */}
<div className="px-4 py-4 bg-gradient-to-br from-blue-50 to-indigo-50">
<div className="text-center">
<div className="inline-flex flex-wrap justify-center bg-white/70 backdrop-blur-sm rounded-lg p-1 shadow-md border border-white/50 gap-1">
{availableTimeWindows.map((timeWindowOption, index) => (
<React.Fragment key={timeWindowOption.seconds}>
{timeWindowOption.active ? (
<span className="px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded-md shadow-sm">
{timeWindowOption.label}
</span>
) : (
<a
href={timeWindowOption.path}
className="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 hover:bg-white hover:shadow-sm rounded-md transition-colors border border-transparent hover:border-slate-200"
>
{timeWindowOption.label}
</a>
)}
</React.Fragment>
))}
</div>
</div>
</div>
</div>
{/* Tables Grid - 2 columns */}
<div className="mt-8 mb-8 grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-8">
<div>
<h2 className="text-xl font-bold text-slate-900 mb-3">By Content Type</h2>
<SortableTable