Files
Genarrative/apps/admin-web/src/app/AdminApp.tsx
T
k88936 422c8931d6
Project CI / Repository checks (push) Successful in 2m45s
Project CI / Frontend tests (push) Successful in 3m22s
Project CI / Backend tests (push) Successful in 5m53s
Project CI / Native shell tests (push) Successful in 16m45s
Feat/AGC错误报告 (#240)
实现:
在rust内存里维护错误事件队列, webview调用tauri command传入, 后台任务agent工具等直接插入
把rust , webview console的日志统一写到AppData文件夹下的(滚动保存的)日志文件.
rust对传入的错误进行筛选,脱敏, 防抖,后通知前端提醒用户.
用户提醒是一个不阻塞的小UI, 展开后可以选择错误上报, 可以附加文字描述
上传时附带最近日志, 错误堆栈等信息

元数据存在数据库, 考虑到字符串信息很难查询, 所以在api server打包成zip存在OSS.
管理页面新增错误报告的查看页面

![shotmd-1788328504.jpg](/attachments/641b43c8-deed-47d8-8c78-36adb9a2548f)
![shotmd-1788328495.jpg](/attachments/33c6ada3-dd4d-44b6-9859-f18f944eb653)
![shotmd-1788328222.jpg](/attachments/59267df9-f624-4e47-bb9b-b69d77f595c4)
![shotmd-1788328230.jpg](/attachments/63ea6a86-9a51-412b-a98c-dce454bcc968)

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/240
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-03 10:02:05 +08:00

310 lines
9.4 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'react';
import {
formatAdminApiError,
getAdminMe,
isAdminApiError,
loginAdmin,
} from '../api/adminApiClient';
import type {
AdminSessionPayload,
ProfileRechargeProductConfigAdminResponse,
ProfileTaskConfigAdminResponse,
ProfileWalletConfigAdminResponse,
} from '../api/adminApiTypes';
import {
clearStoredAdminToken,
getStoredAdminToken,
setStoredAdminToken,
} from '../auth/adminAuthStore';
import { AdminAccountsPage } from '../pages/AdminAccountsPage';
import { AdminDashboardPage } from '../pages/AdminDashboardPage';
import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
import { AdminErrorReportsPage } from '../pages/AdminErrorReportsPage';
import { AdminGrayReleaseConfigPage } from '../pages/AdminGrayReleaseConfigPage';
import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
import { AdminLoginPage } from '../pages/AdminLoginPage';
import { AdminOverviewPage } from '../pages/AdminOverviewPage';
import { AdminProfileWalletConfigPage } from '../pages/AdminProfileWalletConfigPage';
import { AdminRechargeOrderPage } from '../pages/AdminRechargeOrderPage';
import { AdminRechargeProductPage } from '../pages/AdminRechargeProductPage';
import { AdminRedeemCodePage } from '../pages/AdminRedeemCodePage';
import { AdminTaskConfigPage } from '../pages/AdminTaskConfigPage';
import { AdminTrackingEventsPage } from '../pages/AdminTrackingEventsPage';
import type { AdminRouteId } from './adminRoutes';
import {
getAccessibleAdminRoutes,
resolveAccessibleAdminRoute,
resolveAdminRoute,
routeHash,
} from './adminRoutes';
import { AdminShell } from './AdminShell';
type SessionStatus = 'checking' | 'guest' | 'authenticated';
export function AdminApp() {
const [status, setStatus] = useState<SessionStatus>('checking');
const [admin, setAdmin] = useState<AdminSessionPayload | null>(null);
const [token, setToken] = useState('');
const [routeId, setRouteId] = useState<AdminRouteId>(() =>
resolveAdminRoute(window.location.hash),
);
const [loginNotice, setLoginNotice] = useState('');
const [taskConfigResult, setTaskConfigResult] =
useState<ProfileTaskConfigAdminResponse | null>(null);
const [profileWalletConfigResult, setProfileWalletConfigResult] =
useState<ProfileWalletConfigAdminResponse | null>(null);
const [rechargeProductResult, setRechargeProductResult] =
useState<ProfileRechargeProductConfigAdminResponse | null>(null);
const accessibleRoutes = useMemo(
() => (admin ? getAccessibleAdminRoutes(admin) : []),
[admin],
);
const activeRouteId = accessibleRoutes.some((route) => route.id === routeId)
? routeId
: null;
const clearSession = useCallback((message = '') => {
clearStoredAdminToken();
setToken('');
setAdmin(null);
setTaskConfigResult(null);
setProfileWalletConfigResult(null);
setRechargeProductResult(null);
setStatus('guest');
setLoginNotice(message);
}, []);
useEffect(() => {
let isMounted = true;
const storedToken = getStoredAdminToken();
if (!storedToken) {
setStatus('guest');
return;
}
void getAdminMe(storedToken)
.then((response) => {
if (!isMounted) {
return;
}
setToken(storedToken);
setAdmin(response.admin);
setStatus('authenticated');
})
.catch((error: unknown) => {
if (!isMounted) {
return;
}
clearStoredAdminToken();
setToken('');
setAdmin(null);
setStatus('guest');
setLoginNotice(
isAdminApiError(error) && error.status === 401
? '登录状态已失效'
: formatAdminApiError(error),
);
});
return () => {
isMounted = false;
};
}, []);
useEffect(() => {
if (status !== 'authenticated' || !admin) {
return;
}
const nextRouteId = resolveAccessibleAdminRoute(
window.location.hash,
accessibleRoutes,
);
if (!nextRouteId) {
return;
}
setRouteId(nextRouteId);
const nextHash = routeHash(nextRouteId);
if (window.location.hash !== nextHash) {
window.history.replaceState(null, '', nextHash);
}
}, [accessibleRoutes, admin, routeId, status]);
useEffect(() => {
const handleHashChange = () => {
setRouteId(resolveAdminRoute(window.location.hash));
};
window.addEventListener('hashchange', handleHashChange);
return () => window.removeEventListener('hashchange', handleHashChange);
}, []);
const handleRouteChange = useCallback((nextRouteId: AdminRouteId) => {
setRouteId(nextRouteId);
const nextHash = routeHash(nextRouteId);
if (window.location.hash !== nextHash) {
window.location.hash = nextHash;
}
}, []);
const handleLogin = useCallback(
async (username: string, password: string) => {
const response = await loginAdmin(username, password);
setStoredAdminToken(response.token);
setToken(response.token);
setAdmin(response.admin);
setTaskConfigResult(null);
setProfileWalletConfigResult(null);
setRechargeProductResult(null);
setLoginNotice('');
setStatus('authenticated');
},
[],
);
const handleUnauthorized = useCallback(
(message = '登录状态已失效') => {
clearSession(message);
},
[clearSession],
);
const handleLogout = useCallback(() => {
clearSession('');
}, [clearSession]);
if (status === 'checking') {
return (
<main className="admin-loading-screen">
<div className="admin-loading-mark" />
<span>正在校验会话</span>
</main>
);
}
if (status === 'guest' || !admin || !token) {
return <AdminLoginPage notice={loginNotice} onLogin={handleLogin} />;
}
return (
<AdminShell
admin={admin}
routeId={activeRouteId}
routes={accessibleRoutes}
onLogout={handleLogout}
onRouteChange={handleRouteChange}
>
{activeRouteId === null ? (
<section className="admin-panel admin-zero-permission-state">
<h2>暂无访问权限</h2>
</section>
) : null}
{activeRouteId === 'dashboard' ? (
<AdminDashboardPage token={token} onUnauthorized={handleUnauthorized} />
) : null}
{activeRouteId === 'overview' ? (
<AdminOverviewPage token={token} onUnauthorized={handleUnauthorized} />
) : null}
{activeRouteId === 'tables' ? (
<AdminDatabaseTablesPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'debug' ? (
<AdminDebugHttpPage token={token} onUnauthorized={handleUnauthorized} />
) : null}
{activeRouteId === 'tracking' ? (
<AdminTrackingEventsPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'error-reports' ? (
<AdminErrorReportsPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'gray-release' ? (
<AdminGrayReleaseConfigPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'redeem' ? (
<AdminRedeemCodePage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'invite' ? (
<AdminInviteCodePage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'tasks' ? (
<AdminTaskConfigPage
result={taskConfigResult}
token={token}
onUnauthorized={handleUnauthorized}
onResultChange={setTaskConfigResult}
/>
) : null}
{activeRouteId === 'profile-wallet' ? (
<AdminProfileWalletConfigPage
result={profileWalletConfigResult}
token={token}
onUnauthorized={handleUnauthorized}
onResultChange={setProfileWalletConfigResult}
/>
) : null}
{activeRouteId === 'recharge-products' ? (
<AdminRechargeProductPage
result={rechargeProductResult}
token={token}
onUnauthorized={handleUnauthorized}
onResultChange={setRechargeProductResult}
/>
) : null}
{activeRouteId === 'recharge-orders' ? (
<AdminRechargeOrderPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'editor-generation-pricing' ? (
<AdminEditorGenerationPricingPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'editor-showcase' ? (
<AdminEditorShowcaseReviewPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'editor-assets' ? (
<AdminEditorAssetQueryPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'accounts' ? (
<AdminAccountsPage token={token} onUnauthorized={handleUnauthorized} />
) : null}
</AdminShell>
);
}