Sfoglia il codice sorgente

add shutdown button

tuanchris 3 mesi fa
parent
commit
64a6c7e56c
2 ha cambiato i file con 128 aggiunte e 1 eliminazioni
  1. 74 0
      main.py
  2. 54 1
      templates/base.html

+ 74 - 0
main.py

@@ -30,6 +30,8 @@ import time
 import argparse
 from concurrent.futures import ProcessPoolExecutor
 import multiprocessing
+import subprocess
+import platform
 
 # Get log level from environment variable, default to INFO
 log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
@@ -1659,6 +1661,78 @@ async def trigger_update():
             status_code=500
         )
 
+@app.get("/api/system/check_pi")
+async def check_pi():
+    """Check if the system is a Raspberry Pi"""
+    try:
+        # Check if running on ARM architecture (Raspberry Pi indicator)
+        is_arm = platform.machine().startswith('arm') or platform.machine().startswith('aarch')
+
+        # Additional check: look for Raspberry Pi specific files
+        is_pi_file = os.path.exists('/proc/device-tree/model')
+        if is_pi_file:
+            with open('/proc/device-tree/model', 'r') as f:
+                model = f.read()
+                is_raspberry_pi = 'Raspberry Pi' in model
+        else:
+            is_raspberry_pi = False
+
+        # System is considered Pi if either check passes
+        is_pi = is_arm or is_raspberry_pi
+
+        return {"is_pi": is_pi}
+    except Exception as e:
+        logger.error(f"Error checking if system is Pi: {e}")
+        return {"is_pi": False}
+
+@app.post("/api/system/shutdown")
+async def shutdown_system():
+    """Shutdown the Raspberry Pi system"""
+    try:
+        # Double-check it's a Pi before allowing shutdown
+        check_result = await check_pi()
+        if not check_result["is_pi"]:
+            return JSONResponse(
+                content={"success": False, "message": "Shutdown only available on Raspberry Pi"},
+                status_code=403
+            )
+
+        logger.warning("Shutdown initiated via API")
+
+        # Run docker compose down in background
+        try:
+            # Get the directory where main.py is located
+            app_dir = os.path.dirname(os.path.abspath(__file__))
+            subprocess.Popen(
+                ["docker", "compose", "down"],
+                cwd=app_dir,
+                stdout=subprocess.DEVNULL,
+                stderr=subprocess.DEVNULL
+            )
+            logger.info("Docker compose down command issued")
+        except Exception as e:
+            logger.error(f"Error running docker compose down: {e}")
+
+        # Schedule shutdown command after a short delay to allow response to be sent
+        def delayed_shutdown():
+            time.sleep(2)  # Give time for response to be sent
+            try:
+                subprocess.run(["sudo", "shutdown", "-h", "now"], check=True)
+            except Exception as e:
+                logger.error(f"Error executing shutdown command: {e}")
+
+        import threading
+        shutdown_thread = threading.Thread(target=delayed_shutdown)
+        shutdown_thread.start()
+
+        return {"success": True, "message": "System shutdown initiated"}
+    except Exception as e:
+        logger.error(f"Error initiating shutdown: {e}")
+        return JSONResponse(
+            content={"success": False, "message": str(e)},
+            status_code=500
+        )
+
 def entrypoint():
     import uvicorn
     logger.info("Starting FastAPI server on port 8080...")

+ 54 - 1
templates/base.html

@@ -110,6 +110,12 @@
       .dark .bg-white {
         background-color: #262626;
       }
+      .dark #shutdown-button:hover {
+        background-color: #404040;
+      }
+      .dark #theme-toggle:hover {
+        background-color: #404040;
+      }
       .dark .bg-gray-50 {
         background-color: #1a1a1a;
       }
@@ -262,7 +268,15 @@
             </h1>
             </a>
           </div>
-          <div class="flex items-center gap-4">
+          <div class="flex items-center gap-2">
+            <button
+              id="shutdown-button"
+              class="p-1.5 flex rounded-lg hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-red-500 hidden"
+              aria-label="Shutdown system"
+              title="Shutdown Raspberry Pi"
+            >
+              <span class="material-icons text-red-600">power_settings_new</span>
+            </button>
             <button
               id="theme-toggle"
               class="p-1.5 flex rounded-lg hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -678,6 +692,45 @@
           updateThemeIcon();
         });
       });
+
+      // Shutdown button functionality
+      document.addEventListener('DOMContentLoaded', async function() {
+        const shutdownButton = document.getElementById('shutdown-button');
+
+        // Check if system is a Raspberry Pi
+        try {
+          const response = await fetch('/api/system/check_pi');
+          const data = await response.json();
+
+          if (data.is_pi) {
+            shutdownButton.classList.remove('hidden');
+          }
+        } catch (error) {
+          console.error('Error checking Pi status:', error);
+        }
+
+        // Shutdown button click handler
+        shutdownButton.addEventListener('click', async () => {
+          const confirmed = confirm('Are you sure you want to shutdown the Raspberry Pi?\n\nThis will:\n1. Stop Docker containers\n2. Shut down the system\n\nYou will need physical access to restart it.');
+
+          if (!confirmed) return;
+
+          try {
+            showStatusMessage('Initiating shutdown...', 'warning');
+
+            const response = await fetch('/api/system/shutdown', { method: 'POST' });
+            const data = await response.json();
+
+            if (data.success) {
+              showStatusMessage('System is shutting down...', 'success');
+            } else {
+              showStatusMessage('Shutdown failed: ' + data.message, 'error');
+            }
+          } catch (error) {
+            showStatusMessage('Failed to shutdown: ' + error.message, 'error');
+          }
+        });
+      });
     </script>
 
     <!-- Cache All Previews Prompt Modal -->