Mask account emails and compact usage cards

This commit is contained in:
zhoujun0601
2026-08-13 09:37:33 -04:00
parent fc8e24d8a9
commit 63bd0b4fea
4 changed files with 76 additions and 25 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
<script type="module" crossorigin src="/assets/index-Ddm_ZeSC.js"></script> <script type="module" crossorigin src="/assets/index-BUUH7ota.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B0FW1DHS.css"> <link rel="stylesheet" crossorigin href="/assets/index-DTBZgX1u.css">
<div id="root"></div> <div id="root"></div>
+21 -7
View File
@@ -340,7 +340,7 @@ function Dashboard() {
{accounts.map((x) => ( {accounts.map((x) => (
<option key={x.id} value={x.id}> <option key={x.id} value={x.id}>
{x.displayName} {x.displayName}
{x.email ? " · " + x.email : ""} {x.email ? " · " + maskEmail(x.email) : ""}
</option> </option>
))} ))}
</select> </select>
@@ -364,7 +364,9 @@ function Dashboard() {
<div> <div>
<small>Codex Account</small> <small>Codex Account</small>
<b> <b>
{d.displayName} · {d.account.email || "尚未连接"} {d.displayName} · {d.account.email
? maskEmail(d.account.email)
: "尚未连接"}
</b> </b>
</div> </div>
<span className="badge">{planLabel(d.account.planType)}</span> <span className="badge">{planLabel(d.account.planType)}</span>
@@ -437,10 +439,6 @@ function LimitCard({ x }: { x: Limit }) {
<em style={{ width: left + "%", background: leftColor }} /> <em style={{ width: left + "%", background: leftColor }} />
<i style={{ width: used + "%" }} /> <i style={{ width: used + "%" }} />
</div> </div>
<footer>
<span>已使用 {usedLabel}%</span>
<span style={{ color: leftColor }}>剩余 {leftLabel}%</span>
</footer>
</div> </div>
); );
} }
@@ -812,7 +810,7 @@ function CodexSettings() {
}} }}
/> />
<div className="account-meta"> <div className="account-meta">
<span>{x.email || "尚未登录"}</span> <span>{x.email ? maskEmail(x.email) : "尚未登录"}</span>
<span>{planLabel(x.planType)}</span> <span>{planLabel(x.planType)}</span>
<span className={x.validationStatus === "mismatch" ? "error" : ""}> <span className={x.validationStatus === "mismatch" ? "error" : ""}>
{validationLabel(x)} {validationLabel(x)}
@@ -1156,6 +1154,22 @@ const duration = (v?: number) =>
: v < 3600 : v < 3600
? `${Math.floor(v / 60)} 分 ${v % 60} 秒` ? `${Math.floor(v / 60)} 分 ${v % 60} 秒`
: `${(v / 3600).toFixed(1)} 小时`; : `${(v / 3600).toFixed(1)} 小时`;
const maskEmailPart = (part: string) => {
if (part.length <= 1) return "*";
if (part.length === 2) return part[0] + "*";
return part[0] + "*".repeat(part.length - 2) + part.at(-1);
};
const maskEmail = (email: string) => {
const at = email.lastIndexOf("@");
if (at < 1 || at === email.length - 1) return maskEmailPart(email);
const local = email.slice(0, at);
const domain = email.slice(at + 1).split(".");
return `${maskEmailPart(local)}@${domain
.map((part, index) =>
index === domain.length - 1 ? part : maskEmailPart(part),
)
.join(".")}`;
};
const planLabel = (plan?: string) => { const planLabel = (plan?: string) => {
if (!plan) return "未识别套餐"; if (!plan) return "未识别套餐";
const labels: Record<string, string> = { const labels: Record<string, string> = {
+3 -16
View File
@@ -295,10 +295,10 @@ header p {
gap: 18px; gap: 18px;
} }
.limit { .limit {
padding: 24px; padding: 20px;
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
gap: 16px; gap: 12px;
align-items: start; align-items: start;
} }
.limit > small { .limit > small {
@@ -344,18 +344,6 @@ header p {
.bar em { .bar em {
background: var(--green); background: var(--green);
} }
.limit footer {
grid-column: 1/-1;
border-top: 1px solid var(--border);
padding-top: 14px;
display: flex;
gap: 18px;
color: var(--muted);
font-size: 12px;
}
.limit footer span:last-child {
margin-left: auto;
}
.green { .green {
color: var(--green); color: var(--green);
} }
@@ -682,8 +670,7 @@ a {
text-align: left; text-align: left;
} }
.limit > small, .limit > small,
.bar, .bar {
.limit footer {
grid-column: 1; grid-column: 1;
} }
} }
+50
View File
@@ -169,3 +169,53 @@ test("Codex account cards fit within the viewport", async ({ page }) => {
expect(overflow.documentWidth).toBeLessThanOrEqual(overflow.viewportWidth); expect(overflow.documentWidth).toBeLessThanOrEqual(overflow.viewportWidth);
expect(overflow.cardWidth).toBeLessThanOrEqual(overflow.contentWidth); expect(overflow.cardWidth).toBeLessThanOrEqual(overflow.contentWidth);
}); });
test("Codex account emails are masked everywhere they are displayed", async ({
page,
}) => {
await page.route("**/api/v1/**", async (route) => {
const key = new URL(route.request().url()).pathname.replace("/api/v1/", "");
if (key === "dashboard")
return route.fulfill({
json: {
accountId: 1,
displayName: "默认账号",
account: {
email: "test@example.com",
planType: "plus",
connected: true,
},
limits: [
{
limitId: "primary",
windowType: "5h",
usedPercent: 25,
windowDurationMinutes: 300,
resetsAt: 0,
},
],
summary: {},
usage: [],
fetchedAt: 0,
stale: false,
},
});
return route.fulfill({ json: responses[key] ?? {} });
});
await page.goto("/");
await expect(page.locator(".account-select")).toContainText("t**t@e*****e.com");
await expect(page.locator(".account b")).toContainText("t**t@e*****e.com");
await expect(page.getByText("已使用 25%", { exact: true })).toHaveCount(0);
await expect(page.getByText("剩余 75%", { exact: true })).toHaveCount(0);
await expect(page.locator(".bar")).toHaveAttribute(
"aria-label",
"5h:已使用 25%,剩余 75%",
);
await expect(page.locator("body")).not.toContainText("test@example.com");
await page.goto("/settings");
await page.getByRole("tab", { name: "Codex" }).click();
await expect(page.locator(".account-meta").first()).toContainText("t**t@e*****e.com");
await expect(page.locator("body")).not.toContainText("test@example.com");
});