mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix: timestamp handling for database backup in Web UI (#27359)
* Fix #26502: Fix timestamp handling for database backup in Web UI Frontend parsed backup timestamps as UTC, but they were in the server's local timezone, causing wrong relative times. Add `timezone` field to DatabaseBackupDto to expose server timezone. Update frontend to parse timestamps using this timezone. Convert timestamps to user's local timezone before rendering. Fallback to browser timezone if server timezone is missing. Ensures correct relative time display in Web UI. * fix: regenerate open-api types and remove custom backup type - Ran `make open-api` to update types based on backend changes - Removed custom BackupWithTimezone type in MaintenanceBackupsList - Updated timezone props to use the newly generated native type * fix: simplify timezone handling for database backups - Updated DatabaseBackupDto to make timezone a required property - Removed manual DateTime.local().zoneName fallbacks - Cleaned up type casts after regenerating OpenAPI types * fix: Add missing newline at end of spec file
This commit is contained in:
committed by
GitHub
parent
fbe631fe91
commit
95c1f0efeb
@@ -0,0 +1,54 @@
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { renderWithTooltips } from '$tests/helpers';
|
||||
import { screen } from '@testing-library/svelte';
|
||||
import { DateTime } from 'luxon';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import MaintenanceBackupEntry from './MaintenanceBackupEntry.svelte';
|
||||
|
||||
vi.mock('$lib/services/database-backups.service', () => ({
|
||||
getDatabaseBackupActions: () => ({
|
||||
Download: { type: 'command', title: 'Download', onAction: vi.fn() },
|
||||
Delete: { type: 'command', title: 'Delete', onAction: vi.fn() },
|
||||
}),
|
||||
handleRestoreDatabaseBackup: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('MaintenanceBackupEntry', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-03-24T12:00:00Z'));
|
||||
locale.set('en');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('renders relative backup time using the user timezone instead of UTC', () => {
|
||||
const backupTimestamp = '20260324T110000';
|
||||
|
||||
const expectedRelativeTime = DateTime.fromFormat(backupTimestamp, "yyyyMMdd'T'HHmmss", {
|
||||
zone: 'Asia/Tokyo',
|
||||
})
|
||||
.toLocal()
|
||||
.toRelative({ locale: 'en' });
|
||||
|
||||
const utcRelativeTime = DateTime.fromFormat(backupTimestamp, "yyyyMMdd'T'HHmmss", {
|
||||
zone: 'UTC',
|
||||
})
|
||||
.toLocal()
|
||||
.toRelative({ locale: 'en' });
|
||||
|
||||
expect(expectedRelativeTime).toBeTruthy();
|
||||
expect(expectedRelativeTime).not.toEqual(utcRelativeTime);
|
||||
|
||||
renderWithTooltips(MaintenanceBackupEntry, {
|
||||
expectedVersion: '1.2.3',
|
||||
filename: 'immich-db-backup-20260324T110000-v1.2.3-snapshot.sql.gz',
|
||||
filesize: 1024,
|
||||
timezone: 'Asia/Tokyo',
|
||||
});
|
||||
|
||||
expect(screen.getByText(expectedRelativeTime!)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -13,16 +13,17 @@
|
||||
filename: string;
|
||||
filesize: number;
|
||||
expectedVersion: string;
|
||||
timezone?: string;
|
||||
};
|
||||
|
||||
const { filename, filesize, expectedVersion }: Props = $props();
|
||||
const { filename, filesize, expectedVersion, timezone }: Props = $props();
|
||||
|
||||
const filesizeText = $derived(getBytesWithUnit(filesize, 1));
|
||||
|
||||
const backupDateTime = $derived.by(() => {
|
||||
const dateMatch = filename.match(/\d+T\d+/);
|
||||
if (dateMatch) {
|
||||
return DateTime.fromFormat(dateMatch[0], "yyyyMMdd'T'HHmmss", { zone: 'utc' }).toLocal();
|
||||
return DateTime.fromFormat(dateMatch[0], "yyyyMMdd'T'HHmmss", { zone: timezone }).toLocal();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -51,12 +51,13 @@
|
||||
const unknownDateKey = $t('unknown_date');
|
||||
|
||||
for (const backup of backups) {
|
||||
const timezone = backup.timezone;
|
||||
const dateMatch = backup.filename.match(/\d+T\d+/);
|
||||
let dateKey: string;
|
||||
let dt: DateTime;
|
||||
|
||||
if (dateMatch) {
|
||||
dt = DateTime.fromFormat(dateMatch[0], "yyyyMMdd'T'HHmmss", { zone: 'utc' });
|
||||
dt = DateTime.fromFormat(dateMatch[0], "yyyyMMdd'T'HHmmss", { zone: timezone });
|
||||
dateKey = dt.toFormat('LLLL d, yyyy');
|
||||
} else {
|
||||
dt = DateTime.fromMillis(0);
|
||||
@@ -128,6 +129,7 @@
|
||||
filename={backup.filename}
|
||||
filesize={backup.filesize}
|
||||
expectedVersion={props.expectedVersion}
|
||||
timezone={backup.timezone}
|
||||
/>
|
||||
{/each}
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user