diff --git a/e2e/src/api/specs/server.e2e-spec.ts b/e2e/src/api/specs/server.e2e-spec.ts index 3133460ada..4bff4b3dea 100644 --- a/e2e/src/api/specs/server.e2e-spec.ts +++ b/e2e/src/api/specs/server.e2e-spec.ts @@ -163,11 +163,15 @@ describe('/server', () => { expect(body).toEqual({ photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, usageByUser: [ { quotaSizeInBytes: null, photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, userName: 'Immich Admin', userId: admin.userId, videos: 0, @@ -176,6 +180,8 @@ describe('/server', () => { quotaSizeInBytes: null, photos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, userName: 'User 1', userId: nonAdmin.userId, videos: 0, diff --git a/mobile/openapi/lib/model/server_stats_response_dto.dart b/mobile/openapi/lib/model/server_stats_response_dto.dart index 654a34ee6b..531fa8f03e 100644 Binary files a/mobile/openapi/lib/model/server_stats_response_dto.dart and b/mobile/openapi/lib/model/server_stats_response_dto.dart differ diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index e6f9216d74..80235915fe 100644 Binary files a/mobile/openapi/lib/model/usage_by_user_dto.dart and b/mobile/openapi/lib/model/usage_by_user_dto.dart differ diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 2d3f2fa6c2..5bddf2f3d2 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -10966,7 +10966,9 @@ { "photos": 1, "videos": 1, - "diskUsageRaw": 1 + "diskUsageRaw": 2, + "usagePhotos": 1, + "usageVideos": 1 } ], "items": { @@ -10975,6 +10977,16 @@ "title": "Array of usage for each user", "type": "array" }, + "usagePhotos": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "usageVideos": { + "default": 0, + "format": "int64", + "type": "integer" + }, "videos": { "default": 0, "type": "integer" @@ -10984,6 +10996,8 @@ "photos", "usage", "usageByUser", + "usagePhotos", + "usageVideos", "videos" ], "type": "object" @@ -12503,6 +12517,14 @@ "format": "int64", "type": "integer" }, + "usagePhotos": { + "format": "int64", + "type": "integer" + }, + "usageVideos": { + "format": "int64", + "type": "integer" + }, "userId": { "type": "string" }, @@ -12517,6 +12539,8 @@ "photos", "quotaSizeInBytes", "usage", + "usagePhotos", + "usageVideos", "userId", "userName", "videos" diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index c2906ff6e0..332814424d 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -969,6 +969,8 @@ export type UsageByUserDto = { photos: number; quotaSizeInBytes: number | null; usage: number; + usagePhotos: number; + usageVideos: number; userId: string; userName: string; videos: number; @@ -977,6 +979,8 @@ export type ServerStatsResponseDto = { photos: number; usage: number; usageByUser: UsageByUserDto[]; + usagePhotos: number; + usageVideos: number; videos: number; }; export type ServerStorageResponseDto = { diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index e540483351..cbabfa7aed 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -86,6 +86,10 @@ export class UsageByUserDto { @ApiProperty({ type: 'integer', format: 'int64' }) usage!: number; @ApiProperty({ type: 'integer', format: 'int64' }) + usagePhotos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) + usageVideos!: number; + @ApiProperty({ type: 'integer', format: 'int64' }) quotaSizeInBytes!: number | null; } @@ -99,6 +103,12 @@ export class ServerStatsResponseDto { @ApiProperty({ type: 'integer', format: 'int64' }) usage = 0; + @ApiProperty({ type: 'integer', format: 'int64' }) + usagePhotos = 0; + + @ApiProperty({ type: 'integer', format: 'int64' }) + usageVideos = 0; + @ApiProperty({ isArray: true, type: UsageByUserDto, @@ -107,7 +117,9 @@ export class ServerStatsResponseDto { { photos: 1, videos: 1, - diskUsageRaw: 1, + diskUsageRaw: 2, + usagePhotos: 1, + usageVideos: 1, }, ], }) diff --git a/server/src/interfaces/user.interface.ts b/server/src/interfaces/user.interface.ts index 3353d45dce..385a4d3d50 100644 --- a/server/src/interfaces/user.interface.ts +++ b/server/src/interfaces/user.interface.ts @@ -11,6 +11,8 @@ export interface UserStatsQueryResponse { photos: number; videos: number; usage: number; + usagePhotos: number; + usageVideos: number; quotaSizeInBytes: number | null; } diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index ab0a6cc534..c35dc540ce 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -140,7 +140,23 @@ SELECT "assets"."libraryId" IS NULL ), 0 - ) AS "usage" + ) AS "usage", + COALESCE( + SUM("exif"."fileSizeInByte") FILTER ( + WHERE + "assets"."libraryId" IS NULL + AND "assets"."type" = 'IMAGE' + ), + 0 + ) AS "usagePhotos", + COALESCE( + SUM("exif"."fileSizeInByte") FILTER ( + WHERE + "assets"."libraryId" IS NULL + AND "assets"."type" = 'VIDEO' + ), + 0 + ) AS "usageVideos" FROM "users" "users" LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id" diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 6ac8536ef8..a2e4375701 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -108,6 +108,14 @@ export class UserRepository implements IUserRepository { .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') .addSelect('COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL), 0)', 'usage') + .addSelect( + `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'IMAGE'), 0)`, + 'usagePhotos', + ) + .addSelect( + `COALESCE(SUM(exif.fileSizeInByte) FILTER (WHERE assets.libraryId IS NULL AND assets.type = 'VIDEO'), 0)`, + 'usageVideos', + ) .addSelect('users.quotaSizeInBytes', 'quotaSizeInBytes') .leftJoin('users.assets', 'assets') .leftJoin('assets.exifInfo', 'exif') @@ -119,6 +127,8 @@ export class UserRepository implements IUserRepository { stat.photos = Number(stat.photos); stat.videos = Number(stat.videos); stat.usage = Number(stat.usage); + stat.usagePhotos = Number(stat.usagePhotos); + stat.usageVideos = Number(stat.usageVideos); stat.quotaSizeInBytes = stat.quotaSizeInBytes; } diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index ab6eb3b1a4..475d1d6193 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -185,6 +185,8 @@ describe(ServerService.name, () => { photos: 10, videos: 11, usage: 12_345, + usagePhotos: 1, + usageVideos: 11_345, quotaSizeInBytes: 0, }, { @@ -193,6 +195,8 @@ describe(ServerService.name, () => { photos: 10, videos: 20, usage: 123_456, + usagePhotos: 100, + usageVideos: 23_456, quotaSizeInBytes: 0, }, { @@ -201,6 +205,8 @@ describe(ServerService.name, () => { photos: 100, videos: 0, usage: 987_654, + usagePhotos: 900, + usageVideos: 87_654, quotaSizeInBytes: 0, }, ]); @@ -209,11 +215,15 @@ describe(ServerService.name, () => { photos: 120, videos: 31, usage: 1_123_455, + usagePhotos: 1001, + usageVideos: 122_455, usageByUser: [ { photos: 10, quotaSizeInBytes: 0, usage: 12_345, + usagePhotos: 1, + usageVideos: 11_345, userName: '1 User', userId: 'user1', videos: 11, @@ -222,6 +232,8 @@ describe(ServerService.name, () => { photos: 10, quotaSizeInBytes: 0, usage: 123_456, + usagePhotos: 100, + usageVideos: 23_456, userName: '2 User', userId: 'user2', videos: 20, @@ -230,6 +242,8 @@ describe(ServerService.name, () => { photos: 100, quotaSizeInBytes: 0, usage: 987_654, + usagePhotos: 900, + usageVideos: 87_654, userName: '3 User', userId: 'user3', videos: 0, diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index 3fc319a2fd..7df322a84e 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -126,11 +126,16 @@ export class ServerService extends BaseService { usage.photos = user.photos; usage.videos = user.videos; usage.usage = user.usage; + usage.usagePhotos = user.usagePhotos; + usage.usageVideos = user.usageVideos; usage.quotaSizeInBytes = user.quotaSizeInBytes; serverStats.photos += usage.photos; serverStats.videos += usage.videos; serverStats.usage += usage.usage; + serverStats.usagePhotos += usage.usagePhotos; + serverStats.usageVideos += usage.usageVideos; + serverStats.usageByUser.push(usage); } diff --git a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte index feab6a9c6d..bb288511ac 100644 --- a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte +++ b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte @@ -16,6 +16,8 @@ photos: 0, videos: 0, usage: 0, + usagePhotos: 0, + usageVideos: 0, usageByUser: [], }, }: Props = $props(); @@ -105,8 +107,12 @@ class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50" >