@chrysb/alphaclaw 0.4.6-beta.4 → 0.4.6-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/public/js/app.js +158 -1073
- package/lib/public/js/components/envars.js +146 -29
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/general/index.js +155 -0
- package/lib/public/js/components/icons.js +52 -0
- package/lib/public/js/components/info-tooltip.js +4 -7
- package/lib/public/js/components/models-tab/index.js +286 -0
- package/lib/public/js/components/models-tab/provider-auth-card.js +369 -0
- package/lib/public/js/components/models-tab/use-models.js +262 -0
- package/lib/public/js/components/models.js +1 -1
- package/lib/public/js/components/providers.js +1 -1
- package/lib/public/js/components/routes/browse-route.js +35 -0
- package/lib/public/js/components/routes/doctor-route.js +21 -0
- package/lib/public/js/components/routes/envars-route.js +11 -0
- package/lib/public/js/components/routes/general-route.js +45 -0
- package/lib/public/js/components/routes/index.js +11 -0
- package/lib/public/js/components/routes/models-route.js +11 -0
- package/lib/public/js/components/routes/providers-route.js +11 -0
- package/lib/public/js/components/routes/route-redirect.js +10 -0
- package/lib/public/js/components/routes/telegram-route.js +11 -0
- package/lib/public/js/components/routes/usage-route.js +15 -0
- package/lib/public/js/components/routes/watchdog-route.js +32 -0
- package/lib/public/js/components/routes/webhooks-route.js +43 -0
- package/lib/public/js/components/sidebar.js +2 -3
- package/lib/public/js/components/tooltip.js +106 -0
- package/lib/public/js/components/usage-tab/constants.js +1 -1
- package/lib/public/js/components/usage-tab/overview-section.js +124 -50
- package/lib/public/js/components/usage-tab/use-usage-tab.js +42 -11
- package/lib/public/js/components/welcome.js +1 -1
- package/lib/public/js/hooks/use-app-shell-controller.js +230 -0
- package/lib/public/js/hooks/use-app-shell-ui.js +112 -0
- package/lib/public/js/hooks/use-browse-navigation.js +193 -0
- package/lib/public/js/hooks/use-hash-location.js +32 -0
- package/lib/public/js/lib/api.js +35 -0
- package/lib/public/js/lib/app-navigation.js +39 -0
- package/lib/public/js/lib/browse-restart-policy.js +28 -0
- package/lib/public/js/lib/browse-route.js +57 -0
- package/lib/public/js/lib/format.js +12 -0
- package/lib/public/js/lib/model-config.js +1 -0
- package/lib/server/auth-profiles.js +291 -53
- package/lib/server/constants.js +24 -8
- package/lib/server/doctor/service.js +0 -3
- package/lib/server/gateway.js +50 -31
- package/lib/server/onboarding/index.js +2 -0
- package/lib/server/onboarding/validation.js +2 -2
- package/lib/server/routes/models.js +214 -2
- package/lib/server/routes/onboarding.js +2 -0
- package/lib/server/routes/system.js +42 -1
- package/lib/server/watchdog.js +14 -1
- package/lib/server.js +6 -0
- package/lib/setup/env.template +1 -0
- package/package.json +1 -1
package/lib/public/js/app.js
CHANGED
|
@@ -1,681 +1,71 @@
|
|
|
1
1
|
import { h, render } from "https://esm.sh/preact";
|
|
2
|
-
import { useState, useEffect
|
|
2
|
+
import { useState, useEffect } from "https://esm.sh/preact/hooks";
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { Router, Route, Switch, useLocation } from "https://esm.sh/wouter-preact";
|
|
5
|
-
import {
|
|
6
|
-
fetchStatus,
|
|
7
|
-
fetchPairings,
|
|
8
|
-
approvePairing,
|
|
9
|
-
rejectPairing,
|
|
10
|
-
fetchDevicePairings,
|
|
11
|
-
approveDevice,
|
|
12
|
-
rejectDevice,
|
|
13
|
-
fetchOnboardStatus,
|
|
14
|
-
fetchAuthStatus,
|
|
15
|
-
logout,
|
|
16
|
-
fetchDashboardUrl,
|
|
17
|
-
updateSyncCron,
|
|
18
|
-
fetchAlphaclawVersion,
|
|
19
|
-
updateAlphaclaw,
|
|
20
|
-
fetchRestartStatus,
|
|
21
|
-
restartGateway,
|
|
22
|
-
fetchWatchdogStatus,
|
|
23
|
-
fetchDoctorStatus,
|
|
24
|
-
triggerWatchdogRepair,
|
|
25
|
-
updateOpenclaw,
|
|
26
|
-
} from "./lib/api.js";
|
|
27
|
-
import { usePolling } from "./hooks/usePolling.js";
|
|
28
|
-
import { Gateway } from "./components/gateway.js";
|
|
29
|
-
import { Channels, ALL_CHANNELS } from "./components/channels.js";
|
|
30
|
-
import { Pairings } from "./components/pairings.js";
|
|
31
|
-
import { DevicePairings } from "./components/device-pairings.js";
|
|
32
|
-
import { Google } from "./components/google/index.js";
|
|
33
|
-
import { Features } from "./components/features.js";
|
|
34
|
-
import { Providers } from "./components/providers.js";
|
|
5
|
+
import { logout } from "./lib/api.js";
|
|
35
6
|
import { Welcome } from "./components/welcome.js";
|
|
36
|
-
import {
|
|
37
|
-
import { Webhooks } from "./components/webhooks.js";
|
|
38
|
-
import { ToastContainer, showToast } from "./components/toast.js";
|
|
39
|
-
import { TelegramWorkspace } from "./components/telegram-workspace/index.js";
|
|
40
|
-
import { ChevronDownIcon } from "./components/icons.js";
|
|
41
|
-
import { UpdateActionButton } from "./components/update-action-button.js";
|
|
7
|
+
import { ToastContainer } from "./components/toast.js";
|
|
42
8
|
import { GlobalRestartBanner } from "./components/global-restart-banner.js";
|
|
43
9
|
import { LoadingSpinner } from "./components/loading-spinner.js";
|
|
44
|
-
import { WatchdogTab } from "./components/watchdog-tab.js";
|
|
45
|
-
import { FileViewer } from "./components/file-viewer/index.js";
|
|
46
10
|
import { AppSidebar } from "./components/sidebar.js";
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
11
|
+
import {
|
|
12
|
+
BrowseRoute,
|
|
13
|
+
DoctorRoute,
|
|
14
|
+
EnvarsRoute,
|
|
15
|
+
GeneralRoute,
|
|
16
|
+
ModelsRoute,
|
|
17
|
+
ProvidersRoute,
|
|
18
|
+
RouteRedirect,
|
|
19
|
+
TelegramRoute,
|
|
20
|
+
UsageRoute,
|
|
21
|
+
WatchdogRoute,
|
|
22
|
+
WebhooksRoute,
|
|
23
|
+
} from "./components/routes/index.js";
|
|
24
|
+
import { useAppShellController } from "./hooks/use-app-shell-controller.js";
|
|
25
|
+
import { useAppShellUi } from "./hooks/use-app-shell-ui.js";
|
|
26
|
+
import { useBrowseNavigation } from "./hooks/use-browse-navigation.js";
|
|
27
|
+
import { getHashRouterPath, useHashLocation } from "./hooks/use-hash-location.js";
|
|
50
28
|
import { readUiSettings, writeUiSettings } from "./lib/ui-settings.js";
|
|
29
|
+
|
|
51
30
|
const html = htm.bind(h);
|
|
52
|
-
const kDefaultUiTab = "general";
|
|
53
|
-
const kDefaultSidebarWidthPx = 220;
|
|
54
|
-
const kSidebarMinWidthPx = 180;
|
|
55
|
-
const kSidebarMaxWidthPx = 460;
|
|
56
|
-
const kBrowseLastPathUiSettingKey = "browseLastPath";
|
|
57
|
-
const kLastMenuRouteUiSettingKey = "lastMenuRoute";
|
|
58
31
|
const kDoctorWarningDismissedUntilUiSettingKey = "doctorWarningDismissedUntilMs";
|
|
59
32
|
const kOneWeekMs = 7 * 24 * 60 * 60 * 1000;
|
|
60
|
-
const kBrowseRestartRequiredRules = [
|
|
61
|
-
{ type: "file", path: "openclaw.json" },
|
|
62
|
-
{ type: "directory", path: "hooks/transforms" },
|
|
63
|
-
];
|
|
64
|
-
const normalizeBrowsePath = (value) => String(value || "").replace(/^\/+|\/+$/g, "");
|
|
65
|
-
const normalizeRestartRulePath = (value) =>
|
|
66
|
-
String(value || "")
|
|
67
|
-
.trim()
|
|
68
|
-
.replace(/^\/+|\/+$/g, "");
|
|
69
|
-
const matchesBrowseRestartRequiredRule = (path, rule) => {
|
|
70
|
-
const normalizedPath = normalizeRestartRulePath(path);
|
|
71
|
-
if (!normalizedPath) return false;
|
|
72
|
-
if (!rule || typeof rule !== "object") return false;
|
|
73
|
-
const type = String(rule.type || "").toLowerCase();
|
|
74
|
-
const targetPath = normalizeRestartRulePath(rule.path);
|
|
75
|
-
if (!targetPath) return false;
|
|
76
|
-
if (type === "directory") {
|
|
77
|
-
return normalizedPath === targetPath || normalizedPath.startsWith(`${targetPath}/`);
|
|
78
|
-
}
|
|
79
|
-
if (type === "file") {
|
|
80
|
-
return normalizedPath === targetPath;
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
};
|
|
84
|
-
const shouldRequireRestartForBrowsePath = (path) =>
|
|
85
|
-
kBrowseRestartRequiredRules.some((rule) => matchesBrowseRestartRequiredRule(path, rule));
|
|
86
|
-
|
|
87
|
-
const clampSidebarWidth = (value) =>
|
|
88
|
-
Math.max(kSidebarMinWidthPx, Math.min(kSidebarMaxWidthPx, value));
|
|
89
|
-
|
|
90
|
-
const getHashPath = () => {
|
|
91
|
-
const hash = window.location.hash.replace(/^#/, "");
|
|
92
|
-
if (!hash) return `/${kDefaultUiTab}`;
|
|
93
|
-
return hash.startsWith("/") ? hash : `/${hash}`;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const useHashLocation = () => {
|
|
97
|
-
const [location, setLocationState] = useState(getHashPath);
|
|
98
|
-
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
const onHashChange = () => setLocationState(getHashPath());
|
|
101
|
-
window.addEventListener("hashchange", onHashChange);
|
|
102
|
-
return () => window.removeEventListener("hashchange", onHashChange);
|
|
103
|
-
}, []);
|
|
104
|
-
|
|
105
|
-
const setLocation = useCallback((to) => {
|
|
106
|
-
const normalized = to.startsWith("/") ? to : `/${to}`;
|
|
107
|
-
const nextHash = `#${normalized}`;
|
|
108
|
-
if (window.location.hash !== nextHash) {
|
|
109
|
-
window.location.hash = normalized;
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setLocationState(normalized);
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
return [location, setLocation];
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const RouteRedirect = ({ to }) => {
|
|
119
|
-
const [, setLocation] = useLocation();
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
setLocation(to);
|
|
122
|
-
}, [to, setLocation]);
|
|
123
|
-
return null;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const GeneralTab = ({
|
|
127
|
-
statusData = null,
|
|
128
|
-
watchdogData = null,
|
|
129
|
-
doctorStatusData = null,
|
|
130
|
-
doctorWarningDismissedUntilMs = 0,
|
|
131
|
-
onRefreshStatuses = () => {},
|
|
132
|
-
onSwitchTab,
|
|
133
|
-
onNavigate,
|
|
134
|
-
onOpenGmailWebhook = () => {},
|
|
135
|
-
isActive,
|
|
136
|
-
restartingGateway,
|
|
137
|
-
onRestartGateway,
|
|
138
|
-
restartSignal = 0,
|
|
139
|
-
openclawUpdateInProgress = false,
|
|
140
|
-
onOpenclawVersionActionComplete = () => {},
|
|
141
|
-
onOpenclawUpdate,
|
|
142
|
-
onRestartRequired = () => {},
|
|
143
|
-
onDismissDoctorWarning = () => {},
|
|
144
|
-
}) => {
|
|
145
|
-
const [dashboardLoading, setDashboardLoading] = useState(false);
|
|
146
|
-
const [repairingWatchdog, setRepairingWatchdog] = useState(false);
|
|
147
|
-
const status = statusData;
|
|
148
|
-
const watchdogStatus = watchdogData;
|
|
149
|
-
const doctorStatus = doctorStatusData;
|
|
150
|
-
const gatewayStatus = status?.gateway ?? null;
|
|
151
|
-
const channels = status?.channels ?? null;
|
|
152
|
-
const repo = status?.repo || null;
|
|
153
|
-
const syncCron = status?.syncCron || null;
|
|
154
|
-
const openclawVersion = status?.openclawVersion || null;
|
|
155
|
-
const [syncCronEnabled, setSyncCronEnabled] = useState(true);
|
|
156
|
-
const [syncCronSchedule, setSyncCronSchedule] = useState("0 * * * *");
|
|
157
|
-
const [savingSyncCron, setSavingSyncCron] = useState(false);
|
|
158
|
-
const [syncCronChoice, setSyncCronChoice] = useState("0 * * * *");
|
|
159
|
-
|
|
160
|
-
const hasUnpaired = ALL_CHANNELS.some((ch) => {
|
|
161
|
-
const info = channels?.[ch];
|
|
162
|
-
return info && info.status !== "paired";
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const pairingsPoll = usePolling(
|
|
166
|
-
async () => {
|
|
167
|
-
const d = await fetchPairings();
|
|
168
|
-
return d.pending || [];
|
|
169
|
-
},
|
|
170
|
-
1000,
|
|
171
|
-
{ enabled: hasUnpaired && gatewayStatus === "running" },
|
|
172
|
-
);
|
|
173
|
-
const pending = pairingsPoll.data || [];
|
|
174
|
-
|
|
175
|
-
const refreshAfterAction = () => {
|
|
176
|
-
setTimeout(pairingsPoll.refresh, 500);
|
|
177
|
-
setTimeout(pairingsPoll.refresh, 2000);
|
|
178
|
-
setTimeout(onRefreshStatuses, 3000);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const handleApprove = async (id, channel) => {
|
|
182
|
-
await approvePairing(id, channel);
|
|
183
|
-
refreshAfterAction();
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const handleReject = async (id, channel) => {
|
|
187
|
-
await rejectPairing(id, channel);
|
|
188
|
-
refreshAfterAction();
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const devicePoll = usePolling(
|
|
192
|
-
async () => {
|
|
193
|
-
const d = await fetchDevicePairings();
|
|
194
|
-
return d.pending || [];
|
|
195
|
-
},
|
|
196
|
-
2000,
|
|
197
|
-
{ enabled: gatewayStatus === "running" },
|
|
198
|
-
);
|
|
199
|
-
const devicePending = devicePoll.data || [];
|
|
200
|
-
|
|
201
|
-
const handleDeviceApprove = async (id) => {
|
|
202
|
-
await approveDevice(id);
|
|
203
|
-
setTimeout(devicePoll.refresh, 500);
|
|
204
|
-
setTimeout(devicePoll.refresh, 2000);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const handleDeviceReject = async (id) => {
|
|
208
|
-
await rejectDevice(id);
|
|
209
|
-
setTimeout(devicePoll.refresh, 500);
|
|
210
|
-
setTimeout(devicePoll.refresh, 2000);
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
if (!isActive) return;
|
|
215
|
-
onRefreshStatuses();
|
|
216
|
-
pairingsPoll.refresh();
|
|
217
|
-
devicePoll.refresh();
|
|
218
|
-
}, [isActive]);
|
|
219
|
-
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
if (!restartSignal || !isActive) return;
|
|
222
|
-
onRefreshStatuses();
|
|
223
|
-
pairingsPoll.refresh();
|
|
224
|
-
devicePoll.refresh();
|
|
225
|
-
const t1 = setTimeout(() => {
|
|
226
|
-
onRefreshStatuses();
|
|
227
|
-
pairingsPoll.refresh();
|
|
228
|
-
devicePoll.refresh();
|
|
229
|
-
}, 1200);
|
|
230
|
-
const t2 = setTimeout(() => {
|
|
231
|
-
onRefreshStatuses();
|
|
232
|
-
pairingsPoll.refresh();
|
|
233
|
-
devicePoll.refresh();
|
|
234
|
-
}, 3500);
|
|
235
|
-
return () => {
|
|
236
|
-
clearTimeout(t1);
|
|
237
|
-
clearTimeout(t2);
|
|
238
|
-
};
|
|
239
|
-
}, [
|
|
240
|
-
restartSignal,
|
|
241
|
-
isActive,
|
|
242
|
-
onRefreshStatuses,
|
|
243
|
-
pairingsPoll.refresh,
|
|
244
|
-
devicePoll.refresh,
|
|
245
|
-
]);
|
|
246
|
-
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
if (!syncCron) return;
|
|
249
|
-
setSyncCronEnabled(syncCron.enabled !== false);
|
|
250
|
-
setSyncCronSchedule(syncCron.schedule || "0 * * * *");
|
|
251
|
-
setSyncCronChoice(
|
|
252
|
-
syncCron.enabled === false
|
|
253
|
-
? "disabled"
|
|
254
|
-
: syncCron.schedule || "0 * * * *",
|
|
255
|
-
);
|
|
256
|
-
}, [syncCron?.enabled, syncCron?.schedule]);
|
|
257
|
-
|
|
258
|
-
const saveSyncCronSettings = async ({
|
|
259
|
-
enabled = syncCronEnabled,
|
|
260
|
-
schedule = syncCronSchedule,
|
|
261
|
-
}) => {
|
|
262
|
-
if (savingSyncCron) return;
|
|
263
|
-
setSavingSyncCron(true);
|
|
264
|
-
try {
|
|
265
|
-
const data = await updateSyncCron({ enabled, schedule });
|
|
266
|
-
if (!data.ok)
|
|
267
|
-
throw new Error(data.error || "Could not save sync settings");
|
|
268
|
-
showToast("Sync schedule updated", "success");
|
|
269
|
-
onRefreshStatuses();
|
|
270
|
-
} catch (err) {
|
|
271
|
-
showToast(err.message || "Could not save sync settings", "error");
|
|
272
|
-
}
|
|
273
|
-
setSavingSyncCron(false);
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const syncCronStatusText = syncCronEnabled ? "Enabled" : "Disabled";
|
|
277
|
-
const handleWatchdogRepair = async () => {
|
|
278
|
-
if (repairingWatchdog) return;
|
|
279
|
-
setRepairingWatchdog(true);
|
|
280
|
-
try {
|
|
281
|
-
const data = await triggerWatchdogRepair();
|
|
282
|
-
if (!data.ok) throw new Error(data.error || "Repair failed");
|
|
283
|
-
showToast("Repair triggered", "success");
|
|
284
|
-
setTimeout(() => {
|
|
285
|
-
onRefreshStatuses();
|
|
286
|
-
}, 800);
|
|
287
|
-
} catch (err) {
|
|
288
|
-
showToast(err.message || "Could not run repair", "error");
|
|
289
|
-
} finally {
|
|
290
|
-
setRepairingWatchdog(false);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
return html`
|
|
295
|
-
<div class="space-y-4">
|
|
296
|
-
<${Gateway}
|
|
297
|
-
status=${gatewayStatus}
|
|
298
|
-
openclawVersion=${openclawVersion}
|
|
299
|
-
restarting=${restartingGateway}
|
|
300
|
-
onRestart=${onRestartGateway}
|
|
301
|
-
watchdogStatus=${watchdogStatus}
|
|
302
|
-
onOpenWatchdog=${() => onSwitchTab("watchdog")}
|
|
303
|
-
onRepair=${handleWatchdogRepair}
|
|
304
|
-
repairing=${repairingWatchdog}
|
|
305
|
-
openclawUpdateInProgress=${openclawUpdateInProgress}
|
|
306
|
-
onOpenclawVersionActionComplete=${onOpenclawVersionActionComplete}
|
|
307
|
-
onOpenclawUpdate=${onOpenclawUpdate}
|
|
308
|
-
/>
|
|
309
|
-
<${GeneralDoctorWarning}
|
|
310
|
-
doctorStatus=${doctorStatus}
|
|
311
|
-
dismissedUntilMs=${doctorWarningDismissedUntilMs}
|
|
312
|
-
onOpenDoctor=${() => onSwitchTab("doctor")}
|
|
313
|
-
onDismiss=${onDismissDoctorWarning}
|
|
314
|
-
/>
|
|
315
|
-
<${Channels} channels=${channels} onSwitchTab=${onSwitchTab} onNavigate=${onNavigate} />
|
|
316
|
-
<${Pairings}
|
|
317
|
-
pending=${pending}
|
|
318
|
-
channels=${channels}
|
|
319
|
-
visible=${hasUnpaired}
|
|
320
|
-
onApprove=${handleApprove}
|
|
321
|
-
onReject=${handleReject}
|
|
322
|
-
/>
|
|
323
|
-
<${Features} onSwitchTab=${onSwitchTab} />
|
|
324
|
-
<${Google}
|
|
325
|
-
gatewayStatus=${gatewayStatus}
|
|
326
|
-
onRestartRequired=${onRestartRequired}
|
|
327
|
-
onOpenGmailWebhook=${onOpenGmailWebhook}
|
|
328
|
-
/>
|
|
329
|
-
|
|
330
|
-
${repo &&
|
|
331
|
-
html`
|
|
332
|
-
<div class="bg-surface border border-border rounded-xl p-4">
|
|
333
|
-
<div class="flex items-center justify-between gap-3">
|
|
334
|
-
<div class="flex items-center gap-2 min-w-0">
|
|
335
|
-
<svg
|
|
336
|
-
class="w-4 h-4 text-gray-400"
|
|
337
|
-
viewBox="0 0 16 16"
|
|
338
|
-
fill="currentColor"
|
|
339
|
-
>
|
|
340
|
-
<path
|
|
341
|
-
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
|
|
342
|
-
/>
|
|
343
|
-
</svg>
|
|
344
|
-
<a
|
|
345
|
-
href="https://github.com/${repo}"
|
|
346
|
-
target="_blank"
|
|
347
|
-
class="text-sm text-gray-400 hover:text-gray-200 transition-colors truncate"
|
|
348
|
-
>${repo}</a
|
|
349
|
-
>
|
|
350
|
-
</div>
|
|
351
|
-
<div class="flex items-center gap-2 shrink-0">
|
|
352
|
-
<span class="text-xs text-gray-400">Auto-sync</span>
|
|
353
|
-
<div class="relative">
|
|
354
|
-
<select
|
|
355
|
-
value=${syncCronChoice}
|
|
356
|
-
onchange=${(e) => {
|
|
357
|
-
const nextChoice = e.target.value;
|
|
358
|
-
setSyncCronChoice(nextChoice);
|
|
359
|
-
const nextEnabled = nextChoice !== "disabled";
|
|
360
|
-
const nextSchedule = nextEnabled
|
|
361
|
-
? nextChoice
|
|
362
|
-
: syncCronSchedule;
|
|
363
|
-
setSyncCronEnabled(nextEnabled);
|
|
364
|
-
setSyncCronSchedule(nextSchedule);
|
|
365
|
-
saveSyncCronSettings({
|
|
366
|
-
enabled: nextEnabled,
|
|
367
|
-
schedule: nextSchedule,
|
|
368
|
-
});
|
|
369
|
-
}}
|
|
370
|
-
disabled=${savingSyncCron}
|
|
371
|
-
class="appearance-none bg-black/30 border border-border rounded-lg pl-2.5 pr-9 py-1.5 text-xs text-gray-300 ${savingSyncCron
|
|
372
|
-
? "opacity-50 cursor-not-allowed"
|
|
373
|
-
: ""}"
|
|
374
|
-
title=${syncCron?.installed === false
|
|
375
|
-
? "Not Installed Yet"
|
|
376
|
-
: syncCronStatusText}
|
|
377
|
-
>
|
|
378
|
-
<option value="disabled">Disabled</option>
|
|
379
|
-
<option value="*/30 * * * *">Every 30 min</option>
|
|
380
|
-
<option value="0 * * * *">Hourly</option>
|
|
381
|
-
<option value="0 0 * * *">Daily</option>
|
|
382
|
-
</select>
|
|
383
|
-
<${ChevronDownIcon}
|
|
384
|
-
className="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-500"
|
|
385
|
-
/>
|
|
386
|
-
</div>
|
|
387
|
-
</div>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
`}
|
|
391
|
-
|
|
392
|
-
<div class="bg-surface border border-border rounded-xl p-4">
|
|
393
|
-
<div class="flex items-center justify-between">
|
|
394
|
-
<div>
|
|
395
|
-
<h2 class="font-semibold text-sm">OpenClaw Gateway Dashboard</h2>
|
|
396
|
-
</div>
|
|
397
|
-
<${UpdateActionButton}
|
|
398
|
-
onClick=${async () => {
|
|
399
|
-
if (dashboardLoading) return;
|
|
400
|
-
setDashboardLoading(true);
|
|
401
|
-
try {
|
|
402
|
-
const data = await fetchDashboardUrl();
|
|
403
|
-
console.log("[dashboard] response:", JSON.stringify(data));
|
|
404
|
-
window.open(data.url || "/openclaw", "_blank");
|
|
405
|
-
} catch (err) {
|
|
406
|
-
console.error("[dashboard] error:", err);
|
|
407
|
-
window.open("/openclaw", "_blank");
|
|
408
|
-
}
|
|
409
|
-
setDashboardLoading(false);
|
|
410
|
-
}}
|
|
411
|
-
loading=${dashboardLoading}
|
|
412
|
-
warning=${false}
|
|
413
|
-
idleLabel="Open"
|
|
414
|
-
loadingLabel="Opening..."
|
|
415
|
-
/>
|
|
416
|
-
</div>
|
|
417
|
-
<${DevicePairings}
|
|
418
|
-
pending=${devicePending}
|
|
419
|
-
onApprove=${handleDeviceApprove}
|
|
420
|
-
onReject=${handleDeviceReject}
|
|
421
|
-
/>
|
|
422
|
-
</div>
|
|
423
|
-
</div>
|
|
424
|
-
`;
|
|
425
|
-
};
|
|
426
33
|
|
|
427
34
|
const App = () => {
|
|
428
|
-
const appShellRef = useRef(null);
|
|
429
|
-
const [onboarded, setOnboarded] = useState(null);
|
|
430
35
|
const [location, setLocation] = useLocation();
|
|
431
|
-
const [acVersion, setAcVersion] = useState(null);
|
|
432
|
-
const [acLatest, setAcLatest] = useState(null);
|
|
433
|
-
const [acHasUpdate, setAcHasUpdate] = useState(false);
|
|
434
|
-
const [acUpdating, setAcUpdating] = useState(false);
|
|
435
|
-
const [acDismissed, setAcDismissed] = useState(false);
|
|
436
|
-
const [authEnabled, setAuthEnabled] = useState(false);
|
|
437
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
438
|
-
const [sidebarTab, setSidebarTab] = useState(() =>
|
|
439
|
-
location.startsWith("/browse") ? "browse" : "menu",
|
|
440
|
-
);
|
|
441
|
-
const [sidebarWidthPx, setSidebarWidthPx] = useState(() => {
|
|
442
|
-
const settings = readUiSettings();
|
|
443
|
-
if (!Number.isFinite(settings.sidebarWidthPx)) return kDefaultSidebarWidthPx;
|
|
444
|
-
return clampSidebarWidth(settings.sidebarWidthPx);
|
|
445
|
-
});
|
|
446
|
-
const [lastBrowsePath, setLastBrowsePath] = useState(() => {
|
|
447
|
-
const settings = readUiSettings();
|
|
448
|
-
return typeof settings[kBrowseLastPathUiSettingKey] === "string"
|
|
449
|
-
? settings[kBrowseLastPathUiSettingKey]
|
|
450
|
-
: "";
|
|
451
|
-
});
|
|
452
|
-
const [lastMenuRoute, setLastMenuRoute] = useState(() => {
|
|
453
|
-
const settings = readUiSettings();
|
|
454
|
-
const storedRoute = settings[kLastMenuRouteUiSettingKey];
|
|
455
|
-
if (
|
|
456
|
-
typeof storedRoute === "string" &&
|
|
457
|
-
storedRoute.startsWith("/") &&
|
|
458
|
-
!storedRoute.startsWith("/browse")
|
|
459
|
-
) {
|
|
460
|
-
return storedRoute;
|
|
461
|
-
}
|
|
462
|
-
return `/${kDefaultUiTab}`;
|
|
463
|
-
});
|
|
464
36
|
const [doctorWarningDismissedUntilMs, setDoctorWarningDismissedUntilMs] = useState(() => {
|
|
465
37
|
const settings = readUiSettings();
|
|
466
38
|
return Number(settings[kDoctorWarningDismissedUntilUiSettingKey] || 0);
|
|
467
39
|
});
|
|
468
|
-
const [isResizingSidebar, setIsResizingSidebar] = useState(false);
|
|
469
|
-
const [browsePreviewPath, setBrowsePreviewPath] = useState("");
|
|
470
|
-
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
471
|
-
const [mobileTopbarScrolled, setMobileTopbarScrolled] = useState(false);
|
|
472
|
-
const [restartRequired, setRestartRequired] = useState(false);
|
|
473
|
-
const [browseRestartRequired, setBrowseRestartRequired] = useState(false);
|
|
474
|
-
const [restartingGateway, setRestartingGateway] = useState(false);
|
|
475
|
-
const [gatewayRestartSignal, setGatewayRestartSignal] = useState(0);
|
|
476
|
-
const [statusPollCadenceMs, setStatusPollCadenceMs] = useState(15000);
|
|
477
|
-
const [openclawUpdateInProgress, setOpenclawUpdateInProgress] = useState(false);
|
|
478
|
-
const menuRef = useRef(null);
|
|
479
|
-
const routeHistoryRef = useRef([]);
|
|
480
|
-
const menuPaneRef = useRef(null);
|
|
481
|
-
const sharedStatusPoll = usePolling(fetchStatus, statusPollCadenceMs, {
|
|
482
|
-
enabled: onboarded === true,
|
|
483
|
-
});
|
|
484
|
-
const sharedWatchdogPoll = usePolling(fetchWatchdogStatus, statusPollCadenceMs, {
|
|
485
|
-
enabled: onboarded === true,
|
|
486
|
-
});
|
|
487
|
-
const sharedDoctorPoll = usePolling(fetchDoctorStatus, statusPollCadenceMs, {
|
|
488
|
-
enabled: onboarded === true,
|
|
489
|
-
});
|
|
490
|
-
const sharedStatus = sharedStatusPoll.data || null;
|
|
491
|
-
const sharedWatchdogStatus = sharedWatchdogPoll.data?.status || null;
|
|
492
|
-
const sharedDoctorStatus = sharedDoctorPoll.data?.status || null;
|
|
493
|
-
const isAnyRestartRequired = restartRequired || browseRestartRequired;
|
|
494
|
-
const refreshSharedStatuses = useCallback(() => {
|
|
495
|
-
sharedStatusPoll.refresh();
|
|
496
|
-
sharedWatchdogPoll.refresh();
|
|
497
|
-
sharedDoctorPoll.refresh();
|
|
498
|
-
}, [sharedStatusPoll.refresh, sharedWatchdogPoll.refresh, sharedDoctorPoll.refresh]);
|
|
499
|
-
|
|
500
|
-
const closeMenu = useCallback((e) => {
|
|
501
|
-
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
502
|
-
setMenuOpen(false);
|
|
503
|
-
}
|
|
504
|
-
}, []);
|
|
505
|
-
|
|
506
|
-
useEffect(() => {
|
|
507
|
-
if (menuOpen) {
|
|
508
|
-
document.addEventListener("click", closeMenu, true);
|
|
509
|
-
return () => document.removeEventListener("click", closeMenu, true);
|
|
510
|
-
}
|
|
511
|
-
}, [menuOpen, closeMenu]);
|
|
512
|
-
|
|
513
|
-
useEffect(() => {
|
|
514
|
-
fetchOnboardStatus()
|
|
515
|
-
.then((data) => setOnboarded(data.onboarded))
|
|
516
|
-
.catch(() => setOnboarded(false));
|
|
517
|
-
fetchAuthStatus()
|
|
518
|
-
.then((data) => setAuthEnabled(!!data.authEnabled))
|
|
519
|
-
.catch(() => {});
|
|
520
|
-
}, []);
|
|
521
|
-
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
if (!mobileSidebarOpen) return;
|
|
524
|
-
const previousOverflow = document.body.style.overflow;
|
|
525
|
-
document.body.style.overflow = "hidden";
|
|
526
|
-
return () => {
|
|
527
|
-
document.body.style.overflow = previousOverflow;
|
|
528
|
-
};
|
|
529
|
-
}, [mobileSidebarOpen]);
|
|
530
|
-
|
|
531
|
-
useEffect(() => {
|
|
532
|
-
if (!onboarded) return;
|
|
533
|
-
let active = true;
|
|
534
|
-
const check = async (refresh = false) => {
|
|
535
|
-
try {
|
|
536
|
-
const data = await fetchAlphaclawVersion(refresh);
|
|
537
|
-
if (!active) return;
|
|
538
|
-
setAcVersion(data.currentVersion || null);
|
|
539
|
-
setAcLatest(data.latestVersion || null);
|
|
540
|
-
setAcHasUpdate(!!data.hasUpdate);
|
|
541
|
-
} catch {}
|
|
542
|
-
};
|
|
543
|
-
check(true);
|
|
544
|
-
const id = setInterval(() => check(false), 5 * 60 * 1000);
|
|
545
|
-
return () => {
|
|
546
|
-
active = false;
|
|
547
|
-
clearInterval(id);
|
|
548
|
-
};
|
|
549
|
-
}, [onboarded]);
|
|
550
|
-
|
|
551
|
-
const refreshRestartStatus = useCallback(async () => {
|
|
552
|
-
if (!onboarded) return;
|
|
553
|
-
try {
|
|
554
|
-
const data = await fetchRestartStatus();
|
|
555
|
-
setRestartRequired(!!data.restartRequired);
|
|
556
|
-
setRestartingGateway(!!data.restartInProgress);
|
|
557
|
-
} catch {}
|
|
558
|
-
}, [onboarded]);
|
|
559
|
-
|
|
560
|
-
useEffect(() => {
|
|
561
|
-
if (!onboarded) return;
|
|
562
|
-
refreshRestartStatus();
|
|
563
|
-
}, [onboarded, refreshRestartStatus]);
|
|
564
40
|
|
|
565
|
-
|
|
566
|
-
if (onboarded !== true) return;
|
|
567
|
-
const inStatusView =
|
|
568
|
-
location.startsWith("/general") || location.startsWith("/watchdog");
|
|
569
|
-
const gatewayStatus = sharedStatus?.gateway ?? null;
|
|
570
|
-
const watchdogHealth = String(sharedWatchdogStatus?.health || "").toLowerCase();
|
|
571
|
-
const watchdogLifecycle = String(sharedWatchdogStatus?.lifecycle || "").toLowerCase();
|
|
572
|
-
const shouldFastPollWatchdog =
|
|
573
|
-
watchdogHealth === "unknown" ||
|
|
574
|
-
watchdogLifecycle === "restarting" ||
|
|
575
|
-
watchdogLifecycle === "stopped" ||
|
|
576
|
-
!!sharedWatchdogStatus?.operationInProgress;
|
|
577
|
-
const shouldFastPollGateway = !gatewayStatus || gatewayStatus !== "running";
|
|
578
|
-
const nextCadenceMs =
|
|
579
|
-
inStatusView && (shouldFastPollWatchdog || shouldFastPollGateway) ? 2000 : 15000;
|
|
580
|
-
setStatusPollCadenceMs((currentCadenceMs) =>
|
|
581
|
-
currentCadenceMs === nextCadenceMs ? currentCadenceMs : nextCadenceMs,
|
|
582
|
-
);
|
|
583
|
-
}, [
|
|
584
|
-
onboarded,
|
|
41
|
+
const { state: controllerState, actions: controllerActions } = useAppShellController({
|
|
585
42
|
location,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const id = setInterval(refreshRestartStatus, 2000);
|
|
595
|
-
return () => clearInterval(id);
|
|
596
|
-
}, [onboarded, restartRequired, restartingGateway, refreshRestartStatus]);
|
|
43
|
+
});
|
|
44
|
+
const { refs: shellRefs, state: shellState, actions: shellActions } = useAppShellUi();
|
|
45
|
+
const { state: browseState, actions: browseActions, constants: browseConstants } =
|
|
46
|
+
useBrowseNavigation({
|
|
47
|
+
location,
|
|
48
|
+
setLocation,
|
|
49
|
+
onCloseMobileSidebar: shellActions.closeMobileSidebar,
|
|
50
|
+
});
|
|
597
51
|
|
|
598
52
|
useEffect(() => {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
};
|
|
604
|
-
window.addEventListener("alphaclaw:browse-file-saved", handleBrowseFileSaved);
|
|
605
|
-
return () => {
|
|
606
|
-
window.removeEventListener("alphaclaw:browse-file-saved", handleBrowseFileSaved);
|
|
607
|
-
};
|
|
608
|
-
}, []);
|
|
609
|
-
|
|
610
|
-
const handleGatewayRestart = useCallback(async () => {
|
|
611
|
-
if (restartingGateway) return;
|
|
612
|
-
setRestartingGateway(true);
|
|
613
|
-
try {
|
|
614
|
-
const data = await restartGateway();
|
|
615
|
-
if (!data?.ok) throw new Error(data?.error || "Gateway restart failed");
|
|
616
|
-
setRestartRequired(!!data.restartRequired);
|
|
617
|
-
setBrowseRestartRequired(false);
|
|
618
|
-
setGatewayRestartSignal(Date.now());
|
|
619
|
-
refreshSharedStatuses();
|
|
620
|
-
showToast("Gateway restarted", "success");
|
|
621
|
-
setTimeout(refreshRestartStatus, 800);
|
|
622
|
-
} catch (err) {
|
|
623
|
-
showToast(err.message || "Restart failed", "error");
|
|
624
|
-
setTimeout(refreshRestartStatus, 800);
|
|
625
|
-
} finally {
|
|
626
|
-
setRestartingGateway(false);
|
|
627
|
-
}
|
|
628
|
-
}, [restartingGateway, refreshRestartStatus, refreshSharedStatuses]);
|
|
629
|
-
|
|
630
|
-
const handleOpenclawUpdate = useCallback(async () => {
|
|
631
|
-
if (openclawUpdateInProgress) {
|
|
632
|
-
return { ok: false, error: "OpenClaw update already in progress" };
|
|
633
|
-
}
|
|
634
|
-
setOpenclawUpdateInProgress(true);
|
|
635
|
-
try {
|
|
636
|
-
const data = await updateOpenclaw();
|
|
637
|
-
return data;
|
|
638
|
-
} finally {
|
|
639
|
-
setOpenclawUpdateInProgress(false);
|
|
640
|
-
refreshSharedStatuses();
|
|
641
|
-
setTimeout(refreshSharedStatuses, 1200);
|
|
642
|
-
setTimeout(refreshSharedStatuses, 3500);
|
|
643
|
-
setTimeout(refreshRestartStatus, 1200);
|
|
644
|
-
}
|
|
645
|
-
}, [
|
|
646
|
-
openclawUpdateInProgress,
|
|
647
|
-
refreshRestartStatus,
|
|
648
|
-
refreshSharedStatuses,
|
|
649
|
-
]);
|
|
650
|
-
|
|
651
|
-
const handleOpenclawVersionActionComplete = useCallback(
|
|
652
|
-
({ type }) => {
|
|
653
|
-
if (type !== "update") return;
|
|
654
|
-
refreshSharedStatuses();
|
|
655
|
-
setTimeout(refreshSharedStatuses, 1200);
|
|
656
|
-
},
|
|
657
|
-
[refreshSharedStatuses],
|
|
658
|
-
);
|
|
53
|
+
const settings = readUiSettings();
|
|
54
|
+
settings[kDoctorWarningDismissedUntilUiSettingKey] = doctorWarningDismissedUntilMs;
|
|
55
|
+
writeUiSettings(settings);
|
|
56
|
+
}, [doctorWarningDismissedUntilMs]);
|
|
659
57
|
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
58
|
+
const handleSidebarLogout = async () => {
|
|
59
|
+
shellActions.setMenuOpen(false);
|
|
60
|
+
await logout();
|
|
663
61
|
try {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
} else {
|
|
669
|
-
showToast(data.error || "AlphaClaw update failed", "error");
|
|
670
|
-
setAcUpdating(false);
|
|
671
|
-
}
|
|
672
|
-
} catch (err) {
|
|
673
|
-
showToast(err.message || "Could not update AlphaClaw", "error");
|
|
674
|
-
setAcUpdating(false);
|
|
675
|
-
}
|
|
62
|
+
window.localStorage.clear();
|
|
63
|
+
window.sessionStorage.clear();
|
|
64
|
+
} catch {}
|
|
65
|
+
window.location.href = "/login.html";
|
|
676
66
|
};
|
|
677
|
-
|
|
678
|
-
if (onboarded === null) {
|
|
67
|
+
|
|
68
|
+
if (controllerState.onboarded === null) {
|
|
679
69
|
return html`
|
|
680
70
|
<div
|
|
681
71
|
class="min-h-screen flex items-center justify-center"
|
|
@@ -690,382 +80,95 @@ const App = () => {
|
|
|
690
80
|
`;
|
|
691
81
|
}
|
|
692
82
|
|
|
693
|
-
if (!onboarded) {
|
|
83
|
+
if (!controllerState.onboarded) {
|
|
694
84
|
return html`
|
|
695
85
|
<div
|
|
696
86
|
class="min-h-screen flex justify-center pt-12 pb-8 px-4"
|
|
697
87
|
style="position: relative; z-index: 1"
|
|
698
88
|
>
|
|
699
|
-
<${Welcome} onComplete=${
|
|
89
|
+
<${Welcome} onComplete=${controllerActions.handleOnboardingComplete} />
|
|
700
90
|
</div>
|
|
701
91
|
<${ToastContainer} />
|
|
702
92
|
`;
|
|
703
93
|
}
|
|
704
94
|
|
|
705
|
-
const buildBrowseRoute = (relativePath, options = {}) => {
|
|
706
|
-
const view = String(options?.view || "edit");
|
|
707
|
-
const encodedPath = String(relativePath || "")
|
|
708
|
-
.split("/")
|
|
709
|
-
.filter(Boolean)
|
|
710
|
-
.map((segment) => encodeURIComponent(segment))
|
|
711
|
-
.join("/");
|
|
712
|
-
const baseRoute = encodedPath ? `/browse/${encodedPath}` : "/browse";
|
|
713
|
-
const params = new URLSearchParams();
|
|
714
|
-
if (view === "diff" && encodedPath) params.set("view", "diff");
|
|
715
|
-
if (options.line) params.set("line", String(options.line));
|
|
716
|
-
if (options.lineEnd) params.set("lineEnd", String(options.lineEnd));
|
|
717
|
-
const query = params.toString();
|
|
718
|
-
return query ? `${baseRoute}?${query}` : baseRoute;
|
|
719
|
-
};
|
|
720
|
-
const navigateToSubScreen = (screen) => {
|
|
721
|
-
setLocation(`/${screen}`);
|
|
722
|
-
setMobileSidebarOpen(false);
|
|
723
|
-
};
|
|
724
|
-
const handleBrowsePreviewFile = useCallback((nextPreviewPath) => {
|
|
725
|
-
const normalizedPreviewPath = normalizeBrowsePath(nextPreviewPath);
|
|
726
|
-
setBrowsePreviewPath(normalizedPreviewPath);
|
|
727
|
-
}, []);
|
|
728
|
-
const navigateToBrowseFile = (relativePath, options = {}) => {
|
|
729
|
-
const normalizedTargetPath = normalizeBrowsePath(relativePath);
|
|
730
|
-
const selectingDirectory =
|
|
731
|
-
!!options.directory || String(relativePath || "").trim().endsWith("/");
|
|
732
|
-
const shouldPreservePreview = selectingDirectory && !!options.preservePreview;
|
|
733
|
-
const activePath = normalizeBrowsePath(
|
|
734
|
-
browsePreviewPath || selectedBrowsePath || "",
|
|
735
|
-
);
|
|
736
|
-
const nextPreviewPath =
|
|
737
|
-
shouldPreservePreview && activePath && activePath !== normalizedTargetPath
|
|
738
|
-
? activePath
|
|
739
|
-
: "";
|
|
740
|
-
setBrowsePreviewPath(nextPreviewPath);
|
|
741
|
-
const routeOptions = selectingDirectory
|
|
742
|
-
? { ...options, view: "edit" }
|
|
743
|
-
: options;
|
|
744
|
-
setLocation(buildBrowseRoute(normalizedTargetPath, routeOptions));
|
|
745
|
-
setMobileSidebarOpen(false);
|
|
746
|
-
};
|
|
747
|
-
const handleSidebarLogout = async () => {
|
|
748
|
-
setMenuOpen(false);
|
|
749
|
-
await logout();
|
|
750
|
-
try {
|
|
751
|
-
window.localStorage.clear();
|
|
752
|
-
window.sessionStorage.clear();
|
|
753
|
-
} catch {}
|
|
754
|
-
window.location.href = "/login.html";
|
|
755
|
-
};
|
|
756
|
-
const handleSelectSidebarTab = (nextTab) => {
|
|
757
|
-
setSidebarTab(nextTab);
|
|
758
|
-
if (nextTab === "menu" && location.startsWith("/browse")) {
|
|
759
|
-
setBrowsePreviewPath("");
|
|
760
|
-
setLocation(lastMenuRoute || `/${kDefaultUiTab}`);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
if (nextTab === "browse" && !location.startsWith("/browse")) {
|
|
764
|
-
setLocation(buildBrowseRoute(lastBrowsePath));
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
const handleSelectNavItem = (itemId) => {
|
|
768
|
-
setLocation(`/${itemId}`);
|
|
769
|
-
setMobileSidebarOpen(false);
|
|
770
|
-
};
|
|
771
|
-
const exitSubScreen = () => {
|
|
772
|
-
setLocation(`/${kDefaultUiTab}`);
|
|
773
|
-
setMobileSidebarOpen(false);
|
|
774
|
-
};
|
|
775
|
-
const handlePaneScroll = (e) => {
|
|
776
|
-
const nextScrolled = e.currentTarget.scrollTop > 0;
|
|
777
|
-
setMobileTopbarScrolled((currentScrolled) =>
|
|
778
|
-
currentScrolled === nextScrolled ? currentScrolled : nextScrolled,
|
|
779
|
-
);
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
const kNavSections = [
|
|
783
|
-
{
|
|
784
|
-
label: "Setup",
|
|
785
|
-
items: [
|
|
786
|
-
{ id: "general", label: "General" },
|
|
787
|
-
],
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
label: "Monitoring",
|
|
791
|
-
items: [
|
|
792
|
-
{ id: "watchdog", label: "Watchdog" },
|
|
793
|
-
{ id: "usage", label: "Usage" },
|
|
794
|
-
{ id: "doctor", label: "Doctor" },
|
|
795
|
-
],
|
|
796
|
-
},
|
|
797
|
-
{
|
|
798
|
-
label: "Config",
|
|
799
|
-
items: [
|
|
800
|
-
{ id: "providers", label: "Providers" },
|
|
801
|
-
{ id: "envars", label: "Envars" },
|
|
802
|
-
{ id: "webhooks", label: "Webhooks" },
|
|
803
|
-
],
|
|
804
|
-
},
|
|
805
|
-
];
|
|
806
|
-
|
|
807
|
-
const isBrowseRoute = location.startsWith("/browse");
|
|
808
|
-
const browseRoutePath = isBrowseRoute ? String(location || "").split("?")[0] : "";
|
|
809
|
-
const browseRouteQuery =
|
|
810
|
-
isBrowseRoute && String(location || "").includes("?")
|
|
811
|
-
? String(location || "").split("?").slice(1).join("?")
|
|
812
|
-
: "";
|
|
813
|
-
const selectedBrowsePath = isBrowseRoute
|
|
814
|
-
? browseRoutePath
|
|
815
|
-
.replace(/^\/browse\/?/, "")
|
|
816
|
-
.split("/")
|
|
817
|
-
.filter(Boolean)
|
|
818
|
-
.map((segment) => {
|
|
819
|
-
try {
|
|
820
|
-
return decodeURIComponent(segment);
|
|
821
|
-
} catch {
|
|
822
|
-
return segment;
|
|
823
|
-
}
|
|
824
|
-
})
|
|
825
|
-
.join("/")
|
|
826
|
-
: "";
|
|
827
|
-
const activeBrowsePath = browsePreviewPath || selectedBrowsePath;
|
|
828
|
-
const browseQueryParams = isBrowseRoute ? new URLSearchParams(browseRouteQuery) : null;
|
|
829
|
-
const browseViewerMode =
|
|
830
|
-
!browsePreviewPath && browseQueryParams?.get("view") === "diff"
|
|
831
|
-
? "diff"
|
|
832
|
-
: "edit";
|
|
833
|
-
const browseLineTarget = Number.parseInt(browseQueryParams?.get("line") || "", 10) || 0;
|
|
834
|
-
const browseLineEndTarget = Number.parseInt(browseQueryParams?.get("lineEnd") || "", 10) || 0;
|
|
835
|
-
const selectedNavId = isBrowseRoute
|
|
836
|
-
? "browse"
|
|
837
|
-
: location === "/telegram"
|
|
838
|
-
? ""
|
|
839
|
-
: location.startsWith("/providers")
|
|
840
|
-
? "providers"
|
|
841
|
-
: location.startsWith("/watchdog")
|
|
842
|
-
? "watchdog"
|
|
843
|
-
: location.startsWith("/usage")
|
|
844
|
-
? "usage"
|
|
845
|
-
: location.startsWith("/doctor")
|
|
846
|
-
? "doctor"
|
|
847
|
-
: location.startsWith("/envars")
|
|
848
|
-
? "envars"
|
|
849
|
-
: location.startsWith("/webhooks")
|
|
850
|
-
? "webhooks"
|
|
851
|
-
: "general";
|
|
852
|
-
|
|
853
|
-
useEffect(() => {
|
|
854
|
-
setSidebarTab((currentTab) => {
|
|
855
|
-
if (location.startsWith("/browse")) return "browse";
|
|
856
|
-
if (currentTab === "browse") return "menu";
|
|
857
|
-
return currentTab;
|
|
858
|
-
});
|
|
859
|
-
}, [location]);
|
|
860
|
-
|
|
861
|
-
useEffect(() => {
|
|
862
|
-
if (location.startsWith("/browse")) return;
|
|
863
|
-
setBrowsePreviewPath("");
|
|
864
|
-
}, [location]);
|
|
865
|
-
|
|
866
|
-
useEffect(() => {
|
|
867
|
-
const historyStack = routeHistoryRef.current;
|
|
868
|
-
const lastEntry = historyStack[historyStack.length - 1];
|
|
869
|
-
if (lastEntry === location) return;
|
|
870
|
-
historyStack.push(location);
|
|
871
|
-
if (historyStack.length > 100) {
|
|
872
|
-
historyStack.shift();
|
|
873
|
-
}
|
|
874
|
-
}, [location]);
|
|
875
|
-
|
|
876
|
-
useEffect(() => {
|
|
877
|
-
if (location.startsWith("/browse")) return;
|
|
878
|
-
if (location === "/telegram") return;
|
|
879
|
-
setLastMenuRoute((currentRoute) =>
|
|
880
|
-
currentRoute === location ? currentRoute : location,
|
|
881
|
-
);
|
|
882
|
-
}, [location]);
|
|
883
|
-
|
|
884
|
-
useEffect(() => {
|
|
885
|
-
if (!isBrowseRoute) return;
|
|
886
|
-
if (!selectedBrowsePath) return;
|
|
887
|
-
setLastBrowsePath((currentPath) =>
|
|
888
|
-
currentPath === selectedBrowsePath ? currentPath : selectedBrowsePath,
|
|
889
|
-
);
|
|
890
|
-
}, [isBrowseRoute, selectedBrowsePath]);
|
|
891
|
-
|
|
892
|
-
useEffect(() => {
|
|
893
|
-
const handleBrowseGitSynced = () => {
|
|
894
|
-
if (!isBrowseRoute || browseViewerMode !== "diff") return;
|
|
895
|
-
const activePath = String(selectedBrowsePath || "").trim();
|
|
896
|
-
if (!activePath) return;
|
|
897
|
-
setLocation(buildBrowseRoute(activePath, { view: "edit" }));
|
|
898
|
-
};
|
|
899
|
-
window.addEventListener("alphaclaw:browse-git-synced", handleBrowseGitSynced);
|
|
900
|
-
return () => {
|
|
901
|
-
window.removeEventListener(
|
|
902
|
-
"alphaclaw:browse-git-synced",
|
|
903
|
-
handleBrowseGitSynced,
|
|
904
|
-
);
|
|
905
|
-
};
|
|
906
|
-
}, [
|
|
907
|
-
isBrowseRoute,
|
|
908
|
-
browseViewerMode,
|
|
909
|
-
selectedBrowsePath,
|
|
910
|
-
setLocation,
|
|
911
|
-
buildBrowseRoute,
|
|
912
|
-
]);
|
|
913
|
-
|
|
914
|
-
useEffect(() => {
|
|
915
|
-
const settings = readUiSettings();
|
|
916
|
-
settings.sidebarWidthPx = sidebarWidthPx;
|
|
917
|
-
settings[kBrowseLastPathUiSettingKey] = lastBrowsePath;
|
|
918
|
-
settings[kLastMenuRouteUiSettingKey] = lastMenuRoute;
|
|
919
|
-
settings[kDoctorWarningDismissedUntilUiSettingKey] = doctorWarningDismissedUntilMs;
|
|
920
|
-
writeUiSettings(settings);
|
|
921
|
-
}, [sidebarWidthPx, lastBrowsePath, lastMenuRoute, doctorWarningDismissedUntilMs]);
|
|
922
|
-
|
|
923
|
-
const resizeSidebarWithClientX = useCallback((clientX) => {
|
|
924
|
-
const shellElement = appShellRef.current;
|
|
925
|
-
if (!shellElement) return;
|
|
926
|
-
const shellBounds = shellElement.getBoundingClientRect();
|
|
927
|
-
const nextWidth = clampSidebarWidth(Math.round(clientX - shellBounds.left));
|
|
928
|
-
setSidebarWidthPx(nextWidth);
|
|
929
|
-
}, []);
|
|
930
|
-
|
|
931
|
-
const onSidebarResizerPointerDown = (event) => {
|
|
932
|
-
event.preventDefault();
|
|
933
|
-
setIsResizingSidebar(true);
|
|
934
|
-
resizeSidebarWithClientX(event.clientX);
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
useEffect(() => {
|
|
938
|
-
if (!isResizingSidebar) return () => {};
|
|
939
|
-
const onPointerMove = (event) => resizeSidebarWithClientX(event.clientX);
|
|
940
|
-
const onPointerUp = () => setIsResizingSidebar(false);
|
|
941
|
-
window.addEventListener("pointermove", onPointerMove);
|
|
942
|
-
window.addEventListener("pointerup", onPointerUp);
|
|
943
|
-
const previousUserSelect = document.body.style.userSelect;
|
|
944
|
-
const previousCursor = document.body.style.cursor;
|
|
945
|
-
document.body.style.userSelect = "none";
|
|
946
|
-
document.body.style.cursor = "col-resize";
|
|
947
|
-
return () => {
|
|
948
|
-
window.removeEventListener("pointermove", onPointerMove);
|
|
949
|
-
window.removeEventListener("pointerup", onPointerUp);
|
|
950
|
-
document.body.style.userSelect = previousUserSelect;
|
|
951
|
-
document.body.style.cursor = previousCursor;
|
|
952
|
-
};
|
|
953
|
-
}, [isResizingSidebar, resizeSidebarWithClientX]);
|
|
954
|
-
|
|
955
|
-
const renderWebhooks = (hookName = "") => html`
|
|
956
|
-
<div class="pt-4">
|
|
957
|
-
<${Webhooks}
|
|
958
|
-
selectedHookName=${hookName}
|
|
959
|
-
onSelectHook=${(name) => setLocation(`/webhooks/${encodeURIComponent(name)}`)}
|
|
960
|
-
onBackToList=${() => {
|
|
961
|
-
const historyStack = routeHistoryRef.current;
|
|
962
|
-
const hasPreviousRoute = historyStack.length > 1;
|
|
963
|
-
if (!hasPreviousRoute) {
|
|
964
|
-
setLocation("/webhooks");
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
const currentPath = getHashPath();
|
|
968
|
-
window.history.back();
|
|
969
|
-
window.setTimeout(() => {
|
|
970
|
-
if (getHashPath() === currentPath) {
|
|
971
|
-
setLocation("/webhooks");
|
|
972
|
-
}
|
|
973
|
-
}, 180);
|
|
974
|
-
}}
|
|
975
|
-
onRestartRequired=${setRestartRequired}
|
|
976
|
-
onOpenFile=${(relativePath) =>
|
|
977
|
-
navigateToBrowseFile(String(relativePath || "").trim(), { view: "edit" })}
|
|
978
|
-
/>
|
|
979
|
-
</div>
|
|
980
|
-
`;
|
|
981
|
-
|
|
982
95
|
return html`
|
|
983
96
|
<div
|
|
984
97
|
class="app-shell"
|
|
985
|
-
ref=${appShellRef}
|
|
986
|
-
style=${{ "--sidebar-width": `${sidebarWidthPx}px` }}
|
|
98
|
+
ref=${shellRefs.appShellRef}
|
|
99
|
+
style=${{ "--sidebar-width": `${shellState.sidebarWidthPx}px` }}
|
|
987
100
|
>
|
|
988
101
|
<${GlobalRestartBanner}
|
|
989
|
-
visible=${isAnyRestartRequired}
|
|
990
|
-
restarting=${restartingGateway}
|
|
991
|
-
onRestart=${handleGatewayRestart}
|
|
102
|
+
visible=${controllerState.isAnyRestartRequired}
|
|
103
|
+
restarting=${controllerState.restartingGateway}
|
|
104
|
+
onRestart=${controllerActions.handleGatewayRestart}
|
|
992
105
|
/>
|
|
993
106
|
<${AppSidebar}
|
|
994
|
-
mobileSidebarOpen=${mobileSidebarOpen}
|
|
995
|
-
authEnabled=${authEnabled}
|
|
996
|
-
menuRef=${menuRef}
|
|
997
|
-
menuOpen=${menuOpen}
|
|
998
|
-
onToggleMenu=${
|
|
107
|
+
mobileSidebarOpen=${shellState.mobileSidebarOpen}
|
|
108
|
+
authEnabled=${controllerState.authEnabled}
|
|
109
|
+
menuRef=${shellRefs.menuRef}
|
|
110
|
+
menuOpen=${shellState.menuOpen}
|
|
111
|
+
onToggleMenu=${shellActions.onToggleMenu}
|
|
999
112
|
onLogout=${handleSidebarLogout}
|
|
1000
|
-
sidebarTab=${sidebarTab}
|
|
1001
|
-
onSelectSidebarTab=${handleSelectSidebarTab}
|
|
1002
|
-
navSections=${kNavSections}
|
|
1003
|
-
selectedNavId=${selectedNavId}
|
|
1004
|
-
onSelectNavItem=${handleSelectNavItem}
|
|
1005
|
-
selectedBrowsePath=${selectedBrowsePath}
|
|
1006
|
-
onSelectBrowseFile=${navigateToBrowseFile}
|
|
1007
|
-
onPreviewBrowseFile=${handleBrowsePreviewFile}
|
|
1008
|
-
acHasUpdate=${acHasUpdate}
|
|
1009
|
-
acLatest=${acLatest}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
onAcUpdate=${handleAcUpdate}
|
|
113
|
+
sidebarTab=${browseState.sidebarTab}
|
|
114
|
+
onSelectSidebarTab=${browseActions.handleSelectSidebarTab}
|
|
115
|
+
navSections=${browseConstants.kNavSections}
|
|
116
|
+
selectedNavId=${browseState.selectedNavId}
|
|
117
|
+
onSelectNavItem=${browseActions.handleSelectNavItem}
|
|
118
|
+
selectedBrowsePath=${browseState.selectedBrowsePath}
|
|
119
|
+
onSelectBrowseFile=${browseActions.navigateToBrowseFile}
|
|
120
|
+
onPreviewBrowseFile=${browseActions.handleBrowsePreviewFile}
|
|
121
|
+
acHasUpdate=${controllerState.acHasUpdate}
|
|
122
|
+
acLatest=${controllerState.acLatest}
|
|
123
|
+
acUpdating=${controllerState.acUpdating}
|
|
124
|
+
onAcUpdate=${controllerActions.handleAcUpdate}
|
|
1013
125
|
/>
|
|
1014
126
|
<div
|
|
1015
|
-
class=${`sidebar-resizer ${isResizingSidebar ? "is-resizing" : ""}`}
|
|
1016
|
-
onpointerdown=${onSidebarResizerPointerDown}
|
|
127
|
+
class=${`sidebar-resizer ${shellState.isResizingSidebar ? "is-resizing" : ""}`}
|
|
128
|
+
onpointerdown=${shellActions.onSidebarResizerPointerDown}
|
|
1017
129
|
role="separator"
|
|
1018
130
|
aria-orientation="vertical"
|
|
1019
131
|
aria-label="Resize sidebar"
|
|
1020
132
|
></div>
|
|
1021
133
|
|
|
1022
134
|
<div
|
|
1023
|
-
class=${`mobile-sidebar-overlay ${mobileSidebarOpen ? "active" : ""}`}
|
|
1024
|
-
onclick=${
|
|
135
|
+
class=${`mobile-sidebar-overlay ${shellState.mobileSidebarOpen ? "active" : ""}`}
|
|
136
|
+
onclick=${shellActions.closeMobileSidebar}
|
|
1025
137
|
/>
|
|
1026
138
|
|
|
1027
139
|
<div class="app-content">
|
|
1028
140
|
<div
|
|
1029
141
|
class="app-content-pane browse-pane"
|
|
1030
|
-
style=${{ display: isBrowseRoute ? "block" : "none" }}
|
|
142
|
+
style=${{ display: browseState.isBrowseRoute ? "block" : "none" }}
|
|
1031
143
|
>
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
setLocation(buildBrowseRoute(selectedBrowsePath, { view: "edit" }));
|
|
1049
|
-
}}
|
|
1050
|
-
onRequestClearSelection=${() => {
|
|
1051
|
-
setBrowsePreviewPath("");
|
|
1052
|
-
setLocation("/browse");
|
|
1053
|
-
}}
|
|
1054
|
-
/>
|
|
1055
|
-
</div>
|
|
144
|
+
<${BrowseRoute}
|
|
145
|
+
activeBrowsePath=${browseState.activeBrowsePath}
|
|
146
|
+
browseView=${browseState.browseViewerMode}
|
|
147
|
+
lineTarget=${browseState.browseLineTarget}
|
|
148
|
+
lineEndTarget=${browseState.browseLineEndTarget}
|
|
149
|
+
selectedBrowsePath=${browseState.selectedBrowsePath}
|
|
150
|
+
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
151
|
+
onEditSelectedBrowseFile=${() =>
|
|
152
|
+
setLocation(
|
|
153
|
+
browseActions.buildBrowseRoute(browseState.selectedBrowsePath, { view: "edit" }),
|
|
154
|
+
)}
|
|
155
|
+
onClearSelection=${() => {
|
|
156
|
+
browseActions.clearBrowsePreview();
|
|
157
|
+
setLocation("/browse");
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
1056
160
|
</div>
|
|
1057
161
|
<div
|
|
1058
162
|
class="app-content-pane"
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
style=${{ display: isBrowseRoute ? "none" : "block" }}
|
|
163
|
+
onscroll=${shellActions.handlePaneScroll}
|
|
164
|
+
style=${{ display: browseState.isBrowseRoute ? "none" : "block" }}
|
|
1062
165
|
>
|
|
1063
|
-
<div class=${`mobile-topbar ${mobileTopbarScrolled ? "is-scrolled" : ""}`}>
|
|
166
|
+
<div class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}>
|
|
1064
167
|
<button
|
|
1065
168
|
class="mobile-topbar-menu"
|
|
1066
|
-
onclick=${() => setMobileSidebarOpen((open) => !open)}
|
|
169
|
+
onclick=${() => shellActions.setMobileSidebarOpen((open) => !open)}
|
|
1067
170
|
aria-label="Open menu"
|
|
1068
|
-
aria-expanded=${mobileSidebarOpen ? "true" : "false"}
|
|
171
|
+
aria-expanded=${shellState.mobileSidebarOpen ? "true" : "false"}
|
|
1069
172
|
>
|
|
1070
173
|
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
|
|
1071
174
|
<path
|
|
@@ -1078,106 +181,88 @@ const App = () => {
|
|
|
1078
181
|
</span>
|
|
1079
182
|
</div>
|
|
1080
183
|
<div class="max-w-2xl w-full mx-auto">
|
|
1081
|
-
|
|
1082
|
-
<div class="pt-4">
|
|
1083
|
-
<${GeneralTab}
|
|
1084
|
-
statusData=${sharedStatus}
|
|
1085
|
-
watchdogData=${sharedWatchdogStatus}
|
|
1086
|
-
doctorStatusData=${sharedDoctorStatus}
|
|
1087
|
-
doctorWarningDismissedUntilMs=${doctorWarningDismissedUntilMs}
|
|
1088
|
-
onRefreshStatuses=${refreshSharedStatuses}
|
|
1089
|
-
onSwitchTab=${(nextTab) => setLocation(`/${nextTab}`)}
|
|
1090
|
-
onNavigate=${navigateToSubScreen}
|
|
1091
|
-
onOpenGmailWebhook=${() => setLocation("/webhooks/gmail")}
|
|
1092
|
-
isActive=${location === "/general"}
|
|
1093
|
-
restartingGateway=${restartingGateway}
|
|
1094
|
-
onRestartGateway=${handleGatewayRestart}
|
|
1095
|
-
restartSignal=${gatewayRestartSignal}
|
|
1096
|
-
openclawUpdateInProgress=${openclawUpdateInProgress}
|
|
1097
|
-
onOpenclawVersionActionComplete=${handleOpenclawVersionActionComplete}
|
|
1098
|
-
onOpenclawUpdate=${handleOpenclawUpdate}
|
|
1099
|
-
onRestartRequired=${setRestartRequired}
|
|
1100
|
-
onDismissDoctorWarning=${() =>
|
|
1101
|
-
setDoctorWarningDismissedUntilMs(Date.now() + kOneWeekMs)}
|
|
1102
|
-
/>
|
|
1103
|
-
</div>
|
|
1104
|
-
</div>
|
|
1105
|
-
<div style=${{ display: location === "/doctor" ? "block" : "none" }}>
|
|
1106
|
-
<div class="pt-4">
|
|
1107
|
-
<${DoctorTab}
|
|
1108
|
-
isActive=${location === "/doctor"}
|
|
1109
|
-
onOpenFile=${(relativePath, options = {}) => {
|
|
1110
|
-
const browsePath = `workspace/${String(relativePath || "").trim().replace(/^workspace\//, "")}`;
|
|
1111
|
-
navigateToBrowseFile(browsePath, {
|
|
1112
|
-
view: "edit",
|
|
1113
|
-
...(options.line ? { line: options.line } : {}),
|
|
1114
|
-
...(options.lineEnd ? { lineEnd: options.lineEnd } : {}),
|
|
1115
|
-
});
|
|
1116
|
-
}}
|
|
1117
|
-
/>
|
|
1118
|
-
</div>
|
|
1119
|
-
</div>
|
|
1120
|
-
${!isBrowseRoute && location !== "/general" && location !== "/doctor"
|
|
184
|
+
${!browseState.isBrowseRoute
|
|
1121
185
|
? html`
|
|
1122
186
|
<${Switch}>
|
|
187
|
+
<${Route} path="/general">
|
|
188
|
+
<${GeneralRoute}
|
|
189
|
+
statusData=${controllerState.sharedStatus}
|
|
190
|
+
watchdogData=${controllerState.sharedWatchdogStatus}
|
|
191
|
+
doctorStatusData=${controllerState.sharedDoctorStatus}
|
|
192
|
+
doctorWarningDismissedUntilMs=${doctorWarningDismissedUntilMs}
|
|
193
|
+
onRefreshStatuses=${controllerActions.refreshSharedStatuses}
|
|
194
|
+
onSetLocation=${setLocation}
|
|
195
|
+
onNavigate=${browseActions.navigateToSubScreen}
|
|
196
|
+
restartingGateway=${controllerState.restartingGateway}
|
|
197
|
+
onRestartGateway=${controllerActions.handleGatewayRestart}
|
|
198
|
+
restartSignal=${controllerState.gatewayRestartSignal}
|
|
199
|
+
openclawUpdateInProgress=${controllerState.openclawUpdateInProgress}
|
|
200
|
+
onOpenclawVersionActionComplete=${controllerActions.handleOpenclawVersionActionComplete}
|
|
201
|
+
onOpenclawUpdate=${controllerActions.handleOpenclawUpdate}
|
|
202
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
203
|
+
onDismissDoctorWarning=${() =>
|
|
204
|
+
setDoctorWarningDismissedUntilMs(Date.now() + kOneWeekMs)}
|
|
205
|
+
/>
|
|
206
|
+
</${Route}>
|
|
207
|
+
<${Route} path="/doctor">
|
|
208
|
+
<${DoctorRoute} onNavigateToBrowseFile=${browseActions.navigateToBrowseFile} />
|
|
209
|
+
</${Route}>
|
|
1123
210
|
<${Route} path="/telegram">
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
211
|
+
<${TelegramRoute} onBack=${browseActions.exitSubScreen} />
|
|
212
|
+
</${Route}>
|
|
213
|
+
<${Route} path="/models">
|
|
214
|
+
<${ModelsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
|
|
1127
215
|
</${Route}>
|
|
1128
216
|
<${Route} path="/providers">
|
|
1129
|
-
|
|
1130
|
-
<${Providers} onRestartRequired=${setRestartRequired} />
|
|
1131
|
-
</div>
|
|
217
|
+
<${RouteRedirect} to="/models" />
|
|
1132
218
|
</${Route}>
|
|
1133
219
|
<${Route} path="/watchdog">
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
onOpenclawUpdate=${handleOpenclawUpdate}
|
|
1146
|
-
/>
|
|
1147
|
-
</div>
|
|
220
|
+
<${WatchdogRoute}
|
|
221
|
+
statusData=${controllerState.sharedStatus}
|
|
222
|
+
watchdogStatus=${controllerState.sharedWatchdogStatus}
|
|
223
|
+
onRefreshStatuses=${controllerActions.refreshSharedStatuses}
|
|
224
|
+
restartingGateway=${controllerState.restartingGateway}
|
|
225
|
+
onRestartGateway=${controllerActions.handleGatewayRestart}
|
|
226
|
+
restartSignal=${controllerState.gatewayRestartSignal}
|
|
227
|
+
openclawUpdateInProgress=${controllerState.openclawUpdateInProgress}
|
|
228
|
+
onOpenclawVersionActionComplete=${controllerActions.handleOpenclawVersionActionComplete}
|
|
229
|
+
onOpenclawUpdate=${controllerActions.handleOpenclawUpdate}
|
|
230
|
+
/>
|
|
1148
231
|
</${Route}>
|
|
1149
232
|
<${Route} path="/usage/:sessionId">
|
|
1150
233
|
${(params) => html`
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
setLocation(`/usage/${encodeURIComponent(String(id || ""))}`)}
|
|
1156
|
-
onBackToSessions=${() => setLocation("/usage")}
|
|
1157
|
-
/>
|
|
1158
|
-
</div>
|
|
234
|
+
<${UsageRoute}
|
|
235
|
+
sessionId=${decodeURIComponent(params.sessionId || "")}
|
|
236
|
+
onSetLocation=${setLocation}
|
|
237
|
+
/>
|
|
1159
238
|
`}
|
|
1160
239
|
</${Route}>
|
|
1161
240
|
<${Route} path="/usage">
|
|
1162
|
-
|
|
1163
|
-
<${UsageTab}
|
|
1164
|
-
onSelectSession=${(id) =>
|
|
1165
|
-
setLocation(`/usage/${encodeURIComponent(String(id || ""))}`)}
|
|
1166
|
-
onBackToSessions=${() => setLocation("/usage")}
|
|
1167
|
-
/>
|
|
1168
|
-
</div>
|
|
241
|
+
<${UsageRoute} onSetLocation=${setLocation} />
|
|
1169
242
|
</${Route}>
|
|
1170
243
|
<${Route} path="/envars">
|
|
1171
|
-
|
|
1172
|
-
<${Envars} onRestartRequired=${setRestartRequired} />
|
|
1173
|
-
</div>
|
|
244
|
+
<${EnvarsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
|
|
1174
245
|
</${Route}>
|
|
1175
246
|
<${Route} path="/webhooks/:hookName">
|
|
1176
|
-
${(params) =>
|
|
1177
|
-
|
|
247
|
+
${(params) => html`
|
|
248
|
+
<${WebhooksRoute}
|
|
249
|
+
hookName=${decodeURIComponent(params.hookName || "")}
|
|
250
|
+
routeHistoryRef=${browseState.routeHistoryRef}
|
|
251
|
+
getCurrentPath=${getHashRouterPath}
|
|
252
|
+
onSetLocation=${setLocation}
|
|
253
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
254
|
+
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
255
|
+
/>
|
|
256
|
+
`}
|
|
1178
257
|
</${Route}>
|
|
1179
258
|
<${Route} path="/webhooks">
|
|
1180
|
-
|
|
259
|
+
<${WebhooksRoute}
|
|
260
|
+
routeHistoryRef=${browseState.routeHistoryRef}
|
|
261
|
+
getCurrentPath=${getHashRouterPath}
|
|
262
|
+
onSetLocation=${setLocation}
|
|
263
|
+
onRestartRequired=${controllerActions.setRestartRequired}
|
|
264
|
+
onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
|
|
265
|
+
/>
|
|
1181
266
|
</${Route}>
|
|
1182
267
|
<${Route}>
|
|
1183
268
|
<${RouteRedirect} to="/general" />
|
|
@@ -1194,8 +279,8 @@ const App = () => {
|
|
|
1194
279
|
|
|
1195
280
|
<div class="app-statusbar">
|
|
1196
281
|
<div class="statusbar-left">
|
|
1197
|
-
${acVersion
|
|
1198
|
-
? html`<span style="color: var(--text-muted)">v${acVersion}</span>`
|
|
282
|
+
${controllerState.acVersion
|
|
283
|
+
? html`<span style="color: var(--text-muted)">v${controllerState.acVersion}</span>`
|
|
1199
284
|
: null}
|
|
1200
285
|
</div>
|
|
1201
286
|
<div class="statusbar-right">
|