Compare commits

..

2 Commits

Author SHA1 Message Date
Gauthier
55cf903bf6 fix: add bcrypt to the build only dependencies 2025-01-22 12:56:21 +01:00
gauthier-th
334502a8c9 refactor: upgrade to pnpm 10 2025-01-22 00:11:24 +01:00
36 changed files with 4662 additions and 6544 deletions

View File

@@ -7,7 +7,7 @@
"badgeTemplate": "<a href=\"#contributors-\"><img alt=\"All Contributors\" src=\"https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg\"/></a>",
"contributorsPerLine": 7,
"projectName": "jellyseerr",
"projectOwner": "fallenbagel",
"projectOwner": "Fallenbagel",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
@@ -601,15 +601,6 @@
"contributions": [
"design"
]
},
{
"login": "ishanjain28",
"name": "Ishan Jain",
"avatar_url": "https://avatars.githubusercontent.com/u/7921368?v=4",
"profile": "https://ishanjain.me",
"contributions": [
"code"
]
}
]
}

View File

@@ -14,7 +14,7 @@ RUN \
;; \
esac
RUN npm install --global pnpm@9
RUN npm install --global pnpm
COPY package.json pnpm-lock.yaml postinstall-win.js ./
RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile
@@ -29,7 +29,7 @@ RUN pnpm build
# remove development dependencies
RUN pnpm prune --prod --ignore-scripts
RUN rm -rf src server .next/cache charts gen-docs docs
RUN rm -rf src server .next/cache
RUN touch config/DOCKER
@@ -45,7 +45,7 @@ WORKDIR /app
RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
RUN npm install -g pnpm@9
RUN npm install -g pnpm
# copy from build image
COPY --from=BUILD_IMAGE /app ./

View File

@@ -3,7 +3,7 @@ FROM node:22-alpine
COPY . /app
WORKDIR /app
RUN npm install --global pnpm@9
Run npm install --global pnpm
RUN pnpm install

101
README.md
View File

