mirror of https://github.com/immich-app/immich.git
feat(web,server): offline/untracked files admin tool (#4447)
* feat: admin repair orphans tool * chore: open api * fix: include upload folder * fix: bugs * feat: empty placeholder * fix: checks * feat: move buttons to top of page * feat: styling and clipboard * styling * better clicking hitbox * fix: show title on hover * feat: download report * restrict file access to immich related files * Add description --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>pull/4483/head
parent
ed386dd12a
commit
d2807b8d6a
@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.FileChecksumDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**filenames** | **List<String>** | | [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)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.FileChecksumResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**checksum** | **String** | |
|
||||||
|
**filename** | **String** | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.FileReportDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**extras** | **List<String>** | | [default to const []]
|
||||||
|
**orphans** | [**List<FileReportItemDto>**](FileReportItemDto.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)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.FileReportFixDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**items** | [**List<FileReportItemDto>**](FileReportItemDto.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)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
# openapi.model.FileReportItemDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**checksum** | **String** | | [optional]
|
||||||
|
**entityId** | **String** | |
|
||||||
|
**entityType** | [**PathEntityType**](PathEntityType.md) | |
|
||||||
|
**pathType** | [**PathType**](PathType.md) | |
|
||||||
|
**pathValue** | **String** | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.PathEntityType
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.PathType
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class FileChecksumDto {
|
||||||
|
/// Returns a new [FileChecksumDto] instance.
|
||||||
|
FileChecksumDto({
|
||||||
|
this.filenames = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<String> filenames;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is FileChecksumDto &&
|
||||||
|
other.filenames == filenames;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(filenames.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FileChecksumDto[filenames=$filenames]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'filenames'] = this.filenames;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [FileChecksumDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static FileChecksumDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return FileChecksumDto(
|
||||||
|
filenames: json[r'filenames'] is List
|
||||||
|
? (json[r'filenames'] as List).cast<String>()
|
||||||
|
: const [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileChecksumDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <FileChecksumDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = FileChecksumDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, FileChecksumDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, FileChecksumDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = FileChecksumDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of FileChecksumDto-objects as value to a dart map
|
||||||
|
static Map<String, List<FileChecksumDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<FileChecksumDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = FileChecksumDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'filenames',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class FileChecksumResponseDto {
|
||||||
|
/// Returns a new [FileChecksumResponseDto] instance.
|
||||||
|
FileChecksumResponseDto({
|
||||||
|
required this.checksum,
|
||||||
|
required this.filename,
|
||||||
|
});
|
||||||
|
|
||||||
|
String checksum;
|
||||||
|
|
||||||
|
String filename;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is FileChecksumResponseDto &&
|
||||||
|
other.checksum == checksum &&
|
||||||
|
other.filename == filename;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(checksum.hashCode) +
|
||||||
|
(filename.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FileChecksumResponseDto[checksum=$checksum, filename=$filename]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'checksum'] = this.checksum;
|
||||||
|
json[r'filename'] = this.filename;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [FileChecksumResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static FileChecksumResponseDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return FileChecksumResponseDto(
|
||||||
|
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||||
|
filename: mapValueOfType<String>(json, r'filename')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileChecksumResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <FileChecksumResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = FileChecksumResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, FileChecksumResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, FileChecksumResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = FileChecksumResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of FileChecksumResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<FileChecksumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<FileChecksumResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = FileChecksumResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'checksum',
|
||||||
|
'filename',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class FileReportDto {
|
||||||
|
/// Returns a new [FileReportDto] instance.
|
||||||
|
FileReportDto({
|
||||||
|
this.extras = const [],
|
||||||
|
this.orphans = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<String> extras;
|
||||||
|
|
||||||
|
List<FileReportItemDto> orphans;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is FileReportDto &&
|
||||||
|
other.extras == extras &&
|
||||||
|
other.orphans == orphans;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(extras.hashCode) +
|
||||||
|
(orphans.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FileReportDto[extras=$extras, orphans=$orphans]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'extras'] = this.extras;
|
||||||
|
json[r'orphans'] = this.orphans;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [FileReportDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static FileReportDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return FileReportDto(
|
||||||
|
extras: json[r'extras'] is List
|
||||||
|
? (json[r'extras'] as List).cast<String>()
|
||||||
|
: const [],
|
||||||
|
orphans: FileReportItemDto.listFromJson(json[r'orphans']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileReportDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <FileReportDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = FileReportDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, FileReportDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, FileReportDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = FileReportDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of FileReportDto-objects as value to a dart map
|
||||||
|
static Map<String, List<FileReportDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<FileReportDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = FileReportDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'extras',
|
||||||
|
'orphans',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class FileReportFixDto {
|
||||||
|
/// Returns a new [FileReportFixDto] instance.
|
||||||
|
FileReportFixDto({
|
||||||
|
this.items = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<FileReportItemDto> items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is FileReportFixDto &&
|
||||||
|
other.items == items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(items.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FileReportFixDto[items=$items]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'items'] = this.items;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [FileReportFixDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static FileReportFixDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return FileReportFixDto(
|
||||||
|
items: FileReportItemDto.listFromJson(json[r'items']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileReportFixDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <FileReportFixDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = FileReportFixDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, FileReportFixDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, FileReportFixDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = FileReportFixDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of FileReportFixDto-objects as value to a dart map
|
||||||
|
static Map<String, List<FileReportFixDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<FileReportFixDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = FileReportFixDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'items',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class FileReportItemDto {
|
||||||
|
/// Returns a new [FileReportItemDto] instance.
|
||||||
|
FileReportItemDto({
|
||||||
|
this.checksum,
|
||||||
|
required this.entityId,
|
||||||
|
required this.entityType,
|
||||||
|
required this.pathType,
|
||||||
|
required this.pathValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? checksum;
|
||||||
|
|
||||||
|
String entityId;
|
||||||
|
|
||||||
|
PathEntityType entityType;
|
||||||
|
|
||||||
|
PathType pathType;
|
||||||
|
|
||||||
|
String pathValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is FileReportItemDto &&
|
||||||
|
other.checksum == checksum &&
|
||||||
|
other.entityId == entityId &&
|
||||||
|
other.entityType == entityType &&
|
||||||
|
other.pathType == pathType &&
|
||||||
|
other.pathValue == pathValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(checksum == null ? 0 : checksum!.hashCode) +
|
||||||
|
(entityId.hashCode) +
|
||||||
|
(entityType.hashCode) +
|
||||||
|
(pathType.hashCode) +
|
||||||
|
(pathValue.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FileReportItemDto[checksum=$checksum, entityId=$entityId, entityType=$entityType, pathType=$pathType, pathValue=$pathValue]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
if (this.checksum != null) {
|
||||||
|
json[r'checksum'] = this.checksum;
|
||||||
|
} else {
|
||||||
|
// json[r'checksum'] = null;
|
||||||
|
}
|
||||||
|
json[r'entityId'] = this.entityId;
|
||||||
|
json[r'entityType'] = this.entityType;
|
||||||
|
json[r'pathType'] = this.pathType;
|
||||||
|
json[r'pathValue'] = this.pathValue;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [FileReportItemDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static FileReportItemDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return FileReportItemDto(
|
||||||
|
checksum: mapValueOfType<String>(json, r'checksum'),
|
||||||
|
entityId: mapValueOfType<String>(json, r'entityId')!,
|
||||||
|
entityType: PathEntityType.fromJson(json[r'entityType'])!,
|
||||||
|
pathType: PathType.fromJson(json[r'pathType'])!,
|
||||||
|
pathValue: mapValueOfType<String>(json, r'pathValue')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileReportItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <FileReportItemDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = FileReportItemDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, FileReportItemDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, FileReportItemDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = FileReportItemDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of FileReportItemDto-objects as value to a dart map
|
||||||
|
static Map<String, List<FileReportItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<FileReportItemDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = FileReportItemDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'entityId',
|
||||||
|
'entityType',
|
||||||
|
'pathType',
|
||||||
|
'pathValue',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class PathEntityType {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const PathEntityType._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const asset = PathEntityType._(r'asset');
|
||||||
|
static const person = PathEntityType._(r'person');
|
||||||
|
static const user = PathEntityType._(r'user');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][PathEntityType].
|
||||||
|
static const values = <PathEntityType>[
|
||||||
|
asset,
|
||||||
|
person,
|
||||||
|
user,
|
||||||
|
];
|
||||||
|
|
||||||
|
static PathEntityType? fromJson(dynamic value) => PathEntityTypeTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<PathEntityType>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <PathEntityType>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = PathEntityType.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [PathEntityType] to String,
|
||||||
|
/// and [decode] dynamic data back to [PathEntityType].
|
||||||
|
class PathEntityTypeTypeTransformer {
|
||||||
|
factory PathEntityTypeTypeTransformer() => _instance ??= const PathEntityTypeTypeTransformer._();
|
||||||
|
|
||||||
|
const PathEntityTypeTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(PathEntityType data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a PathEntityType.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
PathEntityType? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'asset': return PathEntityType.asset;
|
||||||
|
case r'person': return PathEntityType.person;
|
||||||
|
case r'user': return PathEntityType.user;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [PathEntityTypeTypeTransformer] instance.
|
||||||
|
static PathEntityTypeTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class PathType {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const PathType._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const original = PathType._(r'original');
|
||||||
|
static const jpegThumbnail = PathType._(r'jpeg_thumbnail');
|
||||||
|
static const webpThumbnail = PathType._(r'webp_thumbnail');
|
||||||
|
static const encodedVideo = PathType._(r'encoded_video');
|
||||||
|
static const sidecar = PathType._(r'sidecar');
|
||||||
|
static const face = PathType._(r'face');
|
||||||
|
static const profile = PathType._(r'profile');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][PathType].
|
||||||
|
static const values = <PathType>[
|
||||||
|
original,
|
||||||
|
jpegThumbnail,
|
||||||
|
webpThumbnail,
|
||||||
|
encodedVideo,
|
||||||
|
sidecar,
|
||||||
|
face,
|
||||||
|
profile,
|
||||||
|
];
|
||||||
|
|
||||||
|
static PathType? fromJson(dynamic value) => PathTypeTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<PathType>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <PathType>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = PathType.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [PathType] to String,
|
||||||
|
/// and [decode] dynamic data back to [PathType].
|
||||||
|
class PathTypeTypeTransformer {
|
||||||
|
factory PathTypeTypeTransformer() => _instance ??= const PathTypeTypeTransformer._();
|
||||||
|
|
||||||
|
const PathTypeTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(PathType data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a PathType.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
PathType? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'original': return PathType.original;
|
||||||
|
case r'jpeg_thumbnail': return PathType.jpegThumbnail;
|
||||||
|
case r'webp_thumbnail': return PathType.webpThumbnail;
|
||||||
|
case r'encoded_video': return PathType.encodedVideo;
|
||||||
|
case r'sidecar': return PathType.sidecar;
|
||||||
|
case r'face': return PathType.face;
|
||||||
|
case r'profile': return PathType.profile;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [PathTypeTypeTransformer] instance.
|
||||||
|
static PathTypeTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for FileChecksumDto
|
||||||
|
void main() {
|
||||||
|
// final instance = FileChecksumDto();
|
||||||
|
|
||||||
|
group('test FileChecksumDto', () {
|
||||||
|
// List<String> filenames (default value: const [])
|
||||||
|
test('to test the property `filenames`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for FileChecksumResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = FileChecksumResponseDto();
|
||||||
|
|
||||||
|
group('test FileChecksumResponseDto', () {
|
||||||
|
// String checksum
|
||||||
|
test('to test the property `checksum`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// String filename
|
||||||
|
test('to test the property `filename`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for FileReportDto
|
||||||
|
void main() {
|
||||||
|
// final instance = FileReportDto();
|
||||||
|
|
||||||
|
group('test FileReportDto', () {
|
||||||
|
// List<String> extras (default value: const [])
|
||||||
|
test('to test the property `extras`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// List<FileReportItemDto> orphans (default value: const [])
|
||||||
|
test('to test the property `orphans`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for FileReportFixDto
|
||||||
|
void main() {
|
||||||
|
// final instance = FileReportFixDto();
|
||||||
|
|
||||||
|
group('test FileReportFixDto', () {
|
||||||
|
// List<FileReportItemDto> items (default value: const [])
|
||||||
|
test('to test the property `items`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for FileReportItemDto
|
||||||
|
void main() {
|
||||||
|
// final instance = FileReportItemDto();
|
||||||
|
|
||||||
|
group('test FileReportItemDto', () {
|
||||||
|
// String checksum
|
||||||
|
test('to test the property `checksum`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// String entityId
|
||||||
|
test('to test the property `entityId`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// PathEntityType entityType
|
||||||
|
test('to test the property `entityType`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// PathType pathType
|
||||||
|
test('to test the property `pathType`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// String pathValue
|
||||||
|
test('to test the property `pathValue`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for PathEntityType
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test PathEntityType', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for PathType
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test PathType', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,17 +1,45 @@
|
|||||||
import { DatabaseAction, EntityType } from '@app/infra/entities';
|
import { DatabaseAction, EntityType } from '@app/infra/entities';
|
||||||
import { IAccessRepositoryMock, auditStub, authStub, newAccessRepositoryMock, newAuditRepositoryMock } from '@test';
|
import {
|
||||||
import { IAuditRepository } from '../repositories';
|
IAccessRepositoryMock,
|
||||||
|
auditStub,
|
||||||
|
authStub,
|
||||||
|
newAccessRepositoryMock,
|
||||||
|
newAssetRepositoryMock,
|
||||||
|
newAuditRepositoryMock,
|
||||||
|
newCryptoRepositoryMock,
|
||||||
|
newPersonRepositoryMock,
|
||||||
|
newStorageRepositoryMock,
|
||||||
|
newUserRepositoryMock,
|
||||||
|
} from '@test';
|
||||||
|
import {
|
||||||
|
IAssetRepository,
|
||||||
|
IAuditRepository,
|
||||||
|
ICryptoRepository,
|
||||||
|
IPersonRepository,
|
||||||
|
IStorageRepository,
|
||||||
|
IUserRepository,
|
||||||
|
} from '../repositories';
|
||||||
import { AuditService } from './audit.service';
|
import { AuditService } from './audit.service';
|
||||||
|
|
||||||
describe(AuditService.name, () => {
|
describe(AuditService.name, () => {
|
||||||
let sut: AuditService;
|
let sut: AuditService;
|
||||||
let accessMock: IAccessRepositoryMock;
|
let accessMock: IAccessRepositoryMock;
|
||||||
|
let assetMock: jest.Mocked<IAssetRepository>;
|
||||||
let auditMock: jest.Mocked<IAuditRepository>;
|
let auditMock: jest.Mocked<IAuditRepository>;
|
||||||
|
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||||
|
let personMock: jest.Mocked<IPersonRepository>;
|
||||||
|
let storageMock: jest.Mocked<IStorageRepository>;
|
||||||
|
let userMock: jest.Mocked<IUserRepository>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
accessMock = newAccessRepositoryMock();
|
accessMock = newAccessRepositoryMock();
|
||||||
|
assetMock = newAssetRepositoryMock();
|
||||||
|
cryptoMock = newCryptoRepositoryMock();
|
||||||
auditMock = newAuditRepositoryMock();
|
auditMock = newAuditRepositoryMock();
|
||||||
sut = new AuditService(accessMock, auditMock);
|
personMock = newPersonRepositoryMock();
|
||||||
|
storageMock = newStorageRepositoryMock();
|
||||||
|
userMock = newUserRepositoryMock();
|
||||||
|
sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<svg width="900" height="600" viewBox="0 0 900 600" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="transparent" d="M0 0h900v600H0z"/><path d="M214.359 475.389c16.42 16.712 47.124 13.189 47.124 13.189s4.064-30.62-12.372-47.322c-16.419-16.712-47.109-13.198-47.109-13.198s-4.063 30.619 12.357 47.331z" fill="url(#a)"/><path d="M639.439 125.517c-17.194 9.808-41.345-.121-41.345-.121s3.743-25.827 20.946-35.623c17.194-9.808 41.335.11 41.335.11s-3.743 25.827-20.936 35.634z" fill="url(#b)"/><path d="M324.812 156.133c-17.672 17.987-50.72 14.194-50.72 14.194s-4.373-32.955 13.316-50.931c17.673-17.987 50.704-14.206 50.704-14.206s4.373 32.956-13.3 50.943z" fill="url(#c)"/><ellipse rx="15.17" ry="15.928" transform="matrix(1 0 0 -1 228.07 341.957)" fill="#E1E4E5"/><circle r="8.5" transform="matrix(1 0 0 -1 478.5 509.5)" fill="#9d9ea3"/><circle r="17.518" transform="matrix(1 0 0 -1 693.518 420.518)" fill="#9d9ea3"/><circle cx="708.183" cy="266.183" r="14.183" fill="#4F4F51"/><circle cx="247.603" cy="225.621" r="12.136" fill="#F8AE9D"/><ellipse cx="316.324" cy="510.867" rx="7.324" ry="6.867" fill="#E1E4E5"/><ellipse cx="664.796" cy="371.388" rx="9.796" ry="9.388" fill="#E1E4E5"/><circle cx="625.378" cy="479.378" r="11.377" fill="#E1E4E5"/><ellipse cx="401.025" cy="114.39" rx="5.309" ry="6.068" fill="#E1E4E5"/><circle cx="661.834" cy="300.834" r="5.58" transform="rotate(105 661.834 300.834)" fill="#E1E4E5"/><circle cx="654.769" cy="226.082" r="7.585" fill="#E1E4E5"/><ellipse cx="254.159" cy="284.946" rx="5.309" ry="4.551" fill="#E1E4E5"/><circle cx="521.363" cy="106.27" r="11.613" transform="rotate(105 521.363 106.27)" fill="#E1E4E5"/><path d="M162.314 308.103h-.149C161.284 320.589 152 320.781 152 320.781s10.238.2 10.238 14.628c0-14.428 10.238-14.628 10.238-14.628s-9.281-.192-10.162-12.678zm531.83-158.512h-.256c-1.518 21.504-17.507 21.835-17.507 21.835s17.632.345 17.632 25.192c0-24.847 17.632-25.192 17.632-25.192s-15.983-.331-17.501-21.835z" fill="#E1E4E5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M553.714 397.505v56.123c0 20.672-16.743 37.416-37.415 37.416H329.22c-20.672 0-37.415-16.744-37.415-37.416V266.55c0-20.672 16.743-37.416 37.415-37.416h56.124" fill="url(#d)"/><path fill-rule="evenodd" clip-rule="evenodd" d="M363.07 155.431h214.049c26.28 0 47.566 21.286 47.566 47.566v214.049c0 26.28-21.286 47.566-47.566 47.566H363.07c-26.28 0-47.566-21.286-47.566-47.566V202.997c0-26.28 21.286-47.566 47.566-47.566z" fill="#9d9ea3"/><path d="m425.113 307.765 33.925 33.924 74.038-74.059" stroke="#fff" stroke-width="32.125" stroke-linecap="round" stroke-linejoin="round"/><defs><linearGradient id="a" x1="279.871" y1="532.474" x2="161.165" y2="346.391" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#EEE"/></linearGradient><linearGradient id="b" x1="573.046" y1="156.85" x2="712.364" y2="32.889" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#EEE"/></linearGradient><linearGradient id="c" x1="254.302" y1="217.573" x2="382.065" y2="17.293" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#EEE"/></linearGradient><linearGradient id="d" x1="417.175" y1="82.293" x2="425.251" y2="775.957" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#EEE"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1,26 @@
|
|||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ parent, locals: { api } }) => {
|
||||||
|
const { user } = await parent();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||||
|
} else if (!user.isAdmin) {
|
||||||
|
throw redirect(302, AppRoute.PHOTOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { orphans, extras },
|
||||||
|
} = await api.auditApi.getAuditFiles();
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
orphans,
|
||||||
|
extras,
|
||||||
|
meta: {
|
||||||
|
title: 'Repair',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
@ -0,0 +1,336 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import empty4Url from '$lib/assets/empty-4.svg';
|
||||||
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
|
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
||||||
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
|
import {
|
||||||
|
NotificationType,
|
||||||
|
notificationController,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { downloadManager } from '$lib/stores/download';
|
||||||
|
import { downloadBlob } from '$lib/utils/asset-utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { FileReportItemDto, api, copyToClipboard } from '@api';
|
||||||
|
import CheckAll from 'svelte-material-icons/CheckAll.svelte';
|
||||||
|
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
|
||||||
|
import Download from 'svelte-material-icons/Download.svelte';
|
||||||
|
import Refresh from 'svelte-material-icons/Refresh.svelte';
|
||||||
|
import Wrench from 'svelte-material-icons/Wrench.svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
interface UntrackedFile {
|
||||||
|
filename: string;
|
||||||
|
checksum: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Match {
|
||||||
|
orphan: FileReportItemDto;
|
||||||
|
extra: UntrackedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalize = (filenames: string[]) => filenames.map((filename) => ({ filename, checksum: null }));
|
||||||
|
|
||||||
|
let checking = false;
|
||||||
|
let repairing = false;
|
||||||
|
|
||||||
|
let orphans: FileReportItemDto[] = data.orphans;
|
||||||
|
let extras: UntrackedFile[] = normalize(data.extras);
|
||||||
|
let matches: Match[] = [];
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
if (extras.length > 0) {
|
||||||
|
const blob = new Blob([extras.map(({ filename }) => filename).join('\n')], { type: 'text/plain' });
|
||||||
|
const downloadKey = 'untracked.txt';
|
||||||
|
downloadManager.add(downloadKey, blob.size);
|
||||||
|
downloadManager.update(downloadKey, blob.size);
|
||||||
|
downloadBlob(blob, downloadKey);
|
||||||
|
setTimeout(() => downloadManager.clear(downloadKey), 5_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orphans.length > 0) {
|
||||||
|
const blob = new Blob([JSON.stringify(orphans, null, 4)], { type: 'application/json' });
|
||||||
|
const downloadKey = 'orphans.json';
|
||||||
|
downloadManager.add(downloadKey, blob.size);
|
||||||
|
downloadManager.update(downloadKey, blob.size);
|
||||||
|
downloadBlob(blob, downloadKey);
|
||||||
|
setTimeout(() => downloadManager.clear(downloadKey), 5_000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRepair = async () => {
|
||||||
|
if (matches.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
repairing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.auditApi.fixAuditFiles({
|
||||||
|
fileReportFixDto: {
|
||||||
|
items: matches.map(({ orphan, extra }) => ({
|
||||||
|
entityId: orphan.entityId,
|
||||||
|
entityType: orphan.entityType,
|
||||||
|
pathType: orphan.pathType,
|
||||||
|
pathValue: extra.filename,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
type: NotificationType.Info,
|
||||||
|
message: `Repaired ${matches.length} items`,
|
||||||
|
});
|
||||||
|
|
||||||
|
matches = [];
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to repair items');
|
||||||
|
} finally {
|
||||||
|
repairing = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSplit = (match: Match) => {
|
||||||
|
matches = matches.filter((_match) => _match !== match);
|
||||||
|
orphans = [match.orphan, ...orphans];
|
||||||
|
extras = [match.extra, ...extras];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
matches = [];
|
||||||
|
orphans = [];
|
||||||
|
extras = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: report } = await api.auditApi.getAuditFiles();
|
||||||
|
|
||||||
|
orphans = report.orphans;
|
||||||
|
extras = normalize(report.extras);
|
||||||
|
|
||||||
|
notificationController.show({ message: 'Refreshed', type: NotificationType.Info });
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to load items');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckOne = async (filename: string) => {
|
||||||
|
try {
|
||||||
|
const matched = await loadAndMatch([filename]);
|
||||||
|
if (matched) {
|
||||||
|
notificationController.show({ message: `Matched 1 item`, type: NotificationType.Info });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to check item');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckAll = async () => {
|
||||||
|
checking = true;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chunkSize = 10;
|
||||||
|
const filenames = [...extras.filter(({ checksum }) => !checksum).map(({ filename }) => filename)];
|
||||||
|
for (let i = 0; i < filenames.length; i += chunkSize) {
|
||||||
|
count += await loadAndMatch(filenames.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to check items');
|
||||||
|
} finally {
|
||||||
|
checking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationController.show({ message: `Matched ${count} items`, type: NotificationType.Info });
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadAndMatch = async (filenames: string[]) => {
|
||||||
|
const { data: items } = await api.auditApi.getFileChecksums({
|
||||||
|
fileChecksumDto: { filenames },
|
||||||
|
});
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const { checksum, filename } of items) {
|
||||||
|
const extra = extras.find((extra) => extra.filename === filename);
|
||||||
|
if (extra) {
|
||||||
|
extra.checksum = checksum;
|
||||||
|
extras = [...extras];
|
||||||
|
}
|
||||||
|
|
||||||
|
const orphan = orphans.find((orphan) => orphan.checksum === checksum);
|
||||||
|
if (orphan) {
|
||||||
|
count++;
|
||||||
|
matches = [...matches, { orphan, extra: { filename, checksum } }];
|
||||||
|
orphans = orphans.filter((_orphan) => _orphan !== orphan);
|
||||||
|
extras = extras.filter((extra) => extra.filename !== filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<UserPageLayout user={data.user} title={data.meta.title} admin>
|
||||||
|
<svelte:fragment slot="sidebar" />
|
||||||
|
<div class="flex justify-end gap-2" slot="buttons">
|
||||||
|
<LinkButton on:click={() => handleRepair()} disabled={matches.length === 0 || repairing}>
|
||||||
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
|
<Wrench size="18" />
|
||||||
|
Repair All
|
||||||
|
</div>
|
||||||
|
</LinkButton>
|
||||||
|
<LinkButton on:click={() => handleCheckAll()} disabled={extras.length === 0 || checking}>
|
||||||
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
|
<CheckAll size="18" />
|
||||||
|
Check All
|
||||||
|
</div>
|
||||||
|
</LinkButton>
|
||||||
|
<LinkButton on:click={() => handleDownload()} disabled={extras.length + orphans.length === 0}>
|
||||||
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
|
<Download size="18" />
|
||||||
|
Export
|
||||||
|
</div>
|
||||||
|
</LinkButton>
|
||||||
|
<LinkButton on:click={() => handleRefresh()}>
|
||||||
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
|
<Refresh size="18" />
|
||||||
|
Refresh
|
||||||
|
</div>
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
||||||
|
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
|
||||||
|
{#if matches.length + extras.length + orphans.length === 0}
|
||||||
|
<div class="w-full">
|
||||||
|
<EmptyPlaceholder
|
||||||
|
fullWidth
|
||||||
|
text="Untracked and missing files will show up here"
|
||||||
|
alt="Empty report"
|
||||||
|
src={empty4Url}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="gap-2">
|
||||||
|
<table class="table-fixed mt-5 w-full text-left">
|
||||||
|
<thead
|
||||||
|
class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
|
||||||
|
>
|
||||||
|
<tr class="flex w-full place-items-center p-2 md:p-5">
|
||||||
|
<th class="w-full text-sm place-items-center font-medium flex justify-between" colspan="2">
|
||||||
|
<div class="px-3">
|
||||||
|
<p>MATCHES {matches.length ? `(${matches.length})` : ''}</p>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mt-1">These files are matched by their checksums</p>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg max-h-[500px] block overflow-x-hidden"
|
||||||
|
>
|
||||||
|
{#each matches as match (match.extra.filename)}
|
||||||
|
<tr
|
||||||
|
class="w-full h-[75px] place-items-center border-[3px] border-transparent p-2 odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5 flex justify-between"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => handleSplit(match)}
|
||||||
|
>
|
||||||
|
<td class="text-sm text-ellipsis flex flex-col gap-1 font-mono">
|
||||||
|
<span>{match.orphan.pathValue} =></span>
|
||||||
|
<span>{match.extra.filename}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-sm text-ellipsis d-flex font-mono">
|
||||||
|
<span>({match.orphan.entityType}/{match.orphan.pathType})</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table-fixed mt-5 w-full text-left">
|
||||||
|
<thead
|
||||||
|
class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
|
||||||
|
>
|
||||||
|
<tr class="flex w-full place-items-center p-1 md:p-5">
|
||||||
|
<th class="w-full text-sm font-medium justify-between place-items-center flex" colspan="2">
|
||||||
|
<div class="px-3">
|
||||||
|
<p>OFFLINE PATHS {orphans.length ? `(${orphans.length})` : ''}</p>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mt-1">
|
||||||
|
These files are the results of manually deletion of the default upload library
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="w-full rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg overflow-y-auto max-h-[500px] block overflow-x-hidden"
|
||||||
|
>
|
||||||
|
{#each orphans as orphan, index (index)}
|
||||||
|
<tr
|
||||||
|
class="w-full h-[50px] place-items-center border-[3px] border-transparent odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5 flex justify-between"
|
||||||
|
tabindex="0"
|
||||||
|
title={orphan.pathValue}
|
||||||
|
>
|
||||||
|
<td on:click={() => copyToClipboard(orphan.pathValue)}>
|
||||||
|
<CircleIconButton logo={ContentCopy} size="18" />
|
||||||
|
</td>
|
||||||
|
<td class="truncate text-sm font-mono text-left" title={orphan.pathValue}>
|
||||||
|
{orphan.pathValue}
|
||||||
|
</td>
|
||||||
|
<td class="text-sm font-mono">
|
||||||
|
<span>({orphan.entityType})</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table-fixed mt-5 w-full text-left max-h-[300px]">
|
||||||
|
<thead
|
||||||
|
class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
|
||||||
|
>
|
||||||
|
<tr class="flex w-full place-items-center p-2 md:p-5">
|
||||||
|
<th class="w-full text-sm font-medium place-items-center flex justify-between" colspan="2">
|
||||||
|
<div class="px-3">
|
||||||
|
<p>UNTRACKS FILES {extras.length ? `(${extras.length})` : ''}</p>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mt-1">
|
||||||
|
These files are not tracked by the application. They can be the results of failed moves,
|
||||||
|
interrupted uploads, or left behind due to a bug
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="w-full rounded-md border-2 dark:border-immich-dark-gray dark:text-immich-dark-fg overflow-y-auto max-h-[500px] block overflow-x-hidden"
|
||||||
|
>
|
||||||
|
{#each extras as extra (extra.filename)}
|
||||||
|
<tr
|
||||||
|
class="flex h-[50px] w-full place-items-center border-[3px] border-transparent p-1 odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5 justify-between"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => handleCheckOne(extra.filename)}
|
||||||
|
title={extra.filename}
|
||||||
|
>
|
||||||
|
<td on:click={() => copyToClipboard(extra.filename)}>
|
||||||
|
<CircleIconButton logo={ContentCopy} size="18" />
|
||||||
|
</td>
|
||||||
|
<td class="w-full text-md text-ellipsis flex justify-between pr-5">
|
||||||
|
<span class="text-ellipsis grow truncate font-mono text-sm pr-5" title={extra.filename}
|
||||||
|
>{extra.filename}</span
|
||||||
|
>
|
||||||
|
<span class="text-sm font-mono dark:text-immich-dark-primary text-immich-primary pr-5">
|
||||||
|
{#if extra.checksum}
|
||||||
|
[sha1:{extra.checksum}]
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</UserPageLayout>
|
||||||
Loading…
Reference in New Issue