diff --git a/mobile/openapi/doc/ServerStatsResponseDto.md b/mobile/openapi/doc/ServerStatsResponseDto.md index 3356c366f2..96446e1c28 100644 --- a/mobile/openapi/doc/ServerStatsResponseDto.md +++ b/mobile/openapi/doc/ServerStatsResponseDto.md @@ -8,11 +8,9 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**photos** | **int** | | -**videos** | **int** | | -**objects** | **int** | | -**usageRaw** | **int** | | -**usage** | **String** | | +**photos** | **int** | | [default to 0] +**videos** | **int** | | [default to 0] +**usage** | **int** | | [default to 0] **usageByUser** | [**List**](UsageByUserDto.md) | | [default to const []] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/UsageByUserDto.md b/mobile/openapi/doc/UsageByUserDto.md index ffdc2a88ec..1d1bef8858 100644 --- a/mobile/openapi/doc/UsageByUserDto.md +++ b/mobile/openapi/doc/UsageByUserDto.md @@ -9,10 +9,11 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **userId** | **String** | | -**videos** | **int** | | +**userFirstName** | **String** | | +**userLastName** | **String** | | **photos** | **int** | | -**usageRaw** | **int** | | -**usage** | **String** | | +**videos** | **int** | | +**usage** | **int** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/lib/model/server_stats_response_dto.dart b/mobile/openapi/lib/model/server_stats_response_dto.dart index 7dbc1db42a..aeb40c0c7f 100644 --- a/mobile/openapi/lib/model/server_stats_response_dto.dart +++ b/mobile/openapi/lib/model/server_stats_response_dto.dart @@ -13,11 +13,9 @@ part of openapi.api; class ServerStatsResponseDto { /// Returns a new [ServerStatsResponseDto] instance. ServerStatsResponseDto({ - required this.photos, - required this.videos, - required this.objects, - required this.usageRaw, - required this.usage, + this.photos = 0, + this.videos = 0, + this.usage = 0, this.usageByUser = const [], }); @@ -25,11 +23,7 @@ class ServerStatsResponseDto { int videos; - int objects; - - int usageRaw; - - String usage; + int usage; List usageByUser; @@ -37,8 +31,6 @@ class ServerStatsResponseDto { bool operator ==(Object other) => identical(this, other) || other is ServerStatsResponseDto && other.photos == photos && other.videos == videos && - other.objects == objects && - other.usageRaw == usageRaw && other.usage == usage && other.usageByUser == usageByUser; @@ -47,20 +39,16 @@ class ServerStatsResponseDto { // ignore: unnecessary_parenthesis (photos.hashCode) + (videos.hashCode) + - (objects.hashCode) + - (usageRaw.hashCode) + (usage.hashCode) + (usageByUser.hashCode); @override - String toString() => 'ServerStatsResponseDto[photos=$photos, videos=$videos, objects=$objects, usageRaw=$usageRaw, usage=$usage, usageByUser=$usageByUser]'; + String toString() => 'ServerStatsResponseDto[photos=$photos, videos=$videos, usage=$usage, usageByUser=$usageByUser]'; Map toJson() { final json = {}; json[r'photos'] = this.photos; json[r'videos'] = this.videos; - json[r'objects'] = this.objects; - json[r'usageRaw'] = this.usageRaw; json[r'usage'] = this.usage; json[r'usageByUser'] = this.usageByUser; return json; @@ -87,9 +75,7 @@ class ServerStatsResponseDto { return ServerStatsResponseDto( photos: mapValueOfType(json, r'photos')!, videos: mapValueOfType(json, r'videos')!, - objects: mapValueOfType(json, r'objects')!, - usageRaw: mapValueOfType(json, r'usageRaw')!, - usage: mapValueOfType(json, r'usage')!, + usage: mapValueOfType(json, r'usage')!, usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser'])!, ); } @@ -142,8 +128,6 @@ class ServerStatsResponseDto { static const requiredKeys = { 'photos', 'videos', - 'objects', - 'usageRaw', 'usage', 'usageByUser', }; diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index e18ac81de8..d2cbc4f41b 100644 --- a/mobile/openapi/lib/model/usage_by_user_dto.dart +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -14,48 +14,54 @@ class UsageByUserDto { /// Returns a new [UsageByUserDto] instance. UsageByUserDto({ required this.userId, - required this.videos, + required this.userFirstName, + required this.userLastName, required this.photos, - required this.usageRaw, + required this.videos, required this.usage, }); String userId; - int videos; + String userFirstName; + + String userLastName; int photos; - int usageRaw; + int videos; - String usage; + int usage; @override bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto && other.userId == userId && - other.videos == videos && + other.userFirstName == userFirstName && + other.userLastName == userLastName && other.photos == photos && - other.usageRaw == usageRaw && + other.videos == videos && other.usage == usage; @override int get hashCode => // ignore: unnecessary_parenthesis (userId.hashCode) + - (videos.hashCode) + + (userFirstName.hashCode) + + (userLastName.hashCode) + (photos.hashCode) + - (usageRaw.hashCode) + + (videos.hashCode) + (usage.hashCode); @override - String toString() => 'UsageByUserDto[userId=$userId, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]'; + String toString() => 'UsageByUserDto[userId=$userId, userFirstName=$userFirstName, userLastName=$userLastName, photos=$photos, videos=$videos, usage=$usage]'; Map toJson() { final json = {}; json[r'userId'] = this.userId; - json[r'videos'] = this.videos; + json[r'userFirstName'] = this.userFirstName; + json[r'userLastName'] = this.userLastName; json[r'photos'] = this.photos; - json[r'usageRaw'] = this.usageRaw; + json[r'videos'] = this.videos; json[r'usage'] = this.usage; return json; } @@ -80,10 +86,11 @@ class UsageByUserDto { return UsageByUserDto( userId: mapValueOfType(json, r'userId')!, - videos: mapValueOfType(json, r'videos')!, + userFirstName: mapValueOfType(json, r'userFirstName')!, + userLastName: mapValueOfType(json, r'userLastName')!, photos: mapValueOfType(json, r'photos')!, - usageRaw: mapValueOfType(json, r'usageRaw')!, - usage: mapValueOfType(json, r'usage')!, + videos: mapValueOfType(json, r'videos')!, + usage: mapValueOfType(json, r'usage')!, ); } return null; @@ -134,9 +141,10 @@ class UsageByUserDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'userId', - 'videos', + 'userFirstName', + 'userLastName', 'photos', - 'usageRaw', + 'videos', 'usage', }; } diff --git a/mobile/openapi/test/server_stats_response_dto_test.dart b/mobile/openapi/test/server_stats_response_dto_test.dart index 6e9fb783ad..f5d2c3dc9b 100644 --- a/mobile/openapi/test/server_stats_response_dto_test.dart +++ b/mobile/openapi/test/server_stats_response_dto_test.dart @@ -16,27 +16,17 @@ void main() { // final instance = ServerStatsResponseDto(); group('test ServerStatsResponseDto', () { - // int photos + // int photos (default value: 0) test('to test the property `photos`', () async { // TODO }); - // int videos + // int videos (default value: 0) test('to test the property `videos`', () async { // TODO }); - // int objects - test('to test the property `objects`', () async { - // TODO - }); - - // int usageRaw - test('to test the property `usageRaw`', () async { - // TODO - }); - - // String usage + // int usage (default value: 0) test('to test the property `usage`', () async { // TODO }); diff --git a/mobile/openapi/test/usage_by_user_dto_test.dart b/mobile/openapi/test/usage_by_user_dto_test.dart index 68efc27332..a4bec3f71d 100644 --- a/mobile/openapi/test/usage_by_user_dto_test.dart +++ b/mobile/openapi/test/usage_by_user_dto_test.dart @@ -21,8 +21,13 @@ void main() { // TODO }); - // int videos - test('to test the property `videos`', () async { + // String userFirstName + test('to test the property `userFirstName`', () async { + // TODO + }); + + // String userLastName + test('to test the property `userLastName`', () async { // TODO }); @@ -31,12 +36,12 @@ void main() { // TODO }); - // int usageRaw - test('to test the property `usageRaw`', () async { + // int videos + test('to test the property `videos`', () async { // TODO }); - // String usage + // int usage test('to test the property `usage`', () async { // TODO }); diff --git a/server/apps/immich/src/api-v1/album/album.service.spec.ts b/server/apps/immich/src/api-v1/album/album.service.spec.ts index acb74ab44c..66c7d51108 100644 --- a/server/apps/immich/src/api-v1/album/album.service.spec.ts +++ b/server/apps/immich/src/api-v1/album/album.service.spec.ts @@ -37,6 +37,7 @@ describe('Album service', () => { shouldChangePassword: false, oauthId: '', tags: [], + assets: [], }); const albumId = 'f19ab956-4761-41ea-a5d6-bae948308d58'; const sharedAlbumOwnerId = '2222'; diff --git a/server/apps/immich/src/api-v1/server-info/response-dto/server-stats-response.dto.ts b/server/apps/immich/src/api-v1/server-info/response-dto/server-stats-response.dto.ts index 615acfcf05..ed7a071769 100644 --- a/server/apps/immich/src/api-v1/server-info/response-dto/server-stats-response.dto.ts +++ b/server/apps/immich/src/api-v1/server-info/response-dto/server-stats-response.dto.ts @@ -2,28 +2,14 @@ import { ApiProperty } from '@nestjs/swagger'; import { UsageByUserDto } from './usage-by-user-response.dto'; export class ServerStatsResponseDto { - constructor() { - this.photos = 0; - this.videos = 0; - this.usageByUser = []; - this.usageRaw = 0; - this.usage = ''; - } + @ApiProperty({ type: 'integer' }) + photos = 0; @ApiProperty({ type: 'integer' }) - photos!: number; - - @ApiProperty({ type: 'integer' }) - videos!: number; - - @ApiProperty({ type: 'integer' }) - objects!: number; + videos = 0; @ApiProperty({ type: 'integer', format: 'int64' }) - usageRaw!: number; - - @ApiProperty({ type: 'string' }) - usage!: string; + usage = 0; @ApiProperty({ isArray: true, @@ -37,5 +23,5 @@ export class ServerStatsResponseDto { }, ], }) - usageByUser!: UsageByUserDto[]; + usageByUser: UsageByUserDto[] = []; } diff --git a/server/apps/immich/src/api-v1/server-info/response-dto/usage-by-user-response.dto.ts b/server/apps/immich/src/api-v1/server-info/response-dto/usage-by-user-response.dto.ts index 7502d63afd..ac3a829077 100644 --- a/server/apps/immich/src/api-v1/server-info/response-dto/usage-by-user-response.dto.ts +++ b/server/apps/immich/src/api-v1/server-info/response-dto/usage-by-user-response.dto.ts @@ -1,22 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; export class UsageByUserDto { - constructor(userId: string) { - this.userId = userId; - this.videos = 0; - this.photos = 0; - this.usageRaw = 0; - this.usage = '0B'; - } - @ApiProperty({ type: 'string' }) - userId: string; + userId!: string; + @ApiProperty({ type: 'string' }) + userFirstName!: string; + @ApiProperty({ type: 'string' }) + userLastName!: string; @ApiProperty({ type: 'integer' }) - videos: number; + photos!: number; @ApiProperty({ type: 'integer' }) - photos: number; + videos!: number; @ApiProperty({ type: 'integer', format: 'int64' }) - usageRaw!: number; - @ApiProperty({ type: 'string' }) - usage!: string; + usage!: number; } diff --git a/server/apps/immich/src/api-v1/server-info/server-info.module.ts b/server/apps/immich/src/api-v1/server-info/server-info.module.ts index 1b5154695e..25d8a19e22 100644 --- a/server/apps/immich/src/api-v1/server-info/server-info.module.ts +++ b/server/apps/immich/src/api-v1/server-info/server-info.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { ServerInfoService } from './server-info.service'; import { ServerInfoController } from './server-info.controller'; -import { AssetEntity } from '@app/infra'; +import { UserEntity } from '@app/infra'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - imports: [TypeOrmModule.forFeature([AssetEntity])], + imports: [TypeOrmModule.forFeature([UserEntity])], controllers: [ServerInfoController], providers: [ServerInfoService], }) diff --git a/server/apps/immich/src/api-v1/server-info/server-info.service.ts b/server/apps/immich/src/api-v1/server-info/server-info.service.ts index 243d880dfe..779d4163e6 100644 --- a/server/apps/immich/src/api-v1/server-info/server-info.service.ts +++ b/server/apps/immich/src/api-v1/server-info/server-info.service.ts @@ -4,7 +4,7 @@ import { ServerInfoResponseDto } from './response-dto/server-info-response.dto'; import diskusage from 'diskusage'; import { ServerStatsResponseDto } from './response-dto/server-stats-response.dto'; import { UsageByUserDto } from './response-dto/usage-by-user-response.dto'; -import { AssetEntity } from '@app/infra'; +import { UserEntity } from '@app/infra'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { asHumanReadable } from '../../utils/human-readable.util'; @@ -12,8 +12,8 @@ import { asHumanReadable } from '../../utils/human-readable.util'; @Injectable() export class ServerInfoService { constructor( - @InjectRepository(AssetEntity) - private assetRepository: Repository, + @InjectRepository(UserEntity) + private userRepository: Repository, ) {} async getServerInfo(): Promise { @@ -33,44 +33,48 @@ export class ServerInfoService { } async getStats(): Promise { - const serverStats = new ServerStatsResponseDto(); - type UserStatsQueryResponse = { - assetType: string; - assetCount: string; - totalSizeInBytes: string; - ownerId: string; + userId: string; + userFirstName: string; + userLastName: string; + photos: string; + videos: string; + usage: string; }; - const userStatsQueryResponse: UserStatsQueryResponse[] = await this.assetRepository - .createQueryBuilder('a') - .select('COUNT(a.id)', 'assetCount') - .addSelect('SUM(ei.fileSizeInByte)', 'totalSizeInBytes') - .addSelect('a."ownerId"') - .addSelect('a.type', 'assetType') - .where('a.isVisible = true') - .leftJoin('a.exifInfo', 'ei') - .groupBy('a."ownerId"') - .addGroupBy('a.type') + const userStatsQueryResponse: UserStatsQueryResponse[] = await this.userRepository + .createQueryBuilder('users') + .select('users.id', 'userId') + .addSelect('users.firstName', 'userFirstName') + .addSelect('users.lastName', 'userLastName') + .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), 0)', 'usage') + .leftJoin('users.assets', 'assets') + .leftJoin('assets.exifInfo', 'exif') + .groupBy('users.id') + .orderBy('users.createdAt', 'ASC') .getRawMany(); - const tmpMap = new Map(); - const getUsageByUser = (id: string) => tmpMap.get(id) || new UsageByUserDto(id); - userStatsQueryResponse.forEach((r) => { - const usageByUser = getUsageByUser(r.ownerId); - usageByUser.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0; - usageByUser.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0; - usageByUser.usageRaw += parseInt(r.totalSizeInBytes); - usageByUser.usage = asHumanReadable(usageByUser.usageRaw); + const usageByUser = userStatsQueryResponse.map((userStats) => { + const usage = new UsageByUserDto(); + usage.userId = userStats.userId; + usage.userFirstName = userStats.userFirstName; + usage.userLastName = userStats.userLastName; + usage.photos = Number(userStats.photos); + usage.videos = Number(userStats.videos); + usage.usage = Number(userStats.usage); - serverStats.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0; - serverStats.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0; - serverStats.usageRaw += parseInt(r.totalSizeInBytes); - serverStats.usage = asHumanReadable(serverStats.usageRaw); - tmpMap.set(r.ownerId, usageByUser); + return usage; }); - serverStats.usageByUser = Array.from(tmpMap.values()); + const serverStats = new ServerStatsResponseDto(); + usageByUser.forEach((user) => { + serverStats.photos += user.photos; + serverStats.videos += user.videos; + serverStats.usage += user.usage; + }); + serverStats.usageByUser = usageByUser; return serverStats; } diff --git a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts index 395a375738..877f60087d 100644 --- a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts +++ b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts @@ -25,6 +25,7 @@ describe('TagService', () => { deletedAt: undefined, updatedAt: '2022-12-02T19:29:23.603Z', tags: [], + assets: [], oauthId: 'oauth-id-1', }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index a89e62a3ac..9661eba018 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -4883,25 +4883,29 @@ "userId": { "type": "string" }, - "videos": { - "type": "integer" + "userFirstName": { + "type": "string" + }, + "userLastName": { + "type": "string" }, "photos": { "type": "integer" }, - "usageRaw": { - "type": "integer", - "format": "int64" + "videos": { + "type": "integer" }, "usage": { - "type": "string" + "type": "integer", + "format": "int64" } }, "required": [ "userId", - "videos", + "userFirstName", + "userLastName", "photos", - "usageRaw", + "videos", "usage" ] }, @@ -4909,22 +4913,20 @@ "type": "object", "properties": { "photos": { - "type": "integer" + "type": "integer", + "default": 0 }, "videos": { - "type": "integer" - }, - "objects": { - "type": "integer" - }, - "usageRaw": { "type": "integer", - "format": "int64" + "default": 0 }, "usage": { - "type": "string" + "type": "integer", + "default": 0, + "format": "int64" }, "usageByUser": { + "default": [], "title": "Array of usage for each user", "example": [ { @@ -4942,8 +4944,6 @@ "required": [ "photos", "videos", - "objects", - "usageRaw", "usage", "usageByUser" ] diff --git a/server/libs/domain/src/user/user.service.spec.ts b/server/libs/domain/src/user/user.service.spec.ts index d0ab69fbf7..bcc327b444 100644 --- a/server/libs/domain/src/user/user.service.spec.ts +++ b/server/libs/domain/src/user/user.service.spec.ts @@ -54,6 +54,7 @@ const adminUser: UserEntity = Object.freeze({ createdAt: '2021-01-01', updatedAt: '2021-01-01', tags: [], + assets: [], }); const immichUser: UserEntity = Object.freeze({ @@ -69,6 +70,7 @@ const immichUser: UserEntity = Object.freeze({ createdAt: '2021-01-01', updatedAt: '2021-01-01', tags: [], + assets: [], }); const updatedImmichUser: UserEntity = Object.freeze({ @@ -84,6 +86,7 @@ const updatedImmichUser: UserEntity = Object.freeze({ createdAt: '2021-01-01', updatedAt: '2021-01-01', tags: [], + assets: [], }); const adminUserResponse = Object.freeze({ diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index dc972337e4..73f822a7ff 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -76,6 +76,7 @@ export const userEntityStub = { createdAt: '2021-01-01', updatedAt: '2021-01-01', tags: [], + assets: [], }), user1: Object.freeze({ ...authStub.user1, @@ -88,6 +89,7 @@ export const userEntityStub = { createdAt: '2021-01-01', updatedAt: '2021-01-01', tags: [], + assets: [], }), }; diff --git a/server/libs/infra/src/db/entities/user.entity.ts b/server/libs/infra/src/db/entities/user.entity.ts index d8724aab9a..5fca9a89d0 100644 --- a/server/libs/infra/src/db/entities/user.entity.ts +++ b/server/libs/infra/src/db/entities/user.entity.ts @@ -7,6 +7,7 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { AssetEntity } from './asset.entity'; import { TagEntity } from './tag.entity'; @Entity('users') @@ -49,4 +50,7 @@ export class UserEntity { @OneToMany(() => TagEntity, (tag) => tag.user) tags!: TagEntity[]; + + @OneToMany(() => AssetEntity, (asset) => asset.owner) + assets!: AssetEntity[]; } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 45571fd946..1f8464367a 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1549,19 +1549,7 @@ export interface ServerStatsResponseDto { * @type {number} * @memberof ServerStatsResponseDto */ - 'objects': number; - /** - * - * @type {number} - * @memberof ServerStatsResponseDto - */ - 'usageRaw': number; - /** - * - * @type {string} - * @memberof ServerStatsResponseDto - */ - 'usage': string; + 'usage': number; /** * * @type {Array} @@ -2184,10 +2172,16 @@ export interface UsageByUserDto { 'userId': string; /** * - * @type {number} + * @type {string} * @memberof UsageByUserDto */ - 'videos': number; + 'userFirstName': string; + /** + * + * @type {string} + * @memberof UsageByUserDto + */ + 'userLastName': string; /** * * @type {number} @@ -2199,13 +2193,13 @@ export interface UsageByUserDto { * @type {number} * @memberof UsageByUserDto */ - 'usageRaw': number; + 'videos': number; /** * - * @type {string} + * @type {number} * @memberof UsageByUserDto */ - 'usage': string; + 'usage': number; } /** * 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 b20d897870..2243488c0d 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 @@ -1,43 +1,20 @@
@@ -45,14 +22,9 @@

TOTAL USAGE

- - - + + +
@@ -72,32 +44,19 @@ - {#if stats} - {#each stats.usageByUser as user, i} - - {getFullName(user.userId)} - {user.photos.toLocaleString($locale)} - {user.videos.toLocaleString($locale)} - {asByteUnitString(user.usageRaw)} - - {/each} - {:else} + {#each stats.usageByUser as user (user.userId)} - - - + {user.userFirstName} {user.userLastName} + {user.photos.toLocaleString($locale)} + {user.videos.toLocaleString($locale)} + {asByteUnitString(user.usage, $locale)} - {/if} + {/each} diff --git a/web/src/lib/components/admin-page/server-stats/stats-card.svelte b/web/src/lib/components/admin-page/server-stats/stats-card.svelte index a3dcdab4b6..4a704a787d 100644 --- a/web/src/lib/components/admin-page/server-stats/stats-card.svelte +++ b/web/src/lib/components/admin-page/server-stats/stats-card.svelte @@ -1,19 +1,14 @@ -{#if $page.data.allUsers} - -{/if} +