feat(webui): add automation management view
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
createModelConfiguration,
|
||||
deleteSession,
|
||||
fetchFilePreview,
|
||||
fetchAutomations,
|
||||
fetchCliApps,
|
||||
fetchInstalledCliApps,
|
||||
fetchMcpPresets,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
listSlashCommands,
|
||||
loginProviderOAuth,
|
||||
logoutProviderOAuth,
|
||||
runAutomationAction,
|
||||
runCliAppAction,
|
||||
runMcpPresetAction,
|
||||
saveCustomMcpServer,
|
||||
@@ -99,6 +101,28 @@ describe("webui API helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("fetches workspace automations", async () => {
|
||||
await fetchAutomations("tok");
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"/api/webui/automations",
|
||||
expect.objectContaining({
|
||||
headers: { Authorization: "Bearer tok" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("serializes workspace automation actions", async () => {
|
||||
await runAutomationAction("tok", "disable", "job 1/2");
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"/api/webui/automations/disable?id=job+1%2F2",
|
||||
expect.objectContaining({
|
||||
headers: { Authorization: "Bearer tok" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("fetches the WebUI skill summary", async () => {
|
||||
await fetchSkills("tok");
|
||||
|
||||
|
||||
@@ -334,6 +334,96 @@ describe("App layout", () => {
|
||||
expect(screen.getByText(/Use GitHub CLI/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens Automations from the main sidebar", async () => {
|
||||
mockFetchRoutes({
|
||||
"/api/settings": baseSettingsPayload(),
|
||||
"/api/webui/automations": {
|
||||
jobs: [
|
||||
{
|
||||
id: "job-1",
|
||||
name: "Daily repo check",
|
||||
enabled: true,
|
||||
protected: false,
|
||||
delete_after_run: false,
|
||||
schedule: { kind: "every", every_ms: 86_400_000 },
|
||||
payload: {
|
||||
message: "Check the repo status",
|
||||
kind: "agent_turn",
|
||||
session_key: "websocket:chat-a",
|
||||
},
|
||||
state: {
|
||||
next_run_at_ms: Date.UTC(2026, 3, 17, 10, 0, 0),
|
||||
last_status: "ok",
|
||||
pending: false,
|
||||
run_history: [],
|
||||
},
|
||||
origin: {
|
||||
session_key: "websocket:chat-a",
|
||||
channel: "websocket",
|
||||
chat_id: "chat-a",
|
||||
title: "Release prep",
|
||||
preview: "Check release blockers",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "heartbeat",
|
||||
name: "heartbeat",
|
||||
enabled: true,
|
||||
protected: true,
|
||||
schedule: { kind: "every", every_ms: 60_000 },
|
||||
payload: { message: "", kind: "system_event" },
|
||||
state: { next_run_at_ms: null, pending: false, run_history: [] },
|
||||
origin: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
const automationsButton = within(sidebar).getByRole("button", {
|
||||
name: "Automations",
|
||||
});
|
||||
|
||||
fireEvent.click(automationsButton);
|
||||
|
||||
expect(await screen.findByText("Workspace automations")).toBeInTheDocument();
|
||||
expect(screen.getByText("Daily repo check")).toBeInTheDocument();
|
||||
expect(screen.getByText("Check the repo status")).toBeInTheDocument();
|
||||
expect(screen.getByText("Release prep")).toBeInTheDocument();
|
||||
expect(screen.getByText("heartbeat")).toBeInTheDocument();
|
||||
expect(within(sidebar).getByRole("button", { name: "Automations" })).toHaveAttribute(
|
||||
"aria-current",
|
||||
"page",
|
||||
);
|
||||
expect(document.title).toBe("Automations · nanobot");
|
||||
});
|
||||
|
||||
it("localizes the Automations surface", async () => {
|
||||
await i18n.changeLanguage("zh-CN");
|
||||
mockFetchRoutes({
|
||||
"/api/settings": baseSettingsPayload(),
|
||||
"/api/webui/automations": { jobs: [] },
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "侧边栏导航" });
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "自动任务" }));
|
||||
|
||||
expect(await screen.findByText("工作区自动任务")).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("heading", { name: "自动任务" }).length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("统一查看 cron 提醒、周期性 agent 任务、一次性自动任务和受保护的系统自动任务。")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "刷新" })).toBeInTheDocument();
|
||||
expect(screen.getByText("任务队列")).toBeInTheDocument();
|
||||
expect(screen.getByText("暂无自动任务。")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Workspace automations")).not.toBeInTheDocument();
|
||||
expect(document.title).toBe("自动任务 · nanobot");
|
||||
});
|
||||
|
||||
it("fully collapses the native host sidebar and previews it on hover", async () => {
|
||||
mockSessions = [
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ const SETTINGS_NAV_KEYS = [
|
||||
"image",
|
||||
"browser",
|
||||
"apps",
|
||||
"automations",
|
||||
"runtime",
|
||||
"advanced",
|
||||
];
|
||||
@@ -43,8 +44,21 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
|
||||
"settings.nav.models",
|
||||
"settings.nav.providers",
|
||||
"settings.nav.apps",
|
||||
"settings.nav.automations",
|
||||
"settings.nav.runtime",
|
||||
"settings.nav.advanced",
|
||||
"sidebar.automations",
|
||||
"settings.automations.title",
|
||||
"settings.automations.description",
|
||||
"settings.automations.refresh",
|
||||
"settings.automations.newInChat",
|
||||
"settings.automations.filters.active",
|
||||
"settings.automations.queue",
|
||||
"settings.automations.empty",
|
||||
"settings.automations.systemTask",
|
||||
"settings.automations.labels.schedule",
|
||||
"settings.automations.status.active",
|
||||
"settings.automations.deleteTitle",
|
||||
"settings.sections.interface",
|
||||
"settings.sections.localPreferences",
|
||||
"settings.sections.webSearch",
|
||||
|
||||
Reference in New Issue
Block a user