From 2afbff36a3eab962a2b9afa752d199cc80d843d7 Mon Sep 17 00:00:00 2001 From: izzy Date: Fri, 31 Oct 2025 17:28:57 +0000 Subject: [PATCH] refactor: use system metadata for maintenance status --- open-api/immich-openapi-specs.json | 103 +++++++++++++++--- open-api/typescript-sdk/src/fetch-client.ts | 33 +++++- server/src/config.ts | 6 - server/src/controllers/index.ts | 4 +- .../src/controllers/maintenance.controller.ts | 28 +++++ server/src/dtos/maintenance.dto.ts | 6 + server/src/dtos/system-config.dto.ts | 11 -- server/src/enum.ts | 1 + server/src/main.ts | 25 +---- server/src/repositories/event.repository.ts | 1 + .../src/repositories/websocket.repository.ts | 2 +- server/src/services/cli.service.ts | 9 +- server/src/services/index.ts | 2 + server/src/services/maintenance.service.ts | 53 +++++++++ server/src/services/server.service.ts | 3 +- server/src/services/system-config.service.ts | 14 +-- server/src/workers/api.ts | 4 +- server/src/workers/maintenance.ts | 4 +- server/src/workers/microservices.ts | 4 +- .../admin-settings/MaintenanceSettings.svelte | 40 ++----- web/src/routes/maintenance/+page.svelte | 11 +- 21 files changed, 240 insertions(+), 124 deletions(-) create mode 100644 server/src/controllers/maintenance.controller.ts create mode 100644 server/src/dtos/maintenance.dto.ts create mode 100644 server/src/services/maintenance.service.ts diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index e30b996e4b..a14336395d 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -242,6 +242,83 @@ "description": "This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission." } }, + "/admin/maintenance": { + "get": { + "operationId": "getMaintenanceMode", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaintenanceModeResponseDto" + } + } + }, + "description": "" + } + }, + "tags": [ + "Maintenance (admin)" + ] + } + }, + "/admin/maintenance/end": { + "post": { + "operationId": "endMaintenance", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-permission": "systemMetadata.update", + "description": "This endpoint is an admin-only route, and requires the `systemMetadata.update` permission." + } + }, + "/admin/maintenance/start": { + "post": { + "operationId": "startMaintenance", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-permission": "systemMetadata.update", + "description": "This endpoint is an admin-only route, and requires the `systemMetadata.update` permission." + } + }, "/admin/notifications": { "post": { "operationId": "createNotification", @@ -12575,6 +12652,17 @@ ], "type": "object" }, + "MaintenanceModeResponseDto": { + "properties": { + "isMaintenanceMode": { + "type": "boolean" + } + }, + "required": [ + "isMaintenanceMode" + ], + "type": "object" + }, "ManualJobName": { "enum": [ "person-cleanup", @@ -16374,9 +16462,6 @@ "machineLearning": { "$ref": "#/components/schemas/SystemConfigMachineLearningDto" }, - "maintenance": { - "$ref": "#/components/schemas/SystemConfigMaintenanceDto" - }, "map": { "$ref": "#/components/schemas/SystemConfigMapDto" }, @@ -16428,7 +16513,6 @@ "library", "logging", "machineLearning", - "maintenance", "map", "metadata", "newVersionCheck", @@ -16827,17 +16911,6 @@ ], "type": "object" }, - "SystemConfigMaintenanceDto": { - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "type": "object" - }, "SystemConfigMapDto": { "properties": { "darkStyle": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 7b8c24f7ec..f42a02c701 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -40,6 +40,9 @@ export type ActivityStatisticsResponseDto = { comments: number; likes: number; }; +export type MaintenanceModeResponseDto = { + isMaintenanceMode: boolean; +}; export type NotificationCreateDto = { data?: object; description?: string | null; @@ -1471,9 +1474,6 @@ export type SystemConfigMachineLearningDto = { ocr: OcrConfig; urls: string[]; }; -export type SystemConfigMaintenanceDto = { - enabled: boolean; -}; export type SystemConfigMapDto = { darkStyle: string; enabled: boolean; @@ -1561,7 +1561,6 @@ export type SystemConfigDto = { library: SystemConfigLibraryDto; logging: SystemConfigLoggingDto; machineLearning: SystemConfigMachineLearningDto; - maintenance: SystemConfigMaintenanceDto; map: SystemConfigMapDto; metadata: SystemConfigMetadataDto; newVersionCheck: SystemConfigNewVersionCheckDto; @@ -1754,6 +1753,32 @@ export function unlinkAllOAuthAccountsAdmin(opts?: Oazapfts.RequestOpts) { method: "POST" })); } +export function getMaintenanceMode(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: MaintenanceModeResponseDto; + }>("/admin/maintenance", { + ...opts + })); +} +/** + * This endpoint is an admin-only route, and requires the `systemMetadata.update` permission. + */ +export function endMaintenance(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/admin/maintenance/end", { + ...opts, + method: "POST" + })); +} +/** + * This endpoint is an admin-only route, and requires the `systemMetadata.update` permission. + */ +export function startMaintenance(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/admin/maintenance/start", { + ...opts, + method: "POST" + })); +} export function createNotification({ notificationCreateDto }: { notificationCreateDto: NotificationCreateDto; }, opts?: Oazapfts.RequestOpts) { diff --git a/server/src/config.ts b/server/src/config.ts index 1d369635c3..e81ad49621 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -87,9 +87,6 @@ export interface SystemConfig { lightStyle: string; darkStyle: string; }; - maintenance: { - enabled: boolean; - }; reverseGeocoding: { enabled: boolean; }; @@ -279,9 +276,6 @@ export const defaults = Object.freeze({ lightStyle: 'https://tiles.immich.cloud/v1/style/light.json', darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json', }, - maintenance: { - enabled: false, - }, reverseGeocoding: { enabled: true, }, diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index 69b5aaaebf..d3d3cbc95d 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -11,6 +11,7 @@ import { DuplicateController } from 'src/controllers/duplicate.controller'; import { FaceController } from 'src/controllers/face.controller'; import { JobController } from 'src/controllers/job.controller'; import { LibraryController } from 'src/controllers/library.controller'; +import { MaintenanceController } from 'src/controllers/maintenance.controller'; import { MapController } from 'src/controllers/map.controller'; import { MemoryController } from 'src/controllers/memory.controller'; import { NotificationAdminController } from 'src/controllers/notification-admin.controller'; @@ -47,6 +48,7 @@ export const controllers = [ FaceController, JobController, LibraryController, + MaintenanceController, MapController, MemoryController, NotificationController, @@ -70,4 +72,4 @@ export const controllers = [ ViewController, ]; -export const maintenanceControllers = [MaintenanceServerController, MaintenanceUserController, SystemConfigController]; +export const maintenanceControllers = [MaintenanceServerController, MaintenanceUserController, MaintenanceController]; diff --git a/server/src/controllers/maintenance.controller.ts b/server/src/controllers/maintenance.controller.ts new file mode 100644 index 0000000000..8fb4fa19ea --- /dev/null +++ b/server/src/controllers/maintenance.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Permission } from 'src/enum'; +import { Authenticated } from 'src/middleware/auth.guard'; +import { MaintenanceService } from 'src/services/maintenance.service'; + +@ApiTags('Maintenance (admin)') +@Controller('admin/maintenance') +export class MaintenanceController { + constructor(private service: MaintenanceService) {} + + @Get() + getMaintenanceMode() { + return this.service.getMaintenanceMode(); + } + + @Post('start') + @Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true }) + startMaintenance() { + return this.service.startMaintenance(); + } + + @Post('end') + @Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true }) + endMaintenance() { + return this.service.endMaintenance(); + } +} diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts new file mode 100644 index 0000000000..49b8e5fded --- /dev/null +++ b/server/src/dtos/maintenance.dto.ts @@ -0,0 +1,6 @@ +import { ValidateBoolean } from 'src/validation'; + +export class MaintenanceModeResponseDto { + @ValidateBoolean() + isMaintenanceMode!: boolean; +} diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 1c93fbf418..6d36e2cc8a 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -2,7 +2,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { ArrayMinSize, - IsBoolean, IsInt, IsNotEmpty, IsNumber, @@ -333,11 +332,6 @@ class SystemConfigMapDto { darkStyle!: string; } -export class SystemConfigMaintenanceDto { - @IsBoolean() - enabled!: boolean; -} - class SystemConfigNewVersionCheckDto { @ValidateBoolean() enabled!: boolean; @@ -664,11 +658,6 @@ export class SystemConfigDto implements SystemConfig { @IsObject() map!: SystemConfigMapDto; - @Type(() => SystemConfigMaintenanceDto) - @ValidateNested() - @IsObject() - maintenance!: SystemConfigMaintenanceDto; - @Type(() => SystemConfigNewVersionCheckDto) @ValidateNested() @IsObject() diff --git a/server/src/enum.ts b/server/src/enum.ts index 701a34a711..a7a8aa7b80 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -267,6 +267,7 @@ export enum SystemMetadataKey { FacialRecognitionState = 'facial-recognition-state', MemoriesState = 'memories-state', AdminOnboarding = 'admin-onboarding', + MaintenanceMode = 'maintenance-mode', SystemConfig = 'system-config', SystemFlags = 'system-flags', VersionCheckState = 'version-check-state', diff --git a/server/src/main.ts b/server/src/main.ts index ffc8417af1..5c8412106c 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -4,11 +4,9 @@ import { ChildProcess, fork } from 'node:child_process'; import { dirname, join } from 'node:path'; import { Worker } from 'node:worker_threads'; import { ApiModule, ImmichAdminModule } from 'src/app.module'; -import { ImmichWorker, LogLevel } from 'src/enum'; +import { ImmichWorker, LogLevel, SystemMetadataKey } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { getConfig } from 'src/utils/config'; /** * Manages worker lifecycle @@ -32,12 +30,10 @@ class Workers { * Boot all enabled workers */ async bootstrap() { - const { - maintenance: { enabled: maintenanceMode }, - } = await this.getConfig(); + const { isMaintenanceMode } = await this.getConfig(); const { workers } = new ConfigRepository().getEnv(); - if (maintenanceMode) { + if (isMaintenanceMode) { this.startWorker(ImmichWorker.Maintenance); } else { for (const worker of workers) { @@ -50,24 +46,13 @@ class Workers { * Initialise a short-lived Nest application to build configuration * @returns System configuration */ - private async getConfig() { + private async getConfig(): Promise<{ isMaintenanceMode?: boolean }> { const app = await NestFactory.create(ApiModule); - const logger = await app.resolve(LoggingRepository); - const configRepo = app.get(ConfigRepository); const metadataRepo = app.get(SystemMetadataRepository); - const systemConfig = await getConfig( - { - configRepo, - metadataRepo, - logger, - }, - { withCache: false }, - ); - await app.close(); - return systemConfig; + return metadataRepo.get(SystemMetadataKey.MaintenanceMode).then((value) => value ?? {}); } /** diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index c3e6cd20cf..e63be168c4 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -25,6 +25,7 @@ type EventMap = { // app events AppBootstrap: []; AppShutdown: []; + AppRestart: []; ConfigInit: [{ newConfig: SystemConfig }]; // config events diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts index 030659772d..9fde8526cc 100644 --- a/server/src/repositories/websocket.repository.ts +++ b/server/src/repositories/websocket.repository.ts @@ -16,7 +16,7 @@ import { ArgsOf, EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { handlePromiseError } from 'src/utils/misc'; -export const serverEvents = ['ConfigUpdate'] as const; +export const serverEvents = ['ConfigUpdate', 'AppRestart'] as const; export type ServerEvents = (typeof serverEvents)[number]; export interface ClientEventMap { diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 00c2749f2c..02e2274ac9 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { isAbsolute } from 'node:path'; import { SALT_ROUNDS } from 'src/constants'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; +import { SystemMetadataKey } from 'src/enum'; import { BaseService } from 'src/services/base.service'; @Injectable() @@ -39,15 +40,11 @@ export class CliService extends BaseService { } async disableMaintenanceMode(): Promise { - const config = await this.getConfig({ withCache: false }); - config.maintenance.enabled = false; - await this.updateConfig(config); + this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { isMaintenanceMode: false }); } async enableMaintenanceMode(): Promise { - const config = await this.getConfig({ withCache: false }); - config.maintenance.enabled = true; - await this.updateConfig(config); + this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { isMaintenanceMode: true }); } async grantAdminAccess(email: string): Promise { diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 9a8b0fb2bf..0798a57639 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -14,6 +14,7 @@ import { DownloadService } from 'src/services/download.service'; import { DuplicateService } from 'src/services/duplicate.service'; import { JobService } from 'src/services/job.service'; import { LibraryService } from 'src/services/library.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { MapService } from 'src/services/map.service'; import { MediaService } from 'src/services/media.service'; import { MemoryService } from 'src/services/memory.service'; @@ -60,6 +61,7 @@ export const services = [ DuplicateService, JobService, LibraryService, + MaintenanceService, MapService, MediaService, MemoryService, diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts new file mode 100644 index 0000000000..0c4204eae6 --- /dev/null +++ b/server/src/services/maintenance.service.ts @@ -0,0 +1,53 @@ +import { BadRequestException, INestApplication, Injectable } from '@nestjs/common'; +import { OnEvent } from 'src/decorators'; +import { MaintenanceModeResponseDto } from 'src/dtos/maintenance.dto'; +import { SystemMetadataKey } from 'src/enum'; +import { BaseService } from 'src/services/base.service'; + +@Injectable() +export class MaintenanceService extends BaseService { + nestApplication: INestApplication | undefined; + + async getMaintenanceMode(): Promise { + const value = await this.systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); + return { isMaintenanceMode: false, ...value }; + } + + async startMaintenance(): Promise { + const { isMaintenanceMode } = await this.getMaintenanceMode(); + if (isMaintenanceMode) { + throw new BadRequestException('Already in maintenance mode'); + } + + await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { + isMaintenanceMode: true, + }); + + this.websocketRepository.serverSend('AppRestart'); + this.eventRepository.emit('AppRestart'); + } + + async endMaintenance(): Promise { + const { isMaintenanceMode } = await this.getMaintenanceMode(); + if (!isMaintenanceMode) { + throw new BadRequestException('Not in maintenance mode'); + } + + await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { + isMaintenanceMode: false, + }); + + this.websocketRepository.serverSend('AppRestart'); + this.eventRepository.emit('AppRestart'); + } + + @OnEvent({ name: 'AppRestart', server: true }) + onRestart() { + this.nestApplication + ?.close() // attempt graceful shutdown + .then(() => process.exit(7)); // then signal restart + + // issue: close() seems to hang for API worker, so timeout after 1s + setTimeout(() => process.exit(7), 1000); + } +} diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index fa2809e9e0..edf229c988 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -118,6 +118,7 @@ export class ServerService extends BaseService { const config = await this.getConfig({ withCache: false }); const isInitialized = await this.userRepository.hasAdmin(); const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding); + const maintenanceMode = await this.systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); return { loginPageMessage: config.server.loginPageMessage, @@ -130,7 +131,7 @@ export class ServerService extends BaseService { publicUsers: config.server.publicUsers, mapDarkStyleUrl: config.map.darkStyle, mapLightStyleUrl: config.map.lightStyle, - maintenanceMode: config.maintenance.enabled, + maintenanceMode: maintenanceMode?.isMaintenanceMode || false, }; } diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index 0287ca6d87..50a3711a4c 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, type INestApplication, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; import { defaults } from 'src/config'; @@ -12,8 +12,6 @@ import { toPlainObject } from 'src/utils/object'; @Injectable() export class SystemConfigService extends BaseService { - nestApplication: INestApplication | undefined; - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.SystemConfig }) async onBootstrap() { const config = await this.getConfig({ withCache: false }); @@ -58,16 +56,6 @@ export class SystemConfigService extends BaseService { async onConfigUpdate({ newConfig, oldConfig }: ArgOf<'ConfigUpdate'>) { this.onConfigInit({ newConfig }); clearConfigCache(); - - // if maintenance is toggled, we need to restart - if (newConfig.maintenance.enabled !== oldConfig.maintenance.enabled) { - this.nestApplication - ?.close() // attempt graceful shutdown - .then(() => process.exit(7)); // then signal restart - - // issue: close() seems to hang for API worker, so timeout after 1s - setTimeout(() => process.exit(7), 1000); - } } @OnEvent({ name: 'ConfigValidate' }) diff --git a/server/src/workers/api.ts b/server/src/workers/api.ts index db0237ead4..01b8041635 100644 --- a/server/src/workers/api.ts +++ b/server/src/workers/api.ts @@ -12,7 +12,7 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; import { ApiService } from 'src/services/api.service'; -import { SystemConfigService } from 'src/services/system-config.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { isStartUpError, useSwagger } from 'src/utils/misc'; async function bootstrap() { process.title = 'immich-api'; @@ -25,7 +25,7 @@ async function bootstrap() { const app = await NestFactory.create(ApiModule, { bufferLogs: true }); const logger = await app.resolve(LoggingRepository); const configRepository = app.get(ConfigRepository); - app.get(SystemConfigService).nestApplication = app; + app.get(MaintenanceService).nestApplication = app; const { environment, host, port, resourcePaths } = configRepository.getEnv(); diff --git a/server/src/workers/maintenance.ts b/server/src/workers/maintenance.ts index 86d1b44da2..d1f7620cc1 100644 --- a/server/src/workers/maintenance.ts +++ b/server/src/workers/maintenance.ts @@ -12,7 +12,7 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; import { ApiService } from 'src/services/api.service'; -import { SystemConfigService } from 'src/services/system-config.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { isStartUpError, useSwagger } from 'src/utils/misc'; async function bootstrap() { process.title = 'immich-api'; @@ -25,7 +25,7 @@ async function bootstrap() { const app = await NestFactory.create(MaintenanceModule, { bufferLogs: true }); const logger = await app.resolve(LoggingRepository); const configRepository = app.get(ConfigRepository); - app.get(SystemConfigService).nestApplication = app; + app.get(MaintenanceService).nestApplication = app; const { environment, host, port, resourcePaths } = configRepository.getEnv(); diff --git a/server/src/workers/microservices.ts b/server/src/workers/microservices.ts index a78888715b..37401dbfa8 100644 --- a/server/src/workers/microservices.ts +++ b/server/src/workers/microservices.ts @@ -6,7 +6,7 @@ import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; -import { SystemConfigService } from 'src/services/system-config.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { isStartUpError } from 'src/utils/misc'; export async function bootstrap() { @@ -18,7 +18,7 @@ export async function bootstrap() { const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true }); const logger = await app.resolve(LoggingRepository); const configRepository = app.get(ConfigRepository); - app.get(SystemConfigService).nestApplication = app; + app.get(MaintenanceService).nestApplication = app; const { environment, host } = configRepository.getEnv(); diff --git a/web/src/lib/components/admin-settings/MaintenanceSettings.svelte b/web/src/lib/components/admin-settings/MaintenanceSettings.svelte index 480cc7b0ab..864309c8ac 100644 --- a/web/src/lib/components/admin-settings/MaintenanceSettings.svelte +++ b/web/src/lib/components/admin-settings/MaintenanceSettings.svelte @@ -1,25 +1,16 @@
-
-
- - - onReset({ ...options, configKeys: ['maintenance'] })} - onSave={() => onSave({ maintenance: config.maintenance })} - showResetToDefault={!isEqual(savedConfig.maintenance, defaultConfig.maintenance)} - {disabled} - /> -
-
+
+ +
diff --git a/web/src/routes/maintenance/+page.svelte b/web/src/routes/maintenance/+page.svelte index 1ec5164c37..e8ecdfd9c5 100644 --- a/web/src/routes/maintenance/+page.svelte +++ b/web/src/routes/maintenance/+page.svelte @@ -1,18 +1,11 @@