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 */}