feat: add external maintenance mode status

This commit is contained in:
izzy
2025-11-19 15:54:44 +00:00
parent af741a4761
commit 442fe6e3d0
12 changed files with 430 additions and 7 deletions

View File

@@ -1,9 +1,14 @@
import { BadRequestException, Body, Controller, Post, Res } from '@nestjs/common';
import { BadRequestException, Body, Controller, Get, Post, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { MaintenanceAuthDto, MaintenanceLoginDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
import {
MaintenanceAuthDto,
MaintenanceLoginDto,
MaintenanceStatusResponseDto,
SetMaintenanceModeDto,
} from 'src/dtos/maintenance.dto';
import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { LoginDetails } from 'src/services/auth.service';
@@ -15,6 +20,16 @@ import { respondWithCookie } from 'src/utils/response';
export class MaintenanceController {
constructor(private service: MaintenanceService) {}
@Get('admin/maintenance/status')
@Endpoint({
summary: 'Get maintenance mode status',
description: 'Fetch information about the currently running maintenance action.',
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
})
maintenanceStatus(): MaintenanceStatusResponseDto {
return {};
}
@Post('login')
@Endpoint({
summary: 'Log into maintenance mode',

View File

@@ -17,3 +17,11 @@ export class MaintenanceLoginDto {
export class MaintenanceAuthDto {
username!: string;
}
export class MaintenanceStatusResponseDto {
action?: MaintenanceAction;
progress?: number;
task?: string;
error?: string;
}

View File

@@ -1,14 +1,34 @@
import { Injectable } from '@nestjs/common';
import { MaintenanceStatusResponseDto } from 'src/dtos/maintenance.dto';
@Injectable()
export class MaintenanceEphemeralStateRepository {
#secret: string = null!;
#state: MaintenanceStatusResponseDto = {};
setSecret(secret: string) {
this.#secret = secret;
}
getSecret() {
getSecret(): string {
return this.#secret;
}
setState(state: MaintenanceStatusResponseDto) {
this.#state = state;
}
getState(): MaintenanceStatusResponseDto {
return this.#state;
}
getPublicState(): MaintenanceStatusResponseDto {
const state = structuredClone(this.#state);
if (state.error) {
state.error = 'Something went wrong, see logs!';
}
return state;
}
}

View File

@@ -1,6 +1,11 @@
import { Body, Controller, Get, Post, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { MaintenanceAuthDto, MaintenanceLoginDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
import {
MaintenanceAuthDto,
MaintenanceLoginDto,
MaintenanceStatusResponseDto,
SetMaintenanceModeDto,
} from 'src/dtos/maintenance.dto';
import { ServerConfigDto } from 'src/dtos/server.dto';
import { ImmichCookie, MaintenanceAction } from 'src/enum';
import { MaintenanceRoute } from 'src/maintenance/maintenance-auth.guard';
@@ -18,6 +23,11 @@ export class MaintenanceWorkerController {
return this.service.getSystemConfig();
}
@Get('admin/maintenance/status')
maintenanceStatus(@Req() request: Request): Promise<MaintenanceStatusResponseDto> {
return this.service.status(request.cookies[ImmichCookie.MaintenanceToken]);
}
@Post('admin/maintenance/login')
async maintenanceLogin(
@Req() request: Request,

View File

@@ -4,7 +4,7 @@ import { NextFunction, Request, Response } from 'express';
import { jwtVerify } from 'jose';
import { readFileSync } from 'node:fs';
import { IncomingHttpHeaders } from 'node:http';
import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto';
import { MaintenanceAuthDto, MaintenanceStatusResponseDto } from 'src/dtos/maintenance.dto';
import { ServerConfigDto } from 'src/dtos/server.dto';
import { ImmichCookie, SystemMetadataKey } from 'src/enum';
import { MaintenanceEphemeralStateRepository } from 'src/maintenance/maintenance-ephemeral-state.repository';
@@ -150,6 +150,15 @@ export class MaintenanceWorkerService {
return this.login(jwtToken);
}
async status(potentiallyJwt?: string): Promise<MaintenanceStatusResponseDto> {
try {
await this.login(potentiallyJwt);
return this.maintenanceEphemeralStateRepository.getState();
} catch {
return this.maintenanceEphemeralStateRepository.getPublicState();
}
}
async login(jwt?: string): Promise<MaintenanceAuthDto> {
if (!jwt) {
throw new UnauthorizedException('Missing JWT Token');