mirror of
https://github.com/immich-app/immich.git
synced 2025-05-30 19:55:43 -04:00
fix(server): Server freezes when getting statistic (#994)
* fix(server): Server freezes when getting statistic * remove dead code
This commit is contained in:
parent
b3e51cc849
commit
41ffa0c015
@ -9,7 +9,6 @@ import 'package:openapi/api.dart';
|
|||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**userId** | **String** | |
|
**userId** | **String** | |
|
||||||
**objects** | **int** | |
|
|
||||||
**videos** | **int** | |
|
**videos** | **int** | |
|
||||||
**photos** | **int** | |
|
**photos** | **int** | |
|
||||||
**usageRaw** | **int** | |
|
**usageRaw** | **int** | |
|
||||||
|
@ -43,51 +43,48 @@ class AlbumResponseDto {
|
|||||||
List<AssetResponseDto> assets;
|
List<AssetResponseDto> assets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
|
||||||
identical(this, other) ||
|
other.assetCount == assetCount &&
|
||||||
other is AlbumResponseDto &&
|
other.id == id &&
|
||||||
other.assetCount == assetCount &&
|
other.ownerId == ownerId &&
|
||||||
other.id == id &&
|
other.albumName == albumName &&
|
||||||
other.ownerId == ownerId &&
|
other.createdAt == createdAt &&
|
||||||
other.albumName == albumName &&
|
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
||||||
other.createdAt == createdAt &&
|
other.shared == shared &&
|
||||||
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
other.sharedUsers == sharedUsers &&
|
||||||
other.shared == shared &&
|
other.assets == assets;
|
||||||
other.sharedUsers == sharedUsers &&
|
|
||||||
other.assets == assets;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(assetCount.hashCode) +
|
(assetCount.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(albumName.hashCode) +
|
(albumName.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
||||||
(shared.hashCode) +
|
(shared.hashCode) +
|
||||||
(sharedUsers.hashCode) +
|
(sharedUsers.hashCode) +
|
||||||
(assets.hashCode);
|
(assets.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
||||||
'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'assetCount'] = assetCount;
|
_json[r'assetCount'] = assetCount;
|
||||||
_json[r'id'] = id;
|
_json[r'id'] = id;
|
||||||
_json[r'ownerId'] = ownerId;
|
_json[r'ownerId'] = ownerId;
|
||||||
_json[r'albumName'] = albumName;
|
_json[r'albumName'] = albumName;
|
||||||
_json[r'createdAt'] = createdAt;
|
_json[r'createdAt'] = createdAt;
|
||||||
if (albumThumbnailAssetId != null) {
|
if (albumThumbnailAssetId != null) {
|
||||||
_json[r'albumThumbnailAssetId'] = albumThumbnailAssetId;
|
_json[r'albumThumbnailAssetId'] = albumThumbnailAssetId;
|
||||||
} else {
|
} else {
|
||||||
_json[r'albumThumbnailAssetId'] = null;
|
_json[r'albumThumbnailAssetId'] = null;
|
||||||
}
|
}
|
||||||
_json[r'shared'] = shared;
|
_json[r'shared'] = shared;
|
||||||
_json[r'sharedUsers'] = sharedUsers;
|
_json[r'sharedUsers'] = sharedUsers;
|
||||||
_json[r'assets'] = assets;
|
_json[r'assets'] = assets;
|
||||||
return _json;
|
return _json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +98,13 @@ class AlbumResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
|
assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
|
||||||
// assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
|
assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return AlbumResponseDto(
|
return AlbumResponseDto(
|
||||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
@ -115,8 +112,7 @@ class AlbumResponseDto {
|
|||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
albumName: mapValueOfType<String>(json, r'albumName')!,
|
albumName: mapValueOfType<String>(json, r'albumName')!,
|
||||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||||
albumThumbnailAssetId:
|
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||||
mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
|
||||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||||
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
||||||
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
||||||
@ -125,10 +121,7 @@ class AlbumResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AlbumResponseDto>? listFromJson(
|
static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <AlbumResponseDto>[];
|
final result = <AlbumResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@ -156,18 +149,12 @@ class AlbumResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of AlbumResponseDto-objects as value to a dart map
|
// maps a json object with a list of AlbumResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AlbumResponseDto>> mapListFromJson(
|
static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<AlbumResponseDto>>{};
|
final map = <String, List<AlbumResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AlbumResponseDto.listFromJson(
|
final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@ -189,3 +176,4 @@ class AlbumResponseDto {
|
|||||||
'assets',
|
'assets',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,74 +79,71 @@ class AssetResponseDto {
|
|||||||
String? livePhotoVideoId;
|
String? livePhotoVideoId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||||
identical(this, other) ||
|
other.type == type &&
|
||||||
other is AssetResponseDto &&
|
other.id == id &&
|
||||||
other.type == type &&
|
other.deviceAssetId == deviceAssetId &&
|
||||||
other.id == id &&
|
other.ownerId == ownerId &&
|
||||||
other.deviceAssetId == deviceAssetId &&
|
other.deviceId == deviceId &&
|
||||||
other.ownerId == ownerId &&
|
other.originalPath == originalPath &&
|
||||||
other.deviceId == deviceId &&
|
other.resizePath == resizePath &&
|
||||||
other.originalPath == originalPath &&
|
other.createdAt == createdAt &&
|
||||||
other.resizePath == resizePath &&
|
other.modifiedAt == modifiedAt &&
|
||||||
other.createdAt == createdAt &&
|
other.isFavorite == isFavorite &&
|
||||||
other.modifiedAt == modifiedAt &&
|
other.mimeType == mimeType &&
|
||||||
other.isFavorite == isFavorite &&
|
other.duration == duration &&
|
||||||
other.mimeType == mimeType &&
|
other.webpPath == webpPath &&
|
||||||
other.duration == duration &&
|
other.encodedVideoPath == encodedVideoPath &&
|
||||||
other.webpPath == webpPath &&
|
other.exifInfo == exifInfo &&
|
||||||
other.encodedVideoPath == encodedVideoPath &&
|
other.smartInfo == smartInfo &&
|
||||||
other.exifInfo == exifInfo &&
|
other.livePhotoVideoId == livePhotoVideoId;
|
||||||
other.smartInfo == smartInfo &&
|
|
||||||
other.livePhotoVideoId == livePhotoVideoId;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(type.hashCode) +
|
(type.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(deviceAssetId.hashCode) +
|
(deviceAssetId.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(deviceId.hashCode) +
|
(deviceId.hashCode) +
|
||||||
(originalPath.hashCode) +
|
(originalPath.hashCode) +
|
||||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
(resizePath == null ? 0 : resizePath!.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(modifiedAt.hashCode) +
|
(modifiedAt.hashCode) +
|
||||||
(isFavorite.hashCode) +
|
(isFavorite.hashCode) +
|
||||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration.hashCode) +
|
||||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
(webpPath == null ? 0 : webpPath!.hashCode) +
|
||||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode);
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]';
|
||||||
'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'type'] = type;
|
_json[r'type'] = type;
|
||||||
_json[r'id'] = id;
|
_json[r'id'] = id;
|
||||||
_json[r'deviceAssetId'] = deviceAssetId;
|
_json[r'deviceAssetId'] = deviceAssetId;
|
||||||
_json[r'ownerId'] = ownerId;
|
_json[r'ownerId'] = ownerId;
|
||||||
_json[r'deviceId'] = deviceId;
|
_json[r'deviceId'] = deviceId;
|
||||||
_json[r'originalPath'] = originalPath;
|
_json[r'originalPath'] = originalPath;
|
||||||
if (resizePath != null) {
|
if (resizePath != null) {
|
||||||
_json[r'resizePath'] = resizePath;
|
_json[r'resizePath'] = resizePath;
|
||||||
} else {
|
} else {
|
||||||
_json[r'resizePath'] = null;
|
_json[r'resizePath'] = null;
|
||||||
}
|
}
|
||||||
_json[r'createdAt'] = createdAt;
|
_json[r'createdAt'] = createdAt;
|
||||||
_json[r'modifiedAt'] = modifiedAt;
|
_json[r'modifiedAt'] = modifiedAt;
|
||||||
_json[r'isFavorite'] = isFavorite;
|
_json[r'isFavorite'] = isFavorite;
|
||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
_json[r'mimeType'] = mimeType;
|
_json[r'mimeType'] = mimeType;
|
||||||
} else {
|
} else {
|
||||||
_json[r'mimeType'] = null;
|
_json[r'mimeType'] = null;
|
||||||
}
|
}
|
||||||
_json[r'duration'] = duration;
|
_json[r'duration'] = duration;
|
||||||
if (webpPath != null) {
|
if (webpPath != null) {
|
||||||
_json[r'webpPath'] = webpPath;
|
_json[r'webpPath'] = webpPath;
|
||||||
} else {
|
} else {
|
||||||
@ -185,13 +182,13 @@ class AssetResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
||||||
// assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return AssetResponseDto(
|
return AssetResponseDto(
|
||||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
@ -216,10 +213,7 @@ class AssetResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AssetResponseDto>? listFromJson(
|
static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <AssetResponseDto>[];
|
final result = <AssetResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@ -247,18 +241,12 @@ class AssetResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AssetResponseDto>> mapListFromJson(
|
static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<AssetResponseDto>>{};
|
final map = <String, List<AssetResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AssetResponseDto.listFromJson(
|
final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@ -286,3 +274,4 @@ class AssetResponseDto {
|
|||||||
'livePhotoVideoId',
|
'livePhotoVideoId',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ class UsageByUserDto {
|
|||||||
/// Returns a new [UsageByUserDto] instance.
|
/// Returns a new [UsageByUserDto] instance.
|
||||||
UsageByUserDto({
|
UsageByUserDto({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.objects,
|
|
||||||
required this.videos,
|
required this.videos,
|
||||||
required this.photos,
|
required this.photos,
|
||||||
required this.usageRaw,
|
required this.usageRaw,
|
||||||
@ -23,8 +22,6 @@ class UsageByUserDto {
|
|||||||
|
|
||||||
String userId;
|
String userId;
|
||||||
|
|
||||||
int objects;
|
|
||||||
|
|
||||||
int videos;
|
int videos;
|
||||||
|
|
||||||
int photos;
|
int photos;
|
||||||
@ -36,7 +33,6 @@ class UsageByUserDto {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto &&
|
bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto &&
|
||||||
other.userId == userId &&
|
other.userId == userId &&
|
||||||
other.objects == objects &&
|
|
||||||
other.videos == videos &&
|
other.videos == videos &&
|
||||||
other.photos == photos &&
|
other.photos == photos &&
|
||||||
other.usageRaw == usageRaw &&
|
other.usageRaw == usageRaw &&
|
||||||
@ -46,19 +42,17 @@ class UsageByUserDto {
|
|||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(userId.hashCode) +
|
(userId.hashCode) +
|
||||||
(objects.hashCode) +
|
|
||||||
(videos.hashCode) +
|
(videos.hashCode) +
|
||||||
(photos.hashCode) +
|
(photos.hashCode) +
|
||||||
(usageRaw.hashCode) +
|
(usageRaw.hashCode) +
|
||||||
(usage.hashCode);
|
(usage.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UsageByUserDto[userId=$userId, objects=$objects, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]';
|
String toString() => 'UsageByUserDto[userId=$userId, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'userId'] = userId;
|
_json[r'userId'] = userId;
|
||||||
_json[r'objects'] = objects;
|
|
||||||
_json[r'videos'] = videos;
|
_json[r'videos'] = videos;
|
||||||
_json[r'photos'] = photos;
|
_json[r'photos'] = photos;
|
||||||
_json[r'usageRaw'] = usageRaw;
|
_json[r'usageRaw'] = usageRaw;
|
||||||
@ -86,7 +80,6 @@ class UsageByUserDto {
|
|||||||
|
|
||||||
return UsageByUserDto(
|
return UsageByUserDto(
|
||||||
userId: mapValueOfType<String>(json, r'userId')!,
|
userId: mapValueOfType<String>(json, r'userId')!,
|
||||||
objects: mapValueOfType<int>(json, r'objects')!,
|
|
||||||
videos: mapValueOfType<int>(json, r'videos')!,
|
videos: mapValueOfType<int>(json, r'videos')!,
|
||||||
photos: mapValueOfType<int>(json, r'photos')!,
|
photos: mapValueOfType<int>(json, r'photos')!,
|
||||||
usageRaw: mapValueOfType<int>(json, r'usageRaw')!,
|
usageRaw: mapValueOfType<int>(json, r'usageRaw')!,
|
||||||
@ -141,7 +134,6 @@ class UsageByUserDto {
|
|||||||
/// 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>{
|
||||||
'userId',
|
'userId',
|
||||||
'objects',
|
|
||||||
'videos',
|
'videos',
|
||||||
'photos',
|
'photos',
|
||||||
'usageRaw',
|
'usageRaw',
|
||||||
|
@ -43,46 +43,43 @@ class UserResponseDto {
|
|||||||
DateTime? deletedAt;
|
DateTime? deletedAt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
|
||||||
identical(this, other) ||
|
other.id == id &&
|
||||||
other is UserResponseDto &&
|
other.email == email &&
|
||||||
other.id == id &&
|
other.firstName == firstName &&
|
||||||
other.email == email &&
|
other.lastName == lastName &&
|
||||||
other.firstName == firstName &&
|
other.createdAt == createdAt &&
|
||||||
other.lastName == lastName &&
|
other.profileImagePath == profileImagePath &&
|
||||||
other.createdAt == createdAt &&
|
other.shouldChangePassword == shouldChangePassword &&
|
||||||
other.profileImagePath == profileImagePath &&
|
other.isAdmin == isAdmin &&
|
||||||
other.shouldChangePassword == shouldChangePassword &&
|
other.deletedAt == deletedAt;
|
||||||
other.isAdmin == isAdmin &&
|
|
||||||
other.deletedAt == deletedAt;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(email.hashCode) +
|
(email.hashCode) +
|
||||||
(firstName.hashCode) +
|
(firstName.hashCode) +
|
||||||
(lastName.hashCode) +
|
(lastName.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(profileImagePath.hashCode) +
|
(profileImagePath.hashCode) +
|
||||||
(shouldChangePassword.hashCode) +
|
(shouldChangePassword.hashCode) +
|
||||||
(isAdmin.hashCode) +
|
(isAdmin.hashCode) +
|
||||||
(deletedAt == null ? 0 : deletedAt!.hashCode);
|
(deletedAt == null ? 0 : deletedAt!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]';
|
||||||
'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'id'] = id;
|
_json[r'id'] = id;
|
||||||
_json[r'email'] = email;
|
_json[r'email'] = email;
|
||||||
_json[r'firstName'] = firstName;
|
_json[r'firstName'] = firstName;
|
||||||
_json[r'lastName'] = lastName;
|
_json[r'lastName'] = lastName;
|
||||||
_json[r'createdAt'] = createdAt;
|
_json[r'createdAt'] = createdAt;
|
||||||
_json[r'profileImagePath'] = profileImagePath;
|
_json[r'profileImagePath'] = profileImagePath;
|
||||||
_json[r'shouldChangePassword'] = shouldChangePassword;
|
_json[r'shouldChangePassword'] = shouldChangePassword;
|
||||||
_json[r'isAdmin'] = isAdmin;
|
_json[r'isAdmin'] = isAdmin;
|
||||||
if (deletedAt != null) {
|
if (deletedAt != null) {
|
||||||
_json[r'deletedAt'] = deletedAt!.toUtc().toIso8601String();
|
_json[r'deletedAt'] = deletedAt!.toUtc().toIso8601String();
|
||||||
} else {
|
} else {
|
||||||
@ -101,13 +98,13 @@ class UserResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.');
|
assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.');
|
||||||
// assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.');
|
assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return UserResponseDto(
|
return UserResponseDto(
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
@ -116,8 +113,7 @@ class UserResponseDto {
|
|||||||
lastName: mapValueOfType<String>(json, r'lastName')!,
|
lastName: mapValueOfType<String>(json, r'lastName')!,
|
||||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||||
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
|
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
|
||||||
shouldChangePassword:
|
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
|
||||||
mapValueOfType<bool>(json, r'shouldChangePassword')!,
|
|
||||||
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
|
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
|
||||||
deletedAt: mapDateTime(json, r'deletedAt', ''),
|
deletedAt: mapDateTime(json, r'deletedAt', ''),
|
||||||
);
|
);
|
||||||
@ -125,10 +121,7 @@ class UserResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<UserResponseDto>? listFromJson(
|
static List<UserResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <UserResponseDto>[];
|
final result = <UserResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@ -156,18 +149,12 @@ class UserResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of UserResponseDto-objects as value to a dart map
|
// maps a json object with a list of UserResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<UserResponseDto>> mapListFromJson(
|
static Map<String, List<UserResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<UserResponseDto>>{};
|
final map = <String, List<UserResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = UserResponseDto.listFromJson(
|
final value = UserResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@ -189,3 +176,4 @@ class UserResponseDto {
|
|||||||
'deletedAt',
|
'deletedAt',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ export class ServerStatsResponseDto {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.photos = 0;
|
this.photos = 0;
|
||||||
this.videos = 0;
|
this.videos = 0;
|
||||||
this.objects = 0;
|
|
||||||
this.usageByUser = [];
|
this.usageByUser = [];
|
||||||
this.usageRaw = 0;
|
this.usageRaw = 0;
|
||||||
this.usage = '';
|
this.usage = '';
|
||||||
@ -34,7 +33,6 @@ export class ServerStatsResponseDto {
|
|||||||
{
|
{
|
||||||
photos: 1,
|
photos: 1,
|
||||||
videos: 1,
|
videos: 1,
|
||||||
objects: 1,
|
|
||||||
diskUsageRaw: 1,
|
diskUsageRaw: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -3,16 +3,15 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
export class UsageByUserDto {
|
export class UsageByUserDto {
|
||||||
constructor(userId: string) {
|
constructor(userId: string) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.objects = 0;
|
|
||||||
this.videos = 0;
|
this.videos = 0;
|
||||||
this.photos = 0;
|
this.photos = 0;
|
||||||
|
this.usageRaw = 0;
|
||||||
|
this.usage = '0B';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiProperty({ type: 'string' })
|
@ApiProperty({ type: 'string' })
|
||||||
userId: string;
|
userId: string;
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer' })
|
||||||
objects: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
videos: number;
|
videos: number;
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer' })
|
||||||
photos: number;
|
photos: number;
|
||||||
|
@ -7,8 +7,6 @@ import { UsageByUserDto } from './response-dto/usage-by-user-response.dto';
|
|||||||
import { AssetEntity } from '@app/database/entities/asset.entity';
|
import { AssetEntity } from '@app/database/entities/asset.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import path from 'path';
|
|
||||||
import { readdirSync, statSync } from 'fs';
|
|
||||||
import { asHumanReadable } from '../../utils/human-readable.util';
|
import { asHumanReadable } from '../../utils/human-readable.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -35,59 +33,46 @@ export class ServerInfoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getStats(): Promise<ServerStatsResponseDto> {
|
async getStats(): Promise<ServerStatsResponseDto> {
|
||||||
const res = await this.assetRepository
|
const serverStats = new ServerStatsResponseDto();
|
||||||
.createQueryBuilder('asset')
|
|
||||||
.select(`COUNT(asset.id)`, 'count')
|
type UserStatsQueryResponse = {
|
||||||
.addSelect(`asset.type`, 'type')
|
assetType: string;
|
||||||
.addSelect(`asset.userId`, 'userId')
|
assetCount: string;
|
||||||
.groupBy('asset.type, asset.userId')
|
totalSizeInBytes: string;
|
||||||
.addGroupBy('asset.type')
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userStatsQueryResponse: UserStatsQueryResponse[] = await this.assetRepository
|
||||||
|
.createQueryBuilder('a')
|
||||||
|
.select('COUNT(a.id)', 'assetCount')
|
||||||
|
.addSelect('SUM(ei.fileSizeInByte)', 'totalSizeInBytes')
|
||||||
|
.addSelect('a."userId"')
|
||||||
|
.addSelect('a.type', 'assetType')
|
||||||
|
.where('a.isVisible = true')
|
||||||
|
.leftJoin('a.exifInfo', 'ei')
|
||||||
|
.groupBy('a."userId"')
|
||||||
|
.addGroupBy('a.type')
|
||||||
.getRawMany();
|
.getRawMany();
|
||||||
|
|
||||||
const serverStats = new ServerStatsResponseDto();
|
|
||||||
const tmpMap = new Map<string, UsageByUserDto>();
|
const tmpMap = new Map<string, UsageByUserDto>();
|
||||||
const getUsageByUser = (id: string) => tmpMap.get(id) || new UsageByUserDto(id);
|
const getUsageByUser = (id: string) => tmpMap.get(id) || new UsageByUserDto(id);
|
||||||
res.map((item) => {
|
|
||||||
const usage: UsageByUserDto = getUsageByUser(item.userId);
|
userStatsQueryResponse.forEach((r) => {
|
||||||
if (item.type === 'IMAGE') {
|
const usageByUser = getUsageByUser(r.userId);
|
||||||
usage.photos = parseInt(item.count);
|
usageByUser.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0;
|
||||||
serverStats.photos += usage.photos;
|
usageByUser.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0;
|
||||||
} else if (item.type === 'VIDEO') {
|
usageByUser.usageRaw += parseInt(r.totalSizeInBytes);
|
||||||
usage.videos = parseInt(item.count);
|
usageByUser.usage = asHumanReadable(usageByUser.usageRaw);
|
||||||
serverStats.videos += usage.videos;
|
|
||||||
}
|
serverStats.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0;
|
||||||
tmpMap.set(item.userId, usage);
|
serverStats.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0;
|
||||||
|
serverStats.usageRaw += parseInt(r.totalSizeInBytes);
|
||||||
|
serverStats.usage = asHumanReadable(serverStats.usageRaw);
|
||||||
|
tmpMap.set(r.userId, usageByUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const userId of tmpMap.keys()) {
|
|
||||||
const usage = getUsageByUser(userId);
|
|
||||||
const userDiskUsage = await ServerInfoService.getDirectoryStats(path.join(APP_UPLOAD_LOCATION, userId));
|
|
||||||
usage.usageRaw = userDiskUsage.size;
|
|
||||||
usage.objects = userDiskUsage.fileCount;
|
|
||||||
usage.usage = asHumanReadable(usage.usageRaw);
|
|
||||||
serverStats.usageRaw += usage.usageRaw;
|
|
||||||
serverStats.objects += usage.objects;
|
|
||||||
}
|
|
||||||
serverStats.usage = asHumanReadable(serverStats.usageRaw);
|
|
||||||
serverStats.usageByUser = Array.from(tmpMap.values());
|
serverStats.usageByUser = Array.from(tmpMap.values());
|
||||||
|
|
||||||
return serverStats;
|
return serverStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async getDirectoryStats(dirPath: string) {
|
|
||||||
let size = 0;
|
|
||||||
let fileCount = 0;
|
|
||||||
for (const filename of readdirSync(dirPath)) {
|
|
||||||
const absFilename = path.join(dirPath, filename);
|
|
||||||
const fileStat = statSync(absFilename);
|
|
||||||
if (fileStat.isFile()) {
|
|
||||||
size += fileStat.size;
|
|
||||||
fileCount += 1;
|
|
||||||
} else if (fileStat.isDirectory()) {
|
|
||||||
const subDirStat = await ServerInfoService.getDirectoryStats(absFilename);
|
|
||||||
size += subDirStat.size;
|
|
||||||
fileCount += subDirStat.fileCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { size, fileCount };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1632,12 +1632,6 @@ export interface UsageByUserDto {
|
|||||||
* @memberof UsageByUserDto
|
* @memberof UsageByUserDto
|
||||||
*/
|
*/
|
||||||
'userId': string;
|
'userId': string;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof UsageByUserDto
|
|
||||||
*/
|
|
||||||
'objects': number;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import { ServerStatsResponseDto, UserResponseDto } from '@api';
|
import { ServerStatsResponseDto, UserResponseDto } from '@api';
|
||||||
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
||||||
import PlayCircle from 'svelte-material-icons/PlayCircle.svelte';
|
import PlayCircle from 'svelte-material-icons/PlayCircle.svelte';
|
||||||
import FileImageOutline from 'svelte-material-icons/FileImageOutline.svelte';
|
|
||||||
import Memory from 'svelte-material-icons/Memory.svelte';
|
import Memory from 'svelte-material-icons/Memory.svelte';
|
||||||
import StatsCard from './stats-card.svelte';
|
import StatsCard from './stats-card.svelte';
|
||||||
export let stats: ServerStatsResponseDto;
|
export let stats: ServerStatsResponseDto;
|
||||||
@ -27,7 +26,6 @@
|
|||||||
<div class="flex mt-5 justify-between">
|
<div class="flex mt-5 justify-between">
|
||||||
<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} />
|
<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} />
|
||||||
<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats.videos.toString()} />
|
<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats.videos.toString()} />
|
||||||
<StatsCard logo={FileImageOutline} title={'OBJECTS'} value={stats.objects.toString()} />
|
|
||||||
<StatsCard logo={Memory} title={'STORAGE'} value={spaceUsage} unit={spaceUnit} />
|
<StatsCard logo={Memory} title={'STORAGE'} value={spaceUsage} unit={spaceUnit} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -39,11 +37,10 @@
|
|||||||
class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary w-full h-12"
|
class="border rounded-md mb-4 bg-gray-50 dark:bg-immich-dark-gray dark:border-immich-dark-gray flex text-immich-primary dark:text-immich-dark-primary w-full h-12"
|
||||||
>
|
>
|
||||||
<tr class="flex w-full place-items-center">
|
<tr class="flex w-full place-items-center">
|
||||||
<th class="text-center w-1/5 font-medium text-sm">User</th>
|
<th class="text-center w-1/4 font-medium text-sm">User</th>
|
||||||
<th class="text-center w-1/5 font-medium text-sm">Photos</th>
|
<th class="text-center w-1/4 font-medium text-sm">Photos</th>
|
||||||
<th class="text-center w-1/5 font-medium text-sm">Videos</th>
|
<th class="text-center w-1/4 font-medium text-sm">Videos</th>
|
||||||
<th class="text-center w-1/5 font-medium text-sm">Objects</th>
|
<th class="text-center w-1/4 font-medium text-sm">Size</th>
|
||||||
<th class="text-center w-1/5 font-medium text-sm">Size</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
@ -57,11 +54,10 @@
|
|||||||
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
|
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<td class="text-sm px-2 w-1/5 text-ellipsis">{getFullName(user.userId)}</td>
|
<td class="text-sm px-2 w-1/4 text-ellipsis">{getFullName(user.userId)}</td>
|
||||||
<td class="text-sm px-2 w-1/5 text-ellipsis">{user.photos}</td>
|
<td class="text-sm px-2 w-1/4 text-ellipsis">{user.photos}</td>
|
||||||
<td class="text-sm px-2 w-1/5 text-ellipsis">{user.videos}</td>
|
<td class="text-sm px-2 w-1/4 text-ellipsis">{user.videos}</td>
|
||||||
<td class="text-sm px-2 w-1/5 text-ellipsis">{user.objects}</td>
|
<td class="text-sm px-2 w-1/4 text-ellipsis">{user.usage}</td>
|
||||||
<td class="text-sm px-2 w-1/5 text-ellipsis">{user.usage}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
$: zeros = () => {
|
$: zeros = () => {
|
||||||
let result = '';
|
let result = '';
|
||||||
const maxLength = 9;
|
const maxLength = 13;
|
||||||
const valueLength = parseInt(value).toString().length;
|
const valueLength = parseInt(value).toString().length;
|
||||||
const zeroLength = maxLength - valueLength;
|
const zeroLength = maxLength - valueLength;
|
||||||
for (let i = 0; i < zeroLength; i++) {
|
for (let i = 0; i < zeroLength; i++) {
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="w-[180px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between"
|
class="w-[250px] h-[140px] bg-immich-gray dark:bg-immich-dark-gray rounded-3xl p-5 flex flex-col justify-between"
|
||||||
>
|
>
|
||||||
<div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
|
<div class="flex place-items-center gap-4 text-immich-primary dark:text-immich-dark-primary">
|
||||||
<svelte:component this={logo} size="40" />
|
<svelte:component this={logo} size="40" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user