// API Configuration const API_BASE_URL = '/api/v1'; const STATUS_ENDPOINTS = { overall: `${API_BASE_URL}/status`, services: `${API_BASE_URL}/status/services`, metrics: `${API_BASE_URL}/status/metrics`, incidents: `${API_BASE_URL}/status/incidents`, maintenance: `${API_BASE_URL}/status/maintenance`, uptime: `${API_BASE_URL}/status/uptime`, subscribe: `${API_BASE_URL}/status/subscribe` }; // Global state let statusData = { overall: null, services: [], metrics: null, incidents: [], maintenance: [], uptime: [] }; let updateInterval = null; let charts = {}; // DOM Elements const elements = { overallStatus: document.getElementById('overallStatus'), overallStatusIcon: document.getElementById('overallStatusIcon'), overallStatusText: document.getElementById('overallStatusText'), overallStatusDescription: document.getElementById('overallStatusDescription'), lastUpdated: document.getElementById('lastUpdated'), refreshBtn: document.getElementById('refreshBtn'), servicesList: document.getElementById('servicesList'), incidentsList: document.getElementById('incidentsList'), maintenanceList: document.getElementById('maintenanceList'), noIncidents: document.getElementById('noIncidents'), noMaintenance: document.getElementById('noMaintenance'), uptimeCalendar: document.getElementById('uptimeCalendar'), subscribeForm: document.getElementById('subscribeForm'), toast: document.getElementById('toast') }; // Utility Functions const formatDate = (date) => { return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }).format(new Date(date)); }; const formatDuration = (minutes) => { if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; }; const getStatusColor = (status) => { const colors = { operational: '#10b981', degraded: '#f59e0b', down: '#ef4444', maintenance: '#6366f1' }; return colors[status] || colors.operational; }; const getStatusIcon = (status) => { const icons = { operational: '🟢', degraded: '🟡', down: '🔴', maintenance: '🔵' }; return icons[status] || icons.operational; }; const showToast = (message, type = 'success') => { const toast = elements.toast; const toastMessage = toast.querySelector('.toast-message'); const toastIcon = toast.querySelector('.toast-icon'); toastMessage.textContent = message; if (type === 'success') { toast.style.background = 'var(--success)'; toastIcon.textContent = '✓'; } else { toast.style.background = 'var(--error)'; toastIcon.textContent = '✕'; } toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 4000); }; // Mock Data Generation (for demonstration) const generateMockData = () => { const services = [ { id: 'api', name: 'API Gateway', description: 'Core API services', icon: '🔗' }, { id: 'web', name: 'Web Application', description: 'TaskFlow web interface', icon: '🌐' }, { id: 'auth', name: 'Authentication', description: 'User authentication service', icon: '🔐' }, { id: 'database', name: 'Database', description: 'Primary database cluster', icon: '🗄️' }, { id: 'storage', name: 'File Storage', description: 'Document and file storage', icon: '📁' }, { id: 'notifications', name: 'Notifications', description: 'Email and push notifications', icon: '📧' }, { id: 'search', name: 'Search Service', description: 'Full-text search functionality', icon: '🔍' }, { id: 'analytics', name: 'Analytics', description: 'Usage analytics and reporting', icon: '📊' } ]; const statuses = ['operational', 'operational', 'operational', 'degraded', 'operational']; const responseTimeBase = 150; return { overall: { status: 'operational', description: 'All systems are operating normally', uptime: 99.95, responseTime: 245, activeIncidents: 0, scheduledMaintenance: 0 }, services: services.map(service => ({ ...service, status: statuses[Math.floor(Math.random() * statuses.length)], responseTime: responseTimeBase + Math.floor(Math.random() * 200), uptime: 99.5 + Math.random() * 0.5 })), metrics: { uptime: 99.95, responseTime: 245, requestVolume: 1250000, errorRate: 0.02 }, incidents: [ { id: '1', title: 'Intermittent API Timeouts', description: 'Some users may experience slow response times when accessing the API. Our team is investigating the issue.', status: 'investigating', severity: 'minor', startTime: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), affectedServices: ['api', 'web'] } ], maintenance: [ { id: '1', title: 'Database Maintenance Window', description: 'Scheduled maintenance to upgrade database servers. Brief service interruptions may occur.', startTime: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), endTime: new Date(Date.now() + 24 * 60 * 60 * 1000 + 2 * 60 * 60 * 1000).toISOString(), affectedServices: ['database', 'api'] } ], uptime: generateUptimeData() }; }; const generateUptimeData = () => { const data = []; const now = new Date(); for (let i = 89; i >= 0; i--) { const date = new Date(now); date.setDate(date.getDate() - i); let uptime = 100; if (Math.random() < 0.05) { // 5% chance of issues uptime = 95 + Math.random() * 5; } data.push({ date: date.toISOString().split('T')[0], uptime: uptime }); } return data; }; const generateChartData = (type, range) => { const points = range === '1h' ? 60 : range === '6h' ? 72 : range === '24h' ? 144 : 168; const interval = range === '1h' ? 1 : range === '6h' ? 5 : range === '24h' ? 10 : 60; const data = []; const now = new Date(); for (let i = points - 1; i >= 0; i--) { const time = new Date(now.getTime() - i * interval * 60 * 1000); if (type === 'responseTime') { const baseTime = 200; const variation = Math.sin(i / 10) * 50 + Math.random() * 100; data.push({ time: time.toISOString(), value: Math.max(50, baseTime + variation) }); } else if (type === 'volume') { const baseVolume = 1000; const variation = Math.sin(i / 20) * 300 + Math.random() * 200; data.push({ time: time.toISOString(), value: Math.max(100, baseVolume + variation) }); } } return data; }; // API Functions (with mock data fallback) const fetchStatusData = async (endpoint, mockData = null) => { try { const response = await fetch(endpoint, { method: 'GET', headers: { 'Accept': 'application/json', } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.warn(`API call failed for ${endpoint}, using mock data:`, error.message); return mockData; } }; const fetchAllStatusData = async () => { const mockData = generateMockData(); try { const [overall, services, metrics, incidents, maintenance, uptime] = await Promise.all([ fetchStatusData(STATUS_ENDPOINTS.overall, mockData.overall), fetchStatusData(STATUS_ENDPOINTS.services, mockData.services), fetchStatusData(STATUS_ENDPOINTS.metrics, mockData.metrics), fetchStatusData(STATUS_ENDPOINTS.incidents, mockData.incidents), fetchStatusData(STATUS_ENDPOINTS.maintenance, mockData.maintenance), fetchStatusData(STATUS_ENDPOINTS.uptime, mockData.uptime) ]); statusData = { overall, services, metrics, incidents, maintenance, uptime }; return statusData; } catch (error) { console.error('Failed to fetch status data:', error); statusData = mockData; return statusData; } }; // UI Update Functions const updateOverallStatus = (data) => { const { status, description } = data; elements.overallStatusIcon.textContent = getStatusIcon(status); elements.overallStatusText.textContent = status.charAt(0).toUpperCase() + status.slice(1); elements.overallStatusText.className = `status-label ${status}`; elements.overallStatusDescription.textContent = description; // Update metrics document.getElementById('uptimeMetric').textContent = `${data.uptime}%`; document.getElementById('responseTimeMetric').textContent = `${data.responseTime}ms`; document.getElementById('incidentsMetric').textContent = data.activeIncidents; document.getElementById('maintenanceMetric').textContent = data.scheduledMaintenance; // Update metric statuses const responseTimeStatus = data.responseTime < 300 ? 'operational' : data.responseTime < 500 ? 'degraded' : 'down'; document.getElementById('responseTimeStatus').textContent = responseTimeStatus === 'operational' ? 'Good' : responseTimeStatus === 'degraded' ? 'Slow' : 'Poor'; document.getElementById('responseTimeStatus').className = `metric-status ${responseTimeStatus}`; document.getElementById('incidentsStatus').textContent = data.activeIncidents === 0 ? 'None' : `${data.activeIncidents} Active`; document.getElementById('incidentsStatus').className = `metric-status ${data.activeIncidents === 0 ? 'operational' : 'down'}`; document.getElementById('maintenanceStatus').textContent = data.scheduledMaintenance === 0 ? 'None' : `${data.scheduledMaintenance} Scheduled`; document.getElementById('maintenanceStatus').className = `metric-status ${data.scheduledMaintenance === 0 ? 'operational' : 'maintenance'}`; }; const updateServicesList = (services) => { const servicesList = elements.servicesList; servicesList.innerHTML = ''; services.forEach(service => { const serviceItem = document.createElement('div'); serviceItem.className = 'service-item'; serviceItem.innerHTML = `
${service.icon}

