Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### 修复

- 隐藏无应用更新权限用户头像菜单中的“检查更新”入口
- 插件工具使用中文等非 ASCII 名称时 LLM 调用失败:主流 API 要求工具名匹配 `^[a-zA-Z0-9_-]{1,64}$`,现自动将非法名称转写为合法拼音名(`pypinyin` 缺失时退回下划线替换),冲突追加 `_2`/`_3` 后缀,并在工具描述前缀 `[原名: …]` 保留原名映射;`config_json.plugins` 配置键与插件内部仍使用原始名称,路由不受影响
- 修复聊天页在"生成中"时于输入框持续打字导致消息列表上下轻微抖动的问题:输入框高度测量改为在离屏克隆节点上进行,不再瞬态改变页面布局

Expand Down
53 changes: 53 additions & 0 deletions dashboard/src/components/AvatarDropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import { describe, expect, it, vi } from "vitest";
import type { OctopUser } from "../api/modules/auth";
import { CurrentUserProvider } from "../hooks/useCurrentUser";
import AvatarDropdown from "./AvatarDropdown";

function buildUser(
role: OctopUser["role"],
permissions: string[] = [],
): OctopUser {
return {
id: 1,
username: role,
role,
display_name: role === "admin" ? "Admin User" : "Regular User",
locale: "en",
permissions,
};
}

async function openAccountMenu(user: OctopUser) {
const pointer = userEvent.setup();
render(
<MemoryRouter>
<CurrentUserProvider user={user} setUser={vi.fn()}>
<AvatarDropdown user={user} placement="sidebar" />
</CurrentUserProvider>
</MemoryRouter>,
);

await pointer.click(
screen.getByRole("button", { name: new RegExp(user.display_name!) }),
);
}

describe("<AvatarDropdown /> update access", () => {
it("hides Check for updates from users without update permission", async () => {
await openAccountMenu(buildUser("user"));

expect(screen.queryByText("account.checkUpdates")).not.toBeInTheDocument();
});

it.each([
["an admin", buildUser("admin")],
["an explicitly authorized user", buildUser("user", ["update"])],
])("shows Check for updates to %s", async (_label, user) => {
await openAccountMenu(user);

expect(await screen.findByText("account.checkUpdates")).toBeInTheDocument();
});
});
25 changes: 14 additions & 11 deletions dashboard/src/components/AvatarDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { preferencesApi } from "../api/modules/preferences";
import { clearAuthToken } from "../api/request";
import { applyGuestLocale, applyUserLocale } from "../utils/locale";
import { apiErrorMessage } from "../utils/apiError";
import { userCan } from "../utils/permissions";
import {
MIN_PASSWORD_LENGTH,
passwordPolicyIssue,
Expand Down Expand Up @@ -256,17 +257,19 @@ export default function AvatarDropdown({
<span>{t("account.changePassword")}</span>
</button>

<button
type="button"
className={styles.menuItem}
onClick={() => {
setMenuOpen(false);
navigate("/admin/advanced?tab=updates");
}}
>
<RefreshCw size={16} strokeWidth={1.8} />
<span>{t("account.checkUpdates")}</span>
</button>
{userCan(user, "update") && (
<button
type="button"
className={styles.menuItem}
onClick={() => {
setMenuOpen(false);
navigate("/admin/advanced?tab=updates");
}}
>
<RefreshCw size={16} strokeWidth={1.8} />
<span>{t("account.checkUpdates")}</span>
</button>
)}

<Divider className={styles.menuDivider} />

Expand Down