fix(uptime): enhance monitor status handling and display logic in MonitorCard
This commit is contained in:
@@ -6,6 +6,18 @@ import { Activity, ArrowUp, ArrowDown, Settings, X, Pause, RefreshCw, Plus, Load
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
type BaseMonitorStatus = 'up' | 'down' | 'pending';
|
||||
type EffectiveMonitorStatus = BaseMonitorStatus | 'paused';
|
||||
|
||||
const normalizeMonitorStatus = (status: string | undefined): BaseMonitorStatus => {
|
||||
const normalized = status?.toLowerCase();
|
||||
if (normalized === 'up' || normalized === 'down' || normalized === 'pending') {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return 'down';
|
||||
};
|
||||
|
||||
const MonitorCard: FC<{ monitor: UptimeMonitor; onEdit: (monitor: UptimeMonitor) => void; t: (key: string, options?: Record<string, unknown>) => string }> = ({ monitor, onEdit, t }) => {
|
||||
const { data: history } = useQuery({
|
||||
queryKey: ['uptimeHistory', monitor.id],
|
||||
@@ -64,27 +76,33 @@ const MonitorCard: FC<{ monitor: UptimeMonitor; onEdit: (monitor: UptimeMonitor)
|
||||
? history.reduce((a, b) => new Date(a.created_at) > new Date(b.created_at) ? a : b)
|
||||
: null
|
||||
|
||||
const isPending = monitor.status === 'pending' && (!history || history.length === 0);
|
||||
const isUp = latestBeat ? latestBeat.status === 'up' : monitor.status === 'up';
|
||||
const hasHistory = Boolean(history && history.length > 0);
|
||||
const isPaused = monitor.enabled === false;
|
||||
const effectiveStatus: EffectiveMonitorStatus = isPaused
|
||||
? 'paused'
|
||||
: latestBeat
|
||||
? (latestBeat.status === 'up' ? 'up' : 'down')
|
||||
: monitor.status === 'pending' && !hasHistory
|
||||
? 'pending'
|
||||
: normalizeMonitorStatus(monitor.status);
|
||||
|
||||
return (
|
||||
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 border-l-4 ${isPaused ? 'border-l-yellow-400' : isPending ? 'border-l-amber-500' : isUp ? 'border-l-green-500' : 'border-l-red-500'}`} data-testid="monitor-card">
|
||||
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 border-l-4 ${effectiveStatus === 'paused' ? 'border-l-yellow-400' : effectiveStatus === 'pending' ? 'border-l-amber-500' : effectiveStatus === 'up' ? 'border-l-green-500' : 'border-l-red-500'}`} data-testid="monitor-card">
|
||||
{/* Top Row: Name (left), Badge (center-right), Settings (right) */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white flex-1 min-w-0 truncate">{monitor.name}</h3>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className={`flex items-center justify-center px-3 py-1 rounded-full text-sm font-medium min-w-22.5 ${
|
||||
isPaused
|
||||
effectiveStatus === 'paused'
|
||||
? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
|
||||
: isPending
|
||||
: effectiveStatus === 'pending'
|
||||
? 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 animate-pulse motion-reduce:animate-none'
|
||||
: isUp
|
||||
: effectiveStatus === 'up'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||||
}`} data-testid="status-badge" data-status={isPaused ? 'paused' : monitor.status} role="status" aria-label={isPaused ? t('uptime.paused') : isPending ? t('uptime.pending') : isUp ? 'UP' : 'DOWN'}>
|
||||
{isPaused ? <Pause className="w-4 h-4 mr-1" /> : isPending ? <Loader className="w-4 h-4 mr-1 animate-spin motion-reduce:animate-none" aria-hidden="true" /> : isUp ? <ArrowUp className="w-4 h-4 mr-1" /> : <ArrowDown className="w-4 h-4 mr-1" />}
|
||||
{isPaused ? t('uptime.paused') : isPending ? t('uptime.pending') : monitor.status.toUpperCase()}
|
||||
}`} data-testid="status-badge" data-status={effectiveStatus} role="status" aria-label={effectiveStatus === 'paused' ? t('uptime.paused') : effectiveStatus === 'pending' ? t('uptime.pending') : effectiveStatus === 'up' ? 'UP' : 'DOWN'}>
|
||||
{effectiveStatus === 'paused' ? <Pause className="w-4 h-4 mr-1" /> : effectiveStatus === 'pending' ? <Loader className="w-4 h-4 mr-1 animate-spin motion-reduce:animate-none" aria-hidden="true" /> : effectiveStatus === 'up' ? <ArrowUp className="w-4 h-4 mr-1" /> : <ArrowDown className="w-4 h-4 mr-1" />}
|
||||
{effectiveStatus === 'paused' ? t('uptime.paused') : effectiveStatus === 'pending' ? t('uptime.pending') : effectiveStatus.toUpperCase()}
|
||||
</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
@@ -203,7 +221,7 @@ Message: ${beat.message}`}
|
||||
/>
|
||||
))}
|
||||
{(!history || history.length === 0) && (
|
||||
<div className="absolute w-full text-center text-xs text-gray-400">{isPending ? t('uptime.pendingFirstCheck') : t('uptime.noHistoryAvailable')}</div>
|
||||
<div className="absolute w-full text-center text-xs text-gray-400">{effectiveStatus === 'pending' ? t('uptime.pendingFirstCheck') : t('uptime.noHistoryAvailable')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user