This commit is contained in:
shuaiplus
2026-07-17 11:33:41 +08:00
2 changed files with 25 additions and 1 deletions
+20 -1
View File
@@ -13,6 +13,7 @@ import {
} from '@/lib/api/backup'; } from '@/lib/api/backup';
import { import {
REMOTE_BROWSER_ITEMS_PER_PAGE, REMOTE_BROWSER_ITEMS_PER_PAGE,
REMOTE_BROWSER_REFRESH_TTL_MS,
compareRemoteItems, compareRemoteItems,
createDraftBackupSettings, createDraftBackupSettings,
createDraftDestinationRecord, createDraftDestinationRecord,
@@ -217,6 +218,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
const [remoteBrowserCache, setRemoteBrowserCache] = useState<Record<string, RemoteBackupBrowserResponse>>(persistedRemoteState.cache); const [remoteBrowserCache, setRemoteBrowserCache] = useState<Record<string, RemoteBackupBrowserResponse>>(persistedRemoteState.cache);
const [remoteBrowserPathByDestination, setRemoteBrowserPathByDestination] = useState<Record<string, string>>(persistedRemoteState.pathByDestination); const [remoteBrowserPathByDestination, setRemoteBrowserPathByDestination] = useState<Record<string, string>>(persistedRemoteState.pathByDestination);
const [remoteBrowserPageByKey, setRemoteBrowserPageByKey] = useState<Record<string, number>>(persistedRemoteState.pageByKey); const [remoteBrowserPageByKey, setRemoteBrowserPageByKey] = useState<Record<string, number>>(persistedRemoteState.pageByKey);
const [remoteBrowserRefreshedAt, setRemoteBrowserRefreshedAt] = useState<Record<string, number>>(persistedRemoteState.refreshedAt || {});
const [showAddChooser, setShowAddChooser] = useState(false); const [showAddChooser, setShowAddChooser] = useState(false);
const visibleDestinations = getVisibleDestinations(settings); const visibleDestinations = getVisibleDestinations(settings);
@@ -308,8 +310,22 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
pathByDestination: remoteBrowserPathByDestination, pathByDestination: remoteBrowserPathByDestination,
pageByKey: remoteBrowserPageByKey, pageByKey: remoteBrowserPageByKey,
selectedDestinationId, selectedDestinationId,
refreshedAt: remoteBrowserRefreshedAt,
}); });
}, [props.currentUserId, remoteBrowserCache, remoteBrowserPageByKey, remoteBrowserPathByDestination, selectedDestinationId]); }, [props.currentUserId, remoteBrowserCache, remoteBrowserPageByKey, remoteBrowserPathByDestination, remoteBrowserRefreshedAt, selectedDestinationId]);
useEffect(() => {
if (!savedSelectedDestination) return;
const destinationId = savedSelectedDestination.id;
const path = remoteBrowserPathByDestination[destinationId] || '';
const cacheKey = getRemoteBrowserCacheKey(destinationId, path);
const lastRefreshed = remoteBrowserRefreshedAt[cacheKey] || 0;
const isStale = Date.now() - lastRefreshed > REMOTE_BROWSER_REFRESH_TTL_MS;
if (isStale) {
void loadRemoteBrowser(destinationId, path, { force: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [savedSelectedDestination?.id]);
useEffect(() => { useEffect(() => {
if (!restoreProgress) { if (!restoreProgress) {
@@ -398,6 +414,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
}; };
setRemoteBrowserCache((current) => ({ ...current, [cacheKey]: nextBrowser })); setRemoteBrowserCache((current) => ({ ...current, [cacheKey]: nextBrowser }));
setRemoteBrowserPageByKey((current) => ({ ...current, [cacheKey]: 1 })); setRemoteBrowserPageByKey((current) => ({ ...current, [cacheKey]: 1 }));
setRemoteBrowserRefreshedAt((current) => ({ ...current, [cacheKey]: Date.now() }));
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : t('txt_backup_remote_load_failed'); const message = error instanceof Error ? error.message : t('txt_backup_remote_load_failed');
setLocalError(message); setLocalError(message);
@@ -543,6 +560,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
).cache); ).cache);
setRemoteBrowserPathByDestination((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== destinationIdToDelete))); setRemoteBrowserPathByDestination((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== destinationIdToDelete)));
setRemoteBrowserPageByKey((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToDelete}:`)))); setRemoteBrowserPageByKey((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToDelete}:`))));
setRemoteBrowserRefreshedAt((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToDelete}:`))));
setSelectedDestinationId(nextSelected); setSelectedDestinationId(nextSelected);
setConfirmDeleteDestinationOpen(false); setConfirmDeleteDestinationOpen(false);
props.onNotify('success', t('txt_backup_destination_deleted')); props.onNotify('success', t('txt_backup_destination_deleted'));
@@ -670,6 +688,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
setRemoteBrowserCache((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToInvalidate}:`)))); setRemoteBrowserCache((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToInvalidate}:`))));
setRemoteBrowserPathByDestination((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== destinationIdToInvalidate))); setRemoteBrowserPathByDestination((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== destinationIdToInvalidate)));
setRemoteBrowserPageByKey((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToInvalidate}:`)))); setRemoteBrowserPageByKey((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToInvalidate}:`))));
setRemoteBrowserRefreshedAt((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(`${destinationIdToInvalidate}:`))));
} }
setSelectedDestinationId(nextSelected); setSelectedDestinationId(nextSelected);
props.onNotify('success', t('txt_backup_settings_saved')); props.onNotify('success', t('txt_backup_settings_saved'));
+5
View File
@@ -14,10 +14,12 @@ export interface PersistedRemoteBrowserState {
pathByDestination: Record<string, string>; pathByDestination: Record<string, string>;
pageByKey: Record<string, number>; pageByKey: Record<string, number>;
selectedDestinationId: string | null; selectedDestinationId: string | null;
refreshedAt: Record<string, number>;
} }
export const REMOTE_BROWSER_STORAGE_KEY = 'nodewarden.backup.remote-browser.v1'; export const REMOTE_BROWSER_STORAGE_KEY = 'nodewarden.backup.remote-browser.v1';
export const REMOTE_BROWSER_ITEMS_PER_PAGE = 10; export const REMOTE_BROWSER_ITEMS_PER_PAGE = 10;
export const REMOTE_BROWSER_REFRESH_TTL_MS = 5 * 60 * 1000; // 5 minutes
export const COMMON_TIME_ZONES = [ export const COMMON_TIME_ZONES = [
'UTC', 'UTC',
@@ -148,6 +150,7 @@ export function loadPersistedRemoteBrowserState(userId?: string | null): Persist
pathByDestination: {}, pathByDestination: {},
pageByKey: {}, pageByKey: {},
selectedDestinationId: null, selectedDestinationId: null,
refreshedAt: {},
}; };
} }
const parsed = JSON.parse(raw) as Partial<PersistedRemoteBrowserState>; const parsed = JSON.parse(raw) as Partial<PersistedRemoteBrowserState>;
@@ -156,6 +159,7 @@ export function loadPersistedRemoteBrowserState(userId?: string | null): Persist
pathByDestination: parsed.pathByDestination && typeof parsed.pathByDestination === 'object' ? parsed.pathByDestination : {}, pathByDestination: parsed.pathByDestination && typeof parsed.pathByDestination === 'object' ? parsed.pathByDestination : {},
pageByKey: parsed.pageByKey && typeof parsed.pageByKey === 'object' ? parsed.pageByKey : {}, pageByKey: parsed.pageByKey && typeof parsed.pageByKey === 'object' ? parsed.pageByKey : {},
selectedDestinationId: typeof parsed.selectedDestinationId === 'string' ? parsed.selectedDestinationId : null, selectedDestinationId: typeof parsed.selectedDestinationId === 'string' ? parsed.selectedDestinationId : null,
refreshedAt: parsed.refreshedAt && typeof parsed.refreshedAt === 'object' ? parsed.refreshedAt as Record<string, number> : {},
}; };
} catch { } catch {
return { return {
@@ -163,6 +167,7 @@ export function loadPersistedRemoteBrowserState(userId?: string | null): Persist
pathByDestination: {}, pathByDestination: {},
pageByKey: {}, pageByKey: {},
selectedDestinationId: null, selectedDestinationId: null,
refreshedAt: {},
}; };
} }
} }