Mask account emails and compact usage cards
This commit is contained in:
Vendored
+2
-2
@@ -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
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user