update log4om

This commit is contained in:
2025-10-17 00:33:56 +05:30
parent b66ab53df4
commit 6705661d81
14 changed files with 230 additions and 50 deletions

View File

@@ -1,22 +1,26 @@
// spot-worker.js - Web Worker pour traiter les spots
let lastProcessedData = null;
self.onmessage = function(e) {
const { type, data, messageId } = e.data;
const { type, data, messageId } = e.data;
// Libérer l'ancienne référence
lastProcessedData = null;
switch(type) {
case 'FILTER_SPOTS':
const filtered = filterSpots(data.spots, data.filters, data.watchlist);
// ✅ AJOUTER messageId à la réponse
self.postMessage({ type: 'FILTERED_SPOTS', data: filtered, messageId });
lastProcessedData = null;
break;
case 'SORT_SPOTS':
const sorted = sortSpots(data.spots, data.sortBy, data.sortOrder);
// ✅ AJOUTER messageId à la réponse
self.postMessage({ type: 'SORTED_SPOTS', data: sorted, messageId });
lastProcessedData = null;
break;
default:
console.error('Unknown worker message type:', type);
}

View File

@@ -78,13 +78,27 @@
$: {
if (spotFilters.showAll) {
filteredSpots = spots;
isFiltering = false;
if (filterTimeout) {
clearTimeout(filterTimeout);
filterTimeout = null;
}
} else {
if (filterTimeout) clearTimeout(filterTimeout);
if (filterTimeout) {
clearTimeout(filterTimeout);
}
filterTimeout = setTimeout(async () => {
isFiltering = true;
filteredSpots = await spotWorker.filterSpots(spots, spotFilters, watchlist);
isFiltering = false;
try {
filteredSpots = await spotWorker.filterSpots(spots, spotFilters, watchlist);
} catch (error) {
console.error('Filter error:', error);
filteredSpots = spots;
} finally {
isFiltering = false;
filterTimeout = null;
}
}, 150);
}
}
@@ -220,7 +234,7 @@
wsStatus = 'connected';
reconnectAttempts = 0;
errorMessage = '';
showToast('Connected to server', 'success');
showToast('Connected to DX Cluster', 'connection');
};
ws.onmessage = (event) => {
@@ -273,9 +287,9 @@
case 'spots':
const newSpots = message.data || [];
// Détecter si votre indicatif a été spotté
if (stats.myCallsign && newSpots.length > 0) {
newSpots.forEach(spot => {
// Vérifier si c'est votre callsign ET qu'on ne l'a pas déjà notifié
if (spot.DX === stats.myCallsign && !notifiedSpots.has(spot.ID)) {
notifiedSpots.add(spot.ID);
showToast(
@@ -285,16 +299,22 @@
}
});
if (notifiedSpots.size > 100) {
// ✅ Nettoyer les anciens IDs (garder seulement 200 derniers)
if (notifiedSpots.size > 200) {
const arr = Array.from(notifiedSpots);
notifiedSpots = new Set(arr.slice(-100));
notifiedSpots = new Set(arr.slice(-200));
}
}
spots = newSpots;
// ✅ Debounce la sauvegarde du cache (toutes les 30 secondes max)
if (spots.length > 0) {
spotCache.saveSpots(spots).catch(err => console.error('Cache save error:', err));
if (window.cacheSaveTimeout) clearTimeout(window.cacheSaveTimeout);
window.cacheSaveTimeout = setTimeout(() => {
spotCache.saveSpots(spots).catch(err => console.error('Cache save error:', err));
window.cacheSaveTimeout = null; // ✅ Nettoyer la référence
}, 30000); // 30 secondes
}
break;
case 'spotters':
@@ -359,13 +379,13 @@
const data = await response.json();
if (data.success) {
showToast(`${callsign} Sent - Radio tuned on ${frequency} in ${mode}`, 'success');
showToast(`📻 Tuned to ${callsign} ${frequency} ${mode}`, 'radio');
} else {
showToast('Failed to send', 'error');
showToast('Failed to send to radio', 'error');
}
} catch (error) {
console.error('Error sending callsign:', error);
showToast(`Error: ${error.message}`, 'error');
showToast(`❌ Connection error: ${error.message}`, 'error');
}
}
@@ -380,11 +400,13 @@
const data = await response.json();
if (data.success) {
stats.filters[filterName] = value;
showToast(`Filter ${filterName} updated`, 'success');
const filterLabel = filterName.toUpperCase();
const status = value ? 'ON' : 'OFF';
showToast(`🔧 ${filterLabel} filter ${status}`, 'success');
}
} catch (error) {
console.error('Error updating filter:', error);
showToast(`Update error: ${error.message}`, 'error');
showToast(`❌ Failed to update filter: ${error.message}`, 'error');
}
}
@@ -403,7 +425,7 @@ async function shutdownApp() {
if (reconnectTimer) clearTimeout(reconnectTimer);
wsStatus = 'disconnected';
showToast('FlexDXCluster shutting down...', 'info');
showToast('⚡ Shutting down FlexDXCluster...', 'warning');
// ✅ Envoyer la commande de shutdown au backend
const response = await fetch('/api/shutdown', {
@@ -434,7 +456,7 @@ async function shutdownApp() {
} catch (error) {
console.error('Error shutting down:', error);
if (!isShuttingDown) {
showToast(`Cannot shutdown: ${error.message}`, 'error');
showToast(`❌ Shutdown failed: ${error.message}`, 'error');
}
}
}
@@ -496,6 +518,26 @@ async function shutdownApp() {
window.removeEventListener('sendSpot', handleSendSpot);
};
});
onDestroy(() => {
console.log('Cleaning up App...');
// ✅ Nettoyer tous les timeouts
if (filterTimeout) {
clearTimeout(filterTimeout);
filterTimeout = null;
}
if (window.cacheSaveTimeout) {
clearTimeout(window.cacheSaveTimeout);
window.cacheSaveTimeout = null;
}
notifiedSpots.clear();
console.log('App cleanup complete');
});
</script>
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-2">

