mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-12-08 20:29:05 +00:00
feat(server) user-defined storage structure (#1098)
[Breaking] newly uploaded file will conform to the default structure of `{uploadLocation}/{userId}/year/year-month-day/filename.ext`
This commit is contained in:
106
web/package-lock.json
generated
106
web/package-lock.json
generated
@@ -12,9 +12,11 @@
|
||||
"cookie": "^0.4.2",
|
||||
"copy-image-clipboard": "^2.1.2",
|
||||
"exifr": "^7.1.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.1.1",
|
||||
"socket.io-client": "^4.5.1",
|
||||
"svelte-keydown": "^0.5.0",
|
||||
"svelte-material-icons": "^2.0.2"
|
||||
@@ -34,6 +36,7 @@
|
||||
"@types/leaflet": "^1.7.10",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/luxon": "^3.1.0",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
@@ -3319,6 +3322,12 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz",
|
||||
@@ -6149,6 +6158,26 @@
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.7",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.0",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@@ -8976,6 +9005,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.1.tgz",
|
||||
"integrity": "sha512-Ah6DloGmvseB/pX1cAmjbFvyU/pKuwQMQqz7d0yvuDlVYLTs2WeDHQMpC8tGjm1da+BriHROW/OEIT/KfYg6xw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
@@ -9114,7 +9151,6 @@
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
|
||||
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -9178,6 +9214,11 @@
|
||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -10280,7 +10321,6 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -10835,6 +10875,18 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.13.0.tgz",
|
||||
@@ -11163,6 +11215,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -13726,6 +13783,12 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/luxon": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz",
|
||||
@@ -15703,6 +15766,18 @@
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.7.7",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.0",
|
||||
"source-map": "^0.6.1",
|
||||
"uglify-js": "^3.1.4",
|
||||
"wordwrap": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@@ -17789,6 +17864,11 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"luxon": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.1.tgz",
|
||||
"integrity": "sha512-Ah6DloGmvseB/pX1cAmjbFvyU/pKuwQMQqz7d0yvuDlVYLTs2WeDHQMpC8tGjm1da+BriHROW/OEIT/KfYg6xw=="
|
||||
},
|
||||
"lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
@@ -17887,8 +17967,7 @@
|
||||
"minimist": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
|
||||
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.6",
|
||||
@@ -17934,6 +18013,11 @@
|
||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
||||
"dev": true
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -18708,8 +18792,7 @@
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
@@ -19092,6 +19175,12 @@
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
|
||||
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.13.0.tgz",
|
||||
@@ -19304,6 +19393,11 @@
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@types/leaflet": "^1.7.10",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/luxon": "^3.1.0",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
@@ -62,9 +63,11 @@
|
||||
"cookie": "^0.4.2",
|
||||
"copy-image-clipboard": "^2.1.2",
|
||||
"exifr": "^7.1.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.1.1",
|
||||
"socket.io-client": "^4.5.1",
|
||||
"svelte-keydown": "^0.5.0",
|
||||
"svelte-material-icons": "^2.0.2"
|
||||
|
||||
130
web/src/api/open-api/api.ts
generated
130
web/src/api/open-api/api.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.38.0
|
||||
* The version of the OpenAPI document: 1.38.2
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
@@ -1443,6 +1443,12 @@ export interface SystemConfigDto {
|
||||
* @memberof SystemConfigDto
|
||||
*/
|
||||
'oauth': SystemConfigOAuthDto;
|
||||
/**
|
||||
*
|
||||
* @type {SystemConfigStorageTemplateDto}
|
||||
* @memberof SystemConfigDto
|
||||
*/
|
||||
'storageTemplate': SystemConfigStorageTemplateDto;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -1530,6 +1536,68 @@ export interface SystemConfigOAuthDto {
|
||||
*/
|
||||
'autoRegister': boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SystemConfigStorageTemplateDto
|
||||
*/
|
||||
export interface SystemConfigStorageTemplateDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SystemConfigStorageTemplateDto
|
||||
*/
|
||||
'template': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
export interface SystemConfigTemplateStorageOptionDto {
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'yearOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'monthOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'dayOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'hourOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'minuteOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'secondOptions': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SystemConfigTemplateStorageOptionDto
|
||||
*/
|
||||
'presetOptions': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -5312,6 +5380,39 @@ export const SystemConfigApiAxiosParamCreator = function (configuration?: Config
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getStorageTemplateOptions: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/system-config/storage-template-options`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@@ -5388,6 +5489,15 @@ export const SystemConfigApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getDefaults(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getStorageTemplateOptions(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SystemConfigTemplateStorageOptionDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getStorageTemplateOptions(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SystemConfigDto} systemConfigDto
|
||||
@@ -5424,6 +5534,14 @@ export const SystemConfigApiFactory = function (configuration?: Configuration, b
|
||||
getDefaults(options?: any): AxiosPromise<SystemConfigDto> {
|
||||
return localVarFp.getDefaults(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getStorageTemplateOptions(options?: any): AxiosPromise<SystemConfigTemplateStorageOptionDto> {
|
||||
return localVarFp.getStorageTemplateOptions(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SystemConfigDto} systemConfigDto
|
||||
@@ -5463,6 +5581,16 @@ export class SystemConfigApi extends BaseAPI {
|
||||
return SystemConfigApiFp(this.configuration).getDefaults(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof SystemConfigApi
|
||||
*/
|
||||
public getStorageTemplateOptions(options?: AxiosRequestConfig) {
|
||||
return SystemConfigApiFp(this.configuration).getStorageTemplateOptions(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SystemConfigDto} systemConfigDto
|
||||
|
||||
2
web/src/api/open-api/base.ts
generated
2
web/src/api/open-api/base.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.38.0
|
||||
* The version of the OpenAPI document: 1.38.2
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/common.ts
generated
2
web/src/api/open-api/common.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.38.0
|
||||
* The version of the OpenAPI document: 1.38.2
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/configuration.ts
generated
2
web/src/api/open-api/configuration.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.38.0
|
||||
* The version of the OpenAPI document: 1.38.2
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/index.ts
generated
2
web/src/api/open-api/index.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.38.0
|
||||
* The version of the OpenAPI document: 1.38.2
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
@@ -59,11 +59,11 @@ input:focus-visible {
|
||||
|
||||
@layer utilities {
|
||||
.immich-form-input {
|
||||
@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg disabled:bg-gray-500 dark:disabled:bg-gray-900 disabled:cursor-not-allowed;
|
||||
@apply bg-slate-200 p-2 rounded-lg focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg disabled:bg-gray-400 dark:disabled:bg-gray-800 disabled:cursor-not-allowed disabled:text-gray-200;
|
||||
}
|
||||
|
||||
.immich-form-label {
|
||||
@apply font-medium text-sm text-gray-500 dark:text-gray-300;
|
||||
@apply font-medium text-gray-500 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.immich-btn-primary {
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
const { data: configs } = await api.systemConfigApi.getConfig();
|
||||
|
||||
const result = await api.systemConfigApi.updateConfig({
|
||||
ffmpeg: ffmpegConfig,
|
||||
oauth: configs.oauth
|
||||
...configs,
|
||||
ffmpeg: ffmpegConfig
|
||||
});
|
||||
|
||||
ffmpegConfig = result.data.ffmpeg;
|
||||
savedConfig = result.data.ffmpeg;
|
||||
ffmpegConfig = { ...result.data.ffmpeg };
|
||||
savedConfig = { ...result.data.ffmpeg };
|
||||
|
||||
notificationController.show({
|
||||
message: 'FFmpeg settings saved',
|
||||
@@ -48,8 +48,8 @@
|
||||
async function reset() {
|
||||
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||
|
||||
ffmpegConfig = resetConfig.ffmpeg;
|
||||
savedConfig = resetConfig.ffmpeg;
|
||||
ffmpegConfig = { ...resetConfig.ffmpeg };
|
||||
savedConfig = { ...resetConfig.ffmpeg };
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset FFmpeg settings to the recent saved settings',
|
||||
@@ -60,8 +60,8 @@
|
||||
async function resetToDefault() {
|
||||
const { data: configs } = await api.systemConfigApi.getDefaults();
|
||||
|
||||
ffmpegConfig = configs.ffmpeg;
|
||||
defaultConfig = configs.ffmpeg;
|
||||
ffmpegConfig = { ...configs.ffmpeg };
|
||||
defaultConfig = { ...configs.ffmpeg };
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset FFmpeg settings to default',
|
||||
@@ -74,52 +74,56 @@
|
||||
{#await getConfigs() then}
|
||||
<div in:fade={{ duration: 500 }}>
|
||||
<form autocomplete="off" on:submit|preventDefault>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="CRF"
|
||||
bind:value={ffmpegConfig.crf}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.crf == savedConfig.crf)}
|
||||
/>
|
||||
<div class="flex flex-col gap-4 ml-4 mt-4">
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="CRF"
|
||||
bind:value={ffmpegConfig.crf}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.crf == savedConfig.crf)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="PRESET"
|
||||
bind:value={ffmpegConfig.preset}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.preset == savedConfig.preset)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="PRESET"
|
||||
bind:value={ffmpegConfig.preset}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.preset == savedConfig.preset)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="AUDIO CODEC"
|
||||
bind:value={ffmpegConfig.targetAudioCodec}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetAudioCodec == savedConfig.targetAudioCodec)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="AUDIO CODEC"
|
||||
bind:value={ffmpegConfig.targetAudioCodec}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetAudioCodec == savedConfig.targetAudioCodec)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="VIDEO CODEC"
|
||||
bind:value={ffmpegConfig.targetVideoCodec}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="VIDEO CODEC"
|
||||
bind:value={ffmpegConfig.targetVideoCodec}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="SCALING"
|
||||
bind:value={ffmpegConfig.targetScaling}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetScaling == savedConfig.targetScaling)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="SCALING"
|
||||
bind:value={ffmpegConfig.targetScaling}
|
||||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetScaling == savedConfig.targetScaling)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)}
|
||||
/>
|
||||
<div class="ml-4">
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
async function reset() {
|
||||
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||
|
||||
oauthConfig = resetConfig.oauth;
|
||||
savedConfig = resetConfig.oauth;
|
||||
oauthConfig = { ...resetConfig.oauth };
|
||||
savedConfig = { ...resetConfig.oauth };
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset OAuth settings to the last saved settings',
|
||||
@@ -39,12 +39,12 @@
|
||||
const { data: currentConfig } = await api.systemConfigApi.getConfig();
|
||||
|
||||
const result = await api.systemConfigApi.updateConfig({
|
||||
ffmpeg: currentConfig.ffmpeg,
|
||||
...currentConfig,
|
||||
oauth: oauthConfig
|
||||
});
|
||||
|
||||
oauthConfig = result.data.oauth;
|
||||
savedConfig = result.data.oauth;
|
||||
oauthConfig = { ...result.data.oauth };
|
||||
savedConfig = { ...result.data.oauth };
|
||||
|
||||
notificationController.show({
|
||||
message: 'OAuth settings saved',
|
||||
@@ -62,7 +62,7 @@
|
||||
async function resetToDefault() {
|
||||
const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
|
||||
|
||||
oauthConfig = defaultConfig.oauth;
|
||||
oauthConfig = { ...defaultConfig.oauth };
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset OAuth settings to default',
|
||||
@@ -80,51 +80,52 @@
|
||||
</div>
|
||||
|
||||
<hr class="m-4" />
|
||||
<div class="flex flex-col gap-4 ml-4">
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="ISSUER URL"
|
||||
bind:value={oauthConfig.issuerUrl}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="ISSUER URL"
|
||||
bind:value={oauthConfig.issuerUrl}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.issuerUrl == savedConfig.issuerUrl)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT ID"
|
||||
bind:value={oauthConfig.clientId}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT ID"
|
||||
bind:value={oauthConfig.clientId}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.clientId == savedConfig.clientId)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT SECRET"
|
||||
bind:value={oauthConfig.clientSecret}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIENT SECRET"
|
||||
bind:value={oauthConfig.clientSecret}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.clientSecret == savedConfig.clientSecret)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="SCOPE"
|
||||
bind:value={oauthConfig.scope}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.scope == savedConfig.scope)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="SCOPE"
|
||||
bind:value={oauthConfig.scope}
|
||||
required={true}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.scope == savedConfig.scope)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="BUTTON TEXT"
|
||||
bind:value={oauthConfig.buttonText}
|
||||
required={false}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="BUTTON TEXT"
|
||||
bind:value={oauthConfig.buttonText}
|
||||
required={false}
|
||||
disabled={!oauthConfig.enabled}
|
||||
isEdited={!(oauthConfig.buttonText == savedConfig.buttonText)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<SettingSwitch
|
||||
@@ -135,12 +136,14 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)}
|
||||
/>
|
||||
<div class="ml-4">
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
export let showResetToDefault = true;
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between gap-2 mx-4 mt-8">
|
||||
<div class="flex justify-between gap-2 mt-8">
|
||||
<div class="left">
|
||||
{#if showResetToDefault}
|
||||
<button
|
||||
on:click|preventDefault={() => dispatch('reset-to-default')}
|
||||
on:click={() => dispatch('reset-to-default')}
|
||||
class="text-sm dark:text-immich-dark-primary hover:dark:text-immich-dark-primary/75 text-immich-primary hover:text-immich-primary/75 font-medium bg-none"
|
||||
>
|
||||
Reset to default
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<div class="right">
|
||||
<button
|
||||
on:click|preventDefault={() => dispatch('reset')}
|
||||
on:click={() => dispatch('reset')}
|
||||
class="text-sm bg-gray-500 dark:bg-gray-200 hover:bg-gray-500/75 dark:hover:bg-gray-200/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>Reset
|
||||
</button>
|
||||
|
||||
@@ -12,19 +12,19 @@
|
||||
|
||||
export let inputType: SettingInputFieldType;
|
||||
export let value: string;
|
||||
export let label: string;
|
||||
export let label = '';
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let isEdited: boolean;
|
||||
export let isEdited = false;
|
||||
|
||||
const handleInput = (e: Event) => {
|
||||
value = (e.target as HTMLInputElement).value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<div class="flex place-items-center gap-1">
|
||||
<label class="immich-form-label" for={label}>{label.toUpperCase()} </label>
|
||||
<div class="w-full">
|
||||
<div class={`flex place-items-center gap-1 h-[26px]`}>
|
||||
<label class={`immich-form-label text-xs`} for={label}>{label.toUpperCase()} </label>
|
||||
{#if required}
|
||||
<div class="text-red-400">*</div>
|
||||
{/if}
|
||||
@@ -32,14 +32,14 @@
|
||||
{#if isEdited}
|
||||
<div
|
||||
transition:fly={{ x: 10, duration: 200, easing: quintOut }}
|
||||
class="text-gray-500 text-xs italic"
|
||||
class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
|
||||
>
|
||||
Unsaved change
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
class="immich-form-input w-full"
|
||||
id={label}
|
||||
name={label}
|
||||
type={inputType}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<div class="flex justify-between mx-4 place-items-center">
|
||||
<div>
|
||||
<h2 class="immich-form-label">
|
||||
<h2 class="immich-form-label text-sm">
|
||||
{title.toUpperCase()}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
api,
|
||||
SystemConfigStorageTemplateDto,
|
||||
SystemConfigTemplateStorageOptionDto,
|
||||
UserResponseDto
|
||||
} from '@api';
|
||||
import * as luxon from 'luxon';
|
||||
import handlebar from 'handlebars';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import SupportedDatetimePanel from './supported-datetime-panel.svelte';
|
||||
import SupportedVariablesPanel from './supported-variables-panel.svelte';
|
||||
import SettingButtonsRow from '../setting-buttons-row.svelte';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
||||
|
||||
export let storageConfig: SystemConfigStorageTemplateDto;
|
||||
export let user: UserResponseDto;
|
||||
|
||||
let savedConfig: SystemConfigStorageTemplateDto;
|
||||
let defaultConfig: SystemConfigStorageTemplateDto;
|
||||
let templateOptions: SystemConfigTemplateStorageOptionDto;
|
||||
let selectedPreset = '';
|
||||
|
||||
async function getConfigs() {
|
||||
[savedConfig, defaultConfig, templateOptions] = await Promise.all([
|
||||
api.systemConfigApi.getConfig().then((res) => res.data.storageTemplate),
|
||||
api.systemConfigApi.getDefaults().then((res) => res.data.storageTemplate),
|
||||
api.systemConfigApi.getStorageTemplateOptions().then((res) => res.data)
|
||||
]);
|
||||
|
||||
selectedPreset = templateOptions.presetOptions[0];
|
||||
}
|
||||
|
||||
const getSupportDateTimeFormat = async () => {
|
||||
const { data } = await api.systemConfigApi.getStorageTemplateOptions();
|
||||
return data;
|
||||
};
|
||||
|
||||
$: parsedTemplate = () => {
|
||||
try {
|
||||
return renderTemplate(storageConfig.template);
|
||||
} catch (error) {
|
||||
return 'error';
|
||||
}
|
||||
};
|
||||
|
||||
const renderTemplate = (templateString: string) => {
|
||||
const template = handlebar.compile(templateString, {
|
||||
knownHelpers: undefined
|
||||
});
|
||||
|
||||
const substitutions: Record<string, string> = {
|
||||
filename: 'IMG_10041123',
|
||||
ext: 'jpeg'
|
||||
};
|
||||
|
||||
const dt = luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString());
|
||||
|
||||
const dateTokens = [
|
||||
...templateOptions.yearOptions,
|
||||
...templateOptions.monthOptions,
|
||||
...templateOptions.dayOptions,
|
||||
...templateOptions.hourOptions,
|
||||
...templateOptions.minuteOptions,
|
||||
...templateOptions.secondOptions
|
||||
];
|
||||
|
||||
for (const token of dateTokens) {
|
||||
substitutions[token] = dt.toFormat(token);
|
||||
}
|
||||
|
||||
return template(substitutions);
|
||||
};
|
||||
|
||||
async function reset() {
|
||||
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||
|
||||
storageConfig.template = resetConfig.storageTemplate.template;
|
||||
savedConfig.template = resetConfig.storageTemplate.template;
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset storage template settings to the recent saved settings',
|
||||
type: NotificationType.Info
|
||||
});
|
||||
}
|
||||
|
||||
async function saveSetting() {
|
||||
try {
|
||||
const { data: currentConfig } = await api.systemConfigApi.getConfig();
|
||||
|
||||
const result = await api.systemConfigApi.updateConfig({
|
||||
...currentConfig,
|
||||
storageTemplate: storageConfig
|
||||
});
|
||||
|
||||
storageConfig.template = result.data.storageTemplate.template;
|
||||
savedConfig.template = result.data.storageTemplate.template;
|
||||
|
||||
notificationController.show({
|
||||
message: 'Storage template saved',
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error [storage-template-settings] [saveSetting]', e);
|
||||
notificationController.show({
|
||||
message: 'Unable to save settings',
|
||||
type: NotificationType.Error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function resetToDefault() {
|
||||
const { data: defaultConfig } = await api.systemConfigApi.getDefaults();
|
||||
|
||||
storageConfig.template = defaultConfig.storageTemplate.template;
|
||||
|
||||
notificationController.show({
|
||||
message: 'Reset storage template to default',
|
||||
type: NotificationType.Info
|
||||
});
|
||||
}
|
||||
|
||||
const handlePresetSelection = () => {
|
||||
storageConfig.template = selectedPreset;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="dark:text-immich-dark-fg">
|
||||
{#await getConfigs() then}
|
||||
<div id="directory-path-builder" class="m-4">
|
||||
<h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">
|
||||
Variables
|
||||
</h3>
|
||||
|
||||
<section class="support-date">
|
||||
{#await getSupportDateTimeFormat()}
|
||||
<LoadingSpinner />
|
||||
{:then options}
|
||||
<div transition:fade={{ duration: 200 }}>
|
||||
<SupportedDatetimePanel {options} />
|
||||
</div>
|
||||
{/await}
|
||||
</section>
|
||||
|
||||
<section class="support-date">
|
||||
<SupportedVariablesPanel />
|
||||
</section>
|
||||
|
||||
<div class="mt-4 flex flex-col">
|
||||
<h3 class="font-medium text-immich-primary dark:text-immich-dark-primary text-base">
|
||||
Template
|
||||
</h3>
|
||||
|
||||
<div class="text-xs my-2">
|
||||
<h4>PREVIEW</h4>
|
||||
</div>
|
||||
|
||||
<p class="text-xs">
|
||||
Approximately path length limit : <span
|
||||
class="font-semibold text-immich-primary dark:text-immich-dark-primary"
|
||||
>{parsedTemplate().length + user.id.length + 'UPLOAD_LOCATION'.length}</span
|
||||
>/260
|
||||
</p>
|
||||
|
||||
<p class="text-xs">
|
||||
{user.id} is the user's ID
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="text-xs p-4 bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg py-2 rounded-lg mt-2"
|
||||
>
|
||||
<span class="text-immich-fg/25 dark:text-immich-dark-fg/50"
|
||||
>UPLOAD_LOCATION/{user.id}</span
|
||||
>/{parsedTemplate()}.jpeg
|
||||
</p>
|
||||
|
||||
<form autocomplete="off" class="flex flex-col" on:submit|preventDefault>
|
||||
<div class="flex flex-col my-2">
|
||||
<label class="text-xs" for="presets">PRESET</label>
|
||||
<select
|
||||
class="text-sm bg-slate-200 p-2 rounded-lg mt-2 dark:bg-gray-600 hover:cursor-pointer"
|
||||
name="presets"
|
||||
id="preset-select"
|
||||
bind:value={selectedPreset}
|
||||
on:change={handlePresetSelection}
|
||||
>
|
||||
{#each templateOptions.presetOptions as preset}
|
||||
<option value={preset}>{renderTemplate(preset)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex gap-2 align-bottom">
|
||||
<SettingInputField
|
||||
label="template"
|
||||
required
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
bind:value={storageConfig.template}
|
||||
isEdited={!(storageConfig.template === savedConfig.template)}
|
||||
/>
|
||||
|
||||
<div class="flex-0">
|
||||
<SettingInputField
|
||||
label="Extension"
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
value={'.jpeg'}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
</section>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { SystemConfigTemplateStorageOptionDto } from '@api';
|
||||
import * as luxon from 'luxon';
|
||||
|
||||
export let options: SystemConfigTemplateStorageOptionDto;
|
||||
|
||||
const getLuxonExample = (format: string) => {
|
||||
return luxon.DateTime.fromISO(new Date('2022-09-04T20:03:05.250').toISOString()).toFormat(
|
||||
format
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="text-xs mt-2">
|
||||
<h4>DATE & TIME</h4>
|
||||
</div>
|
||||
|
||||
<div class="text-xs bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg p-4 mt-2 rounded-lg">
|
||||
<div class="mb-2 text-gray-600 dark:text-immich-dark-fg">
|
||||
<p>Asset's creation timestamp is used for the datetime information</p>
|
||||
<p>Sample time 2022-09-04T20:03:05.250</p>
|
||||
</div>
|
||||
<div class="flex gap-[50px]">
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">YEAR</p>
|
||||
<ul>
|
||||
{#each options.yearOptions as yearFormat}
|
||||
<li>{'{{'}{yearFormat}{'}}'} - {getLuxonExample(yearFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">MONTH</p>
|
||||
<ul>
|
||||
{#each options.monthOptions as monthFormat}
|
||||
<li>{'{{'}{monthFormat}{'}}'} - {getLuxonExample(monthFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">DAY</p>
|
||||
<ul>
|
||||
{#each options.dayOptions as dayFormat}
|
||||
<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">HOUR</p>
|
||||
<ul>
|
||||
{#each options.hourOptions as dayFormat}
|
||||
<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">MINUTE</p>
|
||||
<ul>
|
||||
{#each options.minuteOptions as dayFormat}
|
||||
<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">SECOND</p>
|
||||
<ul>
|
||||
{#each options.secondOptions as dayFormat}
|
||||
<li>{'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="text-xs mt-4">
|
||||
<h4>OTHER VARIABLES</h4>
|
||||
</div>
|
||||
|
||||
<div class="text-xs bg-gray-200 dark:bg-gray-700 dark:text-immich-dark-fg p-4 mt-2 rounded-lg">
|
||||
<div class="flex gap-[50px]">
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE NAME</p>
|
||||
<ul>
|
||||
<li>{`{{filename}}`}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-immich-primary font-medium dark:text-immich-dark-primary">FILE EXTENSION</p>
|
||||
<ul>
|
||||
<li>{`{{ext}}`}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,11 +2,13 @@
|
||||
import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
|
||||
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
|
||||
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
|
||||
import StorageTemplateSettings from '$lib/components/admin-page/settings/storate-template/storage-template-settings.svelte';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { api, SystemConfigDto } from '@api';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let systemConfig: SystemConfigDto;
|
||||
|
||||
export let data: PageData;
|
||||
const getConfig = async () => {
|
||||
const { data } = await api.systemConfigApi.getConfig();
|
||||
systemConfig = data;
|
||||
@@ -33,5 +35,12 @@
|
||||
<SettingAccordion title="OAuth Settings" subtitle="Manage the OAuth integration to Immich app">
|
||||
<OAuthSettings oauthConfig={configs.oauth} />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion
|
||||
title="Storage Template"
|
||||
subtitle="Manage the folder structure and file name of the upload asset"
|
||||
>
|
||||
<StorageTemplateSettings storageConfig={configs.storageTemplate} user={data.user} />
|
||||
</SettingAccordion>
|
||||
{/await}
|
||||
</section>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
onMount(() => {
|
||||
allUsers = $page.data.allUsers;
|
||||
console.log('getting all users', allUsers);
|
||||
});
|
||||
|
||||
const isDeleted = (user: UserResponseDto): boolean => {
|
||||
|
||||
Reference in New Issue
Block a user