summaryrefslogtreecommitdiff
path: root/static/status-script.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/status-script.js')
-rw-r--r--static/status-script.js667
1 files changed, 667 insertions, 0 deletions
diff --git a/static/status-script.js b/static/status-script.js
new file mode 100644
index 0000000..0ba2fce
--- /dev/null
+++ b/static/status-script.js
@@ -0,0 +1,667 @@
+// 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 = `
+ <div class="service-info">
+ <div class="service-icon">${service.icon}</div>
+ <div class="service-details">
+ <h3>${service.name}</h3>
+ <p>${service.description}</p>
+ </div>
+ </div>
+ <div class="service-status">
+ <div class="status-dot ${service.status}"></div>
+ <div class="status-text ${service.status}">${service.status.charAt(0).toUpperCase() + service.status.slice(1)}</div>
+ <div class="response-time">${service.responseTime}ms</div>
+ </div>
+ `;
+ 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 = `
+ <div class="incident-header">
+ <div>
+ <div class="incident-title">${incident.title}</div>
+ <div class="incident-status ${incident.status}">${incident.status.charAt(0).toUpperCase() + incident.status.slice(1)}</div>
+ </div>
+ </div>
+ <div class="incident-description">${incident.description}</div>
+ <div class="incident-meta">
+ <span>Started: ${formatDate(incident.startTime)}</span>
+ <span>Severity: ${incident.severity.charAt(0).toUpperCase() + incident.severity.slice(1)}</span>
+ </div>
+ `;
+ 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 = `
+ <div class="maintenance-header">
+ <div>
+ <div class="maintenance-title">${item.title}</div>
+ <div class="maintenance-status">Scheduled</div>
+ </div>
+ </div>
+ <div class="maintenance-description">${item.description}</div>
+ <div class="maintenance-meta">
+ <span>Start: ${formatDate(item.startTime)}</span>
+ <span>End: ${formatDate(item.endTime)}</span>
+ </div>
+ `;
+ 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
+};