update log4om
This commit is contained in:
10
TCPClient.go
10
TCPClient.go
@@ -206,6 +206,11 @@ func (c *TCPClient) SetFilters() {
|
|||||||
Log.Info("FT4: On")
|
Log.Info("FT4: On")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Cfg.Cluster.Beacon {
|
||||||
|
c.Write([]byte("set/beacon\r\n"))
|
||||||
|
Log.Info("Beacon: On")
|
||||||
|
}
|
||||||
|
|
||||||
if !Cfg.Cluster.FT8 {
|
if !Cfg.Cluster.FT8 {
|
||||||
c.Write([]byte("set/noft8\r\n"))
|
c.Write([]byte("set/noft8\r\n"))
|
||||||
Log.Info("FT8: Off")
|
Log.Info("FT8: Off")
|
||||||
@@ -220,6 +225,11 @@ func (c *TCPClient) SetFilters() {
|
|||||||
c.Write([]byte("set/noskimmer\r\n"))
|
c.Write([]byte("set/noskimmer\r\n"))
|
||||||
Log.Info("Skimmer: Off")
|
Log.Info("Skimmer: Off")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !Cfg.Cluster.Beacon {
|
||||||
|
c.Write([]byte("set/nobeacon\r\n"))
|
||||||
|
Log.Info("Beacon: Off")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TCPClient) ReadLine() {
|
func (c *TCPClient) ReadLine() {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
general:
|
general:
|
||||||
delete_log_file_at_start: true
|
delete_log_file_at_start: true
|
||||||
callsign: XXXXX # Log4OM Callsign used to check if you get spotted by someone
|
callsign: XXXXX # Log4OM Callsign used to check if you get spotted by someone
|
||||||
|
QRALocator: JN36dg
|
||||||
log_to_file: true
|
log_to_file: true
|
||||||
log_level: INFO # INFO or DEBUG or WARN
|
log_level: INFO # INFO or DEBUG or WARN
|
||||||
telnetserver: true # not in use for now
|
telnetserver: true # not in use for now
|
||||||
flexradiospot: true # not in use for now
|
flexradiospot: true # not in use for now
|
||||||
|
sendFreqModeToLog4OM: false # if not using a Flex then turn this on to send Freq and Mode to Log4OM which should in turn change the freq on the radio
|
||||||
# Spot colors, if empty then default, colors in HEX AARRGGBB format
|
# Spot colors, if empty then default, colors in HEX AARRGGBB format
|
||||||
spot_color_new_entity:
|
spot_color_new_entity:
|
||||||
background_color_new_entity:
|
background_color_new_entity:
|
||||||
@@ -38,6 +40,7 @@ cluster:
|
|||||||
skimmer: true
|
skimmer: true
|
||||||
ft8: false
|
ft8: false
|
||||||
ft4: false
|
ft4: false
|
||||||
|
beacon: false
|
||||||
command: "SET/FILTER DOC/PASS 1A,3A,4O,9A,9H,C3,CT,CU,DL,E7,EA,EA6,EI,ER,ES,EU,F,G,GD,GI,GJ,GM,GU,GW,HA,HB,HB0,HV,I,IS,IT9,JW,JX,LA,LX,LY,LZ,OE,OH,OH0,OJ0,OK,OM,ON,OY,OZ,PA,S5,SM,SP,SV,SV5,SV9,T7,TA1,TF,TK,UA,UR,YL,YO,YU,Z6,Z3" #"SET/FILTER DOC/PASS 1A,3A,4O,9A,9H,C3,CT,CU,DL,E7,EA,EA6,EI,ER,ES,EU,F,G,GD,GI,GJ,GM,GU,GW,HA,HB,HB0,HV,I,IS,IT9,JW,JX,LA,LX,LY,LZ,OE,OH,OH0,OJ0,OK,OM,ON,OY,OZ,PA,S5,SM,SP,SV,SV5,SV9,T7,TA1,TF,TK,UA,UR,YL,YO,YU,Z6,Z3,ZA,ZB"
|
command: "SET/FILTER DOC/PASS 1A,3A,4O,9A,9H,C3,CT,CU,DL,E7,EA,EA6,EI,ER,ES,EU,F,G,GD,GI,GJ,GM,GU,GW,HA,HB,HB0,HV,I,IS,IT9,JW,JX,LA,LX,LY,LZ,OE,OH,OH0,OJ0,OK,OM,ON,OY,OZ,PA,S5,SM,SP,SV,SV5,SV9,T7,TA1,TF,TK,UA,UR,YL,YO,YU,Z6,Z3" #"SET/FILTER DOC/PASS 1A,3A,4O,9A,9H,C3,CT,CU,DL,E7,EA,EA6,EI,ER,ES,EU,F,G,GD,GI,GJ,GM,GU,GW,HA,HB,HB0,HV,I,IS,IT9,JW,JX,LA,LX,LY,LZ,OE,OH,OH0,OJ0,OK,OM,ON,OY,OZ,PA,S5,SM,SP,SV,SV5,SV9,T7,TA1,TF,TK,UA,UR,YL,YO,YU,Z6,Z3,ZA,ZB"
|
||||||
login_prompt: "login:"
|
login_prompt: "login:"
|
||||||
flex:
|
flex:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Config struct {
|
|||||||
TelnetServer bool `yaml:"telnetserver"`
|
TelnetServer bool `yaml:"telnetserver"`
|
||||||
FlexRadioSpot bool `yaml:"flexradiospot"`
|
FlexRadioSpot bool `yaml:"flexradiospot"`
|
||||||
SpotColorNewEntity string `yaml:"spot_color_new_entity"`
|
SpotColorNewEntity string `yaml:"spot_color_new_entity"`
|
||||||
|
sendFreqModeToLog4OM bool `yaml:"sendFreqModeToLog4OM"`
|
||||||
BackgroundColorNewEntity string `yaml:"background_color_new_entity"`
|
BackgroundColorNewEntity string `yaml:"background_color_new_entity"`
|
||||||
SpotColorNewBand string `yaml:"spot_color_new_band"`
|
SpotColorNewBand string `yaml:"spot_color_new_band"`
|
||||||
BackgroundColorNewBand string `yaml:"background_color_new_band"`
|
BackgroundColorNewBand string `yaml:"background_color_new_band"`
|
||||||
@@ -56,6 +57,7 @@ type Config struct {
|
|||||||
Skimmer bool `yaml:"skimmer"`
|
Skimmer bool `yaml:"skimmer"`
|
||||||
FT8 bool `yaml:"ft8"`
|
FT8 bool `yaml:"ft8"`
|
||||||
FT4 bool `yaml:"ft4"`
|
FT4 bool `yaml:"ft4"`
|
||||||
|
Beacon bool `yaml:"beacon"`
|
||||||
Command string `yaml:"command"`
|
Command string `yaml:"command"`
|
||||||
LoginPrompt string `yaml:"login_prompt"`
|
LoginPrompt string `yaml:"login_prompt"`
|
||||||
} `yaml:"cluster"`
|
} `yaml:"cluster"`
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ func (fc *FlexClient) ReadLine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sending the callsign to Log4OM
|
// Sending the callsign to Log4OM
|
||||||
SendUDPMessage("<CALLSIGN>" + spot.DX)
|
SendUDPMessage([]byte("<CALLSIGN>" + spot.DX))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status when a spot is deleted
|
// Status when a spot is deleted
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
// spot-worker.js - Web Worker pour traiter les spots
|
// spot-worker.js - Web Worker pour traiter les spots
|
||||||
|
|
||||||
self.onmessage = function(e) {
|
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) {
|
switch(type) {
|
||||||
case 'FILTER_SPOTS':
|
case 'FILTER_SPOTS':
|
||||||
const filtered = filterSpots(data.spots, data.filters, data.watchlist);
|
const filtered = filterSpots(data.spots, data.filters, data.watchlist);
|
||||||
// ✅ AJOUTER messageId à la réponse
|
|
||||||
self.postMessage({ type: 'FILTERED_SPOTS', data: filtered, messageId });
|
self.postMessage({ type: 'FILTERED_SPOTS', data: filtered, messageId });
|
||||||
|
lastProcessedData = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'SORT_SPOTS':
|
case 'SORT_SPOTS':
|
||||||
const sorted = sortSpots(data.spots, data.sortBy, data.sortOrder);
|
const sorted = sortSpots(data.spots, data.sortBy, data.sortOrder);
|
||||||
// ✅ AJOUTER messageId à la réponse
|
|
||||||
self.postMessage({ type: 'SORTED_SPOTS', data: sorted, messageId });
|
self.postMessage({ type: 'SORTED_SPOTS', data: sorted, messageId });
|
||||||
|
lastProcessedData = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -78,13 +78,27 @@
|
|||||||
$: {
|
$: {
|
||||||
if (spotFilters.showAll) {
|
if (spotFilters.showAll) {
|
||||||
filteredSpots = spots;
|
filteredSpots = spots;
|
||||||
|
isFiltering = false;
|
||||||
|
if (filterTimeout) {
|
||||||
|
clearTimeout(filterTimeout);
|
||||||
|
filterTimeout = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (filterTimeout) clearTimeout(filterTimeout);
|
if (filterTimeout) {
|
||||||
|
clearTimeout(filterTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
filterTimeout = setTimeout(async () => {
|
filterTimeout = setTimeout(async () => {
|
||||||
isFiltering = true;
|
isFiltering = true;
|
||||||
|
try {
|
||||||
filteredSpots = await spotWorker.filterSpots(spots, spotFilters, watchlist);
|
filteredSpots = await spotWorker.filterSpots(spots, spotFilters, watchlist);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Filter error:', error);
|
||||||
|
filteredSpots = spots;
|
||||||
|
} finally {
|
||||||
isFiltering = false;
|
isFiltering = false;
|
||||||
|
filterTimeout = null;
|
||||||
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +234,7 @@
|
|||||||
wsStatus = 'connected';
|
wsStatus = 'connected';
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
showToast('Connected to server', 'success');
|
showToast('✅ Connected to DX Cluster', 'connection');
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
@@ -273,9 +287,9 @@
|
|||||||
case 'spots':
|
case 'spots':
|
||||||
const newSpots = message.data || [];
|
const newSpots = message.data || [];
|
||||||
|
|
||||||
|
// Détecter si votre indicatif a été spotté
|
||||||
if (stats.myCallsign && newSpots.length > 0) {
|
if (stats.myCallsign && newSpots.length > 0) {
|
||||||
newSpots.forEach(spot => {
|
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)) {
|
if (spot.DX === stats.myCallsign && !notifiedSpots.has(spot.ID)) {
|
||||||
notifiedSpots.add(spot.ID);
|
notifiedSpots.add(spot.ID);
|
||||||
showToast(
|
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);
|
const arr = Array.from(notifiedSpots);
|
||||||
notifiedSpots = new Set(arr.slice(-100));
|
notifiedSpots = new Set(arr.slice(-200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spots = newSpots;
|
spots = newSpots;
|
||||||
|
|
||||||
|
// ✅ Debounce la sauvegarde du cache (toutes les 30 secondes max)
|
||||||
if (spots.length > 0) {
|
if (spots.length > 0) {
|
||||||
|
if (window.cacheSaveTimeout) clearTimeout(window.cacheSaveTimeout);
|
||||||
|
window.cacheSaveTimeout = setTimeout(() => {
|
||||||
spotCache.saveSpots(spots).catch(err => console.error('Cache save error:', err));
|
spotCache.saveSpots(spots).catch(err => console.error('Cache save error:', err));
|
||||||
|
window.cacheSaveTimeout = null; // ✅ Nettoyer la référence
|
||||||
|
}, 30000); // 30 secondes
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'spotters':
|
case 'spotters':
|
||||||
@@ -359,13 +379,13 @@
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showToast(`${callsign} Sent - Radio tuned on ${frequency} in ${mode}`, 'success');
|
showToast(`📻 Tuned to ${callsign} • ${frequency} • ${mode}`, 'radio');
|
||||||
} else {
|
} else {
|
||||||
showToast('Failed to send', 'error');
|
showToast('❌ Failed to send to radio', 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending callsign:', 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();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
stats.filters[filterName] = value;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error updating filter:', 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);
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
wsStatus = 'disconnected';
|
wsStatus = 'disconnected';
|
||||||
|
|
||||||
showToast('FlexDXCluster shutting down...', 'info');
|
showToast('⚡ Shutting down FlexDXCluster...', 'warning');
|
||||||
|
|
||||||
// ✅ Envoyer la commande de shutdown au backend
|
// ✅ Envoyer la commande de shutdown au backend
|
||||||
const response = await fetch('/api/shutdown', {
|
const response = await fetch('/api/shutdown', {
|
||||||
@@ -434,7 +456,7 @@ async function shutdownApp() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error shutting down:', error);
|
console.error('Error shutting down:', error);
|
||||||
if (!isShuttingDown) {
|
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);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-2">
|
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-2">
|
||||||
|
|||||||
@@ -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>
|
<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>
|
<span class="text-sm font-medium">FT4</span>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
export let message;
|
export let message;
|
||||||
export let type = 'info'; // 'success', 'error', 'warning', 'info', 'milestone', 'band'
|
export let type = 'info';
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
success: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>`,
|
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>`,
|
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>`,
|
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>`,
|
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 = {
|
const colors = {
|
||||||
success: 'bg-green-500',
|
success: 'from-green-500 to-emerald-600',
|
||||||
error: 'bg-red-500',
|
error: 'from-red-500 to-rose-600',
|
||||||
warning: 'bg-orange-500',
|
warning: 'from-orange-500 to-amber-600',
|
||||||
info: 'bg-blue-500',
|
info: 'from-blue-500 to-cyan-600',
|
||||||
milestone: 'bg-gradient-to-r from-purple-500 to-pink-500',
|
milestone: 'from-purple-500 to-pink-500',
|
||||||
band: 'bg-gradient-to-r from-orange-500 to-amber-500',
|
band: 'from-orange-500 to-amber-500',
|
||||||
mycall: 'bg-gradient-to-r from-red-500 to-pink-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>
|
</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="fixed bottom-5 right-5 z-50 animate-in slide-in-from-bottom-5 duration-300" class:opacity-0={!visible}>
|
||||||
<div class="flex items-center gap-3">
|
<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]}
|
{#if icons[type]}
|
||||||
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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]}
|
{@html icons[type]}
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="font-medium text-sm">{message}</span>
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="font-semibold text-sm leading-relaxed break-words">{message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,6 +63,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.animate-in {
|
.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>
|
</style>
|
||||||
@@ -57,15 +57,25 @@ class SpotCache {
|
|||||||
const transaction = this.db.transaction(['spots'], 'readwrite');
|
const transaction = this.db.transaction(['spots'], 'readwrite');
|
||||||
const store = transaction.objectStore('spots');
|
const store = transaction.objectStore('spots');
|
||||||
|
|
||||||
// Vider d'abord le store
|
// ✅ Vider d'abord le store
|
||||||
await store.clear();
|
await store.clear();
|
||||||
|
|
||||||
// Ajouter tous les spots avec un timestamp
|
// ✅ Sauvegarder par batch de 100 pour éviter surcharge mémoire
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
spots.forEach(spot => {
|
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 });
|
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);
|
await this.waitForTransaction(transaction);
|
||||||
console.log(`✅ Saved ${spots.length} spots to cache`);
|
console.log(`✅ Saved ${spots.length} spots to cache`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -44,7 +44,17 @@ class SpotWorkerManager {
|
|||||||
|
|
||||||
const messageId = ++this.messageId;
|
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) => {
|
this.callbacks.set(messageId, (filteredSpots) => {
|
||||||
|
clearTimeout(timeoutId); // ✅ Nettoyer le timeout
|
||||||
resolve(filteredSpots);
|
resolve(filteredSpots);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +76,17 @@ class SpotWorkerManager {
|
|||||||
|
|
||||||
const messageId = ++this.messageId;
|
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) => {
|
this.callbacks.set(messageId, (sortedSpots) => {
|
||||||
|
clearTimeout(timeoutId); // ✅ Nettoyer le timeout
|
||||||
resolve(sortedSpots);
|
resolve(sortedSpots);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -12,6 +12,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -21,6 +21,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -58,6 +59,7 @@ type Filters struct {
|
|||||||
Skimmer bool `json:"skimmer"`
|
Skimmer bool `json:"skimmer"`
|
||||||
FT8 bool `json:"ft8"`
|
FT8 bool `json:"ft8"`
|
||||||
FT4 bool `json:"ft4"`
|
FT4 bool `json:"ft4"`
|
||||||
|
Beacon bool `json:"beacon"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIResponse struct {
|
type APIResponse struct {
|
||||||
@@ -91,6 +93,20 @@ type WatchlistSpot struct {
|
|||||||
WorkedBandMode bool `json:"workedBandMode"`
|
WorkedBandMode bool `json:"workedBandMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RemoteControlRequestFreq struct {
|
||||||
|
XMLName xml.Name `xml:"RemoteControlRequest"`
|
||||||
|
MessageId string `xml:"MessageId"`
|
||||||
|
RemoteControlMessage string `xml:"RemoteControlMessage"`
|
||||||
|
Frequency string `xml:"Frequency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteControlRequestMode struct {
|
||||||
|
XMLName xml.Name `xml:"RemoteControlRequest"`
|
||||||
|
MessageId string `xml:"MessageId"`
|
||||||
|
RemoteControlMessage string `xml:"RemoteControlMessage"`
|
||||||
|
Mode string `xml:"Mode"`
|
||||||
|
}
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true // Allow all origins in development
|
return true // Allow all origins in development
|
||||||
@@ -461,6 +477,7 @@ func (s *HTTPServer) calculateStats() Stats {
|
|||||||
Skimmer: Cfg.Cluster.Skimmer,
|
Skimmer: Cfg.Cluster.Skimmer,
|
||||||
FT8: Cfg.Cluster.FT8,
|
FT8: Cfg.Cluster.FT8,
|
||||||
FT4: Cfg.Cluster.FT4,
|
FT4: Cfg.Cluster.FT4,
|
||||||
|
Beacon: Cfg.Cluster.Beacon,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,6 +540,7 @@ type FilterRequest struct {
|
|||||||
Skimmer *bool `json:"skimmer,omitempty"`
|
Skimmer *bool `json:"skimmer,omitempty"`
|
||||||
FT8 *bool `json:"ft8,omitempty"`
|
FT8 *bool `json:"ft8,omitempty"`
|
||||||
FT4 *bool `json:"ft4,omitempty"`
|
FT4 *bool `json:"ft4,omitempty"`
|
||||||
|
Beacon *bool `json:"beacon,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) updateFilters(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) updateFilters(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -564,6 +582,16 @@ func (s *HTTPServer) updateFilters(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Beacon != nil {
|
||||||
|
if *req.Beacon {
|
||||||
|
commands = append(commands, "set/beacon")
|
||||||
|
Cfg.Cluster.Beacon = true
|
||||||
|
} else {
|
||||||
|
commands = append(commands, "set/nobeacon")
|
||||||
|
Cfg.Cluster.Beacon = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, cmd := range commands {
|
for _, cmd := range commands {
|
||||||
s.TCPClient.CmdChan <- cmd
|
s.TCPClient.CmdChan <- cmd
|
||||||
}
|
}
|
||||||
@@ -760,9 +788,39 @@ func (s *HTTPServer) handleSendCallsign(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SendUDPMessage("<CALLSIGN>" + req.Callsign)
|
SendUDPMessage([]byte("<CALLSIGN>" + req.Callsign))
|
||||||
s.Log.Infof("Sent callsign %s to Log4OM via UDP (127.0.0.1:2241)", req.Callsign)
|
s.Log.Infof("Sent callsign %s to Log4OM via UDP (127.0.0.1:2241)", req.Callsign)
|
||||||
|
|
||||||
|
if Cfg.General.sendFreqModeToLog4OM {
|
||||||
|
freqLog4OM := strings.Replace(req.Frequency, ".", "", 1)
|
||||||
|
|
||||||
|
xmlRequestFreq := RemoteControlRequestFreq{
|
||||||
|
MessageId: uuid.New().String(), // Generate a new unique ID
|
||||||
|
RemoteControlMessage: "SetTxFrequency", // Note: Typo matches your required format
|
||||||
|
Frequency: freqLog4OM,
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlRequestMode := RemoteControlRequestMode{
|
||||||
|
MessageId: uuid.New().String(), // Generate a new unique ID
|
||||||
|
RemoteControlMessage: "SetMode", // Note: Typo matches your required format
|
||||||
|
Mode: req.Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlBytesFreq, err := xml.MarshalIndent(xmlRequestFreq, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to marshal XML: %v", err)
|
||||||
|
} else {
|
||||||
|
SendUDPMessage([]byte(xmlBytesFreq))
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlBytesMode, err := xml.MarshalIndent(xmlRequestMode, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to marshal XML: %v", err)
|
||||||
|
} else {
|
||||||
|
SendUDPMessage([]byte(xmlBytesMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.Frequency != "" && s.FlexClient != nil && s.FlexClient.IsConnected {
|
if req.Frequency != "" && s.FlexClient != nil && s.FlexClient.IsConnected {
|
||||||
tuneCmd := fmt.Sprintf("C%v|slice tune 0 %s", CommandNumber, req.Frequency)
|
tuneCmd := fmt.Sprintf("C%v|slice tune 0 %s", CommandNumber, req.Frequency)
|
||||||
s.FlexClient.Write(tuneCmd)
|
s.FlexClient.Write(tuneCmd)
|
||||||
|
|||||||
4
utils.go
4
utils.go
@@ -59,12 +59,12 @@ func CheckSignal(TCPClient *TCPClient, TCPServer *TCPServer, FlexClient *FlexCli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendUDPMessage(message string) {
|
func SendUDPMessage(data []byte) {
|
||||||
conn, err := net.Dial("udp", "127.0.0.1:2241")
|
conn, err := net.Dial("udp", "127.0.0.1:2241")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Some error %v", err)
|
fmt.Printf("Some error %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.Write([]byte(message))
|
conn.Write(data)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user