feat: sync status to web app

This commit is contained in:
izzy
2025-11-20 15:47:30 +00:00
parent f69c49a60f
commit 56a4159295
14 changed files with 300 additions and 105 deletions

View File

@@ -159,6 +159,8 @@ Class | Method | HTTP request | Description
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library *LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library *LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings *LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
*MaintenanceAdminApi* | [**deleteBackup**](doc//MaintenanceAdminApi.md#deletebackup) | **DELETE** /admin/maintenance/admin/maintenance/backups/{filename} | Delete backup
*MaintenanceAdminApi* | [**listBackups**](doc//MaintenanceAdminApi.md#listbackups) | **GET** /admin/maintenance/admin/maintenance/backups/list | List backups
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode *MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
*MaintenanceAdminApi* | [**maintenanceStatus**](doc//MaintenanceAdminApi.md#maintenancestatus) | **GET** /admin/maintenance/admin/maintenance/status | Get maintenance mode status *MaintenanceAdminApi* | [**maintenanceStatus**](doc//MaintenanceAdminApi.md#maintenancestatus) | **GET** /admin/maintenance/admin/maintenance/status | Get maintenance mode status
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode *MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
@@ -409,6 +411,7 @@ Class | Method | HTTP request | Description
- [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md) - [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md)
- [MaintenanceAction](doc//MaintenanceAction.md) - [MaintenanceAction](doc//MaintenanceAction.md)
- [MaintenanceAuthDto](doc//MaintenanceAuthDto.md) - [MaintenanceAuthDto](doc//MaintenanceAuthDto.md)
- [MaintenanceListBackupsResponseDto](doc//MaintenanceListBackupsResponseDto.md)
- [MaintenanceLoginDto](doc//MaintenanceLoginDto.md) - [MaintenanceLoginDto](doc//MaintenanceLoginDto.md)
- [MaintenanceStatusResponseDto](doc//MaintenanceStatusResponseDto.md) - [MaintenanceStatusResponseDto](doc//MaintenanceStatusResponseDto.md)
- [ManualJobName](doc//ManualJobName.md) - [ManualJobName](doc//ManualJobName.md)

View File

@@ -166,6 +166,7 @@ part 'model/logout_response_dto.dart';
part 'model/machine_learning_availability_checks_dto.dart'; part 'model/machine_learning_availability_checks_dto.dart';
part 'model/maintenance_action.dart'; part 'model/maintenance_action.dart';
part 'model/maintenance_auth_dto.dart'; part 'model/maintenance_auth_dto.dart';
part 'model/maintenance_list_backups_response_dto.dart';
part 'model/maintenance_login_dto.dart'; part 'model/maintenance_login_dto.dart';
part 'model/maintenance_status_response_dto.dart'; part 'model/maintenance_status_response_dto.dart';
part 'model/manual_job_name.dart'; part 'model/manual_job_name.dart';

View File

@@ -16,6 +16,103 @@ class MaintenanceAdminApi {
final ApiClient apiClient; final ApiClient apiClient;
/// Delete backup
///
/// Delete a backup by its filename
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] filename (required):
Future<Response> deleteBackupWithHttpInfo(String filename,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/admin/maintenance/backups/{filename}'
.replaceAll('{filename}', filename);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Delete backup
///
/// Delete a backup by its filename
///
/// Parameters:
///
/// * [String] filename (required):
Future<void> deleteBackup(String filename,) async {
final response = await deleteBackupWithHttpInfo(filename,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// List backups
///
/// Get the list of the successful and failed backups
///
/// Note: This method returns the HTTP [Response].
Future<Response> listBackupsWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/maintenance/admin/maintenance/backups/list';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// List backups
///
/// Get the list of the successful and failed backups
Future<MaintenanceListBackupsResponseDto?> listBackups() async {
final response = await listBackupsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceListBackupsResponseDto',) as MaintenanceListBackupsResponseDto;
}
return null;
}
/// Log into maintenance mode /// Log into maintenance mode
/// ///
/// Login with maintenance token or cookie to receive current information and perform further actions. /// Login with maintenance token or cookie to receive current information and perform further actions.

View File

@@ -382,6 +382,8 @@ class ApiClient {
return MaintenanceActionTypeTransformer().decode(value); return MaintenanceActionTypeTransformer().decode(value);
case 'MaintenanceAuthDto': case 'MaintenanceAuthDto':
return MaintenanceAuthDto.fromJson(value); return MaintenanceAuthDto.fromJson(value);
case 'MaintenanceListBackupsResponseDto':
return MaintenanceListBackupsResponseDto.fromJson(value);
case 'MaintenanceLoginDto': case 'MaintenanceLoginDto':
return MaintenanceLoginDto.fromJson(value); return MaintenanceLoginDto.fromJson(value);
case 'MaintenanceStatusResponseDto': case 'MaintenanceStatusResponseDto':

View File

@@ -0,0 +1,111 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MaintenanceListBackupsResponseDto {
/// Returns a new [MaintenanceListBackupsResponseDto] instance.
MaintenanceListBackupsResponseDto({
this.backups = const [],
this.failedBackups = const [],
});
List<String> backups;
List<String> failedBackups;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceListBackupsResponseDto &&
_deepEquality.equals(other.backups, backups) &&
_deepEquality.equals(other.failedBackups, failedBackups);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(backups.hashCode) +
(failedBackups.hashCode);
@override
String toString() => 'MaintenanceListBackupsResponseDto[backups=$backups, failedBackups=$failedBackups]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'backups'] = this.backups;
json[r'failedBackups'] = this.failedBackups;
return json;
}
/// Returns a new [MaintenanceListBackupsResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MaintenanceListBackupsResponseDto? fromJson(dynamic value) {
upgradeDto(value, "MaintenanceListBackupsResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MaintenanceListBackupsResponseDto(
backups: json[r'backups'] is Iterable
? (json[r'backups'] as Iterable).cast<String>().toList(growable: false)
: const [],
failedBackups: json[r'failedBackups'] is Iterable
? (json[r'failedBackups'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<MaintenanceListBackupsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceListBackupsResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceListBackupsResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MaintenanceListBackupsResponseDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceListBackupsResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MaintenanceListBackupsResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MaintenanceListBackupsResponseDto-objects as value to a dart map
static Map<String, List<MaintenanceListBackupsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceListBackupsResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MaintenanceListBackupsResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'backups',
'failedBackups',
};
}

View File

@@ -13,13 +13,13 @@ part of openapi.api;
class MaintenanceStatusResponseDto { class MaintenanceStatusResponseDto {
/// Returns a new [MaintenanceStatusResponseDto] instance. /// Returns a new [MaintenanceStatusResponseDto] instance.
MaintenanceStatusResponseDto({ MaintenanceStatusResponseDto({
this.action, required this.action,
this.error, this.error,
this.progress, this.progress,
this.task, this.task,
}); });
MaintenanceStatusResponseDtoActionEnum? action; MaintenanceAction action;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@@ -55,7 +55,7 @@ class MaintenanceStatusResponseDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(action == null ? 0 : action!.hashCode) + (action.hashCode) +
(error == null ? 0 : error!.hashCode) + (error == null ? 0 : error!.hashCode) +
(progress == null ? 0 : progress!.hashCode) + (progress == null ? 0 : progress!.hashCode) +
(task == null ? 0 : task!.hashCode); (task == null ? 0 : task!.hashCode);
@@ -65,11 +65,7 @@ class MaintenanceStatusResponseDto {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
if (this.action != null) {
json[r'action'] = this.action; json[r'action'] = this.action;
} else {
// json[r'action'] = null;
}
if (this.error != null) { if (this.error != null) {
json[r'error'] = this.error; json[r'error'] = this.error;
} else { } else {
@@ -97,7 +93,7 @@ class MaintenanceStatusResponseDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return MaintenanceStatusResponseDto( return MaintenanceStatusResponseDto(
action: MaintenanceStatusResponseDtoActionEnum.fromJson(json[r'action']), action: MaintenanceAction.fromJson(json[r'action'])!,
error: mapValueOfType<String>(json, r'error'), error: mapValueOfType<String>(json, r'error'),
progress: num.parse('${json[r'progress']}'), progress: num.parse('${json[r'progress']}'),
task: mapValueOfType<String>(json, r'task'), task: mapValueOfType<String>(json, r'task'),
@@ -148,83 +144,7 @@ class MaintenanceStatusResponseDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'action',
}; };
} }
class MaintenanceStatusResponseDtoActionEnum {
/// Instantiate a new enum with the provided [value].
const MaintenanceStatusResponseDtoActionEnum._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const start = MaintenanceStatusResponseDtoActionEnum._(r'start');
static const end = MaintenanceStatusResponseDtoActionEnum._(r'end');
static const restoreDatabase = MaintenanceStatusResponseDtoActionEnum._(r'restore_database');
/// List of all possible values in this [enum][MaintenanceStatusResponseDtoActionEnum].
static const values = <MaintenanceStatusResponseDtoActionEnum>[
start,
end,
restoreDatabase,
];
static MaintenanceStatusResponseDtoActionEnum? fromJson(dynamic value) => MaintenanceStatusResponseDtoActionEnumTypeTransformer().decode(value);
static List<MaintenanceStatusResponseDtoActionEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceStatusResponseDtoActionEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MaintenanceStatusResponseDtoActionEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [MaintenanceStatusResponseDtoActionEnum] to String,
/// and [decode] dynamic data back to [MaintenanceStatusResponseDtoActionEnum].
class MaintenanceStatusResponseDtoActionEnumTypeTransformer {
factory MaintenanceStatusResponseDtoActionEnumTypeTransformer() => _instance ??= const MaintenanceStatusResponseDtoActionEnumTypeTransformer._();
const MaintenanceStatusResponseDtoActionEnumTypeTransformer._();
String encode(MaintenanceStatusResponseDtoActionEnum data) => data.value;
/// Decodes a [dynamic value][data] to a MaintenanceStatusResponseDtoActionEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
MaintenanceStatusResponseDtoActionEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'start': return MaintenanceStatusResponseDtoActionEnum.start;
case r'end': return MaintenanceStatusResponseDtoActionEnum.end;
case r'restore_database': return MaintenanceStatusResponseDtoActionEnum.restoreDatabase;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [MaintenanceStatusResponseDtoActionEnumTypeTransformer] instance.
static MaintenanceStatusResponseDtoActionEnumTypeTransformer? _instance;
}

View File

@@ -16705,12 +16705,11 @@
"MaintenanceStatusResponseDto": { "MaintenanceStatusResponseDto": {
"properties": { "properties": {
"action": { "action": {
"enum": [ "allOf": [
"start", {
"end", "$ref": "#/components/schemas/MaintenanceAction"
"restore_database" }
], ]
"type": "string"
}, },
"error": { "error": {
"type": "string" "type": "string"

View File

@@ -44,8 +44,12 @@ export type SetMaintenanceModeDto = {
action: MaintenanceAction; action: MaintenanceAction;
restoreBackupFilename?: string; restoreBackupFilename?: string;
}; };
export type MaintenanceListBackupsResponseDto = {
backups: string[];
failedBackups: string[];
};
export type MaintenanceStatusResponseDto = { export type MaintenanceStatusResponseDto = {
action?: Action; action: MaintenanceAction;
error?: string; error?: string;
progress?: number; progress?: number;
task?: string; task?: string;
@@ -521,7 +525,7 @@ export type AssetBulkUploadCheckDto = {
assets: AssetBulkUploadCheckItem[]; assets: AssetBulkUploadCheckItem[];
}; };
export type AssetBulkUploadCheckResult = { export type AssetBulkUploadCheckResult = {
action: Action2; action: Action;
assetId?: string; assetId?: string;
id: string; id: string;
isTrashed?: boolean; isTrashed?: boolean;
@@ -1851,6 +1855,28 @@ export function setMaintenanceMode({ setMaintenanceModeDto }: {
body: setMaintenanceModeDto body: setMaintenanceModeDto
}))); })));
} }
/**
* List backups
*/
export function listBackups(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: MaintenanceListBackupsResponseDto;
}>("/admin/maintenance/admin/maintenance/backups/list", {
...opts
}));
}
/**
* Delete backup
*/
export function deleteBackup({ filename }: {
filename: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/admin/maintenance/admin/maintenance/backups/${encodeURIComponent(filename)}`, {
...opts,
method: "DELETE"
}));
}
/** /**
* Get maintenance mode status * Get maintenance mode status
*/ */
@@ -5074,11 +5100,6 @@ export enum MaintenanceAction {
End = "end", End = "end",
RestoreDatabase = "restore_database" RestoreDatabase = "restore_database"
} }
export enum Action {
Start = "start",
End = "end",
RestoreDatabase = "restore_database"
}
export enum NotificationLevel { export enum NotificationLevel {
Success = "success", Success = "success",
Error = "error", Error = "error",
@@ -5284,7 +5305,7 @@ export enum AssetMediaStatus {
Replaced = "replaced", Replaced = "replaced",
Duplicate = "duplicate" Duplicate = "duplicate"
} }
export enum Action2 { export enum Action {
Accept = "accept", Accept = "accept",
Reject = "reject" Reject = "reject"
} }

View File

@@ -19,6 +19,7 @@ export class MaintenanceAuthDto {
} }
export class MaintenanceStatusResponseDto { export class MaintenanceStatusResponseDto {
@ValidateEnum({ enum: MaintenanceAction, name: 'MaintenanceAction' })
action!: MaintenanceAction; action!: MaintenanceAction;
progress?: number; progress?: number;

View File

@@ -22,6 +22,7 @@ export enum AppRoute {
ADMIN_USERS = '/admin/users', ADMIN_USERS = '/admin/users',
ADMIN_LIBRARY_MANAGEMENT = '/admin/library-management', ADMIN_LIBRARY_MANAGEMENT = '/admin/library-management',
ADMIN_SETTINGS = '/admin/system-settings', ADMIN_SETTINGS = '/admin/system-settings',
ADMIN_MAINTENANCE = '/admin/maintenance',
ADMIN_STATS = '/admin/server-status', ADMIN_STATS = '/admin/server-status',
ADMIN_JOBS = '/admin/jobs-status', ADMIN_JOBS = '/admin/jobs-status',
ADMIN_REPAIR = '/admin/repair', ADMIN_REPAIR = '/admin/repair',

View File

@@ -1,6 +1,7 @@
import { type MaintenanceAuthDto } from '@immich/sdk'; import { type MaintenanceAuthDto, type MaintenanceStatusResponseDto } from '@immich/sdk';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const maintenanceStore = { export const maintenanceStore = {
auth: writable<MaintenanceAuthDto>(), auth: writable<MaintenanceAuthDto>(),
status: writable<MaintenanceStatusResponseDto>(),
}; };

View File

@@ -1,9 +1,16 @@
import { page } from '$app/state'; import { page } from '$app/state';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { maintenanceStore } from '$lib/stores/maintenance.store';
import { notificationManager } from '$lib/stores/notification-manager.svelte'; import { notificationManager } from '$lib/stores/notification-manager.svelte';
import { createEventEmitter } from '$lib/utils/eventemitter'; import { createEventEmitter } from '$lib/utils/eventemitter';
import { type AssetResponseDto, type NotificationDto, type ServerVersionResponseDto } from '@immich/sdk'; import {
MaintenanceAction,
type AssetResponseDto,
type MaintenanceStatusResponseDto,
type NotificationDto,
type ServerVersionResponseDto,
} from '@immich/sdk';
import { io, type Socket } from 'socket.io-client'; import { io, type Socket } from 'socket.io-client';
import { get, writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import { user } from './user.store'; import { user } from './user.store';
@@ -37,6 +44,8 @@ export interface Events {
on_notification: (notification: NotificationDto) => void; on_notification: (notification: NotificationDto) => void;
AppRestartV1: (event: AppRestartEvent) => void; AppRestartV1: (event: AppRestartEvent) => void;
MaintenanceStatusV1: (event: MaintenanceStatusResponseDto) => void;
} }
const websocket: Socket<Events> = io({ const websocket: Socket<Events> = io({
@@ -61,6 +70,15 @@ websocket
.on('disconnect', () => websocketStore.connected.set(false)) .on('disconnect', () => websocketStore.connected.set(false))
.on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion)) .on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
.on('AppRestartV1', (mode) => websocketStore.serverRestarting.set(mode)) .on('AppRestartV1', (mode) => websocketStore.serverRestarting.set(mode))
.on('MaintenanceStatusV1', (status) => {
maintenanceStore.status.set(status);
if (status.action === MaintenanceAction.End) {
websocketStore.serverRestarting.set({
isMaintenanceMode: false,
});
}
})
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion)) .on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
.on('on_session_delete', () => authManager.logout()) .on('on_session_delete', () => authManager.logout())
.on('on_notification', () => notificationManager.refresh()) .on('on_notification', () => notificationManager.refresh())
@@ -68,7 +86,7 @@ websocket
export const openWebsocketConnection = () => { export const openWebsocketConnection = () => {
try { try {
if (get(user) || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) { if (get(user) || get(websocketStore.serverRestarting) || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) {
websocket.connect(); websocket.connect();
} }
} catch (error) { } catch (error) {

View File

@@ -1,6 +1,7 @@
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { maintenanceStore } from '$lib/stores/maintenance.store'; import { maintenanceStore } from '$lib/stores/maintenance.store';
import { maintenanceLogin } from '@immich/sdk'; import { websocketStore } from '$lib/stores/websocket';
import { MaintenanceAction, maintenanceLogin, maintenanceStatus } from '@immich/sdk';
export function maintenanceCreateUrl(url: URL) { export function maintenanceCreateUrl(url: URL) {
const target = new URL(AppRoute.MAINTENANCE, url.origin); const target = new URL(AppRoute.MAINTENANCE, url.origin);
@@ -31,3 +32,22 @@ export const loadMaintenanceAuth = async () => {
// silently fail // silently fail
} }
}; };
export const loadMaintenanceStatus = async () => {
try {
const status = await maintenanceStatus();
maintenanceStore.status.set(status);
if (status.action === MaintenanceAction.End) {
websocketStore.serverRestarting.set({
isMaintenanceMode: false,
});
}
} catch (error) {
const status = (error as { status: number })?.status;
if (status && status >= 500 && status < 600) {
await new Promise((r) => setTimeout(r, 1e3));
await loadMaintenanceStatus();
}
}
};

View File

@@ -1,6 +1,6 @@
import { loadMaintenanceAuth } from '$lib/utils/maintenance'; import { loadMaintenanceAuth, loadMaintenanceStatus } from '$lib/utils/maintenance';
import type { PageLoad } from '../admin/$types'; import type { PageLoad } from '../admin/$types';
export const load = (async () => { export const load = (async () => {
await loadMaintenanceAuth(); await Promise.allSettled([loadMaintenanceAuth(), loadMaintenanceStatus()]);
}) satisfies PageLoad; }) satisfies PageLoad;