feat: add public account overview visibility

This commit is contained in:
2026-08-14 14:38:22 +08:00
parent bf37999833
commit 915d26d51d
14 changed files with 660 additions and 62 deletions
+82 -7
View File
@@ -2,6 +2,7 @@ import React, { lazy, Suspense, useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import {
BrowserRouter,
Link,
Navigate,
Route,
Routes,
@@ -18,6 +19,7 @@ import {
Flame,
Github,
Gauge,
LogIn,
Mail,
LogOut,
Moon,
@@ -83,7 +85,13 @@ function App() {
{authenticated ? (
<Route path="*" element={<Shell version={status.version} />} />
) : (
<Route path="*" element={<Login version={status.version} />} />
<>
<Route path="/login" element={<Login version={status.version} />} />
<Route
path="*"
element={<PublicShell version={status.version} />}
/>
</>
)}
</Routes>
</BrowserRouter>
@@ -206,10 +214,32 @@ function Login({ version }: { version: string }) {
</label>
{e && <p className="error">{e}</p>}
<button>登录</button>
<p className="hint public-login-link">
<Link to="/">查看公开总览</Link>
</p>
</form>
</main>
);
}
function PublicShell({ version }: { version: string }) {
const nav = useNavigate();
return (
<div className="public-app">
<header className="public-header">
<Brand version={version} />
<button className="secondary" onClick={() => nav("/login")}>
<LogIn /> 登录后配置
</button>
</header>
<main className="public-content">
<Routes>
<Route path="/" element={<Dashboard publicView />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
</div>
);
}
function Shell({ version }: { version: string }) {
const { logout } = useAuth();
const nav = useNavigate();
@@ -358,7 +388,7 @@ const emptyDashboardCardState = (): DashboardCardState => ({
refresh: "idle",
});
function Dashboard() {
function Dashboard({ publicView = false }: { publicView?: boolean }) {
const [accounts, setAccounts] = useState<Account[] | null>(null),
[cards, setCards] = useState<Record<number, DashboardCardState>>({}),
[expanded, setExpanded] = useState<Record<number, boolean>>({}),
@@ -539,7 +569,12 @@ function Dashboard() {
if (!accounts.length)
return (
<>
<Header title="用量总览" sub="请先在设置中心添加账号" />
<Header
title="用量总览"
sub={
publicView ? "当前还没有可公开展示的账号" : "请先在设置中心添加账号"
}
/>
<div className="panel empty">尚未添加 Codex 账号</div>
</>
);
@@ -569,6 +604,7 @@ function Dashboard() {
key={account.id}
account={account}
state={cards[account.id] || emptyDashboardCardState()}
publicView={publicView}
expanded={Boolean(expanded[account.id])}
onToggle={() =>
setExpanded((current) => ({
@@ -587,12 +623,14 @@ function Dashboard() {
function AccountOverviewCard({
account,
state,
publicView,
expanded,
onToggle,
onRefresh,
}: {
account: Account;
state: DashboardCardState;
publicView: boolean;
expanded: boolean;
onToggle: () => void;
onRefresh: () => void;
@@ -619,7 +657,7 @@ function AccountOverviewCard({
<span className="account-summary-identity">
<strong>{title}</strong>
<span>
{email ? maskEmail(email) : "尚未登录"} ·{" "}
{publicView ? "公开只读" : email ? maskEmail(email) : "尚未登录"} ·{" "}
{connected ? "已连接" : "未连接"}
</span>
</span>
@@ -638,6 +676,7 @@ function AccountOverviewCard({
dashboard={dashboard}
state={state}
onRefresh={onRefresh}
publicView={publicView}
/>
) : (
<div className="account-detail-state">
@@ -701,10 +740,12 @@ function AccountDashboardDetails({
dashboard,
state,
onRefresh,
publicView,
}: {
dashboard: Dash;
state: DashboardCardState;
onRefresh: () => void;
publicView: boolean;
}) {
return (
<>
@@ -747,9 +788,11 @@ function AccountDashboardDetails({
icon={<Mail />}
label="登录邮箱"
value={
dashboard.account.email
? maskEmail(dashboard.account.email)
: "尚未连接"
publicView
? "登录后查看"
: dashboard.account.email
? maskEmail(dashboard.account.email)
: "尚未连接"
}
/>
<AccountInfo
@@ -1124,6 +1167,7 @@ function CodexSettings() {
[deviceLogin, setDeviceLogin] = useState<DeviceLogin | null>(null),
[active, setActive] = useState(0),
[newKind, setNewKind] = useState<"personal" | "team">("team"),
[newPublicVisible, setNewPublicVisible] = useState(false),
[busy, setBusy] = useState(false),
[err, setErr] = useState("");
const load = async (signal?: AbortSignal) => {
@@ -1195,6 +1239,7 @@ function CodexSettings() {
const x = await post("accounts", (value) => decodeAccounts([value])[0], {
displayName: `账号 ${xs.length + 1}`,
expectedKind: newKind,
publicVisible: newPublicVisible,
});
await load();
setActive(x.id);
@@ -1238,6 +1283,14 @@ function CodexSettings() {
<option value="personal">个人订阅</option>
</select>
</label>
<label className="checks">
<input
type="checkbox"
checked={newPublicVisible}
onChange={(e) => setNewPublicVisible(e.target.checked)}
/>
公开显示到未登录总览
</label>
<button disabled={busy} onClick={add}>
{busy && active === 0 ? "服务启动中…" : "添加账号"}
</button>
@@ -1263,6 +1316,7 @@ function CodexSettings() {
await put(`accounts/${x.id}`, decodeOK, {
displayName: name,
expectedKind: x.expectedKind,
publicVisible: x.publicVisible,
});
void load().catch((error) =>
setErr(toErrorMessage(error)),
@@ -1296,6 +1350,7 @@ function CodexSettings() {
await put(`accounts/${x.id}`, decodeOK, {
displayName: x.displayName,
expectedKind: e.target.value,
publicVisible: x.publicVisible,
});
void load().catch((error) => setErr(toErrorMessage(error)));
}}
@@ -1305,6 +1360,26 @@ function CodexSettings() {
<option value="team">Team / Business</option>
</select>
</label>
<label className="checks">
<input
type="checkbox"
checked={x.publicVisible}
onChange={async (e) => {
try {
setErr("");
await put(`accounts/${x.id}`, decodeOK, {
displayName: x.displayName,
expectedKind: x.expectedKind,
publicVisible: e.target.checked,
});
await load();
} catch (q) {
setErr(toErrorMessage(q));
}
}}
/>
公开显示到未登录总览
</label>
<div className="account-buttons">
<button
className="secondary"