d84aded40b
- Add Leaflet.heat plugin (leaflet-heat.js) - Create renderHeatmap() function with confidence-based intensity - Toggle between markers and heatmap view - Support frequency filtering in heatmap mode - Custom gradient: blue -> cyan -> lime -> yellow -> red - Configurable radius (25px), blur (15px), and maxZoom (17) - Console logging for debugging heatmap rendering - Intensity based on device match confidence (0.5 default)
297 lines
13 KiB
HTML
297 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>GigLez - IoT RF Device Mapping Platform</title>
|
|
|
|
<!-- Leaflet CSS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
|
|
|
|
<!-- Custom CSS -->
|
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
</head>
|
|
<body>
|
|
<!-- Header -->
|
|
<header>
|
|
<div class="container">
|
|
<div class="logo">
|
|
<h1>🛰️ GigLez</h1>
|
|
<p>IoT RF Device Mapping Platform</p>
|
|
</div>
|
|
<nav>
|
|
<a href="#map" class="nav-link active">Map</a>
|
|
<a href="#upload" class="nav-link">Upload</a>
|
|
<a href="#search" class="nav-link">Search</a>
|
|
<a href="#stats" class="nav-link">Statistics</a>
|
|
<a href="/docs" class="nav-link" target="_blank">API Docs</a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<main>
|
|
<!-- Map View -->
|
|
<section id="map-section" class="section active">
|
|
<div class="map-controls">
|
|
<div class="control-group">
|
|
<label>
|
|
<input type="checkbox" id="cluster-toggle" checked>
|
|
Cluster Markers
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" id="heatmap-toggle">
|
|
Heatmap View
|
|
</label>
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Filter by Frequency:</label>
|
|
<select id="frequency-filter">
|
|
<option value="">All Frequencies</option>
|
|
<option value="315">315 MHz</option>
|
|
<option value="433">433 MHz</option>
|
|
<option value="868">868 MHz</option>
|
|
<option value="915">915 MHz</option>
|
|
</select>
|
|
</div>
|
|
<div class="stats-summary">
|
|
<span>Total Captures: <strong id="total-captures">0</strong></span>
|
|
<span>Unique Devices: <strong id="unique-devices">0</strong></span>
|
|
</div>
|
|
</div>
|
|
<div id="map"></div>
|
|
</section>
|
|
|
|
<!-- Upload View -->
|
|
<section id="upload-section" class="section">
|
|
<div class="container">
|
|
<h2>Upload RF Captures</h2>
|
|
<p class="subtitle">Upload .sub files with GPS coordinates from your wardriving session</p>
|
|
|
|
<!-- Upload Form -->
|
|
<div class="upload-container">
|
|
<!-- Drop Zone -->
|
|
<div id="drop-zone" class="drop-zone">
|
|
<div class="drop-zone-content">
|
|
<svg class="upload-icon" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17 8 12 3 7 8"></polyline>
|
|
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
</svg>
|
|
<h3>Drop .sub files here</h3>
|
|
<p>or click to browse</p>
|
|
<button type="button" class="btn btn-primary" onclick="document.getElementById('file-input').click()">
|
|
Select Files
|
|
</button>
|
|
<input type="file" id="file-input" multiple accept=".sub" style="display: none;">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GPS Manifest Form -->
|
|
<div class="manifest-form">
|
|
<h3>GPS Coordinates</h3>
|
|
<p>Provide GPS data for your captures</p>
|
|
|
|
<div class="form-group">
|
|
<label>Session UUID (optional)</label>
|
|
<input type="text" id="session-uuid" placeholder="Auto-generated if not provided">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Default GPS Coordinates</label>
|
|
<div class="gps-inputs">
|
|
<input type="number" id="default-lat" placeholder="Latitude" step="0.000001" min="-90" max="90">
|
|
<input type="number" id="default-lon" placeholder="Longitude" step="0.000001" min="-180" max="180">
|
|
</div>
|
|
<small>Applied to all files. Individual coordinates can be set below.</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>GPS Accuracy (meters)</label>
|
|
<input type="number" id="default-accuracy" placeholder="5.0" step="0.1" min="0" value="5.0">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Altitude (meters)</label>
|
|
<input type="number" id="default-altitude" placeholder="Optional" step="0.1">
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-secondary" onclick="useCurrentLocation()">
|
|
Use Current Location
|
|
</button>
|
|
</div>
|
|
|
|
<!-- File List -->
|
|
<div id="file-list" class="file-list" style="display: none;">
|
|
<h3>Files to Upload</h3>
|
|
<div id="files-container"></div>
|
|
<div class="upload-actions">
|
|
<button type="button" class="btn btn-primary btn-large" onclick="uploadFiles()">
|
|
Upload All Files
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="clearFiles()">
|
|
Clear All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Progress -->
|
|
<div id="upload-progress" class="upload-progress" style="display: none;">
|
|
<h3>Uploading...</h3>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="progress-fill"></div>
|
|
</div>
|
|
<p id="progress-text">Preparing upload...</p>
|
|
</div>
|
|
|
|
<!-- Upload Results -->
|
|
<div id="upload-results" class="upload-results" style="display: none;">
|
|
<h3>Upload Complete</h3>
|
|
<div id="results-container"></div>
|
|
<button type="button" class="btn btn-primary" onclick="resetUpload()">
|
|
Upload More Files
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Search View -->
|
|
<section id="search-section" class="section">
|
|
<div class="container">
|
|
<h2>Search Captures</h2>
|
|
<div class="search-form">
|
|
<div class="form-group">
|
|
<label>Search Query</label>
|
|
<input type="text" id="search-query" placeholder="Device name, protocol, or frequency">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Frequency (MHz)</label>
|
|
<select id="search-frequency">
|
|
<option value="">All</option>
|
|
<option value="315">315</option>
|
|
<option value="433">433</option>
|
|
<option value="868">868</option>
|
|
<option value="915">915</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Protocol</label>
|
|
<select id="search-protocol">
|
|
<option value="">All</option>
|
|
<option value="RAW">RAW</option>
|
|
<option value="Princeton">Princeton</option>
|
|
<option value="KeeLoq">KeeLoq</option>
|
|
<option value="MegaCode">MegaCode</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Date Range</label>
|
|
<input type="date" id="search-date-start">
|
|
<input type="date" id="search-date-end">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Geographic Search</label>
|
|
<div class="gps-inputs">
|
|
<input type="number" id="search-lat" placeholder="Latitude" step="0.000001">
|
|
<input type="number" id="search-lon" placeholder="Longitude" step="0.000001">
|
|
<input type="number" id="search-radius" placeholder="Radius (km)" step="0.1" min="0.1">
|
|
</div>
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-primary" onclick="searchCaptures()">
|
|
Search
|
|
</button>
|
|
</div>
|
|
|
|
<div id="search-results" class="search-results" style="display: none;">
|
|
<h3>Search Results</h3>
|
|
<div id="results-list"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Statistics View -->
|
|
<section id="stats-section" class="section">
|
|
<div class="container">
|
|
<h2>Platform Statistics</h2>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<h3>Total Captures</h3>
|
|
<p class="stat-value" id="stat-captures">-</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Unique Devices</h3>
|
|
<p class="stat-value" id="stat-devices">-</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Coverage Area</h3>
|
|
<p class="stat-value" id="stat-coverage">-</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Contributors</h3>
|
|
<p class="stat-value" id="stat-contributors">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<h3>Frequency Distribution</h3>
|
|
<canvas id="frequency-chart"></canvas>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<h3>Captures Over Time</h3>
|
|
<canvas id="timeline-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Capture Detail Modal -->
|
|
<div id="detail-modal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Capture Details</h2>
|
|
<button id="modal-close-btn" class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body" id="modal-body-content">
|
|
<!-- Content loaded dynamically -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer>
|
|
<div class="container">
|
|
<p>© 2026 GigLez - IoT RF Device Mapping Platform</p>
|
|
<p>Wigle for Sub-GHz Signals</p>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- Leaflet JS -->
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
|
|
<script src="https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js"></script>
|
|
|
|
<!-- Chart.js -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
|
|
<!-- Custom JS -->
|
|
<script src="/static/js/detail-modal.js"></script>
|
|
<script src="/static/js/map.js"></script>
|
|
<script src="/static/js/upload.js"></script>
|
|
<script src="/static/js/search.js"></script>
|
|
<script src="/static/js/stats.js"></script>
|
|
<script src="/static/js/main.js"></script>
|
|
</body>
|
|
</html>
|