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 (
Real-time view of every workflow execution — what ran, what succeeded, and what failed.
No logs found{hasFilters ? ' for this filter' : ''}
{hasFilters ? 'Try clearing the filters.' : 'Logs appear here automatically when your workflows run.'}
| 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 ? (
|
{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
{typeof parsed === 'object'
? JSON.stringify(parsed, null, 2)
: parsed}
|
||||||