1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-D0Bmh-UX.js"; 3import { cf as Activity, cL as RefreshCw, b5 as Trash2, a$ as Shield, ch as Database, dX as Server, bb as User, eu as Terminal, aY as TriangleAlert, g6 as ChartNoAxesColumn, de as Funnel, bf as Search, dM as Calendar, bL as Clock, ba as Info } from "./vendor-dVNaLJx_.js"; 4import "./vendor-i18n-R-bMoYp_.js"; 5const LOG_CATEGORIES = [ 6 { id: "ALL", label: "All Logs", icon: Activity }, 7 { id: "admin", label: "Admin Actions", icon: Shield }, 8 { id: "database", label: "Database Actions", icon: Database }, 9 { id: "system", label: "System Events", icon: Server }, 10 { id: "user", label: "User Actions", icon: User }, 11 { id: "worker", label: "Worker Logs", icon: Terminal }, 12 { id: "error", label: "Errors & Crashes", icon: TriangleAlert }, 13 { id: "analytics", label: "Analytics", icon: ChartNoAxesColumn } 14]; 15const UnifiedLogs = () => { 16 const [logs, setLogs] = reactExports.useState([]); 17 const [total, setTotal] = reactExports.useState(0); 18 const [loading, setLoading] = reactExports.useState(false); 19 const [activeTab, setActiveTab] = reactExports.useState("ALL"); 20 const [search, setSearch] = reactExports.useState(""); 21 const [page, setPage] = reactExports.useState(1); 22 const [dateRange, setDateRange] = reactExports.useState({ start: "", end: "" }); 23 const [isClearing, setIsClearing] = reactExports.useState(false); 24 const [expandedLogs, setExpandedLogs] = reactExports.useState(/* @__PURE__ */ new Set()); 25 const [groupWorkerLogs, setGroupWorkerLogs] = reactExports.useState(true); 26 const [clearOption, setClearOption] = reactExports.useState("all"); 27 const toggleLog = (logId) => { 28 setExpandedLogs((prev) => { 29 const next = new Set(prev); 30 if (next.has(logId)) next.delete(logId); 31 else next.add(logId); 32 return next; 33 }); 34 }; 35 reactExports.useEffect(() => { 36 fetchLogs(); 37 }, [page, activeTab, search, dateRange]); 38 const fetchLogs = async () => { 39 setLoading(true); 40 try { 41 const res = await api.getActivityLogs({ 42 page, 43 pageSize: 100, 44 category: activeTab, 45 search, 46 startDate: dateRange.start, 47 endDate: dateRange.end 48 }); 49 setLogs(res.data); 50 setTotal(res.total); 51 } catch (e) { 52 console.error("Failed to fetch logs", e); 53 } finally { 54 setLoading(false); 55 } 56 }; 57 const handleClearLogs = async () => {
58 let confirmMsg = `Are you sure you want to clear ${activeTab === "ALL" ? "ALL" : activeTab} logs`; 59 if (clearOption === "all") confirmMsg += "? This cannot be undone."; 60 else if (clearOption === "24h") confirmMsg += " older than 24 hours?"; 61 else if (clearOption === "3d") confirmMsg += " older than 3 days?"; 62 else if (clearOption === "7d") confirmMsg += " older than 7 days?"; 63 else if (clearOption === "30d") confirmMsg += " older than 30 days?"; 64 if (!confirm(confirmMsg)) return; 65 setIsClearing(true); 66 try { 67 let options = { category: activeTab }; 68 if (clearOption === "30d") { 69 options.clearLegacy = true; 70 } else if (clearOption !== "all") { 71 const now = /* @__PURE__ */ new Date(); 72 let days = 0; 73 if (clearOption === "24h") days = 1; 74 else if (clearOption === "3d") days = 3; 75 else if (clearOption === "7d") days = 7; 76 const beforeDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3); 77 options.beforeDate = beforeDate.toISOString(); 78 } 79 await api.clearActivityLogs(options); 80 fetchLogs(); 81 alert("Logs cleared successfully."); 82 } catch (e) { 83 alert("Failed to clear logs."); 84 } finally { 85 setIsClearing(false); 86 } 87 }; 88 const getSeverityColor = (severity) => { 89 switch (severity) { 90 case "danger": 91 return "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 border-red-200 dark:border-red-800"; 92 case "warning": 93 return "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400 border-orange-200 dark:border-orange-800"; 94 default: 95 return "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700"; 96 } 97 }; 98 const [expandedGroups, setExpandedGroups] = reactExports.useState(/* @__PURE__ */ new Set()); 99 const toggleGroup = (groupId) => { 100 setExpandedGroups((prev) => { 101 const next = new Set(prev); 102 if (next.has(groupId)) { 103 next.delete(groupId); 104 } else { 105 next.add(groupId); 106 } 107 return next; 108 }); 109 }; 110 const processLogsForGrouping = (logs2) => {
111 let currentGroupId = null; 112 let groupColorIndex = 0; 113 const colors = [ 114 "border-l-blue-500 bg-blue-50/50 dark:bg-blue-900/20", 115 "border-l-purple-500 bg-purple-50/50 dark:bg-purple-900/20", 116 "border-l-emerald-500 bg-emerald-50/50 dark:bg-emerald-900/20", 117 "border-l-amber-500 bg-amber-50/50 dark:bg-amber-900/20", 118 "border-l-pink-500 bg-pink-50/50 dark:bg-pink-900/20" 119 ]; 120 const processed = [...logs2].reverse().map((log) => { 121 let groupId = currentGroupId; 122 let groupColor = currentGroupId ? colors[groupColorIndex] : ""; 123 if (log.category === "worker" && (log.action === "WORKER_LOG" || log.action.startsWith("worker/"))) { 124 const details = log.details || ""; 125 const startMatch = details.match(/\[\*\] Processing MIDI (\d+)/); 126 const endMatch = details.match(/\[\+\] Success (\d+)/) || details.match(/\[-\] Failed (\d+)/); 127 if (startMatch) { 128 groupColorIndex = (groupColorIndex + 1) % colors.length; 129 currentGroupId = `midi-${startMatch[1]}-${log.id}`; 130 groupId = currentGroupId; 131 groupColor = colors[groupColorIndex]; 132 } else if (endMatch) { 133 if (!currentGroupId) { 134 groupColorIndex = (groupColorIndex + 1) % colors.length; 135 currentGroupId = `midi-${endMatch[1]}-${log.id}`; 136 } 137 groupId = currentGroupId; 138 groupColor = colors[groupColorIndex]; 139 currentGroupId = null; 140 } else if (details.startsWith(" ")) { 141 if (!currentGroupId) { 142 groupColorIndex = (groupColorIndex + 1) % colors.length; 143 currentGroupId = `unknown-group-${log.id}`; 144 } 145 groupId = currentGroupId; 146 groupColor = colors[groupColorIndex]; 147 } else { 148 currentGroupId = null; 149 groupId = null; 150 groupColor = ""; 151 } 152 } else { 153 currentGroupId = null; 154 groupId = null; 155 groupColor = ""; 156 } 157 return { 158 ...log, 159 groupId, 160 groupColorClass: groupColor 161 }; 162 }).reverse(); 163 const grouped = []; 164 let currentGroup = []; 165 let lastGroupId = null; 166 for (const log of processed) { 167 if (log.groupId) { 168 if (log.groupId === lastGroupId) { 169 currentGroup.push(log); 170 } else { 171 if (currentGroup.length > 0) { 172 grouped.push(currentGroup); 173 } 174 currentGroup = [log]; 175 lastGroupId = log.groupId; 176 } 177 } else { 178 if (currentGroup.length > 0) { 179 grouped.push(currentGroup); 180 currentGroup = []; 181 lastGroupId = null; 182 } 183 grouped.push([log]); 184 } 185 } 186 if (currentGroup.length > 0) { 187 grouped.push(currentGroup); 188 } 189 const finalLogs = []; 190 for (const group of grouped) { 191 if (group[0].groupId && group.length > 1) { 192 group.reverse(); 193 group[0].isGroupStart = true; 194 group[0].groupCount = group.length; 195 for (let i = 1; i < group.length; i++) { 196 group[i].isGroupStart = false;
197 } 198 } else if (group[0].groupId) { 199 group[0].isGroupStart = true; 200 group[0].groupCount = 1; 201 } 202 finalLogs.push(...group); 203 } 204 return finalLogs; 205 }; 206 const augmentedLogs = processLogsForGrouping(logs); 207 const visibleLogs = augmentedLogs.filter((log) => { 208 if (!groupWorkerLogs) return true; 209 if (!log.groupId) return true; 210 if (log.isGroupStart) return true; 211 return expandedGroups.has(log.groupId); 212 }); 213 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full space-y-4", children: [ 214 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center bg-white dark:bg-zinc-900 p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-sm shrink-0", children: [ 215 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 216 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [ 217 /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { className: "text-indigo-500" }), 218 " Unified System Logs" 219 ] }),
220 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Monitor system activity, errors, and user actions." }) 221 ] }), 222 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 223 /* @__PURE__ */ jsxRuntimeExports.jsx( 224 "button", 225 { 226 onClick: () => fetchLogs(), 227 className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg text-zinc-500 transition-colors", 228 title: "Refresh", 229 children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 18, className: loading ? "animate-spin" : "" }) 230 } 231 ), 232 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center", children: [ 233 /* @__PURE__ */ jsxRuntimeExports.jsxs( 234 "select", 235 { 236 value: clearOption, 237 onChange: (e) => setClearOption(e.target.value), 238 className: "px-3 py-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg text-sm text-zinc-800 dark:text-zinc-200 outline-none focus:ring-2 focus:ring-red-500/50 cursor-pointer", 239 children: [
240 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "all", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-800", children: "All Time" }), 241 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "24h", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-800", children: "Older than 24 Hours" }), 242 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "3d", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-800", children: "Older than 3 Days" }), 243 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "7d", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-800", children: "Older than 7 Days" }), 244 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "30d", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-800", children: "Older than 30 Days (Legacy)" }) 245 ] 246 } 247 ), 248 /* @__PURE__ */ jsxRuntimeExports.jsxs( 249 "button", 250 { 251 onClick: handleClearLogs, 252 disabled: isClearing, 253 className: "flex items-center gap-2 px-4 py-2 bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/40 text-red-600 dark:text-red-400 rounded-lg text-sm font-bold border border-red-200 dark:border-red-800 transition-colors disabled:opacity-50", 254 children: [
255 /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 16 }), 256 isClearing ? "Clearing..." : "Clear Logs" 257 ] 258 } 259 ) 260 ] }) 261 ] }) 262 ] }), 263 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col md:flex-row gap-4", children: [ 264 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex bg-white dark:bg-zinc-900 p-1 rounded-lg border border-zinc-200 dark:border-zinc-800 overflow-x-auto custom-scrollbar", children: LOG_CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 265 "button", 266 { 267 onClick: () => { 268 setActiveTab(cat.id); 269 setPage(1); 270 }, 271 className: `flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium whitespace-nowrap transition-all ${activeTab === cat.id ? "bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 shadow-sm" : "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-zinc-800"}`, 272 children: [
273 /* @__PURE__ */ jsxRuntimeExports.jsx(cat.icon, { size: 14 }), 274 cat.label 275 ] 276 }, 277 cat.id 278 )) }), 279 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-2", children: [ 280 /* @__PURE__ */ jsxRuntimeExports.jsxs( 281 "button", 282 { 283 onClick: () => setGroupWorkerLogs((prev) => !prev), 284 className: `h-10 flex items-center gap-1.5 px-3 rounded-lg text-xs font-bold border transition-colors whitespace-nowrap ${groupWorkerLogs ? "bg-indigo-50 border-indigo-200 text-indigo-600 dark:bg-indigo-900/30 dark:border-indigo-800 dark:text-indigo-400" : "bg-white border-zinc-200 text-zinc-500 dark:bg-zinc-900 dark:border-zinc-800 dark:text-zinc-400"}`, 285 title: "Toggle collapsing of repetitive worker step logs", 286 children: [ 287 /* @__PURE__ */ jsxRuntimeExports.jsx(Funnel, { size: 14 }), 288 groupWorkerLogs ? "Group Worker Steps" : "Raw Logs" 289 ] 290 } 291 ),
292 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative min-w-[250px]", children: [ 293 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 16, className: "absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" }), 294 /* @__PURE__ */ jsxRuntimeExports.jsx( 295 "input", 296 { 297 type: "text", 298 placeholder: "Search logs...", 299 value: search, 300 onChange: (e) => setSearch(e.target.value), 301 className: "w-full h-10 pl-9 pr-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg text-sm outline-none focus:border-indigo-500" 302 } 303 ) 304 ] }), 305 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 flex items-center gap-2 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg px-3", children: [ 306 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { size: 16, className: "text-zinc-400" }), 307 /* @__PURE__ */ jsxRuntimeExports.jsx( 308 "input", 309 { 310 type: "date", 311 value: dateRange.start, 312 onChange: (e) => setDateRange({ ...dateRange, start: e.target.value }), 313 className: "bg-transparent text-xs outline-none w-24 dark:text-white" 314 } 315 ),
316 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400", children: "-" }), 317 /* @__PURE__ */ jsxRuntimeExports.jsx( 318 "input", 319 { 320 type: "date", 321 value: dateRange.end, 322 onChange: (e) => setDateRange({ ...dateRange, end: e.target.value }), 323 className: "bg-transparent text-xs outline-none w-24 dark:text-white" 324 } 325 ) 326 ] }) 327 ] }) 328 ] }), 329 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden flex flex-col shadow-sm", children: [ 330 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto flex-1 custom-scrollbar", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left border-collapse", children: [ 331 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-zinc-50 dark:bg-zinc-950 sticky top-0 z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
332 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 text-xs font-bold text-zinc-500 uppercase border-b border-zinc-200 dark:border-zinc-800 w-48", children: "Timestamp" }), 333 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 text-xs font-bold text-zinc-500 uppercase border-b border-zinc-200 dark:border-zinc-800 w-32", children: "Level" }), 334 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 text-xs font-bold text-zinc-500 uppercase border-b border-zinc-200 dark:border-zinc-800 w-72", children: "Source" }), 335 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 text-xs font-bold text-zinc-500 uppercase border-b border-zinc-200 dark:border-zinc-800", children: "Action & Info" }), 336 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 text-xs font-bold text-zinc-500 uppercase border-b border-zinc-200 dark:border-zinc-800 w-24 text-right", children: "Actions" }) 337 ] }) }), 338 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-zinc-100 dark:divide-zinc-800", children: visibleLogs.length > 0 ? visibleLogs.map((log) => { 339 var _a, _b, _c, _d, _e, _f, _g, _h; 340 const { detailsData, statusCode } = (() => { 341 if (!log.details) return { detailsData: {}, statusCode: null }; 342 try { 343 const d = log.details.startsWith("{") ? JSON.parse(log.details) : { message: log.details }; 344 return { detailsData: d, statusCode: d.statusCode }; 345 } catch (e) { 346 return { detailsData: { message: log.details }, statusCode: null }; 347 } 348 })(); 349 const fileMatch = ((_a = log.details) == null ? void 0 : _a.match(/MIDI (\d+)/)) || ((_b = log.details) == null ? void 0 : _b.match(/track (\d+)/i)) || ((_c = log.details) == null ? void 0 : _c.match(/Success (\d+)/)) || ((_d = log.details) == null ? void 0 : _d.match(/Failed (\d+)/)); 350 const fileId = fileMatch ? fileMatch[1] : null; 351 return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
352 /* @__PURE__ */ jsxRuntimeExports.jsxs( 353 "tr", 354 { 355 onClick: () => toggleLog(log.id), 356 className: `cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors ${log.groupColorClass ? "border-l-4 " + log.groupColorClass : "border-l-4 border-transparent"}`, 357 children: [ 358 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5 text-sm font-mono text-zinc-500 whitespace-nowrap align-top", children: new Date(log.timestamp).toLocaleString() }), 359 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5 align-top", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-1.5 py-0.5 rounded text-[10px] font-bold uppercase border ${getSeverityColor(log.severity)}`, children: log.severity }) }), 360 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5 text-sm text-zinc-700 dark:text-zinc-300 align-top", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [ 361 log.category === "worker" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 14, className: "text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(User, { size: 14, className: "text-zinc-400" }),
362 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium whitespace-nowrap", title: log.userName || "System", children: log.userName || "System" }) 363 ] }) }), 364 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5 text-sm align-top", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [ 365 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 366 groupWorkerLogs && log.isGroupStart && log.groupId && /* @__PURE__ */ jsxRuntimeExports.jsx( 367 "button", 368 { 369 onClick: (e) => { 370 e.stopPropagation(); 371 toggleGroup(log.groupId); 372 }, 373 className: "p-0.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded text-zinc-500 flex items-center gap-1", 374 children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-4 h-4 flex items-center justify-center font-bold", children: expandedGroups.has(log.groupId) ? "-" : "+" }) 375 } 376 ),
377 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-indigo-600 dark:text-indigo-400 font-mono text-xs", children: log.action }), 378 statusCode && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] px-1 rounded font-bold ${statusCode >= 400 ? "bg-red-100 text-red-600" : "bg-emerald-100 text-emerald-600"}`, children: statusCode }), 379 groupWorkerLogs && log.isGroupStart && log.groupId && log.groupCount && log.groupCount > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300 text-[10px] px-1.5 py-0.5 rounded font-bold", children: [ 380 "+", 381 log.groupCount - 1, 382 " steps collapsed" 383 ] }) 384 ] }), 385 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 386 /* @__P
386URE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500 line-clamp-1 italic", children: detailsData.message || (((_e = log.details) == null ? void 0 : _e.length) > 0 && !log.details.startsWith("{") ? log.details.substring(0, 100) : "") }), 387 fileId && /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: `/track/${fileId}`, target: "_blank", rel: "noreferrer", onClick: (e) => e.stopPropagation(), className: "text-[10px] text-indigo-500 hover:underline", children: "View MIDI" }) 388 ] }) 389 ] }) }), 390 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5 text-right align-top", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-1", children: [ 391 /* @__PURE__ */ jsxRuntimeExports.jsx( 392 "button", 393 { 394 onClick: () => { 395 if (confirm(`Clear all logs before ${new Date(log.timestamp).toLocaleString()}?`)) { 396 api.clearActivityLogs({ beforeDate: log.timestamp }).then(() => fetchLogs()); 397 } 398 }, 399 className: "p-1 text-zinc-400 hover:text-purple-500 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded", 400 title: "Clear all logs before this time", 401 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 14 }) 402 } 403 ),
404 /* @__PURE__ */ jsxRuntimeExports.jsx( 405 "button", 406 { 407 onClick: () => { 408 if (confirm(`Clear all logs from user '${log.userName}'?`)) { 409 api.clearActivityLogs({ userId: log.userId }).then(() => fetchLogs()); 410 } 411 }, 412 className: "p-1 text-zinc-400 hover:text-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded", 413 title: "Clear all from this user", 414 children: /* @__PURE__ */ jsxRuntimeExports.jsx(User, { size: 14 }) 415 } 416 ), 417 /* @__PURE__ */ jsxRuntimeExports.jsx( 418 "button", 419 { 420 onClick: () => { 421 if (confirm(`Clear all logs from source '${log.action}'?`)) { 422 api.clearActivityLogs({ source: log.action }).then(() => fetchLogs()); 423 } 424 }, 425 className: "p-1 text-zinc-400 hover:text-orange-500 hover:bg-orange-50 dark:hover:bg-orange-900/20 rounded", 426 title: "Clear all from this source", 427 children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }) 428 } 429 ),
430 /* @__PURE__ */ jsxRuntimeExports.jsx( 431 "button", 432 { 433 onClick: () => { 434 if (confirm("Delete this log entry?")) { 435 setLogs((prev) => prev.filter((l) => l.id !== log.id)); 436 api.deleteActivityLog(log.id).catch((e) => { 437 alert("Failed to delete log: " + e.message); 438 fetchLogs(); 439 }); 440 } 441 }, 442 className: "p-1 text-zinc-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded", 443 title: "Delete log", 444 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) 445 } 446 ) 447 ] }) }) 448 ] 449 } 450 ), 451 expandedLogs.has(log.id) && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: `bg-zinc-50/50 dark:bg-zinc-900/50 ${log.groupColorClass ? "border-l-4 " + log.groupColorClass : "border-l-4 border-transparent"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: 5, className: "px-4 py-3 border-b border-zinc-100 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-12", children: [
452 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-4 text-zinc-400 uppercase font-bold text-[10px] tracking-wider", children: [ 453 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 12 }), 454 " Log Properties" 455 ] }), 456 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [ 457 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-950 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [ 458 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 uppercase font-bold mb-1", children: "Entry ID / User ID" }), 459 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300", children: [ 460 "#", 461 log.id, 462 " / ", 463 log.userId && log.userId !== -1 ? `User#${log.userId}` : "Guest" 464 ] }) 465 ] }),
466 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-950 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [ 467 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 uppercase font-bold mb-1", children: "Source Name" }), 468 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300", children: log.userName || "System" }) 469 ] }), 470 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-950 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [ 471 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 uppercase font-bold mb-1", children: "IP Address" }), 472 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300", children: log.ipAddress || ((_g = (_f = log.userName) == null ? void 0 : _f.match(/\((.*?)\)/)) == null ? void 0 : _g[1]) || "N/A" }) 473 ] }),
474 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-950 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [ 475 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 uppercase font-bold mb-1", children: "Category / Group" }), 476 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300 capitalize", children: [ 477 log.category, 478 " ", 479 log.groupId ? `(${log.groupId})` : "" 480 ] }) 481 ] }) 482 ] }), 483 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2 text-zinc-400 uppercase font-bold text-[10px] tracking-wider", children: [ 484 /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 12 }), 485 " Full Log Context" 486 ] }), 487 /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "whitespace-pre-wrap break-words bg-white dark:bg-zinc-950 p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-inner text-xs font-mono text-zinc-600 dark:text-zinc-400 overflow-auto max-h-96", children: ((_h = log.details) == null ? void 0 : _h.startsWith("{")) ? JSON.stringify(JSON.parse(log.details), null, 2) : log.details }) 488 ] }) }) }) 489 ] }, log.id); 490 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: 5, className: "p-8 text-center text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
491 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 24, className: "opacity-50" }), 492 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No logs found matching your criteria." }) 493 ] }) }) }) }) 494 ] }) }), 495 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 flex justify-between items-center", children: [ 496 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: total > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 497 "Showing ", 498 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-zinc-700 dark:text-zinc-300", children: [ 499 (page - 1) * 100 + 1, 500 "-", 501 Math.min(page * 100, total) 502 ] }), 503 " of ", 504 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-zinc-700 dark:text-zinc-300", children: total }), 505 " logs ", 506 groupWorkerLogs && visibleLogs.length < logs.length ? ` (${visibleLogs.length} rows collapsed)` : "" 507 ] }) : "No logs found" }),
508 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 509 /* @__PURE__ */ jsxRuntimeExports.jsx( 510 "button", 511 { 512 disabled: page === 1, 513 onClick: () => setPage((p) => p - 1), 514 className: "px-3 py-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 disabled:opacity-50", 515 children: "Previous" 516 } 517 ), 518 /* @__PURE__ */ jsxRuntimeExports.jsx( 519 "button", 520 { 521 disabled: logs.length < 100, 522 onClick: () => setPage((p) => p + 1), 523 className: "px-3 py-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 disabled:opacity-50", 524 children: "Next" 525 } 526 ) 527 ] }) 528 ] }) 529 ] }) 530 ] }); 531}; 532export { 533 UnifiedLogs as default 534};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.