${service.name}

${service.description}

${service.status.charAt(0).toUpperCase() + service.status.slice(1)}
${service.responseTime}ms
`; servicesList.appendChild(serviceItem); }); }; const updateIncidentsList = (incidents) => { const incidentsList = elements.incidentsList; const noIncidents = elements.noIncidents; if (incidents.length === 0) { incidentsList.style.display = 'none'; noIncidents.style.display = 'block'; return; } incidentsList.style.display = 'flex'; noIncidents.style.display = 'none'; incidentsList.innerHTML = ''; incidents.forEach(incident => { const incidentItem = document.createElement('div'); incidentItem.className = `incident-item ${incident.status}`; incidentItem.innerHTML = `
${incident.title}
${incident.status.charAt(0).toUpperCase() + incident.status.slice(1)}
${incident.description}
Started: ${formatDate(incident.startTime)} Severity: ${incident.severity.charAt(0).toUpperCase() + incident.severity.slice(1)}
`; incidentsList.appendChild(incidentItem); }); }; const updateMaintenanceList = (maintenance) => { const maintenanceList = elements.maintenanceList; const noMaintenance = elements.noMaintenance; if (maintenance.length === 0) { maintenanceList.style.display = 'none'; noMaintenance.style.display = 'block'; return; } maintenanceList.style.display = 'flex'; noMaintenance.style.display = 'none'; maintenanceList.innerHTML = ''; maintenance.forEach(item => { const maintenanceItem = document.createElement('div'); maintenanceItem.className = 'maintenance-item'; maintenanceItem.innerHTML = `
${item.title}
Scheduled
${item.description}
Start: ${formatDate(item.startTime)} End: ${formatDate(item.endTime)}
`; maintenanceList.appendChild(maintenanceItem); }); }; const updateUptimeCalendar = (uptimeData) => { const calendar = elements.uptimeCalendar; calendar.innerHTML = ''; // Group data by weeks const weeks = []; let currentWeek = []; uptimeData.forEach((day, index) => { const date = new Date(day.date); const dayOfWeek = date.getDay(); if (index === 0) { // Fill empty days at the beginning of the first week for (let i = 0; i < dayOfWeek; i++) { currentWeek.push(null); } } currentWeek.push(day); if (currentWeek.length === 7) { weeks.push(currentWeek); currentWeek = []; } }); // Add remaining days if (currentWeek.length > 0) { while (currentWeek.length < 7) { currentWeek.push(null); } weeks.push(currentWeek); } weeks.forEach(week => { const weekElement = document.createElement('div'); weekElement.className = 'uptime-week'; week.forEach(day => { const dayElement = document.createElement('div'); dayElement.className = 'uptime-day'; if (day) { const uptimeClass = day.uptime >= 100 ? 'uptime-100' : day.uptime >= 99 ? 'uptime-99' : day.uptime >= 95 ? 'uptime-95' : day.uptime >= 90 ? 'uptime-90' : 'uptime-down'; dayElement.classList.add(uptimeClass); dayElement.title = `${day.date}: ${day.uptime.toFixed(2)}% uptime`; } else { dayElement.style.visibility = 'hidden'; } weekElement.appendChild(dayElement); }); calendar.appendChild(weekElement); }); }; // Chart Functions const createChart = (canvasId, data, type) => { const canvas = document.getElementById(canvasId); if (!canvas) return null; const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; // Clear canvas ctx.clearRect(0, 0, width, height); if (data.length === 0) return null; // Calculate bounds const values = data.map(d => d.value); const minValue = Math.min(...values); const maxValue = Math.max(...values); const range = maxValue - minValue || 1; // Draw grid ctx.strokeStyle = '#e2e8f0'; ctx.lineWidth = 1; // Horizontal grid lines for (let i = 0; i <= 5; i++) { const y = (height - 40) * i / 5 + 20; ctx.beginPath(); ctx.moveTo(40, y); ctx.lineTo(width - 20, y); ctx.stroke(); } // Draw line ctx.strokeStyle = type === 'responseTime' ? '#6366f1' : '#10b981'; ctx.lineWidth = 2; ctx.beginPath(); data.forEach((point, index) => { const x = 40 + (width - 60) * index / (data.length - 1); const y = height - 20 - ((point.value - minValue) / range) * (height - 40); if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); // Draw points ctx.fillStyle = type === 'responseTime' ? '#6366f1' : '#10b981'; data.forEach((point, index) => { const x = 40 + (width - 60) * index / (data.length - 1); const y = height - 20 - ((point.value - minValue) / range) * (height - 40); ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI); ctx.fill(); }); // Draw labels ctx.fillStyle = '#64748b'; ctx.font = '12px Inter'; ctx.textAlign = 'right'; // Y-axis labels for (let i = 0; i <= 5; i++) { const value = minValue + (range * (5 - i) / 5); const y = (height - 40) * i / 5 + 25; const label = type === 'responseTime' ? `${Math.round(value)}ms` : `${Math.round(value)}`; ctx.fillText(label, 35, y); } return { canvas, ctx, data }; }; const updateCharts = () => { const responseTimeRange = document.getElementById('responseTimeRange').value; const volumeRange = document.getElementById('volumeRange').value; const responseTimeData = generateChartData('responseTime', responseTimeRange); const volumeData = generateChartData('volume', volumeRange); charts.responseTime = createChart('responseTimeChart', responseTimeData, 'responseTime'); charts.volume = createChart('volumeChart', volumeData, 'volume'); }; // Event Listeners const setupEventListeners = () => { // Refresh button elements.refreshBtn.addEventListener('click', async () => { elements.refreshBtn.classList.add('loading'); await refreshStatusData(); elements.refreshBtn.classList.remove('loading'); }); // Chart range selectors document.getElementById('responseTimeRange').addEventListener('change', updateCharts); document.getElementById('volumeRange').addEventListener('change', updateCharts); // Subscribe form elements.subscribeForm.addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('subscribeEmail').value; try { await fetch(STATUS_ENDPOINTS.subscribe, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }) }); showToast('Successfully subscribed to status updates!', 'success'); elements.subscribeForm.reset(); } catch (error) { console.warn('Subscription failed, showing success anyway:', error); showToast('Successfully subscribed to status updates!', 'success'); elements.subscribeForm.reset(); } }); // Auto-refresh toggle let autoRefresh = true; document.addEventListener('visibilitychange', () => { if (document.hidden) { if (updateInterval) { clearInterval(updateInterval); updateInterval = null; } } else if (autoRefresh) { startAutoRefresh(); } }); }; // Main Functions const refreshStatusData = async () => { try { const data = await fetchAllStatusData(); updateOverallStatus(data.overall); updateServicesList(data.services); updateIncidentsList(data.incidents); updateMaintenanceList(data.maintenance); updateUptimeCalendar(data.uptime); updateCharts(); elements.lastUpdated.textContent = formatDate(new Date()); } catch (error) { console.error('Failed to refresh status data:', error); showToast('Failed to refresh status data', 'error'); } }; const startAutoRefresh = () => { if (updateInterval) { clearInterval(updateInterval); } updateInterval = setInterval(refreshStatusData, 30000); // Refresh every 30 seconds }; const init = async () => { setupEventListeners(); await refreshStatusData(); startAutoRefresh(); // Initial chart setup setTimeout(updateCharts, 100); }; // Initialize when DOM is loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Navbar background on scroll window.addEventListener('scroll', () => { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.style.background = 'rgba(255, 255, 255, 0.98)'; } else { navbar.style.background = 'rgba(255, 255, 255, 0.95)'; } }); // Button click handlers document.querySelectorAll('.btn').forEach(btn => { btn.addEventListener('click', (e) => { const buttonText = btn.textContent.toLowerCase(); if (buttonText.includes('trial') || buttonText.includes('start')) { if (!btn.closest('form')) { e.preventDefault(); showToast('Starting your free trial! Redirecting...', 'success'); setTimeout(() => { window.location.href = 'signup.html'; }, 2000); } } }); }); // Export for potential external use window.TaskFlowStatus = { refreshStatusData, statusData, charts };