feat: magic link to enable backups

This commit is contained in:
izzy
2026-06-19 13:06:57 +01:00
parent 5ded6f89b7
commit d18e09de30
17 changed files with 225 additions and 35 deletions
+1 -1
View File
@@ -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:*",
@@ -109,6 +109,7 @@ describe('/server', () => {
configFile: false,
duplicateDetection: false,
facialRecognition: false,
backups: false,
map: true,
reverseGeocoding: true,
importFaces: false,
+1
View File
@@ -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,
+162 -2
View File
@@ -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": {
+4
View File
@@ -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 = {
+23 -23
View File
@@ -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
+4 -3
View File
@@ -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
+1 -1
View File
@@ -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",
+2
View File
@@ -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<SystemConfig>({
backup: {
beta: false,
database: {
enabled: true,
cronExpression: CronExpression.EVERY_DAY_AT_2AM,
+1
View File
@@ -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'),
+3 -1
View File
@@ -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({
@@ -137,6 +137,7 @@ describe(ServerService.name, () => {
duplicateDetection: true,
facialRecognition: true,
importFaces: false,
backups: false,
map: true,
reverseGeocoding: true,
oauth: false,
+2 -1
View File
@@ -86,7 +86,7 @@ export class ServerService extends BaseService {
}
async getFeatures(): Promise<ServerFeaturesDto> {
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,
@@ -46,6 +46,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
[QueueName.Editor]: { concurrency: 2 },
},
backup: {
beta: false,
database: {
enabled: true,
cronExpression: '0 02 * * *',
+1 -1
View File
@@ -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",
@@ -88,7 +88,9 @@
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
{/if}
<NavbarItem title="Backups" href={Route.backups()} icon={mdiBackupRestore} />
{#if featureFlagsManager.value.backups && authManager.user.isAdmin}
<NavbarItem title="Backups" href={Route.backups()} icon={mdiBackupRestore} />
{/if}
<NavbarItem title={$t('utilities')} href={Route.utilities()} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
+14 -1
View File
@@ -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) {