Compare commits

...

1 Commits

Author SHA1 Message Date
LTbinglingfeng
db376c7504 fix(layout): wire header refresh to page loaders and quota config refresh 2026-01-03 01:40:54 +08:00
7 changed files with 88 additions and 23 deletions

View File

@@ -36,6 +36,7 @@ import {
useThemeStore,
} from '@/stores';
import { configApi, versionApi } from '@/services/api';
import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh';
const sidebarIcons: Record<string, ReactNode> = {
dashboard: <IconLayoutDashboard size={18} />,
@@ -384,12 +385,22 @@ export function MainLayout() {
const handleRefreshAll = async () => {
clearCache();
try {
await fetchConfig(undefined, true);
showNotification(t('notification.data_refreshed'), 'success');
} catch (error: any) {
showNotification(`${t('notification.refresh_failed')}: ${error?.message || ''}`, 'error');
const results = await Promise.allSettled([
fetchConfig(undefined, true),
triggerHeaderRefresh()
]);
const rejected = results.find((result) => result.status === 'rejected');
if (rejected && rejected.status === 'rejected') {
const reason = rejected.reason;
const message =
typeof reason === 'string' ? reason : reason instanceof Error ? reason.message : '';
showNotification(
`${t('notification.refresh_failed')}${message ? `: ${message}` : ''}`,
'error'
);
return;
}
showNotification(t('notification.data_refreshed'), 'success');
};
const handleVersionCheck = async () => {

View File

@@ -8,3 +8,4 @@ export { useLocalStorage } from './useLocalStorage';
export { useInterval } from './useInterval';
export { useMediaQuery } from './useMediaQuery';
export { usePagination } from './usePagination';
export { useHeaderRefresh } from './useHeaderRefresh';

View File

@@ -0,0 +1,24 @@
import { useEffect } from 'react';
export type HeaderRefreshHandler = () => void | Promise<void>;
let activeHeaderRefreshHandler: HeaderRefreshHandler | null = null;
export const triggerHeaderRefresh = async () => {
if (!activeHeaderRefreshHandler) return;
await activeHeaderRefreshHandler();
};
export const useHeaderRefresh = (handler?: HeaderRefreshHandler | null) => {
useEffect(() => {
if (!handler) return;
activeHeaderRefreshHandler = handler;
return () => {
if (activeHeaderRefreshHandler === handler) {
activeHeaderRefreshHandler = null;
}
};
}, [handler]);
};

View File

@@ -1,8 +1,9 @@
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useInterval } from '@/hooks/useInterval';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useInterval } from '@/hooks/useInterval';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal';
@@ -270,6 +271,12 @@ export function AuthFilesPage() {
}
}, [showNotification, t]);
const handleHeaderRefresh = useCallback(async () => {
await Promise.all([loadFiles(), loadKeyStats(), loadExcluded()]);
}, [loadFiles, loadKeyStats, loadExcluded]);
useHeaderRefresh(handleHeaderRefresh);
useEffect(() => {
loadFiles();
loadKeyStats();
@@ -824,11 +831,16 @@ export function AuthFilesPage() {
<Card
title={t('auth_files.title_section')}
extra={
<div className={styles.headerActions}>
<Button variant="secondary" size="sm" onClick={() => { loadFiles(); loadKeyStats(); }} disabled={loading}>
{t('common.refresh')}
</Button>
extra={
<div className={styles.headerActions}>
<Button
variant="secondary"
size="sm"
onClick={handleHeaderRefresh}
disabled={loading}
>
{t('common.refresh')}
</Button>
<Button
variant="secondary"
size="sm"

View File

@@ -16,6 +16,7 @@ import {
IconTrash2,
IconX,
} from '@/components/ui/icons';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
import { logsApi } from '@/services/api/logs';
import { MANAGEMENT_API_PREFIX } from '@/utils/constants';
@@ -474,6 +475,8 @@ export function LogsPage() {
}
};
useHeaderRefresh(() => loadLogs(false));
const clearLogs = async () => {
if (!window.confirm(t('logs.clear_confirm'))) return;
try {

View File

@@ -4,9 +4,9 @@
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { useAuthStore } from '@/stores';
import { authFilesApi } from '@/services/api';
import { authFilesApi, configFileApi } from '@/services/api';
import {
QuotaSection,
ANTIGRAVITY_CONFIG,
@@ -26,6 +26,15 @@ export function QuotaPage() {
const disableControls = connectionStatus !== 'connected';
const loadConfig = useCallback(async () => {
try {
await configFileApi.fetchConfigYaml();
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : t('notification.refresh_failed');
setError((prev) => prev || errorMessage);
}
}, [t]);
const loadFiles = useCallback(async () => {
setLoading(true);
setError('');
@@ -40,20 +49,22 @@ export function QuotaPage() {
}
}, [t]);
const handleHeaderRefresh = useCallback(async () => {
await Promise.all([loadConfig(), loadFiles()]);
}, [loadConfig, loadFiles]);
useHeaderRefresh(handleHeaderRefresh);
useEffect(() => {
loadFiles();
}, [loadFiles]);
loadConfig();
}, [loadFiles, loadConfig]);
return (
<div className={styles.container}>
<div className={styles.pageHeader}>
<h1 className={styles.pageTitle}>{t('quota_management.title')}</h1>
<p className={styles.description}>{t('quota_management.description')}</p>
<div className={styles.headerActions}>
<Button variant="secondary" size="sm" onClick={loadFiles} disabled={loading}>
{t('quota_management.refresh_files')}
</Button>
</div>
</div>
{error && <div className={styles.errorBox}>{error}</div>}

View File

@@ -14,6 +14,7 @@ import {
import { Button } from '@/components/ui/Button';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
import { useThemeStore } from '@/stores';
import {
StatCards,
@@ -63,6 +64,8 @@ export function UsagePage() {
importing
} = useUsageData();
useHeaderRefresh(loadUsage);
// Chart lines state
const [chartLines, setChartLines] = useState<string[]>(['all']);
const MAX_CHART_LINES = 9;