import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { RefreshCw, AlertCircle, Settings, Activity } from 'lucide-react'; import { WorkflowRun } from '../types/github'; import { ApiService } from '../services/api'; import { CompactWorkflowCard } from './CompactWorkflowCard'; import { WorkflowRunsModal } from './WorkflowRunsModal'; import { ThemeSwitcher } from './ThemeSwitcher'; import { SettingsModal } from './SettingsModal'; import { ApiStatusModal } from './ApiStatusModal'; import { useSettings } from '../contexts/SettingsContext'; import { NotificationService } from '../services/notifications'; interface DashboardProps {} export const Dashboard: React.FC = () => { const [workflowRuns, setWorkflowRuns] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [lastUpdated, setLastUpdated] = useState(null); const [repositoryCount, setRepositoryCount] = useState(0); const [selectedRepository, setSelectedRepository] = useState<{owner: string, name: string} | null>(null); const [isModalOpen, setIsModalOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isApiStatusOpen, setIsApiStatusOpen] = useState(false); const { settings } = useSettings(); const previousWorkflowRuns = useRef([]); const notificationService = useMemo(() => NotificationService.getInstance(), []); const apiService = useMemo(() => new ApiService(), []); const fetchWorkflowRuns = useCallback(async (isAutoRefresh = false) => { setLoading(true); setError(null); try { const runs = await apiService.getWorkflowRuns(); // Check for notifications if this is an auto-refresh and notifications are enabled if (isAutoRefresh && settings.notifications.enabled && previousWorkflowRuns.current.length > 0) { const previousRuns = previousWorkflowRuns.current; const currentRuns = runs; // Find new failures if (settings.notifications.showFailures) { const newFailures = currentRuns.filter(currentRun => currentRun.conclusion === 'failure' && !previousRuns.some(prevRun => prevRun.id === currentRun.id && prevRun.conclusion === 'failure' ) ); if (newFailures.length > 0) { notificationService.showFailureNotification(newFailures); } } // Find new recoveries (previously failed runs that are now successful) if (settings.notifications.showRecoveries) { const newRecoveries = currentRuns.filter(currentRun => currentRun.conclusion === 'success' && previousRuns.some(prevRun => prevRun.repository.full_name === currentRun.repository.full_name && prevRun.conclusion === 'failure' ) && !previousRuns.some(prevRun => prevRun.id === currentRun.id && prevRun.conclusion === 'success' ) ); if (newRecoveries.length > 0) { notificationService.showSuccessNotification(newRecoveries); } } // Find new waiting runs if (settings.notifications.showWaiting) { const newWaitingRuns = currentRuns.filter(currentRun => currentRun.status === 'waiting' && !previousRuns.some(prevRun => prevRun.id === currentRun.id && prevRun.status === 'waiting' ) ); if (newWaitingRuns.length > 0) { notificationService.showWaitingNotification(newWaitingRuns); } } } previousWorkflowRuns.current = runs; setWorkflowRuns(runs); setLastUpdated(new Date()); } catch (error) { console.error('Error fetching workflow runs:', error); setError('Failed to fetch workflow runs. Please check your configuration.'); } finally { setLoading(false); } }, [apiService, settings.notifications, notificationService]); const fetchConfig = useCallback(async () => { try { const config = await apiService.getConfig(); setRepositoryCount(config.repositories.length); } catch (error) { console.error('Error fetching config:', error); } }, [apiService]); useEffect(() => { fetchConfig(); fetchWorkflowRuns(); }, [fetchConfig, fetchWorkflowRuns]); // Request notification permission when notifications are enabled useEffect(() => { if (settings.notifications.enabled) { notificationService.requestPermission(); } }, [settings.notifications.enabled, notificationService]); // Auto-refresh functionality useEffect(() => { if (!settings.autoRefreshInterval) return; const interval = setInterval(() => { fetchWorkflowRuns(true); }, settings.autoRefreshInterval * 1000); return () => clearInterval(interval); }, [settings.autoRefreshInterval, fetchWorkflowRuns]); // Sort workflow runs by created_at date (newest first) const sortedWorkflowRuns = useMemo(() => { return [...workflowRuns].sort((a, b) => { return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); }); }, [workflowRuns]); const statusCounts = { total: workflowRuns.length, success: workflowRuns.filter(r => r.conclusion === 'success').length, failure: workflowRuns.filter(r => r.conclusion === 'failure').length, active: workflowRuns.filter(r => r.status === 'in_progress' || r.status === 'queued' ).length, waiting: workflowRuns.filter(r => r.status === 'queued').length, }; return (

GitHub Actions Radar

Latest main branch runs

{settings.showLastUpdateTime && lastUpdated && (

Last updated: {lastUpdated.toLocaleTimeString()}

)} {settings.githubUsername && (

Welcome, {settings.githubUsername}!

)}
{repositoryCount} repositories
{statusCounts.total}
Total Runs
{statusCounts.success}
Successful
{statusCounts.failure}
Failed
{statusCounts.active}
Active
{statusCounts.waiting}
Waiting
{error && (

{error}

)}
{loading ? (
) : sortedWorkflowRuns.length === 0 ? (

No workflow runs found.

Check your config.json file

) : ( sortedWorkflowRuns.map(run => ( { setSelectedRepository({ owner: run.repository.owner.login, name: run.repository.name }); setIsModalOpen(true); }} /> )) )}
{ setIsModalOpen(false); setSelectedRepository(null); }} repository={selectedRepository} /> setIsSettingsOpen(false)} /> setIsApiStatusOpen(false)} />
); };