Skip to content

Commit ba53fa5

Browse files
authored
feat: restore dev console link and show gateway token (#15)
1 parent 5eea27d commit ba53fa5

3 files changed

Lines changed: 171 additions & 21 deletions

File tree

src/components/layout/Sidebar.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,21 @@ export function Sidebar() {
6464
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
6565
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
6666

67-
const openDevConsole = () => {
68-
window.electron.openExternal('http://localhost:18789');
67+
const openDevConsole = async () => {
68+
try {
69+
const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as {
70+
success: boolean;
71+
url?: string;
72+
error?: string;
73+
};
74+
if (result.success && result.url) {
75+
window.electron.openExternal(result.url);
76+
} else {
77+
console.error('Failed to get Dev Console URL:', result.error);
78+
}
79+
} catch (err) {
80+
console.error('Error opening Dev Console:', err);
81+
}
6982
};
7083

7184
const navItems = [

src/pages/Dashboard/index.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Clock,
1212
Settings,
1313
Plus,
14+
Terminal,
1415
} from 'lucide-react';
1516
import { Link } from 'react-router-dom';
1617
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -19,12 +20,14 @@ import { Badge } from '@/components/ui/badge';
1920
import { useGatewayStore } from '@/stores/gateway';
2021
import { useChannelsStore } from '@/stores/channels';
2122
import { useSkillsStore } from '@/stores/skills';
23+
import { useSettingsStore } from '@/stores/settings';
2224
import { StatusBadge } from '@/components/common/StatusBadge';
2325

2426
export function Dashboard() {
2527
const gatewayStatus = useGatewayStore((state) => state.status);
2628
const { channels, fetchChannels } = useChannelsStore();
2729
const { skills, fetchSkills } = useSkillsStore();
30+
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
2831

2932
const isGatewayRunning = gatewayStatus.state === 'running';
3033
const [uptime, setUptime] = useState(0);
@@ -59,6 +62,23 @@ export function Dashboard() {
5962

6063
return () => clearInterval(interval);
6164
}, [gatewayStatus.connectedAt]);
65+
66+
const openDevConsole = async () => {
67+
try {
68+
const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as {
69+
success: boolean;
70+
url?: string;
71+
error?: string;
72+
};
73+
if (result.success && result.url) {
74+
window.electron.openExternal(result.url);
75+
} else {
76+
console.error('Failed to get Dev Console URL:', result.error);
77+
}
78+
} catch (err) {
79+
console.error('Error opening Dev Console:', err);
80+
}
81+
};
6282

6383
return (
6484
<div className="space-y-6">
@@ -159,6 +179,16 @@ export function Dashboard() {
159179
<span>Settings</span>
160180
</Link>
161181
</Button>
182+
{devModeUnlocked && (
183+
<Button
184+
variant="outline"
185+
className="h-auto flex-col gap-2 py-4"
186+
onClick={openDevConsole}
187+
>
188+
<Terminal className="h-5 w-5" />
189+
<span>Dev Console</span>
190+
</Button>
191+
)}
162192
</div>
163193
</CardContent>
164194
</Card>

src/pages/Settings/index.tsx

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Settings Page
33
* Application configuration
44
*/
5+
import { useState } from 'react';
56
import {
67
Sun,
78
Moon,
@@ -11,18 +12,26 @@ import {
1112
ExternalLink,
1213
Key,
1314
Download,
15+
Copy,
1416
} from 'lucide-react';
1517
import { Button } from '@/components/ui/button';
1618
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
1719
import { Label } from '@/components/ui/label';
1820
import { Switch } from '@/components/ui/switch';
1921
import { Separator } from '@/components/ui/separator';
2022
import { Badge } from '@/components/ui/badge';
23+
import { Input } from '@/components/ui/input';
24+
import { toast } from 'sonner';
2125
import { useSettingsStore } from '@/stores/settings';
2226
import { useGatewayStore } from '@/stores/gateway';
2327
import { useUpdateStore } from '@/stores/update';
2428
import { ProvidersSettings } from '@/components/settings/ProvidersSettings';
2529
import { UpdateSettings } from '@/components/settings/UpdateSettings';
30+
type ControlUiInfo = {
31+
url: string;
32+
token: string;
33+
port: number;
34+
};
2635

2736
export function Settings() {
2837
const {
@@ -35,16 +44,60 @@ export function Settings() {
3544
autoDownloadUpdate,
3645
setAutoDownloadUpdate,
3746
devModeUnlocked,
47+
setDevModeUnlocked,
3848
} = useSettingsStore();
3949

4050
const { status: gatewayStatus, restart: restartGateway } = useGatewayStore();
4151
const currentVersion = useUpdateStore((state) => state.currentVersion);
52+
const [controlUiInfo, setControlUiInfo] = useState<ControlUiInfo | null>(null);
4253

4354
// Open developer console
44-
const openDevConsole = () => {
45-
window.electron.openExternal('http://localhost:18789');
55+
const openDevConsole = async () => {
56+
try {
57+
const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as {
58+
success: boolean;
59+
url?: string;
60+
token?: string;
61+
port?: number;
62+
error?: string;
63+
};
64+
if (result.success && result.url && result.token && typeof result.port === 'number') {
65+
setControlUiInfo({ url: result.url, token: result.token, port: result.port });
66+
window.electron.openExternal(result.url);
67+
} else {
68+
console.error('Failed to get Dev Console URL:', result.error);
69+
}
70+
} catch (err) {
71+
console.error('Error opening Dev Console:', err);
72+
}
4673
};
47-
74+
75+
const refreshControlUiInfo = async () => {
76+
try {
77+
const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as {
78+
success: boolean;
79+
url?: string;
80+
token?: string;
81+
port?: number;
82+
};
83+
if (result.success && result.url && result.token && typeof result.port === 'number') {
84+
setControlUiInfo({ url: result.url, token: result.token, port: result.port });
85+
}
86+
} catch {
87+
// Ignore refresh errors
88+
}
89+
};
90+
91+
const handleCopyGatewayToken = async () => {
92+
if (!controlUiInfo?.token) return;
93+
try {
94+
await navigator.clipboard.writeText(controlUiInfo.token);
95+
toast.success('Gateway token copied');
96+
} catch (error) {
97+
toast.error(`Failed to copy token: ${String(error)}`);
98+
}
99+
};
100+
48101
return (
49102
<div className="space-y-6 p-6">
50103
<div>
@@ -198,31 +251,85 @@ export function Settings() {
198251
</div>
199252
</CardContent>
200253
</Card>
254+
255+
{/* Advanced */}
256+
<Card>
257+
<CardHeader>
258+
<CardTitle>Advanced</CardTitle>
259+
<CardDescription>Power-user options</CardDescription>
260+
</CardHeader>
261+
<CardContent className="space-y-4">
262+
<div className="flex items-center justify-between">
263+
<div>
264+
<Label>Developer Mode</Label>
265+
<p className="text-sm text-muted-foreground">
266+
Show developer tools and shortcuts
267+
</p>
268+
</div>
269+
<Switch
270+
checked={devModeUnlocked}
271+
onCheckedChange={setDevModeUnlocked}
272+
/>
273+
</div>
274+
</CardContent>
275+
</Card>
201276

202277
{/* Developer */}
203278
{devModeUnlocked && (
204279
<Card>
205280
<CardHeader>
206281
<CardTitle>Developer</CardTitle>
207-
<CardDescription>Advanced options for developers</CardDescription>
208-
</CardHeader>
209-
<CardContent className="space-y-4">
210-
<div className="space-y-2">
211-
<Label>OpenClaw Console</Label>
282+
<CardDescription>Advanced options for developers</CardDescription>
283+
</CardHeader>
284+
<CardContent className="space-y-4">
285+
<div className="space-y-2">
286+
<Label>OpenClaw Console</Label>
287+
<p className="text-sm text-muted-foreground">
288+
Access the native OpenClaw management interface
289+
</p>
290+
<Button variant="outline" onClick={openDevConsole}>
291+
<Terminal className="h-4 w-4 mr-2" />
292+
Open Developer Console
293+
<ExternalLink className="h-3 w-3 ml-2" />
294+
</Button>
295+
<p className="text-xs text-muted-foreground">
296+
Opens the Control UI with gateway token injected
297+
</p>
298+
<div className="space-y-2 pt-2">
299+
<Label>Gateway Token</Label>
212300
<p className="text-sm text-muted-foreground">
213-
Access the native OpenClaw management interface
214-
</p>
215-
<Button variant="outline" onClick={openDevConsole}>
216-
<Terminal className="h-4 w-4 mr-2" />
217-
Open Developer Console
218-
<ExternalLink className="h-3 w-3 ml-2" />
219-
</Button>
220-
<p className="text-xs text-muted-foreground">
221-
Opens http://localhost:18789 in your browser
301+
Paste this into Control UI settings if prompted
222302
</p>
303+
<div className="flex gap-2">
304+
<Input
305+
readOnly
306+
value={controlUiInfo?.token || ''}
307+
placeholder="Token unavailable"
308+
className="font-mono"
309+
/>
310+
<Button
311+
type="button"
312+
variant="outline"
313+
onClick={refreshControlUiInfo}
314+
disabled={!devModeUnlocked}
315+
>
316+
<RefreshCw className="h-4 w-4 mr-2" />
317+
Load
318+
</Button>
319+
<Button
320+
type="button"
321+
variant="outline"
322+
onClick={handleCopyGatewayToken}
323+
disabled={!controlUiInfo?.token}
324+
>
325+
<Copy className="h-4 w-4 mr-2" />
326+
Copy
327+
</Button>
328+
</div>
223329
</div>
224-
</CardContent>
225-
</Card>
330+
</div>
331+
</CardContent>
332+
</Card>
226333
)}
227334

228335
{/* About */}

0 commit comments

Comments
 (0)