mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: release candidate support (#28665)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
d120444a87
commit
c8a1d0e400
41 changed files with 809 additions and 191 deletions
|
|
@ -4,12 +4,12 @@
|
|||
import ServerAboutModal from '$lib/modals/ServerAboutModal.svelte';
|
||||
import { userInteraction } from '$lib/stores/user.svelte';
|
||||
import { websocketStore } from '$lib/stores/websocket';
|
||||
import type { ReleaseEvent } from '$lib/types';
|
||||
import { semverToName } from '$lib/utils';
|
||||
import { requestServerInfo } from '$lib/utils/auth';
|
||||
import {
|
||||
getAboutInfo,
|
||||
getVersionHistory,
|
||||
type ReleaseEventV1,
|
||||
type ServerAboutResponseDto,
|
||||
type ServerVersionHistoryResponseDto,
|
||||
} from '@immich/sdk';
|
||||
|
|
@ -35,11 +35,9 @@
|
|||
userInteraction.versions = versions;
|
||||
});
|
||||
let isMain = $derived(info?.sourceRef === 'main' && info.repository === 'immich-app/immich');
|
||||
let version = $derived(
|
||||
$serverVersion ? `v${$serverVersion.major}.${$serverVersion.minor}.${$serverVersion.patch}` : null,
|
||||
);
|
||||
let version = $derived($serverVersion ? semverToName($serverVersion) : null);
|
||||
|
||||
const getReleaseInfo = (release?: ReleaseEvent) => {
|
||||
const getReleaseInfo = (release?: ReleaseEventV1) => {
|
||||
if (!release || !release?.isAvailable || !authManager.user.isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import type {
|
|||
LoginResponseDto,
|
||||
PersonResponseDto,
|
||||
QueueResponseDto,
|
||||
ReleaseEventV1,
|
||||
SharedLinkResponseDto,
|
||||
SystemConfigDto,
|
||||
TagResponseDto,
|
||||
UserAdminResponseDto,
|
||||
WorkflowResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import type { ReleaseEvent } from '$lib/types';
|
||||
import { BaseEventManager } from '$lib/utils/base-event-manager.svelte';
|
||||
import type { TreeNode } from '$lib/utils/tree-utils';
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ export type Events = {
|
|||
WorkflowUpdate: [WorkflowResponseDto];
|
||||
WorkflowDelete: [WorkflowResponseDto];
|
||||
|
||||
ReleaseEvent: [ReleaseEvent];
|
||||
ReleaseEvent: [ReleaseEventV1];
|
||||
|
||||
WebsocketConnect: [];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { ReleaseEventV1 } from '@immich/sdk';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { type ReleaseEvent } from '$lib/types';
|
||||
|
||||
class ReleaseManager {
|
||||
value = $state<ReleaseEvent | undefined>();
|
||||
value = $state<ReleaseEventV1 | undefined>();
|
||||
|
||||
constructor() {
|
||||
eventManager.on({
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
type AssetResponseDto,
|
||||
type MaintenanceStatusResponseDto,
|
||||
type NotificationDto,
|
||||
type ReleaseEventV1,
|
||||
type ServerVersionResponseDto,
|
||||
type SyncAssetEditV1,
|
||||
type SyncAssetV2,
|
||||
|
|
@ -15,7 +16,6 @@ import { eventManager } from '$lib/managers/event-manager.svelte';
|
|||
import { Route } from '$lib/route';
|
||||
import { maintenanceStore } from '$lib/stores/maintenance.store';
|
||||
import { notificationManager } from '$lib/stores/notification-manager.svelte';
|
||||
import type { ReleaseEvent } from '$lib/types';
|
||||
import { createEventEmitter } from '$lib/utils/eventemitter';
|
||||
|
||||
interface AppRestartEvent {
|
||||
|
|
@ -34,7 +34,7 @@ export interface Events {
|
|||
on_person_thumbnail: (personId: string) => void;
|
||||
on_server_version: (serverVersion: ServerVersionResponseDto) => void;
|
||||
on_config_update: () => void;
|
||||
on_new_release: (event: ReleaseEvent) => void;
|
||||
on_new_release: (event: ReleaseEventV1) => void;
|
||||
on_session_delete: (sessionId: string) => void;
|
||||
on_notification: (notification: NotificationDto) => void;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { QueueResponseDto, ServerVersionResponseDto } from '@immich/sdk';
|
||||
import type { QueueResponseDto } from '@immich/sdk';
|
||||
import type { ActionItem } from '@immich/ui';
|
||||
import type { DateTime } from 'luxon';
|
||||
import type { SvelteSet } from 'svelte/reactivity';
|
||||
|
|
@ -7,14 +7,6 @@ import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
|||
|
||||
export type LatLng = { lng: number; lat: number };
|
||||
|
||||
export interface ReleaseEvent {
|
||||
isAvailable: boolean;
|
||||
/** ISO8601 */
|
||||
checkedAt: string;
|
||||
serverVersion: ServerVersionResponseDto;
|
||||
releaseVersion: ServerVersionResponseDto;
|
||||
}
|
||||
|
||||
export type QueueSnapshot = { timestamp: number; snapshot?: QueueResponseDto[] };
|
||||
|
||||
export type HeaderButtonActionItem = ActionItem & { data?: { title?: string } };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AssetTypeEnum } from '@immich/sdk';
|
||||
import { getAssetUrl, getReleaseType } from '$lib/utils';
|
||||
import { getAssetUrl } from '$lib/utils';
|
||||
import { assetFactory } from '@test-data/factories/asset-factory';
|
||||
import { sharedLinkFactory } from '@test-data/factories/shared-link-factory';
|
||||
|
||||
|
|
@ -161,26 +161,4 @@ describe('utils', () => {
|
|||
expect(url).toContain(asset.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe(getReleaseType.name, () => {
|
||||
it('should return "major" for major version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 2, minor: 0, patch: 0 })).toBe('major');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 3, minor: 2, patch: 1 })).toBe('major');
|
||||
});
|
||||
|
||||
it('should return "minor" for minor version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 1, patch: 0 })).toBe('minor');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 2, patch: 1 })).toBe('minor');
|
||||
});
|
||||
|
||||
it('should return "patch" for patch version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 1 })).toBe('patch');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 5 })).toBe('patch');
|
||||
});
|
||||
|
||||
it('should return "none" for matching versions', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 0 })).toBe('none');
|
||||
expect(getReleaseType({ major: 1, minor: 2, patch: 3 }, { major: 1, minor: 2, patch: 3 })).toBe('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -411,26 +411,8 @@ export function createDateFormatter(localeCode: string | undefined): DateFormatt
|
|||
};
|
||||
}
|
||||
|
||||
export const getReleaseType = (
|
||||
current: ServerVersionResponseDto,
|
||||
newVersion: ServerVersionResponseDto,
|
||||
): 'major' | 'minor' | 'patch' | 'none' => {
|
||||
if (current.major !== newVersion.major) {
|
||||
return 'major';
|
||||
}
|
||||
|
||||
if (current.minor !== newVersion.minor) {
|
||||
return 'minor';
|
||||
}
|
||||
|
||||
if (current.patch !== newVersion.patch) {
|
||||
return 'patch';
|
||||
}
|
||||
|
||||
return 'none';
|
||||
};
|
||||
|
||||
export const semverToName = ({ major, minor, patch }: ServerVersionResponseDto) => `v${major}.${minor}.${patch}`;
|
||||
export const semverToName = ({ major, minor, patch, prerelease }: ServerVersionResponseDto) =>
|
||||
`v${major}.${minor}.${patch}${prerelease ? `-rc.${prerelease}` : ''}`;
|
||||
|
||||
export const withoutIcons = (actions: ActionItem[]): ActionItem[] =>
|
||||
actions.map((action) => ({ ...action, icon: undefined }));
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte';
|
||||
import type { ReleaseEvent } from '$lib/types';
|
||||
import { getReleaseType, semverToName } from '$lib/utils';
|
||||
import { semverToName } from '$lib/utils';
|
||||
import { ReleaseType, type ReleaseEventV1 } from '@immich/sdk';
|
||||
import { modalManager } from '@immich/ui';
|
||||
|
||||
let modal = $state<{
|
||||
|
|
@ -11,16 +11,20 @@
|
|||
close: () => Promise<void>;
|
||||
}>();
|
||||
|
||||
const onReleaseEvent = async (release: ReleaseEvent) => {
|
||||
const onReleaseEvent = async (release: ReleaseEventV1) => {
|
||||
if (!release.isAvailable || !authManager.user.isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const releaseVersion = semverToName(release.releaseVersion);
|
||||
const serverVersion = semverToName(release.serverVersion);
|
||||
const type = getReleaseType(release.serverVersion, release.releaseVersion);
|
||||
|
||||
if (type === 'none' || type === 'patch' || localStorage.getItem('appVersion') === releaseVersion) {
|
||||
if (
|
||||
!release.type ||
|
||||
release.type === ReleaseType.Patch ||
|
||||
release.type === ReleaseType.Prepatch ||
|
||||
localStorage.getItem('appVersion') === releaseVersion
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import SettingSelect from './SettingSelect.svelte';
|
||||
import { ReleaseChannel } from '@immich/sdk';
|
||||
|
||||
const disabled = $derived(featureFlagsManager.value.configFile);
|
||||
const config = $derived(systemConfigManager.value);
|
||||
let configToEdit = $state(systemConfigManager.cloneValue());
|
||||
</script>
|
||||
|
||||
|
|
@ -20,6 +23,20 @@
|
|||
bind:checked={configToEdit.newVersionCheck.enabled}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingSelect
|
||||
label={$t('admin.version_check_channel')}
|
||||
desc={$t('admin.version_check_channel_description')}
|
||||
bind:value={configToEdit.newVersionCheck.channel}
|
||||
options={[
|
||||
{
|
||||
value: ReleaseChannel.Stable,
|
||||
text: $t('admin.release_channel_stable'),
|
||||
},
|
||||
{ value: ReleaseChannel.ReleaseCandidate, text: $t('admin.release_channel_release_candidate') },
|
||||
]}
|
||||
isEdited={configToEdit.newVersionCheck.channel !== config.newVersionCheck.channel}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingButtonsRow bind:configToEdit keys={['newVersionCheck']} {disabled} />
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue