mirror of https://github.com/immich-app/immich.git
feat: nightly tasks (#19879)
parent
df581cc0d5
commit
47c0dc0d7e
@ -0,0 +1,139 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 SystemConfigNightlyTasksDto {
|
||||
/// Returns a new [SystemConfigNightlyTasksDto] instance.
|
||||
SystemConfigNightlyTasksDto({
|
||||
required this.clusterNewFaces,
|
||||
required this.databaseCleanup,
|
||||
required this.generateMemories,
|
||||
required this.missingThumbnails,
|
||||
required this.startTime,
|
||||
required this.syncQuotaUsage,
|
||||
});
|
||||
|
||||
bool clusterNewFaces;
|
||||
|
||||
bool databaseCleanup;
|
||||
|
||||
bool generateMemories;
|
||||
|
||||
bool missingThumbnails;
|
||||
|
||||
String startTime;
|
||||
|
||||
bool syncQuotaUsage;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigNightlyTasksDto &&
|
||||
other.clusterNewFaces == clusterNewFaces &&
|
||||
other.databaseCleanup == databaseCleanup &&
|
||||
other.generateMemories == generateMemories &&
|
||||
other.missingThumbnails == missingThumbnails &&
|
||||
other.startTime == startTime &&
|
||||
other.syncQuotaUsage == syncQuotaUsage;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(clusterNewFaces.hashCode) +
|
||||
(databaseCleanup.hashCode) +
|
||||
(generateMemories.hashCode) +
|
||||
(missingThumbnails.hashCode) +
|
||||
(startTime.hashCode) +
|
||||
(syncQuotaUsage.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigNightlyTasksDto[clusterNewFaces=$clusterNewFaces, databaseCleanup=$databaseCleanup, generateMemories=$generateMemories, missingThumbnails=$missingThumbnails, startTime=$startTime, syncQuotaUsage=$syncQuotaUsage]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'clusterNewFaces'] = this.clusterNewFaces;
|
||||
json[r'databaseCleanup'] = this.databaseCleanup;
|
||||
json[r'generateMemories'] = this.generateMemories;
|
||||
json[r'missingThumbnails'] = this.missingThumbnails;
|
||||
json[r'startTime'] = this.startTime;
|
||||
json[r'syncQuotaUsage'] = this.syncQuotaUsage;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SystemConfigNightlyTasksDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SystemConfigNightlyTasksDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SystemConfigNightlyTasksDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SystemConfigNightlyTasksDto(
|
||||
clusterNewFaces: mapValueOfType<bool>(json, r'clusterNewFaces')!,
|
||||
databaseCleanup: mapValueOfType<bool>(json, r'databaseCleanup')!,
|
||||
generateMemories: mapValueOfType<bool>(json, r'generateMemories')!,
|
||||
missingThumbnails: mapValueOfType<bool>(json, r'missingThumbnails')!,
|
||||
startTime: mapValueOfType<String>(json, r'startTime')!,
|
||||
syncQuotaUsage: mapValueOfType<bool>(json, r'syncQuotaUsage')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SystemConfigNightlyTasksDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SystemConfigNightlyTasksDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SystemConfigNightlyTasksDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SystemConfigNightlyTasksDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SystemConfigNightlyTasksDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SystemConfigNightlyTasksDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SystemConfigNightlyTasksDto-objects as value to a dart map
|
||||
static Map<String, List<SystemConfigNightlyTasksDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SystemConfigNightlyTasksDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SystemConfigNightlyTasksDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'clusterNewFaces',
|
||||
'databaseCleanup',
|
||||
'generateMemories',
|
||||
'missingThumbnails',
|
||||
'startTime',
|
||||
'syncQuotaUsage',
|
||||
};
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
import _ from 'lodash';
|
||||
import { defaults } from 'src/config';
|
||||
import { SystemConfigController } from 'src/controllers/system-config.controller';
|
||||
import { StorageTemplateService } from 'src/services/storage-template.service';
|
||||
import { SystemConfigService } from 'src/services/system-config.service';
|
||||
import request from 'supertest';
|
||||
import { errorDto } from 'test/medium/responses';
|
||||
import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
|
||||
|
||||
describe(SystemConfigController.name, () => {
|
||||
let ctx: ControllerContext;
|
||||
const systemConfigService = mockBaseService(SystemConfigService);
|
||||
const templateService = mockBaseService(StorageTemplateService);
|
||||
|
||||
beforeAll(async () => {
|
||||
ctx = await controllerSetup(SystemConfigController, [
|
||||
{ provide: SystemConfigService, useValue: systemConfigService },
|
||||
{ provide: StorageTemplateService, useValue: templateService },
|
||||
]);
|
||||
return () => ctx.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
systemConfigService.resetAllMocks();
|
||||
templateService.resetAllMocks();
|
||||
ctx.reset();
|
||||
});
|
||||
|
||||
describe('GET /system-config', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/system-config');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /system-config/defaults', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).get('/system-config/defaults');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /system-config', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).put('/system-config');
|
||||
expect(ctx.authenticate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('nightlyTasks', () => {
|
||||
it('should validate nightly jobs start time', async () => {
|
||||
const config = _.cloneDeep(defaults);
|
||||
config.nightlyTasks.startTime = 'invalid';
|
||||
const { status, body } = await request(ctx.getHttpServer()).put('/system-config').send(config);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['nightlyTasks.startTime must be in HH:mm format']));
|
||||
});
|
||||
|
||||
it('should accept a valid time', async () => {
|
||||
const config = _.cloneDeep(defaults);
|
||||
config.nightlyTasks.startTime = '05:05';
|
||||
const { status } = await request(ctx.getHttpServer()).put('/system-config').send(config);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
|
||||
it('should validate a boolean field', async () => {
|
||||
const config = _.cloneDeep(defaults);
|
||||
(config.nightlyTasks.databaseCleanup as any) = 'invalid';
|
||||
const { status, body } = await request(ctx.getHttpServer()).put('/system-config').send(config);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['nightlyTasks.databaseCleanup must be a boolean value']));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import type { SystemConfigDto } from '@immich/sdk';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { SettingsResetEvent, SettingsSaveEvent } from '../admin-settings';
|
||||
|
||||
interface Props {
|
||||
savedConfig: SystemConfigDto;
|
||||
defaultConfig: SystemConfigDto;
|
||||
config: SystemConfigDto;
|
||||
disabled?: boolean;
|
||||
onReset: SettingsResetEvent;
|
||||
onSave: SettingsSaveEvent;
|
||||
}
|
||||
|
||||
let { savedConfig, defaultConfig, config = $bindable(), disabled = false, onReset, onSave }: Props = $props();
|
||||
|
||||
const onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="mt-2">
|
||||
<div in:fade={{ duration: 500 }}>
|
||||
<form autocomplete="off" {onsubmit} class="mx-4 mt-4">
|
||||
<div class="ms-4 mt-4 flex flex-col gap-4">
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label={$t('admin.nightly_tasks_start_time_setting')}
|
||||
description={$t('admin.nightly_tasks_start_time_setting_description')}
|
||||
bind:value={config.nightlyTasks.startTime}
|
||||
required={true}
|
||||
{disabled}
|
||||
isEdited={!(config.nightlyTasks.startTime === savedConfig.nightlyTasks.startTime)}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_database_cleanup_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_database_cleanup_setting_description')}
|
||||
bind:checked={config.nightlyTasks.databaseCleanup}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_missing_thumbnails_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_missing_thumbnails_setting_description')}
|
||||
bind:checked={config.nightlyTasks.missingThumbnails}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_cluster_new_faces_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_cluster_faces_setting_description')}
|
||||
bind:checked={config.nightlyTasks.clusterNewFaces}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_generate_memories_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_generate_memories_setting_description')}
|
||||
bind:checked={config.nightlyTasks.generateMemories}
|
||||
{disabled}
|
||||
/>
|
||||
<SettingSwitch
|
||||
title={$t('admin.nightly_tasks_sync_quota_usage_setting')}
|
||||
subtitle={$t('admin.nightly_tasks_sync_quota_usage_setting_description')}
|
||||
bind:checked={config.nightlyTasks.syncQuotaUsage}
|
||||
{disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingButtonsRow
|
||||
onReset={(options) => onReset({ ...options, configKeys: ['nightlyTasks'] })}
|
||||
onSave={() => onSave({ nightlyTasks: config.nightlyTasks })}
|
||||
showResetToDefault={!isEqual(savedConfig.nightlyTasks, defaultConfig.nightlyTasks)}
|
||||
{disabled}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in New Issue