Compare commits

..

266 Commits

Author SHA1 Message Date
Alex The Bot
9cf40afaf0 Version v1.88.1 2023-11-21 03:00:19 +00:00
Alex
28a15365d6 fix(web): search returns all value (#5210) 2023-11-20 20:58:22 -06:00
Alex
d49b353c49 fix(web): Fix year label calculation (#5211)
* fix(web): revert, fix year label overlapse

* not too bold

* fix
2023-11-20 20:58:05 -06:00
Jason Rasmussen
8b966a0f15 fix(server): date time calculation (#5204) 2023-11-20 17:26:53 -05:00
Alex
30e9763888 chore: post release tasks 2023-11-20 15:11:56 -06:00
Alex The Bot
0f596e278c Version v1.88.0 2023-11-20 20:47:37 +00:00
Alex
74ad8b37bb docs: update requirement for CLI (#5198) 2023-11-20 19:54:46 +00:00
Jason Rasmussen
ec6b56f63c feat(format): bmp format (#5197) 2023-11-20 13:47:24 -06:00
Alex
1fbbb5a236 chore(web): album thumbnail size (#5196) 2023-11-20 13:22:35 -06:00
martin
725f30c494 fix: album sorting options (#5127)
* fix: album sort options

* fix: don't load assets

* pr feedback

* fix: albumStub

* fix(web): album shared without assets

* fix: tests

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-20 13:01:21 -06:00
Alex
347e6191c5 chore(mobile): minor font fix 2023-11-20 12:59:53 -06:00
Alex
94c8fe1098 chore(web): small font size improvement (#5190) 2023-11-20 11:23:47 -06:00
Alex
cb32b5cd7b chore(server): update new CLI into the image (#5192) 2023-11-20 11:23:28 -06:00
Mert
ddf04a7eb4 chore(ml): increase spool threshold to 64MiB (#5176) 2023-11-20 09:05:35 -06:00
Alex
acf099e481 chore(mobile): Mobile make over (#5129)
* chore: added overpass font

* Setting page

* style: app bar dialog

* style: backup controller and album selection page

* style: asset grid

* blanket fix

* blanket fix

* remove description input for local only asset

* revert

* merge main

* style: search page

* sharing page

* text size in sharing page

* style: library page

* library page

* album page + album creation page

* Navigationbar

* style: minor

* update

* album bottom sheet

* album option page

* minor style fix

* remove unused fonts

* remove fonts in pubspec
2023-11-20 08:58:03 -06:00
Mert
f7ada7351e update onnxruntime (#5175) 2023-11-20 08:44:45 -06:00
renovate[bot]
2a5cf20c9f chore(deps): update dependency eslint-config-prettier to v9 (#5173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-20 08:39:00 -06:00
Jonathan Jogenfors
81259115d1 chore(cli): set cli workdir in npm publish (#5185)
* chore: set cli release working dir

* chore: add repo url to npmjs

* chore: bump node setup to v4

* chore: normalize the github url
2023-11-20 13:36:17 +00:00
bo0tzz
f20a6cb321 chore(docs): Redirect old CLI paths (#5183)
* chore(docs): Redirect old CLI paths

* Update docs/vercel.json

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-20 13:20:58 +00:00
Jason Rasmussen
81af3b6f20 chore(cli): push to npm (#5168) 2023-11-20 14:04:20 +01:00
Jason Rasmussen
235b82b3fc chore(build): renovate grouping (#5167) 2023-11-20 09:19:10 +01:00
권중혁
e2317ea35e Add Korean README (#5128)
* docs: add Korean README and links

* chore: correction of some words
2023-11-19 21:10:59 -06:00
Alex
f5d73b0499 feat(web): new fonts (#5165)
* feat(web): new fonts

* remove old fonts and make default font size larger

* fine tunning
2023-11-19 21:06:16 -06:00
Jonathan Jogenfors
c1239a7337 docs: remove old CLI first (#5163) 2023-11-19 16:41:42 -06:00
Jonathan Jogenfors
7e38e7c949 feat(cli): refactor and add album support (#4434)
* Allow building and installing cli

* feat: add format fix

* docs: remove cli folder

* feat: use immich scoped package

* feat: rewrite cli readme

* docs: add info on running without building

* cleanup

* chore: remove import functionality from cli

* feat: add logout to cli

* docs: add todo for file format from server

* docs: add compilation step to cli

* fix: success message spacing

* feat: can create albums

* fix: add check step to cli

* fix: typos

* feat: pull file formats from server

* chore: use crawl service from server

* chore: fix lint

* docs: add cli documentation

* chore: rename ignore pattern

* chore: add version number to cli

* feat: use sdk

* fix: cleanup

* feat: album name on windows

* chore: remove skipped asset field

* feat: add more info to server-info command

* chore: cleanup

* chore: remove unneeded packages

* chore: fix docs links

* feat: add cli v2 milestone

* fix: set correct cli date

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-11-19 22:16:24 +00:00
digitaljamie
88b5f5b500 chore(docs): Update machine-learning.md 2023-11-19 18:47:42 +00:00
shenlong
983473261b refactor(mobile): riverpod codegen + riverpod lint (#4836)
* build(mobile): add riverpod_lint

* refactor(mobile): riverpod_generator for providers

* test(mobile): fix integration test helper

* refactor: ApiService to riverpod codegen

* refactor(mobile): return curatedcontent instead of people dto

* refactor: person provider to asyncnotifier

* mobile: update service providers to use lambda

* mobile: update scaffoldbody default error icon

* remove logger mixin

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-19 10:04:44 -06:00
Guillermo
bfab86b70d fix(web): improve year label position (#5141) 2023-11-19 09:31:06 -06:00
renovate[bot]
5b25d5140c chore(deps): update dependency @types/archiver to v6 (#5146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 09:29:51 -06:00
renovate[bot]
904756bbc5 chore(deps): update docker/build-push-action action to v5.1.0 (#5145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 08:34:43 -05:00
renovate[bot]
3a6e2c92cf chore(deps): update dependency eslint to v8.54.0 (#5140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 08:17:11 -05:00
renovate[bot]
4ade8eae17 chore(deps): update dependency @types/node to v20.9.2 (#5139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-19 05:23:21 +00:00
shenlong
41d43acf5f mobile: add initial DCM analysis_options (#5136)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-18 23:17:08 -06:00
renovate[bot]
fa71641ea4 chore(deps): update redis:6.2-alpine docker digest to 80cc851 (#5131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-18 23:16:52 -06:00
shenlong
fce8d48de6 fix(mobile): use proper context for popping out from share (#5138)
* fix(mobile): use proper context for popping out from share

* mobile: use proper context for popping

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-18 23:13:38 -06:00
Michael Manganiello
6d310d6297 fix(mobile): Mark more strings for translation (#5132)
* fix(mobile): Mark more strings for translation

Moving more strings to the `i18n` JSON file, and also including their
es-US translations.

* Add more translatable strings
2023-11-18 20:32:28 -06:00
renovate[bot]
f5ce3deb3a fix(deps): update server (#5057)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-18 15:59:04 -06:00
Daniel Dietzler
767fe87b2e fix typo (#5124) 2023-11-18 17:49:30 +00:00
Daniel Dietzler
f2877c3a6e chore: add warning to compose file and readme (#5123)
* add warning to compose file

* add readme
2023-11-18 11:43:10 -06:00
Jason Rasmussen
adae5dd758 feat(web)!: SPA (#5069)
* feat(web): SPA

* chore: remove unnecessary prune

* feat(web): merge with immich-server

* Correct method name

* fix: bugs, docs, workflows, etc.

* chore: keep dockerignore for dev

* chore: remove license

* fix: expose 2283

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-17 22:13:36 -06:00
Alex
5118d261ab chore(web): remove deprecation message (#5115)
* chore(web): remove deprecation message

* keep the slot
2023-11-17 22:13:23 -06:00
Alex
cc15c5c69f chore: post release tasks 2023-11-17 21:25:13 -06:00
Alex The Bot
ec51a9f6d6 Version v1.87.0 2023-11-18 02:58:26 +00:00
Jason Rasmussen
9b2ac6aaca feat(server): decomissioned warning (#5113) 2023-11-17 20:31:34 -06:00
shenlong
4daf2478aa feat(mobile): remove announcement overlay and show in app bar dialog (#5104)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-17 15:46:18 -06:00
Fynn Petersen-Frey
63a745c7ad fix(mobile): update local deleted assets in sync (#5099) 2023-11-17 15:35:57 -06:00
davidacampos
47a4984a56 feat(web): add (lower) thumbnail resolution options (#5107)
* Added extra lower resolution options for thumbnails

Added 200p and 720p as options for small and large thumbnails resolutions respectively.

* Also included 1080p in large thumbnails resolution options
2023-11-17 20:25:35 +00:00
Jason Rasmussen
82f12b8ee6 chore(server): remove import file endpoint (#5093)
* chore(server): remove import file endpoint

* chore: open api
2023-11-17 05:44:59 +00:00
Jason Rasmussen
c7b3039a1a chore(server): remove asset search endpoint (#5094)
* chore(server): remove unused search endpoint

* chore: open api
2023-11-16 22:24:31 -06:00
Alex
ed68c49c16 chore(web): annoucement box for breaking change (#5091)
* chore(web): annoucement box for breaking change

* log
2023-11-17 03:38:32 +00:00
Mert
a6af4892e3 fix(ml): better model unloading (#3340)
* restart process on inactivity

* formatting

* always update `last_called`

* load models sequentially

* renamed variable, updated docs

* formatting

* made poll env name consistent with model ttl env
2023-11-16 20:42:44 -06:00
Jordy
98f87c6548 feat(readme): added new zcash donation option in readme (#5087)
* Added ZCash in Dutch(nl) readme

* Added ZCash in Turkish(tr) readme

* Added ZCash in Italian(it) readme

* Added ZCash in French(fr) readme

* Added ZCash in Spanish(es) readme

* Added ZCash in Catalan(ca) readme
2023-11-16 20:10:49 +00:00
Alex
54d770df8a Update README.md 2023-11-16 09:36:35 -06:00
renovate[bot]
b82db1edaa chore(deps): update dependency @testing-library/jest-dom to v6 (#5073)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 22:52:34 -06:00
Jason Rasmussen
87f02cc775 chore(server): shared link e2e for add/remove assets (#5052) 2023-11-15 17:50:55 -06:00
renovate[bot]
69030ea9a7 chore(deps): update dependency @faker-js/faker to v8 (#5059)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 17:50:07 -06:00
martin
956ca816bc fix(web): previous url in person page (#5071) 2023-11-15 17:14:18 -06:00
Fynn Petersen-Frey
343afea713 fix(mobile): rebuild of unified partner timeline (#5065) 2023-11-15 08:31:38 -06:00
renovate[bot]
f54e6fc09f chore(deps): update postgres:14-alpine docker digest to 50d9be7 (#5054)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-15 02:34:42 +00:00
renovate[bot]
f4ef259ba0 fix(deps): update dependency commander to v11.1.0 (#5003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 20:28:58 -06:00
dependabot[bot]
bced117eb4 chore(deps): bump actions/github-script from 6 to 7 (#5032)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-14 20:27:43 -06:00
Alex
54b9bfaeef chore(mobile): translation update (#5053) 2023-11-14 20:27:01 -06:00
Michael Manganiello
c4f7cfc2a6 feat(mobile): Add es-US translations (#4997)
Adding es-US translations for all current strings in the application.
2023-11-14 20:23:58 -06:00
Jason Rasmussen
4b722517f0 chore: cleanup e2e (#5051) 2023-11-14 20:31:06 -05:00
Jason Rasmussen
6127fd4c5c chore(server): improve e2e test speed (#5026)
* feat: improve shared link (41s to 8s)

* feat: improve activity (18s to 8s)

* feat: improve partner (20s to 10s)

* feat: improve server-info (10s to 6s)

* feat: improve system-config

* fix: e2e

* chore: linting
2023-11-14 20:08:22 -05:00
martin
6214d510d6 fix(web): back button from person detail page (#5047)
* feat: return button to the main page

* add album route

* feat: do not use explicit routes
2023-11-14 17:55:03 -05:00
Alex
ecbe7beb6c chore: readme feature list (#5048)
* chore: update feature list

* typo

* typo
2023-11-14 17:54:06 -05:00
Jason Rasmussen
753dab8b3c feat(server): asset search endpoint (#4931)
* feat(server): GET /assets endpoint

* chore: open api

* chore: use dumb name

* feat: search by make, model, lens, city, state, country

* chore: open api

* chore: pagination validation and tests

* chore: pr feedback
2023-11-14 22:47:15 +00:00
Jonathan Jogenfors
7a8f8e5472 chore: don't set max old space (#5050) 2023-11-14 16:32:58 -06:00
Fynn Petersen-Frey
5d8af5f94c fix(mobile): partner assets in albums & share (#5029) 2023-11-14 14:32:38 -06:00
Fynn Petersen-Frey
5145c33ed4 feat(mobile): use app without storage permission (#5014)
* feat(mobile): use app without storage permission

* address review feedback
2023-11-14 14:30:27 -06:00
Ishan Jain
8f3ed8ba8e fix(server): print extra config keys on error (#5036) 2023-11-14 08:13:42 -05:00
Alex
dc4e6c4629 chore: post release tasks 2023-11-13 22:19:24 -06:00
Alex The Bot
a7cacafe25 Version v1.86.0 2023-11-14 04:08:18 +00:00
martin
d25a245049 feat(web,server): user avatar color (#4779) 2023-11-14 03:10:35 +00:00
opdelta
14c7187539 feat(mobile): Added fr-CA translations (#5023)
Co-authored-by: Ziad Lteif <cj291059>
2023-11-13 20:20:54 -06:00
Jason Rasmussen
24670178dc feat(web): use css vraiables (#5022) 2023-11-13 17:16:26 -05:00
Dmitriy P
72fb421f54 feat(web): Add file path info for owned assets (#4943)
* feat(web): Add file path info for external assets

Add file path information to the asset details panel for External assets. This will allow a user to easily locate said asset in the filesystem, to perform any desired tasks on that asset. Styling was chosen to be as unobtrusive as possible.

* feat(web): toggleable file path info for external assets

If the user is the owner of the current asset and it's an external asset, then add a button next to the filename to reveal the asset's file path.

* show path on owned asset and styling

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-13 21:57:58 +00:00
Alex
ac7e8bcdf4 fix(mobile): freeze at splash screen when updating from 1.85 to the new version (#5012)
* fix(mobile): Cannot return to logged in screen due to name changes

* fix(mobile): Cannot return to logged in screen due to name changes

* remove deadcode

* test deprecate

* Add deprecated decorator

* revert api change
2023-11-13 14:15:36 -06:00
Fynn Petersen-Frey
38983838fd fix(mobile): better app state handling (#4989)
* fix(mobile): better app state handling

* watch -> read

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-13 19:51:16 +00:00
Mert
291159e7fc fixed tests (#5017) 2023-11-13 19:37:39 +00:00
Fynn Petersen-Frey
464cf903f4 fix(mobile): better icon spacing in bottom bar (#5010)
* fix(mobile): better icon spacing in bottom bar

* wider buttons
2023-11-13 10:19:51 -06:00
Mert
935f471ccb chore(ml): use strict mypy (#5001)
* improved typing

* improved export typing

* strict mypy & check export folder

* formatting

* add formatting checks for export folder

* re-added init call
2023-11-13 10:18:46 -06:00
Fynn Petersen-Frey
9fa9ad05b1 feat(mobile): unify partner assets on timeline (#4974)
* feat(mobile): unify partner assets on timeline

* skip non-owned assets in bulk actions

* add message when trying to delete partner assets
2023-11-13 09:54:41 -06:00
waclaw66
0c482960ce fix(web): better year labels (#5009)
* better year labels

* fixes

* lint fixes
2023-11-13 09:37:50 -06:00
Alex
c3f8dc8c22 chore(mobile): translation update (#5011) 2023-11-13 09:24:00 -06:00
bo0tzz
a959f2a51d chore(renovate): Ignore un-bumpable mobile deps (#5007) 2023-11-13 10:46:32 +01:00
renovate[bot]
880f4f61d2 chore(deps): update dependency @types/jest to v29.5.8 (#4995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 03:54:25 +00:00
renovate[bot]
ab1d1ef4e7 chore(deps): update dependency eslint to v8.53.0 (#4986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:46:22 -06:00
renovate[bot]
89255d0889 fix(deps): update dependency yaml to v2.3.4 (#4981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:46:06 -06:00
renovate[bot]
14acee9090 fix(deps): update dependency systeminformation to v5.21.17 (#4979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:45:52 -06:00
renovate[bot]
8399130f05 chore(deps): update dependency eslint-plugin-jest to v27.6.0 (#4987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:45:23 -06:00
renovate[bot]
1188012279 fix(deps): update server (#4982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:44:42 -06:00
renovate[bot]
04a8bde7ac chore(deps): update ghcr.io/nginxinc/nginx-unprivileged docker tag to v1.25.1 (#4978)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 21:38:43 -06:00
Fynn Petersen-Frey
069a32dcdb fix(mobile): run user sync operation with lock (#4984) 2023-11-12 10:35:08 -06:00
bo0tzz
388144823a chore(renovate): PR exiftool updates separately (#4985) 2023-11-12 17:07:55 +01:00
renovate[bot]
c23d84be39 chore(deps): update dependency @types/node to v20.9.0 (#4983)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 15:15:44 +00:00
renovate[bot]
66120025b7 fix(deps): update dependency tailwindcss to v3.3.5 (#4980)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 10:04:24 -05:00
renovate[bot]
da33653b0a chore(deps): update dependency @types/js-yaml to v4.0.9 (#4970)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 09:51:24 -05:00
renovate[bot]
3ea0210c1d chore(deps): update dependency @types/mime-types to v2.1.4 (#4971)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 09:50:55 -05:00
Ploonet
98f1e85c87 fix(docs): Typo in libraries exclusion pattern (#4972)
Co-authored-by: Antony Mota <amota.confluenceconseil@boiron.fr>
2023-11-12 09:49:47 -05:00
renovate[bot]
d2509c619e chore(deps): update dependency jest-extended to v4.0.2 (#4976)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 09:48:43 -05:00
renovate[bot]
2bfe5d1573 chore(deps): update dependency @types/mock-fs to v4.13.4 (#4975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 09:48:17 -05:00
Daniel Dietzler
d7d464570f fix maplibre latlng import (#4977) 2023-11-12 14:46:17 +01:00
Alex Tran
2e82476cff chore: styling for partner stylesheet 2023-11-11 23:02:26 -06:00
renovate[bot]
2f462717aa chore(deps): update dependency @types/cli-progress to v3.11.5 (#4968)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 22:36:03 -05:00
renovate[bot]
86e04832a1 chore(deps): update dependency @types/jest to v29.5.8 (#4969)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 22:35:33 -05:00
Alex
96f1a271ef chore: update Makefile to use docker compose (#4967) 2023-11-12 01:31:01 +00:00
Sergey Kondrikov
55e3605ca4 feat(web): uniform random asset selection in slideshow mode (#4953)
* Implement weighted asset selection

* Rename totalAssets -> bucketCount

* Remove redundant check
2023-11-12 01:23:15 +00:00
renovate[bot]
0bf55d8e32 chore(deps): update dependency @types/chai to v4.3.10 (#4966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 01:10:37 +00:00
renovate[bot]
2dcad93d9c chore(deps): update dependency @types/byte-size to v8.1.2 (#4965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-12 01:09:16 +00:00
Mert
328a58ac0d feat(ml): add face models (#4952)
added models to config dropdown

fixed downloading

updated tests

use hf for face models

formatting
2023-11-11 19:04:49 -06:00
Brian Austin
7fca0d8da5 fix: replace first and last name with single field (#4915) 2023-11-11 19:03:32 -06:00
renovate[bot]
413ab2c538 chore(deps): update postgres:14-alpine docker digest to 874f566 (#4963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 19:58:46 -05:00
renovate[bot]
394e0dfe37 chore(deps): update redis:6.2-alpine docker digest to 3995fe6 (#4964)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 19:58:01 -05:00
renovate[bot]
a9b6acec28 chore(deps): update ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17 docker digest to 5ebb90a (#4960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-11 16:54:55 -06:00
Daniel Dietzler
ad4cbf20de refactor: map (#4957)
* fix mixed up lng lat for asset map

* minor cleanup

* update packages
2023-11-11 16:32:39 -06:00
bo0tzz
26fd797ac9 chore: re-enable renovate (#4955)
* Chore: reenable renovate

* chore(renovate): Don't group major updates

* chore(renovate): Use matchFileNames
2023-11-11 16:31:58 -06:00
Alex
35767591d2 feat(web): show partners assets on the main timeline (#4933) 2023-11-11 21:06:19 +00:00
aphymi
3b11854702 fix(mobile): fix JSON-format typos in Asset model (#4942) 2023-11-10 02:56:30 +00:00
Mansour
895129c997 feat!: add docker project name (#4906)
* add: docker project name

* chore: linting

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-09 21:43:21 -05:00
martin
92ec1ce77f fix(server,web): correctly show album level like (#4916)
* fix: like in global activity

* refactor: rename isGlobal to ReactionLevel.Album

* chore: open api

* chore: e2e test for album vs comment duplicate like checking

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-09 21:32:31 -05:00
Sushain Cherivirala
986bbfa831 feat(server): don't re-run face recognition on assets without any faces (#4854)
* Add AssetJobStatus

* fentity

* Add jobStatus field to AssetEntity

* Fix the migration doc paths

* Filter on facesRecognizedAt

* Set facesRecognizedAt field

* Test for facesRecognizedAt

* Done testing

* Adjust FK properties

* Add tests for WithoutProperty.FACES

* chore: non-nullable

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-09 20:55:00 -05:00
Jason Rasmussen
75c065c83a chore(server): e2e test (#4941) 2023-11-09 20:23:03 -05:00
Alex
9c0805c37a fix(server): non-admin cannot use map (#4934)
* fix(server): non-admin cannot user map

* fix: admin route

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-09 13:52:10 -06:00
shenlong
bffc2cdf60 refactor(mobile): build context extensions (#4923)
* refactor: move all extensions to separate package

* refactor(mobile): add BuildContext extension

* refactor(mobile): use theme getters from context

* refactor(mobile): use media query size from context

* refactor(mobile): use auto router methods from context

* refactor(mobile): use navigator methods from context

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-09 10:19:53 -06:00
Daniel Dietzler
a147dee4b6 feat: Maplibre (#4294)
* maplibre on web, custom styles from server

Actually use new vector tile server, custom style.json

support multiple style files, light/dark mode

cleanup, use new map everywhere

send file directly instead of loading first

better light/dark mode switching

remove leaflet

fix mapstyles dto, first draft of map settings

delete and add styles

fix delete default styles

fix tests

only allow one light and one dark style url

revert config core changes

fix server config store

fix tests

move axios fetches to repo

fix package-lock

fix tests

* open api

* add assets to docker container

* web: use mapSettings color for style

* style: add unique ids to map styles

* mobile: use style json for vector / raster

* do not use svelte-material-icons

* add click events to markers, simplify asset detail map

* improve map performance by using asset thumbnails for markers instead of original file

* Remove custom attribution

(by request)

* mobile: update map attribution

* style: map dark mode

* style: map light mode

* zoom level for state

* styling

* overflow gradient

* Limit maxZoom to 14

* mobile: listen for mapStyle changes in MapThumbnail

* mobile: update concurrency

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-11-09 10:10:56 -06:00
Jason Rasmussen
5423f1c25b refactor(server): auth dtos (#4881)
* refactor: auth dtos

* chore: open api
2023-11-09 10:14:15 -05:00
shenlong
5c602bf4d4 fix(mobile): add label for expire after in shared link edit page (#4920)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-09 08:36:57 -06:00
bo0tzz
5db73c5c5c chore(ci): use custom base for server image build (#4456)
* Use base image for server build
* Clean up build scripts
* target tags for base image
* use prod tag instead of runtime
* use runtime stage for dev

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-09 13:12:05 +00:00
Michael Manganiello
52fe392a9e fix(mobile): display simple album date when range is single day (#4919)
This change makes albums with same start and end date, to just display a
single date.

Currently, date range is displayed as `Nov 9 - Nov 9 2023`. With this
change, just `Nov 9 2023` is displayed. No changes are made for albums
where start and end dates do not match.
2023-11-09 03:16:56 +00:00
Alex
5e1c0fb465 chore: post release tasks 2023-11-08 12:51:34 -06:00
Alex The Bot
37ab37bffc Version v1.85.0 2023-11-08 03:20:59 +00:00
shenlong
664b7106ca feat(mobile): shared album activity disable handling (#4890)
* feat(mobile): shared album activity disable handling

* not show comment/like option on non-shared album, alternative text when activity is disabled

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-08 03:07:43 +00:00
Alex
bb28cae671 docs: update (#4893)
* docs: update

* chore:format
2023-11-07 15:52:44 -06:00
shenlong
c2c26c471a fix(mobile): do not show trashed assets in album viewer page (#4894)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-07 13:19:45 -06:00
Strubbl
2dca2850dc Update README.md (#4892)
fix alt parameter spelling of the Discord image tag
2023-11-07 18:57:30 +00:00
dependabot[bot]
7fc8f6433b chore(deps): bump stumpylog/image-cleaner-action from 0.3.0 to 0.4.0 (#4886)
Bumps [stumpylog/image-cleaner-action](https://github.com/stumpylog/image-cleaner-action) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/stumpylog/image-cleaner-action/releases)
- [Changelog](https://github.com/stumpylog/image-cleaner-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/image-cleaner-action/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: stumpylog/image-cleaner-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 13:54:18 -05:00
Jason Rasmussen
f6180fccdc chore(server): compose updates (#4878)
* chore: compose updates

* chore: docs for troubleshooting

* chore: add reference in docker compose to docs
2023-11-07 11:34:03 -05:00
martin
9d01885b58 feat(server, web): Album's options (#4870)
* feat: disable activity

* fix: disable reactions

* fix: tests

* fix: tests

* fix: tests

* pr feedback

* pr feedback

* chore: styling & wording

* refactor component

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-07 04:37:21 +00:00
Jason Rasmussen
ace0a5911c fix(web): optimize deps (#4877) 2023-11-06 22:02:46 -06:00
Fynn Petersen-Frey
21f2d3058a feat(mobile)!: batched full/initial sync (#4840)
* feat(mobile): batched full/initial sync

* use OptionalBetween

* skip/take as integer

---------

Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-06 11:40:43 -06:00
shenlong
26fd9d7e5f feat(mobile): shared album activities (#4833)
* fix(server): global activity like duplicate search

* mobile: user_circle_avatar - fallback to text icon if no profile pic available

* mobile: use favourite icon in search "your activity"

* feat(mobile): shared album activities

* mobile: align hearts with user profile icon

* styling

* replace bottom sheet with dismissible

* add auto focus to the input

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-11-06 09:46:26 -06:00
Jason Rasmussen
c74ea7282a docs: python upload guide (#4867) 2023-11-06 09:08:36 -05:00
Jason Rasmussen
279481ad54 feat(server): make is favorite optional on asset upload (#4865)
* feat(server): make isFavorite optional

* chore: open api

* chore: e2e
2023-11-06 09:04:39 -05:00
Mert
9e7a32804b chore(server): set relations for getByIds (#4855) 2023-11-05 20:15:12 -06:00
martin
a0743d8b7d feat(web): global activity (#4796)
* feat: global activity

* fix: tests

* pr feedback

* use flexbox

* fix: deleted control actions

* fix: flex box

* fix: do not show activity tab by default

* feat: better grouping

* fix: set isShared default value to false

* fix: prevent re-rendering the asset grid

* fix: activity status above the scrollbar

* fix: prevent re-rendering the asset grid

* fix: prevent re-rendering the asset grid

* pr feedback

* pr feedback

* pr feedback

* styling and better thumbnail

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-05 11:24:43 -06:00
Fynn Petersen-Frey
68000c21a8 fix(mobile): backup indicator wrong when only background backup is enabled (#4842)
Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
2023-11-05 10:07:57 -06:00
Jason Rasmussen
e671b30aaf fix(server): duplicate faces bug (#4844) 2023-11-05 10:07:29 -06:00
Sergey Kondrikov
cf1dfdc776 fix(web): unstack after stack child selection (#4834) 2023-11-04 07:59:21 -05:00
doggo
de29480dda fix(mobile): first stack list asset is now highlighted on view (#4802) 2023-11-03 20:43:43 -05:00
Jason Rasmussen
2e424fe249 feat(server): better api error messages (for unhandled exceptions) (#4817)
* feat(server): better error messages

* chore: open api

* chore: remove debug log

* fix: syntax error

* fix: e2e test
2023-11-03 20:33:15 -05:00
Wingy
d4ef6f52bb chore(mobile): change version mismatch text (#4831)
* change version mismatch text

* change text
2023-11-03 20:20:42 -05:00
martin
e1e45f3f32 fix(web): show one face for the same person in the detail panel (#4822) 2023-11-03 16:02:05 -05:00
Jesbin
330f4cadda docs: changes to docker compose command. (#4828) 2023-11-03 16:01:17 -05:00
waclaw66
621eef0edc feat(mobile): share assets from album (#4821)
* share from album

* fix case

* enhance conditional array items
2023-11-03 16:00:55 -05:00
waclaw66
33ce2b7bba fix(mobile): shows asset datetime with original timezone (#4774) 2023-11-03 09:04:41 -05:00
shenlong
81792a5342 fix(mobile): immich app bar tap radius (#4816)
* mobile: tool-tip for server url in app bar dialog

* fix: Add Inkwell around the entire profile image

* mobile: open documentation and github in browser

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-03 09:04:06 -05:00
shenlong
5f43971ccf mobile: allow upload if local asset in selection (#4815)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-03 09:03:01 -05:00
Sergey Kondrikov
38443a6068 fix(web): unstacking issues (#4792)
* Fix typo

* Restore asset store consistency after unstacking

* Fix aspect ratio after unstacking
2023-11-03 09:01:48 -05:00
waclaw66
92bb42950e fix(web): scrollbar year label visibility (#4820)
* fixes year label visibility

* format fix
2023-11-03 08:54:02 -05:00
Jason Rasmussen
b58edae134 fix(web): timeline alignment (#4808) 2023-11-02 15:11:59 -05:00
martin
2b9f20a1b5 fix: update like status (#4803) 2023-11-02 14:43:27 -04:00
Alex
d5f8199655 fix(web): scrollbar not showing year (#4782)
* fix(web): scrollbar not showing year

* grammar

* fix test
2023-11-01 20:50:24 -05:00
Alex
d8903de92e docs: remove read-only related content (#4781)
* docs: remove read-only related content

* format

* broken link
2023-11-01 20:49:57 -05:00
Jason Rasmussen
1d35965d03 feat(web): shuffle slideshow order (#4277)
* feat(web): shuffle slideshow order

* Fix play/stop issues

* Enter/exit fullscreen mode
* Prevent navigation to the next asset after exiting slideshow mode

* Fix entering the slideshow mode from an album page

* Simplify markup of the AssetViewer

Group viewer area and navigation (prev/next/slideshow bar) controls together

* Select a random asset from a random bucket

* Preserve assets order in random mode

* Exit fullscreen mode only if it is active

* Extract SlideshowHistory class

* Use traditional functions instead of arrow functions

* Refactor SlideshowHistory class

* Extract SlideshowBar component

* Fix comments

* Hide Say something in slideshow mode

---------

Co-authored-by: brighteyed <sergey.kondrikov@gmail.com>
2023-11-01 21:34:30 -04:00
Alex
309bf1ad22 chore: post release tasks 2023-11-01 14:43:10 -05:00
Jason Rasmussen
0130591a0f fix: show/set activity like per user (#4775)
* fix: like per user

* chore: open api

* chore: e2e test for userId filtering
2023-11-01 11:49:12 -04:00
Alex The Bot
cf4ec06750 Version v1.84.0 2023-11-01 14:46:59 +00:00
Alex
e8712e6694 fix(server): import scheduler module (#4766) 2023-10-31 23:40:35 -05:00
martin
ce5966c23d feat(web,server): activity (#4682)
* feat: activity

* regenerate api

* fix: make asset owner unable to delete comment

* fix: merge

* fix: tests

* feat: use textarea instead of input

* fix: do actions only if the album is shared

* fix: placeholder opacity

* fix(web): improve messages UI

* fix(web): improve input message UI

* pr feedback

* fix: tests

* pr feedback

* pr feedback

* pr feedback

* fix permissions

* regenerate api

* pr feedback

* pr feedback

* multiple improvements on web

* fix: ui colors

* WIP

* chore: open api

* pr feedback

* fix: add comment

* chore: clean up

* pr feedback

* refactor: endpoints

* chore: open api

* fix: filter by type

* fix: e2e

* feat: e2e remove own comment

* fix: web tests

* remove console.log

* chore: cleanup

* fix: ui tweaks

* pr feedback

* fix web test

* fix: unit tests

* chore: remove unused code

* revert useless changes

* fix: grouping messages

* fix: remove nullable on updatedAt

* fix: text overflow

* styling

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-31 22:13:34 -05:00
Jason Rasmussen
68f6446718 fix(cli): ignore web socket when unavailable and skip metadata init (#4748) 2023-10-31 22:08:21 -05:00
Jason Rasmussen
197f336b5f fix(web): no preload repair report (#4749) 2023-10-31 20:37:32 +00:00
Daniel Dietzler
cd375a976e feat(server): custom library scanning interval (#4390)
* add automatic library scan config options

* add validation

* open api

* use CronJob instead of cron-validator

* fix tests

* catch potential error of the library scan initialization

* better description for input field

* move library scan job initialization to server app service

* fix tests

* add comments to all parameters of cronjob contructor

* make scan a child of a more general library object

* open api

* chore: cleanup

* move cronjob handling to job repoistory

* web: select for common cron expressions

* fix open api

* fix tests

* put scanning settings in nested accordion

* fix system config validation

* refactor, tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-31 15:19:12 -05:00
Jason Rasmussen
088d5addf2 refactor(server): user core (#4733) 2023-10-31 11:01:32 -04:00
shenlong
2377df9dae fix(mobile): store exposure time as string (#4589)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-31 05:33:45 -05:00
waclaw66
ad5ba82f50 fix(mobile): don't show lens info if it's not available (#4737) 2023-10-31 05:33:08 -05:00
Michael Manganiello
b6f18cbe81 fix(server): Correctly set album start and end dates (#4698)
* fix(server): Correctly set album start and end dates

Currently, the query that retrieves album assets uses
`ORDER BY assets.fileCreatedAt DESC`, which makes the existing logic
return the start/end dates reversed (with `startDate` being taken from
the first asset in the array).

Instead of using the index-based approach, this change iterates through
assets to get the min/max `fileCreatedAt`. This will avoid any future
issues, if the query ordering changes, or becomes customizable (e.g. in
case the user prefers to visualize older assets first).

* fix: Maintain constant cost and only swap variables if needed
2023-10-31 05:08:34 -05:00
Mert
87a0ba3db3 feat(ml): export clip models to ONNX and host models on Hugging Face (#4700)
* export clip models

* export to hf

refactored export code

* export mclip, general refactoring

cleanup

* updated conda deps

* do transforms with pillow and numpy, add tokenization config to export, general refactoring

* moved conda dockerfile, re-added poetry

* minor fixes

* updated link

* updated tests

* removed `requirements.txt` from workflow

* fixed mimalloc path

* removed torchvision

* cleaner np typing

* review suggestions

* update default model name

* update test
2023-10-31 05:02:04 -05:00
Jason Rasmussen
3212a47720 refactor(server): user profile picture (#4728) 2023-10-30 19:38:34 -04:00
Jason Rasmussen
431536cdbb refactor(server): user core (#4722) 2023-10-30 17:02:36 -04:00
martin
9a60578088 fix(web): multiple improvements for people page (1) (#4717)
* fix(web): multiple improvements for people page

* feat: better responsive icons
2023-10-30 14:40:28 -05:00
Jason Rasmussen
8dcd159bd6 chore(server): remove user count endpoint (#4724)
* chore: remove unused endpoint

* chore: open api
2023-10-30 19:29:18 +00:00
Skyler Mäntysaari
2f87463170 fix(server): better fix for the OAuth Discovery errors (#4695)
* fix(server/oauth): Handle errors from OAuth Discovery.

* fix(server/oauth): Better fix for OAuth discovery error.

* This doesn't break tests.

* Update server/tsconfig.json

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* Revert back to the mostly original way.

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-30 13:22:30 -04:00
shenlong
9f56bf0ab9 refactor(mobile): app bar (#4687)
* refactor(mobile): add app bar to library and sharing

* mobile: add app bar dialog

* fix(mobile): refetch profile image only when path is changed

* mobile: add server url to dialog

* mobile: move trash to library app bar

* replace discord link with github

* user confirmation before sign out

* edit some styles

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-30 12:17:34 -05:00
Jason Rasmussen
603b056512 refactor(server): auth delete device (#4720)
* refactor(server): auth delete device

* fix: person e2e
2023-10-30 11:48:38 -04:00
Fynn Petersen-Frey
ce04e9e07a feat(server): hardware video acceleration for Rockchip SOCs via RKMPP (#4645)
* feat(server): hardware video acceleration for Rockchip SOCs via RKMPP

* add tests

* use LD_LIBRARY_PATH for custom ffmpeg

* incorporate review feedback

* code re-use for ffmpeg call

* review feedback
2023-10-30 09:39:37 -05:00
Alex
c54a188154 fix(web): sidebar setting not updating when there is a new property added to the data payload (#4708) 2023-10-30 09:17:37 -05:00
Mayuresh Dharwadkar
c77ba46d60 docs: fix typos (#4713) 2023-10-30 09:17:10 -05:00
martin
cc3149c520 fix(server): do not leak people (#4710) 2023-10-30 03:44:05 -05:00
shenlong
512f672e9e fix(mobile): cache key for assets from dto (#4699)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-29 15:28:54 -05:00
shenlong
b117985f66 fix(mobile): first char miss in new description (#4697)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-29 14:16:25 -05:00
Kalyani Mhala
b92a2b2a56 chore: add contribution section to readme (#4690)
* Update README.md

Successfully added contribution section to readme.md file.

* reordering

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-29 13:58:26 -05:00
Alex
a6f39bc74f fix(web): Improve UI/UX for shared link form (#4685)
* chore(web): Improve shared link form

* add verification for password

* improve ux
2023-10-29 13:50:43 -05:00
doggo
daad02504f feat(web): added toggle for Sharing button in the sidebar (#4674)
* Added toggle for Sharing button in the sidebar

* fix: format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-29 01:42:51 +00:00
jarvis2f
8a6889529c feat(server,web,mobile): Add optional password option for share links. (#4655)
* feat(server,web,mobile): Add optional password option for share links.

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>

* feat(server,web): Update shared-link.controller and page.svelte for improved cookie handling and metadata updates.

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>

---------

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>
2023-10-28 20:35:38 -05:00
Alex
b34cbd881a fix(web): scrollbar does not show all years (#4684) 2023-10-29 01:31:33 +00:00
martin
f6eaaab725 docs: update milestone page (#4683)
* docs: update milestone page

* docs: add 20k milestone
2023-10-28 20:20:05 -05:00
shenlong
2a2c74e081 fix(mobile): handle shared assets in viewer (#4679)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-28 14:48:30 -05:00
Skyler Mäntysaari
c653e0f261 fix(server/oauth): Handle errors from OAuth Discovery. (#4678) 2023-10-28 14:35:09 -05:00
martin
f0dd1d715a fix(web): table headers when there's no album (#4673) 2023-10-28 14:34:45 -05:00
Alex The Bot
d98a2a5f79 Version v1.83.0 2023-10-28 13:32:48 +00:00
Alex Tran
275717b8e3 chore(web): motion photo icon 2023-10-27 23:59:44 -05:00
Alex Tran
51dc197b33 fix(web): stacked vs normal asset rendering 2023-10-27 20:57:37 -05:00
shenlong
a42c95a781 mobile: show stack indicator for videos (#4671)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-27 20:09:17 -05:00
Alex
8b5b6d0821 feat(web): manual stacking asset (#4650)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-27 20:34:01 +00:00
Wingy
72dcde9e0f run dev-down on unclean dev-new exit (#4665) 2023-10-27 14:30:17 -05:00
Wingy
a08a687951 refactor(server, web): standardize theme api response (#4664)
* web: standardize theme api response

* revert makefile change that i didn't mean to commit
2023-10-27 02:32:33 +00:00
Jason Rasmussen
7ff68223ab fix(server): config update queue] (#4661) 2023-10-26 20:26:59 -05:00
Jason Rasmussen
c76c1d6bf8 refactor: always use the same bucket size (#4662) 2023-10-26 13:55:10 -05:00
Daniel Dietzler
0167407370 update jobs screenshot in docs (#4660) 2023-10-26 12:55:46 -05:00
shenlong
b49b10141e fix(mobile): stack count reset when navigating to library (#4647)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-26 09:19:06 -05:00
Wingy
cb0e37e76e fix(web): fix Theme Custom CSS endpoint requiring the user to be logged in as the server admin (#4633)
* fix custom css requiring the user to be the admin and logged in

* move theme api to custom endpoint

* add e2e test
2023-10-25 22:13:05 +00:00
Alex
237d1c1bf4 fix(mobile): incorrect date range (#4644)
* fix(mobile): incorrect date range

* remove comment

* pr feedback
2023-10-25 16:56:31 -05:00
Alex
cf71a41bae fix(web): asset ownership does not update when navigating (#4643) 2023-10-25 12:14:15 -05:00
shenlong
52e09b4857 fix(mobile): asset deletion state management (#4568) 2023-10-25 11:02:59 -05:00
Alex
aefd052888 feat(mobile): clear local storage option (#4635)
* feat(mobile): clear local storage option

* en json
2023-10-25 09:53:16 -05:00
Alex
e47a11b8ba chore(mobile): translation update (#4641) 2023-10-25 09:34:34 -05:00
Jason Rasmussen
2ad389f64e refactor(web): material icons (#4636) 2023-10-25 13:48:25 +00:00
Bogdan Cerovac
d5e19e45cd fix(web): fix #4574 link button + improves accessibility (#4575)
* Fixed semantic problem in navigation-bar.svelte

Closes [4574]

* Reintroduced styling for navigation-bar.svelte

Based on button styling

* Horizontal rule set as decoration in navigation-bar.svelte

* aria-current added as an assistive technology indication for current page

Horizontal rule indicates current page for users that can see the screen and with aria-current screen-reader users get the same information

* Temporary fix for accessibility name of the link when SVG replaces text

This is a temporary fix as we first need to fix the svelte-material-icons to support role="img" on rendered SVGs.

* fix(web): horizontal rule replaced with div

hr is not semantically correct, therefore we tried with role="decoration" (that should be role="presentation") but it's actually better to just use a div as it's best practice to not override semantics when we can avoid that...

Btw. the semantics for active element for assistive technology is added with previous commit setting aria-current on the link...

* chore: format

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-25 12:53:44 +00:00
bo0tzz
4a5654a247 chore(compose): Remove unnecessary env values from proxy container (#4398) 2023-10-25 00:23:52 -04:00
Wingy
d4c60eab0d docs: clarify that the server must be run in development mode to generate openapi specs (#4634) 2023-10-24 21:55:40 -05:00
Jason Rasmussen
0fb1d33f17 fix(web,server): web socket auth (for web) (#4632) 2023-10-24 18:07:24 -04:00
Jason Rasmussen
3021eca8e5 chore(server): remove unused method (#4627)
* chore(server): remove unused method

* chore: open api
2023-10-24 12:59:06 -04:00
Jason Rasmussen
5921ec9a58 fix(server): dot files (#4625) 2023-10-24 11:08:18 -05:00
martin
3e3598fd92 fix: suggest people (#4566)
* fix: suggest people

* feat: remove hidden people

* add hidden people when merging faces

* pr feedback

* fix: don't use reactive statement

* fixed section height

* improve merging

* fix: migration

* fix migration

* feat: add asset count

* fix: test

* rename endpoint

* add server test

* improve responsive design

* fix: remove videos from live photos in the asset count

* pr feedback

* fix: rename asset count endpoint

* fix: return firstname and lastname

* fix: reset people only on error

* fix: search

* fix: responsive design & div flickering

* fix: cleanup

* chore: open api

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-24 15:53:49 +00:00
martin
1aae29a0b8 refactor(server, web)!: store latest immich version available on the server (#3565)
* refactor: store latest immich version available on the server

* don't store admins acknowledgement

* merge main

* fix: api

* feat: custom interval

* pr feedback

* remove unused code

* update environment-variables

* pr feedback

* ci: fix server tests

* fix: dart number

* pr feedback

* remove proxy

* pr feedback

* feat: make stringToVersion more flexible

* feat(web): disable check

* feat: working version

* remove env

* fix: check if interval exists when updating the interval

* feat: show last check

* fix: tests

* fix: remove availableVersion when updated

* fix merge

* fix: web

* fix e2e tests

* merge main

* merge main

* pr feedback

* pr feedback

* fix: tests

* pr feedback

* pr feedback

* pr feedback

* pr feedback

* pr feedback

* fix: migration

* regenerate api

* fix: typo

* fix: compare versions

* pr feedback

* fix

* pr feedback

* fix: checkIntervalTime on startup

* refactor: websockets and interval logic

* chore: open api

* chore: remove unused code

* fix: use interval instead of cron

* mobile: handle WS event data as json object

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-24 11:05:42 -04:00
martin
99c6f8fb13 feat(server): allow unassigned asset-faces (#4474)
* feat: un-assign people

* regenerate api

* edit migration script

* fix: tests

* fix: typeorm

* fix: typo

* fix: type

* fix: migration

* fix: update

* fix: contraints

* fix: remove set

* feat: add assetId

* remove assetId

* remove unassignedFaces

* fix: migration

* regenerate api

* fix: tests

* remove changes to the api

* fix: migration

* fix migration

* pr feedback

* fix: revert change

* fix: tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-24 13:12:42 +00:00
DjP-iX
d4c23c8df8 docs: TrueNAS install guide (community) (#4615)
* Create truenas.md

* add truenas images

* complete truenas.md

* fix: sizebar position

* fix: missing quote

* formatting

* formatting

* ordering

* sizing

* styling

* sizing

* update link

* formatting

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-23 20:08:39 +00:00
Wingy
62a11283af feat(web): custom stylesheets (#4602)
* add initial ui and api definitions for stylesheets

* proper saving

* make custom css work

* add textarea

* rebuild api

* run prettier

* add typecast

* update typings

* move css accordion to be sorted alphabetically

* set content-type properly

* rename stylesheets to theme

* fix server test
2023-10-23 18:38:41 +00:00
shenlong
28d35bf04e fix(mobile): unique hero tag for assets from api response (#4600)
* fix(mobile): render error on switching asset while video playing

* fix(mobile): generate proper hero tags for assets from DTOs

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-23 13:28:12 -05:00
Markus
dd52ff2d33 feat(server): "{album}" in storage template (#2973)
* feat(server): add  to storage template

* feat: add album preset

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-10-23 18:00:31 +00:00
Sergey Kondrikov
093347c7ab fix(web): timeline scrolling up (#4612) 2023-10-23 16:35:17 +00:00
Bogdan Cerovac
755649a3c8 fix(web): trash link wrongly wrapped (#4586)
* Added missing alt text on logo in documentation

* Better to use CSS to do the uppercases

Some assistive technology can spell each letter instead of read the word, depends a lot on internals but it's better to prevent...

* Semantic fix - single paragraph instead of many + uppercase via CSS

Single sentence in single paragraph, using css instead

* React requires className instead of class...

* Unnest Trash link from Archive
2023-10-23 12:24:58 -04:00
Daniel Dietzler
6b25435b4f refactor(server): make storage core singleton (#4608) 2023-10-23 11:52:21 -04:00
Michael Manganiello
2288b022bc fix(server): Check album asset membership in bulk (#4603)
Add `AlbumRepository` method to retrieve an album's asset ids, with an
optional parameter to only filter by the provided asset ids. With this,
we can now check asset membership using a single query.

When adding or removing assets to an album, checking whether each asset
is already present in the album now requires a single query, instead of
one query per asset.

Related to #4539 performance improvements.

Before:
```
// Asset membership and permissions check (2 queries per asset)
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","b666ae6c-afa8-4d6f-a1ad-7091a0659320"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","c656ab1c-7775-4ff7-b56f-01308c072a76"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND ("AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL) WHERE ( ("AlbumEntity"."id" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) ) AND ( "AlbumEntity"."deletedAt" IS NULL )) LIMIT 1 -- PARAMETERS: ["3fdf0e58-a1c7-4efe-8288-06e4c3f38df9","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```

After:
```
// Asset membership check (1 query for all assets)
immich_server            | query: SELECT "albums_assets"."assetsId" AS "assetId" FROM "albums_assets_assets" "albums_assets" WHERE "albums_assets"."albumsId" = $1 AND "albums_assets"."assetsId" IN ($2, $3, $4) -- PARAMETERS: ["ca870d76-6311-4e89-bf9a-f5b51ea2452c","b666ae6c-afa8-4d6f-a1ad-7091a0659320","c656ab1c-7775-4ff7-b56f-01308c072a76","cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9"]
// Permissions check (1 query per asset)
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["b666ae6c-afa8-4d6f-a1ad-7091a0659320","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["c656ab1c-7775-4ff7-b56f-01308c072a76","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
immich_server            | query: SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (SELECT 1 FROM "assets" "AssetEntity" WHERE ("AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2)) LIMIT 1 -- PARAMETERS: ["cf82adb2-1fcc-4f9e-9013-8fc03cc8d3a9","6bc60cf1-bd18-4501-a1c2-120b51276fda"]
```
2023-10-23 09:02:27 -04:00
martin
64e4ae7e4b fix(docs): dates and responsive design in milestone page (#4606)
* fix: dates and responsive design

* fix: overflow

* add tags for birthday

* use cake for birthday icon

* use uppercase for enum
2023-10-23 08:44:47 -04:00
Daniel Dietzler
c6b4bc883b refactor(server): make user core singleton (#4607) 2023-10-23 08:38:48 -04:00
Daniel Dietzler
50bc92aac0 refactor(server): make access core singleton (#4609) 2023-10-23 08:37:51 -04:00
Jason Rasmussen
36b3521be8 docs: milestones (#4593)
* docs: milestones

* fix: light mode

* feat: dates and links

* use item interface from timeline

* fix ssr build

* responseive design and styling

---------

Co-authored-by: martabal <74269598+martabal@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-22 21:34:23 -05:00
shenlong
b05132a01a refactor(mobile): server info to use data classes instead of dtos (#4591)
* refactor: server info model to use data classes instead of dtos

* mobile: add return types and refactor private variables in map / stack

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-22 15:15:34 -05:00
shenlong
9b418642a6 refactor(mobile): stack only through merging from timeline (#4598)
* mobile: remove stack selection page

* mobile: require at-least 2 assets to stack

* mobile: sort stack children by fileCreatedAt

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-22 15:07:27 -05:00
Daniel Dietzler
013da0aa3d refactor(server): streamline get config, enable the use of arrays (#4562) 2023-10-22 10:14:32 -05:00
shenlong
8dcc01b2be feat(mobile): shared-links (#4490)
* add shared links page

* feat(mobile): shared link items

* feat(mobile): create / edit shared links page

* server: add changeExpiryTime to SharedLinkEditDto

* fix(mobile): edit expiry to never

* mobile: add icon when shares list is empty

* mobile: create new share from album / timeline

* mobile: add translation texts

* mobile: minor ui fixes

* fix: handle serverURL with /api path

* mobile: show share link on successful creation

* mobile: shared links list - 2 column layout

* mobile: use sharedlink pod class instead of dto

* mobile: show error on link creation

* mobile: show share icon only when remote assets are in selection

* mobile: use server endpoint instead of server url

* styling

* styling

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-22 15:05:10 +00:00
shenlong
cf08ac7538 feat: manual stack assets (#4198) 2023-10-21 21:38:07 -05:00
Bogdan Cerovac
5ead4af2dc docs: Semantic fixes and improvements for the main documentation page (#4576)
* Added missing alt text on logo in documentation

* Better to use CSS to do the uppercases

Some assistive technology can spell each letter instead of read the word, depends a lot on internals but it's better to prevent...

* Semantic fix - single paragraph instead of many + uppercase via CSS

Single sentence in single paragraph, using css instead

* React requires className instead of class...
2023-10-21 16:08:58 -04:00
Bogdan Cerovac
f2c20f60f7 Alternative text that reflects the text in the image (#4577)
Small but important detail
2023-10-21 16:26:16 +00:00
Alex Tran
e0fc6b753c chore: remove outdated docs information 2023-10-20 16:27:58 -05:00
Alessandro (Ale) Segala
ab3f82cfe4 feat(server): add storage template variable assetId (#4555)
* Added assetId as template

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>

* styling

* styling

---------

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-20 16:17:17 -05:00
Alessandro (Ale) Segala
383f11019a feat(server): Two updates to dev environment (#4556)
1. In the `docker-compose.dev.yml` file, increased ulimits for the containers that use TS code. This was one of the reasons for failures in my Podman (on macOS/arm64) environment. It wasn't the only failure with Podman, and didn't investigate further (I switched to Docker on Linux/amd64 after), but it can still help others.

2. Added a `make dev-down` to perform a `docker-compose down` on the dev environment

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
2023-10-20 13:26:28 -05:00
Federico Micelli
250f7fc55c chore: README update italian link (#4551)
* Adding italian version of README and relative links

* Correction

* README update italian link
2023-10-19 19:10:32 -04:00
shenlong
22172a680b fix(mobile): validate response code from download file (#4543)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-10-19 15:19:00 -05:00
Russell Tan
5156d76194 fix(web): Prevents nav loop from person->album->albums (#4522) 2023-10-19 14:36:03 -05:00
Alex
decfb9687b fix(server): add exif info to memory response (#4550) 2023-10-19 14:33:35 -05:00
Jason Rasmussen
5e17b3199f chore: add logo to docs homepage (#4549) 2023-10-19 14:31:26 -05:00
Alex
cfec6a8fdb fix(server): avoid getting timebucket in the future (#4540) 2023-10-19 13:52:37 -05:00
Federico Micelli
2ec63f7914 Adding italian version of README file and relative links (#4537)
* Adding italian version of README and relative links

* Correction
2023-10-19 13:52:16 -05:00
Jason Rasmussen
29182cfc9a fix(server): exif duration with scale (#4541) 2023-10-19 13:51:56 -05:00
Daniel Dietzler
5a7ef02387 refactor(web): Allow dropdown for more general use (#4515) 2023-10-18 21:46:06 -05:00
Jason Rasmussen
4b59f83288 refactor: e2e tests (#4536) 2023-10-18 17:02:42 -05:00
Alex The Bot
31987bc043 Version v1.82.1 2023-10-18 17:14:26 +00:00
Alex
23f0eb6fe8 fix(web): fix websocket mode (#4531) 2023-10-18 12:12:19 -05:00
Jason Rasmussen
0994575bf3 fix(server): album add/remove asset performance (#4516) 2023-10-18 10:56:00 -05:00
Jason Rasmussen
f4a12acd29 fix(web): scrollbar offset (#4518)
* fix(web): scrollbar offset

* fix offset on photo page

* proper fix

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-10-18 10:54:20 -05:00
Jason Rasmussen
335216f6dd feat(server): allow underscores in ML url (#4517) 2023-10-17 21:34:16 +00:00
Efren
5a9acbc05b Fix Issues hyperlink pointing to Releases (#4508) 2023-10-17 07:38:42 +00:00
1042 changed files with 49725 additions and 28333 deletions

20
.dockerignore Normal file
View File

@@ -0,0 +1,20 @@
.vscode/
cli/
design/
docker/
docs/
fastlane/
machine-learning/
misc/
mobile/
server/node_modules
server/coverage/
server/.reverse-geocoding-dump/
server/upload/
server/dist/
web/node_modules/
web/coverage/
web/.svelte-kit
web/build/

2
.gitattributes vendored
View File

@@ -5,6 +5,8 @@ mobile/openapi/**/*.dart linguist-generated=true
mobile/openapi/.openapi-generator/FILES -diff -merge
mobile/openapi/.openapi-generator/FILES linguist-generated=true
mobile/lib/**/*.g.dart -diff -merge
mobile/lib/**/*.g.dart linguist-generated=true
cli/src/api/open-api/**/*.md -diff -merge
cli/src/api/open-api/**/*.md linguist-generated=true

22
.github/workflows/cli-release.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Publish Package to npmjs
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./cli
steps:
- uses: actions/checkout@v2
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.head.repo.fork }}
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |

View File

@@ -29,16 +29,13 @@ jobs:
include:
- primary-name: "immich-server"
- primary-name: "immich-machine-learning"
- primary-name: "immich-web"
- primary-name: "immich-proxy"
env:
# Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
steps:
-
name: Clean temporary images
- name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.3.0
uses: stumpylog/image-cleaner-action/ephemeral@v0.4.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"
@@ -60,17 +57,14 @@ jobs:
include:
- primary-name: "immich-server"
- primary-name: "immich-machine-learning"
- primary-name: "immich-web"
- primary-name: "immich-proxy"
- primary-name: "immich-build-cache"
env:
# Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
steps:
-
name: Clean untagged images
- name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.3.0
uses: stumpylog/image-cleaner-action/untagged@v0.4.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"

View File

@@ -24,100 +24,15 @@ jobs:
fail-fast: false
matrix:
include:
- context: "web"
image: "immich-web"
platforms: "linux/amd64,linux/arm64"
- context: "machine-learning"
file: "machine-learning/Dockerfile"
image: "immich-machine-learning"
platforms: "linux/amd64,linux/arm64"
- context: "nginx"
image: "immich-proxy"
platforms: "linux/amd64,linux/arm64"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
# Workaround to fix error:
# failed to push: failed to copy: io: read/write on closed pipe
# See https://github.com/docker/build-push-action/issues/761
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to Docker Hub
# Only push to Docker Hub when making a release
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
# Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
with:
flavor: |
# Disable latest tag
latest=false
images: |
name=ghcr.io/${{ github.repository_owner }}/${{matrix.image}}
name=altran1502/${{matrix.image}},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch
# Tag with pr-number
type=ref,event=pr
# Tag with git tag on release
type=ref,event=tag
type=raw,value=release,enable=${{ github.event_name == 'release' }}
- name: Determine build cache output
id: cache-target
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Essentially just ignore the cache output (PR can't write to registry cache)
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
else
echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ matrix.image }}" >> $GITHUB_OUTPUT
fi
- name: Build and push image
uses: docker/build-push-action@v5.0.0
with:
context: ${{ matrix.context }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{matrix.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
build_and_push_server_arm_64:
runs-on: self-hosted
strategy:
# Prevent a failure in one image from stopping the other builds
fail-fast: false
matrix:
include:
- context: "server"
- context: "."
file: "server/Dockerfile"
image: "immich-server"
platforms: "linux/arm64,linux/amd64"
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -181,9 +96,10 @@ jobs:
fi
- name: Build and push image
uses: docker/build-push-action@v5.0.0
uses: docker/build-push-action@v5.1.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.file }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}

View File

@@ -32,3 +32,8 @@ jobs:
- name: Run dart analyze
run: dart analyze --fatal-infos
working-directory: ./mobile
# Enable after riverpod generator migration is completed
# - name: Run dart custom lint
# run: dart run custom_lint
# working-directory: ./mobile

View File

@@ -21,7 +21,7 @@ jobs:
submodules: "recursive"
- name: Run e2e tests
run: docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
doc-tests:
name: Run documentation checks
@@ -97,6 +97,10 @@ jobs:
run: npm run format
if: ${{ !cancelled() }}
- name: Run tsc
run: npm run check
if: ${{ !cancelled() }}
- name: Run unit tests & coverage
run: npm run test:cov
if: ${{ !cancelled() }}
@@ -166,16 +170,15 @@ jobs:
- name: Install dependencies
run: |
poetry install --with dev
poetry run pip install --no-deps -r requirements.txt
- name: Lint with ruff
run: |
poetry run ruff check --format=github app
poetry run ruff check --format=github app export
- name: Check black formatting
run: |
poetry run black --check app
poetry run black --check app export
- name: Run mypy type checking
run: |
poetry run mypy --install-types --non-interactive app/
poetry run mypy --install-types --non-interactive --strict app/ export/
- name: Run tests and coverage
run: |
poetry run pytest --cov app

View File

@@ -1,35 +1,32 @@
dev:
docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans || make dev-down
dev-new:
docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
dev-new-update:
docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
dev-down:
docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans
dev-update:
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
dev-scale:
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
stage:
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
docker compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
pull-stage:
docker-compose -f ./docker/docker-compose.staging.yml pull
docker compose -f ./docker/docker-compose.staging.yml pull
test-e2e:
docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
prod:
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
prod-scale:
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
api:
cd ./server && npm run api:generate
attach-server:
docker exec -it docker_immich-server_1 sh
docker exec -it docker_immich-server_1 sh

View File

@@ -2,7 +2,7 @@
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
<br/>
@@ -18,13 +18,15 @@
</a>
<br/>
<p align="center">
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Disclaimer
@@ -65,7 +67,7 @@ password: demo
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
# Features
## Features
| Features | Mobile | Web |
| -------------------------------------------- | ------ | --- |
@@ -84,7 +86,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Virtual scroll | Yes | Yes |
| OAuth support | Yes | Yes |
| API Keys | N/A | Yes |
| LivePhoto backup and playback | iOS | Yes |
| LivePhoto/MotionPhoto backup and playback | Yes | Yes |
| User-defined storage structure | Yes | Yes |
| Public Sharing | No | Yes |
| Archive and Favorites | Yes | Yes |
@@ -94,8 +96,9 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Memories (x years ago) | Yes | Yes |
| Offline support | Yes | No |
| Read-only gallery | Yes | Yes |
| Stacked Photos | Yes | Yes |
# Support the project
## Support the project
I've committed to this project, and I will not stop. I will keep updating the docs, adding new features, and fixing bugs. But I can't do it alone. So I need your help to give me additional motivation to keep going.
@@ -103,10 +106,16 @@ As our hosts in the [selfhosted.show - In the episode 'The-organization-must-not
If you feel like this is the right cause and the app is something you are seeing yourself using for a long time, please consider supporting the project with the option below.
## Donation
### Donation
- [Monthly donation](https://github.com/sponsors/alextran1502) via GitHub Sponsors
- [One-time donation](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
## Contributors
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
</a>

View File

@@ -19,12 +19,14 @@
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Avís legal
@@ -108,4 +110,4 @@ Si creieu que aquesta és una causa justa i l'aplicació és alguna cosa que us
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -19,11 +19,14 @@
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Descargo de responsabilidad
@@ -108,3 +111,4 @@ Si consideras que esta es una causa justa y la aplicación es algo que te gustar
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -18,13 +18,15 @@
</a>
<br/>
<p align="center">
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Clause de non-responsabilité
@@ -110,3 +112,4 @@ Si vous estimez que c'est pour la bonne cause et que vous prévoyez d'utiliser l
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

115
README_it_IT.md Normal file
View File

@@ -0,0 +1,115 @@
<p align="center">
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
</a>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Login With Custom URL">
</p>
<h3 align="center">Immich - Soluzione self-hosted ad alte prestazioni per backup di foto e video</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Main Screenshot">
</a>
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Declino di responsabilità
- ⚠️ Il progetto è in fase di sviluppo **molto avanzato**.
- ⚠️ Possibilità di bug e cambiamenti rilevanti.
- ⚠️ **Non utilizzare l'app come unico salvataggio delle tue foto e dei tuoi video.**
- ⚠️ Utilizza sempre una tecnica [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) di backup per le foto e i video a cui tieni!
## Contenuto
- [Documentazione Ufficiale](https://immich.app/docs)
- [Roadmap](https://github.com/orgs/immich-app/projects/1)
- [Demo](#demo)
- [Funzionalità](#features)
- [Introduzione](https://immich.app/docs/overview/introduction)
- [Installazione](https://immich.app/docs/install/requirements)
- [Linee Guida per Contribuire](https://immich.app/docs/overview/support-the-project)
- [Supporta il Progetto](#support-the-project)
## Documentazione
La documentazione ufficiale, inclusa la guida all'installazione, è disponibile qui: https://immich.app/.
## Demo
Prova la demo del progetto https://demo.immich.app
Sull'app mobile, imposta `https://demo.immich.app/api` come `Server Endpoint URL`
```bash title="Demo Credential"
Credenziali di accesso
email: demo@immich.app
password: demo
```
```
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
# Funzionalità
| Funzionalità | Mobile | Web |
| ---------------------------------------------- | ------ | --- |
| Caricamento e visualizzazione di foto e video | Sì | Sì |
| Backup automatico quando l'app è in esecuzione | Sì | N/A |
| Selezione degli album per backup | Sì | N/A |
| Download foto e video sul dispositivo | Sì | Sì |
| Supporto multi utente | Sì | Sì |
| Album e album condivisi | Sì | Sì |
| Barra di scorrimento con trascinamento | Sì | Sì |
| Supporto formati raw | Sì | Sì |
| Visualizzazione metadata (EXIF, map) | Sì | Sì |
| Ricerca per metadata, oggetti, volti e CLIP | Sì | Sì |
| Funzioni di amministrazione degli utenti | No | Sì |
| Backup in background | Sì | N/A |
| Scroll virtuale | Sì | Sì |
| Supporto OAuth | Sì | Sì |
| API Keys | N/A | Sì |
| Backup e riproduzione di LivePhoto | iOS | Sì |
| Archiviazione impostata dall'utente | Sì | Sì |
| Condivisione pubblica | No | Sì |
| Archivio e Preferiti | Sì | Sì |
| Mappa globale | Sì | Sì |
| Collaborazione con utenti | Sì | Sì |
| Riconoscimento facciale e categorizzazione | Sì | Sì |
| Ricordi (x anni fa) | Sì | Sì |
| Supporto offline | Sì | No |
| Galleria sola lettura | Sì | Sì |
# Supporta il progetto
Mi dedico al progetto e non smetterò di farlo. Manterrò aggiornata la documentazione, aggiungerò nuove funzioni e risolverò i bug, ma non posso farlo da solo. Ho bisogno del tuo aiuto che mi da motivazione per continuare.
Come detto dal nostro host [selfhosted.show - Nell'episodio 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418), quello che il team ed io stiamo facendo è un lavoro enorme. Mi piacerebbe dedicarmi al progetto full-time e chiedo il tuo aiuto affinchè sia possibile.
Se pensi che Immich sia una buona causa e che l'app sia qualcosa che useresti nel lungo termine, sappi che puoi supportare il progetto scegliendo tra le opzioni sotto elencate.
## Donazioni
- [Donazione mensile](https://github.com/sponsors/alextran1502) tramite GitHub Sponsors
- [Donazione una tantum](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) tramite GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -18,12 +18,15 @@
</a>
<br/>
<p align="center">
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## 免責事項

116
README_ko_KR.md Normal file
View File

@@ -0,0 +1,116 @@
<p align="center">
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Login With Custom URL">
</p>
<h3 align="center">Immich - 고성능 자체 호스팅 사진 및 동영상 백업 솔루션</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Main Screenshot">
</a>
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## 주의 사항
- ⚠️ 이 프로젝트는 **매우 활발히** 개발 중입니다.
- ⚠️ 버그 및 잦은 변경 사항이 있을 수 있습니다.
- ⚠️ **사진과 동영상을 저장하는 유일한 방법으로 사용하지 마세요.**
- ⚠️ 중요한 사진과 동영상을 위해 항상 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 백업 계획을 따르세요!
## 목차
- [공식 문서](https://immich.app/docs)
- [로드맵](https://github.com/orgs/immich-app/projects/1)
- [데모](#demo)
- [기능](#features)
- [소개](https://immich.app/docs/overview/introduction)
- [설치](https://immich.app/docs/install/requirements)
- [기여 가이드](https://immich.app/docs/overview/support-the-project)
- [프로젝트 지원](#support-the-project)
## 문서
설치 가이드를 포함한 주요 문서는 https://immich.app 에서 확인할 수 있습니다.
## 데모
https://demo.immich.app 에서 웹 데모를 체험할 수 있습니다.
모바일 앱의 경우 `서버 엔드포인트 URL``https://demo.immich.app`를 입력합니다.
```bash title="Demo Credential"
자격 증명
email: demo@immich.app
password: demo
```
```
사양: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## 기능
| 기능 | 모바일 | 웹 |
| ------------------------------------ | ----- | ----- |
| 사진, 동영상 업로드 및 보기 | 예 | 예 |
| 앱을 열 때 자동으로 백업 | 예 | N/A |
| 백업용 앨범 선택 | 예 | N/A |
| 로컬 기기로 사진 및 동영상 다운로드 | 예 | 예 |
| 다른 사용자 추가 | 예 | 예 |
| 앨범 및 공유 앨범 | 예 | 예 |
| 스와이프/드래그 가능한 스크롤 바 | 예 | 예 |
| RAW 포맷 지원 | 예 | 예 |
| 메타데이터 보기 (EXIF, 위치) | 예 | 예 |
| 메타데이터, 사물, 얼굴 및 클립으로 검색 | 예 | 예 |
| 관리 기능 (사용자 관리) | 아니요 | 예 |
| 백그라운드 백업 | 예 | N/A |
| 가상 스크롤 | 예 | 예 |
| OAuth 지원 | 예 | 예 |
| API 키 | N/A | 예 |
| 라이브 포토/모션 포토 백업 및 재생 | 예 | 예 |
| 사용자 정의 스토리지 구조 | 예 | 예 |
| 모든 사용자와 공유 | 아니요 | 예 |
| 아카이브 및 즐겨찾기 | 예 |예|
| 글로벌 지도 | 예 | 예 |
| 특정 사용자와 공유 | 예 | 예 |
| 얼굴 인식 및 클러스터링 | 예 | 예 |
| 추억 (~년 전) | 예 | 예 |
| 오프라인 지원 | 예 | 아니요 |
| 읽기 전용 갤러리 | 예 | 예 |
| 사진 스택 | 예 | 예 |
## 프로젝트 지원
저는 이 프로젝트에 전념해왔고, 앞으로도 멈추지 않을 것입니다. 문서를 업데이트하고, 새로운 기능을 추가하고, 버그를 수정하려 합니다. 하지만 혼자서는 할 수 없습니다. 계속해서 나아갈 수 있는 추가적인 동기부여를 위해 당신의 도움이 필요합니다.
[selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 진행자가 말했듯이, 우리가 하고 있는 것은 대규모 프로젝트입니다. 언젠가는 이 일을 풀타임으로 하는 것을 희망하며, 이를 실현하기 위해 당신의 도움이 필요합니다.
만약 이에 동의하거나 이 앱을 장기간 사용하고자 한다면, 아래의 수단을 통해 이 프로젝트를 지원해 주세요.
### 후원
- GitHub 스폰서를 통한 [정기 후원](https://github.com/sponsors/alextran1502)
- GitHub 스폰서를 통한 [일시 후원](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502)
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -18,13 +18,15 @@
</a>
<br/>
<p align="center">
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Disclaimer
@@ -110,3 +112,4 @@ Als je denkt dat dit het juiste doel is en de app iets is dat je jezelf al heel
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -19,12 +19,14 @@
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Feragatname
@@ -107,3 +109,4 @@ Eğer bu size doğru bir amaç gibi geliyorsa ve uygulamanın uzun bir süre boy
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz

View File

@@ -23,15 +23,16 @@
<p align="center">
<a href="README.md">English</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
</p>
## 免责声明
- ⚠️ 本项目正在 **非常活跃** 地开发中。

10
cli/.npmignore Normal file
View File

@@ -0,0 +1,10 @@
**/*.spec.js
.editorconfig
.eslintignore
.eslintrc.js
.prettierignore
.prettierrc
package-lock.json
testSetup.js
tsconfig.json
tsconfig.build.json

View File

@@ -1,46 +1,19 @@
A command-line interface for interfacing with Immich
A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/).
# Getting started
Please see the [Immich CLI documentation](https://immich.app/docs/features/command-line-interface).
$ ts-node cli/src
# For developers
To start using the CLI, you need to login with an API key first:
To run the Immich CLI from source, run the following in the cli folder:
$ ts-node cli/src login-key https://your-immich-instance/api your-api-key
$ npm run build
$ ts-node .
NOTE: This will store your api key under ~/.config/immich/auth.yml
You'll need ts-node, the easiest way to install it is to use npm:
Next, you can run commands:
$ npm i -g ts-node
$ ts-node cli/src server-info
You can also build and install the CLI using
When you're done, log out to remove the credentials from your filesystem
$ ts-node cli/src logout
# Usage
```
Usage: immich [options] [command]
Immich command line interface
Options:
-h, --help display help for command
Commands:
upload [options] [paths...] Upload assets
import [options] [paths...] Import existing assets
server-info Display server information
login-key [instanceUrl] [apiKey] Login using an API key
help [command] display help for command
```
# Todo
- Sidecar should check both .jpg.xmp and .xmp
- Sidecar check could be case-insensitive
# Known issues
- Upload can't use sdk due to multiple issues
$ npm run build
$ npm install -g .

330
cli/package-lock.json generated
View File

@@ -1,21 +1,25 @@
{
"name": "immich-cli",
"name": "@immich/cli",
"version": "2.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "immich-cli",
"name": "@immich/cli",
"version": "2.0.4",
"license": "MIT",
"dependencies": {
"axios": "^1.4.0",
"axios": "^1.6.2",
"byte-size": "^8.1.1",
"cli-progress": "^3.12.0",
"commander": "^11.0.0",
"form-data": "^4.0.0",
"glob": "^10.3.1",
"picomatch": "^2.3.1",
"systeminformation": "^5.18.4",
"yaml": "^2.3.1"
},
"bin": {
"immich": "dist/index.js"
},
"devDependencies": {
"@types/byte-size": "^8.1.0",
"@types/chai": "^4.3.5",
@@ -29,7 +33,7 @@
"@typescript-eslint/parser": "^5.48.1",
"chai": "^4.3.7",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unicorn": "^47.0.0",
@@ -775,9 +779,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -798,21 +802,21 @@
}
},
"node_modules/@eslint/js": {
"version": "8.50.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz",
"integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
"integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
@@ -834,9 +838,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@isaacs/cliui": {
@@ -1489,21 +1493,21 @@
}
},
"node_modules/@types/byte-size": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.0.tgz",
"integrity": "sha512-LCIlZh8vyx+I2fgRycE1D34c33QDppYY6quBYYoaOpQ1nGhJ/avSP2VlrAefVotjJxgSk6WkKo0rTcCJwGG7vA==",
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.2.tgz",
"integrity": "sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==",
"dev": true
},
"node_modules/@types/chai": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz",
"integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==",
"version": "4.3.10",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz",
"integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==",
"dev": true
},
"node_modules/@types/cli-progress": {
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz",
"integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==",
"version": "3.11.5",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz",
"integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -1543,9 +1547,9 @@
}
},
"node_modules/@types/jest": {
"version": "29.5.5",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz",
"integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==",
"version": "29.5.8",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz",
"integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==",
"dev": true,
"dependencies": {
"expect": "^29.0.0",
@@ -1553,9 +1557,9 @@
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz",
"integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==",
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true
},
"node_modules/@types/json-schema": {
@@ -1565,25 +1569,28 @@
"dev": true
},
"node_modules/@types/mime-types": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.2.tgz",
"integrity": "sha512-q9QGHMGCiBJCHEvd4ZLdasdqXv570agPsUW0CeIm/B8DzhxsYMerD0l3IlI+EQ1A2RWHY2mmM9x1YIuuWxisCg==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
"dev": true
},
"node_modules/@types/mock-fs": {
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz",
"integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==",
"version": "4.13.4",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
"integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "20.8.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
"dev": true
"version": "20.9.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz",
"integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.2",
@@ -1806,6 +1813,12 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
@@ -1950,9 +1963,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -2402,9 +2415,9 @@
}
},
"node_modules/commander": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"engines": {
"node": ">=16"
}
@@ -2637,18 +2650,19 @@
}
},
"node_modules/eslint": {
"version": "8.50.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
"integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.50.0",
"@humanwhocodes/config-array": "^0.11.11",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.54.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -2691,9 +2705,9 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
"integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
"integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
@@ -2703,9 +2717,9 @@
}
},
"node_modules/eslint-plugin-jest": {
"version": "27.4.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.2.tgz",
"integrity": "sha512-3Nfvv3wbq2+PZlRTf2oaAWXWwbdBejFRBR2O8tAO67o+P8zno+QGbcDYaAXODlreXVg+9gvWhKKmG2rgfb8GEg==",
"version": "27.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz",
"integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^5.10.0"
@@ -3867,9 +3881,9 @@
}
},
"node_modules/jest-extended": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.1.tgz",
"integrity": "sha512-KM6dwuBUAgy6QONuR19CGubZB9Hkjqvl/d5Yc/FXsdB8+gsGxB2VQ+NEdOrr95J4GMPeLnDoPOKyi6+mKCCnZQ==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz",
"integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==",
"dev": true,
"dependencies": {
"jest-diff": "^29.0.0",
@@ -4825,6 +4839,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
@@ -5611,31 +5626,6 @@
"integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==",
"dev": true
},
"node_modules/systeminformation": {
"version": "5.21.9",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.9.tgz",
"integrity": "sha512-7pI4mu9P/2MGDV0T49B52E7IULBGj+kRVk6JSYUj5qfAk7N7C7aNX15fXziqrbgZntc6/jjYzWeb/x41jhg/eA==",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -5862,6 +5852,12 @@
"node": ">=4.2.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -6091,9 +6087,9 @@
"dev": true
},
"node_modules/yaml": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
"integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==",
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"engines": {
"node": ">= 14"
}
@@ -6702,9 +6698,9 @@
"dev": true
},
"@eslint/eslintrc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
@@ -6719,18 +6715,18 @@
}
},
"@eslint/js": {
"version": "8.50.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz",
"integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
"integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
}
@@ -6742,9 +6738,9 @@
"dev": true
},
"@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"@isaacs/cliui": {
@@ -7270,21 +7266,21 @@
}
},
"@types/byte-size": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.0.tgz",
"integrity": "sha512-LCIlZh8vyx+I2fgRycE1D34c33QDppYY6quBYYoaOpQ1nGhJ/avSP2VlrAefVotjJxgSk6WkKo0rTcCJwGG7vA==",
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.2.tgz",
"integrity": "sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==",
"dev": true
},
"@types/chai": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz",
"integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==",
"version": "4.3.10",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz",
"integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==",
"dev": true
},
"@types/cli-progress": {
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz",
"integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==",
"version": "3.11.5",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz",
"integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -7324,9 +7320,9 @@
}
},
"@types/jest": {
"version": "29.5.5",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz",
"integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==",
"version": "29.5.8",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz",
"integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==",
"dev": true,
"requires": {
"expect": "^29.0.0",
@@ -7334,9 +7330,9 @@
}
},
"@types/js-yaml": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz",
"integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==",
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true
},
"@types/json-schema": {
@@ -7346,25 +7342,28 @@
"dev": true
},
"@types/mime-types": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.2.tgz",
"integrity": "sha512-q9QGHMGCiBJCHEvd4ZLdasdqXv570agPsUW0CeIm/B8DzhxsYMerD0l3IlI+EQ1A2RWHY2mmM9x1YIuuWxisCg==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
"dev": true
},
"@types/mock-fs": {
"version": "4.13.2",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz",
"integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==",
"version": "4.13.4",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
"integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "20.8.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==",
"dev": true
"version": "20.9.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz",
"integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
}
},
"@types/normalize-package-data": {
"version": "2.4.2",
@@ -7498,6 +7497,12 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
@@ -7599,9 +7604,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -7923,9 +7928,9 @@
}
},
"commander": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ=="
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="
},
"concat-map": {
"version": "0.0.1",
@@ -8092,18 +8097,19 @@
"dev": true
},
"eslint": {
"version": "8.50.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
"integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.50.0",
"@humanwhocodes/config-array": "^0.11.11",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.54.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -8155,16 +8161,16 @@
}
},
"eslint-config-prettier": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
"integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
"integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
"dev": true,
"requires": {}
},
"eslint-plugin-jest": {
"version": "27.4.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.2.tgz",
"integrity": "sha512-3Nfvv3wbq2+PZlRTf2oaAWXWwbdBejFRBR2O8tAO67o+P8zno+QGbcDYaAXODlreXVg+9gvWhKKmG2rgfb8GEg==",
"version": "27.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz",
"integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "^5.10.0"
@@ -8969,9 +8975,9 @@
}
},
"jest-extended": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.1.tgz",
"integrity": "sha512-KM6dwuBUAgy6QONuR19CGubZB9Hkjqvl/d5Yc/FXsdB8+gsGxB2VQ+NEdOrr95J4GMPeLnDoPOKyi6+mKCCnZQ==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz",
"integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==",
"dev": true,
"requires": {
"jest-diff": "^29.0.0",
@@ -9716,7 +9722,8 @@
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"pirates": {
"version": "4.0.6",
@@ -10273,11 +10280,6 @@
"integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==",
"dev": true
},
"systeminformation": {
"version": "5.21.9",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.9.tgz",
"integrity": "sha512-7pI4mu9P/2MGDV0T49B52E7IULBGj+kRVk6JSYUj5qfAk7N7C7aNX15fXziqrbgZntc6/jjYzWeb/x41jhg/eA=="
},
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -10419,6 +10421,12 @@
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -10582,9 +10590,9 @@
"dev": true
},
"yaml": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
"integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg=="
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA=="
},
"yargs": {
"version": "17.7.2",

View File

@@ -1,14 +1,19 @@
{
"name": "immich-cli",
"name": "@immich/cli",
"version": "2.0.3",
"description": "Command Line Interface (CLI) for Immich",
"main": "dist/index.js",
"bin": {
"immich": "./dist/index.js"
},
"license": "MIT",
"dependencies": {
"axios": "^1.4.0",
"axios": "^1.6.2",
"byte-size": "^8.1.1",
"cli-progress": "^3.12.0",
"commander": "^11.0.0",
"form-data": "^4.0.0",
"glob": "^10.3.1",
"picomatch": "^2.3.1",
"systeminformation": "^5.18.4",
"yaml": "^2.3.1"
},
"devDependencies": {
@@ -24,7 +29,7 @@
"@typescript-eslint/parser": "^5.48.1",
"chai": "^4.3.7",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unicorn": "^47.0.0",
@@ -42,10 +47,12 @@
"scripts": {
"build": "tsc --project tsconfig.build.json",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"prepack": "yarn build ",
"prepack": "npm run build",
"test": "jest",
"test:cov": "jest --coverage",
"format": "prettier --check ."
"format": "prettier --check .",
"format:fix": "prettier --write .",
"check": "tsc --noEmit"
},
"jest": {
"clearMocks": true,
@@ -62,7 +69,15 @@
"collectCoverageFrom": [
"<rootDir>/src/**/*.(t|j)s"
],
"moduleNameMapper": {
"^@api(|/.*)$": "<rootDir>/src/api/$1"
},
"coverageDirectory": "./coverage",
"testEnvironment": "node"
},
"repository": {
"type": "git",
"url": "git+https://github.com/immich-app/immich.git",
"directory": "cli"
}
}

View File

@@ -1,3 +0,0 @@
// ./__mocks__/axios.js
import mockAxios from 'jest-mock-axios';
export default mockAxios;

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.82.0
* The version of the OpenAPI document: 1.88.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.82.0
* The version of the OpenAPI document: 1.88.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.82.0
* The version of the OpenAPI document: 1.88.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.82.0
* The version of the OpenAPI document: 1.88.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -9,7 +9,6 @@ import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
export abstract class BaseCommand {
protected sessionService!: SessionService;
protected immichApi!: ImmichApi;
protected deviceId!: string;
protected user!: UserResponseDto;
protected serverVersion!: ServerVersionResponseDto;

View File

@@ -1,15 +1,19 @@
import { BaseCommand } from '../cli/base-command';
export default class ServerInfo extends BaseCommand {
static description = 'Display server information';
static enableJsonFlag = true;
public async run() {
console.log('Getting server information');
await this.connect();
const { data: versionInfo } = await this.immichApi.serverInfoApi.getServerVersion();
console.log(versionInfo);
console.log(`Server is running version ${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`);
const { data: supportedmedia } = await this.immichApi.serverInfoApi.getSupportedMediaTypes();
console.log(`Supported image types: ${supportedmedia.image.map((extension) => extension.replace('.', ''))}`);
console.log(`Supported video types: ${supportedmedia.video.map((extension) => extension.replace('.', ''))}`);
const { data: statistics } = await this.immichApi.assetApi.getAssetStatistics();
console.log(`Images: ${statistics.images}, Videos: ${statistics.videos}, Total: ${statistics.total}`);
}
}

View File

@@ -1,43 +1,36 @@
import { BaseCommand } from '../cli/base-command';
import { CrawledAsset } from '../cores/models/crawled-asset';
import { CrawlService, UploadService } from '../services';
import * as si from 'systeminformation';
import FormData from 'form-data';
import { Asset } from '../cores/models/asset';
import { CrawlService } from '../services';
import { UploadOptionsDto } from '../cores/dto/upload-options-dto';
import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
import cliProgress from 'cli-progress';
import byteSize from 'byte-size';
import { BaseCommand } from '../cli/base-command';
export default class Upload extends BaseCommand {
private crawlService = new CrawlService();
private uploadService!: UploadService;
deviceId!: string;
uploadLength!: number;
dryRun = false;
public async run(paths: string[], options: UploadOptionsDto): Promise<void> {
await this.connect();
const uuid = await si.uuid();
this.deviceId = uuid.os || 'CLI';
this.uploadService = new UploadService(this.immichApi.apiConfiguration);
const deviceId = 'CLI';
this.dryRun = options.dryRun;
const formatResponse = await this.immichApi.serverInfoApi.getSupportedMediaTypes();
const crawlService = new CrawlService(formatResponse.data.image, formatResponse.data.video);
const crawlOptions = new CrawlOptionsDto();
crawlOptions.pathsToCrawl = paths;
crawlOptions.recursive = options.recursive;
crawlOptions.excludePatterns = options.excludePatterns;
crawlOptions.exclusionPatterns = options.exclusionPatterns;
const crawledFiles: string[] = await this.crawlService.crawl(crawlOptions);
const crawledFiles: string[] = await crawlService.crawl(crawlOptions);
if (crawledFiles.length === 0) {
console.log('No assets found, exiting');
return;
}
const assetsToUpload = crawledFiles.map((path) => new CrawledAsset(path));
const assetsToUpload = crawledFiles.map((path) => new Asset(path, deviceId));
const uploadProgress = new cliProgress.SingleBar(
{
@@ -58,117 +51,86 @@ export default class Upload extends BaseCommand {
totalSize += asset.fileSize;
}
const existingAlbums = (await this.immichApi.albumApi.getAllAlbums()).data;
uploadProgress.start(totalSize, 0);
uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
for (const asset of assetsToUpload) {
uploadProgress.update({
filename: asset.path,
});
try {
for (const asset of assetsToUpload) {
uploadProgress.update({
filename: asset.path,
});
try {
if (options.import) {
const importData = {
assetPath: asset.path,
sidecarPath: asset.sidecarPath,
deviceAssetId: asset.deviceAssetId,
deviceId: this.deviceId,
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
isFavorite: false,
isReadOnly: options.readOnly,
};
let skipUpload = false;
if (!options.skipHash) {
const assetBulkUploadCheckDto = { assets: [{ id: asset.path, checksum: await asset.hash() }] };
if (!this.dryRun) {
await this.uploadService.import(importData);
}
} else {
await this.uploadAsset(asset, options.skipHash);
const checkResponse = await this.immichApi.assetApi.checkBulkUpload({
assetBulkUploadCheckDto,
});
skipUpload = checkResponse.data.results[0].action === 'reject';
}
} catch (error) {
uploadProgress.stop();
throw error;
}
sizeSoFar += asset.fileSize;
if (!asset.skipped) {
totalSizeUploaded += asset.fileSize;
uploadCounter++;
}
if (!skipUpload) {
if (!options.dryRun) {
const res = await this.immichApi.assetApi.uploadFile(asset.getUploadFileRequest());
uploadProgress.update(sizeSoFar, { value_formatted: byteSize(sizeSoFar) });
if (options.album && asset.albumName) {
let album = existingAlbums.find((album) => album.albumName === asset.albumName);
if (!album) {
const res = await this.immichApi.albumApi.createAlbum({
createAlbumDto: { albumName: asset.albumName },
});
album = res.data;
existingAlbums.push(album);
}
await this.immichApi.albumApi.addAssetsToAlbum({ id: album.id, bulkIdsDto: { ids: [res.data.id] } });
}
}
totalSizeUploaded += asset.fileSize;
uploadCounter++;
}
sizeSoFar += asset.fileSize;
uploadProgress.update(sizeSoFar, { value_formatted: byteSize(sizeSoFar) });
}
} finally {
uploadProgress.stop();
}
uploadProgress.stop();
let messageStart;
if (this.dryRun) {
messageStart = 'Would have ';
if (options.dryRun) {
messageStart = 'Would have';
} else {
messageStart = 'Successfully ';
messageStart = 'Successfully';
}
if (options.import) {
console.log(`${messageStart} imported ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
if (uploadCounter === 0) {
console.log('All assets were already uploaded, nothing to do.');
} else {
if (uploadCounter === 0) {
console.log('All assets were already uploaded, nothing to do.');
console.log(`${messageStart} uploaded ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
}
if (options.delete) {
if (options.dryRun) {
console.log(`Would now have deleted assets, but skipped due to dry run`);
} else {
console.log(`${messageStart} uploaded ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
}
if (options.delete) {
if (this.dryRun) {
console.log(`Would now have deleted assets, but skipped due to dry run`);
} else {
console.log('Deleting assets that have been uploaded...');
const deletionProgress = new cliProgress.SingleBar(cliProgress.Presets.shades_classic);
deletionProgress.start(crawledFiles.length, 0);
console.log('Deleting assets that have been uploaded...');
const deletionProgress = new cliProgress.SingleBar(cliProgress.Presets.shades_classic);
deletionProgress.start(crawledFiles.length, 0);
for (const asset of assetsToUpload) {
if (!this.dryRun) {
await asset.delete();
}
deletionProgress.increment();
for (const asset of assetsToUpload) {
if (!options.dryRun) {
await asset.delete();
}
deletionProgress.stop();
console.log('Deletion complete');
deletionProgress.increment();
}
}
}
}
private async uploadAsset(asset: CrawledAsset, skipHash = false) {
await asset.readData();
let skipUpload = false;
if (!skipHash) {
const checksum = await asset.hash();
const checkResponse = await this.uploadService.checkIfAssetAlreadyExists(asset.path, checksum);
skipUpload = checkResponse.data.results[0].action === 'reject';
}
if (skipUpload) {
asset.skipped = true;
} else {
const uploadFormData = new FormData();
uploadFormData.append('deviceAssetId', asset.deviceAssetId);
uploadFormData.append('deviceId', this.deviceId);
uploadFormData.append('fileCreatedAt', asset.fileCreatedAt);
uploadFormData.append('fileModifiedAt', asset.fileModifiedAt);
uploadFormData.append('isFavorite', String(false));
uploadFormData.append('assetData', asset.assetData, { filename: asset.path });
if (asset.sidecarData) {
uploadFormData.append('sidecarData', asset.sidecarData, {
filename: asset.sidecarPath,
contentType: 'application/xml',
});
}
if (!this.dryRun) {
await this.uploadService.upload(uploadFormData);
deletionProgress.stop();
console.log('Deletion complete');
}
}
}

View File

@@ -1,59 +0,0 @@
// Check asset-upload.config.spec.ts for complete list
// TODO: we should get this list from the server via API in the future
// Videos
const videos = ['mp4', 'webm', 'mov', '3gp', 'avi', 'm2ts', 'mts', 'mpg', 'flv', 'mkv', 'wmv'];
// Images
const heic = ['heic', 'heif'];
const jpeg = ['jpg', 'jpeg'];
const png = ['png'];
const gif = ['gif'];
const tiff = ['tif', 'tiff'];
const webp = ['webp'];
const dng = ['dng'];
const other = [
'3fr',
'ari',
'arw',
'avif',
'cap',
'cin',
'cr2',
'cr3',
'crw',
'dcr',
'nef',
'erf',
'fff',
'iiq',
'jxl',
'k25',
'kdc',
'mrw',
'orf',
'ori',
'pef',
'psd',
'raf',
'raw',
'rwl',
'sr2',
'srf',
'srw',
'orf',
'ori',
'x3f',
];
export const ACCEPTED_FILE_EXTENSIONS = [
...videos,
...jpeg,
...png,
...heic,
...gif,
...tiff,
...webp,
...dng,
...other,
];

View File

@@ -1,6 +1,6 @@
export class CrawlOptionsDto {
pathsToCrawl!: string[];
recursive = false;
includeHidden = false;
excludePatterns!: string[];
recursive? = false;
includeHidden? = false;
exclusionPatterns?: string[];
}

View File

@@ -1,9 +1,9 @@
export class UploadOptionsDto {
recursive = false;
excludePatterns!: string[];
exclusionPatterns!: string[];
dryRun = false;
skipHash = false;
delete = false;
import = false;
readOnly = true;
album = false;
}

View File

@@ -1,2 +1 @@
export * from './constants';
export * from './models';

View File

@@ -0,0 +1,91 @@
import * as fs from 'fs';
import { basename } from 'node:path';
import crypto from 'crypto';
import { AssetApiUploadFileRequest } from 'src/api/open-api';
import Os from 'os';
export class Asset {
readonly path: string;
readonly deviceId!: string;
assetData?: File;
deviceAssetId?: string;
fileCreatedAt?: string;
fileModifiedAt?: string;
sidecarData?: File;
sidecarPath?: string;
fileSize!: number;
albumName?: string;
constructor(path: string, deviceId: string) {
this.path = path;
this.deviceId = deviceId;
}
async process() {
const stats = await fs.promises.stat(this.path);
this.deviceAssetId = `${basename(this.path)}-${stats.size}`.replace(/\s+/g, '');
this.fileCreatedAt = stats.mtime.toISOString();
this.fileModifiedAt = stats.mtime.toISOString();
this.fileSize = stats.size;
this.albumName = this.extractAlbumName();
this.assetData = await this.getFileObject(this.path);
// TODO: doesn't xmp replace the file extension? Will need investigation
const sideCarPath = `${this.path}.xmp`;
try {
fs.accessSync(sideCarPath, fs.constants.R_OK);
this.sidecarData = await this.getFileObject(sideCarPath);
} catch (error) {}
}
getUploadFileRequest(): AssetApiUploadFileRequest {
if (!this.assetData) throw new Error('Asset data not set');
if (!this.deviceAssetId) throw new Error('Device asset id not set');
if (!this.fileCreatedAt) throw new Error('File created at not set');
if (!this.fileModifiedAt) throw new Error('File modified at not set');
if (!this.deviceId) throw new Error('Device id not set');
return {
assetData: this.assetData,
deviceAssetId: this.deviceAssetId,
deviceId: this.deviceId,
fileCreatedAt: this.fileCreatedAt,
fileModifiedAt: this.fileModifiedAt,
isFavorite: false,
sidecarData: this.sidecarData,
};
}
private async getFileObject(path: string): Promise<File> {
const buffer = await fs.promises.readFile(path);
return new File([buffer], basename(path));
}
async delete(): Promise<void> {
return fs.promises.unlink(this.path);
}
public async hash(): Promise<string> {
const sha1 = (filePath: string) => {
const hash = crypto.createHash('sha1');
return new Promise<string>((resolve, reject) => {
const rs = fs.createReadStream(filePath);
rs.on('error', reject);
rs.on('data', (chunk) => hash.update(chunk));
rs.on('end', () => resolve(hash.digest('hex')));
});
};
return await sha1(this.path);
}
private extractAlbumName(): string {
if (Os.platform() === 'win32') {
return this.path.split('\\').slice(-2)[0];
} else {
return this.path.split('/').slice(-2)[0];
}
}
}

View File

@@ -1,58 +0,0 @@
import * as fs from 'fs';
import { basename } from 'node:path';
import crypto from 'crypto';
export class CrawledAsset {
public path: string;
public assetData?: fs.ReadStream;
public deviceAssetId?: string;
public fileCreatedAt?: string;
public fileModifiedAt?: string;
public sidecarData?: Buffer;
public sidecarPath?: string;
public fileSize!: number;
public skipped = false;
constructor(path: string) {
this.path = path;
}
async readData() {
this.assetData = fs.createReadStream(this.path);
}
async process() {
const stats = await fs.promises.stat(this.path);
this.deviceAssetId = `${basename(this.path)}-${stats.size}`.replace(/\s+/g, '');
this.fileCreatedAt = stats.mtime.toISOString();
this.fileModifiedAt = stats.mtime.toISOString();
this.fileSize = stats.size;
// TODO: doesn't xmp replace the file extension? Will need investigation
const sideCarPath = `${this.path}.xmp`;
try {
fs.accessSync(sideCarPath, fs.constants.R_OK);
this.sidecarData = await fs.promises.readFile(sideCarPath);
this.sidecarPath = sideCarPath;
} catch (error) {}
}
async delete(): Promise<void> {
return fs.promises.unlink(this.path);
}
public async hash(): Promise<string> {
const sha1 = (filePath: string) => {
const hash = crypto.createHash('sha1');
return new Promise<string>((resolve, reject) => {
const rs = fs.createReadStream(filePath);
rs.on('error', reject);
rs.on('data', (chunk) => hash.update(chunk));
rs.on('end', () => resolve(hash.digest('hex')));
});
};
return await sha1(this.path);
}
}

View File

@@ -1 +1 @@
export * from './crawled-asset';
export * from './asset';

View File

@@ -1,7 +1,10 @@
#! /usr/bin/env node
import { program, Option } from 'commander';
import Upload from './commands/upload';
import ServerInfo from './commands/server-info';
import LoginKey from './commands/login/key';
import Logout from './commands/logout';
program.name('immich').description('Immich command line interface');
@@ -12,6 +15,11 @@ program
.addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false))
.addOption(new Option('-i, --ignore [paths...]', 'Paths to ignore').env('IMMICH_IGNORE_PATHS'))
.addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false))
.addOption(
new Option('-a, --album', 'Automatically create albums based on folder name')
.env('IMMICH_AUTO_CREATE_ALBUM')
.default(false),
)
.addOption(
new Option('-n, --dry-run', "Don't perform any actions, just show what will be done")
.env('IMMICH_DRY_RUN')
@@ -20,33 +28,13 @@ program
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
.argument('[paths...]', 'One or more paths to assets to be uploaded')
.action(async (paths, options) => {
options.excludePatterns = options.ignore;
await new Upload().run(paths, options);
});
program
.command('import')
.description('Import existing assets')
.usage('[options] [paths...]')
.addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false))
.addOption(
new Option('-n, --dry-run', "Don't perform any actions, just show what will be done")
.env('IMMICH_DRY_RUN')
.default(false),
)
.addOption(new Option('-i, --ignore [paths...]', 'Paths to ignore').env('IMMICH_IGNORE_PATHS').default(false))
.addOption(new Option('--no-read-only', 'Import files without read-only protection, allowing Immich to manage them'))
.argument('[paths...]', 'One or more paths to assets to be imported')
.action(async (paths, options) => {
options.import = true;
options.excludePatterns = options.ignore;
options.exclusionPatterns = options.ignore;
await new Upload().run(paths, options);
});
program
.command('server-info')
.description('Display server information')
.action(async () => {
await new ServerInfo().run();
});
@@ -60,4 +48,11 @@ program
await new LoginKey().run(paths, options);
});
program
.command('logout')
.description('Remove stored credentials')
.action(async () => {
await new Logout().run();
});
program.parse(process.argv);

View File

@@ -1,235 +1,206 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { CrawlService } from './crawl.service';
import mockfs from 'mock-fs';
import { toIncludeSameMembers } from 'jest-extended';
import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
import { CrawlOptionsDto } from 'src/cores/dto/crawl-options-dto';
import { CrawlService } from '.';
const matchers = require('jest-extended');
expect.extend(matchers);
interface Test {
test: string;
options: CrawlOptionsDto;
files: Record<string, boolean>;
}
const crawlService = new CrawlService();
const cwd = process.cwd();
describe('CrawlService', () => {
beforeAll(() => {
// Write a dummy output before mock-fs to prevent some annoying errors
console.log();
});
const tests: Test[] = [
{
test: 'should return empty when crawling an empty path list',
options: {
pathsToCrawl: [],
},
files: {},
},
{
test: 'should crawl a single path',
options: {
pathsToCrawl: ['/photos/'],
},
files: {
'/photos/image.jpg': true,
},
},
{
test: 'should exclude by file extension',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/*.tif'],
},
files: {
'/photos/image.jpg': true,
'/photos/image.tif': false,
},
},
{
test: 'should exclude by file extension without case sensitivity',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/*.TIF'],
},
files: {
'/photos/image.jpg': true,
'/photos/image.tif': false,
},
},
{
test: 'should exclude by folder',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/raw/**'],
},
files: {
'/photos/image.jpg': true,
'/photos/raw/image.jpg': false,
'/photos/raw2/image.jpg': true,
'/photos/folder/raw/image.jpg': false,
'/photos/crawl/image.jpg': true,
},
},
{
test: 'should crawl multiple paths',
options: {
pathsToCrawl: ['/photos/', '/images/', '/albums/'],
},
files: {
'/photos/image1.jpg': true,
'/images/image2.jpg': true,
'/albums/image3.jpg': true,
},
},
{
test: 'should support globbing paths',
options: {
pathsToCrawl: ['/photos*'],
},
files: {
'/photos1/image1.jpg': true,
'/photos2/image2.jpg': true,
'/images/image3.jpg': false,
},
},
{
test: 'should crawl a single path without trailing slash',
options: {
pathsToCrawl: ['/photos'],
},
files: {
'/photos/image.jpg': true,
},
},
{
test: 'should crawl a single path',
options: {
pathsToCrawl: ['/photos/'],
},
files: {
'/photos/image.jpg': true,
'/photos/subfolder/image1.jpg': true,
'/photos/subfolder/image2.jpg': true,
'/image1.jpg': false,
},
},
{
test: 'should filter file extensions',
options: {
pathsToCrawl: ['/photos/'],
},
files: {
'/photos/image.jpg': true,
'/photos/image.txt': false,
'/photos/1': false,
},
},
{
test: 'should include photo and video extensions',
options: {
pathsToCrawl: ['/photos/', '/videos/'],
},
files: {
'/photos/image.jpg': true,
'/photos/image.jpeg': true,
'/photos/image.heic': true,
'/photos/image.heif': true,
'/photos/image.png': true,
'/photos/image.gif': true,
'/photos/image.tif': true,
'/photos/image.tiff': true,
'/photos/image.webp': true,
'/photos/image.dng': true,
'/photos/image.nef': true,
'/videos/video.mp4': true,
'/videos/video.mov': true,
'/videos/video.webm': true,
},
},
{
test: 'should check file extensions without case sensitivity',
options: {
pathsToCrawl: ['/photos/'],
},
files: {
'/photos/image.jpg': true,
'/photos/image.Jpg': true,
'/photos/image.jpG': true,
'/photos/image.JPG': true,
'/photos/image.jpEg': true,
'/photos/image.TIFF': true,
'/photos/image.tif': true,
'/photos/image.dng': true,
'/photos/image.NEF': true,
},
},
{
test: 'should normalize the path',
options: {
pathsToCrawl: ['/photos/1/../2'],
},
files: {
'/photos/1/image.jpg': false,
'/photos/2/image.jpg': true,
},
},
{
test: 'should return absolute paths',
options: {
pathsToCrawl: ['photos'],
},
files: {
[`${cwd}/photos/1.jpg`]: true,
[`${cwd}/photos/2.jpg`]: true,
[`/photos/3.jpg`]: false,
},
},
];
it('should crawl a single directory', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single file', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/image.jpg'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a file and a directory', async () => {
mockfs({
'/photos/image.jpg': '',
'/images/photo.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/image.jpg', '/images/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg', '/images/photo.jpg']);
});
it('should exclude by file extension', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.tif': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/*.tif'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should exclude by file extension without case sensitivity', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.tif': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/*.TIF'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should exclude by folder', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/raw/image.jpg': '',
'/photos/raw2/image.jpg': '',
'/photos/folder/raw/image.jpg': '',
'/photos/crawl/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/raw/**'];
options.recursive = true;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg', '/photos/raw2/image.jpg', '/photos/crawl/image.jpg']);
});
it('should crawl multiple paths', async () => {
mockfs({
'/photos/image1.jpg': '',
'/images/image2.jpg': '',
'/albums/image3.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/', '/images/', '/albums/'];
options.recursive = false;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image1.jpg', '/images/image2.jpg', '/albums/image3.jpg']);
});
it('should crawl a single path without trailing slash', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single path without recursion', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/subfolder/image1.jpg': '',
'/photos/subfolder/image2.jpg': '',
'/image1.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single path with recursion', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/subfolder/image1.jpg': '',
'/photos/subfolder/image2.jpg': '',
'/image1.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.recursive = true;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/subfolder/image1.jpg',
'/photos/subfolder/image2.jpg',
]);
});
it('should filter file extensions', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.txt': '',
'/photos/1': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should include photo and video extensions', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.jpeg': '',
'/photos/image.heic': '',
'/photos/image.heif': '',
'/photos/image.png': '',
'/photos/image.gif': '',
'/photos/image.tif': '',
'/photos/image.tiff': '',
'/photos/image.webp': '',
'/photos/image.dng': '',
'/photos/image.nef': '',
'/videos/video.mp4': '',
'/videos/video.mov': '',
'/videos/video.webm': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/', '/videos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/image.jpeg',
'/photos/image.heic',
'/photos/image.heif',
'/photos/image.png',
'/photos/image.gif',
'/photos/image.tif',
'/photos/image.tiff',
'/photos/image.webp',
'/photos/image.dng',
'/photos/image.nef',
'/videos/video.mp4',
'/videos/video.mov',
'/videos/video.webm',
]);
});
it('should check file extensions without case sensitivity', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.Jpg': '',
'/photos/image.jpG': '',
'/photos/image.JPG': '',
'/photos/image.jpEg': '',
'/photos/image.TIFF': '',
'/photos/image.tif': '',
'/photos/image.dng': '',
'/photos/image.NEF': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/image.Jpg',
'/photos/image.jpG',
'/photos/image.JPG',
'/photos/image.jpEg',
'/photos/image.TIFF',
'/photos/image.tif',
'/photos/image.dng',
'/photos/image.NEF',
]);
});
describe(CrawlService.name, () => {
const sut = new CrawlService(
['.jpg', '.jpeg', '.png', '.heif', '.heic', '.tif', '.nef', '.webp', '.tiff', '.dng', '.gif'],
['.mov', '.mp4', '.webm'],
);
afterEach(() => {
mockfs.restore();
});
describe('crawl', () => {
for (const { test, options, files } of tests) {
it(test, async () => {
mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, ''])));
const actual = await sut.crawl(options);
const expected = Object.entries(files)
.filter((entry) => entry[1])
.map(([file]) => file);
expect(actual.sort()).toEqual(expected.sort());
});
}
});
});

View File

@@ -1,47 +1,28 @@
import { CrawlOptionsDto } from 'src/cores/dto/crawl-options-dto';
import { ACCEPTED_FILE_EXTENSIONS } from '../cores';
import { glob } from 'glob';
import * as fs from 'fs';
export class CrawlService {
public async crawl(crawlOptions: CrawlOptionsDto): Promise<string[]> {
const pathsToCrawl: string[] = crawlOptions.pathsToCrawl;
private readonly extensions!: string[];
const directories: string[] = [];
const crawledFiles: string[] = [];
constructor(image: string[], video: string[]) {
this.extensions = image.concat(video).map((extension) => extension.replace('.', ''));
}
for await (const currentPath of pathsToCrawl) {
const stats = await fs.promises.stat(currentPath);
if (stats.isFile() || stats.isSymbolicLink()) {
crawledFiles.push(currentPath);
} else {
directories.push(currentPath);
}
crawl(crawlOptions: CrawlOptionsDto): Promise<string[]> {
const { pathsToCrawl, exclusionPatterns, includeHidden } = crawlOptions;
if (!pathsToCrawl) {
return Promise.resolve([]);
}
let searchPattern: string;
if (directories.length === 1) {
searchPattern = directories[0];
} else if (directories.length === 0) {
return crawledFiles;
} else {
searchPattern = '{' + directories.join(',') + '}';
}
const base = pathsToCrawl.length === 1 ? pathsToCrawl[0] : `{${pathsToCrawl.join(',')}}`;
const extensions = `*{${this.extensions}}`;
if (crawlOptions.recursive) {
searchPattern = searchPattern + '/**/';
}
searchPattern = `${searchPattern}/*.{${ACCEPTED_FILE_EXTENSIONS.join(',')}}`;
const globbedFiles = await glob(searchPattern, {
return glob(`${base}/**/${extensions}`, {
absolute: true,
nocase: true,
nodir: true,
ignore: crawlOptions.excludePatterns,
dot: includeHidden,
ignore: exclusionPatterns,
});
const returnedFiles = crawledFiles.concat(globbedFiles);
returnedFiles.sort();
return returnedFiles;
}
}

View File

@@ -1,2 +1 @@
export * from './upload.service';
export * from './crawl.service';

View File

@@ -46,7 +46,7 @@ export class SessionService {
// Check if server and api key are valid
const { data: userInfo } = await this.api.userApi.getMyUserInfo().catch((error) => {
throw new LoginError(`Failed to connect to the server: ${error.message}`);
throw new LoginError(`Failed to connect to server ${instanceUrl}: ${error.message}`);
});
console.log(`Logged in as ${userInfo.email}`);
@@ -78,7 +78,7 @@ export class SessionService {
private async ping(): Promise<void> {
const { data: pingResponse } = await this.api.serverInfoApi.pingServer().catch((error) => {
throw new Error(`Failed to connect to the server: ${error.message}`);
throw new Error(`Failed to connect to server ${this.api.apiConfiguration.instanceUrl}: ${error.message}`);
});
if (pingResponse.res !== 'pong') {

View File

@@ -1,24 +0,0 @@
import { UploadService } from './upload.service';
import axios from 'axios';
import FormData from 'form-data';
import { ApiConfiguration } from '../cores/api-configuration';
jest.mock('axios', () => jest.fn());
describe('UploadService', () => {
let uploadService: UploadService;
beforeEach(() => {
const apiConfiguration = new ApiConfiguration('https://example.com/api', 'key');
uploadService = new UploadService(apiConfiguration);
});
it('should call axios', async () => {
const data = new FormData();
await uploadService.upload(data);
expect(axios).toHaveBeenCalled();
});
});

View File

@@ -1,65 +0,0 @@
import axios, { AxiosRequestConfig } from 'axios';
import FormData from 'form-data';
import { ApiConfiguration } from '../cores/api-configuration';
export class UploadService {
private readonly uploadConfig: AxiosRequestConfig<any>;
private readonly checkAssetExistenceConfig: AxiosRequestConfig<any>;
private readonly importConfig: AxiosRequestConfig<any>;
constructor(apiConfiguration: ApiConfiguration) {
this.uploadConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/upload`,
headers: {
'x-api-key': apiConfiguration.apiKey,
},
maxContentLength: Number.POSITIVE_INFINITY,
maxBodyLength: Number.POSITIVE_INFINITY,
};
this.importConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/import`,
headers: {
'x-api-key': apiConfiguration.apiKey,
'Content-Type': 'application/json',
},
maxContentLength: Number.POSITIVE_INFINITY,
maxBodyLength: Number.POSITIVE_INFINITY,
};
this.checkAssetExistenceConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/bulk-upload-check`,
headers: {
'x-api-key': apiConfiguration.apiKey,
'Content-Type': 'application/json',
},
};
}
public checkIfAssetAlreadyExists(path: string, checksum: string) {
this.checkAssetExistenceConfig.data = JSON.stringify({ assets: [{ id: path, checksum: checksum }] });
// TODO: retry on 500 errors?
return axios(this.checkAssetExistenceConfig);
}
public upload(data: FormData) {
this.uploadConfig.data = data;
// TODO: retry on 500 errors?
return axios(this.uploadConfig);
}
public import(data: any) {
this.importConfig.data = data;
// TODO: retry on 500 errors?
return axios(this.importConfig);
}
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"strict": true,
"declaration": true,
"removeComments": true,
@@ -8,7 +8,7 @@
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2017",
"target": "es2022",
"moduleResolution": "node16",
"sourceMap": true,
"outDir": "./dist",

5
docker/README.md Normal file
View File

@@ -0,0 +1,5 @@
> [!CAUTION]
> Make sure to use the docker-compose.yml of the current release:
> https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
>
> The compose file on main may not be compatible with the latest release.

View File

@@ -1,26 +1,39 @@
# See:
# - https://immich.app/docs/developer/setup
# - https://immich.app/docs/developer/troubleshooting
version: "3.8"
name: immich-dev
x-server-build: &server-common
image: immich-server-dev:latest
build:
context: ../
dockerfile: server/Dockerfile
target: dev
volumes:
- ../server:/usr/src/app
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
environment:
- NODE_ENV=development
ulimits:
nofile:
soft: 1048576
hard: 1048576
services:
immich-server:
container_name: immich_server
image: immich-server-dev:latest
build:
context: ../server
dockerfile: Dockerfile
target: builder
command: npm run start:debug immich
volumes:
- ../server:/usr/src/app
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
<<: *server-common
ports:
- 3001:3001
- 9230:9230
env_file:
- .env
environment:
- NODE_ENV=development
depends_on:
- redis
- database
@@ -28,26 +41,13 @@ services:
immich-microservices:
container_name: immich_microservices
image: immich-microservices:latest
command: npm run start:debug microservices
<<: *server-common
# extends:
# file: hwaccel.yml
# service: hwaccel
build:
context: ../server
dockerfile: Dockerfile
target: builder
command: npm run start:debug microservices
volumes:
- ../server:/usr/src/app
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 9231:9230
environment:
- NODE_ENV=development
depends_on:
- database
- immich-server
@@ -59,20 +59,19 @@ services:
build:
context: ../web
dockerfile: Dockerfile
target: dev
command: npm run dev --host
env_file:
- .env
environment:
# Rename these values for svelte public interface
- PUBLIC_IMMICH_SERVER_URL=${IMMICH_SERVER_URL}
- PUBLIC_IMMICH_API_URL_EXTERNAL=${IMMICH_API_URL_EXTERNAL}
ports:
- 3000:3000
- 2283:3000
- 24678:24678
volumes:
- ../web:/usr/src/app
- /usr/src/app/node_modules
ulimits:
nofile:
soft: 1048576
hard: 1048576
restart: unless-stopped
depends_on:
- immich-server
@@ -109,11 +108,11 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
env_file:
- .env
environment:
@@ -125,22 +124,5 @@ services:
ports:
- 5432:5432
immich-proxy:
container_name: immich_proxy
image: immich-proxy-dev:latest
environment:
# Make sure these values get passed through from the env file
- IMMICH_SERVER_URL
- IMMICH_WEB_URL
build:
context: ../nginx
dockerfile: Dockerfile
ports:
- 2283:8080
depends_on:
- immich-server
- immich-web
restart: unless-stopped
volumes:
model-cache:

View File

@@ -1,23 +1,44 @@
version: "3.8"
name: immich-prod
x-server-build: &server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
services:
immich-server:
container_name: immich_server
image: immich-server:latest
build:
context: ../server
dockerfile: Dockerfile
command: ["./start-server.sh"]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
command: [ "./start-server.sh" ]
<<: *server-common
ports:
- 2283:3001
depends_on:
- redis
- database
- typesense
immich-microservices:
container_name: immich_microservices
command: [ "./start-microservices.sh" ]
<<: *server-common
# extends:
# file: hwaccel.yml
# service: hwaccel
depends_on:
- redis
- database
- typesense
- immich-server
immich-machine-learning:
container_name: immich_machine_learning
image: immich-machine-learning:latest
@@ -25,45 +46,11 @@ services:
context: ../machine-learning
dockerfile: Dockerfile
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- model-cache:/cache
env_file:
- .env
restart: always
immich-microservices:
container_name: immich_microservices
image: immich-microservices:latest
# extends:
# file: hwaccel.yml
# service: hwaccel
build:
context: ../server
dockerfile: Dockerfile
command: ["./start-microservices.sh"]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
depends_on:
- database
- immich-server
- typesense
restart: always
immich-web:
container_name: immich_web
image: immich-web:latest
build:
context: ../web
dockerfile: Dockerfile
env_file:
- .env
restart: always
depends_on:
- immich-server
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
@@ -73,17 +60,17 @@ services:
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
- ${UPLOAD_LOCATION}/typesense:/data
restart: always
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
restart: always
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
env_file:
- .env
environment:
@@ -91,28 +78,8 @@ services:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
volumes:
- pgdata:/var/lib/postgresql/data
restart: always
immich-proxy:
container_name: immich_proxy
image: immich-proxy:latest
environment:
# Make sure these values get passed through from the env file
- IMMICH_SERVER_URL
- IMMICH_WEB_URL
build:
context: ../nginx
dockerfile: Dockerfile
ports:
- 2283:8080
logging:
driver: none
depends_on:
- immich-server
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
restart: always
volumes:
pgdata:
model-cache:
tsdata:

View File

@@ -1,40 +1,33 @@
version: "3.8"
# Compose file for dockerized end-to-end testing of the backend
name: "immich-test-e2e"
services:
immich-server-test:
image: immich-server-test
immich-server:
image: immich-server-dev:latest
build:
context: ../server
dockerfile: Dockerfile
target: builder
context: ../
dockerfile: server/Dockerfile
target: dev
command: npm run test:e2e
volumes:
- ../server:/usr/src/app
- /usr/src/app/node_modules
environment:
- DB_HOSTNAME=immich-database-test
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=e2e_test
- IMMICH_RUN_ALL_TESTS=true
depends_on:
- immich-database-test
networks:
- immich-test-network
- database
immich-database-test:
container_name: immich-database-test
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
database:
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
command: -c fsync=off
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: e2e_test
networks:
- immich-test-network
logging:
driver: none
networks:
immich-test-network:

View File

@@ -1,15 +1,27 @@
version: "3.8"
#
# WARNING: Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
#
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: ["start.sh", "immich"]
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:3001
depends_on:
- redis
- database
@@ -22,7 +34,7 @@ services:
# extends:
# file: hwaccel.yml
# service: hwaccel
command: ["start.sh", "microservices"]
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
@@ -43,13 +55,6 @@ services:
- .env
restart: always
immich-web:
container_name: immich_web
image: ghcr.io/immich-app/immich-web:${IMMICH_VERSION:-release}
env_file:
- .env
restart: always
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
@@ -64,12 +69,12 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
restart: always
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
env_file:
- .env
environment:
@@ -80,20 +85,6 @@ services:
- pgdata:/var/lib/postgresql/data
restart: always
immich-proxy:
container_name: immich_proxy
image: ghcr.io/immich-app/immich-proxy:${IMMICH_VERSION:-release}
environment:
# Make sure these values get passed through from the env file
- IMMICH_SERVER_URL
- IMMICH_WEB_URL
ports:
- 2283:8080
depends_on:
- immich-server
- immich-web
restart: always
volumes:
pgdata:
model-cache:

24
docker/hwaccel-rkmpp.yml Normal file
View File

@@ -0,0 +1,24 @@
version: "3.8"
# Hardware acceleration for transcoding using RKMPP for Rockchip SOCs
# This is only needed if you want to use hardware acceleration for transcoding.
# Supported host OS is Ubuntu Jammy 22.04 with custom ffmpeg from ppa:liujianfeng1994/rockchip-multimedia
services:
hwaccel:
security_opt: # enables full access to /sys and /proc, still far better than privileged: true
- systempaths=unconfined
- apparmor=unconfined
group_add:
- video
devices:
- /dev/rga:/dev/rga
- /dev/dri:/dev/dri
- /dev/dma_heap:/dev/dma_heap
- /dev/mpp_service:/dev/mpp_service
volumes:
- /usr/bin/ffmpeg:/usr/bin/ffmpeg_mpp:ro
- /lib/aarch64-linux-gnu:/lib/ffmpeg-mpp:ro
- /lib/aarch64-linux-gnu/libblas.so.3:/lib/ffmpeg-mpp/libblas.so.3:ro # symlink is resolved by mounting
- /lib/aarch64-linux-gnu/liblapack.so.3:/lib/ffmpeg-mpp/liblapack.so.3:ro # symlink is resolved by mounting
- /lib/aarch64-linux-gnu/pulseaudio/libpulsecommon-15.99.so:/lib/ffmpeg-mpp/libpulsecommon-15.99.so:ro

View File

@@ -33,8 +33,6 @@ To be concise, Immich can now read in the gallery files, register the path into
- Only new files that are added to the gallery will be detected.
- Deleted and moved files will not be detected.
You can find more information on how to use the feature by reading the documentation [here](/docs/features/read-only-gallery).
## Memory feature
This is considered a fun feature that the team and I wanted to build for so long, but we had to put it off because of the refactoring of the code base. The code base is now in a good enough form to circle back and add more exciting features.

View File

@@ -12,13 +12,9 @@ sidebar_position: 7
| ![cloud-cross](/img/cloud-off.svg) | Asset is only available locally and has not yet been backed up |
| ![cloud-done](/img/cloud-done.svg) | Asset was uploaded from this device and is now backed up in the cloud/server and still available in original on the device |
### How can I sync an existing directory with Immich's server?
### Can I add my existing photo library?
Immich doesn't have two-way synchronization ([yet](https://github.com/immich-app/immich/discussions/1006)), but the [command line tool](/docs/features/bulk-upload.md) can bulk upload items from a directory to Immich.
### Why doesn't Immich watch an existing photo gallery directory?
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
Yes, with an [external library](/docs/features/libraries.md).
### Why are only photos and not videos being uploaded to Immich?

View File

@@ -17,13 +17,13 @@ docker exec -t immich_postgres pg_dumpall -c -U postgres | gzip > "/path/to/back
```
```bash title='Restore'
docker-compose down -v # CAUTION! Deletes all Immich data to start from scratch.
docker-compose pull # Update to latest version of Immich (if desired)
docker-compose create # Create Docker containers for Immich apps without running them.
docker compose down -v # CAUTION! Deletes all Immich data to start from scratch.
docker compose pull # Update to latest version of Immich (if desired)
docker compose create # Create Docker containers for Immich apps without running them.
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
gunzip < "/path/to/backup/dump.sql.gz" | docker exec -i immich_postgres psql -U postgres -d immich # Restore Backup
docker-compose up -d # Start remainder of Immich apps
docker compose up -d # Start remainder of Immich apps
```
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 501 KiB

View File

@@ -1,21 +1,6 @@
# Reverse Proxy
When deploying Immich it is important to understand that a reverse proxy is required in front of the server and web container. The reverse proxy acts as an intermediary between the user and container, forwarding requests to the correct container based on the URL path.
## Default Reverse Proxy
Immich provides a default nginx reverse proxy preconfigured to perform the correct routing and set the necessary headers for the server and web container to use. These headers are crucial to redirect to the correct URL and determine the client's IP address.
## Using a Different Reverse Proxy
While the reverse proxy provided by Immich works well for basic deployments, some users may want to use a different reverse proxy. Fortunately, Immich is flexible enough to accommodate different reverse proxies. Users can either:
1. Add another reverse proxy on top of Immich's reverse proxy
2. Completely replace the default reverse proxy
## Adding a Custom Reverse Proxy
Users can deploy a custom reverse proxy that forwards requests to Immich's reverse proxy. This way, the new reverse proxy can handle TLS termination, load balancing, or other advanced features, while still delegating routing decisions to Immich's reverse proxy. All reverse proxies between Immich and the user must forward all headers and set the `Host`, `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` headers to their appropriate values. Additionally, your reverse proxy should allow for big enough uploads. By following these practices, you ensure that all custom reverse proxies are fully compatible with Immich.
Users can deploy a custom reverse proxy that forwards requests to Immich. This way, the reverse proxy can handle TLS termination, load balancing, or other advanced features. All reverse proxies between Immich and the user must forward all headers and set the `Host`, `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` headers to their appropriate values. Additionally, your reverse proxy should allow for big enough uploads. By following these practices, you ensure that all custom reverse proxies are fully compatible with Immich.
### Nginx example config
@@ -43,7 +28,3 @@ server {
}
}
```
## Replacing the Default Reverse Proxy
Replacing Immich's default reverse proxy is an advanced deployment and support may be limited. When replacing Immich's default proxy it is important to ensure that requests to `/api/*` are routed to the server container and all other requests to the web container. Additionally, the previously mentioned headers should be configured accordingly. You may find our [nginx configuration file](https://github.com/immich-app/immich/blob/main/nginx/templates/default.conf.template) a helpful reference.

View File

@@ -51,8 +51,7 @@ immich-admin list-users
{
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
email: 'immich@example.com.com',
firstName: 'Immich',
lastName: 'Admin',
name: 'Immich Admin',
storageLabel: 'admin',
externalPath: null,
profileImagePath: 'upload/profile/e65e6f88-2a30-4dbe-8dd9-1885f4889b53/e65e6f88-2a30-4dbe-8dd9-1885f4889b53.jpg',

View File

@@ -34,7 +34,7 @@ The web app is a [TypeScript](https://www.typescriptlang.org/) project that uses
### CLI
The CLI is a [TypeScript](https://www.typescriptlang.org/) project that parses command line arguments to programmatically upload/import assets to an Immich server. See [Bulk Upload](/docs/features/bulk-upload.md) for more information about its usage.
The Immich CLI is an [npm](https://www.npmjs.com/) package that lets users control their Immich instance from the command line. It uses the API to perform various tasks, especially uploading assets. See the [CLI documentation](/docs/features/command-line-interface.md) for more information.
## Server

View File

@@ -9,6 +9,6 @@ npm run typeorm:migrations:generate ./src/infra/<migration-name>
```
2. Check if the migration file makes sense.
3. Move the migration file to folder `./src/infra/database/migrations` in your code editor.
3. Move the migration file to folder `./server/src/infra/migrations` in your code editor.
The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately.

View File

@@ -17,6 +17,5 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
| `misc/release/` | Scripts for version pumps and draft releases |
| `mobile/` | Source code for the mobile app, both Android and iOS |
| `nginx/` | Source code for the `immich-proxy` docker image |
| `server/` | Source code for the `immich-server` docker image |
| `web/` | Source code for the `immich-web` docker image |
| `web/` | Source code for the `web` |

View File

@@ -4,7 +4,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
## Generator
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server when running in development mode. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the command below to update the client SDK.
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
```bash
npm run api:generate # Run from the `server/` directory

View File

@@ -52,7 +52,7 @@ If you only want to do web development connected to an existing, remote backend,
3. Start the web development server
```
PUBLIC_IMMICH_SERVER_URL=https://demo.immich.app/api npm run dev
IMMICH_SERVER_URL=https://demo.immich.app/api npm run dev
```
## IDE setup
@@ -61,9 +61,15 @@ PUBLIC_IMMICH_SERVER_URL=https://demo.immich.app/api npm run dev
Setting these in the IDE give a better developer experience, auto-formatting code on save, and providing instant feedback on lint issues.
### Dart Code Metris
The mobile app uses DCM (Dart Code Metrics) for linting and metrics calculation. Please refer to the [Getting Started](https://dcm.dev/docs/getting-started/#installation) page for more information on setting up DCM
Note: Activating the license is not required.
### VSCode
Install `Flutter`, `Prettier`, `ESLint` and `Svelte` extensions.
Install `Flutter`, `DCM`, `Prettier`, `ESLint` and `Svelte` extensions.
in User `settings.json` (`cmd + shift + p` and search for `Open User Settings JSON`) add the following:

View File

@@ -12,6 +12,6 @@ The backend has an end-to-end test suite that can be called with `npm run test:e
Note that there is a bug in nodejs <20.8 that causes segmentation faults when running these tests. If you run into segfaults, ensure you are using at least version 20.8.
To perform a full e2e test, you need to run e2e tests inside docker. The easiest way to do that is to run `make test-e2e` in the root directory. This will build and start a docker-compose consisting of the server, microservices, and a postgres database. It will then perfom the tests and exit.
To perform a full e2e test, you need to run e2e tests inside docker. The easiest way to do that is to run `make test-e2e` in the root directory. This will build and start a docker-compose consisting of the server, microservices, and a postgres database. It will then perform the tests and exit.
If you manually install the dependencies (see the DOCKERFILE) on your development machine, you can also run the full e2e tests manually by setting the `IMMICH_RUN_ALL_TESTS` environment value to true, i.e. `IMMICH_RUN_ALL_TESTS=true npm run test:e2e`.

View File

@@ -0,0 +1,15 @@
# Troubleshooting
:::tip
A great option to get assistance with troubleshooting is to join our [Discord](https://discord.gg/D8JsnBEuKb) server, where we have a dedicated channel for `#contributing`.
:::
## Known Issues
### Running on Windows
Running Immich on Windows can be frustrating and there are lots of ways it can go wrong. Where possible we recommend using Docker on Linux. However, several people have had success running Immich on Windows using Docker via WSL2.
### NTFS Mounted Volumes
The docker-compose.dev.yml and docker-compose.prod.yml use volume mounts for the postgres database. On start-up, postgres will try to `chown` the data directory, but fail. See [this post](https://forums.docker.com/t/data-directory-var-lib-postgresql-data-pgdata-has-wrong-ownership/17963/24) for more information about this issue and possible solutions.

View File

@@ -1,177 +0,0 @@
# Bulk Upload (Using the CLI)
You can use the CLI to upload an existing gallery to the Immich server
[Immich CLI Repository](https://github.com/immich-app/CLI)
## Requirements
- Node.js 16 or above
- Npm
## Installation
```bash
npm i -g immich
```
Pre-installed on the `immich-server` container and can be easily accessed through
```
immich
```
### Options
| Parameter | Description |
| ---------------- | ------------------------------------------------------------------- |
| --yes / -y | Assume yes on all interactive prompts |
| --recursive / -r | Include subfolders |
| --delete / -da | Delete local assets after upload |
| --key / -k | User's API key |
| --server / -s | Immich's server address |
| --threads / -t | Number of threads to use (Default 5) |
| --album/ -al | Create albums for assets based on the parent folder or a given name |
| --import/ -i | Import gallery (assets are not uploaded) |
## Quick Start
Specify user's credential, Immich's server address and port and the directory you would like to upload videos/photos from.
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api file1.jpg file2.jpg
```
By default, subfolders are not included. To upload a directory including subfolder, use the --recursive option:
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive directory/
```
### Obtain the API Key
The API key can be obtained in the user setting panel on the web interface.
![Obtain Api Key](./img/obtain-api-key.png)
---
### Run via Docker
You can run the CLI inside of a docker container to avoid needing to install anything.
:::caution Running inside Docker
Be aware that as this runs inside a container, you need to mount the folder from which you want to import into the container.
:::
```bash title="Upload current directory"
cd /DIRECTORY/WITH/IMAGES
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
```bash title="Upload target directory"
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
```bash title="Create an alias"
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
immich upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
```
:::tip Internal networking
If you are running the CLI container on the same machine as your Immich server, you may not be able to reach the external address. In that case, try the following steps:
1. Find the internal Docker network used by Immich via `docker network ls`.
2. Adapt the above command to pass the `--network <immich_network>` argument to `docker run`, substituting `<immich_network>` with the result from step 1.
3. Use `--server http://immich-server:3001` for the upload command instead of the external address.
```bash title="Upload to internal address"
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://immich-server:3001
```
:::
### Run from source
```bash title="Clone Repository"
git clone https://github.com/immich-app/CLI
```
```bash title="Install dependencies"
npm install
```
```bash title="Build the project"
npm run build
```
```bash title="Run the command"
node bin/index.js upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive your/asset/directory
```
---
## Importing existing libraries
If you do not wish to upload files into the server, existing files can be imported into the immich gallery through the use of the `--import` flag.
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive directory/ --import
```
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api file1.jpg file2.jpg --import
```
The `immich-server` and `immich-microservices` containers must be able to access the files, or directories at the path referenced in the command. The directories referenced must be set under a user's `External Path` setting. More detailed instructions can be found [here](/docs/features/read-only-gallery).
:::tip Matching volume references
The import command is most easily run on the machine running the immich service, as the path to the files on the machine running the command and the server much match identically.
If you are running immich within docker, the volume pointing to your existing library should be identical with your host machine.
```diff title="docker-compose.yml"
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /path/to/media:/path/to/media
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /path/to/media:/path/to/media
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
```
The proper command for above would be as shown below. You should have access to `/path/to/media` exactly on the environment the CLI command is being run on
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
```
If you are running the import using the docker command, please note that the volumes should point to the `/path/to/media` exactly on the environment the CLI command is being run on
```
docker run -it --rm -v "/path/to/media:/path/to/media" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
```
:::

View File

@@ -0,0 +1,139 @@
# The Immich CLI
Immich has a CLI that allows you to perform certain actions from the command line. This CLI replaces the [legacy CLI](https://github.com/immich-app/CLI) that was previously available. The CLI is hosted in the [cli folder of the the main Immich github repository](https://github.com/immich-app/immich/tree/main/cli).
## Features
- Upload photos and videos to Immich
- Check server version
More features are planned for the future.
:::tip Google Photos Takeout
If you are looking to import your Google Photos takeout, we recommed this community maintained tool [immich-go](https://github.com/simulot/immich-go)
:::
## Requirements
- Node.js 20.0 or above
- Npm
## Installation
```bash
npm i -g @immich/cli
```
NOTE: if you previously installed the legacy CLI, you will need to uninstall it first:
```bash
npm uninstall -g immich
```
## Usage
```
immich
```
```
Usage: immich [options] [command]
Immich command line interface
Options:
-h, --help display help for command
Commands:
upload [options] [paths...] Upload assets
server-info Display server information
login-key [instanceUrl] [apiKey] Login using an API key
logout Remove stored credentials
help [command] display help for command
```
## Commands
The upload command supports the following options:
```
Usage: immich upload [options] [paths...]
Upload assets
Arguments:
paths One or more paths to assets to be uploaded
Options:
-r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE)
-i, --ignore [paths...] Paths to ignore (env: IMMICH_IGNORE_PATHS)
-h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH)
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
--help display help for command
```
Note that the above options can read from environment variables as well.
## Quick Start
You begin by authenticating to your Immich server.
```bash
immich login-key [instanceUrl] [apiKey]
```
For instance,
```bash
immich login-key http://192.168.1.216:2283/api HFEJ38DNSDUEG
```
This will store your credentials in a file in your home directory. Please keep the file secure, either by performing the logout command after you are done, or deleting it manually.
Once you are authenticated, you can upload assets to your Immich server.
```bash
immich upload file1.jpg file2.jpg
```
By default, subfolders are not included. To upload a directory including subfolder, use the --recursive option:
```bash
immich upload --recursive directory/
```
If you are unsure what will happen, you can use the `--dry-run` option to see what would happen without actually performing any actions.
```bash
immich upload --dry-run --recursive directory/
```
By default, the upload command will hash the files before uploading them. This is to avoid uploading the same file multiple times. If you are sure that the files are unique, you can skip this step by passing the `--skip-hash` option. Note that Immich always performs its own deduplication through hashing, so this is merely a performance consideration. If you have good bandwidth it might be faster to skip hashing.
```bash
immich upload --skip-hash --recursive directory/
```
You can automatically create albums based on the folder name by passing the `--album` option. This will automatically create albums for each uploaded asset based on the name of the folder they are in.
```bash
immich upload --album --recursive directory/
```
It is possible to skip assets matching a glob pattern by passing the `--ignore` option. See [the library documentation](docs/features/libraries.md) on how to use glob patterns. You can add several exclusion patterns if needed.
```bash
immich upload --ignore **/Raw/** --recursive directory/
```
```bash
immich upload --ignore **/Raw/** **/*.tif --recursive directory/
```
### Obtain the API Key
The API key can be obtained in the user setting panel on the web interface.
![Obtain Api Key](./img/obtain-api-key.png)

View File

@@ -1,5 +1,7 @@
# Facial Recognition
## Overview
Immich recognizes faces in your photos and videos and groups them together. You can then assign names to the faces and search for them.
The list of people is shown in the Explore page.
@@ -13,3 +15,16 @@ Upon clicking on a person, a list of assets that contain their face will be show
The asset detail view will also show the faces that are recognized in the asset.
<img src={require('./img/facial-recognition-3.png').default} title='Facial Recognition 3' />
## Actions
Additional actions you can do with a detected person are:
- Change the feature face photo of the person
- Set date of birth
- Merge two or more detected faces into one person
- Hide face
It can be found from the app bar when you access the detial view of a person
<img src={require('./img/facial-recognition-4.png').default} title='Facial Recognition 4' width="70%"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

View File

@@ -75,7 +75,7 @@ Some basic examples:
- `*.tif` will exclude all files with the extension `.tif`
- `hidden.jpg` will exclude all files named `hidden.jpg`
- `**/Raw/**` will exclude all files in any directory named `Raw`
- `*.(tif,jpg)` will exclude all files with the extension `.tif` or `.jpg`
- `*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg`
### Nightly job
@@ -85,7 +85,7 @@ There is an automatic job that's run once a day and refreshes all modified files
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
- `/home/user/old-pics`: a folder contining childhood photos.
- `/home/user/old-pics`: a folder containing childhood photos.
- `/mnt/nas/christmas-trip`: photos from a christmas trip. The subfolder `/mnt/nas/christmas-trip/Raw` contains the raw files directly from the DSLR. We don't want to import the raw files to Immich
- `/mnt/media/videos`: Videos from the same christmas trip.

View File

@@ -1,13 +1,9 @@
import MobileAppDownload from '../partials/_mobile-app-download.md';
import MobileAppLogin from '../partials/_mobile-app-login.md';
import MobileAppBackup from '../partials/_mobile-app-login.md';
import MobileAppBackup from '../partials/_mobile-app-backup.md';
# Mobile App
:::tip
To upload from other devices, try using the [Bulk Upload CLI](/docs/features/bulk-upload.md).
:::
## Download
<MobileAppDownload />

View File

@@ -1,97 +0,0 @@
# Read-only Gallery [Deprecated]
:::caution
This feature is being deprecated in favor of [Libraries](/docs/features/libraries.md).
:::
## Overview
This feature enables users to use an existing gallery without uploading the assets to Immich.
Upon syncing the file information, it will be read by Immich to generate supported files.
## Usage
:::tip Example scenario
On the VM/system that Immich is running, I have 2 galleries that I want to use with Immich.
- My gallery is stored at `/mnt/media/precious-memory`
- My wife's gallery is stored at `/mnt/media/childhood-memory`
We will use those values in the steps below.
:::
### Mount the gallery to the containers.
`immich-server` and `immich-microservices` containers will need access to the gallery. Mount the directory path as in the example below
```diff title="docker-compose.yml"
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
```
:::tip
Internal and external path have to be identical.
:::
_Remember to bring the container down/up to register the changes. Make sure you can see the mounted path in the container._
### Register the path for the user.
This action is done by the admin of the instance.
- Navigate to `Administration > Users` page on the web.
- Click on the user edit button.
- Add the gallery path to the `External Path` field for the corresponding user and confirm the changes.
<img src={require('./img/me.png').default} width='33%' title='My Account Storage Path' />
<img src={require('./img/my-wife.png').default} width='33%' title='My Wifes Account Storage Path' />
### Sync with the CLI tool.
- Install or update the [CLI Tool](/docs/features/bulk-upload.md). The import feature is supported from version `v0.39.0` of the CLI
- Run the command below to sync the gallery with Immich.
```bash title="Import my gallery"
immich upload --key <my-api-key> --server http://my-server-ip:2283/api /mnt/media/precious-memory --recursive --import
```
```bash title="Import my wife gallery"
immich upload --key <my-wife-api-key> --server http://my-server-ip:2283/api /mnt/media/childhood-memory --recursive --import
```
The `--import` flag will tell Immich to import the files by path instead of uploading them.

View File

@@ -2,7 +2,7 @@
To alleviate [performance issues on low-memory systems](/docs/FAQ.md#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine-learning container on a more powerful system (e.g. your laptop or desktop computer):
- Set `IMMICH_MACHINE_LEARNING_URL` to point to the designated ML system, e.g. `http://workstation:3003`.
- Set the URL in Machine Learning Settings on the Admin Settings page to point to the designated ML system, e.g. `http://workstation:3003`.
- Copy the following `docker-compose.yml` to your ML system.
- Start the container by running `docker-compose up -d` or `docker compose up -d` (depending on your Docker version).

View File

@@ -0,0 +1,42 @@
# Python File Upload
```python
#!/usr/bin/python3
import requests
import os
from datetime import datetime
API_KEY = 'YOUR_API_KEY' # replace with a valid api key
BASE_URL = 'http://127.0.0.1:2283/api' # replace as needed
def upload(file):
stats = os.stat(file)
headers = {
'Accept': 'application/json',
'x-api-key': API_KEY
}
data = {
'deviceAssetId': f'{file}-{stats.st_mtime}',
'deviceId': 'python',
'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime),
'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime),
'isFavorite': 'false',
}
files = {
'assetData': open(file, 'rb')
}
response = requests.post(
f'{BASE_URL}/asset/upload', headers=headers, data=data, files=files)
print(response.json())
# {'id': 'ef96f635-61c7-4639-9e60-61a11c4bbfba', 'duplicate': False}
upload('./test.jpg')
```

View File

@@ -17,6 +17,12 @@ The default configuration looks like this:
"targetAudioCodec": "aac",
"targetResolution": "720",
"maxBitrate": "0",
"bframes": -1,
"refs": 0,
"gopSize": 0,
"npl": 0,
"temporalAQ": false,
"cqMode": "auto",
"twoPass": false,
"transcode": "required",
"tonemap": "hable",
@@ -44,9 +50,15 @@ The default configuration looks like this:
"sidecar": {
"concurrency": 5
},
"library": {
"concurrency": 5
},
"storageTemplateMigration": {
"concurrency": 5
},
"migration": {
"concurrency": 5
},
"thumbnailGeneration": {
"concurrency": 5
},
@@ -55,16 +67,16 @@ The default configuration looks like this:
}
},
"machineLearning": {
"classification": {
"minScore": 0.7,
"enabled": true,
"modelName": "microsoft/resnet-50"
},
"enabled": true,
"url": "http://immich-machine-learning:3003",
"classification": {
"enabled": true,
"modelName": "microsoft/resnet-50",
"minScore": 0.9
},
"clip": {
"enabled": true,
"modelName": "ViT-B-32::openai"
"modelName": "ViT-B-32__openai"
},
"facialRecognition": {
"enabled": true,
@@ -74,6 +86,14 @@ The default configuration looks like this:
"minFaces": 1
}
},
"map": {
"enabled": true,
"tileUrl": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
},
"reverseGeocoding": {
"enabled": true,
"citiesFileOverride": "cities500"
},
"oauth": {
"enabled": false,
"issuerUrl": "",
@@ -96,8 +116,27 @@ The default configuration looks like this:
"thumbnail": {
"webpSize": 250,
"jpegSize": 1440,
"quality": 90,
"quality": 80,
"colorspace": "p3"
},
"newVersionCheck": {
"enabled": true
},
"trash": {
"enabled": true,
"days": 30
},
"theme": {
"customCss": ""
},
"library": {
"scan": {
"enabled": true,
"cronExpression": "0 0 * * *"
}
},
"stylesheets": {
"css": ""
}
}
```

View File

@@ -122,28 +122,6 @@ TYPESENSE_API_KEY=some-random-text
PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
####################################################################################
# Alternative Service Addresses - Optional
#
# This is an advanced feature for users who may be running their immich services on different hosts.
# It will not change which address or port that services bind to within their containers, but it will change where other services look for their peers.
# Note: immich-microservices is bound to 3002, but no references are made
####################################################################################
IMMICH_WEB_URL=http://immich-web:3000
IMMICH_SERVER_URL=http://immich-server:3001
####################################################################################
# Alternative API's External Address - Optional
#
# This is an advanced feature used to control the public server endpoint returned to clients during Well-known discovery.
# You should only use this if you want mobile apps to access the immich API over a custom URL. Do not include trailing slash.
# NOTE: At this time, the web app will not be affected by this setting and will continue to use the relative path: /api
# Examples: http://localhost:3001, http://immich-api.example.com, etc
####################################################################################
#IMMICH_API_URL_EXTERNAL=http://localhost:3001
###################################################################################
# Immich Version - Optional
#

View File

@@ -63,21 +63,6 @@ These environment variables are used by the `docker-compose.yml` file and do **N
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
## URLs
| Variable | Description | Default | Services |
| :------------------------- | :---------------------- | :-------------------------: | :--------- |
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
:::info
The above paths are modifying the internal paths of the containers.
:::
## Database
| Variable | Description | Default | Services |
@@ -188,19 +173,18 @@ Typesense URL example JSON before encoding:
| Variable | Description | Default | Services |
| :----------------------------------------------- | :---------------------------------------------------------------- | :-----------------: | :--------------- |
| `MACHINE_LEARNING_MODEL_TTL`<sup>\*1</sup> | Inactivity time (s) before a model is unloaded (disabled if <= 0) | `0` | machine learning |
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if <= 0) | `300` | machine learning |
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if <= 0) | `10` | machine learning |
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*2</sup> | Thread count of the request thread pool (disabled if <= 0) | number of CPU cores | machine learning |
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if <= 0) | number of CPU cores | machine learning |
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
| `MACHINE_LEARNING_WORKERS`<sup>\*3</sup> | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
\*1: This is an experimental feature. It may result in increased memory use over time when loading models repeatedly.
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
\*2: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
\*3: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around.
\*2: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around.
:::info

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 80
sidebar_position: 90
---
import RegisterAdminUser from '../partials/_register-admin.md';

View File

@@ -0,0 +1,192 @@
---
sidebar_position: 80
---
# TrueNAS SCALE [Community]
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
**Please report issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).**
:::
Immich can easily be installed on TrueNAS SCALE via the **Community** train application.
Consider reviewing the TrueNAS [Apps tutorial](https://www.truenas.com/docs/scale/scaletutorials/apps/) if you have not previously configured applications on your system.
TrueNAS SCALE makes installing and updating Immich easy, but you must use the Immich web portal and mobile app to configure accounts and access libraries.
## First Steps
The Immich app in TrueNAS SCALE installs, completes the initial configuration, then starts the Immich web portal.
When updates become available, SCALE alerts and provides easy updates.
Before installing the Immich app in SCALE, review the [Environment Variables](/docs/install/environment-variables.md) documentation to see if you want to configure any during installation.
You can configure environment variables at any time after deploying the application.
You can allow SCALE to create the datasets Immich requires automatically during app installation.
Or before beginning app installation, [create the datasets](https://www.truenas.com/docs/scale/scaletutorials/storage/datasets/datasetsscale/) to use in the **Storage Configuration** section during installation.
Immich requires seven datasets: **library**, **pgBackup**, **pgData**, **profile**, **thumbs**, **uploads**, and **video**.
You can organize these as one parent with seven child datasets, for example `mnt/tank/immich/library`, `mnt/tank/immich/pgBackup`, and so on.
## Installing the Immich Application
To install the **Immich** application, go to **Apps**, click **Discover Apps**, either begin typing Immich into the search field or scroll down to locate the **Immich** application widget.
<img
src={require('./img/truenas01.png').default}
width="50%"
alt="Immich App Widget"
className="border rounded-xl"
/>
Click on the widget to open the **Immich** application details screen.
<img
src={require('./img/truenas02.png').default}
width="100%"
alt="Immich App Details Screen"
className="border rounded-xl"
/>
Click **Install** to open the Immich application configuration screen.
Application configuration settings are presented in several sections, each explained below.
To find specific fields click in the **Search Input Fields** search field, scroll down to a particular section or click on the section heading on the navigation area in the upper-right corner.
<img
src={require('./img/truenas03.png').default}
width="100%"
alt="Install Immich Screen"
className="border rounded-xl"
/>
Accept the default values in **Application Name** and **Version**.
Accept the default value in **Timezone** or change to match your local timezone.
**Timezone** is only used by the Immich `exiftool` microservice if it cannot be determined from the image metadata.
Accept the default port in **Web Port**.
Immich requires seven storage datasets.
You can allow SCALE to create them for you, or use the dataset(s) created in [First Steps](#first-steps).
Select the storage options you want to use for **Immich Uploads Storage**, **Immich Library Storage**, **Immich Thumbs Storage**, **Immich Profile Storage**, **Immich Video Storage**, **Immich Postgres Data Storage**, **Immich Postgres Backup Storage**.
Select **ixVolume (dataset created automatically by the system)** in **Type** to let SCALE create the dataset or select **Host Path** to use the existing datasets created on the system.
Accept the defaults in Resources or change the CPU and memory limits to suit your use case.
Click **Install**.
The system opens the **Installed Applications** screen with the Immich app in the **Deploying** state.
When the installation completes it changes to **Running**.
<img
src={require('./img/truenas04.png').default}
width="100%"
alt="Immich Installed"
className="border rounded-xl"
/>
Click **Web Portal** on the **Application Info** widget to open the Immich web interface to set up your account and begin uploading photos.
:::tip
For more information on how to use the application once installed, please refer to the [Post Install](/docs/install/post-install.mdx) guide.
:::
## Editing Environment Variables
Go to the **Installed Applications** screen and select Immich from the list of installed applications.
Click **Edit** on the **Application Info** widget to open the **Edit Immich** screen.
The settings on the edit screen are the same as on the install screen.
You cannot edit **Storage Configuration** paths after the initial app install.
Click **Update** to save changes.
TrueNAS automatically updates, recreates, and redeploys the Immich container with the updated environment variables.
## Updating the App
When updates become available, SCALE alerts and provides easy updates.
To update the app to the latest version, click **Update** on the **Application Info** widget from the **Installed Applications** screen.
Update opens an update window for the application that includes two selectable options, Images (to be updated) and Changelog. Click on the down arrow to see the options available for each.
Click **Upgrade** to begin the process and open a counter dialog that shows the upgrade progress. When complete, the update badge and buttons disappear and the application Update state on the Installed screen changes from Update Available to Up to date.
## Understanding Immich Settings in TrueNAS SCALE
Accept the default value or enter a name in **Application Name** field.
In most cases use the default name, but if adding a second deployment of the application you must change this name.
Accept the default version number in **Version**.
When a new version becomes available, the application has an update badge.
The **Installed Applications** screen shows the option to update applications.
### Immich Configuration Settings
You can accept the defaults in the **Immich Configuration** settings, or enter the settings you want to use.
<img
src={require('./img/truenas05.png').default}
width="100%"
alt="Configuration Settings"
className="border rounded-xl"
/>
Accept the default setting in **Timezone** or change to match your local timezone.
**Timezone** is only used by the Immich `exiftool` microservice if it cannot be determined from the image metadata.
You can enter a **Public Login Message** to display on the login page, or leave it blank.
### Networking Settings
Accept the default port numbers in **Web Port**.
The SCALE Immich app listens on port **30041**.
Refer to the TrueNAS [default port list](https://www.truenas.com/docs/references/defaultports/) for a list of assigned port numbers.
To change the port numbers, enter a number within the range 9000-65535.
<img
src={require('./img/truenas06.png').default}
width="100%"
alt="Networking Settings"
className="border rounded-xl"
/>
### Storage Settings
You can install Immich using the default setting **ixVolume (dataset created automatically by the system)** or use the host path option with datasets [created before installing the app](#first-steps).
<img
src={require('./img/truenas07.png').default}
width="100%"
alt="Configure Storage ixVolumes"
className="border rounded-xl"
/>
Select **Host Path (Path that already exists on the system)** to browse to and select the datasets.
<img
src={require('./img/truenas08.png').default}
width="100%"
alt="Configure Storage Host Paths"
className="border rounded-xl"
/>
### Resource Configuration Settings
Accept the default values in **Resources Configuration** or enter new CPU and memory values
By default, this application is limited to use no more than 4 CPU cores and 8 Gigabytes available memory. The application might use considerably less system resources.
<img
src={require('./img/truenas09.png').default}
width="100%"
alt="Resource Limits"
className="border rounded-xl"
/>
To customize the CPU and memory allocated to the container Immich uses, enter new CPU values as a plain integer value followed by the suffix m (milli).
Default is 4000m.
Accept the default value 8Gi allocated memory or enter a new limit in bytes.
Enter a plain integer followed by the measurement suffix, for example 129M or 123Mi.
Systems with compatible GPU(s) display devices in **GPU Configuration**.
See [Managing GPUs](https://www.truenas.com/docs/scale/scaletutorials/systemsettings/advanced/managegpuscale/) for more information about allocating isolated GPU devices in TrueNAS SCALE.

View File

@@ -98,12 +98,12 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
> Note: This can take several minutes depending on your Internet speed and Unraid hardware
9. Once on the Docker page you will see several Immich containers, one of them will be labelled `immich_proxy` and will have a port mapping. Visit the `IP:PORT` displayed in your web browser and you should see the Immich admin setup page.
9. Once on the Docker page you will see several Immich containers, one of them will be labelled `immich_web` and will have a port mapping. Visit the `IP:PORT` displayed in your web browser and you should see the Immich admin setup page.
<img
src={require('./img/unraid06.webp').default}
width="80%"
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
alt="Go to Docker Tab and visit the address listed next to immich-web"
/>
<details >
@@ -112,12 +112,12 @@ alt="Go to Docker Tab and visit the address listed next to immich-proxy"
<img
src={require('./img/unraid07.webp').default}
width="80%"
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
alt="Go to Docker Tab and visit the address listed next to immich-web"
/>
<img
src={require('./img/unraid08.webp').default}
width="90%"
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
alt="Go to Docker Tab and visit the address listed next to immich-web"
/>
</details>

View File

@@ -11,6 +11,6 @@ Running into an issue or have a question? Try the following:
3. Search through existing [GitHub Issues][github-issues].
4. Open a help ticket on [Discord][discord-link].
[github-issues]: https://github.com/immich-app/immich/releases
[github-issues]: https://github.com/immich-app/immich/issues
[github-releases]: https://github.com/immich-app/immich/releases
[discord-link]: https://discord.com/invite/D8JsnBEuKb

View File

@@ -4,7 +4,7 @@ sidebar_position: 1
# Introduction
<img src={require('./img/feature-panel.png').default} alt="Immich" />
<img src={require('./img/feature-panel.png').default} alt="Immich - Self-hosted photos and videos backup tool" />
## Welcome!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 334 KiB

View File

@@ -101,6 +101,11 @@ const config = {
position: 'right',
label: 'Docs',
},
{
to: '/milestones',
position: 'right',
label: 'Milestones',
},
{
to: '/docs/api',
position: 'right',

48
docs/package-lock.json generated
View File

@@ -10,6 +10,8 @@
"dependencies": {
"@docusaurus/core": "^2.4.3",
"@docusaurus/preset-classic": "^2.4.3",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"classnames": "^2.3.2",
@@ -2862,6 +2864,19 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
},
"node_modules/@mdi/js": {
"version": "7.3.67",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.3.67.tgz",
"integrity": "sha512-MnRjknFqpTC6FifhGHjZ0+QYq2bAkZFQqIj8JA2AdPZbBxUvr8QSgB2yPAJ8/ob/XkR41xlg5majDR3c1JP1hw=="
},
"node_modules/@mdi/react": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz",
"integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==",
"dependencies": {
"prop-types": "^15.7.2"
}
},
"node_modules/@mdx-js/mdx": {
"version": "1.6.22",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz",
@@ -13217,19 +13232,19 @@
}
},
"node_modules/tailwindcss": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
"integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.18.2",
"jiti": "^1.19.1",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@@ -16932,6 +16947,19 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
},
"@mdi/js": {
"version": "7.3.67",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.3.67.tgz",
"integrity": "sha512-MnRjknFqpTC6FifhGHjZ0+QYq2bAkZFQqIj8JA2AdPZbBxUvr8QSgB2yPAJ8/ob/XkR41xlg5majDR3c1JP1hw=="
},
"@mdi/react": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz",
"integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@mdx-js/mdx": {
"version": "1.6.22",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz",
@@ -24489,19 +24517,19 @@
}
},
"tailwindcss": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
"integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
"requires": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.18.2",
"jiti": "^1.19.1",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",

View File

@@ -19,6 +19,8 @@
"dependencies": {
"@docusaurus/core": "^2.4.3",
"@docusaurus/preset-classic": "^2.4.3",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"classnames": "^2.3.2",

View File

@@ -0,0 +1,90 @@
import React from 'react';
import Icon from '@mdi/react';
import { mdiCheckboxMarkedCircleOutline } from '@mdi/js';
import useIsBrowser from '@docusaurus/useIsBrowser';
export interface Item {
icon: string;
title: string;
description?: string;
release: string;
tag?: string;
date: Date;
dateType: DateType;
}
export enum DateType {
RELEASE = 'Release Date',
DATE = 'Date',
}
interface Props {
items: Item[];
}
export default function Timeline({ items }: Props): JSX.Element {
const isBrowser = useIsBrowser();
return (
<ul className="flex flex-col pl-4">
{items.map((item, index) => {
const isFirst = index === 0;
const isLast = index === items.length - 1;
const classNames: string[] = [];
if (isFirst) {
classNames.push('');
}
if (isLast) {
classNames.push('rounded rounded-b-full');
}
return (
<li key={index} className="flex min-h-24 w-[700px] max-w-[90vw]">
<div className="md:flex justify-start w-36 mr-8 items-center dark:text-immich-dark-primary text-immich-primary hidden">
{isBrowser ? item.date.toLocaleDateString(navigator.language) : ''}
</div>
<div className={`${isFirst && 'relative top-[50%]'} ${isLast && 'relative bottom-[50%]'}`}>
<div
className={`h-full border-solid border-4 border-immich-primary dark:border-immich-dark-primary ${
isFirst && 'rounded rounded-t-full'
} ${isLast && 'rounded rounded-b-full'}`}
></div>
</div>
<div className="z-10 flex items-center bg-immich-primary dark:bg-immich-dark-primary border-2 border-solid rounded-full dark:text-black text-white relative top-[50%] left-[-3px] translate-y-[-50%] translate-x-[-50%] w-8 h-8 shadow-lg ">
<Icon path={mdiCheckboxMarkedCircleOutline} size={1.25} />
</div>
<section className=" dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl flex flex-col w-full gap-2 p-4 md:ml-4 my-2 hover:bg-immich-primary/10 dark:hover:bg-immich-dark-primary/10 transition-all">
<div className="m-0 text-lg flex w-full items-center justify-between gap-2">
<p className="m-0 items-start flex gap-2">
<Icon path={item.icon} size={1} />
<span>{item.title}</span>
</p>
<span className="dark:text-immich-dark-primary text-immich-primary">
{item.tag ? (
<a
href={`https://github.com/immich-app/immich/releases/tag/${item.tag}`}
target="_blank"
rel="noopener"
>
[{item.release}]{' '}
</a>
) : (
<span>[{item.release}]</span>
)}
</span>
</div>
<div className="md:hidden text-xs">
{`${item.dateType} - ${isBrowser ? item.date.toLocaleDateString(navigator.language) : ''}`}
</div>
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{item.description}</p>
</section>
</li>
);
})}
</ul>
);
}

Some files were not shown because too many files have changed in this diff Show More