Phase 3 Complete: Web Interface MVP
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>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user