@chrysb/alphaclaw 0.2.3 → 0.3.1
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/LICENSE +21 -0
- package/README.md +179 -0
- package/bin/alphaclaw.js +79 -0
- package/lib/public/css/shell.css +57 -2
- package/lib/public/css/theme.css +231 -0
- package/lib/public/js/app.js +330 -89
- package/lib/public/js/components/action-button.js +92 -0
- package/lib/public/js/components/channels.js +16 -7
- package/lib/public/js/components/confirm-dialog.js +25 -19
- package/lib/public/js/components/credentials-modal.js +32 -23
- package/lib/public/js/components/device-pairings.js +15 -2
- package/lib/public/js/components/envars.js +22 -65
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/gateway.js +139 -32
- package/lib/public/js/components/global-restart-banner.js +31 -0
- package/lib/public/js/components/google.js +9 -9
- package/lib/public/js/components/icons.js +19 -0
- package/lib/public/js/components/info-tooltip.js +18 -0
- package/lib/public/js/components/loading-spinner.js +32 -0
- package/lib/public/js/components/modal-shell.js +42 -0
- package/lib/public/js/components/models.js +34 -29
- package/lib/public/js/components/onboarding/welcome-form-step.js +45 -32
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
- package/lib/public/js/components/onboarding/welcome-setup-step.js +7 -24
- package/lib/public/js/components/page-header.js +13 -0
- package/lib/public/js/components/pairings.js +15 -2
- package/lib/public/js/components/providers.js +216 -142
- package/lib/public/js/components/scope-picker.js +1 -1
- package/lib/public/js/components/secret-input.js +1 -0
- package/lib/public/js/components/telegram-workspace.js +37 -49
- package/lib/public/js/components/toast.js +34 -5
- package/lib/public/js/components/toggle-switch.js +25 -0
- package/lib/public/js/components/update-action-button.js +13 -53
- package/lib/public/js/components/watchdog-tab.js +312 -0
- package/lib/public/js/components/webhooks.js +1010 -0
- package/lib/public/js/components/welcome.js +2 -1
- package/lib/public/js/lib/api.js +102 -1
- package/lib/public/js/lib/model-config.js +0 -5
- package/lib/server/alphaclaw-version.js +5 -3
- package/lib/server/constants.js +35 -0
- package/lib/server/discord-api.js +48 -0
- package/lib/server/gateway.js +64 -4
- package/lib/server/log-writer.js +102 -0
- package/lib/server/onboarding/github.js +21 -1
- package/lib/server/openclaw-version.js +2 -6
- package/lib/server/restart-required-state.js +86 -0
- package/lib/server/routes/auth.js +9 -4
- package/lib/server/routes/proxy.js +12 -14
- package/lib/server/routes/system.js +61 -15
- package/lib/server/routes/telegram.js +17 -48
- package/lib/server/routes/watchdog.js +68 -0
- package/lib/server/routes/webhooks.js +214 -0
- package/lib/server/telegram-api.js +11 -0
- package/lib/server/watchdog-db.js +148 -0
- package/lib/server/watchdog-notify.js +93 -0
- package/lib/server/watchdog.js +585 -0
- package/lib/server/webhook-middleware.js +195 -0
- package/lib/server/webhooks-db.js +265 -0
- package/lib/server/webhooks.js +238 -0
- package/lib/server.js +119 -4
- package/lib/setup/core-prompts/AGENTS.md +84 -0
- package/lib/setup/core-prompts/TOOLS.md +13 -0
- package/lib/setup/core-prompts/UI-DRY-OPPORTUNITIES.md +50 -0
- package/lib/setup/gitignore +2 -0
- package/package.json +11 -1
package/lib/public/js/app.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { h, render } from "https://esm.sh/preact";
|
|
2
2
|
import { useState, useEffect, useRef, useCallback } from "https://esm.sh/preact/hooks";
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
|
+
import { Router, Route, Switch, useLocation } from "https://esm.sh/wouter-preact";
|
|
4
5
|
import {
|
|
5
6
|
fetchStatus,
|
|
6
7
|
fetchPairings,
|
|
@@ -16,6 +17,10 @@ import {
|
|
|
16
17
|
updateSyncCron,
|
|
17
18
|
fetchAlphaclawVersion,
|
|
18
19
|
updateAlphaclaw,
|
|
20
|
+
fetchRestartStatus,
|
|
21
|
+
restartGateway,
|
|
22
|
+
fetchWatchdogStatus,
|
|
23
|
+
triggerWatchdogRepair,
|
|
19
24
|
} from "./lib/api.js";
|
|
20
25
|
import { usePolling } from "./hooks/usePolling.js";
|
|
21
26
|
import { Gateway } from "./components/gateway.js";
|
|
@@ -27,21 +32,69 @@ import { Features } from "./components/features.js";
|
|
|
27
32
|
import { Providers } from "./components/providers.js";
|
|
28
33
|
import { Welcome } from "./components/welcome.js";
|
|
29
34
|
import { Envars } from "./components/envars.js";
|
|
35
|
+
import { Webhooks } from "./components/webhooks.js";
|
|
30
36
|
import { ToastContainer, showToast } from "./components/toast.js";
|
|
31
37
|
import { TelegramWorkspace } from "./components/telegram-workspace.js";
|
|
32
38
|
import { ChevronDownIcon } from "./components/icons.js";
|
|
33
39
|
import { UpdateActionButton } from "./components/update-action-button.js";
|
|
40
|
+
import { GlobalRestartBanner } from "./components/global-restart-banner.js";
|
|
41
|
+
import { LoadingSpinner } from "./components/loading-spinner.js";
|
|
42
|
+
import { WatchdogTab } from "./components/watchdog-tab.js";
|
|
34
43
|
const html = htm.bind(h);
|
|
35
|
-
const kUiTabs = ["general", "providers", "envars"];
|
|
36
|
-
const kSubScreens = ["telegram"];
|
|
37
44
|
const kDefaultUiTab = "general";
|
|
38
45
|
|
|
39
|
-
const
|
|
46
|
+
const getHashPath = () => {
|
|
47
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
48
|
+
if (!hash) return `/${kDefaultUiTab}`;
|
|
49
|
+
return hash.startsWith("/") ? hash : `/${hash}`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const useHashLocation = () => {
|
|
53
|
+
const [location, setLocationState] = useState(getHashPath);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const onHashChange = () => setLocationState(getHashPath());
|
|
57
|
+
window.addEventListener("hashchange", onHashChange);
|
|
58
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const setLocation = useCallback((to) => {
|
|
62
|
+
const normalized = to.startsWith("/") ? to : `/${to}`;
|
|
63
|
+
const nextHash = `#${normalized}`;
|
|
64
|
+
if (window.location.hash !== nextHash) {
|
|
65
|
+
window.location.hash = normalized;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setLocationState(normalized);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return [location, setLocation];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const RouteRedirect = ({ to }) => {
|
|
75
|
+
const [, setLocation] = useLocation();
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
setLocation(to);
|
|
78
|
+
}, [to, setLocation]);
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const GeneralTab = ({
|
|
83
|
+
statusData = null,
|
|
84
|
+
watchdogData = null,
|
|
85
|
+
onRefreshStatuses = () => {},
|
|
86
|
+
onSwitchTab,
|
|
87
|
+
onNavigate,
|
|
88
|
+
isActive,
|
|
89
|
+
restartingGateway,
|
|
90
|
+
onRestartGateway,
|
|
91
|
+
restartSignal = 0,
|
|
92
|
+
}) => {
|
|
40
93
|
const [googleKey, setGoogleKey] = useState(0);
|
|
41
94
|
const [dashboardLoading, setDashboardLoading] = useState(false);
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
95
|
+
const [repairingWatchdog, setRepairingWatchdog] = useState(false);
|
|
96
|
+
const status = statusData;
|
|
97
|
+
const watchdogStatus = watchdogData;
|
|
45
98
|
const gatewayStatus = status?.gateway ?? null;
|
|
46
99
|
const channels = status?.channels ?? null;
|
|
47
100
|
const repo = status?.repo || null;
|
|
@@ -67,18 +120,10 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
67
120
|
);
|
|
68
121
|
const pending = pairingsPoll.data || [];
|
|
69
122
|
|
|
70
|
-
// Poll status faster when gateway isn't running yet
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (!gatewayStatus || gatewayStatus !== "running") {
|
|
73
|
-
const id = setInterval(statusPoll.refresh, 3000);
|
|
74
|
-
return () => clearInterval(id);
|
|
75
|
-
}
|
|
76
|
-
}, [gatewayStatus, statusPoll.refresh]);
|
|
77
|
-
|
|
78
123
|
const refreshAfterAction = () => {
|
|
79
124
|
setTimeout(pairingsPoll.refresh, 500);
|
|
80
125
|
setTimeout(pairingsPoll.refresh, 2000);
|
|
81
|
-
setTimeout(
|
|
126
|
+
setTimeout(onRefreshStatuses, 3000);
|
|
82
127
|
};
|
|
83
128
|
|
|
84
129
|
const handleApprove = async (id, channel) => {
|
|
@@ -114,7 +159,7 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
114
159
|
};
|
|
115
160
|
|
|
116
161
|
const fullRefresh = () => {
|
|
117
|
-
|
|
162
|
+
onRefreshStatuses();
|
|
118
163
|
pairingsPoll.refresh();
|
|
119
164
|
devicePoll.refresh();
|
|
120
165
|
setGoogleKey((k) => k + 1);
|
|
@@ -125,6 +170,33 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
125
170
|
fullRefresh();
|
|
126
171
|
}, [isActive]);
|
|
127
172
|
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!restartSignal || !isActive) return;
|
|
175
|
+
onRefreshStatuses();
|
|
176
|
+
pairingsPoll.refresh();
|
|
177
|
+
devicePoll.refresh();
|
|
178
|
+
const t1 = setTimeout(() => {
|
|
179
|
+
onRefreshStatuses();
|
|
180
|
+
pairingsPoll.refresh();
|
|
181
|
+
devicePoll.refresh();
|
|
182
|
+
}, 1200);
|
|
183
|
+
const t2 = setTimeout(() => {
|
|
184
|
+
onRefreshStatuses();
|
|
185
|
+
pairingsPoll.refresh();
|
|
186
|
+
devicePoll.refresh();
|
|
187
|
+
}, 3500);
|
|
188
|
+
return () => {
|
|
189
|
+
clearTimeout(t1);
|
|
190
|
+
clearTimeout(t2);
|
|
191
|
+
};
|
|
192
|
+
}, [
|
|
193
|
+
restartSignal,
|
|
194
|
+
isActive,
|
|
195
|
+
onRefreshStatuses,
|
|
196
|
+
pairingsPoll.refresh,
|
|
197
|
+
devicePoll.refresh,
|
|
198
|
+
]);
|
|
199
|
+
|
|
128
200
|
useEffect(() => {
|
|
129
201
|
if (!syncCron) return;
|
|
130
202
|
setSyncCronEnabled(syncCron.enabled !== false);
|
|
@@ -147,7 +219,7 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
147
219
|
if (!data.ok)
|
|
148
220
|
throw new Error(data.error || "Could not save sync settings");
|
|
149
221
|
showToast("Sync schedule updated", "success");
|
|
150
|
-
|
|
222
|
+
onRefreshStatuses();
|
|
151
223
|
} catch (err) {
|
|
152
224
|
showToast(err.message || "Could not save sync settings", "error");
|
|
153
225
|
}
|
|
@@ -155,10 +227,35 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
155
227
|
};
|
|
156
228
|
|
|
157
229
|
const syncCronStatusText = syncCronEnabled ? "Enabled" : "Disabled";
|
|
230
|
+
const handleWatchdogRepair = async () => {
|
|
231
|
+
if (repairingWatchdog) return;
|
|
232
|
+
setRepairingWatchdog(true);
|
|
233
|
+
try {
|
|
234
|
+
const data = await triggerWatchdogRepair();
|
|
235
|
+
if (!data.ok) throw new Error(data.error || "Repair failed");
|
|
236
|
+
showToast("Repair triggered", "success");
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
onRefreshStatuses();
|
|
239
|
+
}, 800);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
showToast(err.message || "Could not run repair", "error");
|
|
242
|
+
} finally {
|
|
243
|
+
setRepairingWatchdog(false);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
158
246
|
|
|
159
247
|
return html`
|
|
160
248
|
<div class="space-y-4">
|
|
161
|
-
<${Gateway}
|
|
249
|
+
<${Gateway}
|
|
250
|
+
status=${gatewayStatus}
|
|
251
|
+
openclawVersion=${openclawVersion}
|
|
252
|
+
restarting=${restartingGateway}
|
|
253
|
+
onRestart=${onRestartGateway}
|
|
254
|
+
watchdogStatus=${watchdogStatus}
|
|
255
|
+
onOpenWatchdog=${() => onSwitchTab("watchdog")}
|
|
256
|
+
onRepair=${handleWatchdogRepair}
|
|
257
|
+
repairing=${repairingWatchdog}
|
|
258
|
+
/>
|
|
162
259
|
<${Channels} channels=${channels} onSwitchTab=${onSwitchTab} onNavigate=${onNavigate} />
|
|
163
260
|
<${Pairings}
|
|
164
261
|
pending=${pending}
|
|
@@ -279,17 +376,9 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
279
376
|
`;
|
|
280
377
|
};
|
|
281
378
|
|
|
282
|
-
|
|
379
|
+
const App = () => {
|
|
283
380
|
const [onboarded, setOnboarded] = useState(null);
|
|
284
|
-
const [
|
|
285
|
-
const hash = window.location.hash.replace("#", "");
|
|
286
|
-
if (kSubScreens.includes(hash)) return kDefaultUiTab;
|
|
287
|
-
return kUiTabs.includes(hash) ? hash : kDefaultUiTab;
|
|
288
|
-
});
|
|
289
|
-
const [subScreen, setSubScreen] = useState(() => {
|
|
290
|
-
const hash = window.location.hash.replace("#", "");
|
|
291
|
-
return kSubScreens.includes(hash) ? hash : null;
|
|
292
|
-
});
|
|
381
|
+
const [location, setLocation] = useLocation();
|
|
293
382
|
const [acVersion, setAcVersion] = useState(null);
|
|
294
383
|
const [acLatest, setAcLatest] = useState(null);
|
|
295
384
|
const [acHasUpdate, setAcHasUpdate] = useState(false);
|
|
@@ -299,7 +388,23 @@ function App() {
|
|
|
299
388
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
300
389
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
301
390
|
const [mobileTopbarScrolled, setMobileTopbarScrolled] = useState(false);
|
|
391
|
+
const [restartRequired, setRestartRequired] = useState(false);
|
|
392
|
+
const [restartingGateway, setRestartingGateway] = useState(false);
|
|
393
|
+
const [gatewayRestartSignal, setGatewayRestartSignal] = useState(0);
|
|
394
|
+
const [statusPollCadenceMs, setStatusPollCadenceMs] = useState(15000);
|
|
302
395
|
const menuRef = useRef(null);
|
|
396
|
+
const sharedStatusPoll = usePolling(fetchStatus, statusPollCadenceMs, {
|
|
397
|
+
enabled: onboarded === true,
|
|
398
|
+
});
|
|
399
|
+
const sharedWatchdogPoll = usePolling(fetchWatchdogStatus, statusPollCadenceMs, {
|
|
400
|
+
enabled: onboarded === true,
|
|
401
|
+
});
|
|
402
|
+
const sharedStatus = sharedStatusPoll.data || null;
|
|
403
|
+
const sharedWatchdogStatus = sharedWatchdogPoll.data?.status || null;
|
|
404
|
+
const refreshSharedStatuses = useCallback(() => {
|
|
405
|
+
sharedStatusPoll.refresh();
|
|
406
|
+
sharedWatchdogPoll.refresh();
|
|
407
|
+
}, [sharedStatusPoll.refresh, sharedWatchdogPoll.refresh]);
|
|
303
408
|
|
|
304
409
|
const closeMenu = useCallback((e) => {
|
|
305
410
|
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
@@ -323,10 +428,6 @@ function App() {
|
|
|
323
428
|
.catch(() => {});
|
|
324
429
|
}, []);
|
|
325
430
|
|
|
326
|
-
useEffect(() => {
|
|
327
|
-
history.replaceState(null, "", `#${subScreen || tab}`);
|
|
328
|
-
}, [tab, subScreen]);
|
|
329
|
-
|
|
330
431
|
useEffect(() => {
|
|
331
432
|
if (!mobileSidebarOpen) return;
|
|
332
433
|
const previousOverflow = document.body.style.overflow;
|
|
@@ -356,6 +457,72 @@ function App() {
|
|
|
356
457
|
};
|
|
357
458
|
}, [onboarded]);
|
|
358
459
|
|
|
460
|
+
const refreshRestartStatus = useCallback(async () => {
|
|
461
|
+
if (!onboarded) return;
|
|
462
|
+
try {
|
|
463
|
+
const data = await fetchRestartStatus();
|
|
464
|
+
setRestartRequired(!!data.restartRequired);
|
|
465
|
+
setRestartingGateway(!!data.restartInProgress);
|
|
466
|
+
} catch {}
|
|
467
|
+
}, [onboarded]);
|
|
468
|
+
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (!onboarded) return;
|
|
471
|
+
refreshRestartStatus();
|
|
472
|
+
}, [onboarded, refreshRestartStatus]);
|
|
473
|
+
|
|
474
|
+
useEffect(() => {
|
|
475
|
+
if (onboarded !== true) return;
|
|
476
|
+
const inStatusView =
|
|
477
|
+
location.startsWith("/general") || location.startsWith("/watchdog");
|
|
478
|
+
const gatewayStatus = sharedStatus?.gateway ?? null;
|
|
479
|
+
const watchdogHealth = String(sharedWatchdogStatus?.health || "").toLowerCase();
|
|
480
|
+
const watchdogLifecycle = String(sharedWatchdogStatus?.lifecycle || "").toLowerCase();
|
|
481
|
+
const shouldFastPollWatchdog =
|
|
482
|
+
watchdogHealth === "unknown" ||
|
|
483
|
+
watchdogLifecycle === "restarting" ||
|
|
484
|
+
watchdogLifecycle === "stopped" ||
|
|
485
|
+
!!sharedWatchdogStatus?.operationInProgress;
|
|
486
|
+
const shouldFastPollGateway = !gatewayStatus || gatewayStatus !== "running";
|
|
487
|
+
const nextCadenceMs =
|
|
488
|
+
inStatusView && (shouldFastPollWatchdog || shouldFastPollGateway) ? 2000 : 15000;
|
|
489
|
+
setStatusPollCadenceMs((currentCadenceMs) =>
|
|
490
|
+
currentCadenceMs === nextCadenceMs ? currentCadenceMs : nextCadenceMs,
|
|
491
|
+
);
|
|
492
|
+
}, [
|
|
493
|
+
onboarded,
|
|
494
|
+
location,
|
|
495
|
+
sharedStatus?.gateway,
|
|
496
|
+
sharedWatchdogStatus?.health,
|
|
497
|
+
sharedWatchdogStatus?.lifecycle,
|
|
498
|
+
sharedWatchdogStatus?.operationInProgress,
|
|
499
|
+
]);
|
|
500
|
+
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
if (!onboarded || (!restartRequired && !restartingGateway)) return;
|
|
503
|
+
const id = setInterval(refreshRestartStatus, 2000);
|
|
504
|
+
return () => clearInterval(id);
|
|
505
|
+
}, [onboarded, restartRequired, restartingGateway, refreshRestartStatus]);
|
|
506
|
+
|
|
507
|
+
const handleGatewayRestart = useCallback(async () => {
|
|
508
|
+
if (restartingGateway) return;
|
|
509
|
+
setRestartingGateway(true);
|
|
510
|
+
try {
|
|
511
|
+
const data = await restartGateway();
|
|
512
|
+
if (!data?.ok) throw new Error(data?.error || "Gateway restart failed");
|
|
513
|
+
setRestartRequired(!!data.restartRequired);
|
|
514
|
+
setGatewayRestartSignal(Date.now());
|
|
515
|
+
refreshSharedStatuses();
|
|
516
|
+
showToast("Gateway restarted", "success");
|
|
517
|
+
setTimeout(refreshRestartStatus, 800);
|
|
518
|
+
} catch (err) {
|
|
519
|
+
showToast(err.message || "Restart failed", "error");
|
|
520
|
+
setTimeout(refreshRestartStatus, 800);
|
|
521
|
+
} finally {
|
|
522
|
+
setRestartingGateway(false);
|
|
523
|
+
}
|
|
524
|
+
}, [restartingGateway, refreshRestartStatus, refreshSharedStatuses]);
|
|
525
|
+
|
|
359
526
|
const handleAcUpdate = async () => {
|
|
360
527
|
if (acUpdating) return;
|
|
361
528
|
setAcUpdating(true);
|
|
@@ -380,26 +547,10 @@ function App() {
|
|
|
380
547
|
class="min-h-screen flex items-center justify-center"
|
|
381
548
|
style="position: relative; z-index: 1"
|
|
382
549
|
>
|
|
383
|
-
|
|
384
|
-
|
|
550
|
+
<${LoadingSpinner}
|
|
551
|
+
className="h-6 w-6"
|
|
385
552
|
style="color: var(--text-muted)"
|
|
386
|
-
|
|
387
|
-
fill="none"
|
|
388
|
-
>
|
|
389
|
-
<circle
|
|
390
|
-
class="opacity-25"
|
|
391
|
-
cx="12"
|
|
392
|
-
cy="12"
|
|
393
|
-
r="10"
|
|
394
|
-
stroke="currentColor"
|
|
395
|
-
stroke-width="4"
|
|
396
|
-
/>
|
|
397
|
-
<path
|
|
398
|
-
class="opacity-75"
|
|
399
|
-
fill="currentColor"
|
|
400
|
-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
401
|
-
/>
|
|
402
|
-
</svg>
|
|
553
|
+
/>
|
|
403
554
|
</div>
|
|
404
555
|
<${ToastContainer} />
|
|
405
556
|
`;
|
|
@@ -418,11 +569,11 @@ function App() {
|
|
|
418
569
|
}
|
|
419
570
|
|
|
420
571
|
const navigateToSubScreen = (screen) => {
|
|
421
|
-
|
|
572
|
+
setLocation(`/${screen}`);
|
|
422
573
|
setMobileSidebarOpen(false);
|
|
423
574
|
};
|
|
424
575
|
const exitSubScreen = () => {
|
|
425
|
-
|
|
576
|
+
setLocation(`/${kDefaultUiTab}`);
|
|
426
577
|
setMobileSidebarOpen(false);
|
|
427
578
|
};
|
|
428
579
|
const handleAppContentScroll = (e) => {
|
|
@@ -432,14 +583,55 @@ function App() {
|
|
|
432
583
|
);
|
|
433
584
|
};
|
|
434
585
|
|
|
435
|
-
const
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
|
|
586
|
+
const kNavSections = [
|
|
587
|
+
{
|
|
588
|
+
label: "Status",
|
|
589
|
+
items: [
|
|
590
|
+
{ id: "general", label: "General" },
|
|
591
|
+
{ id: "watchdog", label: "Watchdog" },
|
|
592
|
+
],
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
label: "Config",
|
|
596
|
+
items: [
|
|
597
|
+
{ id: "providers", label: "Providers" },
|
|
598
|
+
{ id: "envars", label: "Envars" },
|
|
599
|
+
{ id: "webhooks", label: "Webhooks" },
|
|
600
|
+
],
|
|
601
|
+
},
|
|
439
602
|
];
|
|
440
603
|
|
|
604
|
+
const selectedNavId =
|
|
605
|
+
location === "/telegram"
|
|
606
|
+
? ""
|
|
607
|
+
: location.startsWith("/providers")
|
|
608
|
+
? "providers"
|
|
609
|
+
: location.startsWith("/watchdog")
|
|
610
|
+
? "watchdog"
|
|
611
|
+
: location.startsWith("/envars")
|
|
612
|
+
? "envars"
|
|
613
|
+
: location.startsWith("/webhooks")
|
|
614
|
+
? "webhooks"
|
|
615
|
+
: "general";
|
|
616
|
+
|
|
617
|
+
const renderWebhooks = (hookName = "") => html`
|
|
618
|
+
<div class="pt-4">
|
|
619
|
+
<${Webhooks}
|
|
620
|
+
selectedHookName=${hookName}
|
|
621
|
+
onSelectHook=${(name) => setLocation(`/webhooks/${encodeURIComponent(name)}`)}
|
|
622
|
+
onBackToList=${() => setLocation("/webhooks")}
|
|
623
|
+
onRestartRequired=${setRestartRequired}
|
|
624
|
+
/>
|
|
625
|
+
</div>
|
|
626
|
+
`;
|
|
627
|
+
|
|
441
628
|
return html`
|
|
442
629
|
<div class="app-shell">
|
|
630
|
+
<${GlobalRestartBanner}
|
|
631
|
+
visible=${restartRequired}
|
|
632
|
+
restarting=${restartingGateway}
|
|
633
|
+
onRestart=${handleGatewayRestart}
|
|
634
|
+
/>
|
|
443
635
|
<div class=${`app-sidebar ${mobileSidebarOpen ? "mobile-open" : ""}`}>
|
|
444
636
|
<div class="sidebar-brand">
|
|
445
637
|
<img src="./img/logo.svg" alt="" width="20" height="20" />
|
|
@@ -465,6 +657,10 @@ function App() {
|
|
|
465
657
|
e.preventDefault();
|
|
466
658
|
setMenuOpen(false);
|
|
467
659
|
await logout();
|
|
660
|
+
try {
|
|
661
|
+
window.localStorage.clear();
|
|
662
|
+
window.sessionStorage.clear();
|
|
663
|
+
} catch {}
|
|
468
664
|
window.location.href = "/login.html";
|
|
469
665
|
}}
|
|
470
666
|
>Log out</a>
|
|
@@ -473,23 +669,26 @@ function App() {
|
|
|
473
669
|
</div>
|
|
474
670
|
`}
|
|
475
671
|
</div>
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
672
|
+
${kNavSections.map(
|
|
673
|
+
(section) => html`
|
|
674
|
+
<div class="sidebar-label">${section.label}</div>
|
|
675
|
+
<nav class="sidebar-nav">
|
|
676
|
+
${section.items.map(
|
|
677
|
+
(item) => html`
|
|
678
|
+
<a
|
|
679
|
+
class=${selectedNavId === item.id ? "active" : ""}
|
|
680
|
+
onclick=${() => {
|
|
681
|
+
setLocation(`/${item.id}`);
|
|
682
|
+
setMobileSidebarOpen(false);
|
|
683
|
+
}}
|
|
684
|
+
>
|
|
685
|
+
${item.label}
|
|
686
|
+
</a>
|
|
687
|
+
`,
|
|
688
|
+
)}
|
|
689
|
+
</nav>
|
|
690
|
+
`,
|
|
691
|
+
)}
|
|
493
692
|
<div class="sidebar-footer">
|
|
494
693
|
${acHasUpdate && acLatest && !acDismissed
|
|
495
694
|
? html`
|
|
@@ -530,28 +729,64 @@ function App() {
|
|
|
530
729
|
</span>
|
|
531
730
|
</div>
|
|
532
731
|
<div class="max-w-2xl w-full mx-auto">
|
|
533
|
-
|
|
534
|
-
|
|
732
|
+
<${Switch}>
|
|
733
|
+
<${Route} path="/telegram">
|
|
535
734
|
<div class="pt-4">
|
|
536
735
|
<${TelegramWorkspace} onBack=${exitSubScreen} />
|
|
537
736
|
</div>
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
<div class="pt-4"
|
|
737
|
+
</${Route}>
|
|
738
|
+
<${Route} path="/general">
|
|
739
|
+
<div class="pt-4">
|
|
541
740
|
<${GeneralTab}
|
|
542
|
-
|
|
741
|
+
statusData=${sharedStatus}
|
|
742
|
+
watchdogData=${sharedWatchdogStatus}
|
|
743
|
+
onRefreshStatuses=${refreshSharedStatuses}
|
|
744
|
+
onSwitchTab=${(nextTab) => setLocation(`/${nextTab}`)}
|
|
543
745
|
onNavigate=${navigateToSubScreen}
|
|
544
|
-
isActive=${
|
|
746
|
+
isActive=${location === "/general"}
|
|
747
|
+
restartingGateway=${restartingGateway}
|
|
748
|
+
onRestartGateway=${handleGatewayRestart}
|
|
749
|
+
restartSignal=${gatewayRestartSignal}
|
|
545
750
|
/>
|
|
546
751
|
</div>
|
|
547
|
-
|
|
548
|
-
|
|
752
|
+
</${Route}>
|
|
753
|
+
<${Route} path="/providers">
|
|
754
|
+
<div class="pt-4">
|
|
755
|
+
<${Providers} onRestartRequired=${setRestartRequired} />
|
|
549
756
|
</div>
|
|
550
|
-
|
|
551
|
-
|
|
757
|
+
</${Route}>
|
|
758
|
+
<${Route} path="/watchdog">
|
|
759
|
+
<div class="pt-4">
|
|
760
|
+
<${WatchdogTab}
|
|
761
|
+
gatewayStatus=${sharedStatus?.gateway || null}
|
|
762
|
+
openclawVersion=${sharedStatus?.openclawVersion || null}
|
|
763
|
+
watchdogStatus=${sharedWatchdogStatus}
|
|
764
|
+
onRefreshStatuses=${refreshSharedStatuses}
|
|
765
|
+
restartingGateway=${restartingGateway}
|
|
766
|
+
onRestartGateway=${handleGatewayRestart}
|
|
767
|
+
restartSignal=${gatewayRestartSignal}
|
|
768
|
+
/>
|
|
552
769
|
</div>
|
|
553
|
-
|
|
770
|
+
</${Route}>
|
|
771
|
+
<${Route} path="/envars">
|
|
772
|
+
<div class="pt-4">
|
|
773
|
+
<${Envars} onRestartRequired=${setRestartRequired} />
|
|
774
|
+
</div>
|
|
775
|
+
</${Route}>
|
|
776
|
+
<${Route} path="/webhooks/:hookName">
|
|
777
|
+
${(params) => renderWebhooks(decodeURIComponent(params.hookName || ""))}
|
|
778
|
+
</${Route}>
|
|
779
|
+
<${Route} path="/webhooks">
|
|
780
|
+
${() => renderWebhooks("")}
|
|
781
|
+
</${Route}>
|
|
782
|
+
<${Route}>
|
|
783
|
+
<${RouteRedirect} to="/general" />
|
|
784
|
+
</${Route}>
|
|
785
|
+
</${Switch}>
|
|
554
786
|
</div>
|
|
787
|
+
<${ToastContainer}
|
|
788
|
+
className="fixed bottom-10 right-4 z-50 space-y-2 pointer-events-none"
|
|
789
|
+
/>
|
|
555
790
|
</div>
|
|
556
791
|
|
|
557
792
|
<div class="app-statusbar">
|
|
@@ -579,8 +814,14 @@ function App() {
|
|
|
579
814
|
</div>
|
|
580
815
|
</div>
|
|
581
816
|
</div>
|
|
582
|
-
<${ToastContainer} />
|
|
583
817
|
`;
|
|
584
|
-
}
|
|
818
|
+
};
|
|
585
819
|
|
|
586
|
-
render(
|
|
820
|
+
render(
|
|
821
|
+
html`
|
|
822
|
+
<${Router} hook=${useHashLocation}>
|
|
823
|
+
<${App} />
|
|
824
|
+
</${Router}>
|
|
825
|
+
`,
|
|
826
|
+
document.getElementById("app"),
|
|
827
|
+
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { LoadingSpinner } from "./loading-spinner.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
const kStaticToneClassByTone = {
|
|
8
|
+
primary: "ac-btn-cyan",
|
|
9
|
+
secondary: "ac-btn-secondary",
|
|
10
|
+
success: "ac-btn-green",
|
|
11
|
+
danger: "ac-btn-danger",
|
|
12
|
+
ghost: "ac-btn-ghost",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getToneClass = (tone, isInteractive) => {
|
|
16
|
+
if (tone === "neutral") {
|
|
17
|
+
return isInteractive
|
|
18
|
+
? "border border-border text-gray-500 hover:text-gray-300 hover:border-gray-500"
|
|
19
|
+
: "border border-border text-gray-500";
|
|
20
|
+
}
|
|
21
|
+
if (tone === "warning") {
|
|
22
|
+
return isInteractive
|
|
23
|
+
? "border border-yellow-500/35 text-yellow-400 bg-yellow-500/10 hover:border-yellow-400/60 hover:text-yellow-300 hover:bg-yellow-500/15"
|
|
24
|
+
: "border border-yellow-500/35 text-yellow-400 bg-yellow-500/10";
|
|
25
|
+
}
|
|
26
|
+
return kStaticToneClassByTone[tone] || kStaticToneClassByTone.primary;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const kSizeClassBySize = {
|
|
30
|
+
sm: "h-7 text-xs leading-none px-2.5 py-1 rounded-lg",
|
|
31
|
+
md: "h-9 text-sm font-medium leading-none px-4 rounded-xl",
|
|
32
|
+
lg: "h-10 text-sm font-medium leading-none px-5 rounded-lg",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ActionButton = ({
|
|
36
|
+
onClick,
|
|
37
|
+
type = "button",
|
|
38
|
+
disabled = false,
|
|
39
|
+
loading = false,
|
|
40
|
+
tone = "primary",
|
|
41
|
+
size = "sm",
|
|
42
|
+
idleLabel = "Action",
|
|
43
|
+
loadingLabel = "Working...",
|
|
44
|
+
loadingMode = "replace",
|
|
45
|
+
className = "",
|
|
46
|
+
}) => {
|
|
47
|
+
const isDisabled = disabled || loading;
|
|
48
|
+
const isInteractive = !isDisabled;
|
|
49
|
+
const toneClass = getToneClass(tone, isInteractive);
|
|
50
|
+
const sizeClass = kSizeClassBySize[size] || kSizeClassBySize.sm;
|
|
51
|
+
const loadingClass = loading
|
|
52
|
+
? `cursor-not-allowed ${
|
|
53
|
+
tone === "warning"
|
|
54
|
+
? "opacity-90 animate-pulse shadow-[0_0_0_1px_rgba(234,179,8,0.22),0_0_18px_rgba(234,179,8,0.12)]"
|
|
55
|
+
: "opacity-80"
|
|
56
|
+
}`
|
|
57
|
+
: "";
|
|
58
|
+
const spinnerSizeClass = size === "md" || size === "lg" ? "h-4 w-4" : "h-3 w-3";
|
|
59
|
+
const isInlineLoading = loadingMode === "inline";
|
|
60
|
+
const currentLabel = loading && !isInlineLoading ? loadingLabel : idleLabel;
|
|
61
|
+
|
|
62
|
+
return html`
|
|
63
|
+
<button
|
|
64
|
+
type=${type}
|
|
65
|
+
onclick=${onClick}
|
|
66
|
+
disabled=${isDisabled}
|
|
67
|
+
class="inline-flex items-center justify-center transition-colors whitespace-nowrap ${sizeClass} ${toneClass} ${loadingClass} ${className}"
|
|
68
|
+
>
|
|
69
|
+
${isInlineLoading
|
|
70
|
+
? html`
|
|
71
|
+
<span class="relative inline-flex items-center justify-center leading-none">
|
|
72
|
+
<span class=${loading ? "invisible" : ""}>${currentLabel}</span>
|
|
73
|
+
${loading
|
|
74
|
+
? html`
|
|
75
|
+
<span class="absolute inset-0 inline-flex items-center justify-center">
|
|
76
|
+
<${LoadingSpinner} className=${spinnerSizeClass} />
|
|
77
|
+
</span>
|
|
78
|
+
`
|
|
79
|
+
: null}
|
|
80
|
+
</span>
|
|
81
|
+
`
|
|
82
|
+
: loading
|
|
83
|
+
? html`
|
|
84
|
+
<span class="inline-flex items-center gap-1.5 leading-none">
|
|
85
|
+
<${LoadingSpinner} className=${spinnerSizeClass} />
|
|
86
|
+
${currentLabel}
|
|
87
|
+
</span>
|
|
88
|
+
`
|
|
89
|
+
: currentLabel}
|
|
90
|
+
</button>
|
|
91
|
+
`;
|
|
92
|
+
};
|