@@ -11,7 +11,7 @@
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-66-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-65-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
**Jellyseerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**.
@@ -86,90 +86,89 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fallenbagel"><img src="https://avatars.githubusercontent.com/u/98979876?v=4?s=100" width="100px;" alt="Fallenbagel"/><br /><sub><b>Fallenbagel</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Fallenbagel" title="Code">💻</a> <a href="#maintenance-Fallenbagel" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/seanzhang98"><img src="https://avatars.githubusercontent.com/u/34902361?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="#translation-seanzhang98" title="Translation">🌍</a> <a href="https://github.com/fallenbagel/jellyseerr/commits?author=seanzhang98" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notfakie"><img src="https://avatars.githubusercontent.com/u/103784113?v=4?s=100" width="100px;" alt="notfakie"/><br /><sub><b>notfakie</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=notfakie" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jumail"><img src="https://avatars.githubusercontent.com/u/7672055?v=4?s=100" width="100px;" alt="Mohamed Jumail"/><br /><sub><b>Mohamed Jumail</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/pulls?q=is%3Apr+reviewed-by%3AJumail" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.heywhale.com"><img src="https://avatars.githubusercontent.com/u/4048787?v=4?s=100" width="100px;" alt="Shilong Jiang"/><br /><sub><b>Shilong Jiang</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=jsl9208" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jinas.me"><img src="https://avatars.githubusercontent.com/u/28459081?v=4?s=100" width="100px;" alt="Boring Dragon"/><br /><sub><b>Boring Dragon</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=boring-dragon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sambartik"><img src="https://avatars.githubusercontent.com/u/63553146?v=4?s=100" width="100px;" alt="Samuel Bartík"/><br /><sub><b>Samuel Bartík</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=sambartik" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fallenbagel"><img src="https://avatars.githubusercontent.com/u/98979876?v=4?s=100" width="100px;" alt="Fallenbagel"/><br /><sub><b>Fallenbagel</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Fallenbagel" title="Code">💻</a> <a href="#maintenance-Fallenbagel" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/seanzhang98"><img src="https://avatars.githubusercontent.com/u/34902361?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="#translation-seanzhang98" title="Translation">🌍</a> <a href="https://github.com/Fallenbagel/jellyseerr/commits?author=seanzhang98" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notfakie"><img src="https://avatars.githubusercontent.com/u/103784113?v=4?s=100" width="100px;" alt="notfakie"/><br /><sub><b>notfakie</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=notfakie" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jumail"><img src="https://avatars.githubusercontent.com/u/7672055?v=4?s=100" width="100px;" alt="Mohamed Jumail"/><br /><sub><b>Mohamed Jumail</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/pulls?q=is%3Apr+reviewed-by%3AJumail" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.heywhale.com"><img src="https://avatars.githubusercontent.com/u/4048787?v=4?s=100" width="100px;" alt="Shilong Jiang"/><br /><sub><b>Shilong Jiang</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=jsl9208" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jinas.me"><img src="https://avatars.githubusercontent.com/u/28459081?v=4?s=100" width="100px;" alt="Boring Dragon"/><br /><sub><b>Boring Dragon</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=boring-dragon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sambartik"><img src="https://avatars.githubusercontent.com/u/63553146?v=4?s=100" width="100px;" alt="Samuel Bartík"/><br /><sub><b>Samuel Bartík</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=sambartik" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CyferShepard"><img src="https://avatars.githubusercontent.com/u/24864904?v=4?s=100" width="100px;" alt="Thegan Govender"/><br /><sub><b>Thegan Govender</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=CyferShepard" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jab416171"><img src="https://avatars.githubusercontent.com/u/345752?v=4?s=100" width="100px;" alt="jab416171"/><br /><sub><b>jab416171</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=jab416171" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nvds.be"><img src="https://avatars.githubusercontent.com/u/5257222?v=4?s=100" width="100px;" alt="Nicolai Van der Storm"/><br /><sub><b>Nicolai Van der Storm</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=NicolaiVdS" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CyferShepard"><img src="https://avatars.githubusercontent.com/u/24864904?v=4?s=100" width="100px;" alt="Thegan Govender"/><br /><sub><b>Thegan Govender</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=CyferShepard" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jab416171"><img src="https://avatars.githubusercontent.com/u/345752?v=4?s=100" width="100px;" alt="jab416171"/><br /><sub><b>jab416171</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=jab416171" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nvds.be"><img src="https://avatars.githubusercontent.com/u/5257222?v=4?s=100" width="100px;" alt="Nicolai Van der Storm"/><br /><sub><b>Nicolai Van der Storm</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=NicolaiVdS" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Smexhy"><img src="https://avatars.githubusercontent.com/u/4880625?v=4?s=100" width="100px;" alt="Smexhy"/><br /><sub><b>Smexhy</b></sub></a><br /><a href="#translation-Smexhy" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://dd06-dev.fr"><img src="https://avatars.githubusercontent.com/u/58089504?v=4?s=100" width="100px;" alt="dd060606"/><br /><sub><b>dd060606</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=dd060606" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://qwer.tz"><img src="https://avatars.githubusercontent.com/u/71837281?v=4?s=100" width="100px;" alt="Daniel"/><br /><sub><b>Daniel</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=darmiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://dd06-dev.fr"><img src="https://avatars.githubusercontent.com/u/58089504?v=4?s=100" width="100px;" alt="dd060606"/><br /><sub><b>dd060606</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=dd060606" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://qwer.tz"><img src="https://avatars.githubusercontent.com/u/71837281?v=4?s=100" width="100px;" alt="Daniel"/><br /><sub><b>Daniel</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=darmiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/undone37"><img src="https://avatars.githubusercontent.com/u/10513808?v=4?s=100" width="100px;" alt="undone37"/><br /><sub><b>undone37</b></sub></a><br /><a href="#translation-undone37" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CheChu10"><img src="https://avatars.githubusercontent.com/u/32913133?v=4?s=100" width="100px;" alt="Chechu García"/><br /><sub><b>Chechu García</b></sub></a><br /><a href="#translation-CheChu10" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DimitriDR"><img src="https://avatars.githubusercontent.com/u/56969769?v=4?s=100" width="100px;" alt="Dimitri"/><br /><sub><b>Dimitri</b></sub></a><br /><a href="#translation-DimitriDR" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrey4korop"><img src="https://avatars.githubusercontent.com/u/24610708?v=4?s=100" width="100px;" alt="andrey4korop"/><br /><sub><b>andrey4korop</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=andrey4korop" title="Code">💻</a> <a href="#translation-andrey4korop" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrey4korop"><img src="https://avatars.githubusercontent.com/u/24610708?v=4?s=100" width="100px;" alt="andrey4korop"/><br /><sub><b>andrey4korop</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=andrey4korop" title="Code">💻</a> <a href="#translation-andrey4korop" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://geoffrey-coulaud.fr"><img src="https://avatars.githubusercontent.com/u/20744730?v=4?s=100" width="100px;" alt="Geoffrey Coulaud"/><br /><sub><b>Geoffrey Coulaud</b></sub></a><br /><a href="#translation-GeoffreyCoulaud" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pikachu920"><img src="https://avatars.githubusercontent.com/u/28607612?v=4?s=100" width="100px;" alt="Pikachu920"/><br /><sub><b>Pikachu920</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Pikachu920" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yalagin"><img src="https://avatars.githubusercontent.com/u/12879142?v=4?s=100" width="100px;" alt="Maxim Yalagin"/><br /><sub><b>Maxim Yalagin</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=yalagin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeaboswell"><img src="https://avatars.githubusercontent.com/u/11653068?v=4?s=100" width="100px;" alt="Jesse Boswell"/><br /><sub><b>Jesse Boswell</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=jeaboswell" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pikachu920"><img src="https://avatars.githubusercontent.com/u/28607612?v=4?s=100" width="100px;" alt="Pikachu920"/><br /><sub><b>Pikachu920</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Pikachu920" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yalagin"><img src="https://avatars.githubusercontent.com/u/12879142?v=4?s=100" width="100px;" alt="Maxim Yalagin"/><br /><sub><b>Maxim Yalagin</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=yalagin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeaboswell"><img src="https://avatars.githubusercontent.com/u/11653068?v=4?s=100" width="100px;" alt="Jesse Boswell"/><br /><sub><b>Jesse Boswell</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=jeaboswell" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/d-fendrich"><img src="https://avatars.githubusercontent.com/u/27904138?v=4?s=100" width="100px;" alt="d-fendrich"/><br /><sub><b>d-fendrich</b></sub></a><br /><a href="#translation-d-fendrich" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/davidfdezalcoba"><img src="https://avatars.githubusercontent.com/u/15996018?v=4?s=100" width="100px;" alt="David Fernández Alcoba"/><br /><sub><b>David Fernández Alcoba</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=davidfdezalcoba" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/davidfdezalcoba"><img src="https://avatars.githubusercontent.com/u/15996018?v=4?s=100" width="100px;" alt="David Fernández Alcoba"/><br /><sub><b>David Fernández Alcoba</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=davidfdezalcoba" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Gauvino"><img src="https://avatars.githubusercontent.com/u/68083474?v=4?s=100" width="100px;" alt="Gauvino"/><br /><sub><b>Gauvino</b></sub></a><br /><a href="#translation-Gauvino" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EthanArmbrust"><img src="https://avatars.githubusercontent.com/u/22754714?v=4?s=100" width="100px;" alt="EthanArmbrust"/><br /><sub><b>EthanArmbrust</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=EthanArmbrust" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.piribisoft.com"><img src="https://avatars.githubusercontent.com/u/854646?v=4?s=100" width="100px;" alt="Eduardo"/><br /><sub><b>Eduardo</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=SirMartin" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RickLuiken"><img src="https://avatars.githubusercontent.com/u/34110371?v=4?s=100" width="100px;" alt="RickLuiken"/><br /><sub><b>RickLuiken</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=RickLuiken" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EthanArmbrust"><img src="https://avatars.githubusercontent.com/u/22754714?v=4?s=100" width="100px;" alt="EthanArmbrust"/><br /><sub><b>EthanArmbrust</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=EthanArmbrust" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.piribisoft.com"><img src="https://avatars.githubusercontent.com/u/854646?v=4?s=100" width="100px;" alt="Eduardo"/><br /><sub><b>Eduardo</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=SirMartin" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RickLuiken"><img src="https://avatars.githubusercontent.com/u/34110371?v=4?s=100" width="100px;" alt="RickLuiken"/><br /><sub><b>RickLuiken</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=RickLuiken" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Br33ce"><img src="https://avatars.githubusercontent.com/u/124933490?v=4?s=100" width="100px;" alt="Br33ce"/><br /><sub><b>Br33ce</b></sub></a><br /><a href="#translation-Br33ce" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://athfan.com"><img src="https://avatars.githubusercontent.com/u/13810742?v=4?s=100" width="100px;" alt="Athfan Khaleel"/><br /><sub><b>Athfan Khaleel</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=athphane" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://athfan.com"><img src="https://avatars.githubusercontent.com/u/13810742?v=4?s=100" width="100px;" alt="Athfan Khaleel"/><br /><sub><b>Athfan Khaleel</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=athphane" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mdll23"><img src="https://avatars.githubusercontent.com/u/142844478?v=4?s=100" width="100px;" alt="Michael Dallinger"/><br /><sub><b>Michael Dallinger</b></sub></a><br /><a href="#translation-mdll23" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xeruf"><img src="https://avatars.githubusercontent.com/u/13354331?v=4?s=100" width="100px;" alt="Janek"/><br /><sub><b>Janek</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=xeruf" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xeruf"><img src="https://avatars.githubusercontent.com/u/13354331?v=4?s=100" width="100px;" alt="Janek"/><br /><sub><b>Janek</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=xeruf" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://aleksasiriski.dev"><img src="https://avatars.githubusercontent.com/u/31509435?v=4?s=100" width="100px;" alt="Aleksa Siriški"/><br /><sub><b>Aleksa Siriški</b></sub></a><br /><a href="#infra-aleksasiriski" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://danishhumair.com"><img src="https://avatars.githubusercontent.com/u/121830048?v=4?s=100" width="100px;" alt="Danish Humair"/><br /><sub><b>Danish Humair</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Danish-H" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://arm0.red"><img src="https://avatars.githubusercontent.com/u/16858514?v=4?s=100" width="100px;" alt="Stephen Harris"/><br /><sub><b>Stephen Harris</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=trackmastersteve" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.boniface.me"><img src="https://avatars.githubusercontent.com/u/4031396?v=4?s=100" width="100px;" alt="Joshua M. Boniface"/><br /><sub><b>Joshua M. Boniface</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=joshuaboniface" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://danishhumair.com"><img src="https://avatars.githubusercontent.com/u/121830048?v=4?s=100" width="100px;" alt="Danish Humair"/><br /><sub><b>Danish Humair</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Danish-H" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://arm0.red"><img src="https://avatars.githubusercontent.com/u/16858514?v=4?s=100" width="100px;" alt="Stephen Harris"/><br /><sub><b>Stephen Harris</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=trackmastersteve" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.boniface.me"><img src="https://avatars.githubusercontent.com/u/4031396?v=4?s=100" width="100px;" alt="Joshua M. Boniface"/><br /><sub><b>Joshua M. Boniface</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=joshuaboniface" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://gauthierth.fr/"><img src="https://avatars.githubusercontent.com/u/37781713?v=4?s=100" width="100px;" alt="Gauthier"/><br /><sub><b>Gauthier</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=gauthier-th" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://gauthierth.fr/"><img src="https://avatars.githubusercontent.com/u/37781713?v=4?s=100" width="100px;" alt="Gauthier"/><br /><sub><b>Gauthier</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=gauthier-th" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Kara-Zor-El"><img src="https://avatars.githubusercontent.com/u/69772087?v=4?s=100" width="100px;" alt="Kara"/><br /><sub><b>Kara</b></sub></a><br /><a href="#infra-Kara-Zor-El" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://joaquinolivero.com"><img src="https://avatars.githubusercontent.com/u/66050823?v=4?s=100" width="100px;" alt="Joaquin Olivero"/><br /><sub><b>Joaquin Olivero</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=JoaquinOlivero" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://joaquinolivero.com"><img src="https://avatars.githubusercontent.com/u/66050823?v=4?s=100" width="100px;" alt="Joaquin Olivero"/><br /><sub><b>Joaquin Olivero</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=JoaquinOlivero" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Bretterteig"><img src="https://avatars.githubusercontent.com/u/47298401?v=4?s=100" width="100px;" alt="Julian Behr"/><br /><sub><b>Julian Behr</b></sub></a><br /><a href="#translation-Bretterteig" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ThowZzy"><img src="https://avatars.githubusercontent.com/u/61882536?v=4?s=100" width="100px;" alt="ThowZzy"/><br /><sub><b>ThowZzy</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=ThowZzy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://josephrisk.com"><img src="https://avatars.githubusercontent.com/u/18372584?v=4?s=100" width="100px;" alt="Joseph Risk"/><br /><sub><b>Joseph Risk</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=j0srisk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loetwiek"><img src="https://avatars.githubusercontent.com/u/79059734?v=4?s=100" width="100px;" alt="Loetwiek"/><br /><sub><b>Loetwiek</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Loetwiek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ThowZzy"><img src="https://avatars.githubusercontent.com/u/61882536?v=4?s=100" width="100px;" alt="ThowZzy"/><br /><sub><b>ThowZzy</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=ThowZzy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://josephrisk.com"><img src="https://avatars.githubusercontent.com/u/18372584?v=4?s=100" width="100px;" alt="Joseph Risk"/><br /><sub><b>Joseph Risk</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=j0srisk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loetwiek"><img src="https://avatars.githubusercontent.com/u/79059734?v=4?s=100" width="100px;" alt="Loetwiek"/><br /><sub><b>Loetwiek</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Loetwiek" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fuochi"><img src="https://avatars.githubusercontent.com/u/4720478?v=4?s=100" width="100px;" alt="Fuochi"/><br /><sub><b>Fuochi</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Fuochi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fuochi"><img src="https://avatars.githubusercontent.com/u/4720478?v=4?s=100" width="100px;" alt="Fuochi"/><br /><sub><b>Fuochi</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Fuochi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://mobihen.com"><img src="https://avatars.githubusercontent.com/u/35529491?v=4?s=100" width="100px;" alt="Nir Israel Hen"/><br /><sub><b>Nir Israel Hen</b></sub></a><br /><a href="#translation-mobihen" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XDark187"><img src="https://avatars.githubusercontent.com/u/39034192?v=4?s=100" width="100px;" alt="Baraa"/><br /><sub><b>Baraa</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=XDark187" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franciscofsales"><img src="https://avatars.githubusercontent.com/u/7977645?v=4?s=100" width="100px;" alt="Francisco Sales"/><br /><sub><b>Francisco Sales</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=franciscofsales" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myselfolli"><img src="https://avatars.githubusercontent.com/u/37535998?v=4?s=100" width="100px;" alt="Oliver Laing"/><br /><sub><b>Oliver Laing</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=myselfolli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XDark187"><img src="https://avatars.githubusercontent.com/u/39034192?v=4?s=100" width="100px;" alt="Baraa"/><br /><sub><b>Baraa</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=XDark187" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franciscofsales"><img src="https://avatars.githubusercontent.com/u/7977645?v=4?s=100" width="100px;" alt="Francisco Sales"/><br /><sub><b>Francisco Sales</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=franciscofsales" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myselfolli"><img src="https://avatars.githubusercontent.com/u/37535998?v=4?s=100" width="100px;" alt="Oliver Laing"/><br /><sub><b>Oliver Laing</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=myselfolli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/M0NsTeRRR"><img src="https://avatars.githubusercontent.com/u/37785089?v=4?s=100" width="100px;" alt="Ludovic Ortega"/><br /><sub><b>Ludovic Ortega</b></sub></a><br /><a href="#security-M0NsTeRRR" title="Security">🛡️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://josephrisk.com"><img src="https://avatars.githubusercontent.com/u/18372584?v=4?s=100" width="100px;" alt="Joseph Risk"/><br /><sub><b>Joseph Risk</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=j0srisk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://josephrisk.com"><img src="https://avatars.githubusercontent.com/u/18372584?v=4?s=100" width="100px;" alt="Joseph Risk"/><br /><sub><b>Joseph Risk</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=j0srisk" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loetwiek"><img src="https://avatars.githubusercontent.com/u/79059734?v=4?s=100" width="100px;" alt="Loetwiek"/><br /><sub><b>Loetwiek</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Loetwiek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fuochi"><img src="https://avatars.githubusercontent.com/u/4720478?v=4?s=100" width="100px;" alt="Fuochi"/><br /><sub><b>Fuochi</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Fuochi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/demrich"><img src="https://avatars.githubusercontent.com/u/30092389?v=4?s=100" width="100px;" alt="David Emrich"/><br /><sub><b>David Emrich</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=demrich" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://maxtrier.dk"><img src="https://avatars.githubusercontent.com/u/5898152?v=4?s=100" width="100px;" alt="Max T. Kristiansen"/><br /><sub><b>Max T. Kristiansen</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=maxnatamo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://damsdev.me"><img src="https://avatars.githubusercontent.com/u/60252259?v=4?s=100" width="100px;" alt="Damien Fajole"/><br /><sub><b>Damien Fajole</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=DamsDev1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AhmedNSidd"><img src="https://avatars.githubusercontent.com/u/36286128?v=4?s=100" width="100px;" alt="Ahmed Siddiqui"/><br /><sub><b>Ahmed Siddiqui</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=AhmedNSidd" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zariel"><img src="https://avatars.githubusercontent.com/u/2213?v=4?s=100" width="100px;" alt="Chris Bannister"/><br /><sub><b>Chris Bannister</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Zariel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Loetwiek"><img src="https://avatars.githubusercontent.com/u/79059734?v=4?s=100" width="100px;" alt="Loetwiek"/><br /><sub><b>Loetwiek</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Loetwiek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fuochi"><img src="https://avatars.githubusercontent.com/u/4720478?v=4?s=100" width="100px;" alt="Fuochi"/><br /><sub><b>Fuochi</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Fuochi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/demrich"><img src="https://avatars.githubusercontent.com/u/30092389?v=4?s=100" width="100px;" alt="David Emrich"/><br /><sub><b>David Emrich</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=demrich" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://maxtrier.dk"><img src="https://avatars.githubusercontent.com/u/5898152?v=4?s=100" width="100px;" alt="Max T. Kristiansen"/><br /><sub><b>Max T. Kristiansen</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=maxnatamo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://damsdev.me"><img src="https://avatars.githubusercontent.com/u/60252259?v=4?s=100" width="100px;" alt="Damien Fajole"/><br /><sub><b>Damien Fajole</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=DamsDev1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AhmedNSidd"><img src="https://avatars.githubusercontent.com/u/36286128?v=4?s=100" width="100px;" alt="Ahmed Siddiqui"/><br /><sub><b>Ahmed Siddiqui</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=AhmedNSidd" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zariel"><img src="https://avatars.githubusercontent.com/u/2213?v=4?s=100" width="100px;" alt="Chris Bannister"/><br /><sub><b>Chris Bannister</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Zariel" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/C4J3"><img src="https://avatars.githubusercontent.com/u/13005453?v=4?s=100" width="100px;" alt="Joe"/><br /><sub><b>Joe</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=C4J3" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://me.garnx.fr"><img src="https://avatars.githubusercontent.com/u/37373941?v=4?s=100" width="100px;" alt="Guillaume ARNOUX"/><br /><sub><b>Guillaume ARNOUX</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=guillaumearnx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dr-carrot"><img src="https://avatars.githubusercontent.com/u/17272571?v=4?s=100" width="100px;" alt="dr-carrot"/><br /><sub><b>dr-carrot</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=dr-carrot" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gageorsburn"><img src="https://avatars.githubusercontent.com/u/4692734?v=4?s=100" width="100px;" alt="Gage Orsburn"/><br /><sub><b>Gage Orsburn</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=gageorsburn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GkhnGRBZ"><img src="https://avatars.githubusercontent.com/u/127258824?v=4?s=100" width="100px;" alt="GkhnGRBZ"/><br /><sub><b>GkhnGRBZ</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=GkhnGRBZ" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://benhaney.com"><img src="https://avatars.githubusercontent.com/u/31331498?v=4?s=100" width="100px;" alt="Ben Haney"/><br /><sub><b>Ben Haney</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=benhaney" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wunderharke"><img src="https://avatars.githubusercontent.com/u/5105672?v=4?s=100" width="100px;" alt="Wunderharke"/><br /><sub><b>Wunderharke</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=Wunderharke" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/C4J3"><img src="https://avatars.githubusercontent.com/u/13005453?v=4?s=100" width="100px;" alt="Joe"/><br /><sub><b>Joe</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=C4J3" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://me.garnx.fr"><img src="https://avatars.githubusercontent.com/u/37373941?v=4?s=100" width="100px;" alt="Guillaume ARNOUX"/><br /><sub><b>Guillaume ARNOUX</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=guillaumearnx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dr-carrot"><img src="https://avatars.githubusercontent.com/u/17272571?v=4?s=100" width="100px;" alt="dr-carrot"/><br /><sub><b>dr-carrot</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=dr-carrot" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gageorsburn"><img src="https://avatars.githubusercontent.com/u/4692734?v=4?s=100" width="100px;" alt="Gage Orsburn"/><br /><sub><b>Gage Orsburn</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=gageorsburn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GkhnGRBZ"><img src="https://avatars.githubusercontent.com/u/127258824?v=4?s=100" width="100px;" alt="GkhnGRBZ"/><br /><sub><b>GkhnGRBZ</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=GkhnGRBZ" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://benhaney.com"><img src="https://avatars.githubusercontent.com/u/31331498?v=4?s=100" width="100px;" alt="Ben Haney"/><br /><sub><b>Ben Haney</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=benhaney" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wunderharke"><img src="https://avatars.githubusercontent.com/u/5105672?v=4?s=100" width="100px;" alt="Wunderharke"/><br /><sub><b>Wunderharke</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Wunderharke" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/methbkts"><img src="https://avatars.githubusercontent.com/u/30674934?v=4?s=100" width="100px;" alt="Metin Bektas"/><br /><sub><b>Metin Bektas</b></sub></a><br /><a href="#infra-methbkts" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrewkolda"><img src="https://avatars.githubusercontent.com/u/158614532?v=4?s=100" width="100px;" alt="andrewkolda"/><br /><sub><b>andrewkolda</b></sub></a><br /><a href="#design-andrewkolda" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ishanjain.me"><img src="https://avatars.githubusercontent.com/u/7921368?v=4?s=100" width="100px;" alt="Ishan Jain"/><br /><sub><b>Ishan Jain</b></sub></a><br /><a href="https://github.com/fallenbagel/jellyseerr/commits?author=ishanjain28" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -3,7 +3,7 @@ kubeVersion: ">=1.23.0-0"
name: jellyseerr-chart
description: Jellyseerr helm chart for Kubernetes
type: application
version: 2.1.1
version: 2.1.0
appVersion: "2.3.0"
maintainers:
- name: Jellyseerr

