feat(oauth): consolidate credential and quota management

- add tabbed navigation to the OAuth page
- move auth files and quota lookup into OAuth
- remove their standalone sidebar entries
This commit is contained in:
lzt404 committed 2026-08-02 00:51:19 +08:00
1 parent 365356969e
commit 85e0764ad9
7 files changed
+79 -18

No files matched your search

+2 -18
View File
@@ -6,8 +6,6 @@ import {
Check,
ChevronUp,
ExternalLink,
FileKey,
Gauge,
History,
House,
Languages,
@@ -23,10 +21,8 @@ import appLogo from './assets/logo.jpg';
import { CoreRuntimeProvider, useCoreRuntime } from './coreRuntime';
import { ConfigPanelPage } from './pages/ConfigPanel';
import { ApiAccessPage } from './pages/ApiAccessPage';
import { AuthFileManagementPage } from './pages/AuthFileManagementPage';
import { KernelPage } from './pages/Kernel';
import { OAuthLoginPage } from './pages/ManagementPages';
import { QuotaPage } from './pages/QuotaPage';
import { OAuthManagementPage } from './pages/ManagementPages';
import { AgentsPage } from './pages/AgentsPage';
import { UsageRecordsPage } from './pages/UsageRecordsPage';
import { languageOptions, useI18n } from './i18n';
@@ -60,7 +56,7 @@ const pages = [
id: 'oauth',
labelKey: 'app.nav.oauth',
icon: LogIn,
component: OAuthLoginPage,
component: OAuthManagementPage,
},
{
id: 'api',
@@ -68,18 +64,6 @@ const pages = [
icon: Network,
component: ApiAccessPage,
},
{
id: 'auth-files',
labelKey: 'app.nav.authFiles',
icon: FileKey,
component: AuthFileManagementPage,
},
{
id: 'quota',
labelKey: 'app.nav.quota',
icon: Gauge,
component: QuotaPage,
},
{
id: 'usage-records',
labelKey: 'app.nav.usageRecords',
+1
View File
@@ -60,6 +60,7 @@ export const jaOverrides = {
'quota.confirm.expiry': '最も早い有効期限:{time}',
'quota.confirm.warning': '「確認」を押した場合のみ実行されます。「キャンセル」ではリセット回数を消費しません。',
'oauth.title': 'OAuth ログイン',
'oauth.tabs.label': 'OAuth 管理ページ',
'oauth.loginSuccess': '{provider} へのログインに成功しました',
'oauth.priorityApplyFailed': 'OAuth ログインは成功しましたが、新しい認証情報の優先度を 0 に設定できませんでした:{error}',
'oauth.authFailed': '認証に失敗しました',
+2
View File
@@ -63,6 +63,7 @@ export const zhCN = {
'quota.confirm.expiry': '最早过期:{time}',
'quota.confirm.warning': '只有点击“确定”后才会执行;点击“取消”不会消耗重置机会。',
'oauth.title': 'OAuth 登录',
'oauth.tabs.label': 'OAuth 管理子页面',
'oauth.loginSuccess': '{provider} 登录成功',
'oauth.priorityApplyFailed': 'OAuth 登录成功,但无法将新凭证的优先级设为 0:{error}',
'oauth.authFailed': '认证失败',
@@ -951,6 +952,7 @@ export const en: Record<MessageKey, string> = {
'quota.confirm.expiry': 'Earliest expiry: {time}',
'quota.confirm.warning': 'The reset runs only after you confirm. Canceling will not consume a reset credit.',
'oauth.title': 'OAuth Sign-In',
'oauth.tabs.label': 'OAuth management sections',
'oauth.loginSuccess': '{provider} sign-in succeeded',
'oauth.priorityApplyFailed': 'OAuth sign-in succeeded, but the new credential priority could not be set to 0: {error}',
'oauth.authFailed': 'Authentication failed',
+7
View File
@@ -0,0 +1,7 @@
export const oauthSubpages = [
{ id: 'login', labelKey: 'oauth.title' },
{ id: 'authFiles', labelKey: 'authFiles.title' },
{ id: 'quota', labelKey: 'quota.title' },
] as const;
export type OAuthSubpage = (typeof oauthSubpages)[number]['id'];
+48
View File
@@ -13,12 +13,15 @@ import codexIcon from '../assets/icons/codex.svg';
import grokIcon from '../assets/icons/grok.svg';
import kimiIcon from '../assets/icons/kimi-light.svg';
import { useI18n } from '../i18n';
import { oauthSubpages, type OAuthSubpage } from '../oauthNavigation';
import {
changedOAuthAuthFileNames,
snapshotAuthFiles,
type AuthFileSnapshot,
} from '../services/authFiles';
import { managementApi, responseList } from '../services/managementApi';
import { AuthFileManagementPage } from './AuthFileManagementPage';
import { QuotaPage } from './QuotaPage';
type OAuthProviderId = 'codex' | 'claude' | 'antigravity' | 'kimi' | 'xai';
type OAuthFlowStatus = 'idle' | 'waiting' | 'success' | 'error';
@@ -65,6 +68,51 @@ const XAI_CALLBACK_URL = 'http://127.0.0.1:56121/callback';
const OAUTH_SUCCESS_RESET_MS = 5000;
const OAUTH_POLL_INTERVAL_MS = 3000;
export function OAuthManagementPage() {
const { t } = useI18n();
const [activeSubpage, setActiveSubpage] = useState<OAuthSubpage>('login');
return (
<section className="page oauth-management-page">
<div
className="agent-subpage-tabs oauth-subpage-tabs"
role="tablist"
aria-label={t('oauth.tabs.label')}
>
{oauthSubpages.map((subpage) => {
const active = activeSubpage === subpage.id;
return (
<button
type="button"
id={`oauth-subpage-tab-${subpage.id}`}
key={subpage.id}
role="tab"
className={active ? 'active' : ''}
aria-selected={active}
aria-controls={`oauth-subpage-panel-${subpage.id}`}
tabIndex={active ? 0 : -1}
onClick={() => setActiveSubpage(subpage.id)}
>
{t(subpage.labelKey)}
</button>
);
})}
</div>
<div
className="oauth-subpage-panel"
id={`oauth-subpage-panel-${activeSubpage}`}
role="tabpanel"
aria-labelledby={`oauth-subpage-tab-${activeSubpage}`}
>
{activeSubpage === 'login' ? <OAuthLoginPage /> : null}
{activeSubpage === 'authFiles' ? <AuthFileManagementPage /> : null}
{activeSubpage === 'quota' ? <QuotaPage /> : null}
</div>
</section>
);
}
export function OAuthLoginPage() {
const { t } = useI18n();
const [states, setStates] = useState<Partial<Record<OAuthProviderId, OAuthProviderState>>>({});
+7
View File
@@ -4498,6 +4498,13 @@ textarea:focus-visible {
box-shadow: 0 1px 4px rgba(45, 42, 38, 0.14);
}
.oauth-management-page,
.oauth-subpage-panel {
display: grid;
min-width: 0;
gap: 18px;
}
.agent-core-config {
display: grid;
min-width: 0;
+12
View File
@@ -1,6 +1,7 @@
import { describe, expect, test } from 'bun:test';
import { appUpdateIndicatorState } from '../src/appUpdateModel';
import { canOpenAppPage, isAlwaysAvailablePage } from '../src/navigation';
import { oauthSubpages } from '../src/oauthNavigation';
describe('首页、配置与版本管理导航', () => {
test('内核停止时首页、配置和版本管理始终可进入', () => {
@@ -19,6 +20,17 @@ describe('首页、配置与版本管理导航', () => {
});
});
describe('OAuth 子页面导航', () => {
test('认证文件和额度查询收纳在 OAuth 页面内', () => {
expect(oauthSubpages.map((page) => page.id)).toEqual(['login', 'authFiles', 'quota']);
expect(oauthSubpages.map((page) => page.labelKey)).toEqual([
'oauth.title',
'authFiles.title',
'quota.title',
]);
});
});
describe('软件更新导航提示点', () => {
test('有新版显示橙点,处理中的蓝点优先', () => {
expect(appUpdateIndicatorState(true, false)).toBe('available');