diff --git a/e2e/package.json b/e2e/package.json index 2cc8d0de72..084f3778a5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@eslint/js": "^10.0.0", "@faker-js/faker": "^10.1.0", - "@futo-org/backups-orchestrator-ui": "0.3.1", + "@futo-org/backups-orchestrator-ui": "0.4.0", "@immich/cli": "workspace:*", "@immich/e2e-auth-server": "workspace:*", "@immich/sdk": "workspace:*", diff --git a/e2e/src/specs/server/api/server.e2e-spec.ts b/e2e/src/specs/server/api/server.e2e-spec.ts index 46d998511d..6e45d0ad3f 100644 --- a/e2e/src/specs/server/api/server.e2e-spec.ts +++ b/e2e/src/specs/server/api/server.e2e-spec.ts @@ -109,6 +109,7 @@ describe('/server', () => { configFile: false, duplicateDetection: false, facialRecognition: false, + backups: false, map: true, reverseGeocoding: true, importFaces: false, diff --git a/e2e/src/ui/mock-network/base-network.ts b/e2e/src/ui/mock-network/base-network.ts index 6680b83dd1..9c221fb985 100644 --- a/e2e/src/ui/mock-network/base-network.ts +++ b/e2e/src/ui/mock-network/base-network.ts @@ -118,6 +118,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI smartSearch: false, facialRecognition: false, duplicateDetection: false, + backups: false, map: true, reverseGeocoding: true, importFaces: false, diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 74b9e02d14..c87f22449d 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -16484,6 +16484,20 @@ ] } }, + "/yucca/onboarding/report-error": { + "post": { + "operationId": "reportError", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "Onboarding" + ] + } + }, "/yucca/onboarding/skip": { "post": { "operationId": "skipOnboardingExtraConfig", @@ -16498,6 +16512,20 @@ ] } }, + "/yucca/onboarding/telemetry": { + "post": { + "operationId": "enableTelemetry", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "Onboarding" + ] + } + }, "/yucca/repository": { "get": { "operationId": "getRepositories", @@ -16560,7 +16588,16 @@ "/yucca/repository/inspect": { "get": { "operationId": "inspectRepositories", - "parameters": [], + "parameters": [ + { + "name": "backend", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { @@ -16675,6 +16712,46 @@ ] } }, + "/yucca/repository/{id}/backend": { + "put": { + "operationId": "reconfigureRepositoryPrimaryBackend", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryPrimaryBackendReconfigureRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryCreateResponseDto" + } + } + }, + "description": "" + } + }, + "tags": [ + "Repository" + ] + } + }, "/yucca/repository/{id}/import": { "get": { "operationId": "checkImportRepository", @@ -19170,6 +19247,9 @@ }, "BackendDto": { "properties": { + "description": { + "type": "string" + }, "error": { "type": "string" }, @@ -19188,6 +19268,7 @@ } }, "required": [ + "description", "id", "isOnline", "type" @@ -19227,6 +19308,14 @@ ], "type": "object" }, + "BootstrapStatus": { + "enum": [ + "not-ready", + "ready", + "error" + ], + "type": "string" + }, "BulkIdErrorReason": { "description": "Error reason", "enum": [ @@ -20444,6 +20533,9 @@ "id": { "type": "string" }, + "meter": { + "$ref": "#/components/schemas/RepositoryMeterDto" + }, "metrics": { "$ref": "#/components/schemas/RepositoryMetricsDto" }, @@ -20803,6 +20895,9 @@ "id": { "type": "string" }, + "meter": { + "$ref": "#/components/schemas/RepositoryMeterDto" + }, "metrics": { "$ref": "#/components/schemas/RepositoryMetricsDto" }, @@ -22018,6 +22113,9 @@ }, "OnboardingStatusResponseDto": { "properties": { + "error": { + "type": "string" + }, "hasBackend": { "type": "boolean" }, @@ -22032,6 +22130,20 @@ }, "hasSkippedExtraConfig": { "type": "boolean" + }, + "hasTelemetry": { + "allOf": [ + { + "$ref": "#/components/schemas/TelemetryLevel" + } + ] + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/BootstrapStatus" + } + ] } }, "required": [ @@ -22039,7 +22151,9 @@ "hasBackup", "hasOnboardedKey", "hasSchedule", - "hasSkippedExtraConfig" + "hasSkippedExtraConfig", + "hasTelemetry", + "status" ], "type": "object" }, @@ -23610,6 +23724,24 @@ ], "type": "object" }, + "RepositoryMeterDto": { + "properties": { + "lastUpdated": { + "type": "string" + }, + "objectCount": { + "type": "number" + }, + "sizeBytes": { + "type": "number" + } + }, + "required": [ + "objectCount", + "sizeBytes" + ], + "type": "object" + }, "RepositoryMetricsDto": { "properties": { "lastBackup": { @@ -23630,6 +23762,17 @@ ], "type": "object" }, + "RepositoryPrimaryBackendReconfigureRequestDto": { + "properties": { + "backendId": { + "type": "string" + } + }, + "required": [ + "backendId" + ], + "type": "object" + }, "RepositorySnapshotRestoreFromPointRequestDto": { "properties": { "include": { @@ -24383,6 +24526,10 @@ }, "ServerFeaturesDto": { "properties": { + "backups": { + "description": "Whether the backups feature is enabled", + "type": "boolean" + }, "configFile": { "description": "Whether config file is available", "type": "boolean" @@ -24449,6 +24596,7 @@ } }, "required": [ + "backups", "configFile", "duplicateDetection", "email", @@ -27467,11 +27615,16 @@ }, "SystemConfigBackupsDto": { "properties": { + "beta": { + "description": "Whether the backups feature is enabled", + "type": "boolean" + }, "database": { "$ref": "#/components/schemas/DatabaseBackupConfig" } }, "required": [ + "beta", "database" ], "type": "object" @@ -28728,6 +28881,13 @@ ], "type": "string" }, + "TelemetryLevel": { + "enum": [ + "full", + "none" + ], + "type": "string" + }, "TemplateDto": { "properties": { "template": { diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index ae75bf80a0..6bc6a09ea7 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -2009,6 +2009,8 @@ export type ServerConfigDto = { userDeleteDelay: number; }; export type ServerFeaturesDto = { + /** Whether the backups feature is enabled */ + backups: boolean; /** Whether config file is available */ configFile: boolean; /** Whether duplicate detection is enabled */ @@ -2287,6 +2289,8 @@ export type DatabaseBackupConfig = { keepLastAmount: number; }; export type SystemConfigBackupsDto = { + /** Whether the backups feature is in beta */ + beta: boolean; database: DatabaseBackupConfig; }; export type SystemConfigFFmpegRealtimeDto = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65a4d02824..97dde7132d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,8 +118,8 @@ importers: specifier: ^10.1.0 version: 10.4.0 '@futo-org/backups-orchestrator-ui': - specifier: 0.3.1 - version: 0.3.1(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + specifier: 0.4.0 + version: 0.4.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) '@immich/cli': specifier: workspace:* version: link:../packages/cli @@ -378,8 +378,8 @@ importers: specifier: 2.0.0-rc13 version: 2.0.0-rc13 '@futo-org/backups-orchestrator-api': - specifier: 0.3.1 - version: 0.3.1(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(@nestjs/schedule@6.1.3(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27))(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2) + specifier: 0.4.0 + version: 0.4.0(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(@nestjs/schedule@6.1.3(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27))(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2) '@immich/plugin-sdk': specifier: workspace:* version: link:../packages/plugin-sdk @@ -769,8 +769,8 @@ importers: specifier: ^3.0.0 version: 3.5.11 '@futo-org/backups-orchestrator-ui': - specifier: 0.3.1 - version: 0.3.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + specifier: 0.4.0 + version: 0.4.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) '@immich/justified-layout-wasm': specifier: ^0.4.3 version: 0.4.3 @@ -3030,11 +3030,11 @@ packages: resolution: {integrity: sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==} engines: {node: '>=6'} - '@futo-org/backups-api-client@0.3.1': - resolution: {integrity: sha512-CO2kDerCTRk3fSEwYFR3HWU3A6SGzuu8IM4RI8OgGp/7TI4fGCi4wb5c3KjbZKtSHvUmN4Wy1WPD1GNWv30OIA==} + '@futo-org/backups-api-client@0.4.0': + resolution: {integrity: sha512-OaVhTSsesMOxzk1/I+pVh5Nbm/QzfjY1RDU6HZgNw5PWvaizq2Wv3o9UHzBn2vyH5+8LMs2xKreC+jGwEs6NJQ==} - '@futo-org/backups-orchestrator-api@0.3.1': - resolution: {integrity: sha512-7AGByA1OshaaXHg50MxxnLtlJn8BZnOdpR43clG2DqJxMRps84KMAQlolT+lyL3l6RgT+0KU6Xc8FWd9Qlxr4A==} + '@futo-org/backups-orchestrator-api@0.4.0': + resolution: {integrity: sha512-IC4R0bxEA3YmJGfm3oj5KOb6XzfC8iEd5VPQ+SWlFrOdyrF/WHa0sTxTYa9ddMn1BtKkl4cUnig21hmj3ub/KQ==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3042,13 +3042,13 @@ packages: '@nestjs/schedule': ^6.0.0 '@nestjs/websockets': ^11.0.0 - '@futo-org/backups-orchestrator-ui@0.3.1': - resolution: {integrity: sha512-5IO4EF6w5q7gkBQarDUYRqKWXuEVOxjzcPtVBUzg52PAVB+iEtPYKiTpBqEOWDcM1ooAMQcbqtkzD/D2LSVVgw==} + '@futo-org/backups-orchestrator-ui@0.4.0': + resolution: {integrity: sha512-OimxOvUWwmUpf8xGZRHoK1yWb35oPJHv0gD4u28fnL34nrqdoY8ZeUThqmnn6pT9hqJ3xsoa/qopMONWxkdA7g==} peerDependencies: svelte: ^5.0.0 - '@futo-org/restic-wrapper@1.2.1': - resolution: {integrity: sha512-shMx5f0/RTTYTvJ9Zryq7InWcmHm01kpIXy6wli/RIOx1Jjcx2j/Ab32WWhpYRTMxGhrKlOEmmen0qinMHFSNg==} + '@futo-org/restic-wrapper@1.3.0': + resolution: {integrity: sha512-RIKM2lhQIHpCdF3vTjf86UwGPfCkAvJ9vUquhn93WZaBeHwfVPuAQdiz6SuS4RDvAmyYgEkedREhfHvpEi4/dQ==} engines: {node: '>=20'} '@golevelup/nestjs-discovery@5.0.0': @@ -15958,14 +15958,14 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 7.2.0 - '@futo-org/backups-api-client@0.3.1': + '@futo-org/backups-api-client@0.4.0': dependencies: '@oazapfts/runtime': 1.2.0 - '@futo-org/backups-orchestrator-api@0.3.1(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(@nestjs/schedule@6.1.3(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27))(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)': + '@futo-org/backups-orchestrator-api@0.4.0(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@nestjs/platform-socket.io@11.1.27)(@nestjs/schedule@6.1.3(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27))(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)': dependencies: - '@futo-org/backups-api-client': 0.3.1 - '@futo-org/restic-wrapper': 1.2.1 + '@futo-org/backups-api-client': 0.4.0 + '@futo-org/restic-wrapper': 1.3.0 '@nestjs/common': 11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.27(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.27)(@nestjs/websockets@11.1.27)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/event-emitter': 3.1.0(@nestjs/common@11.1.27(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27) @@ -15991,9 +15991,9 @@ snapshots: - supports-color - utf-8-validate - '@futo-org/backups-orchestrator-ui@0.3.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@futo-org/backups-orchestrator-ui@0.4.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))': dependencies: - '@futo-org/backups-api-client': 0.3.1 + '@futo-org/backups-api-client': 0.4.0 '@immich/ui': 0.59.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) '@mdi/js': 7.4.47 '@oazapfts/runtime': 1.2.0 @@ -16010,9 +16010,9 @@ snapshots: - supports-color - utf-8-validate - '@futo-org/backups-orchestrator-ui@0.3.1(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@futo-org/backups-orchestrator-ui@0.4.0(svelte@5.56.2(@typescript-eslint/types@8.61.0))': dependencies: - '@futo-org/backups-api-client': 0.3.1 + '@futo-org/backups-api-client': 0.4.0 '@immich/ui': 0.59.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) '@mdi/js': 7.4.47 '@oazapfts/runtime': 1.2.0 @@ -16029,7 +16029,7 @@ snapshots: - supports-color - utf-8-validate - '@futo-org/restic-wrapper@1.2.1': + '@futo-org/restic-wrapper@1.3.0': dependencies: zod: 4.3.6 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2c1368e156..3298258e4d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -31,10 +31,11 @@ allowBuilds: dedupePeerDependents: false injectWorkspacePackages: true minimumReleaseAgeExclude: - - '@futo-org/backups-api-client@0.3.1' - - '@futo-org/backups-orchestrator-ui@0.3.1' - - '@futo-org/backups-orchestrator-api@0.3.1' - '@immich/ui@0.81.1' + - '@futo-org/backups-api-client@0.4.0' + - '@futo-org/backups-orchestrator-ui@0.4.0' + - '@futo-org/backups-orchestrator-api@0.4.0' + - '@futo-org/restic-wrapper@1.3.0' overrides: canvas: 3.2.3 sharp: ^0.34.5 diff --git a/server/package.json b/server/package.json index 301c4acc5b..e648a86311 100644 --- a/server/package.json +++ b/server/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@extism/extism": "2.0.0-rc13", - "@futo-org/backups-orchestrator-api": "0.3.1", + "@futo-org/backups-orchestrator-api": "0.4.0", "@immich/plugin-sdk": "workspace:*", "@immich/sql-tools": "^0.5.1", "@nestjs/bullmq": "^11.0.1", diff --git a/server/src/config.ts b/server/src/config.ts index 723e7e564e..05374841eb 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -18,6 +18,7 @@ import { ConcurrentQueueName, FullsizeImageOptions, ImageOptions } from 'src/typ export type SystemConfig = { backup: { + beta: boolean; database: { enabled: boolean; cronExpression: string; @@ -217,6 +218,7 @@ export type MachineLearningConfig = SystemConfig['machineLearning']; export const defaults = Object.freeze({ backup: { + beta: false, database: { enabled: true, cronExpression: CronExpression.EVERY_DAY_AT_2AM, diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 0215558aab..1eeba0daea 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -135,6 +135,7 @@ const ServerFeaturesSchema = z configFile: z.boolean().describe('Whether config file is available'), facialRecognition: z.boolean().describe('Whether facial recognition is enabled'), map: z.boolean().describe('Whether map feature is enabled'), + backups: z.boolean().describe('Whether the backups feature is enabled'), trash: z.boolean().describe('Whether trash feature is enabled'), reverseGeocoding: z.boolean().describe('Whether reverse geocoding is enabled'), importFaces: z.boolean().describe('Whether face import is enabled'), diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 7e1efdc862..8a25525482 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -88,7 +88,9 @@ const SystemConfigIntegrityChecksSchema = z .describe('Integrity checks config') .meta({ id: 'SystemConfigIntegrityChecks' }); -const SystemConfigBackupsSchema = z.object({ database: DatabaseBackupSchema }).meta({ id: 'SystemConfigBackupsDto' }); +const SystemConfigBackupsSchema = z + .object({ beta: configBool.describe('Whether the backups feature is enabled'), database: DatabaseBackupSchema }) + .meta({ id: 'SystemConfigBackupsDto' }); const SystemConfigFFmpegSchema = z .object({ diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index e1575a496a..0b78fbfda3 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -137,6 +137,7 @@ describe(ServerService.name, () => { duplicateDetection: true, facialRecognition: true, importFaces: false, + backups: false, map: true, reverseGeocoding: true, oauth: false, diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index 3b66b677a5..032b13282b 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -86,7 +86,7 @@ export class ServerService extends BaseService { } async getFeatures(): Promise { - const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications, ffmpeg } = + const { reverseGeocoding, metadata, map, backup, machineLearning, trash, oauth, passwordLogin, notifications, ffmpeg } = await this.getConfig({ withCache: false }); const { configFile } = this.configRepository.getEnv(); @@ -95,6 +95,7 @@ export class ServerService extends BaseService { facialRecognition: isFacialRecognitionEnabled(machineLearning), duplicateDetection: isDuplicateDetectionEnabled(machineLearning), map: map.enabled, + backups: backup.beta, reverseGeocoding: reverseGeocoding.enabled, importFaces: metadata.faces.import, sidecar: true, diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index 920b9b9346..a2084f7526 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -46,6 +46,7 @@ const updatedConfig = Object.freeze({ [QueueName.Editor]: { concurrency: 2 }, }, backup: { + beta: false, database: { enabled: true, cronExpression: '0 02 * * *', diff --git a/web/package.json b/web/package.json index daf4868b16..3269f0d118 100644 --- a/web/package.json +++ b/web/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@formatjs/icu-messageformat-parser": "^3.0.0", - "@futo-org/backups-orchestrator-ui": "0.3.1", + "@futo-org/backups-orchestrator-ui": "0.4.0", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "workspace:*", "@immich/ui": "^0.81.1", diff --git a/web/src/lib/components/shared-components/side-bar/UserSidebar.svelte b/web/src/lib/components/shared-components/side-bar/UserSidebar.svelte index 4138051e70..82e2295969 100644 --- a/web/src/lib/components/shared-components/side-bar/UserSidebar.svelte +++ b/web/src/lib/components/shared-components/side-bar/UserSidebar.svelte @@ -88,7 +88,9 @@ {/if} - + {#if featureFlagsManager.value.backups && authManager.user.isAdmin} + + {/if} diff --git a/web/src/routes/link/+page.ts b/web/src/routes/link/+page.ts index 4c113b2e5c..1ba07dc9f2 100644 --- a/web/src/routes/link/+page.ts +++ b/web/src/routes/link/+page.ts @@ -1,3 +1,4 @@ +import { getConfig, updateConfig } from '@immich/sdk'; import { redirect } from '@sveltejs/kit'; import { OpenQueryParam } from '$lib/constants'; import { Route } from '$lib/route'; @@ -8,9 +9,10 @@ enum LinkTarget { UNSUBSCRIBE = 'unsubscribe', VIEW_ASSET = 'view_asset', ACTIVATE_LICENSE = 'activate_license', + BACKUPS = 'backups', } -export const load = (({ url }) => { +export const load = (async ({ url }) => { const queryParams = url.searchParams; const target = queryParams.get('target') as LinkTarget; switch (target) { @@ -22,6 +24,17 @@ export const load = (({ url }) => { return redirect(307, Route.userSettings({ isOpen: OpenQueryParam.NOTIFICATIONS })); } + case LinkTarget.BACKUPS: { + const config = await getConfig().catch(() => undefined); + if (config && !config.backup.beta) { + await updateConfig({ + systemConfigDto: { ...config, backup: { ...config.backup, beta: true } }, + }).catch(() => undefined); + } + + return redirect(307, Route.backups()); + } + case LinkTarget.VIEW_ASSET: { const id = queryParams.get('id'); if (id) {