Compare commits

..

148 Commits

Author SHA1 Message Date
Fallenbagel
f2c659c6f3 chore(release): 1.1.1 2022-06-20 20:11:05 +05:00
semantic-release-bot
99f1a4e4f3 chore(release): 1.2.0 2022-06-20 14:29:16 +00:00
Fallenbagel
fea9457dad Merge pull request #156 from Fallenbagel/develop
Merge branch 'develop' to fix the release workflow
2022-06-20 19:27:42 +05:00
Fallenbagel
883b9377be Merge pull request #155 from Fallenbagel/change-to-self-hosted-workflow
fix yarn dependecies install in release workflow
2022-06-20 19:26:09 +05:00
Fallenbagel
c7ba553208 fix yarn dependecies install in release workflow 2022-06-20 19:25:25 +05:00
Fallenbagel
76472521ed Merge pull request #154 from Fallenbagel/change-to-self-hosted-workflow
add yarn dependencies
2022-06-20 19:21:38 +05:00
Fallenbagel
a34e14b496 add yarn dependencies 2022-06-20 19:19:28 +05:00
Fallenbagel
23c9595933 Merge pull request #153 from Fallenbagel/develop
Merge branch 'develop'
2022-06-20 18:30:20 +05:00
Fallenbagel
715e229e01 Merge pull request #152 from Fallenbagel/change-to-self-hosted-workflow
Changed run-on to self-hosted to speed up release process
2022-06-20 17:39:43 +05:00
Fallenbagel
a5e6217f85 Changed run-on to self-hosted to speed up release process 2022-06-20 17:36:13 +05:00
Fallenbagel
af522516f7 Merge pull request #149 from jab416171/fix-library-scan-text
Fix text for manual library scan
2022-06-19 23:53:56 +05:00
Fallenbagel
647f594dc8 Merge pull request #147 from jab416171/develop
change email sender name to Jellyseerr on initial setup
2022-06-19 23:42:45 +05:00
jab416171
ae60d44f99 Fix text for manual library scan 2022-06-19 12:38:49 -06:00
jab416171
9275119163 change email sender name to Jellyseerr on initial setup 2022-06-19 12:28:09 -06:00
Fallenbagel
94b418bd47 Merge pull request #146 from Fallenbagel/fix-ui-description-mistakes
fix(setup&login): fix a description error in the manual scan in setup and add emby to login page
2022-06-19 22:57:33 +05:00
Fallenbagel
8810c20fc1 fix(setup&login): fix a description error in the manual scan in setup and add emby to login page
Manual scan in setup says "Jellyfin will scan your Jellyfin's server" and same with emby, so I
replaced jellyfin with jellyseerr. And also added emby to login page
2022-06-19 22:27:38 +05:00
Nicolai Van der Storm
63b7be0a38 Update private_registery_push.yml 2022-06-18 22:55:07 +02:00
Nicolai Van der Storm
d3cea69011 Update private_registery_push.yml 2022-06-18 22:48:47 +02:00
Nicolai Van der Storm
31072f4758 Update private_registery_push.yml 2022-06-18 22:45:43 +02:00
Nicolai Van der Storm
89e8825b61 fixes indent issues 2022-06-18 22:39:24 +02:00
Fallenbagel
7956ed8466 Merge pull request #144 from NicolaiVdS/private_registery_workflow
Create private_registery_push.yml
2022-06-19 01:34:08 +05:00
Nicolai Van der Storm
e1081a7bc2 Create private_registery_push.yml 2022-06-18 22:30:15 +02:00
Fallenbagel
fe3495705f Merge pull request #136 from NicolaiVdS/email-validation-and-requirement
feat(userprofile): email requirement and validation + import user button overhaul
2022-06-15 01:29:47 +05:00
Nicolai Van der Storm
29478fc195 fix(import all): fis for import all 2022-06-13 23:13:22 +02:00
Nicolai Van der Storm
f48286043e Merge pull request #139 from Fallenbagel/translation-issue
fix(ui): fixed translation issue where it showed as import {mediaServerName} user
2022-06-13 21:54:07 +02:00
Fallenbagel
d417fcafa1 fix(ui): replaced {mediaServerName} in the plex variable in NL locale
replaced {mediaServerName} in the plex variable in NL locale
2022-06-14 00:51:44 +05:00
Fallenbagel
819190ce98 fix(ui): fixed translation issue where it showed as import {mediaServerName} user
Fixed translation issue where it showed as import {mediaServerName} user as it was using the same
variable both inside the plex import modal and also outside in userlist on the button
2022-06-14 00:48:17 +05:00
Nicolai Van der Storm
a483ca9837 fix(jellyfinimportmodal): fix for importing all jellyfin users 2022-06-13 21:34:51 +02:00
Nicolai Van der Storm
d835336d33 feat(email validation): email requirement and validation + better importer 2022-06-13 14:21:05 +02:00
Nicolai Van der Storm
cc69f66ba9 chore(.idea folder): removed .idea folder and added it to the .gitignore 2022-06-10 12:30:14 +02:00
Nicolai Van der Storm
543859e6f3 feat(uesrprofile): email requirement and validation 2022-06-10 12:19:52 +02:00
Fallenbagel
4fd42874b7 Merge pull request #133 from Fallenbagel/fix-translation-errors
fix(ui): fix translation errors for all locales in the import plex user button
2022-06-09 16:51:56 +05:00
Fallenbagel
0fb5803eb9 fix(ui): fix translation errors for all locales in the import plex user button
fix translation errors for all locales in the import plex user button as it currently shows as
{mediaServerName}
2022-06-09 16:38:18 +05:00
Fallenbagel
00c08b3d67 Merge pull request #132 from notfakie/develop
fix: fix mediaServerType for plex users not being set properly
2022-06-09 16:25:48 +05:00
notfakie
94ade93e16 fix: fix mediaServerType not set for Plex which leads to Plex users seeing Jellyfin settings 2022-06-09 17:08:00 +12:00
Fallenbagel
caa713a968 Merge pull request #128 from NicolaiVdS/feature-add-email-field
feat: add email field in the profile settings
2022-06-05 21:53:53 +05:00
Nicolai Van der Storm
23779f4c7b style: removed .idea folder 2022-06-05 18:43:52 +02:00
Nicolai Van der Storm
5f2ebfe662 Revert "feat(tv): tv seasons"
This reverts commit c117b37cd9.
2022-06-05 18:37:50 +02:00
Nicolai Van der Storm
b22f20b6fa feat(user settings): added email field to user profiel settings
#122
2022-06-05 18:25:15 +02:00
Nicolai Van der Storm
a8bc0c068b feat: email
#122
2022-06-05 18:11:20 +02:00
Nicolai Van der Storm
30c48f16ca feat(user email setting): added field to save user email
fix #122
2022-06-05 17:46:26 +02:00
Fallenbagel
3748f64ce4 Merge pull request #127 from sambartik/fix-sync-errors
fix(jellyfin): fixes sync errors re-introduced in previous commits
2022-06-05 20:27:00 +05:00
Samuel Bartík
d1dbd6e3b9 fix(jellyfin): sync errors 2022-06-05 17:07:27 +02:00
Fallenbagel
6458c054c0 Merge pull request #126 from sambartik/fix-virtual-location
fix(jellyfin): ignore additional items with virtual location type
2022-06-05 18:25:01 +05:00
Samuel Bartík
c81154800f fix(jellyfin): ignore additional items with virtual location type 2022-06-05 12:41:34 +02:00
Fallenbagel
a1cd354691 Merge pull request #125 from sambartik/fix-scan
fix(scan): ignore virtual seasons
2022-06-05 12:35:13 +05:00
Samuel Bartík
6574e18516 fix(scan): ignore virtual seasons
Virtual seasons appeared as available / partially available, even though they were not even shown in the Jellyfin web UI. For more info see #119
2022-06-04 18:07:14 +02:00
Fallenbagel
5298e5fd90 Merge pull request #121 from notfakie/develop
Hide Overseerr settings when running in Jellyfin/Emby mode
2022-06-02 14:42:49 +05:00
notfakie
7450138ac1 fix: hide plex guid cache settings from ui when running in jellyfin/emby mode 2022-06-02 18:47:27 +12:00
notfakie
4b7bdd3d7d fix: remove internal Overseerr sponsor link, this is remaining on the main github page instead 2022-06-02 18:47:25 +12:00
notfakie
739f5f9c9a fix: only show mediaserver settings for current active mediaserver 2022-06-02 18:47:22 +12:00
Nicolai Van der Storm
c117b37cd9 feat(tv): tv seasons
tv seasons
2022-06-01 14:48:05 +02:00
Fallenbagel
3e7d64eb47 Merge pull request #120 from Fallenbagel/embySupport
fix(ui): fix emby ui elements not reflecting the env variable #98
2022-05-30 00:31:05 +05:00
Fallenbagel
b9546e6daa feat(ui): add emby user badge to the userProfile
adds emby user badge to the userProfile general page
2022-05-30 00:21:04 +05:00
Fallenbagel
722dda5856 fix(ui): fix ui elements not reflecting the env variable
Fix emby ui elements not reflecting the emby env variable set during runtime
2022-05-29 23:58:54 +05:00
Fallenbagel
c67ca34111 Merge pull request #118 from jab416171/patch-2
fix aur url
2022-05-29 05:50:09 +05:00
jab416171
16311808b1 fix aur url 2022-05-28 18:34:50 -06:00
Fallenbagel
509c43e552 Merge pull request #117 from jab416171/patch-1
Add manual install steps and aur package info
2022-05-29 05:29:49 +05:00
jab416171
84a97675dc Add manual install steps and aur package info 2022-05-28 18:14:57 -06:00
Fallenbagel
4615286f49 Merge pull request #115 from CyferShepard/JellyfinNoPasswordFix
feat:Remove Requirement for Jellyfin Passwords (#108 #31)
2022-05-28 12:32:02 +05:00
Fallenbagel
7b7354d006 Merge remote-tracking branch 'upstream/develop' into develop 2022-05-28 12:20:26 +05:00
CyferShepard
ad7b3590d7 Move auth.ts to correct folder 2022-05-28 08:50:51 +02:00
CyferShepard
bda7858b66 Delete auth.ts 2022-05-28 08:50:23 +02:00
CyferShepard
d600a45559 feat:Remove Requirement for Jellyfin Passwords 2022-05-28 08:29:45 +02:00
Fallenbagel
1dcfe49b1b Merge pull request #112 from Fallenbagel/forgotPasswordFix
fix: fixes jellyfin forgot password and adds emby support to the forgort password link
2022-05-28 07:29:46 +05:00
Fallenbagel
1dbc565a2e Merge pull request #103 from boring-dragon/develop
feat: conditional media server name to add emby
2022-05-28 07:29:38 +05:00
Fallenbagel
973a3e826f Merge pull request #111 from Fallenbagel/AvatarUrlFix
fix(ui): fix Avatar being broken when setup using internal ip
2022-05-28 07:22:31 +05:00
Fallenbagel
6a6bfe0c68 feat(ui): add emby as a mediaServerType to the import user button
Add emby as a mediaServerType to the import user button and replace any reference to Jellyfin with
mediaServerName
2022-05-27 05:35:00 +05:00
Fallenbagel
410b536c94 feat(ui): add emby user badge to the user list and fix local user badge
add emby user badge to the user list and fix local user badge which was previously not showing
2022-05-27 04:58:24 +05:00
Fallenbagel
18d8d969f1 style(ui): conditional mediaServerName to add emby to setup/login page 2022-05-26 09:39:19 +05:00
Fallenbagel
f8a239b1b8 style(ui): conditional media server name to add emby to settings
Conditionaly media server name to replace every reference of jellyfin with emby in settings tab when
environmental variable set
2022-05-26 08:52:16 +05:00
Fallenbagel
377a4fd85b feat(ui): conditional media server name to add emby to issuedetails play on button 2022-05-26 08:39:39 +05:00
Fallenbagel
14d293799b feat(ui): conditional media server name to add emby to moviedetails 2022-05-26 08:37:51 +05:00
Fallenbagel
ddd773c03f fix: conditional media server name for 4k url to add emby to tvdetails 2022-05-26 08:30:21 +05:00
Fallenbagel
e75b71b816 feat: conditional media server name to add emby to tvdetails 2022-05-26 05:54:57 +05:00
Fallenbagel
ff3e3ce841 feat: conditional media server name to add emby to tvdetails 2022-05-26 05:49:33 +05:00
Fallenbagel
01e81a73a3 fix(ui): fix Avatar being broken when setup using internal ip
allow avatar url to use externalHostname when setup using local ip

fix #110
2022-05-26 02:24:31 +05:00
Samuel Bartík
db05172d8b fix(ui): rectangular avatars getting stretched (#2782) 2022-05-25 20:42:17 +04:00
Mohamed jinas
2bfdf02c79 feat: conditional media server name 2022-05-22 22:12:05 +05:00
semantic-release-bot
eceedbbaad chore(release): 1.1.0 2022-05-21 01:58:03 +00:00
Fallenbagel
29f06a965c Merge branch 'develop' 2022-05-21 06:43:52 +05:00
Fallenbagel
9ec05d3ba4 Fixed the link for the jellyseerr logo 2022-04-24 13:22:47 +05:00
semantic-release-bot
ee14ff5a51 chore(release): 1.0.2 2022-04-20 00:06:57 +00:00
Fallenbagel
6b62d4b862 Merge pull request #82 from Fallenbagel/workFlowfix
ci: adds GITHUB_TOKEN as an env
2022-04-20 05:02:17 +05:00
Fallenbagel
706fea0e97 ci: adds GITHUB_TOKEN as an env
adds GITHUB_TOKEN as an env to fix the github_token missing error during release workflow
2022-04-20 05:00:29 +05:00
Fallenbagel
80956d1a83 Merge pull request #81 from Fallenbagel/fixMediaServerType
fix: fix usertype from local user to mediaServerType
2022-04-20 04:58:20 +05:00
Fallenbagel
6d530d9028 fix: fix usertype from local user to mediaServerType
Fixes usertype from appearing as local user even if the mediaServerType is jellyfin
2022-04-20 04:52:39 +05:00
Fallenbagel
f12237565f Merge pull request #80 from Fallenbagel/packagejsonChanges
update tags and the branch to jellyseerr
2022-04-20 03:49:16 +05:00
Fallenbagel
11f5594ed4 update tags and the branch to jellyseerr 2022-04-20 03:47:46 +05:00
Fallenbagel
e4e58bee05 Merge pull request #79 from Fallenbagel/githubChanges
update workflows and discord locations for jellyseerr
2022-04-20 03:32:46 +05:00
Fallenbagel
13ee3a836c update workflows and discord locations for jellyseerr 2022-04-20 03:29:19 +05:00
Fallenbagel
3f16a353f5 Merge pull request #78 from Fallenbagel/urlValidationFix
fix: relax jellyfin url validation to allow local domains
2022-04-20 03:25:41 +05:00
Fallenbagel
9c43ba95e6 fix: relax jellyfin url validation to allow local domains
Relaxes jellyfin url validation so that http://localhost:8096 and http://jellyfin:8096 urls are
accepted in addition to full urls like https://example.com

fix #123
2022-04-20 03:12:01 +05:00
Fallenbagel
13fb6fd1a7 Updated the docker tags
Updated the docker tags to point to fallenbagel docker repo
2022-04-18 07:27:21 +05:00
Fallenbagel
16e8e3a38e update workflow to test for jellyseerr
update workflow and discord locations to test the docker pipeline
2022-04-18 07:17:16 +05:00
Fallenbagel
6fecdf094d Merge pull request #76 from Fallenbagel/updatePackagejson
Update package.json to reflect the jellyseerr version. This helps fix the version issue.
2022-04-15 14:57:27 +05:00
Fallenbagel
69b271b018 Chore(release):v1.0.1 2022-04-15 14:55:53 +05:00
Fallenbagel
d6ebd9a9b9 Chore(release):v1.0.1 2022-04-15 14:54:30 +05:00
Fallenbagel
70dad332fc Merge pull request #74 from Fallenbagel/versionStatusFix
fix: fix for the jellyseerr out of date even though it is up-to-date
2022-04-15 14:30:41 +05:00
Fallenbagel
a65e430c60 fix: fix for the jellyseerr out of date even though it is up-to-date
Reverting back the changes for the quick jellyseerr version fix for a better implementation
2022-04-15 14:03:03 +05:00
Fallenbagel
18f4b67b72 Merge pull request #73 from Fallenbagel/avatarfix
fix: fix default avatar missing
2022-04-15 12:12:13 +05:00
Fallenbagel
506c31562a fix: fix default avatar missing
Fix the default avatar missing because one of the os_logo_square.png file was missing
2022-04-15 12:07:12 +05:00
Fallenbagel
7a9d7a4834 Merge pull request #71 from jsl9208/feat-emby-mediaurl
feat: add emby detail url support
2022-04-15 11:21:49 +05:00
Fallenbagel
902a033b8a Merge pull request #72 from Fallenbagel/unknownjobfix
fix: replaced Unkown job with jellyfin in jobsandcache
2022-04-15 11:18:46 +05:00
Fallenbagel
00eb20aa5e fix: replaced Unkown job with jellyfin in jobsandcache
Replaced unknown job with jellyfin in jobsandcache and fixed the translations to reflect it as well
2022-04-15 10:46:09 +05:00
Shilong Jiang
a2c27cfa95 feat: add emby detail url support 2022-04-14 20:10:57 +08:00
Fallenbagel
7122b4d08b Replaced arm tags with latest
Replaced `:arm` and `:armv7` tags with `latest` as they are now deprecated.
2022-04-14 00:03:57 +05:00
Fallenbagel
b03b9b1dbb fix: fixed request card not displaying the requested season and episodes
When requested, the request card shows as {seasonCount, plural, one {Season}} and does not display
which season or episode was requested because it was still using the alpha request cards. This fixed
that issue
2022-04-13 17:24:47 +05:00
Fallenbagel
73672e29f8 fix: fixed jellyseerr out of date on stable version
When jellyseerr latest version or the stable version was deployed, the version was shown as out of
date with a message to up date to the latest version even though it was the latest version. This
fixed that issue
2022-04-13 17:21:03 +05:00
Fallenbagel
cc5192209f fixed logo_full.svg render 2022-04-13 13:17:54 +05:00
Fallenbagel
278dcf4b44 Update .all-contributorsrc 2022-04-13 13:17:54 +05:00
Fallenbagel
36e092f225 Update .all-contributorsrc 2022-04-13 13:17:54 +05:00
Fallenbagel
46d5c737a2 chore: github update 2022-04-13 13:17:54 +05:00
Fallenbagel
cba4878db3 feat: update zh_Hans.json
Update zh_Hans.json
2022-04-13 13:17:53 +05:00
Fallenbagel
57cc48a699 style: replaced Overseerr with jellyseerr 2022-04-13 13:17:53 +05:00
Fallenbagel
84f488be06 fix: database migration fix
Fixed the database migration issue fixing the error "SQLITE+ERROR: no such column:
User.jellyfinUsername
2022-04-13 13:17:53 +05:00
Fallenbagel
f885f2a0f3 ci: remove DEPENDABOT 2022-04-13 13:17:53 +05:00
Fallenbagel
eef3e5ea4c docs: added preview 2022-04-13 13:17:53 +05:00
Fallenbagel
8db821c1c1 docs: added new logo
Added new jellyseerr logo
2022-04-13 13:17:53 +05:00
Fallenbagel
a39b882f09 docs: added new logo
Added new jellyseerr logo
2022-04-13 13:17:53 +05:00
Fallenbagel
754dccc4bf first commit 2022-04-13 13:17:53 +05:00
Juan D. Jara
f97ee11430 fix(jellyfin): get jellyfin integration working with the last develop version
re #288
2021-09-27 02:56:02 +02:00
Juan D. Jara
54868fd486 style: fix linter and add types 2021-09-27 02:35:10 +02:00
Juan D. Jara
eea389879f Merge branch 'develop' of https://github.com/sct/overseerr into jellyfin-support 2021-09-27 02:24:30 +02:00
Aiden Vigue
5c917f95b4 fix(backend): use different device ids for jellyfin users 2021-06-17 13:42:08 -04:00
Aiden Vigue
dd4d42fd31 fix(backend): force same device id 2021-06-14 16:33:17 -04:00
Aiden Vigue
e5c6b9cd74 fix(backend): update jellyfin.ts for 10.8.0 2021-06-14 12:27:07 -04:00
Aiden Vigue
508fccae4e fix(build): fix build errors 2021-02-27 22:41:35 +00:00
Aiden Vigue
f77573c838 fix(frontend): revert mpaa change 2021-02-27 22:17:51 +00:00
Aiden Vigue
7dfe38001e fix(backend): fix Jellyfin scan for recently added items 2021-02-27 22:15:32 +00:00
Aiden Vigue
48f55da43e style(frontend): fix padding on MPAA rating 2021-02-27 22:15:32 +00:00
Aiden Vigue
1e97503802 fix(db): add migration 2021-02-27 22:13:53 +00:00
Aiden Vigue
42ff34bb3d fix(backend): remove console statement 2021-02-27 22:13:53 +00:00
Aiden Vigue
107b766c44 fix(frontend): add Jellyfin logo to ExternalLinkBlock 2021-02-27 22:13:23 +00:00
Aiden Vigue
fb51ce5570 feat(rebase): rebase 2021-02-27 22:12:55 +00:00
Aiden Vigue
3357343d98 feat(rebase): rebase 2021-02-27 22:12:54 +00:00
Aiden Vigue
9d61092f37 feat(rebase): rebase 2021-02-27 22:12:54 +00:00
Aiden Vigue
29274614c3 feat(rebase): rebase 2021-02-27 22:12:54 +00:00
Aiden Vigue
19b51592ea feat(rebase): rebase 2021-02-27 22:11:47 +00:00
Aiden Vigue
757c0fc29e feat(rebase): rebase 2021-02-27 22:11:27 +00:00
Aiden Vigue
3eb48abc14 feat(rebase): rebase 2021-02-27 22:11:27 +00:00
Aiden Vigue
01cd9d3872 feat(rebase): rebasse 2021-02-27 22:10:25 +00:00
Aiden Vigue
9582196e1f feat: rebase 2021-02-27 22:09:43 +00:00
Aiden Vigue
3743edab8d feat(rebase): rebase 2021-02-27 22:09:02 +00:00
Aiden Vigue
d81e7cdbab feat(rebase): rebase 2021-02-27 22:09:02 +00:00
Aiden Vigue
6e1d7f7075 feat(rebase): rebase 2021-02-27 22:09:02 +00:00
Aiden Vigue
91cf2de33a feat(rebase): rebase 2021-02-27 22:09:02 +00:00
Aiden Vigue
a6ec2d5220 feat(all): add initial Jellyfin/Emby support 2021-02-27 22:09:02 +00:00
69 changed files with 1849 additions and 1250 deletions

7
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,7 @@
# Global code ownership
- @Fallenbagel
# i18n locale files
src/i18n/locale/ @Fallenbagel

View File

@@ -0,0 +1,39 @@
name: 'create docker image on pull request and push to private registery'
on:
pull_request:
branches:
- develop
workflow_dispatch:
jobs:
build-image:
runs-on: self-hosted
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to private registery
uses: docker/login-action@v2.0.0
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: '${{ secrets.REGISTRY_URL }}/fallenbagel/jellyseerr:${{ github.sha }}'
cache-from: 'type=registry,ref=${{ secrets.REGISTRY_URL }}/fallenbagel/jellyseerr:buildcache'
cache-to: 'type=registry,ref=${{ secrets.REGISTRY_URL }}/fallenbagel/jellyseerr:buildcache,mode=max'

View File

@@ -5,7 +5,7 @@ on: workflow_dispatch
jobs:
semantic-release:
name: Tag and release latest version
runs-on: ubuntu-20.04
runs-on: self-hosted
env:
HUSKY: 0
steps:
@@ -26,6 +26,8 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn
- name: Release
@@ -39,7 +41,7 @@ jobs:
name: Send Discord Notification
needs: semantic-release
if: always()
runs-on: ubuntu-20.04
runs-on: self-hosted
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v2

3
.gitignore vendored
View File

@@ -53,3 +53,6 @@ config/db/db.sqlite3-journal
# VS Code
.vscode/launch.json
# Webstorm
.idea

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
- Emby Support
Along with all the existing Overseerr features:
- Full Plex integration. Authenticate and manage user access with Plex!
- Easy integration with your existing services. Currently, Jellyseerr supports Sonarr and Radarr. More to come!
- Plex library scan, to keep track of the titles which are already available.
@@ -31,6 +32,18 @@ With more features on the way! Check out our [issue tracker](https://github.com/
Check out our dockerhub for instructions on how to install and run Jellyseerr:
https://hub.docker.com/r/fallenbagel/jellyseerr
### Launching Jellyseerr manually:
```bash
yarn install
yarn run build
yarn start
```
### Packages:
Archlinux: [AUR](https://aur.archlinux.org/packages/jellyseerr)
## Preview
<img src="./public/preview.jpg">
@@ -55,4 +68,4 @@ Our [Code of Conduct](https://github.com/fallenbagel/jellyseerr/blob/develop/COD
## Contributing
You can help improve Jellyseerr too! Check out our [Contribution Guide](https://github.com/fallenbagel/jellyseerr/blob/develop/CONTRIBUTING.md) to get started.
You can help improve Jellyseerr too! Check out our [Contribution Guide](https://github.com/fallenbagel/jellyseerr/blob/develop/CONTRIBUTING.md) to get started.

View File

@@ -2,6 +2,10 @@ module.exports = {
env: {
commitTag: process.env.COMMIT_TAG || 'local',
},
publicRuntimeConfig: {
// Will be available on both server and client
JELLYFIN_TYPE: process.env.JELLYFIN_TYPE,
},
images: {
domains: ['image.tmdb.org'],
},

View File

@@ -1,6 +1,6 @@
{
"name": "jellyseerr",
"version": "0.1.0",
"version": "1.1.1",
"private": true,
"scripts": {
"dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts",
@@ -37,6 +37,7 @@
"country-flag-icons": "^1.4.21",
"csurf": "^1.11.0",
"email-templates": "^8.0.10",
"email-validator": "^2.0.4",
"express": "^4.17.3",
"express-openapi-validator": "^4.13.6",
"express-rate-limit": "^6.3.0",
@@ -84,6 +85,7 @@
"@babel/cli": "^7.17.6",
"@commitlint/cli": "^16.2.1",
"@commitlint/config-conventional": "^16.2.1",
"@next/eslint-plugin-next": "^12.1.6",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/exec": "^6.0.3",

View File

@@ -0,0 +1,3 @@
[ZoneTransfer]
LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe
ZoneId=3

View File

@@ -31,6 +31,7 @@ export interface JellyfinLibraryItem {
Id: string;
HasSubtitles: boolean;
Type: 'Movie' | 'Episode' | 'Season' | 'Series';
LocationType: 'FileSystem' | 'Offline' | 'Remote' | 'Virtual';
SeriesName?: string;
SeriesId?: string;
SeasonId?: string;
@@ -205,7 +206,9 @@ class JellyfinAPI {
`/Users/${this.userId}/Items?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series,Movie&Recursive=true&StartIndex=0&ParentId=${id}`
);
return contents.data.Items;
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) {
logger.error(
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
@@ -251,7 +254,9 @@ class JellyfinAPI {
try {
const contents = await this.axios.get<any>(`/Shows/${seriesID}/Seasons`);
return contents.data.Items;
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) {
logger.error(
`Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`,
@@ -270,7 +275,9 @@ class JellyfinAPI {
`/Shows/${seriesID}/Episodes?seasonId=${seasonID}`
);
return contents.data.Items;
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) {
logger.error(
`Something went wrong while getting the list of episodes from the Jellyfin server: ${e.message}`,

View File

@@ -137,6 +137,8 @@ export class User {
@UpdateDateColumn()
public updatedAt: Date;
public warnings: string[] = [];
constructor(init?: Partial<User>) {
Object.assign(this, init);
}

View File

@@ -2,6 +2,7 @@ import { NotificationAgentKey } from '../../lib/settings';
export interface UserSettingsGeneralResponse {
username?: string;
email?: string;
discordId?: string;
locale?: string;
region?: string;

View File

@@ -13,6 +13,7 @@ import {
NotificationAgentKey,
} from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import * as EmailValidator from 'email-validator';
class EmailAgent
extends BaseAgent<NotificationAgentEmail>
@@ -215,14 +216,23 @@ class EmailAgent
this.getSettings(),
payload.notifyUser.settings?.pgpKey
);
await email.send(
this.buildMessage(
type,
payload,
payload.notifyUser.email,
payload.notifyUser.displayName
)
);
if (EmailValidator.validate(payload.notifyUser.email)) {
await email.send(
this.buildMessage(
type,
payload,
payload.notifyUser.email,
payload.notifyUser.displayName
)
);
} else {
logger.warn('Invalid email address provided for user', {
label: 'Notifications',
recipient: payload.notifyUser.displayName,
type: Notification[type],
subject: payload.subject,
});
}
} catch (e) {
logger.error('Error sending email notification', {
label: 'Notifications',
@@ -268,9 +278,18 @@ class EmailAgent
this.getSettings(),
user.settings?.pgpKey
);
await email.send(
this.buildMessage(type, payload, user.email, user.displayName)
);
if (EmailValidator.validate(user.email)) {
await email.send(
this.buildMessage(type, payload, user.email, user.displayName)
);
} else {
logger.warn('Invalid email address provided for user', {
label: 'Notifications',
recipient: user.displayName,
type: Notification[type],
subject: payload.subject,
});
}
} catch (e) {
logger.error('Error sending email notification', {
label: 'Notifications',

View File

@@ -134,6 +134,7 @@ interface FullPublicSettings extends PublicSettings {
enablePushRegistration: boolean;
locale: string;
emailEnabled: boolean;
userEmailRequired: boolean;
newPlexLogin: boolean;
}
@@ -159,6 +160,7 @@ export interface NotificationAgentSlack extends NotificationAgentConfig {
export interface NotificationAgentEmail extends NotificationAgentConfig {
options: {
userEmailRequired: boolean;
emailFrom: string;
smtpHost: string;
smtpPort: number;
@@ -335,6 +337,7 @@ class Settings {
email: {
enabled: false,
options: {
userEmailRequired: false,
emailFrom: '',
smtpHost: '',
smtpPort: 587,
@@ -342,7 +345,7 @@ class Settings {
ignoreTls: false,
requireTls: false,
allowSelfSigned: false,
senderName: 'Overseerr',
senderName: 'Jellyseerr',
},
},
discord: {
@@ -529,6 +532,8 @@ class Settings {
enablePushRegistration: this.data.notifications.agents.webpush.enabled,
locale: this.data.main.locale,
emailEnabled: this.data.notifications.agents.email.enabled,
userEmailRequired:
this.data.notifications.agents.email.options.userEmailRequired,
newPlexLogin: this.data.main.newPlexLogin,
};
}

View File

@@ -9,6 +9,7 @@ import { Permission } from '../lib/permissions';
import { getSettings } from '../lib/settings';
import logger from '../logger';
import { isAuthenticated } from '../middleware/auth';
import * as EmailValidator from 'email-validator';
const authRoutes = Router();
@@ -24,6 +25,16 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => {
where: { id: req.user.id },
});
// check if email is required in settings and if user has an valid email
const settings = await getSettings();
if (
settings.notifications.agents.email.options.userEmailRequired &&
!EmailValidator.validate(user.email)
) {
user.warnings.push('userEmailRequired');
logger.warn(`User ${user.username} has no valid email address`);
}
return res.status(200).json(user);
});
@@ -70,6 +81,9 @@ authRoutes.post('/plex', async (req, res, next) => {
userType: UserType.PLEX,
});
settings.main.mediaServerType = MediaServerType.PLEX;
settings.save();
await userRepository.save(user);
} else {
const mainUser = await userRepository.findOneOrFail({
@@ -196,10 +210,8 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
settings.jellyfin.hostname !== ''
) {
return res.status(500).json({ error: 'Jellyfin login is disabled' });
} else if (!body.username || !body.password) {
return res
.status(500)
.json({ error: 'You must provide an username and a password' });
} else if (!body.username) {
return res.status(500).json({ error: 'You must provide an username' });
} else if (settings.jellyfin.hostname !== '' && body.hostname) {
return res
.status(500)
@@ -213,6 +225,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
settings.jellyfin.hostname !== ''
? settings.jellyfin.hostname
: body.hostname;
const { externalHostname } = getSettings().jellyfin;
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
let user = await userRepository.findOne({
@@ -229,6 +242,10 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
}
// First we need to attempt to log the user in to jellyfin
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: hostname;
const account = await jellyfinserver.login(body.username, body.password);
// Next let's see if the user already exists
@@ -244,7 +261,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
// Update the users avatar with their jellyfin profile pic (incase it changed)
if (account.User.PrimaryImageTag) {
user.avatar = `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
user.avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
} else {
user.avatar = '/os_logo_square.png';
}
@@ -290,7 +307,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
@@ -319,7 +336,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinAuthToken: account.AccessToken,
permissions: settings.main.defaultPermissions,
avatar: account.User.PrimaryImageTag
? `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
@@ -327,7 +344,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
const passedExplicitPassword =
body.password && body.password.length > 0;
if (passedExplicitPassword) {
await user.setPassword(body.password);
await user.setPassword(body.password ?? '');
}
await userRepository.save(user);
}

View File

@@ -303,6 +303,11 @@ settingsRoutes.get('/jellyfin/library', async (req, res) => {
settingsRoutes.get('/jellyfin/users', async (req, res) => {
const settings = getSettings();
const { hostname, externalHostname } = getSettings().jellyfin;
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: hostname;
const userRepository = getRepository(User);
const admin = await userRepository.findOneOrFail({
@@ -321,7 +326,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => {
username: user.Name,
id: user.Id,
thumb: user.PrimaryImageTag
? `${settings.jellyfin.hostname}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
email: user.Name,
}));

View File

@@ -492,56 +492,46 @@ router.post(
);
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
const jellyfinUsersResponse = await jellyfinClient.getUsers();
//const jellyfinUsersResponse = await jellyfinClient.getUsers();
const createdUsers: User[] = [];
for (const account of jellyfinUsersResponse.users) {
if (account.Name) {
const user = await userRepository
.createQueryBuilder('user')
.where('user.jellyfinUserId = :id', { id: account.Id })
.orWhere('user.email = :email', {
email: account.Name,
})
.getOne();
const { hostname, externalHostname } = getSettings().jellyfin;
const jellyfinHost =
externalHostname && externalHostname.length > 0
? externalHostname
: hostname;
const avatar = account.PrimaryImageTag
? `${settings.jellyfin.hostname}/Users/${account.Id}/Images/Primary/?tag=${account.PrimaryImageTag}&quality=90`
: '/os_logo_square.png';
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
const jellyfinUsers = await jellyfinClient.getUsers();
if (user) {
// Update the user's avatar with their Jellyfin thumbnail, in case it changed
user.avatar = avatar;
user.email = account.Name;
user.jellyfinUsername = account.Name;
for (const jellyfinUserId of body.jellyfinUserIds) {
const jellyfinUser = jellyfinUsers.users.find(
(user) => user.Id === jellyfinUserId
);
// In case the user was previously a local account
if (user.userType === UserType.LOCAL) {
user.userType = UserType.JELLYFIN;
user.jellyfinUserId = account.Id;
}
await userRepository.save(user);
} else if (!body || body.jellyfinUserIds.includes(account.Id)) {
// logger.error('CREATED USER', {
// label: 'API',
// });
const user = await userRepository.findOne({
select: ['id', 'jellyfinUserId'],
where: { jellyfinUserId: jellyfinUserId },
});
const newUser = new User({
jellyfinUsername: account.Name,
jellyfinUserId: account.Id,
jellyfinDeviceId: Buffer.from(
`BOT_overseerr_${account.Name ?? ''}`
).toString('base64'),
email: account.Name,
permissions: settings.main.defaultPermissions,
avatar,
userType: UserType.JELLYFIN,
});
await userRepository.save(newUser);
createdUsers.push(newUser);
}
if (!user) {
const newUser = new User({
jellyfinUsername: jellyfinUser?.Name,
jellyfinUserId: jellyfinUser?.Id,
jellyfinDeviceId: Buffer.from(
`BOT_jellyseerr_${jellyfinUser?.Name ?? ''}`
).toString('base64'),
email: jellyfinUser?.Name,
permissions: settings.main.defaultPermissions,
avatar: jellyfinUser?.PrimaryImageTag
? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
await userRepository.save(newUser);
createdUsers.push(newUser);
}
}
return res.status(201).json(User.filterMany(createdUsers));
} catch (e) {
next({ status: 500, message: e.message });

View File

@@ -51,6 +51,7 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
return res.status(200).json({
username: user.username,
email: user.email,
discordId: user.settings?.discordId,
locale: user.settings?.locale,
region: user.settings?.region,
@@ -120,6 +121,7 @@ userSettingsRoutes.post<
user.settings.locale = req.body.locale;
user.settings.region = req.body.region;
user.settings.originalLanguage = req.body.originalLanguage;
user.email = req.body.email ?? user.email;
}
await userRepository.save(user);
@@ -130,6 +132,7 @@ userSettingsRoutes.post<
locale: user.settings.locale,
region: user.settings.region,
originalLanguage: user.settings.originalLanguage,
email: user.email,
});
} catch (e) {
next({ status: 500, message: e.message });

View File

@@ -90,7 +90,7 @@ const IssueComment: React.FC<IssueCommentProps> = ({
<img
src={comment.user.avatar}
alt=""
className="h-10 w-10 scale-100 transform-gpu rounded-full ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
/>
</a>
</Link>

View File

@@ -35,6 +35,7 @@ import IssueComment from './IssueComment';
import IssueDescription from './IssueDescription';
import { MediaServerType } from '../../../server/constants/server';
import useSettings from '../../hooks/useSettings';
import getConfig from 'next/config';
const messages = defineMessages({
openedby: '#{issueId} opened {relativeTime} by {username}',
@@ -99,6 +100,7 @@ const IssueDetails: React.FC = () => {
(opt) => opt.issueType === issueData?.issueType
);
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (!data && !error) {
return <LoadingSpinner />;
@@ -267,7 +269,7 @@ const IssueDetails: React.FC = () => {
>
<a className="group ml-1 inline-flex h-full items-center xl:ml-1.5">
<img
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
src={issueData.createdBy.avatar}
alt=""
/>
@@ -366,13 +368,18 @@ const IssueDetails: React.FC = () => {
>
<PlayIcon />
<span>
{intl.formatMessage(messages.playonplex, {
mediaServerName:
settings.currentSettings.mediaServerType ===
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.playonplex, {
mediaServerName: 'Jellyfin',
})}
</span>
</Button>
)}
@@ -407,13 +414,18 @@ const IssueDetails: React.FC = () => {
>
<PlayIcon />
<span>
{intl.formatMessage(messages.play4konplex, {
mediaServerName:
settings.currentSettings.mediaServerType ===
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
? intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Jellyfin',
})}
</span>
</Button>
)}
@@ -618,13 +630,18 @@ const IssueDetails: React.FC = () => {
>
<PlayIcon />
<span>
{intl.formatMessage(messages.playonplex, {
mediaServerName:
settings.currentSettings.mediaServerType ===
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.playonplex, {
mediaServerName: 'Jellyfin',
})}
</span>
</Button>
)}
@@ -659,13 +676,18 @@ const IssueDetails: React.FC = () => {
>
<PlayIcon />
<span>
{intl.formatMessage(messages.play4konplex, {
mediaServerName:
settings.currentSettings.mediaServerType ===
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
? intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.play4konplex, {
mediaServerName: 'Jellyfin',
})}
</span>
</Button>
)}

View File

@@ -228,7 +228,7 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
<img
src={issue.createdBy.avatar}
alt=""
className="avatar-sm ml-1.5"
className="avatar-sm ml-1.5 object-cover"
/>
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
{issue.createdBy.displayName}

View File

@@ -14,6 +14,7 @@ import useClickOutside from '../../../hooks/useClickOutside';
import { Permission, useUser } from '../../../hooks/useUser';
import Transition from '../../Transition';
import VersionStatus from '../VersionStatus';
import UserWarnings from '../UserWarnings';
const messages = defineMessages({
dashboard: 'Discover',
@@ -177,6 +178,10 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
);
})}
</nav>
<div className="px-2">
<UserWarnings onClick={() => setClosed()} />
</div>
{hasPermission(Permission.ADMIN) && (
<div className="px-2">
<VersionStatus onClick={() => setClosed()} />
@@ -236,6 +241,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
);
})}
</nav>
<div className="px-2">
<UserWarnings />
</div>
{hasPermission(Permission.ADMIN) && (
<div className="px-2">
<VersionStatus />

View File

@@ -40,7 +40,7 @@ const UserDropdown: React.FC = () => {
onClick={() => setDropdownOpen(true)}
>
<img
className="h-8 w-8 rounded-full sm:h-10 sm:w-10"
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
src={user?.avatar}
alt=""
/>

View File

@@ -0,0 +1,66 @@
import React from 'react';
import Link from 'next/link';
import { ExclamationIcon } from '@heroicons/react/outline';
import { defineMessages, useIntl } from 'react-intl';
import { useUser } from '../../../hooks/useUser';
const messages = defineMessages({
emailRequired: 'An email address is required.',
emailInvalid: 'Email address is invalid.',
passwordRequired: 'A password is required.',
});
interface UserWarningsProps {
onClick?: () => void;
}
const UserWarnings: React.FC<UserWarningsProps> = ({ onClick }) => {
const intl = useIntl();
const { user } = useUser();
if (!user) {
return null;
}
let res = null;
//check if a user has warnings
if (user.warnings.length > 0) {
user.warnings.forEach((warning) => {
let link = '';
let warningText = '';
let warningTitle = '';
switch (warning) {
case 'userEmailRequired':
link = '/profile/settings/';
warningTitle = 'Profile is incomplete';
warningText = intl.formatMessage(messages.emailRequired);
}
res = (
<Link href={link}>
<a
onClick={onClick}
onKeyDown={(e) => {
if (e.key === 'Enter' && onClick) {
onClick();
}
}}
role="button"
tabIndex={0}
className="mx-2 mb-2 flex items-center rounded-lg bg-yellow-500 p-2 text-xs text-white ring-1 ring-gray-700 transition duration-300 hover:bg-yellow-400"
>
<ExclamationIcon className="h-6 w-6" />
<div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0">
<span className="font-bold">{warningTitle}</span>
<span className="truncate">{warningText}</span>
</div>
</a>
</Link>
);
});
}
return res;
};
export default UserWarnings;

View File

@@ -50,6 +50,7 @@ const Layout: React.FC = ({ children }) => {
<div className="absolute top-0 h-64 w-full bg-gradient-to-bl from-gray-800 to-gray-900">
<div className="relative inset-0 h-full w-full bg-gradient-to-t from-gray-900 to-transparent" />
</div>
<Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} />
<div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64">

View File

@@ -1,19 +1,19 @@
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Button from '../Common/Button';
import { Field, Form, Formik } from 'formik';
import * as Yup from 'yup';
import axios from 'axios';
import { useToasts } from 'react-toast-notifications';
import * as Yup from 'yup';
import useSettings from '../../hooks/useSettings';
import Button from '../Common/Button';
import getConfig from 'next/config';
const messages = defineMessages({
username: 'Username',
password: 'Password',
host: 'Jellyfin URL',
host: '{mediaServerName} URL',
email: 'Email',
validationhostrequired: 'Jellyfin URL required',
validationhostrequired: '{mediaServerName} URL required',
validationhostformat: 'Valid URL required',
validationemailrequired: 'Email required',
validationemailformat: 'Valid email required',
@@ -40,6 +40,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
const toasts = useToasts();
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (initial) {
const LoginSchema = Yup.object().shape({
@@ -48,16 +49,19 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
/^(?:(?:(?:https?):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/,
intl.formatMessage(messages.validationhostformat)
)
.required(intl.formatMessage(messages.validationhostrequired)),
.required(
intl.formatMessage(messages.validationhostrequired, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
})
),
email: Yup.string()
.email(intl.formatMessage(messages.validationemailformat))
.required(intl.formatMessage(messages.validationemailrequired)),
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string().required(
intl.formatMessage(messages.validationpasswordrequired)
),
password: Yup.string(),
});
return (
<Formik
@@ -97,7 +101,12 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
<Form>
<div className="sm:border-t sm:border-gray-800">
<label htmlFor="host" className="text-label">
{intl.formatMessage(messages.host)}
{intl.formatMessage(messages.host, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
})}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex rounded-md shadow-sm">
@@ -105,7 +114,12 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
id="host"
name="host"
type="text"
placeholder={intl.formatMessage(messages.host)}
placeholder={intl.formatMessage(messages.host, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
})}
/>
</div>
{errors.host && touched.host && (
@@ -185,9 +199,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string().required(
intl.formatMessage(messages.validationpasswordrequired)
),
password: Yup.string(),
});
return (
<div>

View File

@@ -15,12 +15,13 @@ import PlexLoginButton from '../PlexLoginButton';
import Transition from '../Transition';
import JellyfinLogin from './JellyfinLogin';
import LocalLogin from './LocalLogin';
import getConfig from 'next/config';
const messages = defineMessages({
signin: 'Sign In',
signinheader: 'Sign in to continue',
signinwithplex: 'Use your Plex account',
signinwithjellyfin: 'Use your Jellyfin account',
signinwithjellyfin: 'Use your {mediaServerName} account',
signinwithoverseerr: 'Use your {applicationTitle} account',
});
@@ -32,6 +33,7 @@ const Login: React.FC = () => {
const { user, revalidate } = useUser();
const router = useRouter();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to sign in. If we get a success message, we will
@@ -133,7 +135,12 @@ const Login: React.FC = () => {
{settings.currentSettings.mediaServerType ==
MediaServerType.PLEX
? intl.formatMessage(messages.signinwithplex)
: intl.formatMessage(messages.signinwithjellyfin)}
: intl.formatMessage(messages.signinwithjellyfin, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
})}
</button>
<AccordionContent isOpen={openIndexes.includes(0)}>
<div className="px-10 py-8">

View File

@@ -272,7 +272,7 @@ const ManageSlideOver: React.FC<
<img
src={user.avatar}
alt={user.displayName}
className="h-8 w-8 scale-100 transform-gpu rounded-full ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
/>
</a>
</Link>
@@ -387,7 +387,7 @@ const ManageSlideOver: React.FC<
<img
src={user.avatar}
alt={user.displayName}
className="h-8 w-8 scale-100 transform-gpu rounded-full ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
/>
</a>
</Link>

View File

@@ -48,6 +48,7 @@ import PersonCard from '../PersonCard';
import RequestButton from '../RequestButton';
import Slider from '../Slider';
import StatusBadge from '../StatusBadge';
import getConfig from 'next/config';
const messages = defineMessages({
originaltitle: 'Original Title',
@@ -95,6 +96,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
const minStudios = 3;
const [showMoreStudios, setShowMoreStudios] = useState(false);
const [showIssueModal, setShowIssueModal] = useState(false);
const { publicRuntimeConfig } = getConfig();
const {
data,
@@ -130,10 +132,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
if (data.mediaInfo?.mediaUrl) {
mediaLinks.push({
text:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' })
: intl.formatMessage(messages.play, { mediaServerName: 'Plex' }),
text: getAvalaibleMediaServerName(),
url: data.mediaInfo?.mediaUrl,
svg: <PlayIcon />,
});
@@ -146,10 +145,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
})
) {
mediaLinks.push({
text:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' })
: intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' }),
text: getAvalaible4kMediaServerName(),
url: data.mediaInfo?.mediaUrl4k,
svg: <PlayIcon />,
});
@@ -228,6 +224,30 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
return intl.formatMessage(messages.play, { mediaServerName: 'Plex' });
}
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
}
function getAvalaible4kMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' });
}
return intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' });
}
return (
<div
className="media-page"

View File

@@ -231,7 +231,7 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
<img
src={requestData.requestedBy.avatar}
alt=""
className="avatar-sm"
className="avatar-sm object-cover"
/>
<span className="truncate font-semibold group-hover:text-white group-hover:underline">
{requestData.requestedBy.displayName}

View File

@@ -336,7 +336,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
<img
src={requestData.requestedBy.avatar}
alt=""
className="avatar-sm ml-1.5"
className="avatar-sm ml-1.5 object-cover"
/>
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
{requestData.requestedBy.displayName}
@@ -390,7 +390,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
<img
src={requestData.modifiedBy.avatar}
alt=""
className="avatar-sm ml-1.5"
className="avatar-sm ml-1.5 object-cover"
/>
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
{requestData.modifiedBy.displayName}

View File

@@ -534,7 +534,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
<img
src={selectedUser.avatar}
alt=""
className="h-6 w-6 flex-shrink-0 rounded-full"
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
/>
<span className="ml-3 block">
{selectedUser.displayName}
@@ -584,7 +584,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
<img
src={user.avatar}
alt=""
className="h-6 w-6 flex-shrink-0 rounded-full"
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
/>
<span className="ml-3 block flex-shrink-0">
{user.displayName}

View File

@@ -16,6 +16,7 @@ const messages = defineMessages({
validationSmtpHostRequired: 'You must provide a valid hostname or IP address',
validationSmtpPortRequired: 'You must provide a valid port number',
agentenabled: 'Enable Agent',
userEmailRequired: 'Require user email',
emailsender: 'Sender Address',
smtpHost: 'SMTP Host',
smtpPort: 'SMTP Port',
@@ -125,6 +126,7 @@ const NotificationsEmail: React.FC = () => {
<Formik
initialValues={{
enabled: data.enabled,
userEmailRequired: data.options.userEmailRequired,
emailFrom: data.options.emailFrom,
smtpHost: data.options.smtpHost,
smtpPort: data.options.smtpPort ?? 587,
@@ -148,6 +150,7 @@ const NotificationsEmail: React.FC = () => {
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
options: {
userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
@@ -241,6 +244,18 @@ const NotificationsEmail: React.FC = () => {
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div>
<div className="form-row">
<label htmlFor="userEmailRequired" className="checkbox-label">
{intl.formatMessage(messages.userEmailRequired)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="userEmailRequired"
name="userEmailRequired"
/>
</div>
</div>
<div className="form-row">
<label htmlFor="senderName" className="text-label">
{intl.formatMessage(messages.senderName)}

View File

@@ -188,35 +188,6 @@ const SettingsAbout: React.FC = () => {
</List.Item>
</List>
</div>
<div className="section">
<List title={intl.formatMessage(messages.supportoverseerr)}>
<List.Item
title={`${intl.formatMessage(messages.helppaycoffee)} ☕️`}
>
<a
href="https://github.com/sponsors/sct"
target="_blank"
rel="noreferrer"
className="text-indigo-500 transition duration-300 hover:underline"
>
https://github.com/sponsors/sct
</a>
<Badge className="ml-2">
{intl.formatMessage(messages.preferredmethod)}
</Badge>
</List.Item>
<List.Item title="">
<a
href="https://patreon.com/overseerr"
target="_blank"
rel="noreferrer"
className="text-indigo-500 transition duration-300 hover:underline"
>
https://patreon.com/overseerr
</a>
</List.Item>
</List>
</div>
<div className="section">
<Releases currentVersion={data.version} />
</div>

View File

@@ -12,30 +12,31 @@ import Badge from '../Common/Badge';
import Button from '../Common/Button';
import LoadingSpinner from '../Common/LoadingSpinner';
import LibraryItem from './LibraryItem';
import getConfig from 'next/config';
const messages = defineMessages({
jellyfinsettings: 'Jellyfin Settings',
jellyfinsettings: '{mediaServerName} Settings',
jellyfinsettingsDescription:
'Configure the settings for your Jellyfin server. Jellyfin scans your Jellyfin libraries to see what content is available.',
'Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.',
timeout: 'Timeout',
save: 'Save Changes',
saving: 'Saving…',
jellyfinlibraries: 'Jellyfin Libraries',
jellyfinlibraries: '{mediaServerName} Libraries',
jellyfinlibrariesDescription:
'The libraries Jellyfin scans for titles. Click the button below if no libraries are listed.',
'The libraries {mediaServerName} scans for titles. Click the button below if no libraries are listed.',
jellyfinSettingsFailure:
'Something went wrong while saving Jellyfin settings.',
jellyfinSettingsSuccess: 'Jellyfin settings saved successfully!',
jellyfinSettings: 'Jellyfin Settings',
'Something went wrong while saving {mediaServerName} settings.',
jellyfinSettingsSuccess: '{mediaServerName} settings saved successfully!',
jellyfinSettings: '{mediaServerName} Settings',
jellyfinSettingsDescription:
'Optionally configure an external player endpoint for your jellyfin server that is different to the internal URL used during setup',
'Optionally configure an external player endpoint for your {mediaServerName} server that is different to the internal URL used during setup',
externalUrl: 'External URL',
validationUrl: 'You must provide a valid URL',
syncing: 'Syncing',
syncJellyfin: 'Sync Libraries',
manualscanJellyfin: 'Manual Library Scan',
manualscanDescriptionJellyfin:
"Normally, this will only be run once every 24 hours. Jellyfin will check your Jellyfin server's recently added more aggressively. If this is your first time configuring Jellyfin, a one-time full manual library scan is recommended!",
"Normally, this will only be run once every 24 hours. Jellyseerr will check your {mediaServerName} server's recently added more aggressively. If this is your first time configuring Jellyseerr, a one-time full manual library scan is recommended!",
notrunning: 'Not Running',
currentlibrary: 'Current Library: {name}',
librariesRemaining: 'Libraries Remaining: {count}',
@@ -80,6 +81,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
);
const intl = useIntl();
const { addToast } = useToasts();
const { publicRuntimeConfig } = getConfig();
const JellyfinSettingsSchema = Yup.object().shape({
jellyfinExternalUrl: Yup.string().matches(
@@ -161,10 +163,22 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
<>
<div className="mb-6">
<h3 className="heading">
<FormattedMessage {...messages.jellyfinlibraries} />
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinlibraries, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinlibraries, {
mediaServerName: 'Jellyfin',
})}
</h3>
<p className="description">
<FormattedMessage {...messages.jellyfinlibrariesDescription} />
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinlibrariesDescription, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinlibrariesDescription, {
mediaServerName: 'Jellyfin',
})}
</p>
</div>
<div className="section">
@@ -201,7 +215,13 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
<FormattedMessage {...messages.manualscanJellyfin} />
</h3>
<p className="description">
<FormattedMessage {...messages.manualscanDescriptionJellyfin} />
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.manualscanDescriptionJellyfin, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.manualscanDescriptionJellyfin, {
mediaServerName: 'Jellyfin',
})}
</p>
</div>
<div className="section">
@@ -305,10 +325,22 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
<>
<div className="mt-10 mb-6">
<h3 className="heading">
{intl.formatMessage(messages.jellyfinSettings)}
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinSettings, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinSettings, {
mediaServerName: 'Jellyfin',
})}
</h3>
<p className="description">
{intl.formatMessage(messages.jellyfinSettingsDescription)}
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinSettingsDescription, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinSettingsDescription, {
mediaServerName: 'Jellyfin',
})}
</p>
</div>
<Formik
@@ -322,15 +354,31 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
externalHostname: values.jellyfinExternalUrl,
} as JellyfinSettings);
addToast(intl.formatMessage(messages.jellyfinSettingsSuccess), {
autoDismiss: true,
appearance: 'success',
});
addToast(
intl.formatMessage(messages.jellyfinSettingsSuccess, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
}),
{
autoDismiss: true,
appearance: 'success',
}
);
} catch (e) {
addToast(intl.formatMessage(messages.jellyfinSettingsFailure), {
autoDismiss: true,
appearance: 'error',
});
addToast(
intl.formatMessage(messages.jellyfinSettingsFailure, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
}),
{
autoDismiss: true,
appearance: 'error',
}
);
} finally {
revalidate();
}

View File

@@ -10,9 +10,11 @@ import {
} from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import { MediaServerType } from '../../../../server/constants/server';
import { CacheItem } from '../../../../server/interfaces/api/settingsInterfaces';
import { JobId } from '../../../../server/lib/settings';
import Spinner from '../../../assets/spinner.svg';
import useSettings from '../../../hooks/useSettings';
import globalMessages from '../../../i18n/globalMessages';
import { formatBytes } from '../../../utils/numberHelpers';
import Badge from '../../Common/Badge';
@@ -102,6 +104,7 @@ const SettingsJobs: React.FC = () => {
const [isSaving, setIsSaving] = useState(false);
const [jobScheduleMinutes, setJobScheduleMinutes] = useState(5);
const [jobScheduleHours, setJobScheduleHours] = useState(1);
const settings = useSettings();
if (!data && !error) {
return <LoadingSpinner />;
@@ -369,22 +372,33 @@ const SettingsJobs: React.FC = () => {
</tr>
</thead>
<Table.TBody>
{cacheData?.map((cache) => (
<tr key={`cache-list-${cache.id}`}>
<Table.TD>{cache.name}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.hits)}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.misses)}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.keys)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.ksize)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.vsize)}</Table.TD>
<Table.TD alignText="right">
<Button buttonType="danger" onClick={() => flushCache(cache)}>
<TrashIcon />
<span>{intl.formatMessage(messages.flushcache)}</span>
</Button>
</Table.TD>
</tr>
))}
{cacheData
?.filter(
(cache) =>
!(
settings.currentSettings.mediaServerType !==
MediaServerType.PLEX && cache.id === 'plexguid'
)
)
.map((cache) => (
<tr key={`cache-list-${cache.id}`}>
<Table.TD>{cache.name}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.hits)}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.misses)}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.keys)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.ksize)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.vsize)}</Table.TD>
<Table.TD alignText="right">
<Button
buttonType="danger"
onClick={() => flushCache(cache)}
>
<TrashIcon />
<span>{intl.formatMessage(messages.flushcache)}</span>
</Button>
</Table.TD>
</tr>
))}
</Table.TBody>
</Table>
</div>

View File

@@ -1,5 +1,8 @@
import getConfig from 'next/config';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { MediaServerType } from '../../../server/constants/server';
import useSettings from '../../hooks/useSettings';
import globalMessages from '../../i18n/globalMessages';
import PageTitle from '../Common/PageTitle';
import SettingsTabs, { SettingsRoute } from '../Common/SettingsTabs';
@@ -8,7 +11,7 @@ const messages = defineMessages({
menuGeneralSettings: 'General',
menuUsers: 'Users',
menuPlexSettings: 'Plex',
menuJellyfinSettings: 'Jellyfin',
menuJellyfinSettings: '{mediaServerName}',
menuServices: 'Services',
menuNotifications: 'Notifications',
menuLogs: 'Logs',
@@ -18,7 +21,8 @@ const messages = defineMessages({
const SettingsLayout: React.FC = ({ children }) => {
const intl = useIntl();
const { publicRuntimeConfig } = getConfig();
const settings = useSettings();
const settingsRoutes: SettingsRoute[] = [
{
text: intl.formatMessage(messages.menuGeneralSettings),
@@ -30,16 +34,17 @@ const SettingsLayout: React.FC = ({ children }) => {
route: '/settings/users',
regex: /^\/settings\/users/,
},
{
text: intl.formatMessage(messages.menuPlexSettings),
route: '/settings/plex',
regex: /^\/settings\/plex/,
},
{
text: intl.formatMessage(messages.menuJellyfinSettings),
route: '/settings/jellyfin',
regex: /^\/settings\/jellyfin/,
},
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? {
text: intl.formatMessage(messages.menuPlexSettings),
route: '/settings/plex',
regex: /^\/settings\/plex/,
}
: {
text: getAvailableMediaServerName(),
route: '/settings/jellyfin',
regex: /^\/settings\/jellyfin/,
},
{
text: intl.formatMessage(messages.menuServices),
route: '/settings/services',
@@ -76,6 +81,12 @@ const SettingsLayout: React.FC = ({ children }) => {
<div className="mt-10 text-white">{children}</div>
</>
);
function getAvailableMediaServerName() {
return intl.formatMessage(messages.menuJellyfinSettings, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE === 'emby' ? 'Emby' : 'Jellyfin',
});
}
};
export default SettingsLayout;

View File

@@ -14,6 +14,7 @@ import LoadingSpinner from '../../Common/LoadingSpinner';
import PageTitle from '../../Common/PageTitle';
import PermissionEdit from '../../PermissionEdit';
import QuotaSelector from '../../QuotaSelector';
import getConfig from 'next/config';
const messages = defineMessages({
users: 'Users',
@@ -42,6 +43,7 @@ const SettingsUsers: React.FC = () => {
mutate: revalidate,
} = useSWR<MainSettings>('/api/v1/settings/main');
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (!data && !error) {
return <LoadingSpinner />;
@@ -131,16 +133,20 @@ const SettingsUsers: React.FC = () => {
<label htmlFor="newPlexLogin" className="checkbox-label">
{intl.formatMessage(messages.newPlexLogin, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
<span className="label-tip">
{intl.formatMessage(messages.newPlexLoginTip, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}

View File

@@ -5,7 +5,7 @@ import { useUser } from '../../hooks/useUser';
import PlexLoginButton from '../PlexLoginButton';
const messages = defineMessages({
welcome: 'Welcome to Overseerr',
welcome: 'Welcome to Jellyseerr',
signinMessage: 'Get started by signing in with your Plex account',
});

View File

@@ -3,14 +3,15 @@ import { useUser } from '../../hooks/useUser';
import PlexLoginButton from '../PlexLoginButton';
import JellyfinLogin from '../Login/JellyfinLogin';
import axios from 'axios';
import { defineMessages, FormattedMessage } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Accordion from '../Common/Accordion';
import { MediaServerType } from '../../../server/constants/server';
import getConfig from 'next/config';
const messages = defineMessages({
welcome: 'Welcome to Overseerr',
welcome: 'Welcome to Jellyseerr',
signinMessage: 'Get started by signing in',
signinWithJellyfin: 'Use your Jellyfin account',
signinWithJellyfin: 'Use your {mediaServerName} account',
signinWithPlex: 'Use your Plex account',
});
@@ -24,7 +25,8 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
MediaServerType.NOT_CONFIGURED
);
const { user, revalidate } = useUser();
const intl = useIntl();
const { publicRuntimeConfig } = getConfig();
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to login. If we get a success message, we will
// ask swr to revalidate the user which _shouid_ come back with a valid user.
@@ -91,7 +93,13 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
}`}
onClick={() => handleClick(1)}
>
<FormattedMessage {...messages.signinWithJellyfin} />
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.signinWithJellyfin, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.signinWithJellyfin, {
mediaServerName: 'Jellyfin',
})}
</button>
<AccordionContent isOpen={openIndexes.includes(1)}>
<div

View File

@@ -44,6 +44,7 @@ import RequestButton from '../RequestButton';
import RequestModal from '../RequestModal';
import Slider from '../Slider';
import StatusBadge from '../StatusBadge';
import getConfig from 'next/config';
const messages = defineMessages({
firstAirDate: 'First Air Date',
@@ -85,6 +86,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
router.query.manage == '1' ? true : false
);
const [showIssueModal, setShowIssueModal] = useState(false);
const { publicRuntimeConfig } = getConfig();
const {
data,
@@ -124,10 +126,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
})
) {
mediaLinks.push({
text:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' })
: intl.formatMessage(messages.play, { mediaServerName: 'Plex' }),
text: getAvalaibleMediaServerName(),
url: data.mediaInfo?.mediaUrl,
svg: <PlayIcon />,
});
@@ -141,10 +140,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
})
) {
mediaLinks.push({
text:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' })
: intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' }),
text: getAvalaible4kMediaServerName(),
url: data.mediaInfo?.mediaUrl4k,
svg: <PlayIcon />,
});
@@ -228,6 +224,30 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
return intl.formatMessage(messages.play, { mediaServerName: 'Plex' });
}
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
}
function getAvalaible4kMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' });
}
return intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' });
}
return (
<div
className="media-page"

View File

@@ -8,6 +8,8 @@ import useSettings from '../../hooks/useSettings';
import globalMessages from '../../i18n/globalMessages';
import Alert from '../Common/Alert';
import Modal from '../Common/Modal';
import getConfig from 'next/config';
import { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
interface JellyfinImportProps {
onCancel?: () => void;
@@ -15,23 +17,25 @@ interface JellyfinImportProps {
}
const messages = defineMessages({
importfromJellyfin: 'Import Jellyfin Users',
importfromJellyfin: 'Import {mediaServerName} Users',
importfromJellyfinerror:
'Something went wrong while importing Jellyfin users.',
'Something went wrong while importing {mediaServerName} users.',
importedfromJellyfin:
'<strong>{userCount}</strong> Jellyfin {userCount, plural, one {user} other {users}} imported successfully!',
'<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} imported successfully!',
user: 'User',
noJellyfinuserstoimport: 'There are no Jellyfin users to import.',
noJellyfinuserstoimport: 'There are no {mediaServerName} users to import.',
newJellyfinsigninenabled:
'The <strong>Enable New Jellyfin Sign-In</strong> setting is currently enabled. Jellyfin users with library access do not need to be imported in order to sign in.',
'The <strong>Enable New {mediaServerName} Sign-In</strong> setting is currently enabled. {mediaServerName} users with library access do not need to be imported in order to sign in.',
});
const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
onCancel,
onComplete,
children,
}) => {
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const [isImporting, setImporting] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
@@ -47,6 +51,18 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
revalidateOnMount: true,
});
const { data: existingUsers } = useSWR<UserResultsResponse>(
`/api/v1/user?take=${children}`
);
data?.forEach((user, pos) => {
if (
existingUsers?.results.some((data) => data.jellyfinUserId === user.id)
) {
data?.splice(pos, 1);
}
});
const importUsers = async () => {
setImporting(true);
@@ -66,6 +82,8 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
strong: function strong(msg) {
return <strong>{msg}</strong>;
},
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
}),
{
autoDismiss: true,
@@ -77,10 +95,16 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
onComplete();
}
} catch (e) {
addToast(intl.formatMessage(messages.importfromJellyfinerror), {
autoDismiss: true,
appearance: 'error',
});
addToast(
intl.formatMessage(messages.importfromJellyfinerror, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
}),
{
autoDismiss: true,
appearance: 'error',
}
);
} finally {
setImporting(false);
}
@@ -110,7 +134,10 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
return (
<Modal
loading={!data && !error}
title={intl.formatMessage(messages.importfromJellyfin)}
title={intl.formatMessage(messages.importfromJellyfin, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
})}
iconSvg={<InboxInIcon />}
onOk={() => {
importUsers();
@@ -126,6 +153,10 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
{settings.currentSettings.newPlexLogin && (
<Alert
title={intl.formatMessage(messages.newJellyfinsigninenabled, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
strong: function strong(msg) {
return (
<strong className="font-semibold text-white">{msg}</strong>
@@ -240,7 +271,10 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
</>
) : (
<Alert
title={intl.formatMessage(messages.noJellyfinuserstoimport)}
title={intl.formatMessage(messages.noJellyfinuserstoimport, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
})}
type="info"
/>
)}

View File

@@ -9,6 +9,7 @@ import {
} from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
@@ -40,7 +41,7 @@ import PlexImportModal from './PlexImportModal';
const messages = defineMessages({
users: 'Users',
userlist: 'User List',
importfromplex: 'Import {mediaServerName} Users',
importfrommediaserver: 'Import {mediaServerName} Users',
user: 'User',
totalrequests: 'Requests',
accounttype: 'Type',
@@ -87,6 +88,7 @@ const UserList: React.FC = () => {
const intl = useIntl();
const router = useRouter();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const { user: currentUser, hasPermission: currentHasPermission } = useUser();
const [currentSort, setCurrentSort] = useState<Sort>('displayname');
@@ -480,7 +482,9 @@ const UserList: React.FC = () => {
setShowImportModal(false);
revalidate();
}}
/>
>
{data.pageInfo.results}
</JellyfinImportModal>
)}
</Transition>
@@ -503,13 +507,18 @@ const UserList: React.FC = () => {
>
<InboxInIcon />
<span>
{intl.formatMessage(messages.importfromplex, {
mediaServerName:
settings.currentSettings.mediaServerType ===
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.importfrommediaserver, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
? intl.formatMessage(messages.importfrommediaserver, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.importfrommediaserver, {
mediaServerName: 'Jellyfin',
})}
</span>
</Button>
</div>
@@ -596,7 +605,7 @@ const UserList: React.FC = () => {
<Link href={`/users/${user.id}`}>
<a className="h-10 w-10 flex-shrink-0">
<img
className="h-10 w-10 rounded-full"
className="h-10 w-10 rounded-full object-cover"
src={user.avatar}
alt=""
/>
@@ -636,17 +645,23 @@ const UserList: React.FC = () => {
<Badge badgeType="warning">
{intl.formatMessage(messages.plexuser)}
</Badge>
) : (
) : user.userType === UserType.LOCAL ? (
<Badge badgeType="default">
{intl.formatMessage(messages.localuser)}
</Badge>
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
<Badge badgeType="success">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
mediaServerName: 'Emby',
})}
</Badge>
)}
) : user.userType === UserType.JELLYFIN ? (
<Badge badgeType="default">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName: 'Jellyfin',
})}
</Badge>
) : null}
</Table.TD>
<Table.TD>
{user.id === 1

View File

@@ -44,7 +44,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
<div className="flex-shrink-0">
<div className="relative">
<img
className="h-24 w-24 rounded-full bg-gray-600 ring-1 ring-gray-700"
className="h-24 w-24 rounded-full bg-gray-600 object-cover ring-1 ring-gray-700"
src={user.avatar}
alt=""
/>

View File

@@ -7,7 +7,6 @@ import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import * as Yup from 'yup';
import { MediaServerType } from '../../../../../server/constants/server';
import { UserSettingsGeneralResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
import {
availableLanguages,
@@ -25,11 +24,13 @@ import PageTitle from '../../../Common/PageTitle';
import LanguageSelector from '../../../LanguageSelector';
import QuotaSelector from '../../../QuotaSelector';
import RegionSelector from '../../../RegionSelector';
import getConfig from 'next/config';
const messages = defineMessages({
general: 'General',
generalsettings: 'General Settings',
displayName: 'Display Name',
email: 'Email',
save: 'Save Changes',
saving: 'Saving…',
mediaServerUser: '{mediaServerName} User',
@@ -59,7 +60,7 @@ const messages = defineMessages({
const UserGeneralSettings: React.FC = () => {
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const { locale, setLocale } = useLocale();
const [movieQuotaEnabled, setMovieQuotaEnabled] = useState(false);
@@ -120,8 +121,9 @@ const UserGeneralSettings: React.FC = () => {
</div>
<Formik
initialValues={{
displayName: data?.username,
discordId: data?.discordId,
displayName: data?.username ?? '',
email: data?.email ?? '',
discordId: data?.discordId ?? '',
locale: data?.locale,
region: data?.region,
originalLanguage: data?.originalLanguage,
@@ -136,6 +138,7 @@ const UserGeneralSettings: React.FC = () => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/main`, {
username: values.displayName,
email: values.email,
discordId: values.discordId,
locale: values.locale,
region: values.region,
@@ -189,19 +192,25 @@ const UserGeneralSettings: React.FC = () => {
<div className="flex max-w-lg items-center">
{user?.userType === UserType.PLEX ? (
<Badge badgeType="warning">
{intl.formatMessage(messages.plexuser)}
</Badge>
) : user?.userType === UserType.LOCAL ? (
<Badge badgeType="default">
{intl.formatMessage(messages.localuser)}
</Badge>
) : (
<Badge badgeType="default">
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
<Badge badgeType="success">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
mediaServerName: 'Emby',
})}
</Badge>
)}
) : user?.userType === UserType.JELLYFIN ? (
<Badge badgeType="default">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName: 'Jellyfin',
})}
</Badge>
) : null}
</div>
</div>
</div>
@@ -239,6 +248,32 @@ const UserGeneralSettings: React.FC = () => {
)}
</div>
</div>
<div className="form-row">
<label htmlFor="email" className="text-label">
{intl.formatMessage(messages.email)}
{user?.warnings.find((w) => w === 'userEmailRequired') && (
<span className="label-required">*</span>
)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="email"
name="email"
type="text"
placeholder="example@domain.com"
className={
user?.warnings.find((w) => w === 'userEmailRequired')
? 'border-2 border-red-400 focus:border-blue-600'
: ''
}
/>
</div>
{errors.email && touched.email && (
<div className="error">{errors.email}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="discordId" className="text-label">
{intl.formatMessage(messages.discordId)}

View File

@@ -13,6 +13,7 @@ export type { PermissionCheckOptions };
export interface User {
id: number;
warnings: string[];
plexUsername?: string;
username?: string;
displayName: string;

View File

@@ -686,7 +686,8 @@
"components.UserList.nouserstoimport": "No hi ha usuaris nous de Plex a importar.",
"components.UserList.localuser": "Usuari local",
"components.UserList.importfromplexerror": "S'ha produït un error en importar usuaris de Plex.",
"components.UserList.importfromplex": "Importeu usuaris de {mediaServerName}",
"components.UserList.importfrommediaserver": "Importeu usuaris de {mediaServerName}",
"components.UserList.importfromplex": "Importeu usuaris de Plex",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {usuari} other {usuaris}} de Plex importat correctament!",
"components.TvDetails.watchtrailer": "Veure el tràiler",
"components.TvDetails.viewfullcrew": "Mostreu equip complet",

View File

@@ -540,4 +540,4 @@
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "ID chatu",
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Potvrďte heslo",
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Aktuální heslo"
}
}

View File

@@ -850,7 +850,8 @@
"components.UserList.nouserstoimport": "Ingen nye brugere som kan importeres fra Plex.",
"components.UserList.edituser": "Redigér Brugertilladelser",
"components.UserList.email": "Email Adresse",
"components.UserList.importfromplex": "Importér Brugere fra {mediaServerName}",
"components.UserList.importfrommediaserver": "Importér Brugere fra {mediaServerName}",
"components.UserList.importfromplex": "Importér Brugere fra Plex",
"components.UserList.owner": "Ejer",
"components.UserList.password": "Kodeord",
"components.UserList.passwordinfodescription": "Konfigurér en applikations-URL og aktivér emailnotifikationer for at tillade automatisk kodeordsgenerering.",

View File

@@ -223,7 +223,8 @@
"components.Settings.SettingsAbout.Releases.latestversion": "Neuste",
"components.Settings.SettingsAbout.Releases.currentversion": "Aktuell",
"components.UserList.importfromplexerror": "Beim Importieren von Plex-Benutzern ist etwas schief gelaufen.",
"components.UserList.importfromplex": "{mediaServerName}-Benutzer importieren",
"components.UserList.importfrommediaserver": "{mediaServerName}-Benutzer importieren",
"components.UserList.importfromplex": "Plex-Benutzer importieren",
"components.TvDetails.viewfullcrew": "Komplette Crew anzeigen",
"components.TvDetails.TvCrew.fullseriescrew": "Komplette Serien-Crew",
"components.PersonDetails.crewmember": "Crew",

View File

@@ -602,7 +602,8 @@
"components.UserList.localuser": "Τοπικός χρήστης",
"components.UserList.localLoginDisabled": "Η ρύθμιση <strong>Ενεργοποίηση τοπικής σύνδεσης</strong> είναι προς το παρόν απενεργοποιημένη.",
"components.UserList.importfromplexerror": "Κάτι πήγε στραβά κατά την εισαγωγή χρηστών από το Plex.",
"components.UserList.importfromplex": "Εισαγωγή χρηστών από το {mediaServerName}",
"components.UserList.importfrommediaserver": "Εισαγωγή χρηστών από το {mediaServerName}",
"components.UserList.importfromplex": "Εισαγωγή χρηστών από το Plex",
"components.UserList.importedfromplex": "{userCount, plural, one {# νέου χρήστη} other {#νέοι χρήστες}} εισήχθησαν απο το Plex επιτυχώς!",
"components.UserList.email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
"components.UserList.edituser": "Επεξεργασία δικαιωμάτων χρήστη",

View File

@@ -861,7 +861,8 @@
"components.UserList.edituser": "Edit User Permissions",
"components.UserList.email": "Email Address",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
"components.UserList.importfromplex": "Import {mediaServerName} Users",
"components.UserList.importfrommediaserver": "Import {mediaServerName} Users",
"components.UserList.importfromplex": "Import Plex Users",
"components.UserList.importfromplexerror": "Something went wrong while importing Plex users.",
"components.UserList.localLoginDisabled": "The <strong>Enable Local Sign-In</strong> setting is currently disabled.",
"components.UserList.localuser": "Local User",

View File

@@ -223,7 +223,8 @@
"components.Settings.SettingsAbout.Releases.currentversion": "Actual",
"components.MovieDetails.studio": "{studioCount, plural, one {Estudio} other {Estudios}}",
"components.UserList.importfromplexerror": "Algo salió mal importando usuarios de Plex.",
"components.UserList.importfromplex": "Importar Usuarios de {mediaServerName}",
"components.UserList.importfrommediaserver": "Importar Usuarios de {mediaServerName}",
"components.UserList.importfromplex": "Importar Usuarios de Plex",
"components.UserList.importedfromplex": "¡{userCount, plural, one {# nuevo usuario} other {# nuevos usuarios}} importado/s de Plex con éxito!",
"components.TvDetails.viewfullcrew": "Ver Equipo Completo",
"components.TvDetails.firstAirDate": "Primera fecha de emisión",

View File

@@ -223,7 +223,8 @@
"components.Settings.SettingsAbout.Releases.latestversion": "Dernière version",
"components.Settings.SettingsAbout.Releases.currentversion": "Actuelle",
"components.UserList.importfromplexerror": "Une erreur s'est produite durant l'importation des utilisateurs de Plex.",
"components.UserList.importfromplex": "Importer les utilisateurs de {mediaServerName}",
"components.UserList.importfrommediaserver": "Importer les utilisateurs de {mediaServerName}",
"components.UserList.importfromplex": "Importer les utilisateurs de Plex",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {utilisateur} other {utilisateurs}} importé(s) depuis Plex avec succès !",
"components.TvDetails.viewfullcrew": "Voir l'équipe complète de la série",
"components.TvDetails.TvCrew.fullseriescrew": "Équipe complète de la série",

View File

@@ -165,7 +165,8 @@
"components.UserList.password": "Jelszó",
"components.UserList.localuser": "Helyi felhasználó",
"components.UserList.importfromplexerror": "Hiba történt a felhasználók Plex-ről történő importálása közben.",
"components.UserList.importfromplex": "Felhasználók importálása {mediaServerName}-ről",
"components.UserList.importfrommediaserver": "Felhasználók importálása {mediaServerName}-ről",
"components.UserList.importfromplex": "Felhasználók importálása Plex-ről",
"components.UserList.importedfromplex": "{userCount, plural, =0 {Nem lett új} one {# új} other {# új}} felhasználó importálva Plex-ről!",
"components.UserList.email": "E-mail-cím",
"components.UserList.deleteuser": "Felhasználó törlése",

View File

@@ -223,7 +223,8 @@
"components.Settings.SettingsAbout.Releases.latestversion": "Versione più recente",
"components.Settings.SettingsAbout.Releases.currentversion": "Versione attuale",
"components.UserList.importfromplexerror": "Qualcosa è andato storto nell'importare gli utenti Plex.",
"components.UserList.importfromplex": "Importa utenti {mediaServerName}",
"components.UserList.importfrommediaserver": "Importa utenti {mediaServerName}",
"components.UserList.importfromplex": "Importa utenti Plex",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {utente} other {utenti}} Plex {userCount, plural, one {importato} other {importati}} correttamente!",
"components.TvDetails.viewfullcrew": "Vedi troupe completa",
"components.TvDetails.TvCrew.fullseriescrew": "Troupe completa serie",

View File

@@ -231,7 +231,8 @@
"components.TvDetails.watchtrailer": "予告編を見る",
"components.MovieDetails.watchtrailer": "予告編を見る",
"components.UserList.importfromplexerror": "Plexからユーザーをインポート中に問題が発生しました。",
"components.UserList.importfromplex": "{mediaServerName}からユーザーをインポート",
"components.UserList.importfrommediaserver": "{mediaServerName}からユーザーをインポート",
"components.UserList.importfromplex": "Plexからユーザーをインポート",
"components.UserList.importedfromplex": "Plex から新ユーザー {userCount} 名をインポートしました。",
"components.TvDetails.viewfullcrew": "フルクルーを表示",
"components.TvDetails.firstAirDate": "初放送日",

View File

@@ -194,7 +194,8 @@
"components.UserList.userssaved": "Brukertillatelsene ble lagret!",
"components.UserList.users": "Brukere",
"components.UserList.importfromplexerror": "Noe gikk galt ved importering av brukere fra Plex.",
"components.UserList.importfromplex": "Importer brukere fra {mediaServerName}",
"components.UserList.importfrommediaserver": "Importer brukere fra {mediaServerName}",
"components.UserList.importfromplex": "Importer brukere fra Plex",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {ny bruker} other {nye brukere}} ble importert fra Plex!",
"components.Settings.menuUsers": "Brukere",
"components.Settings.SettingsUsers.users": "Brukere",

View File

@@ -214,7 +214,8 @@
"components.UserList.userdeleteerror": "Er ging iets mis bij het verwijderen van de gebruiker.",
"components.UserList.userdeleted": "Gebruiker succesvol verwijderd!",
"components.UserList.importfromplexerror": "Er is iets misgegaan bij het importeren van Plex-gebruikers.",
"components.UserList.importfromplex": "{mediaServerName}-gebruikers importeren",
"components.UserList.importfrommediaserver": "{mediaServerName}-gebruikers importeren",
"components.UserList.importfromplex": "Plex-gebruikers importeren",
"components.UserList.deleteuser": "Gebruiker verwijderen",
"components.UserList.deleteconfirm": "Weet je zeker dat je deze gebruiker wilt verwijderen? Al hun bestaande aanvraaggegevens zullen worden verwijderd.",
"components.TvDetails.watchtrailer": "Trailer bekijken",

View File

@@ -962,7 +962,8 @@
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Wyślij po cichu",
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Nie udało się zapisać ustawień powiadomień telegram.",
"components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "<FindDiscordIdLink>Wielocyfrowy numer ID</FindDiscordIdLink> powiązany z Twoim kontem użytkownika",
"components.UserList.importfromplex": "Importuj użytkowników {mediaServerName}",
"components.UserList.importfrommediaserver": "Importuj użytkowników {mediaServerName}",
"components.UserList.importfromplex": "Importuj użytkowników Plex",
"i18n.available": "Dostępny",
"components.UserList.sortDisplayName": "Wyświetlana nazwa",
"components.UserList.totalrequests": "Prośby",

View File

@@ -228,7 +228,8 @@
"components.MovieDetails.viewfullcrew": "Ver Equipe Técnica Completa",
"components.MovieDetails.MovieCrew.fullcrew": "Equipe Técnica Completa",
"components.UserList.importfromplexerror": "Algo deu errado ao importar usuários do Plex.",
"components.UserList.importfromplex": "Importar Usuários do {mediaServerName}",
"components.UserList.importfrommediaserver": "Importar Usuários do {mediaServerName}",
"components.UserList.importfromplex": "Importar Usuários do Plex",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {usuário Plex importado} other {usuários Plex importados}} com sucesso!",
"components.Settings.Notifications.NotificationsSlack.agentenabled": "Habilitar Agente",
"components.RequestList.RequestItem.failedretry": "Algo deu errado ao retentar fazer a solicitação.",

View File

@@ -199,7 +199,8 @@
"components.UserList.passwordinfodescription": "Configurar um URL de aplicação e ativar as notificações por e-mail para permitir a geração automática de palavra-passe.",
"components.UserList.localuser": "Utilizador Local",
"components.UserList.importfromplexerror": "Ocorreu um erro ao importar utilizadores do Plex.",
"components.UserList.importfromplex": "Importar Utilizadores do {mediaServerName}",
"components.UserList.importfrommediaserver": "Importar Utilizadores do {mediaServerName}",
"components.UserList.importfromplex": "Importar Utilizadores do Plex",
"components.UserList.importedfromplex": "{userCount, plural, one {# novo utilizador} other {# novos utilizadores}} importados do Plex com sucesso!",
"components.UserList.email": "Endereço de E-mail",
"components.UserList.deleteuser": "Apagar Utilizador",

View File

@@ -793,7 +793,8 @@
"components.UserList.usercreatedfailed": "Что-то пошло не так при создании пользователя.",
"components.UserList.passwordinfodescription": "Настройте URL-адрес приложения и включите уведомления по электронной почте, чтобы обеспечить возможность автоматической генерации пароля.",
"components.UserList.importfromplexerror": "Что-то пошло не так при импорте пользователей из Plex.",
"components.UserList.importfromplex": "Импортировать пользователей из {mediaServerName}",
"components.UserList.importfrommediaserver": "Импортировать пользователей из {mediaServerName}",
"components.UserList.importfromplex": "Импортировать пользователей из Plex",
"components.UserList.importedfromplex": "{userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из Plex!",
"components.UserList.edituser": "Изменить разрешения пользователя",
"components.UserList.displayName": "Отображаемое имя",

View File

@@ -1006,7 +1006,8 @@
"components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Diçka shkoi keq duke ruajtur cilësimet.",
"components.Settings.webAppUrlTip": "Në mënyrë opsionale drejto përdoruesit në aplikacionin web në serverin tënd në vend të atij web",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minuta",
"components.UserList.importfromplex": "Importoni përdoruesit {mediaServerName}",
"components.UserList.importfrommediaserver": "Importoni përdoruesit {mediaServerName}",
"components.UserList.importfromplex": "Importoni përdoruesit Plex",
"components.UserList.importfromplexerror": "Diçka shkoi keq duke importuar përdoruesit Plex.",
"components.TvDetails.firstAirDate": "Data e parë e transmetimit",
"components.UserList.email": "Adresa email",

View File

@@ -222,7 +222,8 @@
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Versionsdata är för närvarande inte tillgänglig.",
"components.Settings.SettingsAbout.Releases.latestversion": "Senaste Versionen",
"components.Settings.SettingsAbout.Releases.currentversion": "Aktuell",
"components.UserList.importfromplex": "Importera {mediaServerName}användare",
"components.UserList.importfrommediaserver": "Importera {mediaServerName}användare",
"components.UserList.importfromplex": "Importera Plexanvändare",
"components.UserList.importfromplexerror": "Något gick fel när Plexanvändare importerades.",
"components.TvDetails.watchtrailer": "Kolla Trailer",
"components.Settings.Notifications.allowselfsigned": "Tillåt Självsignerade Certifikat",

View File

@@ -24,7 +24,8 @@
"components.UserList.localuser": "本地用户",
"components.UserList.localLoginDisabled": "<strong>允许本地登录</strong>的设置目前被禁用。",
"components.UserList.importfromplexerror": "导入 Plex 用户时出错。",
"components.UserList.importfromplex": "导入 {mediaServerName} 用户",
"components.UserList.importfrommediaserver": "导入 {mediaServerName} 用户",
"components.UserList.importfromplex": "导入 Plex 用户",
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} 成功导入!",
"components.UserList.email": "电子邮件地址",
"components.UserList.edituser": "编辑用户权限",

View File

@@ -52,7 +52,8 @@
"components.Settings.radarrsettings": "Radarr 設定",
"components.Settings.menuPlexSettings": "Plex",
"components.UserList.importfromplexerror": "匯入 Plex 使用者時出了點問題。",
"components.UserList.importfromplex": "匯入 {mediaServerName} 使用者",
"components.UserList.importfrommediaserver": "匯入 {mediaServerName} 使用者",
"components.UserList.importfromplex": "匯入 Plex 使用者",
"components.UserList.importedfromplex": "匯入 <strong>{userCount}</strong> 個 Plex 使用者成功!",
"components.UserList.localuser": "本地使用者",
"components.UserList.creating": "創建中…",

View File

@@ -2,6 +2,7 @@
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
important: true,
mode: 'jit',
content: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'],
theme: {

View File

@@ -1622,6 +1622,13 @@
dependencies:
glob "7.1.7"
"@next/eslint-plugin-next@^12.1.6":
version "12.1.6"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.1.6.tgz#dde3f98831f15923b25244588d924c716956292e"
integrity sha512-yNUtJ90NEiYFT6TJnNyofKMPYqirKDwpahcbxBgSIuABwYOdkGwzos1ZkYD51Qf0diYwpQZBeVqElTk7Q2WNqw==
dependencies:
glob "7.1.7"
"@next/swc-android-arm64@12.1.0":
version "12.1.0"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.0.tgz#865ba3a9afc204ff2bdeea49dd64d58705007a39"
@@ -4780,6 +4787,11 @@ email-templates@^8.0.10:
nodemailer "^6.7.2"
preview-email "^3.0.5"
email-validator@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed"
integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==
emoji-regex@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.1.tgz#77180edb279b99510a21b79b19e1dc283d8f3991"