View File

@@ -1,6 +1,6 @@
# jellyseerr-chart
![Version: 2.1.1](https://img.shields.io/badge/Version-2.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square)
![Version: 2.1.0](https://img.shields.io/badge/Version-2.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square)
Jellyseerr helm chart for Kubernetes

View File

@@ -13,10 +13,10 @@ describe('General Settings', () => {
});
it('modifies setting that requires restart', () => {
cy.visit('/settings/network');
cy.visit('/settings');
cy.get('#trustProxy').click();
cy.get('[data-testid=settings-network-form]').submit();
cy.get('[data-testid=settings-main-form]').submit();
cy.get('[data-testid=modal-title]').should(
'contain',
'Server Restart Required'
@@ -26,7 +26,7 @@ describe('General Settings', () => {
cy.get('[data-testid=modal-title]').should('not.exist');
cy.get('[type=checkbox]#trustProxy').click();
cy.get('[data-testid=settings-network-form]').submit();
cy.get('[data-testid=settings-main-form]').submit();
cy.get('[data-testid=modal-title]').should('not.exist');
});
});

2
next-env.d.ts vendored
View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -11,7 +11,6 @@ module.exports = {
{ hostname: 'gravatar.com' },
{ hostname: 'image.tmdb.org' },
{ hostname: 'artworks.thetvdb.com' },
{ hostname: 'plex.tv' },
],
},
webpack(config) {

View File

@@ -164,6 +164,12 @@ components:
applicationUrl:
type: string
example: https://os.example.com
trustProxy:
type: boolean
example: true
csrfProtection:
type: boolean
example: false
hideAvailable:
type: boolean
example: false
@@ -185,21 +191,12 @@ components:
enableSpecialEpisodes:
type: boolean
example: false
NetworkSettings:
type: object
properties:
csrfProtection:
type: boolean
example: false
forceIpv4First:
type: boolean
example: false
dnsServers:
type: string
example: '1.1.1.1'
trustProxy:
type: boolean
example: true
PlexLibrary:
type: object
properties:
@@ -2048,37 +2045,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/MainSettings'
/settings/network:
get:
summary: Get network settings
description: Retrieves all network settings in a JSON object.
tags:
- settings
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MainSettings'
post:
summary: Update network settings
description: Updates network settings with the provided values.
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NetworkSettings'
responses:
'200':
description: 'Values were sucessfully updated'
content:
application/json:
schema:
$ref: '#/components/schemas/NetworkSettings'
/settings/main/regenerate:
post:
summary: Get main settings with newly-generated API key

View File

@@ -42,7 +42,6 @@
"@supercharge/request-ip": "1.2.0",
"@svgr/webpack": "6.5.1",
"@tanem/react-nprogress": "5.0.30",
"@types/wink-jaro-distance": "^2.0.2",
"ace-builds": "1.15.2",
"bcrypt": "5.1.0",
"bowser": "2.11.0",
@@ -98,7 +97,6 @@
"typeorm": "0.3.11",
"undici": "^6.20.1",
"web-push": "3.5.0",
"wink-jaro-distance": "^2.0.0",
"winston": "3.8.2",
"winston-daily-rotate-file": "4.7.1",
"xml2js": "0.4.23",
@@ -171,8 +169,8 @@
"typescript": "4.9.5"
},
"engines": {
"node": "^22.0.0",
"pnpm": "^9.0.0"
"node": "^23.0.0",
"pnpm": "^10.0.0"
},
"overrides": {
"sqlite3/node-gyp": "8.4.1",
@@ -247,5 +245,11 @@
},
"@semantic-release/github"
]
},
"pnpm": {
"onlyBuiltDependencies": [
"sqlite3",
"bcrypt"
]
}
}

