소스 검색

fix: captive portal "Control Table" opens real browser, add system buttons

- "Control Table" now attempts window.open to escape the captive portal
  webview and open the real browser. Falls back to showing the URL if
  window.open is blocked.
- Added Restart/Shutdown buttons to WiFiSetupPage so users in captive
  portal mode can manage the system without needing the full app menu.
- Applied to both the backend HTML captive page and the React SPA page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 달 전
부모
커밋
c0e3a5f87a

+ 17 - 3
frontend/src/pages/CaptivePortalPage.tsx

@@ -1,12 +1,18 @@
+import { useState } from 'react'
 import { useNavigate } from 'react-router-dom'
 
 export function CaptivePortalPage() {
   const navigate = useNavigate()
+  const [showUrlHint, setShowUrlHint] = useState(false)
 
   const handleControlTable = () => {
-    // Mark that user chose to use the app via hotspot, so Layout stops redirecting back here
-    sessionStorage.setItem('captive-dismissed', '1')
-    navigate('/')
+    // Try to open in real browser (outside captive portal webview)
+    const url = 'http://10.42.0.1'
+    const w = window.open(url, '_blank')
+    if (!w || w.closed) {
+      // window.open blocked — show URL for user to open manually
+      setShowUrlHint(true)
+    }
   }
 
   return (
@@ -30,6 +36,14 @@ export function CaptivePortalPage() {
             Control Table
           </button>
         </div>
+        {showUrlHint && (
+          <div className="rounded-lg bg-muted p-3 text-sm text-muted-foreground">
+            Open your browser and go to:<br />
+            <code className="mt-1 inline-block rounded bg-background px-2 py-0.5 text-foreground select-all">
+              http://10.42.0.1
+            </code>
+          </div>
+        )}
       </div>
     </div>
   )

+ 42 - 0
frontend/src/pages/WiFiSetupPage.tsx

@@ -389,6 +389,48 @@ export function WiFiSetupPage() {
         </>
       )}
 
+      {/* System Controls */}
+      <Card>
+        <CardContent className="pt-4 pb-3 px-4">
+          <div className="flex items-center gap-2 mb-2">
+            <span className="material-icons-outlined text-base text-muted-foreground">settings_power</span>
+            <span className="font-semibold text-sm">System</span>
+          </div>
+          <div className="flex gap-2">
+            <Button
+              variant="outline"
+              size="sm"
+              className="flex-1 h-8"
+              onClick={async () => {
+                if (!confirm('Restart Dune Weaver?')) return
+                try {
+                  await apiClient.post('/api/system/restart')
+                  toast.success('Restarting...')
+                } catch { toast.error('Failed to restart') }
+              }}
+            >
+              <span className="material-icons-outlined text-sm mr-1.5">restart_alt</span>
+              Restart
+            </Button>
+            <Button
+              variant="outline"
+              size="sm"
+              className="flex-1 h-8 text-destructive hover:text-destructive"
+              onClick={async () => {
+                if (!confirm('Shutdown the system?')) return
+                try {
+                  await apiClient.post('/api/system/shutdown')
+                  toast.success('Shutting down...')
+                } catch { toast.error('Failed to shutdown') }
+              }}
+            >
+              <span className="material-icons-outlined text-sm mr-1.5">power_settings_new</span>
+              Shutdown
+            </Button>
+          </div>
+        </CardContent>
+      </Card>
+
       {/* Available Networks */}
       <Card>
         <CardContent className="pt-4 pb-2 px-4">

+ 23 - 3
modules/wifi/router.py

@@ -122,12 +122,17 @@ CAPTIVE_PORTAL_HTML = """<!DOCTYPE html>
         h1 { font-size: 1.5rem; margin-bottom: 0.5rem; font-weight: 600; }
         p { color: #64748b; margin-bottom: 1.5rem; font-size: 0.9rem; }
         .buttons { display: flex; flex-direction: column; gap: 0.75rem; }
-        a { display: block; padding: 0.75rem 2rem; border-radius: 8px;
-            text-decoration: none; font-weight: 500; text-align: center; }
+        a, button { display: block; padding: 0.75rem 2rem; border-radius: 8px;
+            text-decoration: none; font-weight: 500; text-align: center;
+            width: 100%; font-size: 1rem; cursor: pointer; border: none; }
         .btn-primary { background: #2563eb; color: white; }
         .btn-primary:hover { background: #1d4ed8; }
         .btn-secondary { background: #f1f5f9; color: #0f172a; border: 1px solid #e2e8f0; }
         .btn-secondary:hover { background: #e2e8f0; }
+        .url-hint { display: none; margin-top: 1rem; padding: 0.75rem; background: #f1f5f9;
+                    border-radius: 8px; font-size: 0.85rem; color: #475569; }
+        .url-hint code { background: #e2e8f0; padding: 0.15rem 0.4rem; border-radius: 4px;
+                         font-size: 0.9rem; user-select: all; }
     </style>
 </head>
 <body>
@@ -136,9 +141,24 @@ CAPTIVE_PORTAL_HTML = """<!DOCTYPE html>
         <p>What would you like to do?</p>
         <div class="buttons">
             <a href="/wifi-setup" class="btn-primary">Connect to WiFi</a>
-            <a href="/" class="btn-secondary">Control Table</a>
+            <button class="btn-secondary" onclick="openInBrowser()">Control Table</button>
+        </div>
+        <div class="url-hint" id="url-hint">
+            Open your browser and go to:<br>
+            <code>http://10.42.0.1</code>
         </div>
     </div>
+    <script>
+        function openInBrowser() {
+            // Try to open in real browser (outside captive portal webview)
+            var url = 'http://10.42.0.1';
+            var w = window.open(url, '_blank');
+            if (!w || w.closed) {
+                // window.open blocked — show URL for user to open manually
+                document.getElementById('url-hint').style.display = 'block';
+            }
+        }
+    </script>
 </body>
 </html>"""
 

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
static/dist/assets/index-CHzltdTQ.css


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
static/dist/assets/index-iGL2FzQP.css


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
static/dist/assets/index-olRYAOq_.js


+ 2 - 2
static/dist/index.html

@@ -58,8 +58,8 @@
           .catch(function() {});
       })();
     </script>
-    <script type="module" crossorigin src="/assets/index-Dphkqeue.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-iGL2FzQP.css">
+    <script type="module" crossorigin src="/assets/index-olRYAOq_.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-CHzltdTQ.css">
   <script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
   <body>
     <div id="root"></div>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
static/dist/sw.js


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.