feat(mq): improve Kafka peek and admin localization

This commit is contained in:
Freedom authored and GitHub committed 2026-07-16 18:20:27 +08:00
1 parent a9e76dc130
commit 68b1777aa4
29 files changed
+4561 -636

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";
import { parseNonNegativeSafeInteger } from "@/lib/mq/mqPeekFilters";
describe("parseNonNegativeSafeInteger", () => {
it("accepts non-negative safe integers", () => {
expect(parseNonNegativeSafeInteger("0")).toBe(0);
expect(parseNonNegativeSafeInteger("20")).toBe(20);
expect(parseNonNegativeSafeInteger(String(Number.MAX_SAFE_INTEGER))).toBe(Number.MAX_SAFE_INTEGER);
});
it("rejects decimals and non-integers", () => {
expect(parseNonNegativeSafeInteger("1.9")).toBeNull();
expect(parseNonNegativeSafeInteger("0.1")).toBeNull();
expect(parseNonNegativeSafeInteger("abc")).toBeNull();
});
it("rejects negatives and values outside the safe integer range", () => {
expect(parseNonNegativeSafeInteger("-1")).toBeNull();
expect(parseNonNegativeSafeInteger(String(Number.MAX_SAFE_INTEGER + 1))).toBeNull();
});
it("rejects empty input", () => {
expect(parseNonNegativeSafeInteger("")).toBeNull();
expect(parseNonNegativeSafeInteger(" ")).toBeNull();
});
});
+2 -2
View File
@@ -18,8 +18,8 @@ describe("mqTenantForm", () => {
});
it("requires both a tenant name and at least one allowed cluster", () => {
expect(validateTenantForm("", { adminRoles: [], allowedClusters: ["standalone"] })).toBe("Tenant name is required");
expect(validateTenantForm("tenant-a", { adminRoles: [], allowedClusters: [] })).toBe("Allowed clusters are required");
expect(validateTenantForm("", { adminRoles: [], allowedClusters: ["standalone"] })).toBe("mqTenants.tenantNameRequired");
expect(validateTenantForm("tenant-a", { adminRoles: [], allowedClusters: [] })).toBe("mqTenants.allowedClustersRequired");
expect(validateTenantForm("tenant-a", { adminRoles: [], allowedClusters: ["standalone"] })).toBeUndefined();
});
});
+9 -6
View File
@@ -2,20 +2,23 @@ import { describe, expect, it } from "vitest";
import { formatMqTokenIssueError } from "@/lib/mq/mqTokenErrors";
describe("MQ token errors", () => {
it("turns missing signing configuration backend errors into actionable Chinese copy", () => {
it("turns missing signing configuration backend errors into locale keys", () => {
const result = formatMqTokenIssueError("/api/mq/tokens/issue returned 500: Token signing is not configured for this MQ connection");
expect(result.kind).toBe("missingSigningKey");
expect(result.title).toBe("未配置 Token 签发密钥");
expect(result.message).toContain("无法生成客户端 Token");
expect(result.detail).toContain("Broker Token 签发");
if (result.kind === "missingSigningKey") {
expect(result.titleKey).toBe("mqPermissions.missingSigningKeyTitle");
expect(result.messageKey).toBe("mqPermissions.missingSigningKeyMessage");
expect(result.detailKey).toBe("mqPermissions.missingSigningKeyDetail");
}
});
it("keeps unrelated errors as ordinary messages", () => {
const result = formatMqTokenIssueError("network failed");
expect(result.kind).toBe("generic");
expect(result.title).toBeUndefined();
expect(result.message).toBe("network failed");
if (result.kind === "generic") {
expect(result.message).toBe("network failed");
}
});
});
+12
View File
@@ -0,0 +1,12 @@
/** Parse a non-negative safe integer from user input; rejects decimals and unsafe magnitudes. */
export function parseNonNegativeSafeInteger(text: string): number | null {
const trimmed = text.trim();
if (trimmed === "") {
return null;
}
const value = Number(trimmed);
if (!Number.isSafeInteger(value) || value < 0) {
return null;
}
return value;
}
+2 -2
View File
@@ -33,10 +33,10 @@ export function defaultTenantConfig(clusterOptions: readonly string[]): TenantCo
export function validateTenantForm(name: string | undefined, config: TenantConfig): string | undefined {
if (!name?.trim()) {
return "Tenant name is required";
return "mqTenants.tenantNameRequired";
}
if (!normalizeClusterOptions(config.allowedClusters).length) {
return "Allowed clusters are required";
return "mqTenants.allowedClustersRequired";
}
return undefined;
}
+9 -8
View File
@@ -1,15 +1,16 @@
export type MqTokenIssueErrorView =
| {
kind: "missingSigningKey";
title: string;
message: string;
detail: string;
titleKey: "mqPermissions.missingSigningKeyTitle";
messageKey: "mqPermissions.missingSigningKeyMessage";
detailKey: "mqPermissions.missingSigningKeyDetail";
}
| {
kind: "generic";
title?: undefined;
titleKey?: undefined;
messageKey?: undefined;
detailKey?: undefined;
message: string;
detail?: undefined;
};
export function formatMqTokenIssueError(error: unknown): MqTokenIssueErrorView {
@@ -17,9 +18,9 @@ export function formatMqTokenIssueError(error: unknown): MqTokenIssueErrorView {
if (isMissingTokenSigningConfig(message)) {
return {
kind: "missingSigningKey",
title: "未配置 Token 签发密钥",
message: "当前 MQ 连接还没有配置 Broker Token 签发密钥,无法生成客户端 Token。",
detail: "请编辑该连接,在 MQ 配置中设置“Broker Token 签发”为 HS256 SECRET 或 RS256 PRIVATE,并填写签发密钥后再生成。",
titleKey: "mqPermissions.missingSigningKeyTitle",
messageKey: "mqPermissions.missingSigningKeyMessage",
detailKey: "mqPermissions.missingSigningKeyDetail",
};
}
return { kind: "generic", message };