1import React from "react";
2import { apiFetch } from "../../lib/api";
3import MeshCentralEmbed, { VIEWMODE } from "../../components/MeshCentralEmbed";
5export default function TabEvents({ agentId, agentUuid }) {
6 const [events, setEvents] = React.useState([]);
7 const [loading, setLoading] = React.useState(true);
8 const [error, setError] = React.useState(null);
9 const [filter, setFilter] = React.useState('all'); // all, error, warning, information
10 const [useMeshCentral, setUseMeshCentral] = React.useState(true);
11 const [meshInfo, setMeshInfo] = React.useState(null);
12 const [meshLoading, setMeshLoading] = React.useState(false);
13 const [meshError, setMeshError] = React.useState(null);
15 // Check if MeshCentral is available
16 React.useEffect(() => {
17 if (!agentId && !agentUuid) return;
20 apiFetch(`/agent/${agentUuid || agentId}/meshcentral-url`)
21 .then((res) => res.ok ? res.json() : null)
23 if (data?.meshcentralNodeId) {
25 setUseMeshCentral(true);
27 setUseMeshCentral(false);
30 .catch(() => setUseMeshCentral(false))
31 .finally(() => setMeshLoading(false));
32 }, [agentId, agentUuid]);
34 React.useEffect(() => {
35 if (!agentId || useMeshCentral) return;
39 apiFetch(`/agent/${agentId}/events`)
41 setEvents(Array.isArray(data) ? data : []);
44 setError(err.message || "Failed to load event logs");
47 .finally(() => setLoading(false));
48 }, [agentId, useMeshCentral]);
52 <div className="tab-container">
54 <div className="card">Checking MeshCentral availability...</div>
59 // Render MeshCentral Event Viewer if available
60 if (useMeshCentral && meshInfo?.meshcentralNodeId) {
62 <div className="tab-container" style={{ height: 'calc(100vh - 200px)', display: 'flex', flexDirection: 'column' }}>
63 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
66 className="btn btn-secondary"
67 onClick={() => setUseMeshCentral(false)}
68 style={{ fontSize: '0.9em' }}
73 <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
75 agentId={agentId || agentUuid}
76 nodeId={meshInfo.meshcentralNodeId}
77 viewMode={VIEWMODE.EVENTS}
87 <div className="tab-container">
89 <div className="card">Loading event logs...</div>
96 <div className="tab-container">
98 <div className="card error">{error}</div>
103 // Filter events based on level
104 const filteredEvents = filter === 'all'
106 : events.filter(e => e.level?.toLowerCase() === filter.toLowerCase());
108 // Get event level color
109 const getLevelColor = (level) => {
110 const l = level?.toLowerCase();
111 if (l === 'error' || l === 'critical') return '#dc3545';
112 if (l === 'warning') return '#ffc107';
113 if (l === 'information' || l === 'info') return '#17a2b8';
117 // Get event level icon
118 const getLevelIcon = (level) => {
119 const l = level?.toLowerCase();
120 if (l === 'error' || l === 'critical') return 'error';
121 if (l === 'warning') return 'warning';
122 if (l === 'information' || l === 'info') return 'info';
123 return 'description';
127 <div className="tab-container">
128 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
129 <h2 style={{ margin: 0 }}>Event Viewer</h2>
130 {meshInfo?.available && (
133 onClick={() => setUseMeshCentral(!useMeshCentral)}
134 style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
136 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>
137 {useMeshCentral ? 'list' : 'desktop_windows'}
139 {useMeshCentral ? 'Switch to Legacy View' : 'Switch to MeshCentral'}
144 {/* MeshCentral Event Viewer */}
145 {useMeshCentral && meshInfo?.available && (
146 <div className="card" style={{ padding: '16px', marginBottom: '16px' }}>
148 agentUuid={agent?.uuid}
149 viewMode={VIEWMODE.EVENTS}
150 title="Event Viewer (MeshCentral)"
155 {/* Legacy Event Viewer */}
156 {(!useMeshCentral || !meshInfo?.available) && (
159 <div className="card" style={{ marginBottom: '16px', padding: '12px', display: 'flex', gap: '8px', alignItems: 'center' }}>
160 <span style={{ fontSize: '14px', fontWeight: '500', marginRight: '8px' }}>Filter:</span>
162 className={`btn-sm ${filter === 'all' ? 'btn-primary' : ''}`}
163 onClick={() => setFilter('all')}
165 All ({events.length})
168 className={`btn-sm ${filter === 'error' ? 'btn-primary' : ''}`}
169 onClick={() => setFilter('error')}
170 style={{ color: filter !== 'error' ? '#dc3545' : undefined }}
172 Errors ({events.filter(e => e.level?.toLowerCase() === 'error' || e.level?.toLowerCase() === 'critical').length})
175 className={`btn-sm ${filter === 'warning' ? 'btn-primary' : ''}`}
176 onClick={() => setFilter('warning')}
177 style={{ color: filter !== 'warning' ? '#ffc107' : undefined }}
179 Warnings ({events.filter(e => e.level?.toLowerCase() === 'warning').length})
182 className={`btn-sm ${filter === 'information' ? 'btn-primary' : ''}`}
183 onClick={() => setFilter('information')}
184 style={{ color: filter !== 'information' ? '#17a2b8' : undefined }}
186 Information ({events.filter(e => e.level?.toLowerCase() === 'information' || e.level?.toLowerCase() === 'info').length})
191 <div className="card">
192 {filteredEvents.length === 0 ? (
193 <div style={{ padding: '40px 20px', textAlign: 'center', color: '#888' }}>
194 <span className="material-symbols-outlined" style={{ fontSize: '48px', opacity: 0.3 }}>
197 <p style={{ marginTop: '16px', fontSize: '16px' }}>
198 {filter === 'all' ? 'No event logs available yet' : `No ${filter} events found`}
200 <p style={{ fontSize: '14px', opacity: 0.7 }}>
201 Event logs will be collected from Windows Event Viewer during the next agent update
205 <div className="log-box" style={{ maxHeight: '700px', overflow: 'auto' }}>
206 {filteredEvents.map((e, i) => (
209 className="log-entry"
211 padding: '12px 16px',
212 borderLeft: `4px solid ${getLevelColor(e.level)}`,
215 background: '#f9f9f9'
218 <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
220 className="material-symbols-outlined"
223 color: getLevelColor(e.level)
226 {getLevelIcon(e.level)}
228 <strong style={{ color: getLevelColor(e.level) }}>
229 [{e.level?.toUpperCase() || 'INFO'}]
231 <span style={{ color: '#888', fontSize: '13px', marginLeft: 'auto' }}>
232 {e.timestamp ? new Date(e.timestamp).toLocaleString() : ''}
236 <div style={{ fontSize: '14px', color: '#333', marginLeft: '28px', marginBottom: '4px' }}>
237 {e.message || 'No message'}
241 <div style={{ fontSize: '12px', color: '#888', marginLeft: '28px' }}>
247 <div style={{ fontSize: '12px', color: '#888', marginLeft: '28px' }}>
248 Event ID: {e.event_id}