mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
settings for metrics
This commit is contained in:
parent
db997f9173
commit
902d4d0275
@ -3716,6 +3716,48 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/metrics": {
|
||||||
|
"put": {
|
||||||
|
"operationId": "getMetrics",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SystemConfigMetricsDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Metrics"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/oauth/authorize": {
|
"/oauth/authorize": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "startOAuth",
|
"operationId": "startOAuth",
|
||||||
@ -8061,6 +8103,48 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"MetricServerInfoConfig": {
|
||||||
|
"properties": {
|
||||||
|
"cpuCount": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"cpuModel": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"cpuCount",
|
||||||
|
"cpuModel",
|
||||||
|
"memory",
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"MetricsAssetCountConfig": {
|
||||||
|
"properties": {
|
||||||
|
"image": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"image",
|
||||||
|
"video",
|
||||||
|
"total"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ModelType": {
|
"ModelType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"facial-recognition",
|
"facial-recognition",
|
||||||
@ -8628,6 +8712,9 @@
|
|||||||
"map": {
|
"map": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"metrics": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -8655,6 +8742,7 @@
|
|||||||
"configFile",
|
"configFile",
|
||||||
"facialRecognition",
|
"facialRecognition",
|
||||||
"map",
|
"map",
|
||||||
|
"metrics",
|
||||||
"trash",
|
"trash",
|
||||||
"reverseGeocoding",
|
"reverseGeocoding",
|
||||||
"oauth",
|
"oauth",
|
||||||
@ -9027,6 +9115,9 @@
|
|||||||
"map": {
|
"map": {
|
||||||
"$ref": "#/components/schemas/SystemConfigMapDto"
|
"$ref": "#/components/schemas/SystemConfigMapDto"
|
||||||
},
|
},
|
||||||
|
"metrics": {
|
||||||
|
"$ref": "#/components/schemas/SystemConfigMetricsDto"
|
||||||
|
},
|
||||||
"newVersionCheck": {
|
"newVersionCheck": {
|
||||||
"$ref": "#/components/schemas/SystemConfigNewVersionCheckDto"
|
"$ref": "#/components/schemas/SystemConfigNewVersionCheckDto"
|
||||||
},
|
},
|
||||||
@ -9057,6 +9148,7 @@
|
|||||||
"logging",
|
"logging",
|
||||||
"machineLearning",
|
"machineLearning",
|
||||||
"map",
|
"map",
|
||||||
|
"metrics",
|
||||||
"newVersionCheck",
|
"newVersionCheck",
|
||||||
"oauth",
|
"oauth",
|
||||||
"passwordLogin",
|
"passwordLogin",
|
||||||
@ -9279,6 +9371,25 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SystemConfigMetricsDto": {
|
||||||
|
"properties": {
|
||||||
|
"assetCount": {
|
||||||
|
"$ref": "#/components/schemas/MetricsAssetCountConfig"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"serverInfo": {
|
||||||
|
"$ref": "#/components/schemas/MetricServerInfoConfig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"serverInfo",
|
||||||
|
"assetCount"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SystemConfigNewVersionCheckDto": {
|
"SystemConfigNewVersionCheckDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
|
@ -1,17 +1,59 @@
|
|||||||
export class MetricsServerInfoDto {
|
import { IsBoolean } from 'class-validator';
|
||||||
cpuCount?: number;
|
|
||||||
cpuModel?: string;
|
// TODO I feel like it must be possible to generate those from MetricsServerInfo and MetricsAssetCount
|
||||||
memoryCount?: number;
|
export class MetricServerInfoConfig {
|
||||||
version?: string;
|
@IsBoolean()
|
||||||
|
cpuCount!: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
cpuModel!: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
memory!: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
version!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetricsAssetCountDto {
|
export class MetricsAssetCountConfig {
|
||||||
image?: number;
|
@IsBoolean()
|
||||||
video?: number;
|
image!: boolean;
|
||||||
total?: number;
|
|
||||||
|
@IsBoolean()
|
||||||
|
video!: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
total!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetricsDto {
|
class MetricsServerInfo {
|
||||||
serverInfo!: MetricsServerInfoDto;
|
cpuCount!: number;
|
||||||
assetCount!: MetricsAssetCountDto;
|
cpuModel!: string;
|
||||||
|
memory!: number;
|
||||||
|
version!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetricsAssetCount {
|
||||||
|
image!: number;
|
||||||
|
video!: number;
|
||||||
|
total!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Metrics {
|
||||||
|
serverInfo: {
|
||||||
|
cpuCount: number;
|
||||||
|
cpuModel: string;
|
||||||
|
memory: number;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
assetCount: {
|
||||||
|
image: number;
|
||||||
|
video: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MetricsDto implements Metrics {
|
||||||
|
serverInfo!: MetricsServerInfo;
|
||||||
|
assetCount!: MetricsAssetCount;
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,71 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import _ from 'lodash';
|
||||||
import { serverVersion } from '../domain.constant';
|
import { serverVersion } from '../domain.constant';
|
||||||
import { JobName } from '../job';
|
import { JobName } from '../job';
|
||||||
|
import { ISystemConfigRepository } from '../repositories';
|
||||||
import { IJobRepository } from '../repositories/job.repository';
|
import { IJobRepository } from '../repositories/job.repository';
|
||||||
import { IMetricsRepository, SharedMetrics } from '../repositories/metrics.repository';
|
import { IMetricsRepository } from '../repositories/metrics.repository';
|
||||||
import { MetricsDto } from './metrics.dto';
|
import { FeatureFlag, SystemConfigCore, SystemConfigMetricsDto } from '../system-config';
|
||||||
|
import { Metrics, MetricsDto } from './metrics.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetricsService {
|
export class MetricsService {
|
||||||
|
private configCore: SystemConfigCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(IMetricsRepository) private repository: IMetricsRepository,
|
@Inject(IMetricsRepository) private repository: IMetricsRepository,
|
||||||
) {}
|
@Inject(ISystemConfigRepository) systemConfigRepository: ISystemConfigRepository,
|
||||||
|
) {
|
||||||
|
this.configCore = SystemConfigCore.create(systemConfigRepository);
|
||||||
|
}
|
||||||
|
|
||||||
async handleQueueMetrics() {
|
async handleQueueMetrics() {
|
||||||
// TODO config for what metrics should be fetched and if any at all
|
if (await this.configCore.hasFeature(FeatureFlag.METRICS)) {
|
||||||
|
await this.jobRepository.queue({ name: JobName.METRICS });
|
||||||
await this.jobRepository.queue({ name: JobName.METRICS, data: { assetCount: true, serverInfo: true } });
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMetrics(metrics: SharedMetrics) {
|
async handleSendMetrics() {
|
||||||
const metricsPayload = new MetricsDto();
|
const metricsConfig = await this.configCore.getConfig().then((config) => config.metrics);
|
||||||
if (metrics.serverInfo) {
|
const metrics = await this.getMetrics(metricsConfig);
|
||||||
metricsPayload.serverInfo.version = serverVersion.toString();
|
|
||||||
metricsPayload.serverInfo.cpuCount = this.repository.getCpuCount();
|
|
||||||
metricsPayload.serverInfo.cpuModel = this.repository.getCpuModel();
|
|
||||||
metricsPayload.serverInfo.memoryCount = this.repository.getMemoryCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metrics.assetCount) {
|
await this.repository.sendMetrics(metrics);
|
||||||
metricsPayload.assetCount.image = await this.repository.getImageCount();
|
|
||||||
metricsPayload.assetCount.video = await this.repository.getVideoCount();
|
|
||||||
metricsPayload.assetCount.total = await this.repository.getAssetCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.repository.sendMetrics(metricsPayload);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMetrics(config: SystemConfigMetricsDto) {
|
||||||
|
const metrics: Metrics = new MetricsDto();
|
||||||
|
|
||||||
|
metrics.serverInfo = {
|
||||||
|
cpuCount: this.repository.getCpuCount(),
|
||||||
|
cpuModel: this.repository.getCpuModel(),
|
||||||
|
memory: this.repository.getMemory(),
|
||||||
|
version: serverVersion.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
metrics.assetCount = {
|
||||||
|
image: await this.repository.getImageCount(),
|
||||||
|
video: await this.repository.getVideoCount(),
|
||||||
|
total: await this.repository.getAssetCount(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return _.pick(metrics, this.getKeys(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getKeys(config: SystemConfigMetricsDto) {
|
||||||
|
const result = [];
|
||||||
|
const keys = _.keys(config) as Array<keyof SystemConfigMetricsDto>;
|
||||||
|
for (const key of keys) {
|
||||||
|
const subConfig = _.get(config, key);
|
||||||
|
if (typeof subConfig === 'boolean') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = _.keys(_.pickBy(subConfig)).map((value) => `${key}.${value}`);
|
||||||
|
result.push(...keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
ILibraryRefreshJob,
|
ILibraryRefreshJob,
|
||||||
ISidecarWriteJob,
|
ISidecarWriteJob,
|
||||||
} from '../job/job.interface';
|
} from '../job/job.interface';
|
||||||
import { SharedMetrics } from './metrics.repository';
|
|
||||||
|
|
||||||
export interface JobCounts {
|
export interface JobCounts {
|
||||||
active: number;
|
active: number;
|
||||||
@ -93,7 +92,7 @@ export type JobItem =
|
|||||||
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
|
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
| { name: JobName.METRICS; data: SharedMetrics };
|
| { name: JobName.METRICS; data?: IBaseJob };
|
||||||
|
|
||||||
export type JobHandler<T = any> = (data: T) => boolean | Promise<boolean>;
|
export type JobHandler<T = any> = (data: T) => boolean | Promise<boolean>;
|
||||||
export type JobItemHandler = (item: JobItem) => Promise<void>;
|
export type JobItemHandler = (item: JobItem) => Promise<void>;
|
||||||
|
@ -2,17 +2,12 @@ import { MetricsDto } from '../metrics';
|
|||||||
|
|
||||||
export const IMetricsRepository = 'IMetricsRepository';
|
export const IMetricsRepository = 'IMetricsRepository';
|
||||||
|
|
||||||
export interface SharedMetrics {
|
|
||||||
serverInfo: boolean;
|
|
||||||
assetCount: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMetricsRepository {
|
export interface IMetricsRepository {
|
||||||
getAssetCount(): Promise<number>;
|
getAssetCount(): Promise<number>;
|
||||||
getCpuCount(): number;
|
getCpuCount(): number;
|
||||||
getCpuModel(): string;
|
getCpuModel(): string;
|
||||||
getMemoryCount(): number;
|
getMemory(): number;
|
||||||
getImageCount(): Promise<number>;
|
getImageCount(): Promise<number>;
|
||||||
getVideoCount(): Promise<number>;
|
getVideoCount(): Promise<number>;
|
||||||
sendMetrics(payload: MetricsDto): Promise<void>;
|
sendMetrics(payload: Partial<MetricsDto>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ export class ServerFeaturesDto implements FeatureFlags {
|
|||||||
configFile!: boolean;
|
configFile!: boolean;
|
||||||
facialRecognition!: boolean;
|
facialRecognition!: boolean;
|
||||||
map!: boolean;
|
map!: boolean;
|
||||||
|
metrics!: boolean;
|
||||||
trash!: boolean;
|
trash!: boolean;
|
||||||
reverseGeocoding!: boolean;
|
reverseGeocoding!: boolean;
|
||||||
oauth!: boolean;
|
oauth!: boolean;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export * from './system-config-ffmpeg.dto';
|
export * from './system-config-ffmpeg.dto';
|
||||||
export * from './system-config-library.dto';
|
export * from './system-config-library.dto';
|
||||||
|
export * from './system-config-metrics.dto';
|
||||||
export * from './system-config-oauth.dto';
|
export * from './system-config-oauth.dto';
|
||||||
export * from './system-config-password-login.dto';
|
export * from './system-config-password-login.dto';
|
||||||
export * from './system-config-storage-template.dto';
|
export * from './system-config-storage-template.dto';
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { MetricServerInfoConfig, MetricsAssetCountConfig } from '@app/domain';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsBoolean, IsObject, ValidateNested } from 'class-validator';
|
||||||
|
|
||||||
|
export class SystemConfigMetricsDto {
|
||||||
|
@IsBoolean()
|
||||||
|
enabled!: boolean;
|
||||||
|
|
||||||
|
@Type(() => MetricServerInfoConfig)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsObject()
|
||||||
|
serverInfo!: MetricServerInfoConfig;
|
||||||
|
|
||||||
|
@Type(() => MetricsAssetCountConfig)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsObject()
|
||||||
|
assetCount!: MetricsAssetCountConfig;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { SystemConfig } from '@app/infra/entities';
|
import { SystemConfig } from '@app/infra/entities';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsObject, ValidateNested } from 'class-validator';
|
import { IsObject, ValidateNested } from 'class-validator';
|
||||||
|
import { SystemConfigMetricsDto } from '.';
|
||||||
import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto';
|
import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto';
|
||||||
import { SystemConfigJobDto } from './system-config-job.dto';
|
import { SystemConfigJobDto } from './system-config-job.dto';
|
||||||
import { SystemConfigLibraryDto } from './system-config-library.dto';
|
import { SystemConfigLibraryDto } from './system-config-library.dto';
|
||||||
@ -37,6 +38,11 @@ export class SystemConfigDto implements SystemConfig {
|
|||||||
@IsObject()
|
@IsObject()
|
||||||
map!: SystemConfigMapDto;
|
map!: SystemConfigMapDto;
|
||||||
|
|
||||||
|
@Type(() => SystemConfigMetricsDto)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsObject()
|
||||||
|
metrics!: SystemConfigMetricsDto;
|
||||||
|
|
||||||
@Type(() => SystemConfigNewVersionCheckDto)
|
@Type(() => SystemConfigNewVersionCheckDto)
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
|
@ -82,6 +82,20 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
lightStyle: '',
|
lightStyle: '',
|
||||||
darkStyle: '',
|
darkStyle: '',
|
||||||
},
|
},
|
||||||
|
metrics: {
|
||||||
|
enabled: false,
|
||||||
|
serverInfo: {
|
||||||
|
cpuCount: true,
|
||||||
|
cpuModel: true,
|
||||||
|
memory: true,
|
||||||
|
version: true,
|
||||||
|
},
|
||||||
|
assetCount: {
|
||||||
|
image: true,
|
||||||
|
video: true,
|
||||||
|
total: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
reverseGeocoding: {
|
reverseGeocoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
@ -132,6 +146,7 @@ export enum FeatureFlag {
|
|||||||
CLIP_ENCODE = 'clipEncode',
|
CLIP_ENCODE = 'clipEncode',
|
||||||
FACIAL_RECOGNITION = 'facialRecognition',
|
FACIAL_RECOGNITION = 'facialRecognition',
|
||||||
MAP = 'map',
|
MAP = 'map',
|
||||||
|
METRICS = 'metrics',
|
||||||
REVERSE_GEOCODING = 'reverseGeocoding',
|
REVERSE_GEOCODING = 'reverseGeocoding',
|
||||||
SIDECAR = 'sidecar',
|
SIDECAR = 'sidecar',
|
||||||
SEARCH = 'search',
|
SEARCH = 'search',
|
||||||
@ -204,6 +219,7 @@ export class SystemConfigCore {
|
|||||||
[FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled,
|
[FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled,
|
||||||
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
|
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
|
||||||
[FeatureFlag.MAP]: config.map.enabled,
|
[FeatureFlag.MAP]: config.map.enabled,
|
||||||
|
[FeatureFlag.METRICS]: config.metrics.enabled,
|
||||||
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
|
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
|
||||||
[FeatureFlag.SIDECAR]: true,
|
[FeatureFlag.SIDECAR]: true,
|
||||||
[FeatureFlag.SEARCH]: true,
|
[FeatureFlag.SEARCH]: true,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
FaceController,
|
FaceController,
|
||||||
JobController,
|
JobController,
|
||||||
LibraryController,
|
LibraryController,
|
||||||
|
MetricsController,
|
||||||
OAuthController,
|
OAuthController,
|
||||||
PartnerController,
|
PartnerController,
|
||||||
PersonController,
|
PersonController,
|
||||||
@ -54,6 +55,7 @@ import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
|
|||||||
FaceController,
|
FaceController,
|
||||||
JobController,
|
JobController,
|
||||||
LibraryController,
|
LibraryController,
|
||||||
|
MetricsController,
|
||||||
OAuthController,
|
OAuthController,
|
||||||
PartnerController,
|
PartnerController,
|
||||||
SearchController,
|
SearchController,
|
||||||
|
@ -8,6 +8,7 @@ export * from './auth.controller';
|
|||||||
export * from './face.controller';
|
export * from './face.controller';
|
||||||
export * from './job.controller';
|
export * from './job.controller';
|
||||||
export * from './library.controller';
|
export * from './library.controller';
|
||||||
|
export * from './metrics.controller';
|
||||||
export * from './oauth.controller';
|
export * from './oauth.controller';
|
||||||
export * from './partner.controller';
|
export * from './partner.controller';
|
||||||
export * from './person.controller';
|
export * from './person.controller';
|
||||||
|
18
server/src/immich/controllers/metrics.controller.ts
Normal file
18
server/src/immich/controllers/metrics.controller.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Metrics, MetricsService, SystemConfigMetricsDto } from '@app/domain';
|
||||||
|
import { Body, Controller, Put } from '@nestjs/common';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { Authenticated } from '../app.guard';
|
||||||
|
import { UseValidation } from '../app.utils';
|
||||||
|
|
||||||
|
@ApiTags('Metrics')
|
||||||
|
@Controller('metrics')
|
||||||
|
@Authenticated()
|
||||||
|
@UseValidation()
|
||||||
|
export class MetricsController {
|
||||||
|
constructor(private service: MetricsService) {}
|
||||||
|
|
||||||
|
@Put()
|
||||||
|
getMetrics(@Body() dto: SystemConfigMetricsDto): Promise<Partial<Metrics>> {
|
||||||
|
return this.service.getMetrics(dto);
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,15 @@ export enum SystemConfigKey {
|
|||||||
MAP_LIGHT_STYLE = 'map.lightStyle',
|
MAP_LIGHT_STYLE = 'map.lightStyle',
|
||||||
MAP_DARK_STYLE = 'map.darkStyle',
|
MAP_DARK_STYLE = 'map.darkStyle',
|
||||||
|
|
||||||
|
METRICS_ENABLED = 'metrics.enabled',
|
||||||
|
METRICS_SERVER_INFO_CPU_COUNT = 'metrics.serverInfo.cpuCount',
|
||||||
|
METRICS_SERVER_INFO_CPU_MODEL = 'metrics.serverInfo.cpuModel',
|
||||||
|
METRICS_SERVER_INFO_MEMORY = 'metrics.serverInfo.memory',
|
||||||
|
METRICS_SERVER_INFO_VERSION = 'metrics.serverInfo.version',
|
||||||
|
METRICS_ASSET_COUNT_IMAGE = 'metrics.assetCount.image',
|
||||||
|
METRICS_ASSET_COUNT_VIDEO = 'metrics.assetCount.video',
|
||||||
|
METRICS_ASSET_COUNT_TOTAL = 'metrics.assetCount.total',
|
||||||
|
|
||||||
REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled',
|
REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled',
|
||||||
|
|
||||||
NEW_VERSION_CHECK_ENABLED = 'newVersionCheck.enabled',
|
NEW_VERSION_CHECK_ENABLED = 'newVersionCheck.enabled',
|
||||||
@ -196,6 +205,20 @@ export interface SystemConfig {
|
|||||||
lightStyle: string;
|
lightStyle: string;
|
||||||
darkStyle: string;
|
darkStyle: string;
|
||||||
};
|
};
|
||||||
|
metrics: {
|
||||||
|
enabled: boolean;
|
||||||
|
serverInfo: {
|
||||||
|
cpuCount: boolean;
|
||||||
|
cpuModel: boolean;
|
||||||
|
memory: boolean;
|
||||||
|
version: boolean;
|
||||||
|
};
|
||||||
|
assetCount: {
|
||||||
|
image: boolean;
|
||||||
|
video: boolean;
|
||||||
|
total: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
reverseGeocoding: {
|
reverseGeocoding: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ import { AssetEntity, AssetType } from '../entities';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetricsRepository implements IMetricsRepository {
|
export class MetricsRepository implements IMetricsRepository {
|
||||||
constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
|
constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
|
||||||
async sendMetrics(payload: MetricsDto): Promise<void> {
|
async sendMetrics(payload: Partial<MetricsDto>): Promise<void> {
|
||||||
await axios.post('IMMICH-DATA-DOMAIN', payload);
|
await axios.post('IMMICH-DATA-DOMAIN', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export class MetricsRepository implements IMetricsRepository {
|
|||||||
return os.cpus()[0].model;
|
return os.cpus()[0].model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMemoryCount() {
|
getMemory() {
|
||||||
return os.totalmem();
|
return os.totalmem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export class AppService {
|
|||||||
[JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data),
|
[JobName.VIDEO_CONVERSION]: (data) => this.mediaService.handleVideoConversion(data),
|
||||||
[JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data),
|
[JobName.QUEUE_METADATA_EXTRACTION]: (data) => this.metadataService.handleQueueMetadataExtraction(data),
|
||||||
[JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data),
|
[JobName.METADATA_EXTRACTION]: (data) => this.metadataService.handleMetadataExtraction(data),
|
||||||
[JobName.METRICS]: (data) => this.metricsService.handleMetrics(data),
|
[JobName.METRICS]: () => this.metricsService.handleSendMetrics(),
|
||||||
[JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data),
|
[JobName.LINK_LIVE_PHOTOS]: (data) => this.metadataService.handleLivePhotoLinking(data),
|
||||||
[JobName.QUEUE_RECOGNIZE_FACES]: (data) => this.personService.handleQueueRecognizeFaces(data),
|
[JobName.QUEUE_RECOGNIZE_FACES]: (data) => this.personService.handleQueueRecognizeFaces(data),
|
||||||
[JobName.RECOGNIZE_FACES]: (data) => this.personService.handleRecognizeFaces(data),
|
[JobName.RECOGNIZE_FACES]: (data) => this.personService.handleRecognizeFaces(data),
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { api, SystemConfigMetricsDto } from '@api';
|
||||||
|
import { 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 type { ResetOptions } from '$lib/utils/dipatch';
|
||||||
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
|
|
||||||
|
export let config: SystemConfigMetricsDto; // this is the config that is being edited
|
||||||
|
export let disabled = false;
|
||||||
|
|
||||||
|
let savedConfig: SystemConfigMetricsDto;
|
||||||
|
let defaultConfig: SystemConfigMetricsDto;
|
||||||
|
|
||||||
|
$: sharedMetrics = getSharedMetrics(config);
|
||||||
|
|
||||||
|
const handleReset = (detail: ResetOptions) => {
|
||||||
|
if (detail.default) {
|
||||||
|
resetToDefault();
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function refreshConfig() {
|
||||||
|
[savedConfig, defaultConfig] = await Promise.all([
|
||||||
|
api.systemConfigApi.getConfig().then((res) => res.data.metrics),
|
||||||
|
api.systemConfigApi.getConfigDefaults().then((res) => res.data.metrics),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSetting() {
|
||||||
|
try {
|
||||||
|
const { data: current } = await api.systemConfigApi.getConfig();
|
||||||
|
const { data: updated } = await api.systemConfigApi.updateConfig({
|
||||||
|
systemConfigDto: {
|
||||||
|
...current,
|
||||||
|
metrics: {
|
||||||
|
enabled: config.enabled,
|
||||||
|
serverInfo: config.serverInfo,
|
||||||
|
assetCount: config.assetCount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
config = { ...updated.metrics };
|
||||||
|
savedConfig = { ...updated.metrics };
|
||||||
|
|
||||||
|
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to save settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reset() {
|
||||||
|
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||||
|
|
||||||
|
config = { ...resetConfig.metrics };
|
||||||
|
savedConfig = { ...resetConfig.metrics };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset settings to the recent saved settings',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetToDefault() {
|
||||||
|
const { data: configs } = await api.systemConfigApi.getConfigDefaults();
|
||||||
|
|
||||||
|
config = { ...configs.metrics };
|
||||||
|
defaultConfig = { ...configs.metrics };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset map settings to default',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSharedMetrics(systemConfigMetricsDto: SystemConfigMetricsDto) {
|
||||||
|
return api.metricsApi.getMetrics({ systemConfigMetricsDto }).then((response) => response.data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<SettingSwitch title="ENABLED" {disabled} subtitle="Enable sharing metrics" bind:checked={config.enabled} />
|
||||||
|
|
||||||
|
<SettingAccordion title="Server Info Metrics" subtitle="Manage which server infos the instance should share">
|
||||||
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
<SettingSwitch
|
||||||
|
title="CPU Count"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.serverInfo.cpuCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSwitch
|
||||||
|
title="CPU Model"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.serverInfo.cpuModel}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSwitch
|
||||||
|
title="Memory"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.serverInfo.memory}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSwitch
|
||||||
|
title="Version"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.serverInfo.version}
|
||||||
|
/>
|
||||||
|
</div></SettingAccordion
|
||||||
|
>
|
||||||
|
|
||||||
|
<SettingAccordion title="Asset Count Metrics" subtitle="Manage which asset counts the instance should share">
|
||||||
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
<SettingSwitch
|
||||||
|
title="Image Count"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.assetCount.image}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSwitch
|
||||||
|
title="Video Count"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.assetCount.video}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSwitch
|
||||||
|
title="Total Assets Count"
|
||||||
|
disabled={disabled || !config.enabled}
|
||||||
|
bind:checked={config.assetCount.total}
|
||||||
|
/>
|
||||||
|
</div></SettingAccordion
|
||||||
|
>
|
||||||
|
|
||||||
|
{#if config.enabled}
|
||||||
|
{#await sharedMetrics}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:then metrics}
|
||||||
|
<div class="mt-2 rounded-lg bg-gray-200 p-4 text-xs dark:bg-gray-700 dark:text-immich-dark-fg">
|
||||||
|
<pre><code>{JSON.stringify(metrics, null, 2)}</code></pre>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<SettingButtonsRow
|
||||||
|
on:reset={({ detail }) => handleReset(detail)}
|
||||||
|
on:save={saveSetting}
|
||||||
|
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
|
||||||
|
{disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
@ -4,6 +4,7 @@
|
|||||||
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
|
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
|
||||||
import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte';
|
import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte';
|
||||||
import MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte';
|
import MapSettings from '$lib/components/admin-page/settings/map-settings/map-settings.svelte';
|
||||||
|
import MetricsSettings from '$lib/components/admin-page/settings/metrics-settings/metrics-settings.svelte';
|
||||||
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
|
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
|
||||||
import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
|
import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
|
||||||
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
|
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
|
||||||
@ -87,6 +88,13 @@
|
|||||||
<MapSettings disabled={$featureFlags.configFile} config={configs} />
|
<MapSettings disabled={$featureFlags.configFile} config={configs} />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion
|
||||||
|
title="Metrics Settings"
|
||||||
|
subtitle="Manage which - if any - metrics you want to share with Immich"
|
||||||
|
>
|
||||||
|
<MetricsSettings disabled={$featureFlags.configFile} config={configs.metrics} />
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
|
<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
|
||||||
<OAuthSettings disabled={$featureFlags.configFile} oauthConfig={configs.oauth} />
|
<OAuthSettings disabled={$featureFlags.configFile} oauthConfig={configs.oauth} />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user