From 02b93a3ef0d93243f46b1d9cd18fcb741e4ca303 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Thu, 1 Jan 2026 17:58:06 +0700 Subject: [PATCH] Handle connecting/disconnecting states --- cmd/repeater/static/app.js | 138 +++++++++++++++++++++++++--------- cmd/repeater/static/style.css | 42 +++++++++++ internal/app/app.go | 14 ++-- internal/models/models.go | 1 + internal/wifi/wifi.go | 9 +++ openapi.yaml | 11 +++ 6 files changed, 175 insertions(+), 40 deletions(-) diff --git a/cmd/repeater/static/app.js b/cmd/repeater/static/app.js index 3bb7f82..a7c367d 100644 --- a/cmd/repeater/static/app.js +++ b/cmd/repeater/static/app.js @@ -9,6 +9,7 @@ const appState = { wifiReconnectAttempts: 0, maxReconnectAttempts: 5, connectedSSID: null, + connectionState: 'disconnected', networks: [], uptime: 0, uptimeInterval: null @@ -104,10 +105,11 @@ async function connectToWifi() { const password = document.getElementById('wifiPassword').value; - if (appState.selectedWifi.security !== 'Open' && !password) { - showToast('warning', 'Attention', 'Mot de passe requis pour ce réseau'); - return; - } + // Password requirement disabled + // if (appState.selectedWifi.security !== 'Open' && !password) { + // showToast('warning', 'Attention', 'Mot de passe requis pour ce réseau'); + // return; + // } showLoading(true); @@ -250,12 +252,32 @@ function updateStatusDisplay(status) { const wifiDot = wifiStatus.querySelector('.status-dot'); const wifiText = wifiStatus.querySelector('.status-text'); - if (status.connected) { - wifiDot.className = 'status-dot active'; - wifiText.textContent = `Connecté: ${status.connectedSSID}`; - } else { - wifiDot.className = 'status-dot offline'; - wifiText.textContent = 'Déconnecté'; + // Use connectionState for more detailed status + const state = status.connectionState || (status.connected ? 'connected' : 'disconnected'); + appState.connectionState = state; + + switch (state) { + case 'connected': + wifiDot.className = 'status-dot active'; + wifiText.textContent = `Connecté: ${status.connectedSSID}`; + break; + case 'connecting': + wifiDot.className = 'status-dot connecting'; + wifiText.textContent = status.connectedSSID ? `Connexion à ${status.connectedSSID}...` : 'Connexion...'; + break; + case 'disconnecting': + wifiDot.className = 'status-dot disconnecting'; + wifiText.textContent = 'Déconnexion...'; + break; + case 'roaming': + wifiDot.className = 'status-dot active'; + wifiText.textContent = `Roaming: ${status.connectedSSID}`; + break; + case 'disconnected': + default: + wifiDot.className = 'status-dot offline'; + wifiText.textContent = 'Déconnecté'; + break; } // Update hotspot status badge @@ -282,11 +304,14 @@ function updateStatusDisplay(status) { // Update hotspot details if available updateHotspotDetails(status.hotspotStatus); - // Check if connectedSSID changed and refresh WiFi list if needed - const connectedSSIDChanged = appState.connectedSSID !== status.connectedSSID; + // Check if connectedSSID or state changed and refresh WiFi list if needed + const prevSSID = appState.connectedSSID; + const prevState = appState.connectionState; appState.connectedSSID = status.connectedSSID; - if (connectedSSIDChanged && appState.networks.length > 0) { + const connectedChanged = prevSSID !== status.connectedSSID || prevState !== state; + + if (connectedChanged && appState.networks.length > 0) { displayWifiNetworks(appState.networks); } @@ -314,9 +339,20 @@ function displayWifiNetworks(networks) { const wifiItem = document.createElement('div'); wifiItem.className = 'wifi-item'; - // Mark the currently connected network + // Mark the network based on connection state if (appState.connectedSSID && network.ssid === appState.connectedSSID) { - wifiItem.classList.add('connected'); + switch (appState.connectionState) { + case 'connected': + case 'roaming': + wifiItem.classList.add('connected'); + break; + case 'connecting': + wifiItem.classList.add('connecting'); + break; + case 'disconnecting': + wifiItem.classList.add('disconnecting'); + break; + } } wifiItem.onclick = () => selectWifi(network, wifiItem); @@ -576,27 +612,61 @@ function handleStateChange(data) { const wifiDot = wifiStatus.querySelector('.status-dot'); const wifiText = wifiStatus.querySelector('.status-text'); - if (data.state === 'connected') { - wifiDot.className = 'status-dot active'; - wifiText.textContent = `Connecté: ${data.ssid}`; - appState.connectedSSID = data.ssid; + // Update state in appState + appState.connectionState = data.state; - // Refresh network list to show connected network - if (appState.networks.length > 0) { - displayWifiNetworks(appState.networks); - } - } else if (data.state === 'disconnected') { - wifiDot.className = 'status-dot offline'; - wifiText.textContent = 'Déconnecté'; - appState.connectedSSID = null; + switch (data.state) { + case 'connected': + wifiDot.className = 'status-dot active'; + wifiText.textContent = `Connecté: ${data.ssid}`; + appState.connectedSSID = data.ssid; - // Refresh network list to remove connected highlighting - if (appState.networks.length > 0) { - displayWifiNetworks(appState.networks); - } - } else if (data.state === 'connecting') { - wifiDot.className = 'status-dot'; - wifiText.textContent = `Connexion à ${data.ssid}...`; + // Refresh network list to show connected network + if (appState.networks.length > 0) { + displayWifiNetworks(appState.networks); + } + break; + + case 'connecting': + wifiDot.className = 'status-dot connecting'; + wifiText.textContent = data.ssid ? `Connexion à ${data.ssid}...` : 'Connexion...'; + + // Refresh network list to show connecting state + if (appState.networks.length > 0) { + displayWifiNetworks(appState.networks); + } + break; + + case 'disconnecting': + wifiDot.className = 'status-dot disconnecting'; + wifiText.textContent = 'Déconnexion...'; + + // Refresh network list to show disconnecting state + if (appState.networks.length > 0) { + displayWifiNetworks(appState.networks); + } + break; + + case 'roaming': + wifiDot.className = 'status-dot active'; + wifiText.textContent = `Roaming: ${data.ssid}`; + appState.connectedSSID = data.ssid; + break; + + case 'disconnected': + wifiDot.className = 'status-dot offline'; + wifiText.textContent = 'Déconnecté'; + appState.connectedSSID = null; + + // Refresh network list to remove connected highlighting + if (appState.networks.length > 0) { + displayWifiNetworks(appState.networks); + } + break; + + default: + console.warn('Unknown WiFi state:', data.state); + break; } console.log(`WiFi state changed: ${data.previous_state} → ${data.state}`, data.ssid); diff --git a/cmd/repeater/static/style.css b/cmd/repeater/static/style.css index 26464cd..b518696 100644 --- a/cmd/repeater/static/style.css +++ b/cmd/repeater/static/style.css @@ -107,11 +107,26 @@ body { animation: none; } +.status-dot.connecting { + background: var(--warning-color); + animation: blink 1s ease-in-out infinite; +} + +.status-dot.disconnecting { + background: var(--warning-color); + animation: blink 1s ease-in-out infinite; +} + @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + /* Stats Grid */ .stats-grid { display: grid; @@ -428,6 +443,33 @@ body { font-weight: 700; } +.wifi-item.connecting { + background: #fef3c7; + border-left-color: var(--warning-color) !important; + animation: pulse-item 1.5s ease-in-out infinite; +} + +.wifi-item.connecting .wifi-ssid { + color: var(--warning-color); + font-weight: 700; +} + +.wifi-item.disconnecting { + background: #fee2e2; + border-left-color: #dc2626 !important; + animation: pulse-item 1.5s ease-in-out infinite; +} + +.wifi-item.disconnecting .wifi-ssid { + color: #dc2626; + font-weight: 700; +} + +@keyframes pulse-item { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + .wifi-item.loading { justify-content: center; color: var(--text-secondary); diff --git a/internal/app/app.go b/internal/app/app.go index de556da..d8e792b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -31,12 +31,13 @@ type App struct { func New(assets embed.FS) *App { return &App{ Status: models.SystemStatus{ - Connected: false, - ConnectedSSID: "", - HotspotStatus: nil, - ConnectedCount: 0, - DataUsage: 0.0, - Uptime: 0, + Connected: false, + ConnectionState: "disconnected", + ConnectedSSID: "", + HotspotStatus: nil, + ConnectedCount: 0, + DataUsage: 0.0, + Uptime: 0, }, StartTime: time.Now(), Assets: assets, @@ -136,6 +137,7 @@ func (a *App) periodicStatusUpdate() { for range ticker.C { a.StatusMutex.Lock() a.Status.Connected = wifi.IsConnected() + a.Status.ConnectionState = wifi.GetConnectionState() a.Status.ConnectedSSID = wifi.GetConnectedSSID() a.Status.Uptime = getSystemUptime() diff --git a/internal/models/models.go b/internal/models/models.go index b1500d1..64d5c79 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -41,6 +41,7 @@ type HotspotStatus struct { // SystemStatus represents overall system status type SystemStatus struct { Connected bool `json:"connected"` + ConnectionState string `json:"connectionState"` // Connection state: connected, disconnected, connecting, disconnecting, roaming ConnectedSSID string `json:"connectedSSID"` HotspotStatus *HotspotStatus `json:"hotspotStatus,omitempty"` // Detailed hotspot status ConnectedCount int `json:"connectedCount"` diff --git a/internal/wifi/wifi.go b/internal/wifi/wifi.go index b242e2a..ee678c1 100644 --- a/internal/wifi/wifi.go +++ b/internal/wifi/wifi.go @@ -241,6 +241,15 @@ func GetConnectedSSID() string { return props.Name } +// GetConnectionState returns the current WiFi connection state +func GetConnectionState() string { + state, err := station.GetState() + if err != nil { + return string(iwd.StateDisconnected) + } + return string(state) +} + // StartEventMonitoring initializes D-Bus signal monitoring and WebSocket broadcasting func StartEventMonitoring() error { // Initialize broadcaster diff --git a/openapi.yaml b/openapi.yaml index c357310..4357d09 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -454,6 +454,16 @@ components: type: boolean description: Whether router is connected to upstream WiFi example: true + connectionState: + type: string + description: Current WiFi connection state + enum: + - connected + - disconnected + - connecting + - disconnecting + - roaming + example: "connected" connectedSSID: type: string description: SSID of connected upstream network (empty if not connected) @@ -485,6 +495,7 @@ components: $ref: '#/components/schemas/ConnectedDevice' required: - connected + - connectionState - connectedSSID - connectedCount - dataUsage