Compare commits
42 Commits
v2.3.0
...
preview-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75afa742ea | ||
|
|
525a538f34 | ||
|
|
0d2273ff6e | ||
|
|
e035cd84ae | ||
|
|
438ccfe9c3 | ||
|
|
c181cee328 | ||
|
|
98a5b05816 | ||
|
|
b29959b063 | ||
|
|
9a2c12e51c | ||
|
|
620135aeac | ||
|
|
2dbd1096d2 | ||
|
|
24d3f523fc | ||
|
|
2b7974fa06 | ||
|
|
907ba6fdea | ||
|
|
efaad21554 | ||
|
|
6ab463285d | ||
|
|
418f0c2eb8 | ||
|
|
002557d2d0 | ||
|
|
62c1a70b37 | ||
|
|
1b325e7c32 | ||
|
|
f247642b76 | ||
|
|
396cd968ef | ||
|
|
ca739315b2 | ||
|
|
9143a6c027 | ||
|
|
d7fc03650f | ||
|
|
80fc5c1a78 | ||
|
|
95737d36e6 | ||
|
|
0fd6ca85a4 | ||
|
|
7cee9b475d | ||
|
|
ff9af866f8 | ||
|
|
5ffe6419ee | ||
|
|
8afcf5a8d8 | ||
|
|
17d93a8cb9 | ||
|
|
549082c53e | ||
|
|
fbef7e2c72 | ||
|
|
93d2e26ae9 | ||
|
|
f09a432635 | ||
|
|
a8f84d4f74 | ||
|
|
88e96fa163 | ||
|
|
2d814c1416 | ||
|
|
2f4b848b2c | ||
|
|
0ee3e69a61 |
@@ -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>",
|
"badgeTemplate": "<a href=\"#contributors-\"><img alt=\"All Contributors\" src=\"https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg\"/></a>",
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
"projectName": "jellyseerr",
|
"projectName": "jellyseerr",
|
||||||
"projectOwner": "Fallenbagel",
|
"projectOwner": "fallenbagel",
|
||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
"skipCi": true,
|
"skipCi": true,
|
||||||
@@ -592,6 +592,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"infra"
|
"infra"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "andrewkolda",
|
||||||
|
"name": "andrewkolda",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/158614532?v=4",
|
||||||
|
"profile": "https://github.com/andrewkolda",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ishanjain28",
|
||||||
|
"name": "Ishan Jain",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/7921368?v=4",
|
||||||
|
"profile": "https://ishanjain.me",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
85
.github/workflows/ci.yml
vendored
85
.github/workflows/ci.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Lint & Test Build
|
name: Lint & Test Build
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
container: node:22-alpine
|
container: node:22-alpine
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -43,15 +43,23 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
build_and_push:
|
build:
|
||||||
name: Build & Publish Docker Images
|
name: Build & Publish Docker Images
|
||||||
if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-22.04
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
platform: linux/amd64
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
platform: linux/arm64
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
outputs:
|
||||||
|
digest-amd64: ${{ steps.set_outputs.outputs.digest-amd64 }}
|
||||||
|
digest-arm64: ${{ steps.set_outputs.outputs.digest-arm64 }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
@@ -70,24 +78,77 @@ jobs:
|
|||||||
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
|
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
|
||||||
env:
|
env:
|
||||||
OWNER: ${{ github.repository_owner }}
|
OWNER: ${{ github.repository_owner }}
|
||||||
- name: Build and push
|
- name: Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
fallenbagel/jellyseerr
|
||||||
|
ghcr.io/${{ env.OWNER_LC }}/jellyseerr
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=sha,prefix=,suffix=,format=short
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: ${{ matrix.platform }}
|
||||||
push: true
|
push: true
|
||||||
build-args: |
|
build-args: |
|
||||||
COMMIT_TAG=${{ github.sha }}
|
COMMIT_TAG=${{ github.sha }}
|
||||||
tags: |
|
outputs: |
|
||||||
fallenbagel/jellyseerr:develop
|
type=image,push-by-digest=true,name=fallenbagel/jellyseerr,push=true
|
||||||
ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
|
type=image,push-by-digest=true,name=ghcr.io/${{ env.OWNER_LC }}/jellyseerr,push=true
|
||||||
|
cache-from: type=gha,scope=${{ matrix.platform }}
|
||||||
|
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
|
||||||
|
provenance: false
|
||||||
|
- name: Set outputs
|
||||||
|
id: set_outputs
|
||||||
|
run: |
|
||||||
|
platform="${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}"
|
||||||
|
echo "digest-${platform}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
merge_and_push:
|
||||||
|
name: Create and Push Multi-arch Manifest
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Set lower case owner name
|
||||||
|
run: |
|
||||||
|
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
|
||||||
|
env:
|
||||||
|
OWNER: ${{ github.repository_owner }}
|
||||||
|
- name: Create and push manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create fallenbagel/jellyseerr:develop \
|
||||||
|
--amend fallenbagel/jellyseerr@${{ needs.build.outputs.digest-amd64 }} \
|
||||||
|
--amend fallenbagel/jellyseerr@${{ needs.build.outputs.digest-arm64 }}
|
||||||
|
docker manifest push fallenbagel/jellyseerr:develop
|
||||||
|
|
||||||
|
# GHCR manifest
|
||||||
|
docker manifest create ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop \
|
||||||
|
--amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-amd64 }} \
|
||||||
|
--amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-arm64 }}
|
||||||
|
docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
|
||||||
|
|
||||||
discord:
|
discord:
|
||||||
name: Send Discord Notification
|
name: Send Discord Notification
|
||||||
needs: build_and_push
|
needs: merge_and_push
|
||||||
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
|
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Get Build Job Status
|
- name: Get Build Job Status
|
||||||
uses: technote-space/workflow-conclusion-action@v3
|
uses: technote-space/workflow-conclusion-action@v3
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ RUN \
|
|||||||
;; \
|
;; \
|
||||||
esac
|
esac
|
||||||
|
|
||||||
RUN npm install --global pnpm
|
RUN npm install --global pnpm@9
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml postinstall-win.js ./
|
COPY package.json pnpm-lock.yaml postinstall-win.js ./
|
||||||
RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile
|
RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile
|
||||||
@@ -29,7 +29,7 @@ RUN pnpm build
|
|||||||
# remove development dependencies
|
# remove development dependencies
|
||||||
RUN pnpm prune --prod --ignore-scripts
|
RUN pnpm prune --prod --ignore-scripts
|
||||||
|
|
||||||
RUN rm -rf src server .next/cache
|
RUN rm -rf src server .next/cache charts gen-docs docs
|
||||||
|
|
||||||
RUN touch config/DOCKER
|
RUN touch config/DOCKER
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
|
RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm@9
|
||||||
|
|
||||||
# copy from build image
|
# copy from build image
|
||||||
COPY --from=BUILD_IMAGE /app ./
|
COPY --from=BUILD_IMAGE /app ./
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM node:22-alpine
|
|||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
Run npm install --global pnpm
|
RUN npm install --global pnpm@9
|
||||||
|
|
||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
|
|
||||||
|
|||||||
102
README.md
102
README.md
@@ -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="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>
|
<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 -->
|
<!-- 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-64-orange.svg"/></a>
|
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-66-orange.svg"/></a>
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- 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/)**.
|
**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,88 +86,90 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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/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/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/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://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://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://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/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>
|
||||||
<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/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://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://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://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://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://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>
|
<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>
|
||||||
<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/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/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://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/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/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/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>
|
||||||
<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/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/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="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="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/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>
|
<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>
|
||||||
<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/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="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="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://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="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>
|
||||||
<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://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/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="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="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/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>
|
||||||
<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://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/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/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/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="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>
|
||||||
<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/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/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://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://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://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/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/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>
|
||||||
<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://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://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/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/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="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="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/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>
|
||||||
<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/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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ kubeVersion: ">=1.23.0-0"
|
|||||||
name: jellyseerr-chart
|
name: jellyseerr-chart
|
||||||
description: Jellyseerr helm chart for Kubernetes
|
description: Jellyseerr helm chart for Kubernetes
|
||||||
type: application
|
type: application
|
||||||
version: 1.3.0
|
version: 2.1.1
|
||||||
appVersion: "2.2.3"
|
appVersion: "2.3.0"
|
||||||
maintainers:
|
maintainers:
|
||||||
- name: Jellyseerr
|
- name: Jellyseerr
|
||||||
url: https://github.com/Fallenbagel/jellyseerr
|
url: https://github.com/Fallenbagel/jellyseerr
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# jellyseerr-chart
|
# jellyseerr-chart
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
Jellyseerr helm chart for Kubernetes
|
Jellyseerr helm chart for Kubernetes
|
||||||
|
|
||||||
@@ -25,10 +25,6 @@ Kubernetes: `>=1.23.0-0`
|
|||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
|-----|------|---------|-------------|
|
|-----|------|---------|-------------|
|
||||||
| affinity | object | `{}` | |
|
| affinity | object | `{}` | |
|
||||||
| autoscaling.enabled | bool | `false` | |
|
|
||||||
| autoscaling.maxReplicas | int | `100` | |
|
|
||||||
| autoscaling.minReplicas | int | `1` | |
|
|
||||||
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
|
|
||||||
| config | object | `{"persistence":{"accessModes":["ReadWriteOnce"],"annotations":{},"name":"","size":"5Gi","volumeName":""}}` | Creating PVC to store configuration |
|
| config | object | `{"persistence":{"accessModes":["ReadWriteOnce"],"annotations":{},"name":"","size":"5Gi","volumeName":""}}` | Creating PVC to store configuration |
|
||||||
| config.persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes of persistent disk |
|
| config.persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes of persistent disk |
|
||||||
| config.persistence.annotations | object | `{}` | Annotations for PVCs |
|
| config.persistence.annotations | object | `{}` | Annotations for PVCs |
|
||||||
@@ -39,7 +35,7 @@ Kubernetes: `>=1.23.0-0`
|
|||||||
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the jellyseerr pods |
|
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the jellyseerr pods |
|
||||||
| fullnameOverride | string | `""` | |
|
| fullnameOverride | string | `""` | |
|
||||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||||
| image.registry | string | `"docker.io"` | |
|
| image.registry | string | `"ghcr.io"` | |
|
||||||
| image.repository | string | `"fallenbagel/jellyseerr"` | |
|
| image.repository | string | `"fallenbagel/jellyseerr"` | |
|
||||||
| image.sha | string | `""` | |
|
| image.sha | string | `""` | |
|
||||||
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
|
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
|
||||||
@@ -67,3 +63,5 @@ Kubernetes: `>=1.23.0-0`
|
|||||||
| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
|
| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
|
||||||
| strategy | object | `{"type":"Recreate"}` | Deployment strategy |
|
| strategy | object | `{"type":"Recreate"}` | Deployment strategy |
|
||||||
| tolerations | list | `[]` | |
|
| tolerations | list | `[]` | |
|
||||||
|
| volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition. |
|
||||||
|
| volumes | list | `[]` | Additional volumes on the output Deployment definition. |
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
{{- include "jellyseerr.labels" . | nindent 4 }}
|
{{- include "jellyseerr.labels" . | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
{{- if not .Values.autoscaling.enabled }}
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: {{ .Values.replicaCount }}
|
||||||
{{- end }}
|
|
||||||
strategy:
|
strategy:
|
||||||
type: {{ .Values.strategy.type }}
|
type: {{ .Values.strategy.type }}
|
||||||
selector:
|
selector:
|
||||||
@@ -67,10 +65,16 @@ spec:
|
|||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config
|
- name: config
|
||||||
mountPath: /app/config
|
mountPath: /app/config
|
||||||
|
{{- with .Values.volumeMounts }}
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
volumes:
|
volumes:
|
||||||
- name: config
|
- name: config
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ include "jellyseerr.configPersistenceName" . }}
|
claimName: {{ include "jellyseerr.configPersistenceName" . }}
|
||||||
|
{{- with .Values.volumes }}
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
{{- if .Values.autoscaling.enabled }}
|
|
||||||
apiVersion: autoscaling/v2
|
|
||||||
kind: HorizontalPodAutoscaler
|
|
||||||
metadata:
|
|
||||||
name: {{ include "jellyseerr.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "jellyseerr.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
scaleTargetRef:
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
name: {{ include "jellyseerr.fullname" . }}
|
|
||||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
|
||||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
|
||||||
metrics:
|
|
||||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
|
||||||
- type: Resource
|
|
||||||
resource:
|
|
||||||
name: cpu
|
|
||||||
target:
|
|
||||||
type: Utilization
|
|
||||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
|
||||||
- type: Resource
|
|
||||||
resource:
|
|
||||||
name: memory
|
|
||||||
target:
|
|
||||||
type: Utilization
|
|
||||||
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
registry: docker.io
|
registry: ghcr.io
|
||||||
repository: fallenbagel/jellyseerr
|
repository: fallenbagel/jellyseerr
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
# -- Overrides the image tag whose default is the chart appVersion.
|
# -- Overrides the image tag whose default is the chart appVersion.
|
||||||
@@ -94,12 +94,18 @@ resources: {}
|
|||||||
# cpu: 100m
|
# cpu: 100m
|
||||||
# memory: 128Mi
|
# memory: 128Mi
|
||||||
|
|
||||||
autoscaling:
|
# -- Additional volumes on the output Deployment definition.
|
||||||
enabled: false
|
volumes: []
|
||||||
minReplicas: 1
|
# - name: foo
|
||||||
maxReplicas: 100
|
# secret:
|
||||||
targetCPUUtilizationPercentage: 80
|
# secretName: mysecret
|
||||||
# targetMemoryUtilizationPercentage: 80
|
# optional: false
|
||||||
|
|
||||||
|
# -- Additional volumeMounts on the output Deployment definition.
|
||||||
|
volumeMounts: []
|
||||||
|
# - name: foo
|
||||||
|
# mountPath: "/etc/foo"
|
||||||
|
# readOnly: true
|
||||||
|
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ describe('General Settings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('modifies setting that requires restart', () => {
|
it('modifies setting that requires restart', () => {
|
||||||
cy.visit('/settings');
|
cy.visit('/settings/network');
|
||||||
|
|
||||||
cy.get('#trustProxy').click();
|
cy.get('#trustProxy').click();
|
||||||
cy.get('[data-testid=settings-main-form]').submit();
|
cy.get('[data-testid=settings-network-form]').submit();
|
||||||
cy.get('[data-testid=modal-title]').should(
|
cy.get('[data-testid=modal-title]').should(
|
||||||
'contain',
|
'contain',
|
||||||
'Server Restart Required'
|
'Server Restart Required'
|
||||||
@@ -26,7 +26,7 @@ describe('General Settings', () => {
|
|||||||
cy.get('[data-testid=modal-title]').should('not.exist');
|
cy.get('[data-testid=modal-title]').should('not.exist');
|
||||||
|
|
||||||
cy.get('[type=checkbox]#trustProxy').click();
|
cy.get('[type=checkbox]#trustProxy').click();
|
||||||
cy.get('[data-testid=settings-main-form]').submit();
|
cy.get('[data-testid=settings-network-form]').submit();
|
||||||
cy.get('[data-testid=modal-title]').should('not.exist');
|
cy.get('[data-testid=modal-title]').should('not.exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const config: Config = {
|
|||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
trailingSlash: false,
|
trailingSlash: false,
|
||||||
|
|
||||||
organizationName: 'Fallenbagel',
|
organizationName: 'fallenbagel',
|
||||||
projectName: 'Jellyseerr',
|
projectName: 'Jellyseerr',
|
||||||
deploymentBranch: 'gh-pages',
|
deploymentBranch: 'gh-pages',
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ const config: Config = {
|
|||||||
routeBasePath: '/',
|
routeBasePath: '/',
|
||||||
path: '../docs',
|
path: '../docs',
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/Fallenbagel/jellyseerr/edit/develop/docs/',
|
'https://github.com/fallenbagel/jellyseerr/edit/develop/docs/',
|
||||||
},
|
},
|
||||||
blog: false,
|
blog: false,
|
||||||
pages: false,
|
pages: false,
|
||||||
@@ -70,7 +70,7 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
href: 'https://github.com/Fallenbagel/jellyseerr',
|
href: 'https://github.com/fallenbagel/jellyseerr',
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ module.exports = {
|
|||||||
{ hostname: 'gravatar.com' },
|
{ hostname: 'gravatar.com' },
|
||||||
{ hostname: 'image.tmdb.org' },
|
{ hostname: 'image.tmdb.org' },
|
||||||
{ hostname: 'artworks.thetvdb.com' },
|
{ hostname: 'artworks.thetvdb.com' },
|
||||||
|
{ hostname: 'plex.tv' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
webpack(config) {
|
webpack(config) {
|
||||||
|
|||||||
@@ -164,12 +164,6 @@ components:
|
|||||||
applicationUrl:
|
applicationUrl:
|
||||||
type: string
|
type: string
|
||||||
example: https://os.example.com
|
example: https://os.example.com
|
||||||
trustProxy:
|
|
||||||
type: boolean
|
|
||||||
example: true
|
|
||||||
csrfProtection:
|
|
||||||
type: boolean
|
|
||||||
example: false
|
|
||||||
hideAvailable:
|
hideAvailable:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
@@ -191,12 +185,21 @@ components:
|
|||||||
enableSpecialEpisodes:
|
enableSpecialEpisodes:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
NetworkSettings:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
csrfProtection:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
forceIpv4First:
|
forceIpv4First:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
dnsServers:
|
dnsServers:
|
||||||
type: string
|
type: string
|
||||||
example: '1.1.1.1'
|
example: '1.1.1.1'
|
||||||
|
trustProxy:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
PlexLibrary:
|
PlexLibrary:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2045,6 +2048,37 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/MainSettings'
|
$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:
|
/settings/main/regenerate:
|
||||||
post:
|
post:
|
||||||
summary: Get main settings with newly-generated API key
|
summary: Get main settings with newly-generated API key
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@supercharge/request-ip": "1.2.0",
|
"@supercharge/request-ip": "1.2.0",
|
||||||
"@svgr/webpack": "6.5.1",
|
"@svgr/webpack": "6.5.1",
|
||||||
"@tanem/react-nprogress": "5.0.30",
|
"@tanem/react-nprogress": "5.0.30",
|
||||||
|
"@types/wink-jaro-distance": "^2.0.2",
|
||||||
"ace-builds": "1.15.2",
|
"ace-builds": "1.15.2",
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"bowser": "2.11.0",
|
"bowser": "2.11.0",
|
||||||
@@ -97,6 +98,7 @@
|
|||||||
"typeorm": "0.3.11",
|
"typeorm": "0.3.11",
|
||||||
"undici": "^6.20.1",
|
"undici": "^6.20.1",
|
||||||
"web-push": "3.5.0",
|
"web-push": "3.5.0",
|
||||||
|
"wink-jaro-distance": "^2.0.0",
|
||||||
"winston": "3.8.2",
|
"winston": "3.8.2",
|
||||||
"winston-daily-rotate-file": "4.7.1",
|
"winston-daily-rotate-file": "4.7.1",
|
||||||
"xml2js": "0.4.23",
|
"xml2js": "0.4.23",
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
'@tanem/react-nprogress':
|
'@tanem/react-nprogress':
|
||||||
specifier: 5.0.30
|
specifier: 5.0.30
|
||||||
version: 5.0.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 5.0.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@types/wink-jaro-distance':
|
||||||
|
specifier: ^2.0.2
|
||||||
|
version: 2.0.2
|
||||||
ace-builds:
|
ace-builds:
|
||||||
specifier: 1.15.2
|
specifier: 1.15.2
|
||||||
version: 1.15.2
|
version: 1.15.2
|
||||||
@@ -203,6 +206,9 @@ importers:
|
|||||||
web-push:
|
web-push:
|
||||||
specifier: 3.5.0
|
specifier: 3.5.0
|
||||||
version: 3.5.0
|
version: 3.5.0
|
||||||
|
wink-jaro-distance:
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.0.0
|
||||||
winston:
|
winston:
|
||||||
specifier: 3.8.2
|
specifier: 3.8.2
|
||||||
version: 3.8.2
|
version: 3.8.2
|
||||||
@@ -3250,6 +3256,9 @@ packages:
|
|||||||
'@types/webxr@0.5.20':
|
'@types/webxr@0.5.20':
|
||||||
resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==}
|
resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==}
|
||||||
|
|
||||||
|
'@types/wink-jaro-distance@2.0.2':
|
||||||
|
resolution: {integrity: sha512-Q79orp7qA/g/uLdFmqd5MtEa0ZfJW5X1WXikAu8IVHt24IrHWrcTNYNdPpLK5mwVg34C6FQnrv/DMtcUhjE/zA==}
|
||||||
|
|
||||||
'@types/xml2js@0.4.11':
|
'@types/xml2js@0.4.11':
|
||||||
resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==}
|
resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==}
|
||||||
|
|
||||||
@@ -9467,6 +9476,9 @@ packages:
|
|||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||||
|
|
||||||
|
wink-jaro-distance@2.0.0:
|
||||||
|
resolution: {integrity: sha512-9bcUaXCi9N8iYpGWbFkf83OsBkg17r4hEyxusEzl+nnReLRPqxhB9YNeRn3g54SYnVRNXP029lY3HDsbdxTAuA==}
|
||||||
|
|
||||||
winston-daily-rotate-file@4.7.1:
|
winston-daily-rotate-file@4.7.1:
|
||||||
resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==}
|
resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -13737,6 +13749,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/webxr@0.5.20': {}
|
'@types/webxr@0.5.20': {}
|
||||||
|
|
||||||
|
'@types/wink-jaro-distance@2.0.2': {}
|
||||||
|
|
||||||
'@types/xml2js@0.4.11':
|
'@types/xml2js@0.4.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.10.5
|
'@types/node': 22.10.5
|
||||||
@@ -20905,6 +20919,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
|
|
||||||
|
wink-jaro-distance@2.0.0: {}
|
||||||
|
|
||||||
winston-daily-rotate-file@4.7.1(winston@3.8.2):
|
winston-daily-rotate-file@4.7.1(winston@3.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
file-stream-rotator: 0.6.1
|
file-stream-rotator: 0.6.1
|
||||||
|
|||||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import ExternalAPI from '@server/api/externalapi';
|
import ExternalAPI from '@server/api/externalapi';
|
||||||
import cacheManager from '@server/lib/cache';
|
import cacheManager from '@server/lib/cache';
|
||||||
import { getSettings } from '@server/lib/settings';
|
import { getSettings } from '@server/lib/settings';
|
||||||
|
import jaro from 'wink-jaro-distance';
|
||||||
|
|
||||||
interface RTAlgoliaSearchResponse {
|
interface RTAlgoliaSearchResponse {
|
||||||
results: {
|
results: {
|
||||||
@@ -15,7 +16,7 @@ interface RTAlgoliaHit {
|
|||||||
tmsId: string;
|
tmsId: string;
|
||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
titles: string[];
|
titles?: string[];
|
||||||
description: string;
|
description: string;
|
||||||
releaseYear: number;
|
releaseYear: number;
|
||||||
rating: string;
|
rating: string;
|
||||||
@@ -24,9 +25,9 @@ interface RTAlgoliaHit {
|
|||||||
isEmsSearchable: boolean;
|
isEmsSearchable: boolean;
|
||||||
rtId: number;
|
rtId: number;
|
||||||
vanity: string;
|
vanity: string;
|
||||||
aka: string[];
|
aka?: string[];
|
||||||
posterImageUrl: string;
|
posterImageUrl: string;
|
||||||
rottenTomatoes: {
|
rottenTomatoes?: {
|
||||||
audienceScore: number;
|
audienceScore: number;
|
||||||
criticsIconUrl: string;
|
criticsIconUrl: string;
|
||||||
wantToSeeCount: number;
|
wantToSeeCount: number;
|
||||||
@@ -47,6 +48,47 @@ export interface RTRating {
|
|||||||
url: string;
|
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
|
* This is a best-effort API. The Rotten Tomatoes API is technically
|
||||||
* private and getting access costs money/requires approval.
|
* private and getting access costs money/requires approval.
|
||||||
@@ -90,47 +132,21 @@ class RottenTomatoes extends ExternalAPI {
|
|||||||
year: number
|
year: number
|
||||||
): Promise<RTRating | null> {
|
): Promise<RTRating | null> {
|
||||||
try {
|
try {
|
||||||
|
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"movie"');
|
||||||
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
||||||
requests: [
|
requests: [
|
||||||
{
|
{
|
||||||
indexName: 'content_rt',
|
indexName: 'content_rt',
|
||||||
query: name,
|
query: name.replace(/\bthe\b ?/gi, ''),
|
||||||
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
|
params: `filters=${filters}&hitsPerPage=20`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
||||||
|
const movie = best(contentResults?.hits || [], name, year);
|
||||||
|
|
||||||
if (!contentResults) {
|
if (!movie?.rottenTomatoes) return null;
|
||||||
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 {
|
return {
|
||||||
title: movie.title,
|
title: movie.title,
|
||||||
@@ -158,33 +174,21 @@ class RottenTomatoes extends ExternalAPI {
|
|||||||
year?: number
|
year?: number
|
||||||
): Promise<RTRating | null> {
|
): Promise<RTRating | null> {
|
||||||
try {
|
try {
|
||||||
|
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"tv"');
|
||||||
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
||||||
requests: [
|
requests: [
|
||||||
{
|
{
|
||||||
indexName: 'content_rt',
|
indexName: 'content_rt',
|
||||||
query: name,
|
query: name,
|
||||||
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
|
params: `filters=${filters}&hitsPerPage=20`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
||||||
|
const tvshow = best(contentResults?.hits || [], name, year);
|
||||||
|
|
||||||
if (!contentResults) {
|
if (!tvshow?.rottenTomatoes) return null;
|
||||||
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 {
|
return {
|
||||||
title: tvshow.title,
|
title: tvshow.title,
|
||||||
|
|||||||
@@ -734,8 +734,11 @@ export class MediaRequest {
|
|||||||
media.mediaType === MediaType.MOVIE &&
|
media.mediaType === MediaType.MOVIE &&
|
||||||
this.status === MediaRequestStatus.DECLINED
|
this.status === MediaRequestStatus.DECLINED
|
||||||
) {
|
) {
|
||||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
const statusField = this.is4k ? 'status4k' : 'status';
|
||||||
mediaRepository.save(media);
|
await mediaRepository.update(
|
||||||
|
{ id: this.media.id },
|
||||||
|
{ [statusField]: MediaStatus.UNKNOWN }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -752,8 +755,11 @@ export class MediaRequest {
|
|||||||
).length === 0 &&
|
).length === 0 &&
|
||||||
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING
|
media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING
|
||||||
) {
|
) {
|
||||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
const statusField = this.is4k ? 'status4k' : 'status';
|
||||||
mediaRepository.save(media);
|
mediaRepository.update(
|
||||||
|
{ id: this.media.id },
|
||||||
|
{ [statusField]: MediaStatus.UNKNOWN }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approve child seasons if parent is approved
|
// Approve child seasons if parent is approved
|
||||||
@@ -955,8 +961,10 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const requestRepository = getRepository(MediaRequest);
|
const requestRepository = getRepository(MediaRequest);
|
||||||
this.status = MediaRequestStatus.APPROVED;
|
|
||||||
await requestRepository.save(this);
|
await requestRepository.update(this.id, {
|
||||||
|
status: MediaRequestStatus.APPROVED,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,18 +994,22 @@ export class MediaRequest {
|
|||||||
throw new Error('Media data not found');
|
throw new Error('Media data not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
const updateFields = {
|
||||||
radarrMovie.id;
|
[this.is4k ? 'externalServiceId4k' : 'externalServiceId']:
|
||||||
media[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] =
|
radarrMovie.id,
|
||||||
radarrMovie.titleSlug;
|
[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']:
|
||||||
media[this.is4k ? 'serviceId4k' : 'serviceId'] = radarrSettings?.id;
|
radarrMovie.titleSlug,
|
||||||
await mediaRepository.save(media);
|
[this.is4k ? 'serviceId4k' : 'serviceId']: radarrMovie?.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
await mediaRepository.update({ id: this.media.id }, updateFields);
|
||||||
})
|
})
|
||||||
.catch(async () => {
|
.catch(async () => {
|
||||||
const requestRepository = getRepository(MediaRequest);
|
const requestRepository = getRepository(MediaRequest);
|
||||||
|
|
||||||
this.status = MediaRequestStatus.FAILED;
|
await requestRepository.update(this.id, {
|
||||||
await requestRepository.save(this);
|
status: MediaRequestStatus.FAILED,
|
||||||
|
});
|
||||||
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Something went wrong sending movie request to Radarr, marking status as FAILED',
|
'Something went wrong sending movie request to Radarr, marking status as FAILED',
|
||||||
@@ -1113,8 +1125,9 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const requestRepository = getRepository(MediaRequest);
|
const requestRepository = getRepository(MediaRequest);
|
||||||
this.status = MediaRequestStatus.APPROVED;
|
await requestRepository.update(this.id, {
|
||||||
await requestRepository.save(this);
|
status: MediaRequestStatus.APPROVED,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,13 +83,13 @@ export class User {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
public jellyfinUserId?: string;
|
public jellyfinUserId?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, select: false })
|
||||||
public jellyfinDeviceId?: string;
|
public jellyfinDeviceId?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, select: false })
|
||||||
public jellyfinAuthToken?: string;
|
public jellyfinAuthToken?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, select: false })
|
||||||
public plexToken?: string;
|
public plexToken?: string;
|
||||||
|
|
||||||
@Column({ type: 'integer', default: 0 })
|
@Column({ type: 'integer', default: 0 })
|
||||||
|
|||||||
@@ -72,23 +72,26 @@ app
|
|||||||
|
|
||||||
// Load Settings
|
// Load Settings
|
||||||
const settings = await getSettings().load();
|
const settings = await getSettings().load();
|
||||||
restartFlag.initializeSettings(settings.main);
|
restartFlag.initializeSettings(settings);
|
||||||
|
|
||||||
// Check if we force IPv4 first
|
// Check if we force IPv4 first
|
||||||
if (process.env.forceIpv4First === 'true' || settings.main.forceIpv4First) {
|
if (
|
||||||
|
process.env.forceIpv4First === 'true' ||
|
||||||
|
settings.network.forceIpv4First
|
||||||
|
) {
|
||||||
dns.setDefaultResultOrder('ipv4first');
|
dns.setDefaultResultOrder('ipv4first');
|
||||||
net.setDefaultAutoSelectFamily(false);
|
net.setDefaultAutoSelectFamily(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.main.dnsServers.trim() !== '') {
|
if (settings.network.dnsServers.trim() !== '') {
|
||||||
dns.setServers(
|
dns.setServers(
|
||||||
settings.main.dnsServers.split(',').map((server) => server.trim())
|
settings.network.dnsServers.split(',').map((server) => server.trim())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register HTTP proxy
|
// Register HTTP proxy
|
||||||
if (settings.main.proxy.enabled) {
|
if (settings.network.proxy.enabled) {
|
||||||
await createCustomProxyAgent(settings.main.proxy);
|
await createCustomProxyAgent(settings.network.proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate library types
|
// Migrate library types
|
||||||
@@ -143,7 +146,7 @@ app
|
|||||||
await DiscoverSlider.bootstrapSliders();
|
await DiscoverSlider.bootstrapSliders();
|
||||||
|
|
||||||
const server = express();
|
const server = express();
|
||||||
if (settings.main.trustProxy) {
|
if (settings.network.trustProxy) {
|
||||||
server.enable('trust proxy');
|
server.enable('trust proxy');
|
||||||
}
|
}
|
||||||
server.use(cookieParser());
|
server.use(cookieParser());
|
||||||
@@ -164,7 +167,7 @@ app
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (settings.main.csrfProtection) {
|
if (settings.network.csrfProtection) {
|
||||||
server.use(
|
server.use(
|
||||||
csurf({
|
csurf({
|
||||||
cookie: {
|
cookie: {
|
||||||
@@ -194,7 +197,7 @@ app
|
|||||||
cookie: {
|
cookie: {
|
||||||
maxAge: 1000 * 60 * 60 * 24 * 30,
|
maxAge: 1000 * 60 * 60 * 24 * 30,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: settings.main.csrfProtection ? 'strict' : 'lax',
|
sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
|
||||||
secure: 'auto',
|
secure: 'auto',
|
||||||
},
|
},
|
||||||
store: new TypeormStore({
|
store: new TypeormStore({
|
||||||
|
|||||||
@@ -70,6 +70,35 @@ export const startJobs = (): void => {
|
|||||||
running: () => plexFullScanner.status().running,
|
running: () => plexFullScanner.status().running,
|
||||||
cancelFn: () => plexFullScanner.cancel(),
|
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 (
|
} else if (
|
||||||
mediaServerType === MediaServerType.JELLYFIN ||
|
mediaServerType === MediaServerType.JELLYFIN ||
|
||||||
mediaServerType === MediaServerType.EMBY
|
mediaServerType === MediaServerType.EMBY
|
||||||
@@ -112,21 +141,6 @@ 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
|
// Run full radarr scan every 24 hours
|
||||||
scheduledJobs.push({
|
scheduledJobs.push({
|
||||||
id: 'radarr-scan',
|
id: 'radarr-scan',
|
||||||
@@ -223,19 +237,5 @@ 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' });
|
logger.info('Scheduled jobs loaded', { label: 'Jobs' });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ export interface MainSettings {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
applicationTitle: string;
|
applicationTitle: string;
|
||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
csrfProtection: boolean;
|
|
||||||
cacheImages: boolean;
|
cacheImages: boolean;
|
||||||
defaultPermissions: number;
|
defaultPermissions: number;
|
||||||
defaultQuotas: {
|
defaultQuotas: {
|
||||||
@@ -128,13 +127,17 @@ export interface MainSettings {
|
|||||||
discoverRegion: string;
|
discoverRegion: string;
|
||||||
streamingRegion: string;
|
streamingRegion: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
trustProxy: boolean;
|
|
||||||
mediaServerType: number;
|
mediaServerType: number;
|
||||||
partialRequestsEnabled: boolean;
|
partialRequestsEnabled: boolean;
|
||||||
enableSpecialEpisodes: boolean;
|
enableSpecialEpisodes: boolean;
|
||||||
|
locale: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkSettings {
|
||||||
|
csrfProtection: boolean;
|
||||||
forceIpv4First: boolean;
|
forceIpv4First: boolean;
|
||||||
dnsServers: string;
|
dnsServers: string;
|
||||||
locale: string;
|
trustProxy: boolean;
|
||||||
proxy: ProxySettings;
|
proxy: ProxySettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +316,7 @@ export interface AllSettings {
|
|||||||
public: PublicSettings;
|
public: PublicSettings;
|
||||||
notifications: NotificationSettings;
|
notifications: NotificationSettings;
|
||||||
jobs: Record<JobId, JobSettings>;
|
jobs: Record<JobId, JobSettings>;
|
||||||
|
network: NetworkSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
|
const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
|
||||||
@@ -331,7 +335,6 @@ class Settings {
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
applicationTitle: 'Jellyseerr',
|
applicationTitle: 'Jellyseerr',
|
||||||
applicationUrl: '',
|
applicationUrl: '',
|
||||||
csrfProtection: false,
|
|
||||||
cacheImages: false,
|
cacheImages: false,
|
||||||
defaultPermissions: Permission.REQUEST,
|
defaultPermissions: Permission.REQUEST,
|
||||||
defaultQuotas: {
|
defaultQuotas: {
|
||||||
@@ -344,23 +347,10 @@ class Settings {
|
|||||||
discoverRegion: '',
|
discoverRegion: '',
|
||||||
streamingRegion: '',
|
streamingRegion: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
trustProxy: false,
|
|
||||||
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
||||||
partialRequestsEnabled: true,
|
partialRequestsEnabled: true,
|
||||||
enableSpecialEpisodes: false,
|
enableSpecialEpisodes: false,
|
||||||
forceIpv4First: false,
|
|
||||||
dnsServers: '',
|
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
proxy: {
|
|
||||||
enabled: false,
|
|
||||||
hostname: '',
|
|
||||||
port: 8080,
|
|
||||||
useSsl: false,
|
|
||||||
user: '',
|
|
||||||
password: '',
|
|
||||||
bypassFilter: '',
|
|
||||||
bypassLocalAddresses: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -513,6 +503,22 @@ class Settings {
|
|||||||
schedule: '0 0 5 * * *',
|
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) {
|
if (initialSettings) {
|
||||||
this.data = merge(this.data, initialSettings);
|
this.data = merge(this.data, initialSettings);
|
||||||
@@ -622,6 +628,14 @@ class Settings {
|
|||||||
this.data.jobs = data;
|
this.data.jobs = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get network(): NetworkSettings {
|
||||||
|
return this.data.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
set network(data: NetworkSettings) {
|
||||||
|
this.data.network = data;
|
||||||
|
}
|
||||||
|
|
||||||
get clientId(): string {
|
get clientId(): string {
|
||||||
return this.data.clientId;
|
return this.data.clientId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
@@ -263,6 +263,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
|||||||
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
|
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
|
||||||
let user = await userRepository.findOne({
|
let user = await userRepository.findOne({
|
||||||
where: { jellyfinUsername: body.username },
|
where: { jellyfinUsername: body.username },
|
||||||
|
select: { id: true, jellyfinDeviceId: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
let deviceId = '';
|
let deviceId = '';
|
||||||
|
|||||||
@@ -78,6 +78,21 @@ settingsRoutes.post('/main', async (req, res) => {
|
|||||||
return res.status(200).json(settings.main);
|
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) => {
|
settingsRoutes.post('/main/regenerate', async (req, res, next) => {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
|
|||||||
export default async function createCustomProxyAgent(
|
export default async function createCustomProxyAgent(
|
||||||
proxySettings: ProxySettings
|
proxySettings: ProxySettings
|
||||||
) {
|
) {
|
||||||
const defaultAgent = new Agent();
|
const defaultAgent = new Agent({ keepAliveTimeout: 5000 });
|
||||||
|
|
||||||
const skipUrl = (url: string) => {
|
const skipUrl = (url: string) => {
|
||||||
const hostname = new URL(url).hostname;
|
const hostname = new URL(url).hostname;
|
||||||
@@ -63,6 +63,7 @@ export default async function createCustomProxyAgent(
|
|||||||
interceptors: {
|
interceptors: {
|
||||||
Client: [noProxyInterceptor],
|
Client: [noProxyInterceptor],
|
||||||
},
|
},
|
||||||
|
keepAliveTimeout: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
setGlobalDispatcher(proxyAgent);
|
setGlobalDispatcher(proxyAgent);
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import type { MainSettings } from '@server/lib/settings';
|
import type { AllSettings, NetworkSettings } from '@server/lib/settings';
|
||||||
import { getSettings } from '@server/lib/settings';
|
import { getSettings } from '@server/lib/settings';
|
||||||
|
|
||||||
class RestartFlag {
|
class RestartFlag {
|
||||||
private settings: MainSettings;
|
private networkSettings: NetworkSettings;
|
||||||
|
|
||||||
public initializeSettings(settings: MainSettings): void {
|
public initializeSettings(settings: AllSettings): void {
|
||||||
this.settings = { ...settings };
|
this.networkSettings = {
|
||||||
|
...settings.network,
|
||||||
|
proxy: { ...settings.network.proxy },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public isSet(): boolean {
|
public isSet(): boolean {
|
||||||
const settings = getSettings().main;
|
const networkSettings = getSettings().network;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.settings.csrfProtection !== settings.csrfProtection ||
|
this.networkSettings.csrfProtection !== networkSettings.csrfProtection ||
|
||||||
this.settings.trustProxy !== settings.trustProxy ||
|
this.networkSettings.trustProxy !== networkSettings.trustProxy ||
|
||||||
this.settings.proxy.enabled !== settings.proxy.enabled ||
|
this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled ||
|
||||||
this.settings.forceIpv4First !== settings.forceIpv4First ||
|
this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First ||
|
||||||
this.settings.dnsServers !== settings.dnsServers
|
this.networkSettings.dnsServers !== networkSettings.dnsServers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -38,14 +38,14 @@ import {
|
|||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
EyeSlashIcon,
|
EyeSlashIcon,
|
||||||
FilmIcon,
|
FilmIcon,
|
||||||
|
MinusCircleIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
|
StarIcon,
|
||||||
TicketIcon,
|
TicketIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import {
|
import {
|
||||||
ChevronDoubleDownIcon,
|
ChevronDoubleDownIcon,
|
||||||
ChevronDoubleUpIcon,
|
ChevronDoubleUpIcon,
|
||||||
MinusCircleIcon,
|
|
||||||
StarIcon,
|
|
||||||
} from '@heroicons/react/24/solid';
|
} from '@heroicons/react/24/solid';
|
||||||
import { type RatingResponse } from '@server/api/ratings';
|
import { type RatingResponse } from '@server/api/ratings';
|
||||||
import { IssueStatus } from '@server/constants/issue';
|
import { IssueStatus } from '@server/constants/issue';
|
||||||
@@ -102,7 +102,7 @@ const messages = defineMessages('components.MovieDetails', {
|
|||||||
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
||||||
watchlistDeleted:
|
watchlistDeleted:
|
||||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||||
watchlistError: 'Something went wrong try again.',
|
watchlistError: 'Something went wrong. Please try again.',
|
||||||
removefromwatchlist: 'Remove From Watchlist',
|
removefromwatchlist: 'Remove From Watchlist',
|
||||||
addtowatchlist: 'Add To Watchlist',
|
addtowatchlist: 'Add To Watchlist',
|
||||||
});
|
});
|
||||||
@@ -190,7 +190,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
mediaLinks.push({
|
mediaLinks.push({
|
||||||
text: getAvalaibleMediaServerName(),
|
text: getAvailableMediaServerName(),
|
||||||
url: plexUrl,
|
url: plexUrl,
|
||||||
svg: <PlayIcon />,
|
svg: <PlayIcon />,
|
||||||
});
|
});
|
||||||
@@ -204,7 +204,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
mediaLinks.push({
|
mediaLinks.push({
|
||||||
text: getAvalaible4kMediaServerName(),
|
text: getAvailable4kMediaServerName(),
|
||||||
url: plexUrl4k,
|
url: plexUrl4k,
|
||||||
svg: <PlayIcon />,
|
svg: <PlayIcon />,
|
||||||
});
|
});
|
||||||
@@ -292,7 +292,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
(provider) => provider.iso_3166_1 === streamingRegion
|
(provider) => provider.iso_3166_1 === streamingRegion
|
||||||
)?.flatrate ?? [];
|
)?.flatrate ?? [];
|
||||||
|
|
||||||
function getAvalaibleMediaServerName() {
|
function getAvailableMediaServerName() {
|
||||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvalaible4kMediaServerName() {
|
function getAvailable4kMediaServerName() {
|
||||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const messages = defineMessages('components.Settings', {
|
|||||||
menuPlexSettings: 'Plex',
|
menuPlexSettings: 'Plex',
|
||||||
menuJellyfinSettings: '{mediaServerName}',
|
menuJellyfinSettings: '{mediaServerName}',
|
||||||
menuServices: 'Services',
|
menuServices: 'Services',
|
||||||
|
menuNetwork: 'Network',
|
||||||
menuNotifications: 'Notifications',
|
menuNotifications: 'Notifications',
|
||||||
menuLogs: 'Logs',
|
menuLogs: 'Logs',
|
||||||
menuJobs: 'Jobs & Cache',
|
menuJobs: 'Jobs & Cache',
|
||||||
@@ -53,6 +54,11 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
|
|||||||
route: '/settings/services',
|
route: '/settings/services',
|
||||||
regex: /^\/settings\/services/,
|
regex: /^\/settings\/services/,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.menuNetwork),
|
||||||
|
route: '/settings/network',
|
||||||
|
regex: /^\/settings\/network/,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(messages.menuNotifications),
|
text: intl.formatMessage(messages.menuNotifications),
|
||||||
route: '/settings/notifications/email',
|
route: '/settings/notifications/email',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Button from '@app/components/Common/Button';
|
|||||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||||
import PageTitle from '@app/components/Common/PageTitle';
|
import PageTitle from '@app/components/Common/PageTitle';
|
||||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||||
import Tooltip from '@app/components/Common/Tooltip';
|
|
||||||
import LanguageSelector from '@app/components/LanguageSelector';
|
import LanguageSelector from '@app/components/LanguageSelector';
|
||||||
import RegionSelector from '@app/components/RegionSelector';
|
import RegionSelector from '@app/components/RegionSelector';
|
||||||
import CopyButton from '@app/components/Settings/CopyButton';
|
import CopyButton from '@app/components/Settings/CopyButton';
|
||||||
@@ -42,39 +41,15 @@ const messages = defineMessages('components.Settings.SettingsMain', {
|
|||||||
toastSettingsSuccess: 'Settings saved successfully!',
|
toastSettingsSuccess: 'Settings saved successfully!',
|
||||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||||
hideAvailable: 'Hide Available Media',
|
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',
|
cacheImages: 'Enable Image Caching',
|
||||||
cacheImagesTip:
|
cacheImagesTip:
|
||||||
'Cache externally sourced images (requires a significant amount of disk space)',
|
'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',
|
validationApplicationTitle: 'You must provide an application title',
|
||||||
validationApplicationUrl: 'You must provide a valid URL',
|
validationApplicationUrl: 'You must provide a valid URL',
|
||||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||||
partialRequestsEnabled: 'Allow Partial Series Requests',
|
partialRequestsEnabled: 'Allow Partial Series Requests',
|
||||||
enableSpecialEpisodes: 'Allow Special Episodes 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',
|
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 = () => {
|
const SettingsMain = () => {
|
||||||
@@ -105,12 +80,6 @@ const SettingsMain = () => {
|
|||||||
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
|
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
|
||||||
(value) => !value || !value.endsWith('/')
|
(value) => !value || !value.endsWith('/')
|
||||||
),
|
),
|
||||||
proxyPort: Yup.number().when('proxyEnabled', {
|
|
||||||
is: (proxyEnabled: boolean) => proxyEnabled,
|
|
||||||
then: Yup.number().required(
|
|
||||||
intl.formatMessage(messages.validationProxyPort)
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const regenerate = async () => {
|
const regenerate = async () => {
|
||||||
@@ -158,7 +127,6 @@ const SettingsMain = () => {
|
|||||||
initialValues={{
|
initialValues={{
|
||||||
applicationTitle: data?.applicationTitle,
|
applicationTitle: data?.applicationTitle,
|
||||||
applicationUrl: data?.applicationUrl,
|
applicationUrl: data?.applicationUrl,
|
||||||
csrfProtection: data?.csrfProtection,
|
|
||||||
hideAvailable: data?.hideAvailable,
|
hideAvailable: data?.hideAvailable,
|
||||||
locale: data?.locale ?? 'en',
|
locale: data?.locale ?? 'en',
|
||||||
discoverRegion: data?.discoverRegion,
|
discoverRegion: data?.discoverRegion,
|
||||||
@@ -166,18 +134,7 @@ const SettingsMain = () => {
|
|||||||
streamingRegion: data?.streamingRegion || 'US',
|
streamingRegion: data?.streamingRegion || 'US',
|
||||||
partialRequestsEnabled: data?.partialRequestsEnabled,
|
partialRequestsEnabled: data?.partialRequestsEnabled,
|
||||||
enableSpecialEpisodes: data?.enableSpecialEpisodes,
|
enableSpecialEpisodes: data?.enableSpecialEpisodes,
|
||||||
forceIpv4First: data?.forceIpv4First,
|
|
||||||
dnsServers: data?.dnsServers,
|
|
||||||
trustProxy: data?.trustProxy,
|
|
||||||
cacheImages: data?.cacheImages,
|
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
|
enableReinitialize
|
||||||
validationSchema={MainSettingsSchema}
|
validationSchema={MainSettingsSchema}
|
||||||
@@ -191,7 +148,6 @@ const SettingsMain = () => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
applicationTitle: values.applicationTitle,
|
applicationTitle: values.applicationTitle,
|
||||||
applicationUrl: values.applicationUrl,
|
applicationUrl: values.applicationUrl,
|
||||||
csrfProtection: values.csrfProtection,
|
|
||||||
hideAvailable: values.hideAvailable,
|
hideAvailable: values.hideAvailable,
|
||||||
locale: values.locale,
|
locale: values.locale,
|
||||||
discoverRegion: values.discoverRegion,
|
discoverRegion: values.discoverRegion,
|
||||||
@@ -199,20 +155,7 @@ const SettingsMain = () => {
|
|||||||
originalLanguage: values.originalLanguage,
|
originalLanguage: values.originalLanguage,
|
||||||
partialRequestsEnabled: values.partialRequestsEnabled,
|
partialRequestsEnabled: values.partialRequestsEnabled,
|
||||||
enableSpecialEpisodes: values.enableSpecialEpisodes,
|
enableSpecialEpisodes: values.enableSpecialEpisodes,
|
||||||
forceIpv4First: values.forceIpv4First,
|
|
||||||
dnsServers: values.dnsServers,
|
|
||||||
trustProxy: values.trustProxy,
|
|
||||||
cacheImages: values.cacheImages,
|
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();
|
if (!res.ok) throw new Error();
|
||||||
@@ -321,58 +264,6 @@ const SettingsMain = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="form-row">
|
||||||
<label htmlFor="cacheImages" className="checkbox-label">
|
<label htmlFor="cacheImages" className="checkbox-label">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
@@ -534,231 +425,6 @@ const SettingsMain = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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="actions">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||||
|
|||||||
461
src/components/Settings/SettingsNetwork/index.tsx
Normal file
461
src/components/Settings/SettingsNetwork/index.tsx
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
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;
|
||||||
@@ -133,10 +133,6 @@ const Setup = () => {
|
|||||||
setCurrentStep(3);
|
setCurrentStep(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStep === 3) {
|
|
||||||
validateLibraries();
|
|
||||||
}
|
|
||||||
}, [
|
}, [
|
||||||
settings.currentSettings.mediaServerType,
|
settings.currentSettings.mediaServerType,
|
||||||
settings.currentSettings.initialized,
|
settings.currentSettings.initialized,
|
||||||
@@ -148,6 +144,13 @@ const Setup = () => {
|
|||||||
validateLibraries,
|
validateLibraries,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentStep === 3) {
|
||||||
|
validateLibraries();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentStep]);
|
||||||
|
|
||||||
const handleComplete = () => {
|
const handleComplete = () => {
|
||||||
validateLibraries();
|
validateLibraries();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const messages = defineMessages('components.TitleCard', {
|
|||||||
watchlistDeleted:
|
watchlistDeleted:
|
||||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||||
watchlistCancel: 'watchlist for <strong>{title}</strong> canceled.',
|
watchlistCancel: 'watchlist for <strong>{title}</strong> canceled.',
|
||||||
watchlistError: 'Something went wrong try again.',
|
watchlistError: 'Something went wrong. Please try again.',
|
||||||
});
|
});
|
||||||
|
|
||||||
const TitleCard = ({
|
const TitleCard = ({
|
||||||
|
|||||||
@@ -41,13 +41,11 @@ import {
|
|||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
EyeSlashIcon,
|
EyeSlashIcon,
|
||||||
FilmIcon,
|
FilmIcon,
|
||||||
PlayIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import {
|
|
||||||
ChevronDownIcon,
|
|
||||||
MinusCircleIcon,
|
MinusCircleIcon,
|
||||||
|
PlayIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
} from '@heroicons/react/24/solid';
|
} from '@heroicons/react/24/outline';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||||
import type { RTRating } from '@server/api/rating/rottentomatoes';
|
import type { RTRating } from '@server/api/rating/rottentomatoes';
|
||||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||||
import { IssueStatus } from '@server/constants/issue';
|
import { IssueStatus } from '@server/constants/issue';
|
||||||
@@ -103,7 +101,7 @@ const messages = defineMessages('components.TvDetails', {
|
|||||||
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
||||||
watchlistDeleted:
|
watchlistDeleted:
|
||||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||||
watchlistError: 'Something went wrong try again.',
|
watchlistError: 'Something went wrong. Please try again.',
|
||||||
removefromwatchlist: 'Remove From Watchlist',
|
removefromwatchlist: 'Remove From Watchlist',
|
||||||
addtowatchlist: 'Add To Watchlist',
|
addtowatchlist: 'Add To Watchlist',
|
||||||
});
|
});
|
||||||
@@ -189,7 +187,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
mediaLinks.push({
|
mediaLinks.push({
|
||||||
text: getAvalaibleMediaServerName(),
|
text: getAvailableMediaServerName(),
|
||||||
url: plexUrl,
|
url: plexUrl,
|
||||||
svg: <PlayIcon />,
|
svg: <PlayIcon />,
|
||||||
});
|
});
|
||||||
@@ -203,7 +201,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
mediaLinks.push({
|
mediaLinks.push({
|
||||||
text: getAvalaible4kMediaServerName(),
|
text: getAvailable4kMediaServerName(),
|
||||||
url: plexUrl4k,
|
url: plexUrl4k,
|
||||||
svg: <PlayIcon />,
|
svg: <PlayIcon />,
|
||||||
});
|
});
|
||||||
@@ -324,7 +322,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
(provider) => provider.iso_3166_1 === streamingRegion
|
(provider) => provider.iso_3166_1 === streamingRegion
|
||||||
)?.flatrate ?? [];
|
)?.flatrate ?? [];
|
||||||
|
|
||||||
function getAvalaibleMediaServerName() {
|
function getAvailableMediaServerName() {
|
||||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||||
}
|
}
|
||||||
@@ -336,7 +334,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
|||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvalaible4kMediaServerName() {
|
function getAvailable4kMediaServerName() {
|
||||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import Modal from '@app/components/Common/Modal';
|
import Modal from '@app/components/Common/Modal';
|
||||||
import PermissionEdit from '@app/components/PermissionEdit';
|
import PermissionEdit from '@app/components/PermissionEdit';
|
||||||
import type { User } from '@app/hooks/useUser';
|
import type { User } from '@app/hooks/useUser';
|
||||||
import { useUser } from '@app/hooks/useUser';
|
import { Permission, useUser } from '@app/hooks/useUser';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
|
import { hasPermission } from '@server/lib/permissions';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
@@ -79,7 +80,10 @@ const BulkEditModal = ({
|
|||||||
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
|
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
|
||||||
({ permissions: aPerms }, { permissions: bPerms }) => {
|
({ permissions: aPerms }, { permissions: bPerms }) => {
|
||||||
return {
|
return {
|
||||||
permissions: aPerms === bPerms ? aPerms : NaN,
|
permissions:
|
||||||
|
aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms)
|
||||||
|
? aPerms
|
||||||
|
: NaN,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ permissions: selectedUsers[0].permissions }
|
{ permissions: selectedUsers[0].permissions }
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const globalMessages = defineMessages('i18n', {
|
|||||||
blacklist: 'Blacklist',
|
blacklist: 'Blacklist',
|
||||||
blacklisted: 'Blacklisted',
|
blacklisted: 'Blacklisted',
|
||||||
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
|
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
|
||||||
blacklistError: 'Something went wrong try again.',
|
blacklistError: 'Something went wrong. Please try again.',
|
||||||
blacklistDuplicateError:
|
blacklistDuplicateError:
|
||||||
'<strong>{title}</strong> has already been blacklisted.',
|
'<strong>{title}</strong> has already been blacklisted.',
|
||||||
removeFromBlacklistSuccess:
|
removeFromBlacklistSuccess:
|
||||||
|
|||||||
@@ -340,7 +340,7 @@
|
|||||||
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
|
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
|
||||||
"components.MovieDetails.viewfullcrew": "View Full Crew",
|
"components.MovieDetails.viewfullcrew": "View Full Crew",
|
||||||
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||||
"components.MovieDetails.watchlistError": "Something went wrong try again.",
|
"components.MovieDetails.watchlistError": "Something went wrong. Please try again.",
|
||||||
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||||
"components.MovieDetails.watchtrailer": "Watch Trailer",
|
"components.MovieDetails.watchtrailer": "Watch Trailer",
|
||||||
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
|
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
|
||||||
@@ -915,16 +915,9 @@
|
|||||||
"components.Settings.SettingsMain.applicationurl": "Application URL",
|
"components.Settings.SettingsMain.applicationurl": "Application URL",
|
||||||
"components.Settings.SettingsMain.cacheImages": "Enable Image Caching",
|
"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.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.discoverRegion": "Discover Region",
|
||||||
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
|
"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.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.general": "General",
|
||||||
"components.Settings.SettingsMain.generalsettings": "General Settings",
|
"components.Settings.SettingsMain.generalsettings": "General Settings",
|
||||||
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
|
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
|
||||||
@@ -933,27 +926,39 @@
|
|||||||
"components.Settings.SettingsMain.originallanguage": "Discover Language",
|
"components.Settings.SettingsMain.originallanguage": "Discover Language",
|
||||||
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",
|
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",
|
||||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests",
|
"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.streamingRegion": "Streaming Region",
|
||||||
"components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability",
|
"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.toastApiKeyFailure": "Something went wrong while generating a new API key.",
|
||||||
"components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!",
|
"components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!",
|
||||||
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
|
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
|
||||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!",
|
"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.validationApplicationTitle": "You must provide an application title",
|
||||||
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
|
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
|
||||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
|
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
|
||||||
"components.Settings.SettingsMain.validationProxyPort": "You must provide a valid port",
|
"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.SettingsUsers.defaultPermissions": "Default Permissions",
|
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
||||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
||||||
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
|
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
|
||||||
@@ -1069,6 +1074,7 @@
|
|||||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||||
"components.Settings.menuJobs": "Jobs & Cache",
|
"components.Settings.menuJobs": "Jobs & Cache",
|
||||||
"components.Settings.menuLogs": "Logs",
|
"components.Settings.menuLogs": "Logs",
|
||||||
|
"components.Settings.menuNetwork": "Network",
|
||||||
"components.Settings.menuNotifications": "Notifications",
|
"components.Settings.menuNotifications": "Notifications",
|
||||||
"components.Settings.menuPlexSettings": "Plex",
|
"components.Settings.menuPlexSettings": "Plex",
|
||||||
"components.Settings.menuServices": "Services",
|
"components.Settings.menuServices": "Services",
|
||||||
@@ -1171,7 +1177,7 @@
|
|||||||
"components.TitleCard.tvdbid": "TheTVDB ID",
|
"components.TitleCard.tvdbid": "TheTVDB ID",
|
||||||
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
|
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
|
||||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||||
"components.TitleCard.watchlistError": "Something went wrong try again.",
|
"components.TitleCard.watchlistError": "Something went wrong. Please try again.",
|
||||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||||
"components.TvDetails.Season.noepisodes": "Episode list unavailable.",
|
"components.TvDetails.Season.noepisodes": "Episode list unavailable.",
|
||||||
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
|
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
|
||||||
@@ -1209,7 +1215,7 @@
|
|||||||
"components.TvDetails.tmdbuserscore": "TMDB User Score",
|
"components.TvDetails.tmdbuserscore": "TMDB User Score",
|
||||||
"components.TvDetails.viewfullcrew": "View Full Crew",
|
"components.TvDetails.viewfullcrew": "View Full Crew",
|
||||||
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||||
"components.TvDetails.watchlistError": "Something went wrong try again.",
|
"components.TvDetails.watchlistError": "Something went wrong. Please try again.",
|
||||||
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||||
"components.TvDetails.watchtrailer": "Watch Trailer",
|
"components.TvDetails.watchtrailer": "Watch Trailer",
|
||||||
"components.UserList.accounttype": "Type",
|
"components.UserList.accounttype": "Type",
|
||||||
@@ -1393,7 +1399,7 @@
|
|||||||
"i18n.back": "Back",
|
"i18n.back": "Back",
|
||||||
"i18n.blacklist": "Blacklist",
|
"i18n.blacklist": "Blacklist",
|
||||||
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
|
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
|
||||||
"i18n.blacklistError": "Something went wrong try again.",
|
"i18n.blacklistError": "Something went wrong. Please try again.",
|
||||||
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
|
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
|
||||||
"i18n.blacklisted": "Blacklisted",
|
"i18n.blacklisted": "Blacklisted",
|
||||||
"i18n.cancel": "Cancel",
|
"i18n.cancel": "Cancel",
|
||||||
|
|||||||
16
src/pages/settings/network.tsx
Normal file
16
src/pages/settings/network.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
Reference in New Issue
Block a user