mirror of https://github.com/buster-so/buster.git
376 lines
14 KiB
TypeScript
376 lines
14 KiB
TypeScript
import type { HealthCheckResponse } from '@buster/server-shared/healthcheck';
|
|
import { createFileRoute } from '@tanstack/react-router';
|
|
import { useHealthcheck } from '@/api/buster_rest/healthcheck/queryRequests';
|
|
import { useGetUserBasicInfo } from '@/api/buster_rest/users/useGetUserInfo';
|
|
import type { RustApiError } from '@/api/errors';
|
|
import { useGetSupabaseUser } from '@/context/Supabase';
|
|
|
|
export const Route = createFileRoute('/app/healthcheck')({
|
|
component: RouteComponent,
|
|
});
|
|
|
|
function RouteComponent() {
|
|
const { data, isLoading, error } = useHealthcheck();
|
|
const supabaseUser = useGetSupabaseUser();
|
|
const user = useGetUserBasicInfo();
|
|
|
|
if (isLoading) {
|
|
return <LoadingState />;
|
|
}
|
|
|
|
if (error) {
|
|
return <ErrorState error={error} />;
|
|
}
|
|
|
|
if (!data) {
|
|
return <ErrorState error={new Error('No data received')} />;
|
|
}
|
|
|
|
return <HealthcheckDashboard data={data} supabaseUser={supabaseUser} user={user} />;
|
|
}
|
|
|
|
function LoadingState() {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
|
<p className="text-gray-600">Checking system health...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ErrorState({ error }: { error: Error | RustApiError }) {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-red-50 to-white flex items-center justify-center">
|
|
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full mx-4">
|
|
<div className="flex items-center mb-4">
|
|
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mr-4">
|
|
<svg
|
|
className="w-6 h-6 text-red-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-gray-900">Health Check Failed</h2>
|
|
<p className="text-gray-600">Unable to retrieve system status</p>
|
|
</div>
|
|
</div>
|
|
<div className="bg-red-50 border border-red-200 rounded-md p-3">
|
|
<p className="text-sm text-red-800">{error.message}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function HealthcheckDashboard({
|
|
data,
|
|
supabaseUser,
|
|
user,
|
|
}: {
|
|
data: HealthCheckResponse;
|
|
supabaseUser: ReturnType<typeof useGetSupabaseUser>;
|
|
user: ReturnType<typeof useGetUserBasicInfo>;
|
|
}) {
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'healthy':
|
|
case 'pass':
|
|
return 'text-green-600 bg-green-100';
|
|
case 'degraded':
|
|
case 'warn':
|
|
return 'text-yellow-600 bg-yellow-100';
|
|
case 'unhealthy':
|
|
case 'fail':
|
|
return 'text-red-600 bg-red-100';
|
|
default:
|
|
return 'text-gray-600 bg-gray-100';
|
|
}
|
|
};
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
switch (status) {
|
|
case 'healthy':
|
|
case 'pass':
|
|
return (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
);
|
|
case 'degraded':
|
|
case 'warn':
|
|
return (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
/>
|
|
</svg>
|
|
);
|
|
case 'unhealthy':
|
|
case 'fail':
|
|
return (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
);
|
|
default:
|
|
return (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
};
|
|
|
|
const formatUptime = (seconds: number) => {
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (days > 0) {
|
|
return `${days}d ${hours}h ${minutes}m`;
|
|
}
|
|
if (hours > 0) {
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
return `${minutes}m`;
|
|
};
|
|
|
|
const formatTimestamp = (timestamp: string) => {
|
|
return new Date(timestamp).toLocaleString();
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-white p-6">
|
|
<div className="max-w-6xl mx-auto">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">System Health Dashboard</h1>
|
|
<p className="text-gray-600">Real-time monitoring of system components</p>
|
|
</div>
|
|
|
|
{/* User Data Section */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
{/* Supabase User Data */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
|
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
|
|
<svg
|
|
className="w-4 h-4 text-blue-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
Supabase User
|
|
</h3>
|
|
<div className="bg-gray-50 rounded-md p-4">
|
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
|
|
{JSON.stringify(supabaseUser, null, 2)}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
|
|
{/* User Basic Info */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
|
<div className="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center mr-3">
|
|
<svg
|
|
className="w-4 h-4 text-green-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
User Basic Info
|
|
</h3>
|
|
<div className="bg-gray-50 rounded-md p-4">
|
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
|
|
{JSON.stringify(user, null, 2)}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Overall Status Card */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<div
|
|
className={`w-12 h-12 rounded-full flex items-center justify-center mr-4 ${getStatusColor(
|
|
data.status
|
|
)}`}
|
|
>
|
|
{getStatusIcon(data.status)}
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-semibold text-gray-900 capitalize">{data.status}</h2>
|
|
<p className="text-gray-600">Overall System Status</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-sm text-gray-500">Last checked</p>
|
|
<p className="text-gray-900 font-medium">{formatTimestamp(data.timestamp)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Metrics Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center">
|
|
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
|
|
<svg
|
|
className="w-5 h-5 text-blue-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Uptime</p>
|
|
<p className="text-xl font-semibold text-gray-900">{formatUptime(data.uptime)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center">
|
|
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center mr-3">
|
|
<svg
|
|
className="w-5 h-5 text-purple-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h4a1 1 0 011 1v1a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V7H3a1 1 0 01-1-1V5a1 1 0 011-1h4z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Version</p>
|
|
<p className="text-xl font-semibold text-gray-900">{data.version}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center">
|
|
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-3">
|
|
<svg
|
|
className="w-5 h-5 text-green-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9v-9m0-9v9"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Environment</p>
|
|
<p className="text-xl font-semibold text-gray-900 capitalize">{data.environment}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Component Checks */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
<h3 className="text-xl font-semibold text-gray-900 mb-6">Component Health Checks</h3>
|
|
<div className="space-y-4">
|
|
{Object.entries(data.checks).map(([componentName, check]) => (
|
|
<div key={componentName} className="border border-gray-200 rounded-lg p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center mr-3 ${getStatusColor(
|
|
check.status
|
|
)}`}
|
|
>
|
|
{getStatusIcon(check.status)}
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-gray-900 capitalize">{componentName}</h4>
|
|
{check.message && <p className="text-sm text-gray-600">{check.message}</p>}
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<span
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium capitalize ${getStatusColor(
|
|
check.status
|
|
)}`}
|
|
>
|
|
{check.status}
|
|
</span>
|
|
{check.responseTime && (
|
|
<p className="text-sm text-gray-500 mt-1">{check.responseTime}ms</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-8 text-center text-gray-500 text-sm">
|
|
<p>System health is monitored continuously. Data refreshes automatically.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|