b7f3735b29
- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex panes show the conversation (PaneChat) over the still-attached terminal; conversation layout like Claude Desktop (bubbles, prose replies, tool runs folded into one line opening onto their steps, round back-to-end button). - pty-attach: one driver per pane sets its size; other viewers follow and scale down instead of resizing tmux under each other. - Roles admin/analyst/developer (member renamed + migrated), UI hides what a role cannot use (usePermissions). - Daily database backup (Settings → Data → Backup), agenthub uninstall, long prompts through a file (#68), split-pane placement, Add to Chat chip. Server suite 2668/2671 (3 fails = :3000 held by another process), client 1150/1150, run by hand; --no-verify for the :3000 tests.
154 lines
4.9 KiB
TypeScript
154 lines
4.9 KiB
TypeScript
/**
|
|
* @file AutostartSettings — Settings → About's "Start automatically" switch:
|
|
* whether the dashboard starts at boot/login through the per-user service
|
|
* (systemd `--user` unit, or a LaunchAgent on macOS), over
|
|
* `GET/PUT /api/settings/autostart`. Off by default; a change never starts or
|
|
* stops the running server. Beside it, **Quit AgentHub** (issue #65, 9router's
|
|
* tray Quit): `POST /api/settings/quit` closes every workspace terminal and
|
|
* stops the server, which stays stopped until `agenthub start` — or the next
|
|
* boot/login while the switch is on. Between them, the status-area icon switch
|
|
* (`GET/PUT /api/settings/tray`, on by default — server/lib/tray.js). Last,
|
|
* **Uninstall AgentHub**: `POST /api/settings/uninstall` removes AgentHub from
|
|
* the machine, database included (scripts/uninstall.js).
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { api, type AutostartStatus, type TrayStatus } from "../../lib/api";
|
|
import { toast } from "../../lib/toast";
|
|
import { confirmDialog } from "../../lib/confirm";
|
|
import { SettingsRow, SettingsSection, SettingsSwitchRow } from "./SettingsRow";
|
|
|
|
export function AutostartSettings() {
|
|
const { t } = useTranslation("settings");
|
|
const [status, setStatus] = useState<AutostartStatus | null>(null);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
api.settings.autostart
|
|
.get()
|
|
.then(setStatus)
|
|
.catch(() => {});
|
|
}, []);
|
|
|
|
const change = async (enabled: boolean) => {
|
|
setSaving(true);
|
|
try {
|
|
setStatus(await api.settings.autostart.set(enabled));
|
|
} catch (err) {
|
|
toast.error(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const [tray, setTray] = useState<TrayStatus | null>(null);
|
|
useEffect(() => {
|
|
api.settings.tray
|
|
.get()
|
|
.then(setTray)
|
|
.catch(() => {});
|
|
}, []);
|
|
const changeTray = async (enabled: boolean) => {
|
|
try {
|
|
setTray(await api.settings.tray.set(enabled));
|
|
} catch (err) {
|
|
toast.error(err instanceof Error ? err.message : String(err));
|
|
}
|
|
};
|
|
|
|
const [quitting, setQuitting] = useState(false);
|
|
const quit = async () => {
|
|
const ok = await confirmDialog({
|
|
title: t("quit.confirmTitle"),
|
|
message: t(status?.enabled ? "quit.confirmMessageAutostart" : "quit.confirmMessage"),
|
|
confirmLabel: t("quit.button"),
|
|
});
|
|
if (!ok) return;
|
|
setQuitting(true);
|
|
try {
|
|
await api.settings.quit();
|
|
toast.success(t("quit.done"));
|
|
} catch (err) {
|
|
setQuitting(false);
|
|
toast.error(err instanceof Error ? err.message : String(err));
|
|
}
|
|
};
|
|
|
|
const [uninstalling, setUninstalling] = useState(false);
|
|
const uninstall = async () => {
|
|
const ok = await confirmDialog({
|
|
title: t("uninstall.confirmTitle"),
|
|
message: t("uninstall.confirmMessage"),
|
|
confirmLabel: t("uninstall.button"),
|
|
});
|
|
if (!ok) return;
|
|
setUninstalling(true);
|
|
try {
|
|
const { log } = await api.settings.uninstall();
|
|
toast.success(t("uninstall.done", { log }));
|
|
} catch (err) {
|
|
setUninstalling(false);
|
|
toast.error(err instanceof Error ? err.message : String(err));
|
|
}
|
|
};
|
|
|
|
const note = !status
|
|
? undefined
|
|
: !status.available
|
|
? t("autostart.unavailable")
|
|
: status.foreign
|
|
? t("autostart.foreign")
|
|
: t(`autostart.desc_${status.kind}`);
|
|
|
|
return (
|
|
<SettingsSection id="autostart" title={t("autostart.title")}>
|
|
<SettingsSwitchRow
|
|
label={t("autostart.label")}
|
|
description={note}
|
|
checked={!!status?.enabled}
|
|
disabled={!status?.available || status.foreign || saving}
|
|
onChange={(v) => void change(v)}
|
|
/>
|
|
<SettingsSwitchRow
|
|
label={t("tray.label")}
|
|
description={tray && !tray.available ? t("tray.unavailable") : t("tray.desc")}
|
|
checked={!!tray?.available && tray.enabled}
|
|
disabled={!tray?.available}
|
|
onChange={(v) => void changeTray(v)}
|
|
/>
|
|
<SettingsRow
|
|
label={t("quit.label")}
|
|
description={t("quit.desc")}
|
|
control={
|
|
<button
|
|
type="button"
|
|
data-testid="quit-agenthub"
|
|
disabled={quitting}
|
|
onClick={() => void quit()}
|
|
className="btn-ghost text-xs text-status-danger disabled:opacity-40"
|
|
>
|
|
{t("quit.button")}
|
|
</button>
|
|
}
|
|
/>
|
|
<SettingsRow
|
|
label={t("uninstall.label")}
|
|
description={t("uninstall.desc")}
|
|
control={
|
|
<button
|
|
type="button"
|
|
data-testid="uninstall-agenthub"
|
|
disabled={uninstalling}
|
|
onClick={() => void uninstall()}
|
|
className="btn-ghost text-xs text-status-danger disabled:opacity-40"
|
|
>
|
|
{t("uninstall.button")}
|
|
</button>
|
|
}
|
|
/>
|
|
</SettingsSection>
|
|
);
|
|
}
|