1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

feat(server, web)!: Move reverse geocoding settings to the UI (#4222)

* feat: reverse geocoding settings

* chore: open api

* re-init geocoder if precision has been updated

* update docs

* chore: update verbiage

* fix: re-init logic

* fix: reset to default

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler 2023-09-26 09:03:57 +02:00 committed by GitHub
parent 7bc6e9ef64
commit 9bada51d56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 331 additions and 77 deletions

View File

@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto {
*/
'existingIds': Array<string>;
}
/**
*
* @export
* @enum {string}
*/
export const CitiesFile = {
Cities15000: 'cities15000',
Cities5000: 'cities5000',
Cities1000: 'cities1000',
Cities500: 'cities500'
} as const;
export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile];
/**
*
* @export
@ -2650,6 +2666,12 @@ export interface ServerFeaturesDto {
* @memberof ServerFeaturesDto
*/
'passwordLogin': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'reverseGeocoding': boolean;
/**
*
* @type {boolean}
@ -3093,6 +3115,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto
*/
'passwordLogin': SystemConfigPasswordLoginDto;
/**
*
* @type {SystemConfigReverseGeocodingDto}
* @memberof SystemConfigDto
*/
'reverseGeocoding': SystemConfigReverseGeocodingDto;
/**
*
* @type {SystemConfigStorageTemplateDto}
@ -3438,6 +3466,27 @@ export interface SystemConfigPasswordLoginDto {
*/
'enabled': boolean;
}
/**
*
* @export
* @interface SystemConfigReverseGeocodingDto
*/
export interface SystemConfigReverseGeocodingDto {
/**
*
* @type {CitiesFile}
* @memberof SystemConfigReverseGeocodingDto
*/
'citiesFileOverride': CitiesFile;
/**
*
* @type {boolean}
* @memberof SystemConfigReverseGeocodingDto
*/
'enabled': boolean;
}
/**
*
* @export

View File

@ -50,9 +50,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## Geocoding
| Variable | Description | Default | Services |
| :--------------------------------- | :---------------------------------- | :--------------------------: | :------------ |
| `DISABLE_REVERSE_GEOCODING` | Disable Reverse Geocoding Precision | `false` | microservices |
| `REVERSE_GEOCODING_PRECISION` | Reverse Geocoding Precision | `3` | microservices |
| :--------------------------------- | :------------------------------- | :--------------------------: | :------------ |
| `REVERSE_GEOCODING_DUMP_DIRECTORY` | Reverse Geocoding Dump Directory | `./.reverse-geocoding-dump/` | microservices |
## Ports

View File

@ -43,6 +43,7 @@ doc/CheckDuplicateAssetDto.md
doc/CheckDuplicateAssetResponseDto.md
doc/CheckExistingAssetsDto.md
doc/CheckExistingAssetsResponseDto.md
doc/CitiesFile.md
doc/ClassificationConfig.md
doc/Colorspace.md
doc/CreateAlbumDto.md
@ -126,6 +127,7 @@ doc/SystemConfigMachineLearningDto.md
doc/SystemConfigMapDto.md
doc/SystemConfigOAuthDto.md
doc/SystemConfigPasswordLoginDto.md
doc/SystemConfigReverseGeocodingDto.md
doc/SystemConfigStorageTemplateDto.md
doc/SystemConfigTemplateStorageOptionDto.md
doc/SystemConfigThumbnailDto.md
@ -207,6 +209,7 @@ lib/model/check_duplicate_asset_dto.dart
lib/model/check_duplicate_asset_response_dto.dart
lib/model/check_existing_assets_dto.dart
lib/model/check_existing_assets_response_dto.dart
lib/model/cities_file.dart
lib/model/classification_config.dart
lib/model/clip_config.dart
lib/model/clip_mode.dart
@ -284,6 +287,7 @@ lib/model/system_config_machine_learning_dto.dart
lib/model/system_config_map_dto.dart
lib/model/system_config_o_auth_dto.dart
lib/model/system_config_password_login_dto.dart
lib/model/system_config_reverse_geocoding_dto.dart
lib/model/system_config_storage_template_dto.dart
lib/model/system_config_template_storage_option_dto.dart
lib/model/system_config_thumbnail_dto.dart
@ -343,6 +347,7 @@ test/check_duplicate_asset_dto_test.dart
test/check_duplicate_asset_response_dto_test.dart
test/check_existing_assets_dto_test.dart
test/check_existing_assets_response_dto_test.dart
test/cities_file_test.dart
test/classification_config_test.dart
test/clip_config_test.dart
test/clip_mode_test.dart
@ -429,6 +434,7 @@ test/system_config_machine_learning_dto_test.dart
test/system_config_map_dto_test.dart
test/system_config_o_auth_dto_test.dart
test/system_config_password_login_dto_test.dart
test/system_config_reverse_geocoding_dto_test.dart
test/system_config_storage_template_dto_test.dart
test/system_config_template_storage_option_dto_test.dart
test/system_config_thumbnail_dto_test.dart

BIN
mobile/openapi/README.md generated

Binary file not shown.

BIN
mobile/openapi/doc/CitiesFile.md generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/lib/model/cities_file.dart generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/test/cities_file_test.dart generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5914,6 +5914,15 @@
],
"type": "object"
},
"CitiesFile": {
"enum": [
"cities15000",
"cities5000",
"cities1000",
"cities500"
],
"type": "string"
},
"ClassificationConfig": {
"properties": {
"enabled": {
@ -7229,6 +7238,9 @@
"passwordLogin": {
"type": "boolean"
},
"reverseGeocoding": {
"type": "boolean"
},
"search": {
"type": "boolean"
},
@ -7244,6 +7256,7 @@
"configFile",
"facialRecognition",
"map",
"reverseGeocoding",
"oauth",
"oauthAutoLaunch",
"passwordLogin",
@ -7590,6 +7603,9 @@
"passwordLogin": {
"$ref": "#/components/schemas/SystemConfigPasswordLoginDto"
},
"reverseGeocoding": {
"$ref": "#/components/schemas/SystemConfigReverseGeocodingDto"
},
"storageTemplate": {
"$ref": "#/components/schemas/SystemConfigStorageTemplateDto"
},
@ -7603,6 +7619,7 @@
"map",
"oauth",
"passwordLogin",
"reverseGeocoding",
"storageTemplate",
"job",
"thumbnail"
@ -7843,6 +7860,21 @@
],
"type": "object"
},
"SystemConfigReverseGeocodingDto": {
"properties": {
"citiesFileOverride": {
"$ref": "#/components/schemas/CitiesFile"
},
"enabled": {
"type": "boolean"
}
},
"required": [
"citiesFileOverride",
"enabled"
],
"type": "object"
},
"SystemConfigStorageTemplateDto": {
"properties": {
"template": {

View File

@ -1,3 +1,5 @@
import { InitOptions } from 'local-reverse-geocoder';
export const IGeocodingRepository = 'IGeocodingRepository';
export interface GeoPoint {
@ -12,7 +14,7 @@ export interface ReverseGeocodeResult {
}
export interface IGeocodingRepository {
init(): Promise<void>;
init(options: Partial<InitOptions>): Promise<void>;
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
deleteCache(): Promise<void>;
}

View File

@ -90,6 +90,7 @@ export class ServerFeaturesDto implements FeatureFlags {
configFile!: boolean;
facialRecognition!: boolean;
map!: boolean;
reverseGeocoding!: boolean;
oauth!: boolean;
oauthAutoLaunch!: boolean;
passwordLogin!: boolean;

View File

@ -151,6 +151,7 @@ describe(ServerInfoService.name, () => {
clipEncode: true,
facialRecognition: true,
map: true,
reverseGeocoding: true,
oauth: false,
oauthAutoLaunch: false,
passwordLogin: true,

View File

@ -0,0 +1,12 @@
import { CitiesFile } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsEnum } from 'class-validator';
export class SystemConfigReverseGeocodingDto {
@IsBoolean()
enabled!: boolean;
@IsEnum(CitiesFile)
@ApiProperty({ enum: CitiesFile, enumName: 'CitiesFile' })
citiesFileOverride!: CitiesFile;
}

View File

@ -8,6 +8,7 @@ import { SystemConfigMachineLearningDto } from './system-config-machine-learning
import { SystemConfigMapDto } from './system-config-map.dto';
import { SystemConfigOAuthDto } from './system-config-oauth.dto';
import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto';
import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto';
import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto';
export class SystemConfigDto implements SystemConfig {
@ -36,6 +37,11 @@ export class SystemConfigDto implements SystemConfig {
@IsObject()
passwordLogin!: SystemConfigPasswordLoginDto;
@Type(() => SystemConfigReverseGeocodingDto)
@ValidateNested()
@IsObject()
reverseGeocoding!: SystemConfigReverseGeocodingDto;
@Type(() => SystemConfigStorageTemplateDto)
@ValidateNested()
@IsObject()

View File

@ -1,6 +1,7 @@
import {
AudioCodec,
CQMode,
CitiesFile,
Colorspace,
SystemConfig,
SystemConfigEntity,
@ -81,6 +82,10 @@ export const defaults = Object.freeze<SystemConfig>({
enabled: true,
tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
},
reverseGeocoding: {
enabled: true,
citiesFileOverride: CitiesFile.CITIES_500,
},
oauth: {
enabled: false,
issuerUrl: '',
@ -115,6 +120,7 @@ export enum FeatureFlag {
FACIAL_RECOGNITION = 'facialRecognition',
TAG_IMAGE = 'tagImage',
MAP = 'map',
REVERSE_GEOCODING = 'reverseGeocoding',
SIDECAR = 'sidecar',
SEARCH = 'search',
OAUTH = 'oauth',
@ -177,6 +183,7 @@ export class SystemConfigCore {
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
[FeatureFlag.TAG_IMAGE]: mlEnabled && config.machineLearning.classification.enabled,
[FeatureFlag.MAP]: config.map.enabled,
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
[FeatureFlag.SIDECAR]: true,
[FeatureFlag.SEARCH]: process.env.TYPESENSE_ENABLED !== 'false',

View File

@ -1,6 +1,7 @@
import {
AudioCodec,
CQMode,
CitiesFile,
Colorspace,
SystemConfig,
SystemConfigEntity,
@ -80,6 +81,10 @@ const updatedConfig = Object.freeze<SystemConfig>({
enabled: true,
tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
},
reverseGeocoding: {
enabled: true,
citiesFileOverride: CitiesFile.CITIES_500,
},
oauth: {
autoLaunch: true,
autoRegister: true,

View File

@ -64,6 +64,9 @@ export enum SystemConfigKey {
MAP_ENABLED = 'map.enabled',
MAP_TILE_URL = 'map.tileUrl',
REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled',
REVERSE_GEOCODING_CITIES_FILE_OVERRIDE = 'reverseGeocoding.citiesFileOverride',
OAUTH_ENABLED = 'oauth.enabled',
OAUTH_ISSUER_URL = 'oauth.issuerUrl',
OAUTH_CLIENT_ID = 'oauth.clientId',
@ -130,6 +133,13 @@ export enum Colorspace {
P3 = 'p3',
}
export enum CitiesFile {
CITIES_15000 = 'cities15000',
CITIES_5000 = 'cities5000',
CITIES_1000 = 'cities1000',
CITIES_500 = 'cities500',
}
export interface SystemConfig {
ffmpeg: {
crf: number;
@ -175,6 +185,10 @@ export interface SystemConfig {
enabled: boolean;
tileUrl: string;
};
reverseGeocoding: {
enabled: boolean;
citiesFileOverride: CitiesFile;
};
oauth: {
enabled: boolean;
issuerUrl: string;

View File

@ -2,7 +2,6 @@ import { QueueName } from '@app/domain';
import { RegisterQueueOptions } from '@nestjs/bullmq';
import { QueueOptions } from 'bullmq';
import { RedisOptions } from 'ioredis';
import { InitOptions } from 'local-reverse-geocoder';
import { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration';
function parseRedisConfig(): RedisOptions {
@ -72,20 +71,5 @@ function parseTypeSenseConfig(): ConfigurationOptions {
export const typesenseConfig: ConfigurationOptions = parseTypeSenseConfig();
function parseLocalGeocodingConfig(): InitOptions {
const precision = Number(process.env.REVERSE_GEOCODING_PRECISION);
return {
citiesFileOverride: precision ? ['cities15000', 'cities5000', 'cities1000', 'cities500'][precision] : undefined,
load: {
admin1: true,
admin2: true,
admin3And4: false,
alternateNames: false,
},
countries: [],
dumpDirectory: process.env.REVERSE_GEOCODING_DUMP_DIRECTORY || process.cwd() + '/.reverse-geocoding-dump/',
};
}
export const localGeocodingConfig: InitOptions = parseLocalGeocodingConfig();
export const REVERSE_GEOCODING_DUMP_DIRECTORY =
process.env.REVERSE_GEOCODING_DUMP_DIRECTORY || process.cwd() + '/.reverse-geocoding-dump/';

View File

@ -1,9 +1,9 @@
import { GeoPoint, IGeocodingRepository, ReverseGeocodeResult } from '@app/domain';
import { localGeocodingConfig } from '@app/infra';
import { REVERSE_GEOCODING_DUMP_DIRECTORY } from '@app/infra';
import { Injectable, Logger } from '@nestjs/common';
import { readdir, rm } from 'fs/promises';
import { getName } from 'i18n-iso-countries';
import geocoder, { AddressObject } from 'local-reverse-geocoder';
import geocoder, { AddressObject, InitOptions } from 'local-reverse-geocoder';
import path from 'path';
import { promisify } from 'util';
@ -18,19 +18,33 @@ export type GeoData = AddressObject & {
admin2Code?: AdminCode | string;
};
const init = (): Promise<void> => new Promise<void>((resolve) => geocoder.init(localGeocodingConfig, resolve));
const lookup = promisify<GeoPoint[], number, AddressObject[][]>(geocoder.lookUp).bind(geocoder);
@Injectable()
export class GeocodingRepository implements IGeocodingRepository {
private logger = new Logger(GeocodingRepository.name);
async init(): Promise<void> {
await init();
async init(options: Partial<InitOptions>): Promise<void> {
return new Promise<void>((resolve) => {
geocoder.init(
{
load: {
admin1: true,
admin2: true,
admin3And4: false,
alternateNames: false,
},
countries: [],
dumpDirectory: REVERSE_GEOCODING_DUMP_DIRECTORY,
...options,
},
resolve,
);
});
}
async deleteCache() {
const dumpDirectory = localGeocodingConfig.dumpDirectory;
const dumpDirectory = REVERSE_GEOCODING_DUMP_DIRECTORY;
if (dumpDirectory) {
// delete contents
const items = await readdir(dumpDirectory, { withFileTypes: true });

View File

@ -1,4 +1,5 @@
import {
FeatureFlag,
IAlbumRepository,
IAssetRepository,
IBaseJob,
@ -7,17 +8,18 @@ import {
IGeocodingRepository,
IJobRepository,
IStorageRepository,
ISystemConfigRepository,
JobName,
JOBS_ASSET_PAGINATION_SIZE,
QueueName,
StorageCore,
StorageFolder,
SystemConfigCore,
usePagination,
WithoutProperty,
} from '@app/domain';
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
import { Inject, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DefaultReadTaskOptions, ExifDateTime, exiftool, ReadTaskOptions, Tags } from 'exiftool-vendored';
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
import * as geotz from 'geo-tz';
@ -51,8 +53,9 @@ const validate = <T>(value: T): T | null => (typeof value === 'string' ? null :
export class MetadataExtractionProcessor {
private logger = new Logger(MetadataExtractionProcessor.name);
private reverseGeocodingEnabled: boolean;
private storageCore: StorageCore;
private configCore: SystemConfigCore;
private oldCities?: string;
constructor(
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
@ -61,31 +64,35 @@ export class MetadataExtractionProcessor {
@Inject(IGeocodingRepository) private geocodingRepository: IGeocodingRepository,
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
configService: ConfigService,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
) {
this.reverseGeocodingEnabled = !configService.get('DISABLE_REVERSE_GEOCODING');
this.storageCore = new StorageCore(storageRepository);
this.configCore = new SystemConfigCore(configRepository);
this.configCore.config$.subscribe(() => this.init());
}
async init(deleteCache = false) {
this.logger.log(`Reverse geocoding is ${this.reverseGeocodingEnabled ? 'enabled' : 'disabled'}`);
if (!this.reverseGeocodingEnabled) {
const { reverseGeocoding } = await this.configCore.getConfig();
const { citiesFileOverride } = reverseGeocoding;
if (!reverseGeocoding.enabled) {
return;
}
try {
if (deleteCache) {
await this.geocodingRepository.deleteCache();
} else if (this.oldCities && this.oldCities === citiesFileOverride) {
return;
}
this.logger.log('Initializing Reverse Geocoding');
await this.jobRepository.pause(QueueName.METADATA_EXTRACTION);
await this.geocodingRepository.init();
await this.geocodingRepository.init({ citiesFileOverride });
await this.jobRepository.resume(QueueName.METADATA_EXTRACTION);
this.logger.log('Reverse Geocoding Initialized');
} catch (error: any) {
this.logger.log(`Initialized local reverse geocoder with ${citiesFileOverride}`);
this.oldCities = citiesFileOverride;
} catch (error: Error | any) {
this.logger.error(`Unable to initialize reverse geocoding: ${error}`, error?.stack);
}
}
@ -161,7 +168,7 @@ export class MetadataExtractionProcessor {
private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntity) {
const { latitude, longitude } = exifData;
if (!this.reverseGeocodingEnabled || !longitude || !latitude) {
if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) {
return;
}

View File

@ -85,6 +85,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
configFile: false,
facialRecognition: true,
map: true,
reverseGeocoding: true,
oauth: false,
oauthAutoLaunch: false,
passwordLogin: true,

View File

@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto {
*/
'existingIds': Array<string>;
}
/**
*
* @export
* @enum {string}
*/
export const CitiesFile = {
Cities15000: 'cities15000',
Cities5000: 'cities5000',
Cities1000: 'cities1000',
Cities500: 'cities500'
} as const;
export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile];
/**
*
* @export
@ -2650,6 +2666,12 @@ export interface ServerFeaturesDto {
* @memberof ServerFeaturesDto
*/
'passwordLogin': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'reverseGeocoding': boolean;
/**
*
* @type {boolean}
@ -3093,6 +3115,12 @@ export interface SystemConfigDto {
* @memberof SystemConfigDto
*/
'passwordLogin': SystemConfigPasswordLoginDto;
/**
*
* @type {SystemConfigReverseGeocodingDto}
* @memberof SystemConfigDto
*/
'reverseGeocoding': SystemConfigReverseGeocodingDto;
/**
*
* @type {SystemConfigStorageTemplateDto}
@ -3438,6 +3466,27 @@ export interface SystemConfigPasswordLoginDto {
*/
'enabled': boolean;
}
/**
*
* @export
* @interface SystemConfigReverseGeocodingDto
*/
export interface SystemConfigReverseGeocodingDto {
/**
*
* @type {CitiesFile}
* @memberof SystemConfigReverseGeocodingDto
*/
'citiesFileOverride': CitiesFile;
/**
*
* @type {boolean}
* @memberof SystemConfigReverseGeocodingDto
*/
'enabled': boolean;
}
/**
*
* @export

View File

@ -4,23 +4,25 @@
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigMapDto } from '@api';
import { isEqual } from 'lodash-es';
import { api, CitiesFile, SystemConfigDto } from '@api';
import { cloneDeep, isEqual } from 'lodash-es';
import { fade } from 'svelte/transition';
import SettingAccordion from '../setting-accordion.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingSelect from '../setting-select.svelte';
export let mapConfig: SystemConfigMapDto; // this is the config that is being edited
export let config: SystemConfigDto; // this is the config that is being edited
export let disabled = false;
let savedConfig: SystemConfigMapDto;
let defaultConfig: SystemConfigMapDto;
let savedConfig: SystemConfigDto;
let defaultConfig: SystemConfigDto;
async function getConfigs() {
async function refreshConfig() {
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.map),
api.systemConfigApi.getDefaults().then((res) => res.data.map),
api.systemConfigApi.getConfig().then((res) => res.data),
api.systemConfigApi.getDefaults().then((res) => res.data),
]);
}
@ -28,11 +30,21 @@
try {
const { data: current } = await api.systemConfigApi.getConfig();
const { data: updated } = await api.systemConfigApi.updateConfig({
systemConfigDto: { ...current, map: mapConfig },
systemConfigDto: {
...current,
map: {
enabled: config.map.enabled,
tileUrl: config.map.tileUrl,
},
reverseGeocoding: {
enabled: config.reverseGeocoding.enabled,
citiesFileOverride: config.reverseGeocoding.citiesFileOverride,
},
},
});
mapConfig = { ...updated.map };
savedConfig = { ...updated.map };
config = cloneDeep(updated);
savedConfig = cloneDeep(updated);
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
} catch (error) {
@ -43,8 +55,8 @@
async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
mapConfig = { ...resetConfig.map };
savedConfig = { ...resetConfig.map };
config = cloneDeep(resetConfig);
savedConfig = cloneDeep(resetConfig);
notificationController.show({
message: 'Reset settings to the recent saved settings',
@ -55,8 +67,8 @@
async function resetToDefault() {
const { data: configs } = await api.systemConfigApi.getDefaults();
mapConfig = { ...configs.map };
defaultConfig = { ...configs.map };
config = cloneDeep(configs);
defaultConfig = cloneDeep(configs);
notificationController.show({
message: 'Reset map settings to default',
@ -65,12 +77,19 @@
}
</script>
<div>
{#await getConfigs() then}
<div class="mt-2">
{#await refreshConfig() then}
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4">
<SettingAccordion title="Map Settings" subtitle="Manage map settings">
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch title="ENABLED" {disabled} subtitle="Enable map features" bind:checked={mapConfig.enabled} />
<SettingSwitch
title="ENABLED"
{disabled}
subtitle="Enable map features"
bind:checked={config.map.enabled}
/>
<hr />
@ -78,17 +97,61 @@
inputType={SettingInputFieldType.TEXT}
label="Tile URL"
desc="URL to a leaflet compatible tile server"
bind:value={mapConfig.tileUrl}
bind:value={config.map.tileUrl}
required={true}
disabled={disabled || !mapConfig.enabled}
isEdited={mapConfig.tileUrl !== savedConfig.tileUrl}
disabled={disabled || !config.map.enabled}
isEdited={config.map.tileUrl !== savedConfig.map.tileUrl}
/>
</div></SettingAccordion
>
<SettingAccordion title="Reverse Geocoding Settings">
<svelte:fragment slot="subtitle">
<p class="text-sm dark:text-immich-dark-fg">
Manage <a
href="https://immich.app/docs/features/reverse-geocoding"
class="underline"
target="_blank"
rel="noreferrer">Reverse Geocoding</a
> settings
</p>
</svelte:fragment>
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
{disabled}
subtitle="Enable reverse geocoding"
bind:checked={config.reverseGeocoding.enabled}
/>
<hr />
<SettingSelect
label="Precision"
desc="Set reverse geocoding precision"
name="reverse-geocoding-precision"
bind:value={config.reverseGeocoding.citiesFileOverride}
options={[
{ value: CitiesFile.Cities500, text: 'Cities with more than 500 people' },
{ value: CitiesFile.Cities1000, text: 'Cities with more than 1000 people' },
{ value: CitiesFile.Cities5000, text: 'Cities with more than 5000 people' },
{ value: CitiesFile.Cities15000, text: 'Cities with more than 15000 people' },
]}
disabled={disabled || !config.reverseGeocoding.enabled}
isEdited={config.reverseGeocoding.citiesFileOverride !==
savedConfig.reverseGeocoding.citiesFileOverride}
/>
</div></SettingAccordion
>
<SettingButtonsRow
on:reset={reset}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
showResetToDefault={!isEqual(
{ ...savedConfig.map, ...savedConfig.reverseGeocoding },
{ ...defaultConfig.map, ...defaultConfig.reverseGeocoding },
)}
{disabled}
/>
</div>

View File

@ -14,7 +14,9 @@
{title}
</h2>
<slot name="subtitle">
<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
</slot>
</div>
<button

View File

@ -10,6 +10,7 @@ export const featureFlags = writable<FeatureFlags>({
sidecar: true,
tagImage: true,
map: true,
reverseGeocoding: true,
search: true,
oauth: false,
oauthAutoLaunch: false,

View File

@ -67,12 +67,12 @@
<JobSettings disabled={$featureFlags.configFile} jobConfig={configs.job} />
</SettingAccordion>
<SettingAccordion title="Machine Learning Settings" subtitle="Manage model settings">
<SettingAccordion title="Machine Learning Settings" subtitle="Manage machine learning features and settings">
<MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} />
</SettingAccordion>
<SettingAccordion title="Map Settings" subtitle="Manage map settings">
<MapSettings disabled={$featureFlags.configFile} mapConfig={configs.map} />
<SettingAccordion title="Map & GPS Settings" subtitle="Manage map related features and setting">
<MapSettings disabled={$featureFlags.configFile} config={configs} />
</SettingAccordion>
<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">