import React, { useState, useEffect, useRef } from 'react'; import { RefreshCw, Trash2, CheckCircle, XCircle, Clock, Filter, ChevronDown, ChevronRight, Activity } from 'lucide-react'; const PER_PAGE = 25; const ActivityLogsApp = () => { const [logs, setLogs] = useState([]); const [summary, setSummary] = useState({ total: 0, success: 0, failed: 0, last_run: null }); const [workflows, setWorkflows] = useState([]); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState({ status: '', workflow_id: '' }); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [total, setTotal] = useState(0); const [expandedRow, setExpandedRow] = useState(null); const [autoRefresh, setAutoRefresh] = useState(false); const pageRef = useRef(page); const filtersRef = useRef(filters); const intervalRef = useRef(null); useEffect(() => { pageRef.current = page; }, [page]); useEffect(() => { filtersRef.current = filters; }, [filters]); const apiUrl = () => window.wpbotAutomator.apiUrl.replace(/\/$/, ''); const headers = () => ({ 'X-WP-Nonce': window.wpbotAutomator.nonce }); const fetchSummary = async () => { try { const res = await fetch(`${apiUrl()}/logs/summary`, { headers: headers() }); const data = await res.json(); setSummary(data); } catch (e) { console.error(e); } }; const fetchWorkflows = async () => { try { const res = await fetch(`${apiUrl()}/workflows`, { headers: headers() }); const data = await res.json(); setWorkflows(Array.isArray(data) ? data : []); } catch (e) { console.error(e); } }; const fetchLogs = async (currentPage, currentFilters) => { setLoading(true); try { const params = new URLSearchParams({ per_page: PER_PAGE, page: currentPage }); if (currentFilters.status) params.set('status', currentFilters.status); if (currentFilters.workflow_id) params.set('workflow_id', currentFilters.workflow_id); const res = await fetch(`${apiUrl()}/logs?${params}`, { headers: headers() }); const data = await res.json(); setLogs(data.logs || []); setTotal(data.total || 0); setTotalPages(data.total_pages || 1); } catch (e) { console.error(e); } finally { setLoading(false); } }; useEffect(() => { fetchWorkflows(); fetchSummary(); fetchLogs(1, { status: '', workflow_id: '' }); }, []); useEffect(() => { clearInterval(intervalRef.current); if (!autoRefresh) return; intervalRef.current = setInterval(() => { fetchSummary(); fetchLogs(pageRef.current, filtersRef.current); }, 10000); return () => clearInterval(intervalRef.current); }, [autoRefresh]); const applyFilter = (key, value) => { const next = { ...filtersRef.current, [key]: value }; setFilters(next); setPage(1); fetchSummary(); fetchLogs(1, next); }; const clearFilters = () => { const next = { status: '', workflow_id: '' }; setFilters(next); setPage(1); fetchLogs(1, next); }; const goToPage = (p) => { setPage(p); fetchLogs(p, filtersRef.current); }; const handleRefresh = () => { fetchSummary(); fetchLogs(pageRef.current, filtersRef.current); }; const handleClearLogs = async () => { if (!window.confirm('Clear all logs? This cannot be undone.')) return; await fetch(`${apiUrl()}/logs`, { method: 'DELETE', headers: headers() }); setSummary({ total: 0, success: 0, failed: 0, last_run: null }); setLogs([]); setTotal(0); setTotalPages(1); setPage(1); }; const formatRelative = (datetime) => { if (!datetime) return '-'; const d = new Date(datetime.replace(' ', 'T') + 'Z'); const diff = Math.floor((Date.now() - d) / 1000); if (diff < 60) return `${diff}s ago`; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; return d.toLocaleDateString(); }; const formatAbsolute = (datetime) => { if (!datetime) return ''; return new Date(datetime.replace(' ', 'T') + 'Z').toLocaleString(); }; const humanize = (str) => { if (!str) return '-'; return str.replace(/[_-]/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); }; const formatTrigger = (trigger) => { if (!trigger) return '-'; const parts = trigger.split(':'); return humanize(parts[parts.length - 1]); }; const parseLogData = (raw) => { if (!raw) return null; try { return JSON.parse(raw); } catch { return raw; } }; const successRate = summary.total > 0 ? Math.round((summary.success / summary.total) * 100) : null; const hasFilters = filters.status || filters.workflow_id; return (
{/* Header */}

Activity Log

Real-time view of every workflow execution — what ran, what succeeded, and what failed.

{/* Auto-refresh toggle */}
{/* Stats */}
Total Runs
{summary.total.toLocaleString()}
{summary.last_run ? `Last run: ${formatRelative(summary.last_run)}` : 'No runs yet'}
Successful
{summary.success.toLocaleString()}
{successRate !== null ? `${successRate}% success rate` : 'No data yet'}
Failed
{summary.failed.toLocaleString()}
{successRate !== null ? `${100 - successRate}% failure rate` : 'No data yet'}
{/* Filters */}
{hasFilters && ( )} {total.toLocaleString()} {total === 1 ? 'entry' : 'entries'}
{/* Table */}
{loading && logs.length === 0 ? (
Loading logs…
) : logs.length === 0 ? (

No logs found{hasFilters ? ' for this filter' : ''}

{hasFilters ? 'Try clearing the filters.' : 'Logs appear here automatically when your workflows run.'}

) : ( <> {logs.map((log) => { const expanded = expandedRow === log.id; const parsed = parseLogData(log.log_data); const isFailed = log.status !== 'success'; return ( setExpandedRow(expanded ? null : log.id)} className={`cursor-pointer transition-colors hover:bg-muted/20 ${expanded ? 'bg-muted/10' : ''} ${isFailed ? 'border-l-2 border-l-rose-400' : ''}`} > {expanded && ( )} ); })}
Time Workflow Trigger Action Status Message
{expanded ? : } {formatRelative(log.created_at)} {log.workflow_name || `Workflow #${log.workflow_id}`} {formatTrigger(log.trigger_type)} {log.action_id ? ( {humanize(log.action_id)} ) : ( — )} {isFailed ? ( Failed ) : ( Success )} {log.error_message || (parsed && parsed.message) || '—'}
{/* Meta row */}
Log ID: {log.id} Time: {formatAbsolute(log.created_at)} Workflow: {log.workflow_name || `#${log.workflow_id}`} Trigger: {log.trigger_type || '—'} {log.action_id && Action: {log.action_id}}
{/* Error box */} {log.error_message && (
Error
{log.error_message}
)} {/* Result data */} {parsed && (
Result Data
                                      {typeof parsed === 'object'
                                        ? JSON.stringify(parsed, null, 2)
                                        : parsed}
                                    
)}
{/* Pagination */} {totalPages > 1 && (
Page {page} of {totalPages} — {total.toLocaleString()} total entries
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const start = Math.max(1, Math.min(page - 2, totalPages - 4)); const p = start + i; return p <= totalPages ? ( ) : null; })}
)} )}
); }; export default ActivityLogsApp;