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

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 { apiClient } from '@/lib/apiClient'
 import { Button } from '@/components/ui/button'
 import { Button } from '@/components/ui/button'
 import {
 import {
@@ -32,32 +32,17 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
   const [state, setState] = useState<UpdateState>('confirming')
   const [state, setState] = useState<UpdateState>('confirming')
   const [errorMessage, setErrorMessage] = useState('')
   const [errorMessage, setErrorMessage] = useState('')
   const [messageIndex, setMessageIndex] = useState(0)
   const [messageIndex, setMessageIndex] = useState(0)
-  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null)
   const messageRef = 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
   // Reset state when dialog closes
   useEffect(() => {
   useEffect(() => {
     if (!open) {
     if (!open) {
-      cleanup()
+      if (messageRef.current) clearInterval(messageRef.current)
       setState('confirming')
       setState('confirming')
       setErrorMessage('')
       setErrorMessage('')
       setMessageIndex(0)
       setMessageIndex(0)
     }
     }
-  }, [open, cleanup])
-
-  // Cleanup on unmount
-  useEffect(() => cleanup, [cleanup])
+  }, [open])
 
 
   // Rotate fun messages every 4 seconds while updating
   // Rotate fun messages every 4 seconds while updating
   useEffect(() => {
   useEffect(() => {
@@ -70,30 +55,6 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
     }
     }
   }, [state])
   }, [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 () => {
   const handleUpdate = async () => {
     setState('updating')
     setState('updating')
     setMessageIndex(0)
     setMessageIndex(0)
@@ -111,14 +72,12 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
     }
     }
   }
   }
 
 
-  const isUpdating = state === 'updating'
-
   return (
   return (
-    <Dialog open={open} onOpenChange={isUpdating ? undefined : onOpenChange}>
+    <Dialog open={open} onOpenChange={state === 'confirming' ? onOpenChange : undefined}>
       <DialogContent
       <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' && (
         {state === 'confirming' && (
           <>
           <>
@@ -163,7 +122,7 @@ export function UpdateDialog({ open, onOpenChange, currentVersion, latestVersion
               <p className="text-sm text-muted-foreground">
               <p className="text-sm text-muted-foreground">
                 This usually takes 1-2 minutes.
                 This usually takes 1-2 minutes.
                 <br />
                 <br />
-                The page will reload automatically.
+                Please reload the page after a couple of minutes.
               </p>
               </p>
             </div>
             </div>
           </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
 Dune Weaver LED Controller - Embedded NeoPixel LED controller for Raspberry Pi
 Provides direct GPIO control of WS2812B LED strips with beautiful effects
 Provides direct GPIO control of WS2812B LED strips with beautiful effects
 """
 """
-import os
 import threading
 import threading
 import time
 import time
 import logging
 import logging
@@ -60,14 +59,6 @@ class DWLEDController:
         if self._initialized:
         if self._initialized:
             return True
             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)
         # Try standard NeoPixel library first (works on Pi 4 and earlier)
         # If that fails, fall back to Pi 5-specific library
         # If that fails, fall back to Pi 5-specific library
         neopixel_module = None
         neopixel_module = None

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


+ 1 - 1
static/dist/index.html

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

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


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