9661
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
User-agent: *
Disallow: /

View File

@@ -1,7 +1,6 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import jaro from 'wink-jaro-distance';
interface RTAlgoliaSearchResponse {
results: {
@@ -16,7 +15,7 @@ interface RTAlgoliaHit {
tmsId: string;
type: string;
title: string;
titles?: string[];
titles: string[];
description: string;
releaseYear: number;
rating: string;
@@ -25,9 +24,9 @@ interface RTAlgoliaHit {
isEmsSearchable: boolean;
rtId: number;
vanity: string;
aka?: string[];
aka: string[];
posterImageUrl: string;
rottenTomatoes?: {
rottenTomatoes: {
audienceScore: number;
criticsIconUrl: string;
wantToSeeCount: number;
@@ -48,47 +47,6 @@ export interface RTRating {
url: string;
}
// Tunables
const INEXACT_TITLE_FACTOR = 0.25;
const ALTERNATE_TITLE_FACTOR = 0.8;
const PER_YEAR_PENALTY = 0.4;
const MINIMUM_SCORE = 0.175;
// Normalization for title comparisons.
// Lowercase and strip non-alphanumeric (unicode-aware).
const norm = (s: string): string =>
s.toLowerCase().replace(/[^\p{L}\p{N} ]/gu, '');
// Title similarity. 1 if exact, quarter-jaro otherwise.
const similarity = (a: string, b: string): number =>
a === b ? 1 : jaro(a, b).similarity * INEXACT_TITLE_FACTOR;
// Gets the best similarity score between the searched title and all alternate
// titles of the search result. Non-main titles are penalized.
const t_score = ({ title, titles, aka }: RTAlgoliaHit, s: string): number => {
const f = (t: string, i: number) =>
similarity(norm(t), norm(s)) * (i ? ALTERNATE_TITLE_FACTOR : 1);
return Math.max(...[title].concat(aka || [], titles || []).map(f));
};
// Year difference to score: 0 -> 1.0, 1 -> 0.6, 2 -> 0.2, 3+ -> 0.0
const y_score = (r: RTAlgoliaHit, y?: number): number =>
y ? Math.max(0, 1 - Math.abs(r.releaseYear - y) * PER_YEAR_PENALTY) : 1;
// Cut score in half if result has no ratings.
const extra_score = (r: RTAlgoliaHit): number => (r.rottenTomatoes ? 1 : 0.5);
// Score search result as product of all subscores
const score = (r: RTAlgoliaHit, name: string, year?: number): number =>
t_score(r, name) * y_score(r, year) * extra_score(r);
// Score each search result and return the highest scoring result, if any
const best = (rs: RTAlgoliaHit[], name: string, year?: number): RTAlgoliaHit =>
rs
.map((r) => ({ score: score(r, name, year), result: r }))
.filter(({ score }) => score > MINIMUM_SCORE)
.sort(({ score: a }, { score: b }) => b - a)[0]?.result;
/**
* This is a best-effort API. The Rotten Tomatoes API is technically
* private and getting access costs money/requires approval.
@@ -132,21 +90,47 @@ class RottenTomatoes extends ExternalAPI {
year: number
): Promise<RTRating | null> {
try {
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"movie"');
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
requests: [
{
indexName: 'content_rt',
query: name.replace(/\bthe\b ?/gi, ''),
params: `filters=${filters}&hitsPerPage=20`,
query: name,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const movie = best(contentResults?.hits || [], name, year);
if (!movie?.rottenTomatoes) return null;
if (!contentResults) {
return null;
}
// First, attempt to match exact name and year
let movie = contentResults.hits.find(
(movie) => movie.releaseYear === year && movie.title === name
);
// If we don't find a movie, try to match partial name and year
if (!movie) {
movie = contentResults.hits.find(
(movie) => movie.releaseYear === year && movie.title.includes(name)
);
}
// If we still dont find a movie, try to match just on year
if (!movie) {
movie = contentResults.hits.find((movie) => movie.releaseYear === year);
}
// One last try, try exact name match only
if (!movie) {
movie = contentResults.hits.find((movie) => movie.title === name);
}
if (!movie?.rottenTomatoes) {
return null;
}
return {
title: movie.title,
@@ -174,21 +158,33 @@ class RottenTomatoes extends ExternalAPI {
year?: number
): Promise<RTRating | null> {
try {
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"tv"');
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
requests: [
{
indexName: 'content_rt',
query: name,
params: `filters=${filters}&hitsPerPage=20`,
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
},
],
});
const contentResults = data.results.find((r) => r.index === 'content_rt');
const tvshow = best(contentResults?.hits || [], name, year);
if (!tvshow?.rottenTomatoes) return null;
if (!contentResults) {
return null;
}
let tvshow: RTAlgoliaHit | undefined = contentResults.hits[0];
if (year) {
tvshow = contentResults.hits.find(
(series) => series.releaseYear === year
);
}
if (!tvshow || !tvshow.rottenTomatoes) {
return null;
}
return {
title: tvshow.title,

View File

@@ -734,11 +734,8 @@ export class MediaRequest {
media.mediaType === MediaType.MOVIE &&
this.status === MediaRequestStatus.DECLINED
) {
const statusField = this.is4k ? 'status4k' : 'status';
await mediaRepository.update(
{ id: this.media.id },
{ [statusField]: MediaStatus.UNKNOWN }
);
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
mediaRepository.save(media);
}
/**
@@ -755,11 +752,8 @@ export class MediaRequest {
).length === 0 &&
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING
) {
const statusField = this.is4k ? 'status4k' : 'status';
mediaRepository.update(
{ id: this.media.id },
{ [statusField]: MediaStatus.UNKNOWN }
);
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
mediaRepository.save(media);
}
// Approve child seasons if parent is approved
@@ -961,10 +955,8 @@ export class MediaRequest {
});
const requestRepository = getRepository(MediaRequest);
await requestRepository.update(this.id, {
status: MediaRequestStatus.APPROVED,
});
this.status = MediaRequestStatus.APPROVED;
await requestRepository.save(this);
return;
}
@@ -994,22 +986,18 @@ export class MediaRequest {
throw new Error('Media data not found');
}
const updateFields = {
[this.is4k ? 'externalServiceId4k' : 'externalServiceId']:
radarrMovie.id,
[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']:
radarrMovie.titleSlug,
[this.is4k ? 'serviceId4k' : 'serviceId']: radarrMovie?.id,
};
await mediaRepository.update({ id: this.media.id }, updateFields);
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
radarrMovie.id;
media[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] =
radarrMovie.titleSlug;
media[this.is4k ? 'serviceId4k' : 'serviceId'] = radarrSettings?.id;
await mediaRepository.save(media);
})
.catch(async () => {
const requestRepository = getRepository(MediaRequest);
await requestRepository.update(this.id, {
status: MediaRequestStatus.FAILED,
});
this.status = MediaRequestStatus.FAILED;
await requestRepository.save(this);
logger.warn(
'Something went wrong sending movie request to Radarr, marking status as FAILED',
@@ -1125,9 +1113,8 @@ export class MediaRequest {
});
const requestRepository = getRepository(MediaRequest);
await requestRepository.update(this.id, {
status: MediaRequestStatus.APPROVED,
});
this.status = MediaRequestStatus.APPROVED;
await requestRepository.save(this);
return;
}

View File

@@ -83,13 +83,13 @@ export class User {
@Column({ nullable: true })
public jellyfinUserId?: string;
@Column({ nullable: true, select: false })
@Column({ nullable: true })
public jellyfinDeviceId?: string;
@Column({ nullable: true, select: false })
@Column({ nullable: true })
public jellyfinAuthToken?: string;
@Column({ nullable: true, select: false })
@Column({ nullable: true })
public plexToken?: string;
@Column({ type: 'integer', default: 0 })

View File

@@ -72,26 +72,23 @@ app
// Load Settings
const settings = await getSettings().load();
restartFlag.initializeSettings(settings);
restartFlag.initializeSettings(settings.main);
// Check if we force IPv4 first
if (
process.env.forceIpv4First === 'true' ||
settings.network.forceIpv4First
) {
if (process.env.forceIpv4First === 'true' || settings.main.forceIpv4First) {
dns.setDefaultResultOrder('ipv4first');
net.setDefaultAutoSelectFamily(false);
}
if (settings.network.dnsServers.trim() !== '') {
if (settings.main.dnsServers.trim() !== '') {
dns.setServers(
settings.network.dnsServers.split(',').map((server) => server.trim())
settings.main.dnsServers.split(',').map((server) => server.trim())
);
}
// Register HTTP proxy
if (settings.network.proxy.enabled) {
await createCustomProxyAgent(settings.network.proxy);
if (settings.main.proxy.enabled) {
await createCustomProxyAgent(settings.main.proxy);
}
// Migrate library types
@@ -146,7 +143,7 @@ app
await DiscoverSlider.bootstrapSliders();
const server = express();
if (settings.network.trustProxy) {
if (settings.main.trustProxy) {
server.enable('trust proxy');
}
server.use(cookieParser());
@@ -167,8 +164,8 @@ app
next();
}
});
if (settings.network.csrfProtection) {
server.use(
if (settings.main.csrfProtection) {
server.use(() =>
csurf({
cookie: {
httpOnly: true,
@@ -197,7 +194,7 @@ app
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 30,
httpOnly: true,
sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
sameSite: settings.main.csrfProtection ? 'strict' : 'lax',
secure: 'auto',
},
store: new TypeormStore({

View File

@@ -70,35 +70,6 @@ export const startJobs = (): void => {
running: () => plexFullScanner.status().running,
cancelFn: () => plexFullScanner.cancel(),
});
scheduledJobs.push({
id: 'plex-refresh-token',
name: 'Plex Refresh Token',
type: 'process',
interval: 'fixed',
cronSchedule: jobs['plex-refresh-token'].schedule,
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
logger.info('Starting scheduled job: Plex Refresh Token', {
label: 'Jobs',
});
refreshToken.run();
}),
});
// Watchlist Sync
scheduledJobs.push({
id: 'plex-watchlist-sync',
name: 'Plex Watchlist Sync',
type: 'process',
interval: 'seconds',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs',
});
watchlistSync.syncWatchlist();
}),
});
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
@@ -141,6 +112,21 @@ export const startJobs = (): void => {
});
}
// Watchlist Sync
scheduledJobs.push({
id: 'plex-watchlist-sync',
name: 'Plex Watchlist Sync',
type: 'process',
interval: 'seconds',
cronSchedule: jobs['plex-watchlist-sync'].schedule,
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
logger.info('Starting scheduled job: Plex Watchlist Sync', {
label: 'Jobs',
});
watchlistSync.syncWatchlist();
}),
});
// Run full radarr scan every 24 hours
scheduledJobs.push({
id: 'radarr-scan',
@@ -237,5 +223,19 @@ export const startJobs = (): void => {
}),
});
scheduledJobs.push({
id: 'plex-refresh-token',
name: 'Plex Refresh Token',
type: 'process',
interval: 'fixed',
cronSchedule: jobs['plex-refresh-token'].schedule,
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
logger.info('Starting scheduled job: Plex Refresh Token', {
label: 'Jobs',
});
refreshToken.run();
}),
});
logger.info('Scheduled jobs loaded', { label: 'Jobs' });
};

View File

@@ -115,6 +115,7 @@ export interface MainSettings {
apiKey: string;
applicationTitle: string;
applicationUrl: string;
csrfProtection: boolean;
cacheImages: boolean;
defaultPermissions: number;
defaultQuotas: {
@@ -127,17 +128,13 @@ export interface MainSettings {
discoverRegion: string;
streamingRegion: string;
originalLanguage: string;
trustProxy: boolean;
mediaServerType: number;
partialRequestsEnabled: boolean;
enableSpecialEpisodes: boolean;
locale: string;
}
export interface NetworkSettings {
csrfProtection: boolean;
forceIpv4First: boolean;
dnsServers: string;
trustProxy: boolean;
locale: string;
proxy: ProxySettings;
}
@@ -316,7 +313,6 @@ export interface AllSettings {
public: PublicSettings;
notifications: NotificationSettings;
jobs: Record<JobId, JobSettings>;
network: NetworkSettings;
}
const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
@@ -335,6 +331,7 @@ class Settings {
apiKey: '',
applicationTitle: 'Jellyseerr',
applicationUrl: '',
csrfProtection: false,
cacheImages: false,
defaultPermissions: Permission.REQUEST,
defaultQuotas: {
@@ -347,10 +344,23 @@ class Settings {
discoverRegion: '',
streamingRegion: '',
originalLanguage: '',
trustProxy: false,
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,
enableSpecialEpisodes: false,
forceIpv4First: false,
dnsServers: '',
locale: 'en',
proxy: {
enabled: false,
hostname: '',
port: 8080,
useSsl: false,
user: '',
password: '',
bypassFilter: '',
bypassLocalAddresses: true,
},
},
plex: {
name: '',
@@ -503,22 +513,6 @@ class Settings {
schedule: '0 0 5 * * *',
},
},
network: {
csrfProtection: false,
trustProxy: false,
forceIpv4First: false,
dnsServers: '',
proxy: {
enabled: false,
hostname: '',
port: 8080,
useSsl: false,
user: '',
password: '',
bypassFilter: '',
bypassLocalAddresses: true,
},
},
};
if (initialSettings) {
this.data = merge(this.data, initialSettings);
@@ -628,14 +622,6 @@ class Settings {
this.data.jobs = data;
}
get network(): NetworkSettings {
return this.data.network;
}
set network(data: NetworkSettings) {
this.data.network = data;
}
get clientId(): string {
return this.data.clientId;
}

View File

@@ -1,33 +0,0 @@
import type { AllSettings } from '@server/lib/settings';
const migrateNetworkSettings = (settings: any): AllSettings => {
if (settings.network) {
return settings;
}
const newSettings = { ...settings };
newSettings.network = {
...settings.network,
csrfProtection: settings.main.csrfProtection ?? false,
trustProxy: settings.main.trustProxy ?? false,
forceIpv4First: settings.main.forceIpv4First ?? false,
dnsServers: settings.main.dnsServers ?? '',
proxy: settings.main.proxy ?? {
enabled: false,
hostname: '',
port: 8080,
useSsl: false,
user: '',
password: '',
bypassFilter: '',
bypassLocalAddresses: true,
},
};
delete settings.main.csrfProtection;
delete settings.main.trustProxy;
delete settings.main.forceIpv4First;
delete settings.main.dnsServers;
delete settings.main.proxy;
return newSettings;
};
export default migrateNetworkSettings;

View File

@@ -263,7 +263,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
let user = await userRepository.findOne({
where: { jellyfinUsername: body.username },
select: { id: true, jellyfinDeviceId: true },
});
let deviceId = '';

View File

@@ -78,21 +78,6 @@ settingsRoutes.post('/main', async (req, res) => {
return res.status(200).json(settings.main);
});
settingsRoutes.get('/network', (req, res) => {
const settings = getSettings();
res.status(200).json(settings.network);
});
settingsRoutes.post('/network', async (req, res) => {
const settings = getSettings();
settings.network = merge(settings.network, req.body);
await settings.save();
return res.status(200).json(settings.network);
});
settingsRoutes.post('/main/regenerate', async (req, res, next) => {
const settings = getSettings();

View File

@@ -6,7 +6,7 @@ import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
export default async function createCustomProxyAgent(
proxySettings: ProxySettings
) {
const defaultAgent = new Agent({ keepAliveTimeout: 5000 });
const defaultAgent = new Agent();
const skipUrl = (url: string) => {
const hostname = new URL(url).hostname;
@@ -63,7 +63,6 @@ export default async function createCustomProxyAgent(
interceptors: {
Client: [noProxyInterceptor],
},
keepAliveTimeout: 5000,
});
setGlobalDispatcher(proxyAgent);

View File

@@ -1,25 +1,22 @@
import type { AllSettings, NetworkSettings } from '@server/lib/settings';
import type { MainSettings } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
class RestartFlag {
private networkSettings: NetworkSettings;
private settings: MainSettings;
public initializeSettings(settings: AllSettings): void {
this.networkSettings = {
...settings.network,
proxy: { ...settings.network.proxy },
};
public initializeSettings(settings: MainSettings): void {
this.settings = { ...settings };
}
public isSet(): boolean {
const networkSettings = getSettings().network;
const settings = getSettings().main;
return (
this.networkSettings.csrfProtection !== networkSettings.csrfProtection ||
this.networkSettings.trustProxy !== networkSettings.trustProxy ||
this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled ||
this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First ||
this.networkSettings.dnsServers !== networkSettings.dnsServers
this.settings.csrfProtection !== settings.csrfProtection ||
this.settings.trustProxy !== settings.trustProxy ||
this.settings.proxy.enabled !== settings.proxy.enabled ||
this.settings.forceIpv4First !== settings.forceIpv4First ||
this.settings.dnsServers !== settings.dnsServers
);
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -102,7 +102,7 @@ const messages = defineMessages('components.MovieDetails', {
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistError: 'Something went wrong. Please try again.',
watchlistError: 'Something went wrong try again.',
removefromwatchlist: 'Remove From Watchlist',
addtowatchlist: 'Add To Watchlist',
});
@@ -190,7 +190,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
})
) {
mediaLinks.push({
text: getAvailableMediaServerName(),
text: getAvalaibleMediaServerName(),
url: plexUrl,
svg: <PlayIcon />,
});
@@ -204,7 +204,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
})
) {
mediaLinks.push({
text: getAvailable4kMediaServerName(),
text: getAvalaible4kMediaServerName(),
url: plexUrl4k,
svg: <PlayIcon />,
});
@@ -292,7 +292,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
(provider) => provider.iso_3166_1 === streamingRegion
)?.flatrate ?? [];
function getAvailableMediaServerName() {
function getAvalaibleMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
@@ -304,7 +304,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
}
function getAvailable4kMediaServerName() {
function getAvalaible4kMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}

View File

@@ -13,7 +13,6 @@ const messages = defineMessages('components.Settings', {
menuPlexSettings: 'Plex',
menuJellyfinSettings: '{mediaServerName}',
menuServices: 'Services',
menuNetwork: 'Network',
menuNotifications: 'Notifications',
menuLogs: 'Logs',
menuJobs: 'Jobs & Cache',
@@ -54,11 +53,6 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
route: '/settings/services',
regex: /^\/settings\/services/,
},
{
text: intl.formatMessage(messages.menuNetwork),
route: '/settings/network',
regex: /^\/settings\/network/,
},
{
text: intl.formatMessage(messages.menuNotifications),
route: '/settings/notifications/email',

View File

@@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import Tooltip from '@app/components/Common/Tooltip';
import LanguageSelector from '@app/components/LanguageSelector';
import RegionSelector from '@app/components/RegionSelector';
import CopyButton from '@app/components/Settings/CopyButton';
@@ -41,15 +42,39 @@ const messages = defineMessages('components.Settings.SettingsMain', {
toastSettingsSuccess: 'Settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
hideAvailable: 'Hide Available Media',
csrfProtection: 'Enable CSRF Protection',
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
csrfProtectionHoverTip:
'Do NOT enable this setting unless you understand what you are doing!',
cacheImages: 'Enable Image Caching',
cacheImagesTip:
'Cache externally sourced images (requires a significant amount of disk space)',
trustProxy: 'Enable Proxy Support',
trustProxyTip:
'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
validationApplicationTitle: 'You must provide an application title',
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
partialRequestsEnabled: 'Allow Partial Series Requests',
enableSpecialEpisodes: 'Allow Special Episodes Requests',
forceIpv4First: 'IPv4 Resolution First',
forceIpv4FirstTip:
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
dnsServers: 'Custom DNS Servers',
dnsServersTip:
'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"',
locale: 'Display Language',
proxyEnabled: 'HTTP(S) Proxy',
proxyHostname: 'Proxy Hostname',
proxyPort: 'Proxy Port',
proxySsl: 'Use SSL For Proxy',
proxyUser: 'Proxy Username',
proxyPassword: 'Proxy Password',
proxyBypassFilter: 'Proxy Ignored Addresses',
proxyBypassFilterTip:
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
validationProxyPort: 'You must provide a valid port',
});
const SettingsMain = () => {
@@ -80,6 +105,12 @@ const SettingsMain = () => {
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
(value) => !value || !value.endsWith('/')
),
proxyPort: Yup.number().when('proxyEnabled', {
is: (proxyEnabled: boolean) => proxyEnabled,
then: Yup.number().required(
intl.formatMessage(messages.validationProxyPort)
),
}),
});
const regenerate = async () => {
@@ -127,6 +158,7 @@ const SettingsMain = () => {
initialValues={{
applicationTitle: data?.applicationTitle,
applicationUrl: data?.applicationUrl,
csrfProtection: data?.csrfProtection,
hideAvailable: data?.hideAvailable,
locale: data?.locale ?? 'en',
discoverRegion: data?.discoverRegion,
@@ -134,7 +166,18 @@ const SettingsMain = () => {
streamingRegion: data?.streamingRegion || 'US',
partialRequestsEnabled: data?.partialRequestsEnabled,
enableSpecialEpisodes: data?.enableSpecialEpisodes,
forceIpv4First: data?.forceIpv4First,
dnsServers: data?.dnsServers,
trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages,
proxyEnabled: data?.proxy?.enabled,
proxyHostname: data?.proxy?.hostname,
proxyPort: data?.proxy?.port,
proxySsl: data?.proxy?.useSsl,
proxyUser: data?.proxy?.user,
proxyPassword: data?.proxy?.password,
proxyBypassFilter: data?.proxy?.bypassFilter,
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
}}
enableReinitialize
validationSchema={MainSettingsSchema}
@@ -148,6 +191,7 @@ const SettingsMain = () => {
body: JSON.stringify({
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
csrfProtection: values.csrfProtection,
hideAvailable: values.hideAvailable,
locale: values.locale,
discoverRegion: values.discoverRegion,
@@ -155,7 +199,20 @@ const SettingsMain = () => {
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
enableSpecialEpisodes: values.enableSpecialEpisodes,
forceIpv4First: values.forceIpv4First,
dnsServers: values.dnsServers,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
}),
});
if (!res.ok) throw new Error();
@@ -264,6 +321,58 @@ const SettingsMain = () => {
)}
</div>
</div>
<div className="form-row">
<label htmlFor="trustProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="trustProxy"
name="trustProxy"
onChange={() => {
setFieldValue('trustProxy', !values.trustProxy);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="csrfProtection" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</label>
<div className="form-input-area">
<Tooltip
content={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
>
<Field
type="checkbox"
id="csrfProtection"
name="csrfProtection"
onChange={() => {
setFieldValue(
'csrfProtection',
!values.csrfProtection
);
}}
/>
</Tooltip>
</div>
</div>
<div className="form-row">
<label htmlFor="cacheImages" className="checkbox-label">
<span className="mr-2">
@@ -425,6 +534,231 @@ const SettingsMain = () => {
/>
</div>
</div>
<div className="form-row">
<label htmlFor="forceIpv4First" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.forceIpv4First)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.forceIpv4FirstTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="forceIpv4First"
name="forceIpv4First"
onChange={() => {
setFieldValue('forceIpv4First', !values.forceIpv4First);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="dnsServers" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.dnsServers)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.dnsServersTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="dnsServers"
name="dnsServers"
type="text"
inputMode="url"
/>
</div>
{errors.dnsServers &&
touched.dnsServers &&
typeof errors.dnsServers === 'string' && (
<div className="error">{errors.dnsServers}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.proxyEnabled)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyEnabled"
name="proxyEnabled"
onChange={() => {
setFieldValue('proxyEnabled', !values.proxyEnabled);
}}
/>
</div>
</div>
{values.proxyEnabled && (
<>
<div className="mr-2 ml-4">
<div className="form-row">
<label
htmlFor="proxyHostname"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyHostname)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyHostname"
name="proxyHostname"
type="text"
/>
</div>
{errors.proxyHostname &&
touched.proxyHostname &&
typeof errors.proxyHostname === 'string' && (
<div className="error">
{errors.proxyHostname}
</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyPort" className="checkbox-label">
{intl.formatMessage(messages.proxyPort)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPort"
name="proxyPort"
type="text"
/>
</div>
{errors.proxyPort &&
touched.proxyPort &&
typeof errors.proxyPort === 'string' && (
<div className="error">{errors.proxyPort}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxySsl" className="checkbox-label">
{intl.formatMessage(messages.proxySsl)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxySsl"
name="proxySsl"
onChange={() => {
setFieldValue('proxySsl', !values.proxySsl);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="proxyUser" className="checkbox-label">
{intl.formatMessage(messages.proxyUser)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyUser"
name="proxyUser"
type="text"
/>
</div>
{errors.proxyUser &&
touched.proxyUser &&
typeof errors.proxyUser === 'string' && (
<div className="error">{errors.proxyUser}</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyPassword"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyPassword)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPassword"
name="proxyPassword"
type="password"
/>
</div>
{errors.proxyPassword &&
touched.proxyPassword &&
typeof errors.proxyPassword === 'string' && (
<div className="error">
{errors.proxyPassword}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassFilter"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyBypassFilter)}
<span className="label-tip">
{intl.formatMessage(messages.proxyBypassFilterTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyBypassFilter"
name="proxyBypassFilter"
type="text"
/>
</div>
{errors.proxyBypassFilter &&
touched.proxyBypassFilter &&
typeof errors.proxyBypassFilter === 'string' && (
<div className="error">
{errors.proxyBypassFilter}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassLocalAddresses"
className="checkbox-label"
>
{intl.formatMessage(
messages.proxyBypassLocalAddresses
)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyBypassLocalAddresses"
name="proxyBypassLocalAddresses"
onChange={() => {
setFieldValue(
'proxyBypassLocalAddresses',
!values.proxyBypassLocalAddresses
);
}}
/>
</div>
</div>
</div>
</>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -1,461 +0,0 @@
import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import Tooltip from '@app/components/Common/Tooltip';
import SettingsBadge from '@app/components/Settings/SettingsBadge';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { NetworkSettings } from '@server/lib/settings';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
import * as Yup from 'yup';
const messages = defineMessages('components.Settings.SettingsNetwork', {
toastSettingsSuccess: 'Settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
network: 'Network',
networksettings: 'Network Settings',
networksettingsDescription:
'Configure network settings for your Jellyseerr instance.',
csrfProtection: 'Enable CSRF Protection',
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
csrfProtectionHoverTip:
'Do NOT enable this setting unless you understand what you are doing!',
trustProxy: 'Enable Proxy Support',
trustProxyTip:
'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
forceIpv4First: 'IPv4 Resolution First',
forceIpv4FirstTip:
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
dnsServers: 'Custom DNS Servers',
dnsServersTip:
'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"',
proxyEnabled: 'HTTP(S) Proxy',
proxyHostname: 'Proxy Hostname',
proxyPort: 'Proxy Port',
proxySsl: 'Use SSL For Proxy',
proxyUser: 'Proxy Username',
proxyPassword: 'Proxy Password',
proxyBypassFilter: 'Proxy Ignored Addresses',
proxyBypassFilterTip:
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
validationProxyPort: 'You must provide a valid port',
});
const SettingsMain = () => {
const { addToast } = useToasts();
const intl = useIntl();
const {
data,
error,
mutate: revalidate,
} = useSWR<NetworkSettings>('/api/v1/settings/network');
const NetworkSettingsSchema = Yup.object().shape({
proxyPort: Yup.number().when('proxyEnabled', {
is: (proxyEnabled: boolean) => proxyEnabled,
then: Yup.number().required(
intl.formatMessage(messages.validationProxyPort)
),
}),
});
if (!data && !error) {
return <LoadingSpinner />;
}
return (
<>
<PageTitle
title={[
intl.formatMessage(messages.network),
intl.formatMessage(globalMessages.settings),
]}
/>
<div className="mb-6">
<h3 className="heading">
{intl.formatMessage(messages.networksettings)}
</h3>
<p className="description">
{intl.formatMessage(messages.networksettingsDescription)}
</p>
</div>
<div className="section">
<Formik
initialValues={{
csrfProtection: data?.csrfProtection,
forceIpv4First: data?.forceIpv4First,
dnsServers: data?.dnsServers,
trustProxy: data?.trustProxy,
proxyEnabled: data?.proxy?.enabled,
proxyHostname: data?.proxy?.hostname,
proxyPort: data?.proxy?.port,
proxySsl: data?.proxy?.useSsl,
proxyUser: data?.proxy?.user,
proxyPassword: data?.proxy?.password,
proxyBypassFilter: data?.proxy?.bypassFilter,
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
}}
enableReinitialize
validationSchema={NetworkSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/network', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
csrfProtection: values.csrfProtection,
forceIpv4First: values.forceIpv4First,
dnsServers: values.dnsServers,
trustProxy: values.trustProxy,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
addToast(intl.formatMessage(messages.toastSettingsFailure), {
autoDismiss: true,
appearance: 'error',
});
} finally {
revalidate();
}
}}
>
{({
errors,
touched,
isSubmitting,
isValid,
values,
setFieldValue,
}) => {
return (
<Form className="section" data-testid="settings-network-form">
<div className="form-row">
<label htmlFor="trustProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="trustProxy"
name="trustProxy"
onChange={() => {
setFieldValue('trustProxy', !values.trustProxy);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="csrfProtection" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</label>
<div className="form-input-area">
<Tooltip
content={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
>
<Field
type="checkbox"
id="csrfProtection"
name="csrfProtection"
onChange={() => {
setFieldValue(
'csrfProtection',
!values.csrfProtection
);
}}
/>
</Tooltip>
</div>
</div>
<div className="form-row">
<label htmlFor="forceIpv4First" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.forceIpv4First)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.forceIpv4FirstTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="forceIpv4First"
name="forceIpv4First"
onChange={() => {
setFieldValue('forceIpv4First', !values.forceIpv4First);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="dnsServers" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.dnsServers)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.dnsServersTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="dnsServers"
name="dnsServers"
type="text"
inputMode="url"
/>
</div>
{errors.dnsServers &&
touched.dnsServers &&
typeof errors.dnsServers === 'string' && (
<div className="error">{errors.dnsServers}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.proxyEnabled)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyEnabled"
name="proxyEnabled"
onChange={() => {
setFieldValue('proxyEnabled', !values.proxyEnabled);
}}
/>
</div>
</div>
{values.proxyEnabled && (
<>
<div className="mr-2 ml-4">
<div className="form-row">
<label
htmlFor="proxyHostname"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyHostname)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyHostname"
name="proxyHostname"
type="text"
/>
</div>
{errors.proxyHostname &&
touched.proxyHostname &&
typeof errors.proxyHostname === 'string' && (
<div className="error">
{errors.proxyHostname}
</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyPort" className="checkbox-label">
{intl.formatMessage(messages.proxyPort)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPort"
name="proxyPort"
type="text"
/>
</div>
{errors.proxyPort &&
touched.proxyPort &&
typeof errors.proxyPort === 'string' && (
<div className="error">{errors.proxyPort}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxySsl" className="checkbox-label">
{intl.formatMessage(messages.proxySsl)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxySsl"
name="proxySsl"
onChange={() => {
setFieldValue('proxySsl', !values.proxySsl);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="proxyUser" className="checkbox-label">
{intl.formatMessage(messages.proxyUser)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyUser"
name="proxyUser"
type="text"
/>
</div>
{errors.proxyUser &&
touched.proxyUser &&
typeof errors.proxyUser === 'string' && (
<div className="error">{errors.proxyUser}</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyPassword"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyPassword)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPassword"
name="proxyPassword"
type="password"
/>
</div>
{errors.proxyPassword &&
touched.proxyPassword &&
typeof errors.proxyPassword === 'string' && (
<div className="error">
{errors.proxyPassword}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassFilter"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyBypassFilter)}
<span className="label-tip">
{intl.formatMessage(messages.proxyBypassFilterTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyBypassFilter"
name="proxyBypassFilter"
type="text"
/>
</div>
{errors.proxyBypassFilter &&
touched.proxyBypassFilter &&
typeof errors.proxyBypassFilter === 'string' && (
<div className="error">
{errors.proxyBypassFilter}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassLocalAddresses"
className="checkbox-label"
>
{intl.formatMessage(
messages.proxyBypassLocalAddresses
)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyBypassLocalAddresses"
name="proxyBypassLocalAddresses"
onChange={() => {
setFieldValue(
'proxyBypassLocalAddresses',
!values.proxyBypassLocalAddresses
);
}}
/>
</div>
</div>
</div>
</>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</span>
</Button>
</span>
</div>
</div>
</Form>
);
}}
</Formik>
</div>
</>
);
};
export default SettingsMain;

View File

@@ -133,6 +133,10 @@ const Setup = () => {
setCurrentStep(3);
}
}
if (currentStep === 3) {
validateLibraries();
}
}, [
settings.currentSettings.mediaServerType,
settings.currentSettings.initialized,
@@ -144,13 +148,6 @@ const Setup = () => {
validateLibraries,
]);
useEffect(() => {
if (currentStep === 3) {
validateLibraries();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentStep]);
const handleComplete = () => {
validateLibraries();
};

View File

@@ -51,7 +51,7 @@ const messages = defineMessages('components.TitleCard', {
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistCancel: 'watchlist for <strong>{title}</strong> canceled.',
watchlistError: 'Something went wrong. Please try again.',
watchlistError: 'Something went wrong try again.',
});
const TitleCard = ({

View File

@@ -101,7 +101,7 @@ const messages = defineMessages('components.TvDetails', {
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
watchlistDeleted:
'<strong>{title}</strong> Removed from watchlist successfully!',
watchlistError: 'Something went wrong. Please try again.',
watchlistError: 'Something went wrong try again.',
removefromwatchlist: 'Remove From Watchlist',
addtowatchlist: 'Add To Watchlist',
});
@@ -187,7 +187,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
})
) {
mediaLinks.push({
text: getAvailableMediaServerName(),
text: getAvalaibleMediaServerName(),
url: plexUrl,
svg: <PlayIcon />,
});
@@ -201,7 +201,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
})
) {
mediaLinks.push({
text: getAvailable4kMediaServerName(),
text: getAvalaible4kMediaServerName(),
url: plexUrl4k,
svg: <PlayIcon />,
});
@@ -322,7 +322,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
(provider) => provider.iso_3166_1 === streamingRegion
)?.flatrate ?? [];
function getAvailableMediaServerName() {
function getAvalaibleMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
@@ -334,7 +334,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
}
function getAvailable4kMediaServerName() {
function getAvalaible4kMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}

View File

@@ -1,10 +1,9 @@
import Modal from '@app/components/Common/Modal';
import PermissionEdit from '@app/components/PermissionEdit';
import type { User } from '@app/hooks/useUser';
import { Permission, useUser } from '@app/hooks/useUser';
import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { hasPermission } from '@server/lib/permissions';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -80,10 +79,7 @@ const BulkEditModal = ({
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
({ permissions: aPerms }, { permissions: bPerms }) => {
return {
permissions:
aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms)
? aPerms
: NaN,
permissions: aPerms === bPerms ? aPerms : NaN,
};
},
{ permissions: selectedUsers[0].permissions }

View File

@@ -58,7 +58,7 @@ const globalMessages = defineMessages('i18n', {
blacklist: 'Blacklist',
blacklisted: 'Blacklisted',
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
blacklistError: 'Something went wrong. Please try again.',
blacklistError: 'Something went wrong try again.',
blacklistDuplicateError:
'<strong>{title}</strong> has already been blacklisted.',
removeFromBlacklistSuccess:

View File

@@ -340,7 +340,7 @@
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
"components.MovieDetails.viewfullcrew": "View Full Crew",
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.MovieDetails.watchlistError": "Something went wrong. Please try again.",
"components.MovieDetails.watchlistError": "Something went wrong try again.",
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.MovieDetails.watchtrailer": "Watch Trailer",
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
@@ -915,9 +915,16 @@
"components.Settings.SettingsMain.applicationurl": "Application URL",
"components.Settings.SettingsMain.cacheImages": "Enable Image Caching",
"components.Settings.SettingsMain.cacheImagesTip": "Cache externally sourced images (requires a significant amount of disk space)",
"components.Settings.SettingsMain.csrfProtection": "Enable CSRF Protection",
"components.Settings.SettingsMain.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.discoverRegion": "Discover Region",
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
"components.Settings.SettingsMain.dnsServers": "Custom DNS Servers",
"components.Settings.SettingsMain.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"",
"components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests",
"components.Settings.SettingsMain.forceIpv4First": "IPv4 Resolution First",
"components.Settings.SettingsMain.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
"components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
@@ -926,39 +933,27 @@
"components.Settings.SettingsMain.originallanguage": "Discover Language",
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",
"components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests",
"components.Settings.SettingsMain.proxyBypassFilter": "Proxy Ignored Addresses",
"components.Settings.SettingsMain.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains",
"components.Settings.SettingsMain.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses",
"components.Settings.SettingsMain.proxyEnabled": "HTTP(S) Proxy",
"components.Settings.SettingsMain.proxyHostname": "Proxy Hostname",
"components.Settings.SettingsMain.proxyPassword": "Proxy Password",
"components.Settings.SettingsMain.proxyPort": "Proxy Port",
"components.Settings.SettingsMain.proxySsl": "Use SSL For Proxy",
"components.Settings.SettingsMain.proxyUser": "Proxy Username",
"components.Settings.SettingsMain.streamingRegion": "Streaming Region",
"components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability",
"components.Settings.SettingsMain.toastApiKeyFailure": "Something went wrong while generating a new API key.",
"components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!",
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsMain.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsMain.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title",
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection",
"components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
"components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsNetwork.dnsServers": "Custom DNS Servers",
"components.Settings.SettingsNetwork.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"",
"components.Settings.SettingsNetwork.forceIpv4First": "IPv4 Resolution First",
"components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
"components.Settings.SettingsNetwork.network": "Network",
"components.Settings.SettingsNetwork.networksettings": "Network Settings",
"components.Settings.SettingsNetwork.networksettingsDescription": "Configure network settings for your Jellyseerr instance.",
"components.Settings.SettingsNetwork.proxyBypassFilter": "Proxy Ignored Addresses",
"components.Settings.SettingsNetwork.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains",
"components.Settings.SettingsNetwork.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses",
"components.Settings.SettingsNetwork.proxyEnabled": "HTTP(S) Proxy",
"components.Settings.SettingsNetwork.proxyHostname": "Proxy Hostname",
"components.Settings.SettingsNetwork.proxyPassword": "Proxy Password",
"components.Settings.SettingsNetwork.proxyPort": "Proxy Port",
"components.Settings.SettingsNetwork.proxySsl": "Use SSL For Proxy",
"components.Settings.SettingsNetwork.proxyUser": "Proxy Username",
"components.Settings.SettingsNetwork.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsNetwork.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsNetwork.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
"components.Settings.SettingsMain.validationProxyPort": "You must provide a valid port",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
@@ -1074,7 +1069,6 @@
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
"components.Settings.menuJobs": "Jobs & Cache",
"components.Settings.menuLogs": "Logs",
"components.Settings.menuNetwork": "Network",
"components.Settings.menuNotifications": "Notifications",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuServices": "Services",
@@ -1177,7 +1171,7 @@
"components.TitleCard.tvdbid": "TheTVDB ID",
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TitleCard.watchlistError": "Something went wrong. Please try again.",
"components.TitleCard.watchlistError": "Something went wrong try again.",
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.TvDetails.Season.noepisodes": "Episode list unavailable.",
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
@@ -1215,7 +1209,7 @@
"components.TvDetails.tmdbuserscore": "TMDB User Score",
"components.TvDetails.viewfullcrew": "View Full Crew",
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TvDetails.watchlistError": "Something went wrong. Please try again.",
"components.TvDetails.watchlistError": "Something went wrong try again.",
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.TvDetails.watchtrailer": "Watch Trailer",
"components.UserList.accounttype": "Type",
@@ -1399,7 +1393,7 @@
"i18n.back": "Back",
"i18n.blacklist": "Blacklist",
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
"i18n.blacklistError": "Something went wrong. Please try again.",
"i18n.blacklistError": "Something went wrong try again.",
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
"i18n.blacklisted": "Blacklisted",
"i18n.cancel": "Cancel",

View File

@@ -1,16 +0,0 @@
import SettingsLayout from '@app/components/Settings/SettingsLayout';
import SettingsNetwork from '@app/components/Settings/SettingsNetwork';
import useRouteGuard from '@app/hooks/useRouteGuard';
import { Permission } from '@app/hooks/useUser';
import type { NextPage } from 'next';
const SettingsNetworkPage: NextPage = () => {
useRouteGuard(Permission.ADMIN);
return (
<SettingsLayout>
<SettingsNetwork />
</SettingsLayout>
);
};
export default SettingsNetworkPage;