From 1e5423c4db00721fc2104fac5636df730ee32966 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 4 Apr 2026 12:52:08 +0200 Subject: [PATCH] popup --- .gitignore | 1 + Makefile | 2 +- cmd/server/icon.ico | Bin 0 -> 4286 bytes cmd/server/main.go | 16 +- cmd/server/resource.syso | Bin 0 -> 4550 bytes cmd/server/web/dist/index.html | 5 +- internal/api/handlers.go | 23 +- internal/services/weather/weather.go | 5 +- web/popup.html | 16 + web/src/App.svelte | 32 +- web/src/PopupApp.svelte | 448 ++++++++++++++++++++++++ web/src/components/AntennaGenius.svelte | 1 + web/src/components/RotatorGenius.svelte | 56 ++- web/src/components/Ultrabeam.svelte | 17 +- web/src/lib/api.js | 2 + web/src/popup.js | 7 + web/vite.config.js | 8 +- 17 files changed, 618 insertions(+), 21 deletions(-) create mode 100644 cmd/server/icon.ico create mode 100644 cmd/server/resource.syso create mode 100644 web/popup.html create mode 100644 web/src/PopupApp.svelte create mode 100644 web/src/popup.js diff --git a/.gitignore b/.gitignore index ad6177e..05da85a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ web/dist/ web/build/ web/.svelte-kit/ web/package-lock.json +cmd/server/web # Logs *.log \ No newline at end of file diff --git a/Makefile b/Makefile index 7987522..31dd268 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ frontend: ## backend: Build le backend Go backend: frontend @echo "Building Go binary..." - cd $(BACKEND_DIR) && go build -ldflags -H=windowsgui . + cd $(BACKEND_DIR) && go build -o ../../SMaster.exe -ldflags -H=windowsgui . @echo "Backend built successfully" ## build: Build complet (frontend + backend) diff --git a/cmd/server/icon.ico b/cmd/server/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..db5da39a1091ed1255d364ed37fc782dd313cde8 GIT binary patch literal 4286 zcmeHL%W@J?5FPhPcm^br1ZH>!210lYgjWm!rQ$PgV%%7krIs4YmAZD}*5B|i+jB0N zazc=Tk;Dy2ozV2{zI|`^>AqFbv|IR%#Weoaet9)5q-k0PfJ!?6s8>H&GYTV&6q>ek zXH}M1=D^8;;81^q7K^z?WfpsjeAau>0GO)1JY`5 zik0+BP4`_vHSUwsZdA^n81nWwE-(ArS8QxQo^QApmPTs+`1?G^1XzYoUf++&y9aUk z{wgb9PLnDRlWd!*z})fe{4=ln$iX+m zZb$}2d=ikU;~H&fJ82KMx*YP&`*pKHxrbPttY50EeGGF7pxnv$)f#PROCPuK#{I~% z&P8m?U|aw*;HVf3G@v!uA?krw)opxn-a z1DJCS*wB`C^mP-{=~F^}nbV`fx|FxQQpG%^jxl@YZ3W8IRj^hDKWR%J>YRJIk@*fC z=GRqyIaBwL`(4B!*U!2!1fAkv8!26ou)`!Qs0^?IRs=xQ2>(AId%#}mnZUJu$xI|8~_c-&&{{mtTD`ADg z*4hRCeGB71d&k7StXT=XBA-5=7;@5xs6JrN_i-lTI{QDfu_6a%P`*A-$@_=89OKU9 z_f5<{^6;z|l~2zMRR`9W@BVeqf4-%qxJN!d)#clZlvER5$wXFEEqJDzc>nU`S2Zk6 q&efDpvKyD*6ZVPo{hy_`-P+i*z^(t%?{e*y`S_C$`~Lv{>+?4ob9(at literal 0 HcmV?d00001 diff --git a/cmd/server/main.go b/cmd/server/main.go index 89c502e..f43f342 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -44,8 +44,11 @@ func main() { log.Fatalf("Failed to start device manager: %v", err) } + // Channel de shutdown partagé entre main et le handler API + shutdownChan := make(chan struct{}) + // Create HTTP server with embedded files - server := api.NewServer(deviceManager, hub, cfg) + server := api.NewServer(deviceManager, hub, cfg, shutdownChan) mux := server.SetupRoutes() // Serve embedded static files @@ -76,12 +79,17 @@ func main() { } }() - // Wait for interrupt signal + // Wait for interrupt signal or API shutdown request quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") + select { + case <-quit: + log.Println("Signal received, shutting down...") + case <-shutdownChan: + log.Println("API shutdown requested, shutting down...") + } + deviceManager.Stop() log.Println("Server stopped") } diff --git a/cmd/server/resource.syso b/cmd/server/resource.syso new file mode 100644 index 0000000000000000000000000000000000000000..0c8ce8be7b6be2bc8d1156b2fead298a3c175d33 GIT binary patch literal 4550 zcmeHLOH&g;5Z(Y6t03gD5WxURmV^ZIAcVXi4!(Jf@kS$XF3R#YE&Z}%%#|m>^DV3)rANEKt+3`gH|9Xa@OZ ztg8~$70PqEib1m+zs=ZmCYc4d?G1N<9!9OC2DW?Kyliw`g>u>HO zKkJqi#MO*9Y4Uot$98Mx9&^aMa?~re#F%U@j>~2WJpr0Lj!}lP?dGt{TY$g0ziLdE zd$3iBc_ho&2T-Q~(v_%3jZucO)Uj)?S&w;EIk%lRXcs^Y*hq~lkmeflsF{uR=|Y|6 zdF*`0{Z8Q--h{0J_C>mYT9TR`l@iV$LAo464xr8zAwyZpQP+-5t4QL7@S0^;*Cd@OGLp-;*Y+3ft+Vkxsp+LG<`={M}78c@$EheexRd zcl~qxX}gBHvJTu$;*LQsF{gR?ANRW0po(B;$9h`fFfk{#@s`F#`hk9l}d^UJ#@hKd8@%X|O2 z>p$<(bZ}JOJ`Ty}XA#NHx+FR=tYX16-NgMDXMSaUvdX#|aZ7CS@_oW{V!i)6Iooz+ z%vxYqzpr;Wc71lD+OOP_`-7W@G+=fJIEQ-tZy?8#ld5U_6MKQ BiF5z} literal 0 HcmV?d00001 diff --git a/cmd/server/web/dist/index.html b/cmd/server/web/dist/index.html index c9ae829..5bde79d 100644 --- a/cmd/server/web/dist/index.html +++ b/cmd/server/web/dist/index.html @@ -7,8 +7,9 @@ - - + + +
diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 08a4aee..c834a17 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "strconv" + "time" "git.rouggy.com/rouggy/ShackMaster/internal/config" "github.com/gorilla/websocket" @@ -15,13 +16,15 @@ type Server struct { hub *Hub config *config.Config upgrader websocket.Upgrader + shutdownChan chan struct{} } -func NewServer(dm *DeviceManager, hub *Hub, cfg *config.Config) *Server { +func NewServer(dm *DeviceManager, hub *Hub, cfg *config.Config, shutdownChan chan struct{}) *Server { return &Server{ deviceManager: dm, hub: hub, config: cfg, + shutdownChan: shutdownChan, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -74,6 +77,9 @@ func (s *Server) SetupRoutes() *http.ServeMux { mux.HandleFunc("/api/power/fanmode", s.handlePowerFanMode) mux.HandleFunc("/api/power/operate", s.handlePowerOperate) + // Shutdown endpoint + mux.HandleFunc("/api/shutdown", s.handleShutdown) + // Note: Static files are now served from embedded FS in main.go return mux @@ -510,6 +516,21 @@ func (s *Server) handleUltrabeamDirection(w http.ResponseWriter, r *http.Request s.sendJSON(w, map[string]string{"status": "ok"}) } +func (s *Server) handleShutdown(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + s.sendJSON(w, map[string]string{"status": "shutting down"}) + + go func() { + time.Sleep(200 * time.Millisecond) + log.Println("Shutdown requested via API") + close(s.shutdownChan) + }() +} + func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) diff --git a/internal/services/weather/weather.go b/internal/services/weather/weather.go index d3797e1..6d68411 100644 --- a/internal/services/weather/weather.go +++ b/internal/services/weather/weather.go @@ -101,13 +101,14 @@ func (c *Client) GetWeatherData() (*WeatherData, error) { } // Convert to our structure + // OWM retourne wind_speed et gust en m/s — conversion en km/h weatherData := &WeatherData{ Temperature: owmData.Main.Temp, FeelsLike: owmData.Main.FeelsLike, Humidity: owmData.Main.Humidity, Pressure: owmData.Main.Pressure, - WindSpeed: owmData.Wind.Speed, - WindGust: owmData.Wind.Gust, + WindSpeed: owmData.Wind.Speed * 3.6, + WindGust: owmData.Wind.Gust * 3.6, WindDeg: owmData.Wind.Deg, Clouds: owmData.Clouds.All, UpdatedAt: time.Unix(owmData.Dt, 0).Format(time.RFC3339), diff --git a/web/popup.html b/web/popup.html new file mode 100644 index 0000000..c8a4b2b --- /dev/null +++ b/web/popup.html @@ -0,0 +1,16 @@ + + + + + + Rotator Control + + + + + + + diff --git a/web/src/App.svelte b/web/src/App.svelte index e3a1791..43ac403 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -71,6 +71,14 @@ return date.toTimeString().slice(0, 8); } + async function shutdown() { + try { + await api.shutdown(); + } catch (e) { + // Connexion coupee apres shutdown, c'est normal + } + } + // Weather data from status $: weatherData = status?.weather || { wind_speed: 0, @@ -83,11 +91,14 @@
-

{callsign} Shack

+

{callsign}'s Shack

{isConnected ? 'Connected' : 'Disconnected'}
+
@@ -183,6 +194,25 @@ border-radius: 16px; } + .shutdown-btn { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + padding: 6px 12px; + background: rgba(239, 68, 68, 0.15); + color: #f87171; + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: 16px; + cursor: pointer; + transition: background 0.2s; + } + + .shutdown-btn:hover { + background: rgba(239, 68, 68, 0.35); + color: #fca5a5; + } + .header-center { flex: 1; display: flex; diff --git a/web/src/PopupApp.svelte b/web/src/PopupApp.svelte new file mode 100644 index 0000000..fdb8dfd --- /dev/null +++ b/web/src/PopupApp.svelte @@ -0,0 +1,448 @@ + + + + + diff --git a/web/src/components/AntennaGenius.svelte b/web/src/components/AntennaGenius.svelte index f0ffe10..4e46ae4 100644 --- a/web/src/components/AntennaGenius.svelte +++ b/web/src/components/AntennaGenius.svelte @@ -165,6 +165,7 @@ h2 { font-size: 14px; font-weight: 600; + line-height: 1; color: var(--accent-cyan); margin: 0; letter-spacing: 0.5px; diff --git a/web/src/components/RotatorGenius.svelte b/web/src/components/RotatorGenius.svelte index 6cec8e9..471341a 100644 --- a/web/src/components/RotatorGenius.svelte +++ b/web/src/components/RotatorGenius.svelte @@ -93,6 +93,27 @@ } // Handle click on compass to set heading + let popupWindow = null; + + function openPopup() { + const features = [ + 'width=380', + 'height=460', + 'toolbar=no', + 'menubar=no', + 'scrollbars=no', + 'resizable=yes', + 'status=no', + 'location=no', + 'popup=yes', + ].join(','); + if (popupWindow && !popupWindow.closed) { + popupWindow.focus(); + return; + } + popupWindow = window.open('/popup.html', 'rotator-popup', features); + } + async function handleCompassClick(event) { const svg = event.currentTarget; const rect = svg.getBoundingClientRect(); @@ -125,7 +146,10 @@

Rotator Genius

- +
+ + +
@@ -385,11 +409,41 @@ h2 { font-size: 14px; font-weight: 600; + line-height: 1; color: var(--accent-cyan); margin: 0; letter-spacing: 0.5px; } + .header-right { + display: flex; + align-items: center; + gap: 8px; + } + + .btn-popup { + width: 24px; + height: 24px; + border: 1px solid rgba(79,195,247,0.3); + border-radius: 4px; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: rgba(79,195,247,0.6); + background: transparent; + padding: 0; + line-height: 1; + transition: all 0.2s; + } + + .btn-popup:hover { + border-color: rgba(79,195,247,0.7); + color: #4fc3f7; + background: rgba(79,195,247,0.1); + } + .status-dot { width: 8px; height: 8px; diff --git a/web/src/components/Ultrabeam.svelte b/web/src/components/Ultrabeam.svelte index d319733..031bdef 100644 --- a/web/src/components/Ultrabeam.svelte +++ b/web/src/components/Ultrabeam.svelte @@ -162,9 +162,9 @@
-

Ultrabeam VL2.3

- -
+
Ultrabeam VL2.3
+ +
@@ -332,7 +332,7 @@ flex-direction: column; } - .card-header { + .card-header { display: flex; justify-content: space-between; align-items: center; @@ -347,10 +347,11 @@ gap: 12px; } - h2 { + .card-title { margin: 0; font-size: 14px; font-weight: 600; + line-height: 1; color: #4fc3f7; letter-spacing: 0.5px; } @@ -363,11 +364,11 @@ } .status-dot { - width: 12px; - height: 12px; + width: 8px; + height: 8px; border-radius: 50%; background: #4caf50; - box-shadow: 0 0 12px rgba(76, 175, 80, 0.8); + box-shadow: 0 0 8px #4caf50; animation: pulse 2s ease-in-out infinite; } diff --git a/web/src/lib/api.js b/web/src/lib/api.js index 26d4d94..fb30866 100644 --- a/web/src/lib/api.js +++ b/web/src/lib/api.js @@ -110,4 +110,6 @@ export const api = { body: JSON.stringify({ direction }), }), }, + // Shutdown + shutdown: () => request('/shutdown', { method: 'POST' }), }; \ No newline at end of file diff --git a/web/src/popup.js b/web/src/popup.js new file mode 100644 index 0000000..f37e3da --- /dev/null +++ b/web/src/popup.js @@ -0,0 +1,7 @@ +import PopupApp from './PopupApp.svelte'; + +const app = new PopupApp({ + target: document.getElementById('popup-app'), +}); + +export default app; diff --git a/web/vite.config.js b/web/vite.config.js index 9add09e..36b225a 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -4,7 +4,13 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ plugins: [svelte()], build: { - outDir: 'dist' + outDir: 'dist', + rollupOptions: { + input: { + main: 'index.html', + popup: 'popup.html', + } + } }, server: { port: 5173,