Просмотр исходного кода

Revert /dev/mem guard (broke LEDs), simplify update dialog

The /dev/mem permission check was too strict — rpi_ws281x can also use
/dev/gpiomem which the gpio group provides. Revert the guard to restore
LED functionality. Also remove auto-polling from update dialog and ask
the user to reload manually instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 месяцев назад
Родитель
Сommit
1a0b6b98b5

+ 8 - 49
frontend/src/components/UpdateDialog.tsx

@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback, useRef } from 'react'
+import { useState, useEffect, useRef } from 'react'
 import { apiClient } from '@/lib/apiClient'
 import { Button } from '@/components/ui/button'
 import {
@@ -32,32 +32,17 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
   const [state, setState] = useState<UpdateState>('confirming')
   const [errorMessage, setErrorMessage] = useState('')
   const [messageIndex, setMessageIndex] = useState(0)
-  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null)
   const messageRef = useRef<ReturnType<typeof setInterval> | null>(null)
 
-  const cleanup = useCallback(() => {
-    if (pollRef.current) {
-      clearInterval(pollRef.current)
-      pollRef.current = null
-    }
-    if (messageRef.current) {
-      clearInterval(messageRef.current)
-      messageRef.current = null
-    }
-  }, [])
-
   // Reset state when dialog closes
   useEffect(() => {
     if (!open) {
-      cleanup()
+      if (messageRef.current) clearInterval(messageRef.current)
       setState('confirming')
       setErrorMessage('')
       setMessageIndex(0)
     }
-  }, [open, cleanup])
-
-  // Cleanup on unmount
-  useEffect(() => cleanup, [cleanup])
+  }, [open])
 
   // Rotate fun messages every 4 seconds while updating
   useEffect(() => {
@@ -70,30 +55,6 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
     }
   }, [state])
 
-  // Poll backend every 3 seconds while updating
-  useEffect(() => {
-    if (state !== 'updating') return
-
-    // Wait a few seconds before starting to poll (give the service time to go down)
-    const startDelay = setTimeout(() => {
-      pollRef.current = setInterval(async () => {
-        try {
-          await apiClient.request('/api/version')
-          // Backend is back — reload the page
-          cleanup()
-          window.location.reload()
-        } catch {
-          // Still down, keep polling
-        }
-      }, 3000)
-    }, 5000)
-
-    return () => {
-      clearTimeout(startDelay)
-      if (pollRef.current) clearInterval(pollRef.current)
-    }
-  }, [state, cleanup])
-
   const handleUpdate = async () => {
     setState('updating')
     setMessageIndex(0)
@@ -111,14 +72,12 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
     }
   }
 
-  const isUpdating = state === 'updating'
-
   return (
-    <Dialog open={open} onOpenChange={isUpdating ? undefined : onOpenChange}>
+    <Dialog open={open} onOpenChange={state === 'confirming' ? onOpenChange : undefined}>
       <DialogContent
-        onPointerDownOutside={isUpdating ? (e) => e.preventDefault() : undefined}
-        onEscapeKeyDown={isUpdating ? (e) => e.preventDefault() : undefined}
-        className={isUpdating ? '[&>button:last-child]:hidden' : ''}
+        onPointerDownOutside={state === 'updating' ? (e) => e.preventDefault() : undefined}
+        onEscapeKeyDown={state === 'updating' ? (e) => e.preventDefault() : undefined}
+        className={state === 'updating' ? '[&>button:last-child]:hidden' : ''}
       >
         {state === 'confirming' && (
           <>
@@ -163,7 +122,7 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
               <p className="text-sm text-muted-foreground">
                 This usually takes 1-2 minutes.
                 <br />
-                The page will reload automatically.
+                Please reload the page after a couple of minutes.
               </p>
             </div>
           </div>

+ 0 - 9
modules/led/dw_led_controller.py

@@ -2,7 +2,6 @@
 Dune Weaver LED Controller - Embedded NeoPixel LED controller for Raspberry Pi
 Provides direct GPIO control of WS2812B LED strips with beautiful effects
 """
-import os
 import threading
 import time
 import logging
@@ -60,14 +59,6 @@ class DWLEDController:
         if self._initialized:
             return True
 
-        # Guard: the rpi_ws281x C library will SEGV if it can't access /dev/mem.
-        # Python try/except can't catch a segfault, so check permissions first.
-        if os.path.exists('/dev/mem') and not os.access('/dev/mem', os.R_OK | os.W_OK):
-            error_msg = "Cannot access /dev/mem — NeoPixel requires root permissions. Run the service with sudo."
-            self._init_error = error_msg
-            logger.warning(error_msg)
-            return False
-
         # Try standard NeoPixel library first (works on Pi 4 and earlier)
         # If that fails, fall back to Pi 5-specific library
         neopixel_module = None

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/dist/assets/index-C7Snmecn.js


+ 1 - 1
static/dist/index.html

@@ -58,7 +58,7 @@
           .catch(function() {});
       })();
     </script>
-    <script type="module" crossorigin src="/assets/index-Dbujgl2S.js"></script>
+    <script type="module" crossorigin src="/assets/index-C7Snmecn.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-UpWUtbXD.css">
   <script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
   <body>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/dist/sw.js


Некоторые файлы не были показаны из-за большого количества измененных файлов