+
+
+ {getStatusIcon(status)}
+
+
+
{component.name}
+ {service?.responseTime && service.responseTime > 0 && (
+
+
+ {service.responseTime}ms
+
+ )}
+
{component.description && (
-
{component.description}
+
{component.description}
)}
- {service && (
-
-
Uptime: {uptime}%
- {service.responseTime > 0 && (
-
- Response: {service.responseTime}ms
-
- )}
+
+
+
+
{uptime}% uptime (90 days)
- )}
+ {service?.lastChecked && (
+
Last checked: {format(new Date(service.lastChecked), 'HH:mm:ss')}
+ )}
+
-
- {status === 'up' ? 'Operational' :
- status === 'down' ? 'Down' :
- status === 'paused' ? 'Paused' : 'Warning'}
-
+
+ {getStatusBadge(status)}
+
- {/* Individual component uptime history */}
{component.service_id && (
-
+
+
90-day uptime history
+
+
)}
);
diff --git a/application/src/components/public/CurrentStatusSection.tsx b/application/src/components/public/CurrentStatusSection.tsx
index f24e197..e6f1132 100644
--- a/application/src/components/public/CurrentStatusSection.tsx
+++ b/application/src/components/public/CurrentStatusSection.tsx
@@ -1,6 +1,6 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Shield, Clock } from 'lucide-react';
+import { Shield, Clock, CheckCircle, AlertTriangle, XCircle, Wrench } from 'lucide-react';
import { format } from 'date-fns';
import { OperationalPageRecord } from '@/types/operational.types';
import { StatusPageComponentRecord } from '@/types/statusPageComponents.types';
@@ -75,31 +75,82 @@ const getStatusColor = (status: OperationalPageRecord['status']) => {
}
};
+const getStatusIcon = (status: OperationalPageRecord['status']) => {
+ switch (status) {
+ case 'operational':
+ return
;
+ case 'degraded':
+ return
;
+ case 'maintenance':
+ return
;
+ case 'major_outage':
+ return
;
+ default:
+ return
;
+ }
+};
+
+const getStatusBackground = (status: OperationalPageRecord['status']) => {
+ switch (status) {
+ case 'operational':
+ return 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800';
+ case 'degraded':
+ return 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800';
+ case 'maintenance':
+ return 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800';
+ case 'major_outage':
+ return 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800';
+ default:
+ return 'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800';
+ }
+};
+
export const CurrentStatusSection = ({ page, components, services }: CurrentStatusSectionProps) => {
const actualStatus = getActualStatus(components, services);
+ const displayStatus = actualStatus; // Use actual status for real-time accuracy
return (
-
+
-
-
- Current Status
+
+
+ System Status
-
-
-
-
- {getStatusMessage(actualStatus)}
-
+
+
+
+ {getStatusIcon(displayStatus)}
+
+
+ {getStatusMessage(displayStatus)}
+
+
+ Status automatically updated based on component health
+
+
+
+
+ {displayStatus === 'operational' ? 'All Systems Operational' :
+ displayStatus === 'degraded' ? 'Degraded Performance' :
+ displayStatus === 'maintenance' ? 'Under Maintenance' : 'Major Outage'}
+
-
-
-
Last updated {format(new Date(page.updated), 'MMM dd, yyyy HH:mm')} UTC
+
+
+
+
+ Last updated: {format(new Date(), 'MMM dd, yyyy HH:mm')} UTC
+
+
+
+
Live status monitoring
+
diff --git a/application/src/components/public/OverallUptimeSection.tsx b/application/src/components/public/OverallUptimeSection.tsx
index 9387f1b..2cf8f6c 100644
--- a/application/src/components/public/OverallUptimeSection.tsx
+++ b/application/src/components/public/OverallUptimeSection.tsx
@@ -1,5 +1,7 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { TrendingUp, Calendar, BarChart3 } from 'lucide-react';
import { UptimeData } from '@/types/service.types';
import { UptimeHistoryRenderer } from './UptimeHistoryRenderer';
@@ -24,24 +26,170 @@ export const OverallUptimeSection = ({ uptimeData }: OverallUptimeSectionProps)
return Math.round((upRecords / totalRecords) * 100 * 100) / 100;
};
+ const getUptimeTrend = () => {
+ const uptime = getOverallUptime();
+ if (uptime >= 99.9) return 'excellent';
+ if (uptime >= 99.5) return 'good';
+ if (uptime >= 95) return 'fair';
+ return 'poor';
+ };
+
+ const getIncidentCount = () => {
+ const allHistories = Object.values(uptimeData);
+ let incidents = 0;
+
+ allHistories.forEach(history => {
+ let wasDown = false;
+ history.forEach(record => {
+ if (record.status === 'down' && !wasDown) {
+ incidents++;
+ wasDown = true;
+ } else if (record.status === 'up') {
+ wasDown = false;
+ }
+ });
+ });
+
+ return incidents;
+ };
+
+ const getBadgeClassName = (trend: string) => {
+ switch (trend) {
+ case 'excellent':
+ return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
+ case 'good':
+ return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200';
+ case 'fair':
+ return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200';
+ default:
+ return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
+ }
+ };
+
+ const getTrendText = (trend: string) => {
+ switch (trend) {
+ case 'excellent':
+ return 'Excellent';
+ case 'good':
+ return 'Good';
+ case 'fair':
+ return 'Fair';
+ default:
+ return 'Needs Improvement';
+ }
+ };
+
+ const getStatusMessage = (uptime: number) => {
+ if (uptime >= 99.9) {
+ return "All systems are performing excellently with minimal downtime.";
+ } else if (uptime >= 99.5) {
+ return "Systems are performing well with occasional minor issues.";
+ } else if (uptime >= 95) {
+ return "We're working to improve system reliability and reduce incidents.";
+ } else {
+ return "We apologize for recent service disruptions and are actively working on improvements.";
+ }
+ };
+
+ const overallUptime = getOverallUptime();
+ const trend = getUptimeTrend();
+ const incidentCount = getIncidentCount();
+
return (
- Overall Uptime History (Last 90 days)
+
+
+ Performance Metrics (Last 90 Days)
+
+
+ Historical performance and reliability statistics
+
-
-
- {Object.keys(uptimeData).length > 0 ?
-
:
-
}
+
+
+
+
+
+
+ Overall Uptime
+
+
+ {getTrendText(trend)}
+
+
+
{overallUptime}%
+
+ Target: 99.9%
+
+
+
+
+
+
+ Incidents
+
+
{incidentCount}
+
+ Last 90 days
+
+
+
+
+
+
+ Avg Response
+
+
100ms
+
+ Response time
+
+
-
-
90 days ago
-
Today
+
+
+
+
+
+ {Object.keys(uptimeData).length > 0 ? (
+
+ ) : (
+
+
+ {Array.from({ length: 90 }, (_, i) => (
+
+ ))}
+
+
+ )}
+
+
+
+ 90 days ago
+ Today
+
-
-
{getOverallUptime()}%
-
Overall uptime
+
+
+
+ {getStatusMessage(overallUptime)}
+
diff --git a/application/src/components/public/PublicStatusPage.tsx b/application/src/components/public/PublicStatusPage.tsx
index 8cc686b..8f775f0 100644
--- a/application/src/components/public/PublicStatusPage.tsx
+++ b/application/src/components/public/PublicStatusPage.tsx
@@ -2,6 +2,7 @@
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Button } from '@/components/ui/button';
+import { RefreshCw, AlertCircle } from 'lucide-react';
import { usePublicStatusPageData } from './hooks/usePublicStatusPageData';
import { StatusPageHeader } from './StatusPageHeader';
import { CurrentStatusSection } from './CurrentStatusSection';
@@ -11,19 +12,36 @@ import { PublicStatusPageFooter } from './PublicStatusPageFooter';
export const PublicStatusPage = () => {
const { slug } = useParams<{ slug: string }>();
+ console.log('PublicStatusPage - slug from params:', slug);
+
const { page, components, services, uptimeData, loading, error } = usePublicStatusPageData(slug);
+ const [lastUpdated, setLastUpdated] = useState(new Date());
+
+ // Auto-refresh every 30 seconds
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setLastUpdated(new Date());
+ // The usePublicStatusPageData hook handles data refetching
+ }, 30000);
+
+ return () => clearInterval(interval);
+ }, []);
// Apply theme to document
useEffect(() => {
if (page) {
const root = document.documentElement;
+
+ // Remove any existing theme classes
+ root.classList.remove('dark', 'light');
+
+ // Apply the selected theme
if (page.theme === 'dark') {
root.classList.add('dark');
- root.classList.remove('light');
- } else {
+ } else if (page.theme === 'light') {
root.classList.add('light');
- root.classList.remove('dark');
}
+ // For 'default' theme, don't add any class (uses system preference)
}
// Cleanup on unmount
@@ -33,12 +51,18 @@ export const PublicStatusPage = () => {
};
}, [page?.theme]);
+ console.log('PublicStatusPage state:', { loading, error, page: !!page, components: components.length, services: services.length });
+
if (loading) {
return (
-
-
-
Loading status page...
+
+
+
+
Loading Status Page
+
Fetching real-time system status...
+
Slug: {slug || 'No slug provided'}
+
);
@@ -47,10 +71,26 @@ export const PublicStatusPage = () => {
if (error || !page) {
return (
-
-
Page Not Found
-
{error || 'The requested status page could not be found.'}
-
+
+
+
+
Status Page Not Found
+
+ {error || 'The requested status page could not be found or is not publicly accessible.'}
+
+
Slug: {slug || 'No slug provided'}
+
+
+
+
+
);
@@ -62,7 +102,7 @@ export const PublicStatusPage = () => {
{/* Main Content */}
-
+
{/* Current Status */}
@@ -78,7 +118,7 @@ export const PublicStatusPage = () => {
{/* Footer */}
-
+
{/* Custom CSS */}
{page.custom_css && (
diff --git a/application/src/components/public/PublicStatusPageFooter.tsx b/application/src/components/public/PublicStatusPageFooter.tsx
index 41efd3f..785bb8a 100644
--- a/application/src/components/public/PublicStatusPageFooter.tsx
+++ b/application/src/components/public/PublicStatusPageFooter.tsx
@@ -1,23 +1,86 @@
-import { Globe } from 'lucide-react';
import { OperationalPageRecord } from '@/types/operational.types';
+import { format } from 'date-fns';
+import { Clock, Shield, Zap, RefreshCw } from 'lucide-react';
+import { Button } from '@/components/ui/button';
interface PublicStatusPageFooterProps {
page: OperationalPageRecord;
}
export const PublicStatusPageFooter = ({ page }: PublicStatusPageFooterProps) => {
+ const handleRefresh = () => {
+ window.location.reload();
+ };
+
return (
-
-
-
- {page.custom_domain ? (
-
Status page hosted at {page.custom_domain}
- ) : (
-
Status page
- )}
+
+
);
};
\ No newline at end of file
diff --git a/application/src/components/public/StatusPageHeader.tsx b/application/src/components/public/StatusPageHeader.tsx
index ad2c041..76d758c 100644
--- a/application/src/components/public/StatusPageHeader.tsx
+++ b/application/src/components/public/StatusPageHeader.tsx
@@ -1,6 +1,7 @@
-import { StatusBadge } from '@/components/operational-page/StatusBadge';
import { OperationalPageRecord } from '@/types/operational.types';
+import { Shield, Globe, ExternalLink } from 'lucide-react';
+import { Button } from '@/components/ui/button';
interface StatusPageHeaderProps {
page: OperationalPageRecord;
@@ -8,23 +9,63 @@ interface StatusPageHeaderProps {
export const StatusPageHeader = ({ page }: StatusPageHeaderProps) => {
return (
-
-
+
);
};
\ No newline at end of file
diff --git a/application/src/components/public/hooks/usePublicStatusPageData.ts b/application/src/components/public/hooks/usePublicStatusPageData.ts
index 2a0842b..75983ac 100644
--- a/application/src/components/public/hooks/usePublicStatusPageData.ts
+++ b/application/src/components/public/hooks/usePublicStatusPageData.ts
@@ -18,38 +18,59 @@ export const usePublicStatusPageData = (slug: string | undefined) => {
useEffect(() => {
const fetchPublicPage = async () => {
- if (!slug) return;
+ if (!slug) {
+ console.log('No slug provided');
+ setError('No status page slug provided');
+ setLoading(false);
+ return;
+ }
try {
+ console.log('Fetching public status page for slug:', slug);
setLoading(true);
+ setError(null);
// Fetch operational page
+ console.log('Fetching operational pages...');
const pages = await operationalPageService.getOperationalPages();
+ console.log('All pages:', pages);
+
const foundPage = pages.find(p => p.slug === slug && p.is_public === 'true');
+ console.log('Found page:', foundPage);
if (!foundPage) {
+ console.log('Page not found or not public');
setError('Status page not found or not public');
+ setLoading(false);
return;
}
setPage(foundPage);
+ console.log('Page set successfully');
// Fetch components for this page
+ console.log('Fetching components for page:', foundPage.id);
const pageComponents = await statusPageComponentsService.getStatusPageComponentsByOperationalId(foundPage.id);
+ console.log('Components found:', pageComponents);
setComponents(pageComponents);
// Fetch all services
+ console.log('Fetching all services...');
const allServices = await serviceService.getServices();
+ console.log('Services found:', allServices);
setServices(allServices);
// Fetch uptime data for each component that has a service
+ console.log('Fetching uptime data...');
const uptimePromises = pageComponents
.filter(component => component.service_id)
.map(async (component) => {
try {
+ console.log('Fetching uptime for service:', component.service_id);
const endDate = new Date();
const startDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); // Last 90 days
const history = await uptimeService.getUptimeHistory(component.service_id, 2000, startDate, endDate);
+ console.log(`Uptime history for ${component.service_id}:`, history.length, 'records');
return { serviceId: component.service_id, history };
} catch (error) {
console.error(`Error fetching uptime for service ${component.service_id}:`, error);
@@ -63,10 +84,13 @@ export const usePublicStatusPageData = (slug: string | undefined) => {
uptimeMap[result.serviceId] = result.history;
});
setUptimeData(uptimeMap);
+ console.log('Uptime data set successfully');
+
+ console.log('All data fetched successfully');
} catch (err) {
console.error('Error fetching public page:', err);
- setError('Failed to load status page');
+ setError(`Failed to load status page: ${err}`);
} finally {
setLoading(false);
}