mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 06:20:13 +00:00
fix: gate auth monitor stats polling (#1421)
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||||
import { useCliproxyStats } from '@/hooks/use-cliproxy-stats';
|
import { useCliproxyStats, useCliproxyStatus } from '@/hooks/use-cliproxy-stats';
|
||||||
import { buildAccountVisualGroups } from '@/lib/account-visual-groups';
|
import { buildAccountVisualGroups } from '@/lib/account-visual-groups';
|
||||||
import { getProviderDisplayName } from '@/lib/provider-config';
|
import { getProviderDisplayName } from '@/lib/provider-config';
|
||||||
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
|
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
|
||||||
@@ -26,14 +26,25 @@ export interface AuthMonitorData {
|
|||||||
/** Hook for computing auth monitor data from CLIProxy auth and stats */
|
/** Hook for computing auth monitor data from CLIProxy auth and stats */
|
||||||
export function useAuthMonitorData(): AuthMonitorData {
|
export function useAuthMonitorData(): AuthMonitorData {
|
||||||
const { data, isLoading, error } = useCliproxyAuth();
|
const { data, isLoading, error } = useCliproxyAuth();
|
||||||
const { data: statsData, isLoading: statsLoading, dataUpdatedAt } = useCliproxyStats();
|
const { data: proxyStatus } = useCliproxyStatus();
|
||||||
|
const statsEnabled = proxyStatus?.running === true;
|
||||||
|
const {
|
||||||
|
data: statsData,
|
||||||
|
isLoading: statsLoading,
|
||||||
|
dataUpdatedAt,
|
||||||
|
} = useCliproxyStats(statsEnabled);
|
||||||
|
const activeStatsData = statsEnabled ? statsData : undefined;
|
||||||
|
const activeStatsUpdatedAt = statsEnabled ? dataUpdatedAt : 0;
|
||||||
const [timeSinceUpdate, setTimeSinceUpdate] = useState('');
|
const [timeSinceUpdate, setTimeSinceUpdate] = useState('');
|
||||||
|
|
||||||
// Live countdown showing time since last data update
|
// Live countdown showing time since last data update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dataUpdatedAt) return;
|
if (!activeStatsUpdatedAt) {
|
||||||
|
setTimeSinceUpdate('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const updateTime = () => {
|
const updateTime = () => {
|
||||||
const diff = Math.floor((Date.now() - dataUpdatedAt) / 1000);
|
const diff = Math.floor((Date.now() - activeStatsUpdatedAt) / 1000);
|
||||||
if (diff < 60) {
|
if (diff < 60) {
|
||||||
setTimeSinceUpdate(`${diff}s ago`);
|
setTimeSinceUpdate(`${diff}s ago`);
|
||||||
} else {
|
} else {
|
||||||
@@ -43,7 +54,7 @@ export function useAuthMonitorData(): AuthMonitorData {
|
|||||||
updateTime();
|
updateTime();
|
||||||
const interval = setInterval(updateTime, 1000);
|
const interval = setInterval(updateTime, 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [dataUpdatedAt]);
|
}, [activeStatsUpdatedAt]);
|
||||||
|
|
||||||
// Transform auth status data into account rows
|
// Transform auth status data into account rows
|
||||||
const { accounts, totalSuccess, totalFailure, totalRequests, providerStats } = useMemo(() => {
|
const { accounts, totalSuccess, totalFailure, totalRequests, providerStats } = useMemo(() => {
|
||||||
@@ -79,7 +90,7 @@ export function useAuthMonitorData(): AuthMonitorData {
|
|||||||
provider: account.provider || status.provider,
|
provider: account.provider || status.provider,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
buildAccountVisualGroups(normalizedAccounts, statsData).forEach((groupedAccount) => {
|
buildAccountVisualGroups(normalizedAccounts, activeStatsData).forEach((groupedAccount) => {
|
||||||
tSuccess += groupedAccount.successCount;
|
tSuccess += groupedAccount.successCount;
|
||||||
tFailure += groupedAccount.failureCount;
|
tFailure += groupedAccount.failureCount;
|
||||||
providerData.success += groupedAccount.successCount;
|
providerData.success += groupedAccount.successCount;
|
||||||
@@ -131,7 +142,7 @@ export function useAuthMonitorData(): AuthMonitorData {
|
|||||||
totalRequests: tSuccess + tFailure,
|
totalRequests: tSuccess + tFailure,
|
||||||
providerStats: providerStatsArr,
|
providerStats: providerStatsArr,
|
||||||
};
|
};
|
||||||
}, [data?.authStatus, statsData]);
|
}, [data?.authStatus, activeStatsData]);
|
||||||
|
|
||||||
const overallSuccessRate =
|
const overallSuccessRate =
|
||||||
totalRequests > 0 ? Math.round((totalSuccess / totalRequests) * 100) : 100;
|
totalRequests > 0 ? Math.round((totalSuccess / totalRequests) * 100) : 100;
|
||||||
@@ -143,7 +154,7 @@ export function useAuthMonitorData(): AuthMonitorData {
|
|||||||
totalRequests,
|
totalRequests,
|
||||||
providerStats,
|
providerStats,
|
||||||
overallSuccessRate,
|
overallSuccessRate,
|
||||||
isLoading: isLoading || statsLoading,
|
isLoading: isLoading || (statsEnabled && statsLoading),
|
||||||
error: error ?? null,
|
error: error ?? null,
|
||||||
timeSinceUpdate,
|
timeSinceUpdate,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { useAuthMonitorData } from '@/components/monitoring/auth-monitor/hooks';
|
||||||
|
|
||||||
|
const { useCliproxyAuthMock, useCliproxyStatsMock, useCliproxyStatusMock } = vi.hoisted(() => ({
|
||||||
|
useCliproxyAuthMock: vi.fn(),
|
||||||
|
useCliproxyStatsMock: vi.fn(),
|
||||||
|
useCliproxyStatusMock: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/hooks/use-cliproxy', () => ({
|
||||||
|
useCliproxyAuth: useCliproxyAuthMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/hooks/use-cliproxy-stats', () => ({
|
||||||
|
useCliproxyStats: useCliproxyStatsMock,
|
||||||
|
useCliproxyStatus: useCliproxyStatusMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const authStatus = [
|
||||||
|
{
|
||||||
|
provider: 'codex',
|
||||||
|
displayName: 'OpenAI Codex',
|
||||||
|
accounts: [
|
||||||
|
{
|
||||||
|
id: 'codex-account',
|
||||||
|
email: 'codex@example.com',
|
||||||
|
tokenFile: '/tmp/codex.json',
|
||||||
|
provider: 'codex',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('useAuthMonitorData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
useCliproxyAuthMock.mockReturnValue({
|
||||||
|
data: { authStatus },
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
useCliproxyStatsMock.mockReturnValue({
|
||||||
|
data: undefined,
|
||||||
|
isLoading: false,
|
||||||
|
dataUpdatedAt: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps account data visible without polling stats when CLIProxy is unavailable', () => {
|
||||||
|
useCliproxyStatusMock.mockReturnValue({
|
||||||
|
data: { running: false },
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAuthMonitorData());
|
||||||
|
|
||||||
|
expect(useCliproxyStatsMock).toHaveBeenCalledWith(false);
|
||||||
|
expect(result.current.isLoading).toBe(false);
|
||||||
|
expect(result.current.accounts).toHaveLength(1);
|
||||||
|
expect(result.current.totalRequests).toBe(0);
|
||||||
|
expect(result.current.providerStats[0]).toMatchObject({
|
||||||
|
provider: 'codex',
|
||||||
|
accountCount: 1,
|
||||||
|
totalRequests: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables live stats only after CLIProxy is running', () => {
|
||||||
|
useCliproxyStatusMock.mockReturnValue({
|
||||||
|
data: { running: true },
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
useCliproxyStatsMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
accountStats: {
|
||||||
|
'codex:codex@example.com': {
|
||||||
|
source: 'codex@example.com',
|
||||||
|
successCount: 8,
|
||||||
|
failureCount: 2,
|
||||||
|
totalTokens: 100,
|
||||||
|
provider: 'codex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
dataUpdatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAuthMonitorData());
|
||||||
|
|
||||||
|
expect(useCliproxyStatsMock).toHaveBeenCalledWith(true);
|
||||||
|
expect(result.current.totalSuccess).toBe(8);
|
||||||
|
expect(result.current.totalFailure).toBe(2);
|
||||||
|
expect(result.current.totalRequests).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user