View File

@@ -51,7 +51,7 @@
<p class="text-xs text-slate-400 mt-1">Clients</p>
</div>
<div class="col-span-3 bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
<div class="col-span-3 bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
<div class="flex items-center justify-center gap-6 h-full">
<label class="flex items-center gap-2 cursor-pointer">
<input
@@ -85,6 +85,18 @@
<div class="relative w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span class="text-sm font-medium">FT4</span>
</label>
<!-- ✅ AJOUTER ce switch Beacon -->
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={stats.filters.beacon}
on:change={(e) => handleFilterChange('beacon', e.target.checked)}
class="sr-only peer"
/>
<div class="relative w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span class="text-sm font-medium">Beacon</span>
</label>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<script>
export let message;
export let type = 'info'; // 'success', 'error', 'warning', 'info', 'milestone', 'band'
export let type = 'info';
const icons = {
success: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>`,
@@ -9,28 +9,44 @@
info: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>`,
milestone: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path>`,
band: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>`,
mycall: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"></path>`
mycall: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"></path>`,
radio: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"></path>`,
connection: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>`
};
const colors = {
success: 'bg-green-500',
error: 'bg-red-500',
warning: 'bg-orange-500',
info: 'bg-blue-500',
milestone: 'bg-gradient-to-r from-purple-500 to-pink-500',
band: 'bg-gradient-to-r from-orange-500 to-amber-500',
mycall: 'bg-gradient-to-r from-red-500 to-pink-500'
success: 'from-green-500 to-emerald-600',
error: 'from-red-500 to-rose-600',
warning: 'from-orange-500 to-amber-600',
info: 'from-blue-500 to-cyan-600',
milestone: 'from-purple-500 to-pink-500',
band: 'from-orange-500 to-amber-500',
mycall: 'from-red-500 to-pink-500',
radio: 'from-indigo-500 to-purple-600',
connection: 'from-green-400 to-emerald-500'
};
// Animation d'entrée plus dynamique
let visible = false;
$: if (message) {
visible = true;
}
</script>
<div class="fixed bottom-5 right-5 {colors[type]} text-white px-5 py-3 rounded-lg shadow-lg z-50 animate-in slide-in-from-bottom-5 duration-300 min-w-[300px] backdrop-blur-sm">
<div class="flex items-center gap-3">
{#if icons[type]}
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{@html icons[type]}
</svg>
{/if}
<span class="font-medium text-sm">{message}</span>
<div class="fixed bottom-5 right-5 z-50 animate-in slide-in-from-bottom-5 duration-300" class:opacity-0={!visible}>
<div class="bg-gradient-to-r {colors[type]} text-white px-5 py-4 rounded-xl shadow-2xl backdrop-blur-sm min-w-[350px] max-w-[500px] border border-white/20">
<div class="flex items-start gap-3">
{#if icons[type]}
<div class="flex-shrink-0 w-8 h-8 bg-white/20 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{@html icons[type]}
</svg>
</div>
{/if}
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm leading-relaxed break-words">{message}</p>
</div>
</div>
</div>
</div>
@@ -47,6 +63,6 @@
}
.animate-in {
animation: slide-in-from-bottom 0.3s ease-out;
animation: slide-in-from-bottom 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
</style>

View File

@@ -57,14 +57,24 @@ class SpotCache {
const transaction = this.db.transaction(['spots'], 'readwrite');
const store = transaction.objectStore('spots');
// Vider d'abord le store
// Vider d'abord le store
await store.clear();
// Ajouter tous les spots avec un timestamp
// ✅ Sauvegarder par batch de 100 pour éviter surcharge mémoire
const timestamp = Date.now();
spots.forEach(spot => {
store.put({ ...spot, timestamp });
});
const batchSize = 100;
for (let i = 0; i < spots.length; i += batchSize) {
const batch = spots.slice(i, i + batchSize);
batch.forEach(spot => {
store.put({ ...spot, timestamp });
});
// ✅ Petite pause pour libérer la mémoire
if (i + batchSize < spots.length) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
await this.waitForTransaction(transaction);
console.log(`✅ Saved ${spots.length} spots to cache`);

View File

@@ -44,7 +44,17 @@ class SpotWorkerManager {
const messageId = ++this.messageId;
// ✅ Créer un timeout pour éviter les callbacks orphelins
const timeoutId = setTimeout(() => {
if (this.callbacks.has(messageId)) {
console.warn('Worker callback timeout, cleaning up');
this.callbacks.delete(messageId);
resolve(spots); // Fallback sur les spots non filtrés
}
}, 5000); // 5 secondes max
this.callbacks.set(messageId, (filteredSpots) => {
clearTimeout(timeoutId); // ✅ Nettoyer le timeout
resolve(filteredSpots);
});
@@ -66,7 +76,17 @@ class SpotWorkerManager {
const messageId = ++this.messageId;
// ✅ Timeout pour éviter les callbacks orphelins
const timeoutId = setTimeout(() => {
if (this.callbacks.has(messageId)) {
console.warn('Worker callback timeout, cleaning up');
this.callbacks.delete(messageId);
resolve(spots);
}
}, 5000);
this.callbacks.set(messageId, (sortedSpots) => {
clearTimeout(timeoutId); // ✅ Nettoyer le timeout
resolve(sortedSpots);
});