From b8884d89e38fee39499f0b46bb366d81071578da Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 14 Jan 2026 17:35:07 +0100 Subject: [PATCH] updated frontend --- internal/devices/flexradio/flexradio.go | 91 +++- web/src/App.svelte | 27 +- web/src/components/PowerGenius.svelte | 11 - web/src/components/StatusBanner.svelte | 625 ++++++++++++++++++++++++ web/src/components/TunerGenius.svelte | 11 - 5 files changed, 719 insertions(+), 46 deletions(-) create mode 100644 web/src/components/StatusBanner.svelte diff --git a/internal/devices/flexradio/flexradio.go b/internal/devices/flexradio/flexradio.go index 7e1fafe..d8d7642 100644 --- a/internal/devices/flexradio/flexradio.go +++ b/internal/devices/flexradio/flexradio.go @@ -32,6 +32,9 @@ type Client struct { // Callbacks onFrequencyChange func(freqMHz float64) checkTransmitAllowed func() bool // Returns true if transmit allowed (motors not moving) + + // Reconnection settings + reconnectInterval time.Duration } func New(host string, port int) *Client { @@ -42,6 +45,7 @@ func New(host string, port int) *Client { lastStatus: &Status{ Connected: false, }, + reconnectInterval: 5 * time.Second, // Reconnect every 5 seconds if disconnected } } @@ -68,6 +72,7 @@ func (c *Client) Connect() error { conn, err := net.DialTimeout("tcp", addr, 5*time.Second) if err != nil { + log.Printf("FlexRadio: Connection failed: %v", err) return fmt.Errorf("failed to connect: %w", err) } @@ -83,30 +88,37 @@ func (c *Client) Start() error { return nil } - if err := c.Connect(); err != nil { - return err - } - - // Update connected status - c.statusMu.Lock() - if c.lastStatus != nil { - c.lastStatus.Connected = true - } - c.statusMu.Unlock() - c.running = true - // Start message listener + // Try initial connection but don't fail if it doesn't work + // The messageLoop will handle reconnection + err := c.Connect() + if err != nil { + log.Printf("FlexRadio: Initial connection failed, will retry: %v", err) + } else { + // Update connected status + c.statusMu.Lock() + if c.lastStatus != nil { + c.lastStatus.Connected = true + } + c.statusMu.Unlock() + + // Subscribe to slice updates for frequency tracking + c.subscribeToSlices() + } + + // Start message listener (handles reconnection) go c.messageLoop() - // Subscribe to slice updates for frequency tracking + return nil +} + +func (c *Client) subscribeToSlices() { log.Println("FlexRadio: Subscribing to slice updates...") _, err := c.sendCommand("sub slice all") if err != nil { log.Printf("FlexRadio: Warning - failed to subscribe to slices: %v", err) } - - return nil } func (c *Client) Stop() { @@ -173,15 +185,52 @@ func (c *Client) sendCommand(cmd string) (string, error) { func (c *Client) messageLoop() { log.Println("FlexRadio: Message loop started") + reconnectTicker := time.NewTicker(c.reconnectInterval) + defer reconnectTicker.Stop() + for c.running { + c.connMu.Lock() + isConnected := c.conn != nil && c.reader != nil + c.connMu.Unlock() + + if !isConnected { + // Update status to disconnected + c.statusMu.Lock() + if c.lastStatus != nil { + c.lastStatus.Connected = false + } + c.statusMu.Unlock() + + // Wait for reconnect interval + select { + case <-reconnectTicker.C: + log.Println("FlexRadio: Attempting to reconnect...") + if err := c.Connect(); err != nil { + log.Printf("FlexRadio: Reconnect failed: %v", err) + continue + } + + // Successfully reconnected + c.statusMu.Lock() + if c.lastStatus != nil { + c.lastStatus.Connected = true + } + c.statusMu.Unlock() + + // Re-subscribe to slices after reconnection + c.subscribeToSlices() + + case <-c.stopChan: + log.Println("FlexRadio: Message loop stopping (stop signal received)") + return + } + continue + } + + // Read from connection c.connMu.Lock() if c.conn == nil || c.reader == nil { c.connMu.Unlock() - time.Sleep(1 * time.Second) - if err := c.Connect(); err != nil { - log.Printf("FlexRadio: Reconnect failed: %v", err) - continue - } continue } @@ -211,6 +260,8 @@ func (c *Client) messageLoop() { c.lastStatus.Connected = false } c.statusMu.Unlock() + + log.Println("FlexRadio: Connection lost, will attempt reconnection...") continue } diff --git a/web/src/App.svelte b/web/src/App.svelte index 2bd383f..9f0e9c6 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -2,6 +2,7 @@ import { onMount, onDestroy } from 'svelte'; import { wsService, connected, systemStatus } from './lib/websocket.js'; import { api } from './lib/api.js'; + import StatusBanner from './components/StatusBanner.svelte'; import WebSwitch from './components/WebSwitch.svelte'; import PowerGenius from './components/PowerGenius.svelte'; import TunerGenius from './components/TunerGenius.svelte'; @@ -13,6 +14,8 @@ let isConnected = false; let currentTime = new Date(); let callsign = 'F4BPO'; // Default + let latitude = null; + let longitude = null; const unsubscribeStatus = systemStatus.subscribe(value => { status = value; @@ -40,6 +43,10 @@ if (config.callsign) { callsign = config.callsign; } + if (config.location) { + latitude = config.location.latitude; + longitude = config.location.longitude; + } } catch (err) { console.error('Failed to fetch config:', err); } @@ -107,6 +114,16 @@ + + +
@@ -132,12 +149,13 @@ } header { - background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); - padding: 16px 24px; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); + padding: 8px 24px; display: flex; justify-content: space-between; align-items: center; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + border-bottom: 1px solid rgba(79, 195, 247, 0.2); flex-wrap: wrap; gap: 16px; } @@ -243,6 +261,7 @@ .date { font-size: 12px; color: rgba(255, 255, 255, 0.7); + padding-top: 0px; } main { @@ -292,4 +311,4 @@ flex-wrap: wrap; } } - + \ No newline at end of file diff --git a/web/src/components/PowerGenius.svelte b/web/src/components/PowerGenius.svelte index 6f3f80c..b0a25f0 100644 --- a/web/src/components/PowerGenius.svelte +++ b/web/src/components/PowerGenius.svelte @@ -73,7 +73,6 @@
-
@@ -282,16 +281,6 @@ transition: width 0.3s ease; } - .power-bar-glow { - position: absolute; - top: 0; - right: 0; - width: 20px; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5)); - animation: shimmer 2s infinite; - } - @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } diff --git a/web/src/components/StatusBanner.svelte b/web/src/components/StatusBanner.svelte new file mode 100644 index 0000000..065d082 --- /dev/null +++ b/web/src/components/StatusBanner.svelte @@ -0,0 +1,625 @@ + + +
+ +
+
+ 📻 +
+ + {#if connected && frequency > 0} +
+ + {formatFrequency(frequency)} + + MHz +
+ + {#if currentBand} + + {currentBand} + + {/if} + + {#if mode} + + {mode} + + {/if} + + {#if txEnabled} + + TX + + {/if} + {:else} + FlexRadio non connecté + {/if} +
+ + +
+ + +
+ {#if latitude && longitude} +
+ + + + + + + + + + + + {formatTime(sunrise)} + + + + + + + + + + + + + {formatTime(sunset)} + +
+ + {#if isGrayline} + + ✨ GRAYLINE + + {:else if timeToNextEvent} + + {timeToNextEvent} + + {/if} + {:else} + 📍 Position non configurée + {/if} +
+ + +
+ + +
+ {#if hasWindWarning} +
+ ⚠️ + + Vent: {windSpeed.toFixed(0)} km/h + +
+ {/if} + + {#if hasGustWarning} +
+ 🌪️ + + Rafales: {windGust.toFixed(0)} km/h + +
+ {/if} + + {#if !hasAnyWarning} +
+ + Météo OK +
+ {/if} +
+
+ + \ No newline at end of file diff --git a/web/src/components/TunerGenius.svelte b/web/src/components/TunerGenius.svelte index 5dbf8d8..558ee24 100644 --- a/web/src/components/TunerGenius.svelte +++ b/web/src/components/TunerGenius.svelte @@ -67,7 +67,6 @@
-
@@ -265,16 +264,6 @@ transition: width 0.3s ease; } - .power-bar-glow { - position: absolute; - top: 0; - right: 0; - width: 20px; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5)); - animation: shimmer 2s infinite; - } - @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); }