Files
ente/infra/experiments/logs-viewer/index.html
Neeraj e1423f2030 Add web-based log viewer for Ente application logs
This adds a comprehensive web-based log viewer that provides similar
functionality to the mobile log viewer, allowing analysis of log files
from customer support requests.

Features:
- Upload and parse ZIP files containing daily log files
- Advanced filtering by log level, logger name, process, and timeline
- Text search with wildcard support (logger:Service*)
- Interactive analytics with click-to-filter charts
- Modern UI using Ente's design system and Material-UI components
- Infinite scroll for performance with large log files
- Export functionality for filtered results
- Responsive design for desktop and mobile

Technical highlights:
- Client-side ZIP processing with JSZip
- Efficient log parsing supporting Ente's super_logging format
- Real-time filtering with optimized algorithms
- Memory-efficient rendering with virtual scrolling
- CSS custom properties for theming consistency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 13:55:24 +05:30

218 lines
9.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ente Log Viewer</title>
<!-- Material-UI CSS -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mui/material@latest/dist/index.css" />
<!-- Ente theme -->
<link rel="stylesheet" href="ente-theme.css">
<!-- Custom styles -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="app">
<!-- Header -->
<header class="header">
<div class="header-content">
<h1>📋 Ente Log Viewer</h1>
<div class="header-actions">
<button id="filter-btn" class="mui-icon-btn" title="Filter logs">
<span class="material-icons">filter_list</span>
<span class="filter-count" id="filter-count" style="display: none;"></span>
</button>
<button id="sort-btn" class="mui-icon-btn" title="Sort order">
<span class="material-icons">arrow_downward</span>
</button>
<div class="dropdown">
<button class="mui-icon-btn dropdown-toggle" title="More actions">
<span class="material-icons">more_vert</span>
</button>
<div class="dropdown-menu mui-menu">
<button id="analytics-btn" class="dropdown-item mui-menu-item">
<span class="material-icons">analytics</span>
<span>Analytics</span>
</button>
<button id="export-btn" class="dropdown-item mui-menu-item">
<span class="material-icons">download</span>
<span>Export Logs</span>
</button>
<button id="clear-btn" class="dropdown-item mui-menu-item danger">
<span class="material-icons">delete</span>
<span>Clear Logs</span>
</button>
</div>
</div>
</div>
</div>
</header>
<!-- Upload Section -->
<div class="upload-section" id="upload-section">
<div class="upload-area" id="upload-area">
<div class="upload-content">
<div class="upload-icon">📁</div>
<h2>Upload Log Files</h2>
<p>Drag and drop a zip file containing log files, or click to browse</p>
<input type="file" id="file-input" accept=".zip" hidden>
<button id="browse-btn" class="primary-btn">Choose ZIP File</button>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content" id="main-content" style="display: none;">
<!-- Search Bar -->
<div class="search-section">
<div class="search-bar mui-search-container">
<div class="mui-textfield">
<input type="text" id="search-input" placeholder="Search logs... (try 'logger:SyncService' or text search)" class="mui-input" />
<span class="mui-search-icon material-icons">search</span>
<button id="clear-search" class="mui-clear-btn" style="display: none;">
<span class="material-icons">clear</span>
</button>
</div>
</div>
</div>
<!-- Timeline Filter -->
<div class="timeline-section" id="timeline-section" style="display: none;">
<div class="timeline-header">
<span class="material-icons">timeline</span>
<span>Timeline Filter</span>
<button id="timeline-toggle" class="mui-icon-btn timeline-btn">
<span class="material-icons">timeline</span>
</button>
</div>
<div class="timeline-controls" id="timeline-controls" style="display: none;">
<div class="timeline-range">
<div class="mui-textfield">
<input type="datetime-local" id="start-time" class="mui-input" />
</div>
<span class="range-separator">to</span>
<div class="mui-textfield">
<input type="datetime-local" id="end-time" class="mui-input" />
</div>
<button id="reset-timeline" class="mui-button secondary">
<span class="material-icons">refresh</span>
<span>Reset</span>
</button>
</div>
</div>
</div>
<!-- Active Filters -->
<div class="active-filters" id="active-filters" style="display: none;">
<div class="filter-chips" id="filter-chips"></div>
</div>
<!-- Log Stats -->
<div class="log-stats" id="log-stats">
<span id="log-count">0 logs loaded</span>
<span id="filtered-count"></span>
</div>
<!-- Log List -->
<div class="log-list-container">
<div class="log-list" id="log-list">
<!-- Log entries will be populated here -->
</div>
<div class="loading" id="loading" style="display: none;">Loading...</div>
<div class="load-more" id="load-more" style="display: none;">
<button class="secondary-btn">Load More</button>
</div>
</div>
</div>
</div>
<!-- Filter Dialog -->
<div class="dialog-overlay" id="filter-dialog" style="display: none;">
<div class="dialog">
<div class="dialog-header">
<h2>Filter Logs</h2>
<button class="close-btn" id="close-filter"></button>
</div>
<div class="dialog-content">
<!-- Log Levels -->
<div class="filter-section">
<h3>Log Levels</h3>
<div class="level-chips" id="level-chips">
<!-- Level chips will be populated here -->
</div>
</div>
<!-- Process -->
<div class="filter-section">
<h3>Process</h3>
<div class="process-list" id="process-list">
<!-- Process checkboxes will be populated here -->
</div>
</div>
<!-- Loggers -->
<div class="filter-section">
<h3>Loggers</h3>
<div class="logger-list" id="logger-list">
<!-- Logger checkboxes will be populated here -->
</div>
</div>
</div>
<div class="dialog-actions">
<button id="clear-filters" class="secondary-btn">Clear All</button>
<button id="cancel-filter" class="secondary-btn">Cancel</button>
<button id="apply-filters" class="primary-btn">Apply</button>
</div>
</div>
</div>
<!-- Analytics Dialog -->
<div class="dialog-overlay" id="analytics-dialog" style="display: none;">
<div class="dialog">
<div class="dialog-header">
<h2>Logger Analytics</h2>
<button class="close-btn" id="close-analytics"></button>
</div>
<div class="dialog-content">
<div id="analytics-content">
<!-- Analytics charts will be populated here -->
</div>
</div>
<div class="dialog-actions">
<button id="close-analytics-btn" class="secondary-btn">Close</button>
</div>
</div>
</div>
<!-- Log Detail Dialog -->
<div class="dialog-overlay" id="detail-dialog" style="display: none;">
<div class="dialog large">
<div class="dialog-header">
<h2>Log Details</h2>
<button class="close-btn" id="close-detail"></button>
</div>
<div class="dialog-content">
<div id="detail-content">
<!-- Log details will be populated here -->
</div>
</div>
<div class="dialog-actions">
<button id="copy-log" class="secondary-btn">Copy</button>
<button id="close-detail-btn" class="secondary-btn">Close</button>
</div>
</div>
</div>
<!-- Material-UI JavaScript -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@mui/material@latest/umd/material-ui.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="script.js"></script>
</body>
</html>