diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 66e62efcc..bfc7a1668 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -64,8 +64,21 @@ export function Sidebar() { const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked); - const openDevConsole = () => { - window.electron.openExternal('http://localhost:18789'); + const openDevConsole = async () => { + try { + const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as { + success: boolean; + url?: string; + error?: string; + }; + if (result.success && result.url) { + window.electron.openExternal(result.url); + } else { + console.error('Failed to get Dev Console URL:', result.error); + } + } catch (err) { + console.error('Error opening Dev Console:', err); + } }; const navItems = [ diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 24e9ed936..031fcccdb 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -11,6 +11,7 @@ import { Clock, Settings, Plus, + Terminal, } from 'lucide-react'; import { Link } from 'react-router-dom'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; @@ -19,12 +20,14 @@ import { Badge } from '@/components/ui/badge'; import { useGatewayStore } from '@/stores/gateway'; import { useChannelsStore } from '@/stores/channels'; import { useSkillsStore } from '@/stores/skills'; +import { useSettingsStore } from '@/stores/settings'; import { StatusBadge } from '@/components/common/StatusBadge'; export function Dashboard() { const gatewayStatus = useGatewayStore((state) => state.status); const { channels, fetchChannels } = useChannelsStore(); const { skills, fetchSkills } = useSkillsStore(); + const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked); const isGatewayRunning = gatewayStatus.state === 'running'; const [uptime, setUptime] = useState(0); @@ -59,6 +62,23 @@ export function Dashboard() { return () => clearInterval(interval); }, [gatewayStatus.connectedAt]); + + const openDevConsole = async () => { + try { + const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as { + success: boolean; + url?: string; + error?: string; + }; + if (result.success && result.url) { + window.electron.openExternal(result.url); + } else { + console.error('Failed to get Dev Console URL:', result.error); + } + } catch (err) { + console.error('Error opening Dev Console:', err); + } + }; return (
@@ -159,6 +179,16 @@ export function Dashboard() { Settings + {devModeUnlocked && ( + + )}
diff --git a/src/pages/Settings/index.tsx b/src/pages/Settings/index.tsx index 49f7cc1d8..5ab9651e2 100644 --- a/src/pages/Settings/index.tsx +++ b/src/pages/Settings/index.tsx @@ -2,6 +2,7 @@ * Settings Page * Application configuration */ +import { useState } from 'react'; import { Sun, Moon, @@ -11,6 +12,7 @@ import { ExternalLink, Key, Download, + Copy, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; @@ -18,11 +20,18 @@ import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { toast } from 'sonner'; import { useSettingsStore } from '@/stores/settings'; import { useGatewayStore } from '@/stores/gateway'; import { useUpdateStore } from '@/stores/update'; import { ProvidersSettings } from '@/components/settings/ProvidersSettings'; import { UpdateSettings } from '@/components/settings/UpdateSettings'; +type ControlUiInfo = { + url: string; + token: string; + port: number; +}; export function Settings() { const { @@ -35,16 +44,60 @@ export function Settings() { autoDownloadUpdate, setAutoDownloadUpdate, devModeUnlocked, + setDevModeUnlocked, } = useSettingsStore(); const { status: gatewayStatus, restart: restartGateway } = useGatewayStore(); const currentVersion = useUpdateStore((state) => state.currentVersion); + const [controlUiInfo, setControlUiInfo] = useState(null); // Open developer console - const openDevConsole = () => { - window.electron.openExternal('http://localhost:18789'); + const openDevConsole = async () => { + try { + const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as { + success: boolean; + url?: string; + token?: string; + port?: number; + error?: string; + }; + if (result.success && result.url && result.token && typeof result.port === 'number') { + setControlUiInfo({ url: result.url, token: result.token, port: result.port }); + window.electron.openExternal(result.url); + } else { + console.error('Failed to get Dev Console URL:', result.error); + } + } catch (err) { + console.error('Error opening Dev Console:', err); + } }; - + + const refreshControlUiInfo = async () => { + try { + const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as { + success: boolean; + url?: string; + token?: string; + port?: number; + }; + if (result.success && result.url && result.token && typeof result.port === 'number') { + setControlUiInfo({ url: result.url, token: result.token, port: result.port }); + } + } catch { + // Ignore refresh errors + } + }; + + const handleCopyGatewayToken = async () => { + if (!controlUiInfo?.token) return; + try { + await navigator.clipboard.writeText(controlUiInfo.token); + toast.success('Gateway token copied'); + } catch (error) { + toast.error(`Failed to copy token: ${String(error)}`); + } + }; + return (
@@ -198,31 +251,85 @@ export function Settings() {
+ + {/* Advanced */} + + + Advanced + Power-user options + + +
+
+ +

+ Show developer tools and shortcuts +

+
+ +
+
+
{/* Developer */} {devModeUnlocked && ( Developer - Advanced options for developers - - -
- + Advanced options for developers + + +
+ +

+ Access the native OpenClaw management interface +

+ +

+ Opens the Control UI with gateway token injected +

+
+

- Access the native OpenClaw management interface -

- -

- Opens http://localhost:18789 in your browser + Paste this into Control UI settings if prompted

+
+ + + +
- - +
+
+ )} {/* About */}