PHP Classes

File: public_html/webdoors/cwn/js/cwn.js

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   public_html/webdoors/cwn/js/cwn.js   Download  
File: public_html/webdoors/cwn/js/cwn.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 25,607 bytes
 

Contents

Class file image Download
/** * Community Wireless Node Map - Main JavaScript */ let map; let markers; let allNetworks = []; let currentUser = null; let userLocation = null; const NETWORK_PAGE_SIZE = 500; let networkLoadTimer = null; // Initialize on page load $(document).ready(function() { initMap(); loadNetworks(); loadUserInfo(); // Character counter for submit description $('#description').on('input', function() { const len = $(this).val().length; $('#charCount').text(len); if (len < 10) { $('#charCount').removeClass('text-success text-warning').addClass('text-danger'); } else if (len > 450) { $('#charCount').removeClass('text-success text-danger').addClass('text-warning'); } else { $('#charCount').removeClass('text-danger text-warning').addClass('text-success'); } }); // Character counter for edit description $('#editDescription').on('input', function() { const len = $(this).val().length; $('#editCharCount').text(len); if (len < 10) { $('#editCharCount').removeClass('text-success text-warning').addClass('text-danger'); } else if (len > 450) { $('#editCharCount').removeClass('text-success text-danger').addClass('text-warning'); } else { $('#editCharCount').removeClass('text-danger text-warning').addClass('text-success'); } }); }); /** * Initialize the map */ function initMap() { // Create map centered on US (will be updated if geolocation available) map = L.map('map').setView([39.8283, -98.5795], 4); // Add OpenStreetMap tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', maxZoom: 19 }).addTo(map); // Initialize marker cluster group markers = L.markerClusterGroup({ chunkedLoading: true, maxClusterRadius: 50 }); map.addLayer(markers); map.on('moveend', function() { scheduleNetworkLoad(); }); // Add map click handler for picking location map.on('click', function(e) { if (window.pickingLocation) { $('#latitude').val(e.latlng.lat.toFixed(3)); $('#longitude').val(e.latlng.lng.toFixed(3)); window.pickingLocation = false; map.getContainer().style.cursor = ''; } }); // Request user's location and zoom to it requestUserLocation(); } /** * Request user's location and center map on it */ function requestUserLocation() { if (!navigator.geolocation) { console.log('Geolocation not supported'); return; } navigator.geolocation.getCurrentPosition( function(position) { const lat = position.coords.latitude; const lon = position.coords.longitude; // Store user location userLocation = { lat: lat, lng: lon }; // Center map on user location with good zoom level map.setView([lat, lon], 10); // Add "You are here" marker const userMarker = L.marker([lat, lon], { icon: L.divIcon({ className: 'user-location-marker', html: '<i class="fas fa-street-view" style="font-size: 24px; color: #dc3545;"></i>', iconSize: [30, 30], iconAnchor: [15, 30] }) }).addTo(map); userMarker.bindPopup('<strong>? You are here</strong>').openPopup(); console.log('Map centered on user location:', lat, lon); }, function(error) { console.log('Unable to get location:', error.message); // Keep default map view on US }, { enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 // Cache for 5 minutes } ); } /** * Load all networks and display on map (including PacketBBS bridge nodes) */ function loadNetworks() { const bounds = map.getBounds(); const bbox = [ bounds.getSouth(), bounds.getNorth(), bounds.getWest(), bounds.getEast() ].join(','); const cwnRequest = $.get('api.php', { action: 'list', limit: NETWORK_PAGE_SIZE, bbox: bbox }); const pbbRequest = $.get('api.php', { action: 'packetbbs_nodes' }); $.when(cwnRequest, pbbRequest).done(function(cwnResult, pbbResult) { const data = cwnResult[0]; const pbbData = pbbResult[0]; allNetworks = data.networks || []; const total = parseInt(data.total_all ?? data.total, 10) || 0; const visibleTotal = parseInt(data.total, 10) || allNetworks.length; displayNetworksOnMap(allNetworks, total, pbbData.nodes || []); displayNetworksList(allNetworks, total, visibleTotal); updateStatistics(data); }).fail(function(xhr) { showError('Failed to load networks: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } function scheduleNetworkLoad() { clearTimeout(networkLoadTimer); networkLoadTimer = setTimeout(loadNetworks, 250); } /** * Display networks on map (CWN + PacketBBS bridge nodes) */ function displayNetworksOnMap(networks, total, packetBbsNodes) { markers.clearLayers(); // PacketBBS bridge nodes ? distinct orange tower icon (packetBbsNodes || []).forEach(function (node) { const lat = parseFloat(node.lat); const lon = parseFloat(node.lon); if (isNaN(lat) || isNaN(lon)) return; const name = node.handle || node.node_id_prefix || '?'; const prefix = node.node_id_prefix || ''; const lastSeen = node.last_seen_at ? new Date(node.last_seen_at).toLocaleString() : 'Never'; const pbbIcon = L.divIcon({ className: '', html: '<i class="fas fa-broadcast-tower" style="font-size:20px;color:#fd7e14;"></i>', iconSize: [24, 24], iconAnchor: [12, 12] }); const popupContent = '<div>' + '<h6><i class="fas fa-broadcast-tower me-1" style="color:#fd7e14"></i>' + escapeHtml(name) + '</h6>' + '<span class="badge bg-warning text-dark">PacketBBS Node</span>' + (prefix ? '<p class="mb-1 mt-2"><code class="small">' + escapeHtml(prefix) + '?</code></p>' : '') + '<small class="text-muted"><i class="fas fa-clock me-1"></i>' + escapeHtml(lastSeen) + '</small>' + '<hr class="my-2">' + '<a href="/packetbbs-nodes#node-' + node.id + '" class="btn btn-sm btn-outline-warning w-100" target="_top">' + '<i class="fas fa-info-circle me-1"></i>More Info</a>' + '</div>'; L.marker([lat, lon], { icon: pbbIcon }) .bindPopup(popupContent) .addTo(markers); }); networks.forEach(function(network) { const marker = L.marker([network.latitude, network.longitude], { icon: markerIconFor(network) }); const description = networkDescription(network); const sourceLine = network.source_type === 'meshcore' ? `<i class="fas fa-satellite-dish"></i> MeshCore repeater<br>` : `<i class="fas fa-user"></i> ${escapeHtml(network.submitted_by_username)}@${escapeHtml(network.bbs_name)}<br>`; // Create popup content const popupContent = ` <div> <h6>${escapeHtml(network.ssid)}</h6> <span class="badge network-type-${network.network_type}">${network.network_type}</span> ${sourceBadge(network)} <p class="mt-2 mb-2">${escapeHtml(description).substring(0, 100)}${description.length > 100 ? '...' : ''}</p> <small class="text-muted"> ${sourceLine} <i class="fas fa-calendar"></i> ${formatDate(network.source_type === 'meshcore' ? network.last_seen_at : network.date_added)} </small> <hr class="my-2"> <button class="btn btn-sm btn-primary w-100" onclick="viewNetwork(${network.id})"> <i class="fas fa-info-circle"></i> View Details </button> </div> `; marker.bindPopup(popupContent); markers.addLayer(marker); }); $('#totalNetworks').text(total ?? networks.length); } /** * Display networks list */ function displayNetworksList(networks, total, visibleTotal) { const container = $('#networksList'); if (networks.length === 0) { container.html('<div class="text-center text-muted py-3">No networks found</div>'); return; } let html = '<div class="list-group">'; networks.slice(0, 10).forEach(function(network) { const description = networkDescription(network); const sourceMeta = network.source_type === 'meshcore' ? `<i class="fas fa-satellite-dish"></i> MeshCore &nbsp;` : `<i class="fas fa-user"></i> ${escapeHtml(network.submitted_by_username)} &nbsp;`; html += ` <div class="list-group-item list-group-item-action network-card" onclick="viewNetwork(${network.id})" style="cursor: pointer;"> <div class="d-flex justify-content-between align-items-start"> <div class="flex-grow-1"> <h6 class="mb-1"> <i class="fas ${network.source_type === 'meshcore' ? 'fa-broadcast-tower' : 'fa-wifi'}"></i> ${escapeHtml(network.ssid)} <span class="badge network-type-${network.network_type} ms-2">${network.network_type}</span> ${sourceBadge(network)} </h6> <p class="mb-1">${escapeHtml(description).substring(0, 150)}${description.length > 150 ? '...' : ''}</p> <div class="network-meta"> <i class="fas fa-map-marker-alt"></i> ${network.latitude}, ${network.longitude} &nbsp; ${sourceMeta} <i class="fas fa-calendar"></i> ${formatDate(network.source_type === 'meshcore' ? network.last_seen_at : network.date_added)} </div> </div> </div> </div> `; }); html += '</div>'; const totalNetworks = total ?? networks.length; const visibleNetworks = visibleTotal ?? networks.length; if (visibleNetworks > 10) { const markerNote = visibleNetworks > networks.length ? ` Displaying ${networks.length} map markers; zoom in to narrow the area.` : ''; html += `<div class="text-center mt-3"><small class="text-muted">Showing 10 of ${visibleNetworks} visible networks. ${totalNetworks} total.${markerNote}</small></div>`; } container.html(html); } /** * Load user information */ function loadUserInfo() { // Get credit balance and user info from API $.get('../../api/user/credits') .done(function(data) { currentUser = data; const balance = data.credit_balance || 0; $('#userCredits').text(balance); updateUserStats(); // Update parent window credit display using SDK if (typeof WebDoorCredits !== 'undefined') { WebDoorCredits.updateDisplay(balance); } }) .fail(function() { $('#userCredits').text('?'); }); } /** * Update user statistics */ function updateUserStats() { if (!currentUser) return; const myNets = allNetworks.filter(n => n.submitted_by == currentUser.id); $('#mySubmissions').text(myNets.length); $('#creditsEarned').text(myNets.length * 3); } /** * Update statistics display */ function updateStatistics(data) { $('#totalSearches').text('0'); // TODO: Get from user stats } /** * Show submit form */ function showSubmitForm() { $('#submitForm')[0].reset(); $('#charCount').text('0').removeClass('text-success text-warning text-danger'); $('#submitModal').modal('show'); } /** * Use current location */ function useCurrentLocation() { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser'); return; } navigator.geolocation.getCurrentPosition( function(position) { $('#latitude').val(position.coords.latitude.toFixed(3)); $('#longitude').val(position.coords.longitude.toFixed(3)); userLocation = { lat: position.coords.latitude, lng: position.coords.longitude }; }, function(error) { alert('Unable to get your location: ' + error.message); } ); } /** * Pick location on map */ function pickOnMap() { $('#submitModal').modal('hide'); alert('Click on the map to select a location'); map.getContainer().style.cursor = 'crosshair'; window.pickingLocation = true; // Wait for location to be picked, then reopen modal const checkPicked = setInterval(function() { if (!window.pickingLocation) { clearInterval(checkPicked); setTimeout(function() { $('#submitModal').modal('show'); }, 500); } }, 100); } /** * Toggle password field */ function togglePassword() { const isOpen = $('#isOpen').is(':checked'); $('#wifiPassword').prop('disabled', isOpen); if (isOpen) { $('#wifiPassword').val(''); } } /** * Submit new network */ function submitNetwork() { const data = { ssid: $('#ssid').val().trim(), latitude: parseFloat($('#latitude').val()), longitude: parseFloat($('#longitude').val()), description: $('#description').val().trim(), wifi_password: $('#wifiPassword').val().trim() || null, network_type: $('#networkType').val() }; // Validation if (!data.ssid || !data.latitude || !data.longitude || !data.description) { alert('Please fill in all required fields'); return; } if (data.description.length < 10 || data.description.length > 500) { alert('Description must be 10-500 characters'); return; } $.ajax({ url: 'api.php?action=submit', method: 'POST', contentType: 'application/json', data: JSON.stringify(data) }) .done(function(response) { $('#submitModal').modal('hide'); showSuccess(`Network submitted successfully! You earned ${response.credits_earned} credits.`); // Refresh data loadNetworks(); loadUserInfo(); }) .fail(function(xhr) { showError('Submission failed: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * View network details */ function viewNetwork(id) { $.get(`api.php?action=get&id=${id}`) .done(function(network) { displayNetworkDetails(network); }) .fail(function(xhr) { showError('Failed to load network: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * Display network details in modal */ function displayNetworkDetails(network) { const isMesh = network.source_type === 'meshcore'; $('#detailsTitle').html(`<i class="fas ${isMesh ? 'fa-broadcast-tower' : 'fa-wifi'}"></i> ${escapeHtml(network.ssid)}`); const passwordDisplay = network.wifi_password ? `<span class="password-field">${escapeHtml(network.wifi_password)}</span>` : '<em class="text-muted">Open network (no password)</em>'; const description = networkDescription(network); const publicKey = network.public_key || ''; const publicKeyRow = isMesh && publicKey ? ` <dt class="col-sm-4">Public Key</dt> <dd class="col-sm-8"> <code title="${escapeHtml(publicKey)}">${escapeHtml(publicKey.substring(0, 12))}...</code> <button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="copyText('${escapeHtml(publicKey)}')"> <i class="fas fa-copy"></i> </button> </dd> ` : ''; const meshRows = isMesh ? ` ${publicKeyRow} <dt class="col-sm-4">Hop Count</dt> <dd class="col-sm-8">${network.hop_count ?? 'Unknown'}</dd> <dt class="col-sm-4">Last Seen</dt> <dd class="col-sm-8">${formatDate(network.last_seen_at)}</dd> ` : ` <dt class="col-sm-4">Submitted By</dt> <dd class="col-sm-8">${escapeHtml(network.submitted_by_username)}@${escapeHtml(network.bbs_name)}</dd> <dt class="col-sm-4">Date Added</dt> <dd class="col-sm-8">${formatDate(network.date_added)}</dd> <dt class="col-sm-4">Last Verified</dt> <dd class="col-sm-8">${formatDate(network.date_verified)}</dd> `; const html = ` <dl class="network-details row"> <dt class="col-sm-4">Network Name</dt> <dd class="col-sm-8">${escapeHtml(network.ssid)}</dd> <dt class="col-sm-4">Type</dt> <dd class="col-sm-8"> <span class="badge network-type-${network.network_type}">${network.network_type}</span> ${sourceBadge(network)} </dd> <dt class="col-sm-4">Location</dt> <dd class="col-sm-8"> <span class="coordinates">${network.latitude}, ${network.longitude}</span> <br> <a href="https://www.google.com/maps?q=${network.latitude},${network.longitude}" target="_blank" class="btn btn-sm btn-outline-primary mt-1"> <i class="fas fa-map"></i> Get Directions </a> </dd> <dt class="col-sm-4">Description</dt> <dd class="col-sm-8">${escapeHtml(description)}</dd> <dt class="col-sm-4">WiFi Password</dt> <dd class="col-sm-8">${passwordDisplay}</dd> ${meshRows} </dl> `; $('#detailsBody').html(html); // Add edit/delete buttons if user owns this network if (!isMesh && currentUser && network.submitted_by == currentUser.id) { $('#detailsActions').html(` <button class="btn btn-warning" onclick="editNetwork(${network.id})"> <i class="fas fa-edit"></i> Edit </button> <button class="btn btn-danger" onclick="deleteNetwork(${network.id})"> <i class="fas fa-trash"></i> Delete </button> `); } else { $('#detailsActions').html(''); } $('#detailsModal').modal('show'); // Center map on this network map.setView([network.latitude, network.longitude], 13); } /** * Use my location to find nearby networks */ function useMyLocation() { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser'); return; } const confirmed = confirm('This will cost 1 credit to search for nearby networks. Continue?'); if (!confirmed) return; navigator.geolocation.getCurrentPosition( function(position) { searchNearby(position.coords.latitude, position.coords.longitude, 10); }, function(error) { alert('Unable to get your location: ' + error.message); } ); } /** * Search for nearby networks */ function searchNearby(lat, lon, radiusKm) { $.ajax({ url: 'api.php?action=search', method: 'POST', contentType: 'application/json', data: JSON.stringify({ type: 'radius', latitude: lat, longitude: lon, radius_km: radiusKm }) }) .done(function(response) { showSuccess(`Found ${response.count} networks nearby (${response.credits_spent} credit spent)`); if (response.results.length > 0) { displayNetworksOnMap(response.results); displayNetworksList(response.results); // Center map on user location map.setView([lat, lon], 11); // Add marker for user location L.marker([lat, lon], { icon: L.divIcon({ className: 'custom-marker', html: '<i class="fas fa-user"></i>', iconSize: [30, 30] }) }).addTo(map).bindPopup('You are here'); } loadUserInfo(); // Refresh credits }) .fail(function(xhr) { showError('Search failed: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * Refresh networks */ function refreshNetworks() { loadNetworks(); showSuccess('Networks refreshed'); } /** * Delete network */ function deleteNetwork(id) { if (!confirm('Are you sure you want to delete this network?')) { return; } $.ajax({ url: `api.php?action=delete&id=${id}`, method: 'DELETE' }) .done(function() { $('#detailsModal').modal('hide'); showSuccess('Network deleted successfully'); loadNetworks(); }) .fail(function(xhr) { showError('Delete failed: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * Edit network */ function editNetwork(id) { // Get the network data $.get(`api.php?action=get&id=${id}`) .done(function(network) { // Close details modal $('#detailsModal').modal('hide'); // Populate edit form $('#editNetworkId').val(network.id); $('#editDescription').val(network.description); $('#editWifiPassword').val(network.wifi_password || ''); $('#editNetworkType').val(network.network_type); $('#editIsOpen').prop('checked', !network.wifi_password); // Update character counter const len = network.description.length; $('#editCharCount').text(len); if (len < 10) { $('#editCharCount').removeClass('text-success text-warning').addClass('text-danger'); } else if (len > 450) { $('#editCharCount').removeClass('text-success text-danger').addClass('text-warning'); } else { $('#editCharCount').removeClass('text-danger text-warning').addClass('text-success'); } // Show edit modal $('#editModal').modal('show'); }) .fail(function(xhr) { showError('Failed to load network: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * Save network edits */ function saveNetworkEdit() { const id = $('#editNetworkId').val(); const data = { description: $('#editDescription').val().trim(), wifi_password: $('#editWifiPassword').val().trim() || null, network_type: $('#editNetworkType').val() }; // Validation if (!data.description) { alert('Description is required'); return; } if (data.description.length < 10 || data.description.length > 500) { alert('Description must be 10-500 characters'); return; } $.ajax({ url: `api.php?action=update&id=${id}`, method: 'PUT', contentType: 'application/json', data: JSON.stringify(data) }) .done(function(response) { $('#editModal').modal('hide'); showSuccess('Network updated successfully!'); // Refresh data loadNetworks(); // Reopen the details modal with updated data setTimeout(function() { viewNetwork(id); }, 300); }) .fail(function(xhr) { showError('Update failed: ' + (xhr.responseJSON?.error || 'Unknown error')); }); } /** * Toggle password field in edit modal */ function toggleEditPassword() { const isOpen = $('#editIsOpen').is(':checked'); $('#editWifiPassword').prop('disabled', isOpen); if (isOpen) { $('#editWifiPassword').val(''); } } /** * Utility functions */ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatDate(dateStr) { if (!dateStr) return 'Unknown'; const date = new Date(dateStr); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); } function networkDescription(network) { return network.description || (network.source_type === 'meshcore' ? 'MeshCore repeater advertisement' : ''); } function sourceBadge(network) { if (network.source_type !== 'meshcore') { return ''; } return '<span class="badge bg-info text-dark ms-2">mesh</span>'; } function markerIconFor(network) { const isMesh = network.source_type === 'meshcore'; return L.divIcon({ className: '', html: `<i class="fas ${isMesh ? 'fa-broadcast-tower' : 'fa-wifi'}" style="font-size:20px;color:${isMesh ? '#0dcaf0' : '#0d6efd'}"></i>`, iconSize: [24, 24], iconAnchor: [12, 12] }); } function copyText(text) { if (!navigator.clipboard) { return; } navigator.clipboard.writeText(text); } function showSuccess(message) { // Simple alert for now - could be enhanced with toast notifications alert(message); } function showError(message) { alert('Error: ' + message); }