48fcb00241
Major Achievements: - ✅ Full web interface (1,520+ lines of frontend code) - ✅ Interactive Leaflet.js map with marker clustering - ✅ Drag-and-drop upload system with GPS input - ✅ Search & filter UI with multi-criteria - ✅ Statistics dashboard with Chart.js - ✅ Responsive mobile-friendly design Backend: - ✅ FastAPI static file serving - ✅ Simplified server mode (main_simple.py) - ✅ Improved startup script with port auto-selection - ✅ PostgreSQL schema ready (requires setup) Database: - ✅ SQLite populated with 85 Flipper Zero signatures - ✅ Device matching system operational - ✅ Frequency-based search working Documentation: - ✅ PHASE_3_COMPLETE.md - Technical summary - ✅ WEB_INTERFACE_README.md - User guide - ✅ WEBAPP_STARTUP_GUIDE.md - Troubleshooting - ✅ POSTGRESQL_SETUP_EXPLANATION.md - DB setup guide - ✅ DATABASE_POPULATION_SUCCESS.md - Import report - ✅ DEVICE_IDENTIFICATION_REPORT.md - Matching analysis Files Created: - templates/index.html (260 lines) - static/css/main.css (500 lines) - static/js/*.js (760 lines total) - src/api/main_simple.py (simplified server) - start_web.sh (auto port selection) Status: Production MVP Ready Next: Phase 4 - API & Integration 🛰️ Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
3.6 KiB
JavaScript
135 lines
3.6 KiB
JavaScript
// GigLez - Main Application Logic
|
|
|
|
// Navigation
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
setupNavigation();
|
|
initializeApp();
|
|
});
|
|
|
|
function setupNavigation() {
|
|
const navLinks = document.querySelectorAll('.nav-link');
|
|
const sections = document.querySelectorAll('.section');
|
|
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
|
|
const target = link.getAttribute('href').substring(1);
|
|
|
|
// Don't navigate for external links
|
|
if (link.hasAttribute('target')) {
|
|
window.open(link.getAttribute('href'), '_blank');
|
|
return;
|
|
}
|
|
|
|
// Update active nav link
|
|
navLinks.forEach(l => l.classList.remove('active'));
|
|
link.classList.add('active');
|
|
|
|
// Show target section
|
|
sections.forEach(s => s.classList.remove('active'));
|
|
const targetSection = document.getElementById(`${target}-section`);
|
|
if (targetSection) {
|
|
targetSection.classList.add('active');
|
|
|
|
// Trigger section-specific actions
|
|
onSectionChange(target);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function onSectionChange(sectionName) {
|
|
switch (sectionName) {
|
|
case 'map':
|
|
// Refresh map tiles
|
|
if (window.map) {
|
|
setTimeout(() => window.map.invalidateSize(), 100);
|
|
}
|
|
break;
|
|
|
|
case 'stats':
|
|
// Load statistics
|
|
if (typeof loadStatistics === 'function') {
|
|
loadStatistics();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function initializeApp() {
|
|
// Check API connection
|
|
checkAPIConnection();
|
|
|
|
// Load initial data
|
|
loadInitialData();
|
|
|
|
// Set up periodic refresh
|
|
setInterval(refreshData, 60000); // Refresh every minute
|
|
}
|
|
|
|
async function checkAPIConnection() {
|
|
try {
|
|
const response = await fetch('/health');
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'healthy') {
|
|
console.log('✅ API connection healthy');
|
|
} else {
|
|
console.warn('⚠️ API connection unhealthy:', data);
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ API connection failed:', error);
|
|
}
|
|
}
|
|
|
|
async function loadInitialData() {
|
|
// Load captures for map
|
|
if (typeof loadCaptures === 'function') {
|
|
await loadCaptures();
|
|
}
|
|
}
|
|
|
|
async function refreshData() {
|
|
// Refresh map data if on map view
|
|
const mapSection = document.getElementById('map-section');
|
|
if (mapSection.classList.contains('active')) {
|
|
if (typeof window.reloadMapData === 'function') {
|
|
window.reloadMapData();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
function showNotification(message, type = 'info') {
|
|
// Simple notification system
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.classList.add('fade-out');
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
function formatFrequency(hz) {
|
|
return (hz / 1e6).toFixed(2) + ' MHz';
|
|
}
|
|
|
|
function formatDate(dateString) {
|
|
return new Date(dateString).toLocaleString();
|
|
}
|
|
|
|
function formatCoordinates(lat, lon) {
|
|
return `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
|
|
}
|
|
|
|
// Export utilities
|
|
window.showNotification = showNotification;
|
|
window.formatFrequency = formatFrequency;
|
|
window.formatDate = formatDate;
|
|
window.formatCoordinates = formatCoordinates;
|