Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ This document records all notable changes to ShadCN Admin.
---


## [1.21.0-dev.2](https://github.com/perfect-panel/frontend/compare/v1.21.0-dev.1...v1.21.0-dev.2) (2026-09-05)

### 🐛 Bug Fixes / 问题修复

* **frontend:** remove retired gateway dependencies ([c038e9a](https://github.com/perfect-panel/frontend/commit/c038e9a55dad283860e95f77902673ddffaddfbb))

### 📚 Documentation / 文档更新

* **api:** sync Swagger from backend ([1f8f1bc](https://github.com/perfect-panel/frontend/commit/1f8f1bc52c7807e0669a70f017aaef78727ec6ab))

## [1.21.0-dev.1](https://github.com/perfect-panel/frontend/compare/v1.20.0...v1.21.0-dev.1) (2026-08-29)

### ✨ Features / 新功能
Expand Down
5 changes: 3 additions & 2 deletions apps/admin/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# API base URL
# API base URL. Empty uses the page origin; Vite proxies to localhost:8080 in development.
VITE_API_BASE_URL=
# API prefix path
# Optional reverse-proxy prefix (e.g. /api). Empty calls /v1 and /v2 directly.
# Production proxies must strip this prefix before forwarding to the backend.
VITE_API_PREFIX=

# CDN URL for static assets
Expand Down
8 changes: 0 additions & 8 deletions apps/admin/public/assets/locales/en-US/tool.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
{
"adminUpdateSuccess": "Admin updated successfully",
"cancel": "Cancel",
"confirmReboot": "Confirm Reboot",
"confirmSystemReboot": "Confirm System Reboot",
"confirmUpdate": "Confirm Update",
"rebootDescription": "Are you sure you want to reboot the system? This action cannot be undone.",
"rebooting": "Rebooting...",
"refreshLogs": "Refresh Logs",
"serverVersion": "Server Version",
"systemLogs": "System Logs",
"systemReboot": "System Reboot",
"systemServices": "System Services",
"update": "Update",
"updateFailed": "Update failed",
"updateServerDescription": "Are you sure you want to update the server version from {{current}} to {{latest}}?",
"updateSuccess": "Update completed successfully",
"updateWebDescription": "Are you sure you want to update the web version from {{current}} to {{latest}}?",
"userUpdateSuccess": "User updated successfully",
"webVersion": "Web Version"
}
8 changes: 0 additions & 8 deletions apps/admin/public/assets/locales/zh-CN/tool.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
{
"adminUpdateSuccess": "管理端更新成功",
"cancel": "取消",
"confirmReboot": "确认重启",
"confirmSystemReboot": "确认系统重启",
"confirmUpdate": "确认更新",
"rebootDescription": "确定要重启系统吗?此操作无法撤销。",
"rebooting": "重启中...",
"refreshLogs": "刷新日志",
"serverVersion": "服务器版本",
"systemLogs": "系统日志",
"systemReboot": "系统重启",
"systemServices": "系统服务",
"update": "更新",
"updateFailed": "更新失败",
"updateServerDescription": "确定要将服务器版本从 {{current}} 更新到 {{latest}} 吗?",
"updateSuccess": "更新成功",
"updateWebDescription": "确定要将前端版本从 {{current}} 更新到 {{latest}} 吗?",
"userUpdateSuccess": "用户端更新成功",
"webVersion": "前端版本"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// @vitest-environment jsdom

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import SystemVersionCard from "./system-version-card";

const { request } = vi.hoisted(() => ({ request: vi.fn() }));

vi.mock("@workspace/ui/lib/request", () => ({ default: request }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (_key: string, fallback: string) => fallback }),
}));
vi.mock("@workspace/ui/composed/icon", () => ({ Icon: () => null }));

let client: QueryClient;

beforeEach(() => {
request.mockReset();
client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
});

afterEach(() => {
cleanup();
client.clear();
});

function renderCard() {
render(
<QueryClientProvider client={client}>
<SystemVersionCard />
</QueryClientProvider>
);
}

describe("system version card", () => {
it("loads the backend version without module credentials or gateway calls", async () => {
request.mockResolvedValue({
data: { code: 200, data: { version: "1.20.2" } },
});
renderCard();

expect(await screen.findByText("V1.20.2")).toBeTruthy();
expect(request).toHaveBeenCalledTimes(1);
expect(request).toHaveBeenCalledWith(
"/v1/admin/tool/version",
expect.objectContaining({ method: "GET" })
);
expect(screen.getByRole("button", { name: "System Reboot" })).toBeTruthy();
expect(screen.queryByRole("button", { name: /update/i })).toBeNull();
});

it("does not display an invented server version when the response has no version", async () => {
request.mockResolvedValue({ data: { code: 200, data: {} } });
renderCard();

await waitFor(() => expect(request).toHaveBeenCalledTimes(1));
expect(screen.getByText("—")).toBeTruthy();
expect(screen.queryByText("V1.0.0")).toBeNull();
});

it("keeps restart available for retry after the backend rejects the request", async () => {
request.mockResolvedValueOnce({
data: { code: 200, data: { version: "1.20.2" } },
});
renderCard();
await screen.findByText("V1.20.2");
request.mockRejectedValueOnce(new Error("restart failed"));

fireEvent.click(screen.getByRole("button", { name: "System Reboot" }));
fireEvent.click(screen.getByRole("button", { name: "Confirm Reboot" }));

await waitFor(() => {
const confirm = screen.getByRole("button", {
name: "Confirm Reboot",
}) as HTMLButtonElement;
expect(confirm.disabled).toBe(false);
});
expect(request).toHaveBeenLastCalledWith(
"/v1/admin/tool/restart",
expect.objectContaining({ method: "GET" })
);
});
});
Loading
Loading