bug
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import ErrorBanner from './components/ErrorBanner.svelte';
|
||||
import { spotWorker } from './lib/spotWorker.js';
|
||||
import { spotCache } from './lib/spotCache.js';
|
||||
import LogsTab from './components/LogsTab.svelte';
|
||||
|
||||
|
||||
// State
|
||||
@@ -39,6 +40,7 @@
|
||||
let errorMessage = '';
|
||||
let toastMessage = '';
|
||||
let toastType = 'info';
|
||||
let logs = [];
|
||||
|
||||
let spotFilters = {
|
||||
showAll: true,
|
||||
@@ -328,6 +330,21 @@
|
||||
spotCache.saveQSOs(recentQSOs).catch(err => console.error('Cache save error:', err));
|
||||
}
|
||||
break;
|
||||
case 'appLog':
|
||||
// Un seul log applicatif
|
||||
if (message.data) {
|
||||
logs = [...logs, message.data];
|
||||
// Garder seulement les 500 derniers
|
||||
if (logs.length > 500) {
|
||||
logs = logs.slice(-500);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'appLogs':
|
||||
// Logs initiaux (au chargement)
|
||||
logs = message.data || [];
|
||||
break;
|
||||
|
||||
case 'logStats':
|
||||
logStats = message.data || {};
|
||||
break;
|
||||
@@ -608,6 +625,7 @@ async function shutdownApp() {
|
||||
{recentQSOs}
|
||||
{logStats}
|
||||
{dxccProgress}
|
||||
{logs}
|
||||
on:toast={(e) => showToast(e.detail.message, e.detail.type)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
139
frontend/src/components/LogsTab.svelte
Normal file
139
frontend/src/components/LogsTab.svelte
Normal file
@@ -0,0 +1,139 @@
|
||||
<script>
|
||||
export let logs = [];
|
||||
|
||||
let autoScroll = true;
|
||||
let container;
|
||||
let selectedLevels = {
|
||||
debug: true,
|
||||
info: true,
|
||||
warning: true,
|
||||
error: true
|
||||
};
|
||||
|
||||
// ✅ Filtrer les logs par niveau sélectionné
|
||||
$: filteredLogs = logs.filter(log => {
|
||||
const level = log.level.toLowerCase();
|
||||
return selectedLevels[level] || false;
|
||||
});
|
||||
|
||||
// ✅ Auto-scroll UNIQUEMENT si activé
|
||||
$: if (autoScroll && container && filteredLogs.length > 0) {
|
||||
setTimeout(() => {
|
||||
if (autoScroll) { // ✅ Vérifier à nouveau car peut avoir changé
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function getLevelColor(level) {
|
||||
switch(level.toLowerCase()) {
|
||||
case 'error': return 'text-red-400';
|
||||
case 'warning':
|
||||
case 'warn': return 'text-yellow-400';
|
||||
case 'info': return 'text-blue-400';
|
||||
case 'debug': return 'text-slate-400';
|
||||
default: return 'text-slate-300';
|
||||
}
|
||||
}
|
||||
|
||||
function getLevelBadge(level) {
|
||||
switch(level.toLowerCase()) {
|
||||
case 'error': return 'bg-red-500/20 text-red-400 border-red-500/50';
|
||||
case 'warning':
|
||||
case 'warn': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50';
|
||||
case 'info': return 'bg-blue-500/20 text-blue-400 border-blue-500/50';
|
||||
case 'debug': return 'bg-slate-500/20 text-slate-400 border-slate-500/50';
|
||||
default: return 'bg-slate-500/20 text-slate-300 border-slate-500/50';
|
||||
}
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
logs = [];
|
||||
}
|
||||
|
||||
function toggleLevel(level) {
|
||||
selectedLevels[level] = !selectedLevels[level];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col bg-slate-900/50 rounded-lg border border-slate-700/50">
|
||||
<!-- Header -->
|
||||
<div class="p-3 border-b border-slate-700/50 flex-shrink-0">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-lg font-bold">Application Logs</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={autoScroll}
|
||||
class="rounded cursor-pointer">
|
||||
<span>Auto-scroll</span>
|
||||
</label>
|
||||
<button
|
||||
on:click={clearLogs}
|
||||
class="px-3 py-1.5 bg-red-600/20 hover:bg-red-600/40 text-red-400 rounded text-sm transition-colors">
|
||||
Clear Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Filtres par niveau -->
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="text-slate-400 mr-2">Show:</span>
|
||||
|
||||
<button
|
||||
on:click={() => toggleLevel('debug')}
|
||||
class="px-2 py-1 rounded border transition-colors {selectedLevels.debug ? 'bg-slate-500/20 text-slate-400 border-slate-500/50' : 'bg-slate-800/50 text-slate-600 border-slate-700/30'}">
|
||||
DEBUG
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => toggleLevel('info')}
|
||||
class="px-2 py-1 rounded border transition-colors {selectedLevels.info ? 'bg-blue-500/20 text-blue-400 border-blue-500/50' : 'bg-slate-800/50 text-slate-600 border-slate-700/30'}">
|
||||
INFO
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => toggleLevel('warning')}
|
||||
class="px-2 py-1 rounded border transition-colors {selectedLevels.warning ? 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50' : 'bg-slate-800/50 text-slate-600 border-slate-700/30'}">
|
||||
WARN
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => toggleLevel('error')}
|
||||
class="px-2 py-1 rounded border transition-colors {selectedLevels.error ? 'bg-red-500/20 text-red-400 border-red-500/50' : 'bg-slate-800/50 text-slate-600 border-slate-700/30'}">
|
||||
ERROR
|
||||
</button>
|
||||
|
||||
<span class="ml-auto text-slate-500">{filteredLogs.length} / {logs.length} logs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs container -->
|
||||
<div
|
||||
bind:this={container}
|
||||
class="flex-1 overflow-y-auto p-3 font-mono text-xs"
|
||||
style="min-height: 0;">
|
||||
|
||||
{#if filteredLogs.length === 0}
|
||||
<div class="text-center py-8 text-slate-400">
|
||||
<svg class="w-12 h-12 mx-auto mb-3 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p class="text-sm">
|
||||
{logs.length === 0 ? 'No logs yet' : 'No logs matching selected levels'}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
{#each filteredLogs as log (log.timestamp + log.message)}
|
||||
<div class="flex gap-3 py-1 hover:bg-slate-800/30 px-2 rounded">
|
||||
<span class="text-slate-500 flex-shrink-0">{log.timestamp}</span>
|
||||
<span class="px-2 py-0.5 rounded border text-xs font-semibold flex-shrink-0 {getLevelBadge(log.level)}">
|
||||
{log.level.toUpperCase()}
|
||||
</span>
|
||||
<span class="{getLevelColor(log.level)} flex-1 break-all">{log.message}</span>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,6 +3,7 @@
|
||||
import StatsTab from './StatsTab.svelte';
|
||||
import WatchlistTab from './WatchlistTab.svelte';
|
||||
import LogTab from './LogTab.svelte';
|
||||
import LogsTab from './LogsTab.svelte';
|
||||
|
||||
export let activeTab;
|
||||
export let topSpotters;
|
||||
@@ -12,6 +13,7 @@
|
||||
export let logStats;
|
||||
export let dxccProgress;
|
||||
export let showOnlyActive = false; // ✅ Export pour persister l'état
|
||||
export let logs = [];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -49,8 +51,18 @@
|
||||
<svg class="w-4 h-4 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
Log
|
||||
Log4OM
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-semibold transition-colors {activeTab === 'logs' ? 'bg-blue-500/20 text-blue-400 border-b-2 border-blue-500' : 'text-slate-400 hover:text-slate-300'}"
|
||||
on:click={() => activeTab = 'logs'}>
|
||||
<svg class="w-4 h-4 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
AppLogs
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
@@ -70,6 +82,8 @@
|
||||
{logStats}
|
||||
{dxccProgress}
|
||||
/>
|
||||
{:else if activeTab === 'logs'}
|
||||
<LogsTab {logs} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +51,6 @@
|
||||
}
|
||||
|
||||
function getCleanComment(spot) {
|
||||
// Retirer le commentaire original brut s'il existe
|
||||
if (!spot.OriginalComment) return '';
|
||||
return spot.OriginalComment.trim();
|
||||
}
|
||||
@@ -67,11 +66,11 @@
|
||||
<div class="flex text-left text-xs text-slate-400 font-semibold">
|
||||
<div class="p-2" style="width: 10%;">DX</div>
|
||||
<div class="p-2" style="width: 18%;">Country</div>
|
||||
<div class="p-2" style="width: 7%;">Time</div>
|
||||
<div class="p-2" style="width: 10%;">Freq</div>
|
||||
<div class="p-2" style="width: 7%;">Band</div>
|
||||
<div class="p-2" style="width: 7%;">Mode</div>
|
||||
<div class="p-2" style="width: 10%;">Spotter</div>
|
||||
<div class="p-2" style="width: 7%;">Time</div>
|
||||
<div class="p-2" style="width: 18%;">Comment</div>
|
||||
<div class="p-2" style="width: 13%;">Status</div>
|
||||
</div>
|
||||
@@ -92,6 +91,7 @@
|
||||
<div class="p-2 flex items-center text-slate-400 text-xs truncate" style="width: 18%;" title={item.CountryName || 'N/A'}>
|
||||
{item.CountryName || 'N/A'}
|
||||
</div>
|
||||
<div class="p-2 flex items-center text-slate-400 text-xs" style="width: 7%;">{item.UTCTime}</div>
|
||||
<div class="p-2 flex items-center font-mono text-xs" style="width: 10%;">{item.FrequencyMhz}</div>
|
||||
<div class="p-2 flex items-center" style="width: 7%;">
|
||||
<span class="px-1.5 py-0.5 bg-slate-700/50 rounded text-xs">{item.Band}</span>
|
||||
@@ -102,7 +102,6 @@
|
||||
<div class="p-2 flex items-center text-slate-300 text-xs truncate" style="width: 10%;" title={item.SpotterCallsign}>
|
||||
{item.SpotterCallsign}
|
||||
</div>
|
||||
<div class="p-2 flex items-center text-slate-400 text-xs" style="width: 7%;">{item.UTCTime}</div>
|
||||
<div class="p-2 flex items-center text-slate-400 text-xs truncate" style="width: 18%;" title={getCleanComment(item)}>
|
||||
{getCleanComment(item)}
|
||||
</div>
|
||||
|
||||
@@ -10,25 +10,49 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-[repeat(4,1fr)_auto] gap-3 mb-3 items-center">
|
||||
<!-- Total Spots -->
|
||||
<div class="grid grid-cols-[repeat(6,1fr)_auto] gap-3 mb-3 items-center">
|
||||
<!-- Spots Received -->
|
||||
<div class="bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
<svg class="w-6 h-6 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||
</svg>
|
||||
<div class="text-xl font-bold text-blue-400">{stats.totalSpots}</div>
|
||||
<div class="text-xl font-bold text-cyan-400">{stats.spotsReceived || 0}</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 mt-1">Total Spots</p>
|
||||
<p class="text-xs text-slate-400 mt-1">Received</p>
|
||||
</div>
|
||||
|
||||
<!-- Spots Processed -->
|
||||
<div class="bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div class="text-xl font-bold text-green-400">{stats.spotsProcessed || 0}</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 mt-1">Processed</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Rate -->
|
||||
<div class="bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="w-6 h-6 {stats.spotSuccessRate >= 95 ? 'text-green-400' : stats.spotSuccessRate >= 80 ? 'text-yellow-400' : 'text-red-400'}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<div class="text-xl font-bold {stats.spotSuccessRate >= 95 ? 'text-green-400' : stats.spotSuccessRate >= 80 ? 'text-yellow-400' : 'text-red-400'}">
|
||||
{stats.spotSuccessRate ? stats.spotSuccessRate.toFixed(1) : '0.0'}%
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 mt-1">Success Rate</p>
|
||||
</div>
|
||||
|
||||
<!-- New DXCC -->
|
||||
<div class="bg-slate-800/50 backdrop-blur rounded-lg p-3 border border-slate-700/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-6 h-6 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div class="text-xl font-bold text-green-400">{stats.newDXCC}</div>
|
||||
<div class="text-xl font-bold text-emerald-400">{stats.newDXCC}</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 mt-1">New DXCC</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user