mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-29 04:59:46 -05:00
Compare commits
34 Commits
preview-ne
...
v2.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4b707e619 | ||
|
|
8233d97f21 | ||
|
|
d362b030f9 | ||
|
|
cc876c8276 | ||
|
|
f2d7a21648 | ||
|
|
a7fe00d123 | ||
|
|
e43fc721c8 | ||
|
|
c53e465130 | ||
|
|
dc2cd9f28e | ||
|
|
dfa0229a6d | ||
|
|
63dfe003b0 | ||
|
|
a47db19ae7 | ||
|
|
65def9d20d | ||
|
|
a302929966 | ||
|
|
f735d86064 | ||
|
|
66c5de2bfa | ||
|
|
6cf1ac7295 | ||
|
|
25bf4b275a | ||
|
|
103f028d99 | ||
|
|
2101d0fff5 | ||
|
|
09f50ac80f | ||
|
|
24fde7aec2 | ||
|
|
d03bdf0cf9 | ||
|
|
12986990ae | ||
|
|
325e2ed6d3 | ||
|
|
e7c11da52b | ||
|
|
5712e19804 | ||
|
|
4b549763e5 | ||
|
|
24151d27f7 | ||
|
|
f3cc8cba0a | ||
|
|
57e7d68092 | ||
|
|
d3622f7bb3 | ||
|
|
20c821e2eb | ||
|
|
7b82ced5e6 |
@@ -556,51 +556,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "GkhnGRBZ",
|
||||
"name": "GkhnGRBZ",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/127258824?v=4",
|
||||
"profile": "https://github.com/GkhnGRBZ",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "benhaney",
|
||||
"name": "Ben Haney",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/31331498?v=4",
|
||||
"profile": "http://benhaney.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Wunderharke",
|
||||
"name": "Wunderharke",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5105672?v=4",
|
||||
"profile": "https://github.com/Wunderharke",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "methbkts",
|
||||
"name": "Metin Bektas",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30674934?v=4",
|
||||
"profile": "https://github.com/methbkts",
|
||||
"contributions": [
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andrewkolda",
|
||||
"name": "andrewkolda",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/158614532?v=4",
|
||||
"profile": "https://github.com/andrewkolda",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -63,8 +63,6 @@ body:
|
||||
- PostgreSQL
|
||||
label: Database
|
||||
description: Which database backend are you using?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: device
|
||||
attributes:
|
||||
|
||||
87
.github/workflows/ci.yml
vendored
87
.github/workflows/ci.yml
vendored
@@ -12,8 +12,8 @@ jobs:
|
||||
test:
|
||||
name: Lint & Test Build
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-24.04
|
||||
container: node:22-alpine
|
||||
runs-on: ubuntu-22.04
|
||||
container: node:20-alpine
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -43,23 +43,15 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
build:
|
||||
build_and_push:
|
||||
name: Build & Publish Docker Images
|
||||
if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
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 }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to Docker Hub
|
||||
@@ -78,77 +70,24 @@ jobs:
|
||||
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
|
||||
env:
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
- 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
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
COMMIT_TAG=${{ github.sha }}
|
||||
outputs: |
|
||||
type=image,push-by-digest=true,name=fallenbagel/jellyseerr,push=true
|
||||
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
|
||||
tags: |
|
||||
fallenbagel/jellyseerr:develop
|
||||
ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop
|
||||
|
||||
discord:
|
||||
name: Send Discord Notification
|
||||
needs: merge_and_push
|
||||
needs: build_and_push
|
||||
if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
uses: technote-space/workflow-conclusion-action@v3
|
||||
|
||||
2
.github/workflows/cypress.yml
vendored
2
.github/workflows/cypress.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
- name: Pnpm Setup
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
||||
135
.github/workflows/helm.yml
vendored
135
.github/workflows/helm.yml
vendored
@@ -1,135 +0,0 @@
|
||||
name: Release Charts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
package-helm-chart:
|
||||
name: Package helm chart
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
outputs:
|
||||
has_artifacts: ${{ steps.check-artifacts.outputs.has_artifacts }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Install Oras
|
||||
uses: oras-project/setup-oras@v1
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Package helm charts
|
||||
run: |
|
||||
mkdir -p ./.cr-release-packages
|
||||
for chart_path in ./charts/*; do
|
||||
if [ -d "$chart_path" ] && [ -f "$chart_path/Chart.yaml" ]; then
|
||||
chart_name=$(grep '^name:' "$chart_path/Chart.yaml" | awk '{print $2}')
|
||||
# get current version
|
||||
current_version=$(grep '^version:' "$chart_path/Chart.yaml" | awk '{print $2}')
|
||||
# try to get current release version
|
||||
set +e
|
||||
oras discover ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:${current_version}
|
||||
oras_exit_code=$?
|
||||
set -e
|
||||
|
||||
if [ $oras_exit_code -ne 0 ]; then
|
||||
helm dependency build "$chart_path"
|
||||
helm package "$chart_path" --destination ./.cr-release-packages
|
||||
else
|
||||
echo "No version change for $chart_name. Skipping."
|
||||
fi
|
||||
else
|
||||
echo "Skipping $chart_name: Not a valid Helm chart"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Check if artifacts exist
|
||||
id: check-artifacts
|
||||
run: |
|
||||
if ls .cr-release-packages/* >/dev/null 2>&1; then
|
||||
echo "has_artifacts=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_artifacts=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
if: steps.check-artifacts.outputs.has_artifacts == 'true'
|
||||
with:
|
||||
name: artifacts
|
||||
include-hidden-files: true
|
||||
path: .cr-release-packages/
|
||||
|
||||
publish:
|
||||
name: Publish to ghcr.io
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write # needed for pushing to github registry
|
||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||
needs: [package-helm-chart]
|
||||
if: needs.package-helm-chart.outputs.has_artifacts == 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Install Oras
|
||||
uses: oras-project/setup-oras@v1
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3
|
||||
|
||||
- name: Downloads artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifacts
|
||||
path: .cr-release-packages/
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push charts to GHCR
|
||||
env:
|
||||
COSIGN_YES: true
|
||||
run: |
|
||||
for chart_path in `find .cr-release-packages -name '*.tgz' -print`; do
|
||||
# push chart to OCI
|
||||
chart_release_file=$(basename "$chart_path")
|
||||
chart_name=${chart_release_file%-*}
|
||||
helm push ${chart_path} oci://ghcr.io/${GITHUB_REPOSITORY@L} |& tee helm-push-output.log
|
||||
chart_digest=$(awk -F "[, ]+" '/Digest/{print $NF}' < helm-push-output.log)
|
||||
# sign chart
|
||||
cosign sign "ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}@${chart_digest}"
|
||||
# push artifacthub-repo.yml to OCI
|
||||
oras push \
|
||||
ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:artifacthub.io \
|
||||
--config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \
|
||||
charts/$chart_name/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml \
|
||||
|& tee oras-push-output.log
|
||||
artifacthub_digest=$(grep "Digest:" oras-push-output.log | awk '{print $2}')
|
||||
# sign artifacthub-repo.yml
|
||||
cosign sign "ghcr.io/${GITHUB_REPOSITORY@L}/${chart_name}:artifacthub.io@${artifacthub_digest}"
|
||||
done
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
@@ -26,12 +26,6 @@ jobs:
|
||||
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.GH_TOKEN }}
|
||||
- name: Pnpm Setup
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
||||
503
CHANGELOG.md
503
CHANGELOG.md
@@ -1,3 +1,506 @@
|
||||
## [2.2.2](https://github.com/fallenbagel/jellyseerr/compare/v2.2.1...v2.2.2) (2024-12-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overriderules:** apply override rules to tv shows during request ([#1198](https://github.com/fallenbagel/jellyseerr/issues/1198)) ([f8a8ebd](https://github.com/fallenbagel/jellyseerr/commit/f8a8ebdf76f939ccc28ce7b39343e3a606c90b33)), closes [#1197](https://github.com/fallenbagel/jellyseerr/issues/1197) [#1195](https://github.com/fallenbagel/jellyseerr/issues/1195)
|
||||
|
||||
## [2.2.1](https://github.com/fallenbagel/jellyseerr/compare/v2.2.0...v2.2.1) (2024-12-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overriderules:** apply override rules during request only for non-admin/non-auto-approve users ([#1197](https://github.com/fallenbagel/jellyseerr/issues/1197)) ([8da4870](https://github.com/fallenbagel/jellyseerr/commit/8da48709977fa0111225c3519f9128bea41867fc)), closes [#1195](https://github.com/fallenbagel/jellyseerr/issues/1195)
|
||||
|
||||
# [2.2.0](https://github.com/fallenbagel/jellyseerr/compare/v2.1.0...v2.2.0) (2024-12-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **avatarproxy:** add support for Emby avatars ([#1128](https://github.com/fallenbagel/jellyseerr/issues/1128)) ([17418f8](https://github.com/fallenbagel/jellyseerr/commit/17418f82af53362338aebe9602373a3c8fa027f7)), closes [#1101](https://github.com/fallenbagel/jellyseerr/issues/1101)
|
||||
* **blacklist:** remove a "undefined" appearing when the blacklist modal closes ([#1142](https://github.com/fallenbagel/jellyseerr/issues/1142)) ([b01f98f](https://github.com/fallenbagel/jellyseerr/commit/b01f98f7e280a037eba303eeaa836f6623daa440))
|
||||
* **discover:** display recent requests even if there is an error with *arr ([#1141](https://github.com/fallenbagel/jellyseerr/issues/1141)) ([fa443c0](https://github.com/fallenbagel/jellyseerr/commit/fa443c05bedfca8208bfb05ab02c3b0e678e4ca0))
|
||||
* **discover:** resolve a typing issue with the WatchlistItem interface ([#1156](https://github.com/fallenbagel/jellyseerr/issues/1156)) ([de6e591](https://github.com/fallenbagel/jellyseerr/commit/de6e591baedacb33704216842dddaa2b96bfae19))
|
||||
* **emby:** change default value of Accept-Encoding header ([#1157](https://github.com/fallenbagel/jellyseerr/issues/1157)) ([7c734bc](https://github.com/fallenbagel/jellyseerr/commit/7c734bc8732a511e62edfcc371028ead6b6f1b12))
|
||||
* fix PostgreSQL migrations and TelegramMessageThreadId migration ([#1171](https://github.com/fallenbagel/jellyseerr/issues/1171)) ([0491a04](https://github.com/fallenbagel/jellyseerr/commit/0491a04ef1816e81bb495746cc529fc621e4e147))
|
||||
* handle non-existent rottentomatoes rating for movies ([#1169](https://github.com/fallenbagel/jellyseerr/issues/1169)) ([347a24a](https://github.com/fallenbagel/jellyseerr/commit/347a24a97b354725c4ccb3b5a07793b96ff60b80))
|
||||
* remove non-null requirement for some fields ([#1175](https://github.com/fallenbagel/jellyseerr/issues/1175)) ([13d15d1](https://github.com/fallenbagel/jellyseerr/commit/13d15d1dcf4a80bc0b544fecbeced706f2dbd816)), closes [#628](https://github.com/fallenbagel/jellyseerr/issues/628)
|
||||
* **requestlist:** use default value of sort direction only if valid ([#1174](https://github.com/fallenbagel/jellyseerr/issues/1174)) ([59c22cc](https://github.com/fallenbagel/jellyseerr/commit/59c22ccc089c960b523ccfb69efc680b2687c353)), closes [#1147](https://github.com/fallenbagel/jellyseerr/issues/1147)
|
||||
* **server/settings:** write settings to a temp file then move to avoid corruption ([#1067](https://github.com/fallenbagel/jellyseerr/issues/1067)) ([01bbece](https://github.com/fallenbagel/jellyseerr/commit/01bbeced65b82f5041462cd7a6c9016274acade4))
|
||||
* **ui:** allow thetvdb images for unmatched series ([#1105](https://github.com/fallenbagel/jellyseerr/issues/1105)) ([9b151fe](https://github.com/fallenbagel/jellyseerr/commit/9b151feb4f44d631b44c88c089f184c4c93161c5)), closes [#1075](https://github.com/fallenbagel/jellyseerr/issues/1075)
|
||||
* **ui:** display Rotten Tomatoes for 0% ratings ([#1178](https://github.com/fallenbagel/jellyseerr/issues/1178)) ([5345207](https://github.com/fallenbagel/jellyseerr/commit/534520794071d8530d6325460e61dabfcb46fbf0)), closes [#1166](https://github.com/fallenbagel/jellyseerr/issues/1166)
|
||||
* **ui:** resize streaming service logos ([#1106](https://github.com/fallenbagel/jellyseerr/issues/1106)) ([fe5d016](https://github.com/fallenbagel/jellyseerr/commit/fe5d016929d18c38aef7a3d48e4828188131e025)), closes [#1103](https://github.com/fallenbagel/jellyseerr/issues/1103)
|
||||
* use less strict validation for external URLs ([#1104](https://github.com/fallenbagel/jellyseerr/issues/1104)) ([14f316a](https://github.com/fallenbagel/jellyseerr/commit/14f316a9a6d91c25c43e07ae66923785f90b1fdf)), closes [#1068](https://github.com/fallenbagel/jellyseerr/issues/1068)
|
||||
* use links instead of buttons for external links in movie/tv details page ([#923](https://github.com/fallenbagel/jellyseerr/issues/923)) ([5776715](https://github.com/fallenbagel/jellyseerr/commit/57767156f79cb0bcb761f6fc0907d747f126e146))
|
||||
* use tmdb first as metadata provider and fallback to tvdb ([#1138](https://github.com/fallenbagel/jellyseerr/issues/1138)) ([84fd884](https://github.com/fallenbagel/jellyseerr/commit/84fd884052ea2177c92d144367c4b4ed1dde3b73)), closes [#1137](https://github.com/fallenbagel/jellyseerr/issues/1137)
|
||||
* **usediscover hook:** fixing duplicate movies ([#708](https://github.com/fallenbagel/jellyseerr/issues/708)) ([39dbb7f](https://github.com/fallenbagel/jellyseerr/commit/39dbb7f7e59cf4b1b5f029089c6b1ea6a0d7e5f5))
|
||||
* **usersettings:** allow unset email and add more explicit email error message ([#1096](https://github.com/fallenbagel/jellyseerr/issues/1096)) ([39a5ccb](https://github.com/fallenbagel/jellyseerr/commit/39a5ccb7f3a6ed4e93b12e11021bb30515936ce7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a setting for special episodes ([#1193](https://github.com/fallenbagel/jellyseerr/issues/1193)) ([b6e2e6c](https://github.com/fallenbagel/jellyseerr/commit/b6e2e6ce615cb94cea8d2335140fe245a0ca2d8a))
|
||||
* add postgres support + migrations ([#628](https://github.com/fallenbagel/jellyseerr/issues/628)) ([44a9221](https://github.com/fallenbagel/jellyseerr/commit/44a9221a9dca501fa57c0bcbd743aed9889059ff)), closes [#186](https://github.com/fallenbagel/jellyseerr/issues/186)
|
||||
* **helm:** add base helm chart ([#1116](https://github.com/fallenbagel/jellyseerr/issues/1116)) ([27e3d46](https://github.com/fallenbagel/jellyseerr/commit/27e3d465bd7eaa3f382c961220f8af1860a15c7f))
|
||||
* **notifications:** added telegram thread id's ([#1145](https://github.com/fallenbagel/jellyseerr/issues/1145)) ([d76d794](https://github.com/fallenbagel/jellyseerr/commit/d76d79441142ccc6fe2357549f39a1fba3546ff9))
|
||||
* **notifications:** improve discord notifications ([#1102](https://github.com/fallenbagel/jellyseerr/issues/1102)) ([5c24e79](https://github.com/fallenbagel/jellyseerr/commit/5c24e79b1dddc3c8421e57e67302fa3dc064f87f))
|
||||
* override rules ([#945](https://github.com/fallenbagel/jellyseerr/issues/945)) ([9a59529](https://github.com/fallenbagel/jellyseerr/commit/9a595296dbdd00bb3477052b53412e6019667740))
|
||||
* **requestlist:** sort direction ([#1147](https://github.com/fallenbagel/jellyseerr/issues/1147)) ([66a5ab4](https://github.com/fallenbagel/jellyseerr/commit/66a5ab41ab646501f72a658782e8a89f9faf939f))
|
||||
* **usersettings:** add separate setting for streaming region ([#993](https://github.com/fallenbagel/jellyseerr/issues/993)) ([89831f7](https://github.com/fallenbagel/jellyseerr/commit/89831f70909df0a76dfa8a027702e4e5f9b57be8)), closes [#890](https://github.com/fallenbagel/jellyseerr/issues/890)
|
||||
|
||||
# [2.1.0](https://github.com/fallenbagel/jellyseerr/compare/v2.0.1...v2.1.0) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **blacklist:** request data only when modal is shown, remove useless ratelimit and lazy load blacklist ([#1084](https://github.com/fallenbagel/jellyseerr/issues/1084)) ([694913c](https://github.com/fallenbagel/jellyseerr/commit/694913c767c558147f413e2375b2512567541127))
|
||||
* cache Jellyfin/Emby avatars from API ([#1045](https://github.com/fallenbagel/jellyseerr/issues/1045)) ([0bbcfcb](https://github.com/fallenbagel/jellyseerr/commit/0bbcfcbd5e03137aba35ceb07e42f623aefa41d7))
|
||||
* **externalapi:** extract basic auth and pass it through header ([#1062](https://github.com/fallenbagel/jellyseerr/issues/1062)) ([cf59102](https://github.com/fallenbagel/jellyseerr/commit/cf59102ef91fa0e907cc6369b0fe60b503c823ca)), closes [#1027](https://github.com/fallenbagel/jellyseerr/issues/1027)
|
||||
* fixes wrong avatar rendered for the modifiedBy user in request list ([#1028](https://github.com/fallenbagel/jellyseerr/issues/1028)) ([cbb1a74](https://github.com/fallenbagel/jellyseerr/commit/cbb1a74526ef5c003b7081c31146c52e7e551d60)), closes [#1017](https://github.com/fallenbagel/jellyseerr/issues/1017)
|
||||
* **i18n:** update extractMessages function for better escaping of characters ([#1079](https://github.com/fallenbagel/jellyseerr/issues/1079)) ([a2d2fd3](https://github.com/fallenbagel/jellyseerr/commit/a2d2fd3c2a53fc98d6288bd049fd8e37a1914280))
|
||||
* remove language profiles dropdown for Sonarr v4 ([#1000](https://github.com/fallenbagel/jellyseerr/issues/1000)) ([d331798](https://github.com/fallenbagel/jellyseerr/commit/d331798b28a7bd32a27fc0ccbad2354be2e15b02)), closes [#207](https://github.com/fallenbagel/jellyseerr/issues/207)
|
||||
* resolve error when setup on second attempt ([#1061](https://github.com/fallenbagel/jellyseerr/issues/1061)) ([64f4610](https://github.com/fallenbagel/jellyseerr/commit/64f4610b9ffcad01c24ecdd81b8b3a2f3db4c98d))
|
||||
* **setup:** add leading slash validation for baseUrl ([#1083](https://github.com/fallenbagel/jellyseerr/issues/1083)) ([2829c25](https://github.com/fallenbagel/jellyseerr/commit/2829c2548aa0cd03f92433d3bc3b9b2739e98486))
|
||||
* update i18n translations ([#1090](https://github.com/fallenbagel/jellyseerr/issues/1090)) ([f25b32a](https://github.com/fallenbagel/jellyseerr/commit/f25b32aec8ec3c2fd40ccfc6a83f18ddc99c1a15))
|
||||
* use fs/promises for settings ([#1057](https://github.com/fallenbagel/jellyseerr/issues/1057)) ([f2ed101](https://github.com/fallenbagel/jellyseerr/commit/f2ed101e522561dab8563b744d908ff036c957c5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a warning if permissions are missing from config folder ([#1030](https://github.com/fallenbagel/jellyseerr/issues/1030)) ([f2b6315](https://github.com/fallenbagel/jellyseerr/commit/f2b63156d1d4aa903eb261d2c80c059c39d9091b))
|
||||
* add bypass list, bypass local addresses and username/password to proxy setting ([#1059](https://github.com/fallenbagel/jellyseerr/issues/1059)) ([ca838a0](https://github.com/fallenbagel/jellyseerr/commit/ca838a00fa4acb0ccdfbac8be4cf7fde493346f7))
|
||||
* add more logs to migrations and create a settings backup ([#1036](https://github.com/fallenbagel/jellyseerr/issues/1036)) ([326001c](https://github.com/fallenbagel/jellyseerr/commit/326001c3ecc92dc730f327130a71e797882a62b9))
|
||||
* exit Jellyseerr when migration fails ([#1026](https://github.com/fallenbagel/jellyseerr/issues/1026)) ([a2b3408](https://github.com/fallenbagel/jellyseerr/commit/a2b3408c9aa5e22e1193f535c969325254f08193))
|
||||
* proxy setting ([#1031](https://github.com/fallenbagel/jellyseerr/issues/1031)) ([4b4eeb6](https://github.com/fallenbagel/jellyseerr/commit/4b4eeb6ec707e0971fe8745910edbfb546bf25fe))
|
||||
|
||||
## [2.0.1](https://github.com/fallenbagel/jellyseerr/compare/v2.0.0...v2.0.1) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fetch override to attach XSRF token to fix csrfProtection issue ([#1014](https://github.com/fallenbagel/jellyseerr/issues/1014)) ([4945b54](https://github.com/fallenbagel/jellyseerr/commit/4945b5429848b36fc0ee41cf0277ed79f53d8286)), closes [#1011](https://github.com/fallenbagel/jellyseerr/issues/1011)
|
||||
* handle non-existent rottentomatoes rating ([#1018](https://github.com/fallenbagel/jellyseerr/issues/1018)) ([a351264](https://github.com/fallenbagel/jellyseerr/commit/a351264b878b2660ae7a6415f26d38b52015c591))
|
||||
* rewrite avatarproxy and CachedImage ([#1016](https://github.com/fallenbagel/jellyseerr/issues/1016)) ([4e48fdf](https://github.com/fallenbagel/jellyseerr/commit/4e48fdf2cb9f76ae5c25073b585718650abd3288)), closes [#1012](https://github.com/fallenbagel/jellyseerr/issues/1012) [#1013](https://github.com/fallenbagel/jellyseerr/issues/1013)
|
||||
* use jellyfinMediaId4k for mediaUrl4k ([#1006](https://github.com/fallenbagel/jellyseerr/issues/1006)) ([a0f80fe](https://github.com/fallenbagel/jellyseerr/commit/a0f80fe7647ef4a9025ca93407cd21ddc640fed1)), closes [#520](https://github.com/fallenbagel/jellyseerr/issues/520)
|
||||
|
||||
# [2.0.0](https://github.com/fallenbagel/jellyseerr/compare/v1.9.2...v2.0.0) (2024-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* abort availability sync job if auth token invalid/connection lost ([#845](https://github.com/fallenbagel/jellyseerr/issues/845)) ([bdee340](https://github.com/fallenbagel/jellyseerr/commit/bdee34053080c8975a88ba16a9e8f402e10fe7e1))
|
||||
* add an error message to say when an email is already taken ([#947](https://github.com/fallenbagel/jellyseerr/issues/947)) ([89e0a83](https://github.com/fallenbagel/jellyseerr/commit/89e0a831ec85a6905f539f59b7523bb1feb90bcf))
|
||||
* add missing brackets ([#888](https://github.com/fallenbagel/jellyseerr/issues/888)) ([6cea8bb](https://github.com/fallenbagel/jellyseerr/commit/6cea8bba592b8db566b4d8147630385f5c377f1b))
|
||||
* add missing content-type header ([#887](https://github.com/fallenbagel/jellyseerr/issues/887)) ([2be9c7d](https://github.com/fallenbagel/jellyseerr/commit/2be9c7dcc1f418726a19e99cfdb3933257a03c6f))
|
||||
* add missing header when creating an issue ([#879](https://github.com/fallenbagel/jellyseerr/issues/879)) ([084e1b2](https://github.com/fallenbagel/jellyseerr/commit/084e1b224e109f0f8279741b9a5ead138396d7f8))
|
||||
* add missing parameter to delete requests from ExternalAPI ([#904](https://github.com/fallenbagel/jellyseerr/issues/904)) ([36d98a2](https://github.com/fallenbagel/jellyseerr/commit/36d98a2681921a8770027b78878688f2782e8b77)), closes [#903](https://github.com/fallenbagel/jellyseerr/issues/903)
|
||||
* **api:** fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([0116c13](https://github.com/fallenbagel/jellyseerr/commit/0116c13e0632d1ccec43299fbb10cd71db45bc29))
|
||||
* **api:** handle non-existent ratings on IMDb ([#822](https://github.com/fallenbagel/jellyseerr/issues/822)) ([74a2d25](https://github.com/fallenbagel/jellyseerr/commit/74a2d25f153b07a0cae5b44adca5fa1fed5a3b9e))
|
||||
* **api:** save new password when reset password of local account ([#886](https://github.com/fallenbagel/jellyseerr/issues/886)) ([5cc4389](https://github.com/fallenbagel/jellyseerr/commit/5cc43898256b130c2576f34a3d4e7ce6a3940d3e))
|
||||
* **blacklist:** add blacklist to mobile menu ([#980](https://github.com/fallenbagel/jellyseerr/issues/980)) ([f390da4](https://github.com/fallenbagel/jellyseerr/commit/f390da486625a22951956ba96867de63f73bfc2b)), closes [#979](https://github.com/fallenbagel/jellyseerr/issues/979)
|
||||
* change SeriesSearch to MissingEpisodeSearch for season requests ([#711](https://github.com/fallenbagel/jellyseerr/issues/711)) ([ee7e91c](https://github.com/fallenbagel/jellyseerr/commit/ee7e91c7c948b17b556a625919eb1252a721bb6e))
|
||||
* **docker:** add postinstall script ([#839](https://github.com/fallenbagel/jellyseerr/issues/839)) ([f714132](https://github.com/fallenbagel/jellyseerr/commit/f7141329094d88eb0940b1db1f21376142cb8893))
|
||||
* enhance error messages when Fetch API fails ([#893](https://github.com/fallenbagel/jellyseerr/issues/893)) ([fccfca6](https://github.com/fallenbagel/jellyseerr/commit/fccfca6ed06c8dc599e1ea4b1b3dbac48eb3a7f6))
|
||||
* handle status badge for season packs ([#927](https://github.com/fallenbagel/jellyseerr/issues/927)) ([80f6301](https://github.com/fallenbagel/jellyseerr/commit/80f63017ac5e9b1720a19c761dbef4dd517f1c2c))
|
||||
* length of undefined on users warnings ([#875](https://github.com/fallenbagel/jellyseerr/issues/875)) ([c600566](https://github.com/fallenbagel/jellyseerr/commit/c600566ac0045c2314f9013b063007b087ee4327))
|
||||
* remove DNS caching ([#837](https://github.com/fallenbagel/jellyseerr/issues/837)) ([268c7df](https://github.com/fallenbagel/jellyseerr/commit/268c7df28eea8b911d6a53297f5ce296983067ce))
|
||||
* remove email requirement for the user, and use the username if no email provided ([#900](https://github.com/fallenbagel/jellyseerr/issues/900)) ([d5f817e](https://github.com/fallenbagel/jellyseerr/commit/d5f817e734131cdacc229361d9498a095af57950))
|
||||
* remove protocol-relative URLs from next/image ([#889](https://github.com/fallenbagel/jellyseerr/issues/889)) ([c80d9a8](https://github.com/fallenbagel/jellyseerr/commit/c80d9a853a2a3451293a5382ef183c18add0c040))
|
||||
* resize episode preview image ([#842](https://github.com/fallenbagel/jellyseerr/issues/842)) ([96ba53f](https://github.com/fallenbagel/jellyseerr/commit/96ba53fecc7b9d269f0d974051ab62836b0102bc))
|
||||
* resize header image in network and studio pages ([#902](https://github.com/fallenbagel/jellyseerr/issues/902)) ([4220855](https://github.com/fallenbagel/jellyseerr/commit/422085523e5dfc132f3c3ca19eaa87117828b7be))
|
||||
* rewrite request from axios to Fetch ([#920](https://github.com/fallenbagel/jellyseerr/issues/920)) ([9aee888](https://github.com/fallenbagel/jellyseerr/commit/9aee8887d3cca6e018f4be1c8400c22e86bf8dab))
|
||||
* rewrite the rate limit utility ([#896](https://github.com/fallenbagel/jellyseerr/issues/896)) ([3fc14c9](https://github.com/fallenbagel/jellyseerr/commit/3fc14c9e2262463afec666e7f54e38d0d36cff68))
|
||||
* **session:** set the correct TTL for the cookie store ([#992](https://github.com/fallenbagel/jellyseerr/issues/992)) ([96e1d40](https://github.com/fallenbagel/jellyseerr/commit/96e1d40304749ce00d2ff7359efc39a1d9724358)), closes [#991](https://github.com/fallenbagel/jellyseerr/issues/991)
|
||||
* set correct user type when importing from emby ([#949](https://github.com/fallenbagel/jellyseerr/issues/949)) ([e57d265](https://github.com/fallenbagel/jellyseerr/commit/e57d2654d1c634a91649722d3a2bf4d73c4a02ca)), closes [#948](https://github.com/fallenbagel/jellyseerr/issues/948)
|
||||
* **setup:** page display when homepage is loading ([#940](https://github.com/fallenbagel/jellyseerr/issues/940)) ([7423bbb](https://github.com/fallenbagel/jellyseerr/commit/7423bbbffc5bee2e52e3348254f035dc8527d973))
|
||||
* **tmdb:** fallback movie/show overview to English when none is available in requested locale ([#928](https://github.com/fallenbagel/jellyseerr/issues/928)) ([12f908d](https://github.com/fallenbagel/jellyseerr/commit/12f908de7f5fbd717a5f151858b6edee3be13ed9)), closes [#925](https://github.com/fallenbagel/jellyseerr/issues/925)
|
||||
* update the filter removing existing users from Jellyfin import modal ([#924](https://github.com/fallenbagel/jellyseerr/issues/924)) ([61dcd8e](https://github.com/fallenbagel/jellyseerr/commit/61dcd8e487d7886773ccb12501623c17838476e5))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **jellyfin:** abstract jellyfin hostname, updated ui to reflect it, better validation ([#773](https://github.com/fallenbagel/jellyseerr/issues/773)) ([38ad875](https://github.com/fallenbagel/jellyseerr/commit/38ad875dd7848b4e92ac3ccdd16dbf785f6a5c4d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add environment variable for API key ([#831](https://github.com/fallenbagel/jellyseerr/issues/831)) ([45ef150](https://github.com/fallenbagel/jellyseerr/commit/45ef150e36944d456cc9440574b5ac75f2e4bbc1))
|
||||
* adds status filter for tv shows ([#796](https://github.com/fallenbagel/jellyseerr/issues/796)) ([cfd1bc2](https://github.com/fallenbagel/jellyseerr/commit/cfd1bc253557d6e19725743b8aa9a2fa33bbe760)), closes [#605](https://github.com/fallenbagel/jellyseerr/issues/605)
|
||||
* allow request managers to delete data from sonarr/radarr ([#644](https://github.com/fallenbagel/jellyseerr/issues/644)) ([a5d22ba](https://github.com/fallenbagel/jellyseerr/commit/a5d22ba5b83dd0e812b16f06476d993b5d59cb2a))
|
||||
* blacklist items from Discover page ([#632](https://github.com/fallenbagel/jellyseerr/issues/632)) ([818aa60](https://github.com/fallenbagel/jellyseerr/commit/818aa60aac185da07bfb71b08e0448939b63a736)), closes [#490](https://github.com/fallenbagel/jellyseerr/issues/490)
|
||||
* Jellyfin/Emby server type setup ([#685](https://github.com/fallenbagel/jellyseerr/issues/685)) ([15cb949](https://github.com/fallenbagel/jellyseerr/commit/15cb949f1f2e617853f90ae7bb8ae5d6622f610e))
|
||||
* **jellyfinapi:** switch to API tokens instead of auth tokens ([#868](https://github.com/fallenbagel/jellyseerr/issues/868)) ([bd4da6d](https://github.com/fallenbagel/jellyseerr/commit/bd4da6d5fc8cb55c2bc3d9a8336787cbd30814d0))
|
||||
* Option on item's page to add/remove from watchlist ([#781](https://github.com/fallenbagel/jellyseerr/issues/781)) ([2348f23](https://github.com/fallenbagel/jellyseerr/commit/2348f23f433195d64dee3e6eeede296fca5fdbc9)), closes [#730](https://github.com/fallenbagel/jellyseerr/issues/730)
|
||||
* refresh monitored downloads before getting queue items ([#994](https://github.com/fallenbagel/jellyseerr/issues/994)) ([92ba262](https://github.com/fallenbagel/jellyseerr/commit/92ba26207dcb1ddd696e0f01931d2609c521ae45)), closes [#866](https://github.com/fallenbagel/jellyseerr/issues/866)
|
||||
* show quality profile on request ([#847](https://github.com/fallenbagel/jellyseerr/issues/847)) ([6445332](https://github.com/fallenbagel/jellyseerr/commit/64453320d36595e75dcb710dfd43997bf2d2acd5))
|
||||
* **translation:** added full Hebrew translation ([#871](https://github.com/fallenbagel/jellyseerr/issues/871)) ([c96ca67](https://github.com/fallenbagel/jellyseerr/commit/c96ca6742e0a6d5685319c52f995fe06e439a450))
|
||||
* update Plex logo ([#884](https://github.com/fallenbagel/jellyseerr/issues/884)) ([3a363ae](https://github.com/fallenbagel/jellyseerr/commit/3a363ae1ffa7f384be6f7d25f8558b1e55a73fb3))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix(api): fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([#892](https://github.com/fallenbagel/jellyseerr/issues/892)) ([62dbde4](https://github.com/fallenbagel/jellyseerr/commit/62dbde448c7f7d530de8534bb8538452d0f91276))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
|
||||
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
|
||||
variable users can log out and log back in to set the mediaServerType to `3` (Emby).
|
||||
|
||||
* feat(api): add severType to the api
|
||||
* This adds a serverType to the `/auth/jellyfin` which requires a serverType to be
|
||||
set (`jellyfin`/`emby`)
|
||||
|
||||
* refactor: use enums for serverType and rename selectedservice to serverType
|
||||
|
||||
* refactor(auth): jellyfin/emby authentication to set MediaServerType
|
||||
|
||||
* fix: issue page formatMessage for 4k media
|
||||
|
||||
* refactor: cleaner way of handling serverType change using MediaServerType instead of strings
|
||||
|
||||
instead of using strings now it will use MediaServerType enums for serverType
|
||||
|
||||
* revert: removed conditional render of the auto-request permission
|
||||
|
||||
reverts the conditional render toshow the auto-request permission if the mediaServerType was set to
|
||||
Plex as this should be handled in a different PR and Cypress tests should be modified
|
||||
accordingly(currently cypress test would fail if this conditional check is there)
|
||||
|
||||
* feat: add server type step to setup
|
||||
|
||||
* feat: migrate existing emby setups to use emby mediaServerType
|
||||
|
||||
* fix: scan jobs not running when media server type is emby
|
||||
|
||||
* fix: emby media server type migration
|
||||
|
||||
* refactor: change emby logo to full logo
|
||||
|
||||
* style: decrease emby logo size in setup screen
|
||||
|
||||
* refactor: use title case for servertype i18n message
|
||||
|
||||
* refactor(i18n): fix a typo
|
||||
|
||||
* refactor: use enums instead of numbers
|
||||
|
||||
* fix: remove old references to JELLYFIN_TYPE environment variable
|
||||
|
||||
* fix: go back to the last step when refresh the setup page
|
||||
|
||||
* fix: move "scanning in background" tip next to the scanning section
|
||||
|
||||
* fix: redirect the setup page when Jellyseerr is already setup
|
||||
* **jellyfin:** Jellyfin settings now does not include a hostname. Instead it abstracted it to ip,
|
||||
port, useSsl, and urlBase. However, migration of old settings to new settings should work
|
||||
automatically.
|
||||
|
||||
* refactor: remove console logs and use getHostname and ApiErrorCodes
|
||||
|
||||
* fix: store req.body jellyfin settings temporarily and store only if valid
|
||||
|
||||
This should fix the issue where settings are saved even if the url
|
||||
was invalid. Now the settings will only be saved if the url is
|
||||
valid. Sort of like a test connection.
|
||||
|
||||
* refactor: clean up commented out code
|
||||
|
||||
* refactor(i18n): extract translation keys
|
||||
|
||||
* fix(auth): auth failing with jellyfin login is disabled
|
||||
|
||||
* fix(settings): jellyfin migrations replacing the rest of the settings
|
||||
|
||||
* fix(settings): jellyfin hostname should be carried out if hostname exists
|
||||
|
||||
* fix(settings): merging the wrong settings source
|
||||
|
||||
* refactor(settings): use migrator for dynamic settings migrations
|
||||
|
||||
* refactor(settingsmigrator): settings migration handler and the migrations
|
||||
|
||||
* test(cypress): fix cypress tests failing
|
||||
|
||||
cypress settings were lacking some of the jobs so when the startJobs() is called when the app
|
||||
starts, it was failing to schedule the jobs where their cron timings were not specified in the
|
||||
cypress settings. Therefore, this commit adds those jobs back. In addition, other setting options
|
||||
were added to keep cypress settings consistent with a normal user.
|
||||
|
||||
* chore(prettierignore): ignore cypress/config/settings.cypress.json as it does not need prettier
|
||||
|
||||
* chore(prettier): ran formatter on cypress config to fix format check error
|
||||
|
||||
format check locally passes on this file. However, it fails during the github actions format check.
|
||||
Therefore, json language features formatter was run instead of prettier to see if that fixes the
|
||||
issue.
|
||||
|
||||
* test(cypress): add only missing jobs to the cypress settings
|
||||
|
||||
* ci: attempt at trying to get formatter to pass on cypress config json file
|
||||
|
||||
* refactor: revert the changes brought to try and fix formatter
|
||||
|
||||
added back the rest of the cypress settings and removed cypress settings from .prettierignore
|
||||
|
||||
* refactor(settings): better erorr logging when jellyfin connection test fails in settings page
|
||||
|
||||
## [1.9.2](https://github.com/fallenbagel/jellyseerr/compare/v1.9.1...v1.9.2) (2024-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** improve login resilience with headerless fallback authentication ([#814](https://github.com/fallenbagel/jellyseerr/issues/814)) ([a9741fa](https://github.com/fallenbagel/jellyseerr/commit/a9741fa36d06710aa00d28db3dd2c29f2b0973d3))
|
||||
* **auth:** validation of ipv6/ipv4 ([#812](https://github.com/fallenbagel/jellyseerr/issues/812)) ([9aeb360](https://github.com/fallenbagel/jellyseerr/commit/9aeb3604e6498c388df1d30dd0b613ba84160fc0)), closes [#795](https://github.com/fallenbagel/jellyseerr/issues/795)
|
||||
* bypass cache-able lookups when resolving localhost ([#813](https://github.com/fallenbagel/jellyseerr/issues/813)) ([b5a0699](https://github.com/fallenbagel/jellyseerr/commit/b5a069901a9545772deaa9c491f2075261da0189))
|
||||
|
||||
## [1.9.1](https://github.com/fallenbagel/jellyseerr/compare/v1.9.0...v1.9.1) (2024-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** add DNS caching ([#810](https://github.com/fallenbagel/jellyseerr/issues/810)) ([46ee8a4](https://github.com/fallenbagel/jellyseerr/commit/46ee8a4ca13b026bd929b4027eb001cc74064bb8)), closes [#387](https://github.com/fallenbagel/jellyseerr/issues/387) [#657](https://github.com/fallenbagel/jellyseerr/issues/657) [#728](https://github.com/fallenbagel/jellyseerr/issues/728)
|
||||
* empty email in user settings ([#807](https://github.com/fallenbagel/jellyseerr/issues/807)) ([20863d4](https://github.com/fallenbagel/jellyseerr/commit/20863d4a8dabe78fb5c52995b5bcb2da557a804e)), closes [#803](https://github.com/fallenbagel/jellyseerr/issues/803)
|
||||
* **jellyfinscanner:** assign only 4k available badge for a 4k request instead of both badges ([#805](https://github.com/fallenbagel/jellyseerr/issues/805)) ([d31a2c3](https://github.com/fallenbagel/jellyseerr/commit/d31a2c37e639c1126b446277fa5d666d8102fef5))
|
||||
* remove the settings button of media when useless ([#809](https://github.com/fallenbagel/jellyseerr/issues/809)) ([f52939e](https://github.com/fallenbagel/jellyseerr/commit/f52939e4cdcbee94fc35165f613f6b3e21599e3c))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "ci: update format check command to ignore .prettierignore files (#787)" (#788) ([4757f1c](https://github.com/fallenbagel/jellyseerr/commit/4757f1c3e599304410a737c11f97db92a2bfcefd)), closes [#787](https://github.com/fallenbagel/jellyseerr/issues/787) [#788](https://github.com/fallenbagel/jellyseerr/issues/788)
|
||||
|
||||
# [1.9.0](https://github.com/fallenbagel/jellyseerr/compare/v1.8.1...v1.9.0) (2024-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** save user email on the first try ([#760](https://github.com/fallenbagel/jellyseerr/issues/760)) ([0bbcfdc](https://github.com/fallenbagel/jellyseerr/commit/0bbcfdc4f9ff9735f45232a2412ac8444f525de9)), closes [#227](https://github.com/fallenbagel/jellyseerr/issues/227) [#748](https://github.com/fallenbagel/jellyseerr/issues/748)
|
||||
* **api:** small errors on overseerr-api.yaml ([#721](https://github.com/fallenbagel/jellyseerr/issues/721)) ([0eea109](https://github.com/fallenbagel/jellyseerr/commit/0eea1090dfdba4333646280c84b09b0197fefa74))
|
||||
* **auth:** case-sensitive logins not updating authtokens ([#778](https://github.com/fallenbagel/jellyseerr/issues/778)) ([2bd125d](https://github.com/fallenbagel/jellyseerr/commit/2bd125d9a55d15a398ceb5f2996105a5e861b6e0))
|
||||
* **jellyfinapi:** use external api class for jellyfin api requests ([#762](https://github.com/fallenbagel/jellyseerr/issues/762)) ([650c339](https://github.com/fallenbagel/jellyseerr/commit/650c339d74d4fe85ef7f76184901e86f4eeada85)), closes [#728](https://github.com/fallenbagel/jellyseerr/issues/728) [#387](https://github.com/fallenbagel/jellyseerr/issues/387)
|
||||
* **logging:** handle media server connection refused error/toast ([#748](https://github.com/fallenbagel/jellyseerr/issues/748)) ([f486fb5](https://github.com/fallenbagel/jellyseerr/commit/f486fb5e75f9ea21456952b6a52cb841e30f3556))
|
||||
* use UTF8 encoding for webhook JSON ([#714](https://github.com/fallenbagel/jellyseerr/issues/714)) ([c0a0b9c](https://github.com/fallenbagel/jellyseerr/commit/c0a0b9c8a8b0c2eeaf3fa9159f10742baa9f6c1f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Latin American Spanish translation ([#725](https://github.com/fallenbagel/jellyseerr/issues/725)) ([783fda9](https://github.com/fallenbagel/jellyseerr/commit/783fda9621aef8ffd46e5f036136de82ed502ccc)), closes [#677](https://github.com/fallenbagel/jellyseerr/issues/677)
|
||||
* add merge conflict labeler workflow ([#719](https://github.com/fallenbagel/jellyseerr/issues/719)) ([d9d07c7](https://github.com/fallenbagel/jellyseerr/commit/d9d07c705a24d5c49905066aac45a3c6a2e36a53))
|
||||
* **auth:** send real information on login ([#470](https://github.com/fallenbagel/jellyseerr/issues/470)) ([d765055](https://github.com/fallenbagel/jellyseerr/commit/d765055da83ee94546399f6348aee14d8427d462))
|
||||
* **settings:** stores jellyfin/emby server name in the settings ([#763](https://github.com/fallenbagel/jellyseerr/issues/763)) ([7a5e8d6](https://github.com/fallenbagel/jellyseerr/commit/7a5e8d69bf620c8e7bf5f284840b1a5fe757ae5f))
|
||||
|
||||
## [1.8.1](https://github.com/fallenbagel/jellyseerr/compare/v1.8.0...v1.8.1) (2024-04-17)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix: disable seasonfolder option in sonarr for jellyfin/Emby users" (#718) ([cd0fa3e](https://github.com/fallenbagel/jellyseerr/commit/cd0fa3e2232dcb522673143f113fc382fb2ff0a3)), closes [#718](https://github.com/fallenbagel/jellyseerr/issues/718)
|
||||
|
||||
# [1.8.0](https://github.com/fallenbagel/jellyseerr/compare/v1.7.0...v1.8.0) (2024-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct width issue in datepicker of filterSliderOver ([f564cdd](https://github.com/fallenbagel/jellyseerr/commit/f564cddff4525ccebffbf304672d49c57aefe635)), closes [#415](https://github.com/fallenbagel/jellyseerr/issues/415)
|
||||
* disable seasonfolder option in sonarr for jellyfin/Emby users ([8ec8f2a](https://github.com/fallenbagel/jellyseerr/commit/8ec8f2ac5730aad3b12dcd8ed95bb553b46b399c)), closes [#126](https://github.com/fallenbagel/jellyseerr/issues/126) [#575](https://github.com/fallenbagel/jellyseerr/issues/575)
|
||||
* **embyauth:** remove the accidentally added mediaServerType change code from another PR ([#684](https://github.com/fallenbagel/jellyseerr/issues/684)) ([c2e8771](https://github.com/fallenbagel/jellyseerr/commit/c2e87714b4c4aa11bf68dcd82b76979f82990f3c))
|
||||
* ensure watchlist updates are immediately reflected ([b85d7f3](https://github.com/fallenbagel/jellyseerr/commit/b85d7f37b931735ca2ad955dccb6599bf445fc73))
|
||||
* fix german translation for "components.Discover.FilterSlideover.tmdbuservotecount" ([e032c02](https://github.com/fallenbagel/jellyseerr/commit/e032c02f5f84dc4b6b470eecb18ba2c376c55f37))
|
||||
* fix the translations for watchlist permissions and userSettings page ([8c82a61](https://github.com/fallenbagel/jellyseerr/commit/8c82a61450a7525c0e2f1b64e6939da47a7c715d))
|
||||
* **i18n:** fixed jellyfin jobs ([7eed236](https://github.com/fallenbagel/jellyseerr/commit/7eed23637ddfb10bdcb19698e7ae171f07299502))
|
||||
* **jellyfin.ts:** process virtual seasons if they have non virtual episodes ([#639](https://github.com/fallenbagel/jellyseerr/issues/639)) ([db84f65](https://github.com/fallenbagel/jellyseerr/commit/db84f6529ab285be26c96daaab065dfabf347417))
|
||||
* **jellyfinapi:** refactors jellyfin library sync to support automatic grouping and collections ([#700](https://github.com/fallenbagel/jellyseerr/issues/700)) ([3856061](https://github.com/fallenbagel/jellyseerr/commit/3856061fe1ee4d3457996586b4979ad9dd60765a)), closes [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#256](https://github.com/fallenbagel/jellyseerr/issues/256) [#489](https://github.com/fallenbagel/jellyseerr/issues/489) [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#515](https://github.com/fallenbagel/jellyseerr/issues/515) [#474](https://github.com/fallenbagel/jellyseerr/issues/474) [#473](https://github.com/fallenbagel/jellyseerr/issues/473)
|
||||
* **jellyfinlogin:** use externalHostname if set for forgetpassword link ([405f6bb](https://github.com/fallenbagel/jellyseerr/commit/405f6bbb7ffc390327c99dcef2cbbf9b3bc75f01)), closes [#199](https://github.com/fallenbagel/jellyseerr/issues/199) [#424](https://github.com/fallenbagel/jellyseerr/issues/424) [#212](https://github.com/fallenbagel/jellyseerr/issues/212)
|
||||
* **jellyfinscanner:** conditionally assign the jellyfinMediaId and jellyfinMediaId4k ([#686](https://github.com/fallenbagel/jellyseerr/issues/686)) ([530be42](https://github.com/fallenbagel/jellyseerr/commit/530be4272cce1b0d74d7f4156b8d794cda6ea03f)), closes [#681](https://github.com/fallenbagel/jellyseerr/issues/681)
|
||||
* **langcode:** fixes the ukranian language code ([dc67aaa](https://github.com/fallenbagel/jellyseerr/commit/dc67aaaf53eae86ba20c6c2798c92ec40962d85f)), closes [#504](https://github.com/fallenbagel/jellyseerr/issues/504)
|
||||
* nullable type for jellyfinMediaId(4k) ([#702](https://github.com/fallenbagel/jellyseerr/issues/702)) ([0900a95](https://github.com/fallenbagel/jellyseerr/commit/0900a95532501b6f4d9698de7530a771512924fc)), closes [#668](https://github.com/fallenbagel/jellyseerr/issues/668)
|
||||
* request watchlist items sequentially to prevent bypassing quota ([#3667](https://github.com/fallenbagel/jellyseerr/issues/3667)) ([b40ba07](https://github.com/fallenbagel/jellyseerr/commit/b40ba07a4de5857b8392f667038eeb0b22aa5d9a))
|
||||
* resolved issue with region selector and all regions value ([#3652](https://github.com/fallenbagel/jellyseerr/issues/3652)) ([28a2c50](https://github.com/fallenbagel/jellyseerr/commit/28a2c50495d0ce531da7f8c442bd488a54b1e84c))
|
||||
* typos on readme ([#655](https://github.com/fallenbagel/jellyseerr/issues/655)) ([eee9a02](https://github.com/fallenbagel/jellyseerr/commit/eee9a025d246c72bcd3aca753d9e49c1f8f064ea))
|
||||
* **watchlist:** added missing prop for watchlist item removal button in watchlist page ([a0ec992](https://github.com/fallenbagel/jellyseerr/commit/a0ec992028093257e9fa043622e236014f02dea3))
|
||||
* **watchlist:** discover local watchlist item display and profile local watchlist slider visibility ([3cb9494](https://github.com/fallenbagel/jellyseerr/commit/3cb9494e6210151716587d8c4b22e0a21692cf88))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ko language ([#3619](https://github.com/fallenbagel/jellyseerr/issues/3619)) ([9250735](https://github.com/fallenbagel/jellyseerr/commit/92507359b48db08b0066047d6505660b8c8b0b12))
|
||||
* add Peacock to Network Slider ([#3545](https://github.com/fallenbagel/jellyseerr/issues/3545)) ([0c39057](https://github.com/fallenbagel/jellyseerr/commit/0c39057ca58743697e9dcc3b678440ac3688c65a))
|
||||
* add tooltips to tautulli avatars ([#3601](https://github.com/fallenbagel/jellyseerr/issues/3601)) ([c484810](https://github.com/fallenbagel/jellyseerr/commit/c484810f965f8d04643c25c6d283dd83f4bd4a23))
|
||||
* added Letterboxd links for the external link blocks for movies ([981f5e6](https://github.com/fallenbagel/jellyseerr/commit/981f5e679c4c707e119741240a58de8bb07f9d6c))
|
||||
* check if first jellyfin user is admin ([#635](https://github.com/fallenbagel/jellyseerr/issues/635)) ([010df62](https://github.com/fallenbagel/jellyseerr/commit/010df62776191fe4c195e590df338f8d8523f55b)), closes [#610](https://github.com/fallenbagel/jellyseerr/issues/610)
|
||||
* jellyseerr makeover ([#715](https://github.com/fallenbagel/jellyseerr/issues/715)) ([0c27132](https://github.com/fallenbagel/jellyseerr/commit/0c2713213c56de342f76300d12ce01fd543d2ce3))
|
||||
* **job:** media availability support for jellyfin/emby ([#522](https://github.com/fallenbagel/jellyseerr/issues/522)) ([3eb1bb3](https://github.com/fallenbagel/jellyseerr/commit/3eb1bb3d8ff22391acb2e629bbec7b6e4b65ca95)), closes [#406](https://github.com/fallenbagel/jellyseerr/issues/406) [#193](https://github.com/fallenbagel/jellyseerr/issues/193) [#516](https://github.com/fallenbagel/jellyseerr/issues/516) [#362](https://github.com/fallenbagel/jellyseerr/issues/362) [#84](https://github.com/fallenbagel/jellyseerr/issues/84)
|
||||
* **notif:** add Pushover sound options ([#2403](https://github.com/fallenbagel/jellyseerr/issues/2403)) ([3ea5076](https://github.com/fallenbagel/jellyseerr/commit/3ea5076053359b518b1b4d537e7b61580d9275a3))
|
||||
* select default seriesType for anime ([#3627](https://github.com/fallenbagel/jellyseerr/issues/3627)) ([f628635](https://github.com/fallenbagel/jellyseerr/commit/f6286359cfd2ed93fc692aa2efda37310e02c11c)), closes [#3626](https://github.com/fallenbagel/jellyseerr/issues/3626)
|
||||
* standard series type selector ([#3628](https://github.com/fallenbagel/jellyseerr/issues/3628)) ([7bdd25e](https://github.com/fallenbagel/jellyseerr/commit/7bdd25e5a45843a3e530d3fa2b0887664b53eec8))
|
||||
* translations update from Hosted Weblate ([#3258](https://github.com/fallenbagel/jellyseerr/issues/3258)) ([e62a078](https://github.com/fallenbagel/jellyseerr/commit/e62a078298ced7dec627fb3ff9fc8f99a39d5e1b))
|
||||
* update SameSite policy of session cookie to Lax ([#3650](https://github.com/fallenbagel/jellyseerr/issues/3650)) ([c84ca43](https://github.com/fallenbagel/jellyseerr/commit/c84ca4307465af4278f3dad5cf9c2b8cbae3fada))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **jellyfinapi:** reverts [#450](https://github.com/fallenbagel/jellyseerr/issues/450) as it broke library sync support for local accounts using LDAP ([b5acc09](https://github.com/fallenbagel/jellyseerr/commit/b5acc09ba98e2dd9b61e6b78721e4dd9f42a996c)), closes [#489](https://github.com/fallenbagel/jellyseerr/issues/489)
|
||||
|
||||
# [1.7.0](https://github.com/fallenbagel/jellyseerr/compare/v1.6.0...v1.7.0) (2023-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust the plex watchlist sync schedule to have fuzziness ([#3502](https://github.com/fallenbagel/jellyseerr/issues/3502)) ([2c3f533](https://github.com/fallenbagel/jellyseerr/commit/2c3f5330764492e1323afd2d1f25e28ad78a2f2f))
|
||||
* handle issue causing incorrect media to change to unknown ([#3516](https://github.com/fallenbagel/jellyseerr/issues/3516)) ([83b008c](https://github.com/fallenbagel/jellyseerr/commit/83b008c8391459bd02dc74bcdb0d8caf27207bdf))
|
||||
* improved handling of edge case that could cause availability sync to fail ([#3497](https://github.com/fallenbagel/jellyseerr/issues/3497)) ([d0836ce](https://github.com/fallenbagel/jellyseerr/commit/d0836ce0efd55fccf2546087a0c4f94f7cb2e82a))
|
||||
* Include all defaults in payload ([#3538](https://github.com/fallenbagel/jellyseerr/issues/3538)) ([cb63bf2](https://github.com/fallenbagel/jellyseerr/commit/cb63bf217b9e8810a5210b4bf475b2a96583cc84))
|
||||
* multiple notifications for available media ([048fa96](https://github.com/fallenbagel/jellyseerr/commit/048fa967f2e5b23831ac9917c703934c50ef75f0))
|
||||
* repeat notifications for available 4k media ([30361f2](https://github.com/fallenbagel/jellyseerr/commit/30361f2ab751d9a882a9120e0f3df28dc42cc2cd))
|
||||
* resolved issue with create slider causing incorrect form submission ([#3514](https://github.com/fallenbagel/jellyseerr/issues/3514)) ([a761b7d](https://github.com/fallenbagel/jellyseerr/commit/a761b7dd35a5bd61bb4eb0275b75d1e0977e6a2d))
|
||||
* resolved user access check issue ([#3551](https://github.com/fallenbagel/jellyseerr/issues/3551)) ([2816c66](https://github.com/fallenbagel/jellyseerr/commit/2816c66300bf870d493c0665b0e984d60f707dfd))
|
||||
* **server/api/jellyfin.ts:** use /Library/VirtualFolders Jellyfin API call to fetch Jellyfin libs ([8685f57](https://github.com/fallenbagel/jellyseerr/commit/8685f5796a99d9700146bae9892319db10508d68)), closes [#256](https://github.com/fallenbagel/jellyseerr/issues/256)
|
||||
* **statusbadge:** handle missing season/episode number ([#3526](https://github.com/fallenbagel/jellyseerr/issues/3526)) ([01de972](https://github.com/fallenbagel/jellyseerr/commit/01de972a8fe2ea3c18d5b2f426d01b5b14d142d4))
|
||||
* **tautulli:** only test connection if hostname is defined ([#3573](https://github.com/fallenbagel/jellyseerr/issues/3573)) ([f7b4dfc](https://github.com/fallenbagel/jellyseerr/commit/f7b4dfcac472d08c54779a14fc1ad3c90927df26))
|
||||
* **ui:** corrected issues icon color ([#3498](https://github.com/fallenbagel/jellyseerr/issues/3498)) ([c1a47bd](https://github.com/fallenbagel/jellyseerr/commit/c1a47bd9de332cb4925974690f5a33448b5cc2e6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **rating:** added IMDB Radarr proxy ([#3496](https://github.com/fallenbagel/jellyseerr/issues/3496)) ([b4191f9](https://github.com/fallenbagel/jellyseerr/commit/b4191f9c65b7ff08764e61d18e7a75bc8d4b3325))
|
||||
|
||||
# [1.6.0](https://github.com/fallenbagel/jellyseerr/compare/v1.5.0...v1.6.0) (2023-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* availability sync file detection ([#3371](https://github.com/fallenbagel/jellyseerr/issues/3371)) ([7522aa3](https://github.com/fallenbagel/jellyseerr/commit/7522aa31743b169c903ebdf9d4d698645d27514c))
|
||||
* corrected initial fallback data load on details page ([#3395](https://github.com/fallenbagel/jellyseerr/issues/3395)) ([4bd8764](https://github.com/fallenbagel/jellyseerr/commit/4bd87647d0551c20e13589a62690a6f3e5ad8ff7))
|
||||
* correctly load series fallback modal with sonarr v4 ([#3451](https://github.com/fallenbagel/jellyseerr/issues/3451)) ([e051b1d](https://github.com/fallenbagel/jellyseerr/commit/e051b1dfea9c9320cc9dd420c475ae74cff0d901))
|
||||
* **deps:** update all non-major dependencies ([#3223](https://github.com/fallenbagel/jellyseerr/issues/3223)) ([f5191ad](https://github.com/fallenbagel/jellyseerr/commit/f5191aded680357522a65bbdcc40d162b8fbf594))
|
||||
* error deleting users with over 1000 requests ([#3376](https://github.com/fallenbagel/jellyseerr/issues/3376)) ([ac77b03](https://github.com/fallenbagel/jellyseerr/commit/ac77b037d5fb0c54f5edf4b29d04adb57aef388f))
|
||||
* external url regex is now consistent with internal url ([33ec443](https://github.com/fallenbagel/jellyseerr/commit/33ec4436fb82e1eb1bc97dd650088c27785e9d94))
|
||||
* externalLinkBlock ([46cd4d0](https://github.com/fallenbagel/jellyseerr/commit/46cd4d01d9a3cf17d79350c5e678202820272299))
|
||||
* fix regex for internal url to use a more effecient one ([e848386](https://github.com/fallenbagel/jellyseerr/commit/e848386d10f05f157e7a6dde8847ecab50c169ac))
|
||||
* fixes RT ratings for tv shows ([#3492](https://github.com/fallenbagel/jellyseerr/issues/3492)) ([04fbd00](https://github.com/fallenbagel/jellyseerr/commit/04fbd00d4ac29045592588ef8b664d1916991e37)), closes [#3491](https://github.com/fallenbagel/jellyseerr/issues/3491)
|
||||
* **genreselector:** fix searching in Genre filter ([#3468](https://github.com/fallenbagel/jellyseerr/issues/3468)) ([d7fa35e](https://github.com/fallenbagel/jellyseerr/commit/d7fa35e066cf371797aaa46ca464aa531ba8fb35))
|
||||
* handle search results with collections ([#3393](https://github.com/fallenbagel/jellyseerr/issues/3393)) ([70b1540](https://github.com/fallenbagel/jellyseerr/commit/70b1540ae23e83e01013856a9e06ad39e600922d))
|
||||
* lock body scroll when using webkit ([#3399](https://github.com/fallenbagel/jellyseerr/issues/3399)) ([c27f960](https://github.com/fallenbagel/jellyseerr/commit/c27f96096ac8cc6c387f9d1dde5b263576ac2132))
|
||||
* **logs:** jellyfin auth error now has the severity warn consistent with local login ([cc041b5](https://github.com/fallenbagel/jellyseerr/commit/cc041b5e0aa2b67573edba5919772b77a5111162)), closes [#224](https://github.com/fallenbagel/jellyseerr/issues/224)
|
||||
* make a (shallow) copy of radarr/sonarr tags into a request before adding user tags ([#3485](https://github.com/fallenbagel/jellyseerr/issues/3485)) ([48f7666](https://github.com/fallenbagel/jellyseerr/commit/48f76662d5c08156f1da3f47e216c5f02668f64b))
|
||||
* **ui:** corrected default badge hover opacity ([#3369](https://github.com/fallenbagel/jellyseerr/issues/3369)) ([a4d07f5](https://github.com/fallenbagel/jellyseerr/commit/a4d07f5afab613317d96c9c6e9b47157a5a28986))
|
||||
* **ui:** corrected mobile menu spacing in collection details ([#3432](https://github.com/fallenbagel/jellyseerr/issues/3432)) ([77a33cb](https://github.com/fallenbagel/jellyseerr/commit/77a33cb74d744bb747b791785799b632af8c7862))
|
||||
* **ui:** Make play symbol white ([1fe4bb8](https://github.com/fallenbagel/jellyseerr/commit/1fe4bb8a0415a72791ced75a2fba1027287398d5))
|
||||
* **ui:** Resize Emby icon and add margins ([ad69d67](https://github.com/fallenbagel/jellyseerr/commit/ad69d6715e976630092bfbbb1843886523551014))
|
||||
* **watchlist:** add validation for creation request ([03316c6](https://github.com/fallenbagel/jellyseerr/commit/03316c642d1ecf89753789af08caf6e3aac80113))
|
||||
* **watchlist:** fix github code scanning ([c08897b](https://github.com/fallenbagel/jellyseerr/commit/c08897bdc1cff65862c62347572bbbd01b6c36ac))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **add watchlist:** adding midding functionality from overserr ([5f1c10d](https://github.com/fallenbagel/jellyseerr/commit/5f1c10d50aaa430bcda96218ef2cc12a0eb926f3))
|
||||
* adds streaming services custom slider ([#3361](https://github.com/fallenbagel/jellyseerr/issues/3361)) ([2520d8f](https://github.com/fallenbagel/jellyseerr/commit/2520d8f739abfde608f3ef66a9fbe6b7b5c6647a))
|
||||
* auto tagging requested media with username ([#3338](https://github.com/fallenbagel/jellyseerr/issues/3338)) ([24f268b](https://github.com/fallenbagel/jellyseerr/commit/24f268b6cb67d9a8d8675cd6e09dd83a7f499add))
|
||||
* **discover:** support filtering by tmdb user vote count on discover page ([#3407](https://github.com/fallenbagel/jellyseerr/issues/3407)) ([aa84977](https://github.com/fallenbagel/jellyseerr/commit/aa849776809dfe891e67ff4db6861ef44df1a774))
|
||||
* **settings:** add internal url to jellyfin settings form ([0a30cd3](https://github.com/fallenbagel/jellyseerr/commit/0a30cd356d217a39546c016cc8bfa6ff6ad75e3e)), closes [#194](https://github.com/fallenbagel/jellyseerr/issues/194)
|
||||
* **src/components/externallinkblock/index.tsx:** support Emby icon ([672061c](https://github.com/fallenbagel/jellyseerr/commit/672061cd646c97c9954790c8e50eac88ea2666e9))
|
||||
* **tooltip:** email tooltip now appears when hovered over info icon ([cd7930e](https://github.com/fallenbagel/jellyseerr/commit/cd7930eef98451a781e5c9dc5ec223600a379f42))
|
||||
* translations update ([47287c3](https://github.com/fallenbagel/jellyseerr/commit/47287c368885d14bd1a56e3e8318ce22dd0f6ddf)), closes [#381](https://github.com/fallenbagel/jellyseerr/issues/381)
|
||||
* **watchlist:** add translation for en ([b7e3d28](https://github.com/fallenbagel/jellyseerr/commit/b7e3d285ed35b623062eceb0d99035cafbf075a6))
|
||||
|
||||
# [1.5.0](https://github.com/fallenbagel/jellyseerr/compare/v1.4.1...v1.5.0) (2023-04-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add better checks on 4k detection of series ([bc9017f](https://github.com/fallenbagel/jellyseerr/commit/bc9017f54d84ec24c4d74d38e1b4e24219425d41))
|
||||
* added a refresh interval if download status is in progress ([#3275](https://github.com/fallenbagel/jellyseerr/issues/3275)) ([1e2c6f4](https://github.com/fallenbagel/jellyseerr/commit/1e2c6f46ab66c836f321b5d8e34f1e8124c0b542))
|
||||
* **build:** increase threshold for amount of data to be fetched when SSR'ing ([#3320](https://github.com/fallenbagel/jellyseerr/issues/3320)) ([d7b83d2](https://github.com/fallenbagel/jellyseerr/commit/d7b83d22cee3d20db564cc0564d42802b02327e3))
|
||||
* disable availability sync temporarily ([2e5cf22](https://github.com/fallenbagel/jellyseerr/commit/2e5cf226265686012329248e7f729fec324c3deb))
|
||||
* hide remove button when default service is not configured ([7d4455b](https://github.com/fallenbagel/jellyseerr/commit/7d4455ba6bfd12e2730f7085cbb87df246f01d22))
|
||||
* **jellyfin scan:** temporary workaround fix for jellyfin scan when display specials within season ([38fb66d](https://github.com/fallenbagel/jellyseerr/commit/38fb66d31e41232c01898d0d362af8338eb7b960)), closes [#215](https://github.com/fallenbagel/jellyseerr/issues/215) [#176](https://github.com/fallenbagel/jellyseerr/issues/176) [#246](https://github.com/fallenbagel/jellyseerr/issues/246)
|
||||
* lint issues ([bcd2bb7](https://github.com/fallenbagel/jellyseerr/commit/bcd2bb7c96810f5a6932f42468a628d2db1bc771))
|
||||
* logger was set to info for the wrong logs ([#3354](https://github.com/fallenbagel/jellyseerr/issues/3354)) ([c36a4ba](https://github.com/fallenbagel/jellyseerr/commit/c36a4ba2b8df05873f5dfd0946a9bc3dc4ecfd1d))
|
||||
* remove unnecessary parenthesis from api key generation ([#3336](https://github.com/fallenbagel/jellyseerr/issues/3336)) ([6bd3f01](https://github.com/fallenbagel/jellyseerr/commit/6bd3f015d65507efca60279007bd2b86ee860643))
|
||||
* **snapcraft:** use the correct config folder for image cache ([#3302](https://github.com/fallenbagel/jellyseerr/issues/3302)) ([c93467b](https://github.com/fallenbagel/jellyseerr/commit/c93467b3acf2c256324297e7e8f21e9944005dd4))
|
||||
* **ui:** hide mini status badge if non-4K media status is unknown ([#3346](https://github.com/fallenbagel/jellyseerr/issues/3346)) ([50f06da](https://github.com/fallenbagel/jellyseerr/commit/50f06dabbffc693f0843584a64d1d96e77982820))
|
||||
* **ui:** hide search bar behind slideover when opened ([#3348](https://github.com/fallenbagel/jellyseerr/issues/3348)) ([b3882de](https://github.com/fallenbagel/jellyseerr/commit/b3882de8930a70adb2f93a27be6370bfa1826587))
|
||||
* **ui:** prevent title cards from flickering when quickly hovering across them ([#3349](https://github.com/fallenbagel/jellyseerr/issues/3349)) ([eb5502a](https://github.com/fallenbagel/jellyseerr/commit/eb5502a16f86e37a933f6beca0678c2d228e77d5))
|
||||
* **watchlist:** correctly load more than 20 watchlist items ([#3351](https://github.com/fallenbagel/jellyseerr/issues/3351)) ([af880a6](https://github.com/fallenbagel/jellyseerr/commit/af880a6c839794b34bddcd7e0fe56353aa48ba36))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a button in ManageSlideOver to remove the movie and the file from Radarr/Sonarr ([2e74584](https://github.com/fallenbagel/jellyseerr/commit/2e7458457e995dd3ec6dd96035fe997646cdd446))
|
||||
* availability sync rework ([#3219](https://github.com/fallenbagel/jellyseerr/issues/3219)) ([ae38183](https://github.com/fallenbagel/jellyseerr/commit/ae3818304b2f75222d1bd223ece94f829a3b42d0)), closes [#377](https://github.com/fallenbagel/jellyseerr/issues/377)
|
||||
* full title of download item on hover with tooltip ([#3296](https://github.com/fallenbagel/jellyseerr/issues/3296)) ([33e7691](https://github.com/fallenbagel/jellyseerr/commit/33e7691b94d7d369a0a1410e434850bc51e5572e))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **imageproxy:** do not set cookies to image proxy so CDNs can cache images ([#3332](https://github.com/fallenbagel/jellyseerr/issues/3332)) ([966639d](https://github.com/fallenbagel/jellyseerr/commit/966639df430d32f6bfebdb16314dc4590d21caf8))
|
||||
|
||||
## [1.4.1](https://github.com/fallenbagel/jellyseerr/compare/v1.4.0...v1.4.1) (2023-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass in library type when scanning recently added items ([#3287](https://github.com/fallenbagel/jellyseerr/issues/3287)) ([8942eb8](https://github.com/fallenbagel/jellyseerr/commit/8942eb8b7c4fa1d16aa2e72e8ba7120a653c9aa2))
|
||||
* **ui:** air date will use UTC for timezone ([#3297](https://github.com/fallenbagel/jellyseerr/issues/3297)) ([3e43586](https://github.com/fallenbagel/jellyseerr/commit/3e43586acc0804c3fff524509caa890a104e132b))
|
||||
* **ui:** correct range slider styling in chrome ([#3299](https://github.com/fallenbagel/jellyseerr/issues/3299)) ([d954328](https://github.com/fallenbagel/jellyseerr/commit/d9543289111d72245564d25d300a71b0ea3954ba))
|
||||
* **ui:** show 5 icons when possible on mobile menu ([#3298](https://github.com/fallenbagel/jellyseerr/issues/3298)) ([7040da1](https://github.com/fallenbagel/jellyseerr/commit/7040da1334f6d18e19a494c73caa17f7df552dfe))
|
||||
* **ui:** style range thumbs correctly for firefox ([#3294](https://github.com/fallenbagel/jellyseerr/issues/3294)) ([9d10e6a](https://github.com/fallenbagel/jellyseerr/commit/9d10e6a88c0996671f1d9d20792e1930dbc82329))
|
||||
|
||||
# [1.4.0](https://github.com/fallenbagel/jellyseerr/compare/v1.3.0...v1.4.0) (2023-01-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add bg-opacity to in-progress status badges ([#3190](https://github.com/fallenbagel/jellyseerr/issues/3190)) ([68223f4](https://github.com/fallenbagel/jellyseerr/commit/68223f4b1e98b01825516dcba39cbb2d3df31a70))
|
||||
* added download status and title to request card/item error components ([#3186](https://github.com/fallenbagel/jellyseerr/issues/3186)) ([3309f77](https://github.com/fallenbagel/jellyseerr/commit/3309f77aa4be1d70b27693531c119a8e26822518))
|
||||
* arrow icons were misplaced on mobile in slider edit ([#3260](https://github.com/fallenbagel/jellyseerr/issues/3260)) ([d328485](https://github.com/fallenbagel/jellyseerr/commit/d328485161b9cae6a70ef0713b4878207bc6015e))
|
||||
* **build:** update usage of publish snap action ([#3272](https://github.com/fallenbagel/jellyseerr/issues/3272)) ([51b05cd](https://github.com/fallenbagel/jellyseerr/commit/51b05cd8fbb5d332807d8c00b2ffb7b10c3d0179))
|
||||
* changed overflow scroll to only if necessary ([#3184](https://github.com/fallenbagel/jellyseerr/issues/3184)) ([27feeea](https://github.com/fallenbagel/jellyseerr/commit/27feeea69121336557deda1f32b65a5daa146f82))
|
||||
* convert genre/studio to string in create slider ([#3201](https://github.com/fallenbagel/jellyseerr/issues/3201)) ([93afead](https://github.com/fallenbagel/jellyseerr/commit/93afead92e497f2e5bce67a34fffdaa08d20c7f2))
|
||||
* correct checkbox position (again) for slider edits ([#3227](https://github.com/fallenbagel/jellyseerr/issues/3227)) ([3ba6df1](https://github.com/fallenbagel/jellyseerr/commit/3ba6df1a41c084c4a6a90354338047623abef521))
|
||||
* correct grid sizing for webkit on streaming services ([#3248](https://github.com/fallenbagel/jellyseerr/issues/3248)) ([6fd11cf](https://github.com/fallenbagel/jellyseerr/commit/6fd11cf4254e1a19310592bec78a6de52bc073a8))
|
||||
* correct issue detail bottom padding on mobile displays ([#3268](https://github.com/fallenbagel/jellyseerr/issues/3268)) ([3db010b](https://github.com/fallenbagel/jellyseerr/commit/3db010b9eaec62aa08d973a61caf1801471bbf3e))
|
||||
* correct link to correct keyword results for series ([#3208](https://github.com/fallenbagel/jellyseerr/issues/3208)) ([4e9be7a](https://github.com/fallenbagel/jellyseerr/commit/4e9be7a3f7304ee7be5ee6fd34b1ea8f6c0cf399))
|
||||
* correct spacing between sliders ([#3225](https://github.com/fallenbagel/jellyseerr/issues/3225)) ([62e2de7](https://github.com/fallenbagel/jellyseerr/commit/62e2de70bf37b72d5f63370b662d4103a642775b))
|
||||
* correctly check mobile menu permissions ([#3271](https://github.com/fallenbagel/jellyseerr/issues/3271)) ([f4a22dc](https://github.com/fallenbagel/jellyseerr/commit/f4a22dc437404558f301ccfc195cf0a300dd1ff2))
|
||||
* correctly restore selected streaming service filters ([#3249](https://github.com/fallenbagel/jellyseerr/issues/3249)) ([154f3e7](https://github.com/fallenbagel/jellyseerr/commit/154f3e72efbf0b663358b3029156f54516f01a2f))
|
||||
* create shared class to add bottom spacing ([#3269](https://github.com/fallenbagel/jellyseerr/issues/3269)) ([5d1c6f7](https://github.com/fallenbagel/jellyseerr/commit/5d1c6f706555613d97ed9e61d8b665543c2f239b))
|
||||
* **deps:** pin dependency @headlessui/react to 1.7.7 ([#3194](https://github.com/fallenbagel/jellyseerr/issues/3194)) [skip ci] ([c4b16ab](https://github.com/fallenbagel/jellyseerr/commit/c4b16abc62647c74215155942a4230a31a238677))
|
||||
* **deps:** update dependency @heroicons/react to v2 ([#2970](https://github.com/fallenbagel/jellyseerr/issues/2970)) ([dd48d59](https://github.com/fallenbagel/jellyseerr/commit/dd48d59b20e2d1800ea30912116f4a4f1bb7928f))
|
||||
* **deps:** update dependency axios to v1 ([#3202](https://github.com/fallenbagel/jellyseerr/issues/3202)) ([421029e](https://github.com/fallenbagel/jellyseerr/commit/421029ebab66c9a6622ba47e56d7f6473524cce4))
|
||||
* **deps:** update dependency swr to v2 ([#3212](https://github.com/fallenbagel/jellyseerr/issues/3212)) ([7b6db50](https://github.com/fallenbagel/jellyseerr/commit/7b6db50ae55b1fc60d19a5cff62dd46bb989fa51))
|
||||
* **experimental:** use new RT API (sorta) ([#3179](https://github.com/fallenbagel/jellyseerr/issues/3179)) ([357cab8](https://github.com/fallenbagel/jellyseerr/commit/357cab87ac7752b8e119b51c938b343c661d83c2))
|
||||
* improve small screen layout for discover editing ([#3221](https://github.com/fallenbagel/jellyseerr/issues/3221)) ([d23b213](https://github.com/fallenbagel/jellyseerr/commit/d23b2132de05f072f7f9daad83d81421d747cf99))
|
||||
* include new package calendar css in build ([#3235](https://github.com/fallenbagel/jellyseerr/issues/3235)) ([c2a1a20](https://github.com/fallenbagel/jellyseerr/commit/c2a1a20a3bb20039a1936c7fe0ecb9e8311a0aea))
|
||||
* issues with issues ([#3267](https://github.com/fallenbagel/jellyseerr/issues/3267)) ([fd21971](https://github.com/fallenbagel/jellyseerr/commit/fd219717c01c558814d7a80de6304272b5a7944e))
|
||||
* multiple genre filtering now works ([#3282](https://github.com/fallenbagel/jellyseerr/issues/3282)) ([5076938](https://github.com/fallenbagel/jellyseerr/commit/507693881b939819413f0959df5ef6b7a357eb5c))
|
||||
* prevent double encode if we are on /search endpoint ([#3238](https://github.com/fallenbagel/jellyseerr/issues/3238)) ([a343f8a](https://github.com/fallenbagel/jellyseerr/commit/a343f8ad915491a9c81512c7e541a1dac8906025))
|
||||
* **request:** approve request when retrying request ([#3234](https://github.com/fallenbagel/jellyseerr/issues/3234)) ([b515701](https://github.com/fallenbagel/jellyseerr/commit/b5157010c46cd9083993d5ee0172007b83d631da))
|
||||
* **request:** mark request as approved if media is already available when retrying failed request ([#3244](https://github.com/fallenbagel/jellyseerr/issues/3244)) ([cb65074](https://github.com/fallenbagel/jellyseerr/commit/cb650745f6a33e69391a633e6d272831f314e098))
|
||||
* restore border to ghost button and fix discover slider visibility toggle position ([#3226](https://github.com/fallenbagel/jellyseerr/issues/3226)) ([2eebb7f](https://github.com/fallenbagel/jellyseerr/commit/2eebb7fd3941b34fe9472aaf9d28265df8cce311))
|
||||
* restore status badges on titles on actors page when hide available media enabled ([#3206](https://github.com/fallenbagel/jellyseerr/issues/3206)) ([9d3446d](https://github.com/fallenbagel/jellyseerr/commit/9d3446d370499c3251159393e5c791b01225e05c))
|
||||
* screen would zoom on mobile if date picker input was selected ([#3241](https://github.com/fallenbagel/jellyseerr/issues/3241)) ([3aefddd](https://github.com/fallenbagel/jellyseerr/commit/3aefddd48834d86150d5f5cceb2d08af3a78847b))
|
||||
* series displayed an empty season with series list/request modal ([#3147](https://github.com/fallenbagel/jellyseerr/issues/3147)) ([2179637](https://github.com/fallenbagel/jellyseerr/commit/2179637d437999290eaa4152f6f37c71fc3d8ba3))
|
||||
* tooltip shows properly if not in progress ([#3185](https://github.com/fallenbagel/jellyseerr/issues/3185)) ([6face8c](https://github.com/fallenbagel/jellyseerr/commit/6face8cc4564b978fb98af32659b326d8c5cede8))
|
||||
* **ui:** series first air date sorting ([#3283](https://github.com/fallenbagel/jellyseerr/issues/3283)) ([374c78c](https://github.com/fallenbagel/jellyseerr/commit/374c78c989cc86bb144a954a91d5d183c4b591c0))
|
||||
* update StatusBadgeMini to shrink on title cards (and remove ring) ([#3210](https://github.com/fallenbagel/jellyseerr/issues/3210)) ([042a1a9](https://github.com/fallenbagel/jellyseerr/commit/042a1a950fdd4d4a61edf4bc19657f9b7a526da8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add discover customization ([#3182](https://github.com/fallenbagel/jellyseerr/issues/3182)) ([cd35748](https://github.com/fallenbagel/jellyseerr/commit/cd3574851a12517cbfadc109e6412a7a9e44c114))
|
||||
* add keywords to movie/series detail pages ([#3204](https://github.com/fallenbagel/jellyseerr/issues/3204)) ([e084649](https://github.com/fallenbagel/jellyseerr/commit/e084649878a58c296786141d12dd69a69a27ee85))
|
||||
* add streaming services filter ([#3247](https://github.com/fallenbagel/jellyseerr/issues/3247)) ([1154156](https://github.com/fallenbagel/jellyseerr/commit/1154156459403494e8daf0c89a3ba356aeea1d97))
|
||||
* discover inline customization ([#3220](https://github.com/fallenbagel/jellyseerr/issues/3220)) ([8bd10b5](https://github.com/fallenbagel/jellyseerr/commit/8bd10b5bf3d1b8069872b616c7c8596caeb4937e))
|
||||
* discover overhaul (filters!) ([#3232](https://github.com/fallenbagel/jellyseerr/issues/3232)) ([dd00e48](https://github.com/fallenbagel/jellyseerr/commit/dd00e48f59054b44bef6b32a2c169e59f6175051))
|
||||
* discover slider edit arrow buttons for reordering ([#3259](https://github.com/fallenbagel/jellyseerr/issues/3259)) ([da00d45](https://github.com/fallenbagel/jellyseerr/commit/da00d454e17e8b00d04f6e26f6dd5153ed6ced81))
|
||||
* **lang:** translations update from Hosted Weblate ([#3030](https://github.com/fallenbagel/jellyseerr/issues/3030)) ([0d8b390](https://github.com/fallenbagel/jellyseerr/commit/0d8b390b678731e76bd1f0f8a0a4952c11e77f4d))
|
||||
* new mobile menu ([#3251](https://github.com/fallenbagel/jellyseerr/issues/3251)) ([fcbca17](https://github.com/fallenbagel/jellyseerr/commit/fcbca1722f31f32633a57bc5048f46c9da057d87))
|
||||
* translations update from Hosted Weblate ([#3218](https://github.com/fallenbagel/jellyseerr/issues/3218)) ([5940ff7](https://github.com/fallenbagel/jellyseerr/commit/5940ff7f5f62eed9ac5aa6f02803418aaa09813a))
|
||||
* **ui:** add episode number to front of episode name in season details ([#3086](https://github.com/fallenbagel/jellyseerr/issues/3086)) ([a672b32](https://github.com/fallenbagel/jellyseerr/commit/a672b324ec391a20f6f3a1daed82a8d276a52c2c))
|
||||
* **ui:** request card progress bar ([#3123](https://github.com/fallenbagel/jellyseerr/issues/3123)) ([03853a1](https://github.com/fallenbagel/jellyseerr/commit/03853a1b9155c8a2153c8885022a74619af1bc15))
|
||||
|
||||
# [1.3.0](https://github.com/fallenbagel/jellyseerr/compare/v1.2.1...v1.3.0) (2023-01-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -8,7 +8,7 @@ All help is welcome and greatly appreciated! If you would like to contribute to
|
||||
|
||||
- HTML/Typescript/Javascript editor
|
||||
- [VSCode](https://code.visualstudio.com/) is recommended. Upon opening the project, a few extensions will be automatically recommended for install.
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 22.x)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 20.x)
|
||||
- [Pnpm](https://pnpm.io/cli/install)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:22-alpine AS BUILD_IMAGE
|
||||
FROM node:20-alpine AS BUILD_IMAGE
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN \
|
||||
;; \
|
||||
esac
|
||||
|
||||
RUN npm install --global pnpm@9
|
||||
RUN npm install --global pnpm
|
||||
|
||||
COPY package.json pnpm-lock.yaml postinstall-win.js ./
|
||||
RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile
|
||||
@@ -29,14 +29,14 @@ RUN pnpm build
|
||||
# remove development dependencies
|
||||
RUN pnpm prune --prod --ignore-scripts
|
||||
|
||||
RUN rm -rf src server .next/cache charts gen-docs docs
|
||||
RUN rm -rf src server .next/cache
|
||||
|
||||
RUN touch config/DOCKER
|
||||
|
||||
RUN echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json
|
||||
|
||||
|
||||
FROM node:22-alpine
|
||||
FROM node:20-alpine
|
||||
|
||||
# Metadata for Github Package Registry
|
||||
LABEL org.opencontainers.image.source="https://github.com/Fallenbagel/jellyseerr"
|
||||
@@ -45,7 +45,7 @@ WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
|
||||
|
||||
RUN npm install -g pnpm@9
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# copy from build image
|
||||
COPY --from=BUILD_IMAGE /app ./
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM node:22-alpine
|
||||
FROM node:20-alpine
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm install --global pnpm@9
|
||||
Run npm install --global pnpm
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
|
||||
22
README.md
22
README.md
@@ -11,17 +11,17 @@
|
||||
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-65-orange.svg"/></a>
|
||||
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-60-orange.svg"/></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
**Jellyseerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**.
|
||||
**Jellyseerr** is a free and open source software application for managing requests for your media library.
|
||||
It is a fork of [Overseerr](https://github.com/sct/overseerr) built to bring support for [Jellyfin](https://github.com/jellyfin/jellyfin) & [Emby](https://github.com/MediaBrowser/Emby) media servers!
|
||||
|
||||
## Current Features
|
||||
|
||||
- Full Jellyfin/Emby/Plex integration including authentication with user import & management.
|
||||
- Support for **PostgreSQL** and **SQLite** databases.
|
||||
- Supports Movies, Shows and Mixed Libraries.
|
||||
- Ability to change email addresses for SMTP purposes.
|
||||
- Full Jellyfin/Emby/Plex integration including authentication with user import & management
|
||||
- Supports Movies, Shows and Mixed Libraries
|
||||
- Ability to change email addresses for smtp purposes
|
||||
- Easy integration with your existing services. Currently, Jellyseerr supports Sonarr and Radarr. More to come!
|
||||
- Jellyfin/Emby/Plex library scan, to keep track of the titles which are already available.
|
||||
- Customizable request system, which allows users to request individual seasons or movies in a friendly, easy-to-use interface.
|
||||
@@ -29,7 +29,8 @@
|
||||
- Granular permission system.
|
||||
- Support for various notification agents.
|
||||
- Mobile-friendly design, for when you need to approve requests on the go!
|
||||
- Support for watchlisting & blacklisting media.
|
||||
|
||||
(Upcoming Features include: Multiple Server Instances, and much more!)
|
||||
|
||||
With more features on the way! Check out our [issue tracker](https://github.com/fallenbagel/jellyseerr/issues) to see the features which have already been requested.
|
||||
|
||||
@@ -162,13 +163,6 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://me.garnx.fr"><img src="https://avatars.githubusercontent.com/u/37373941?v=4?s=100" width="100px;" alt="Guillaume ARNOUX"/><br /><sub><b>Guillaume ARNOUX</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=guillaumearnx" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dr-carrot"><img src="https://avatars.githubusercontent.com/u/17272571?v=4?s=100" width="100px;" alt="dr-carrot"/><br /><sub><b>dr-carrot</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=dr-carrot" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gageorsburn"><img src="https://avatars.githubusercontent.com/u/4692734?v=4?s=100" width="100px;" alt="Gage Orsburn"/><br /><sub><b>Gage Orsburn</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=gageorsburn" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GkhnGRBZ"><img src="https://avatars.githubusercontent.com/u/127258824?v=4?s=100" width="100px;" alt="GkhnGRBZ"/><br /><sub><b>GkhnGRBZ</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=GkhnGRBZ" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://benhaney.com"><img src="https://avatars.githubusercontent.com/u/31331498?v=4?s=100" width="100px;" alt="Ben Haney"/><br /><sub><b>Ben Haney</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=benhaney" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wunderharke"><img src="https://avatars.githubusercontent.com/u/5105672?v=4?s=100" width="100px;" alt="Wunderharke"/><br /><sub><b>Wunderharke</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Wunderharke" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/methbkts"><img src="https://avatars.githubusercontent.com/u/30674934?v=4?s=100" width="100px;" alt="Metin Bektas"/><br /><sub><b>Metin Bektas</b></sub></a><br /><a href="#infra-methbkts" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andrewkolda"><img src="https://avatars.githubusercontent.com/u/158614532?v=4?s=100" width="100px;" alt="andrewkolda"/><br /><sub><b>andrewkolda</b></sub></a><br /><a href="#design-andrewkolda" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
repositoryID: c6b3f2dc-444c-4e37-b397-6a5ff563ee8b
|
||||
@@ -21,5 +21,3 @@
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
# go template
|
||||
*.gotmpl
|
||||
@@ -1,10 +1,10 @@
|
||||
apiVersion: v2
|
||||
kubeVersion: ">=1.23.0-0"
|
||||
name: jellyseerr-chart
|
||||
name: Jellyseerr
|
||||
description: Jellyseerr helm chart for Kubernetes
|
||||
type: application
|
||||
version: 2.1.1
|
||||
appVersion: "2.3.0"
|
||||
version: 1.1.0
|
||||
appVersion: "2.1.0"
|
||||
maintainers:
|
||||
- name: Jellyseerr
|
||||
url: https://github.com/Fallenbagel/jellyseerr
|
||||
@@ -1,6 +1,6 @@
|
||||
# jellyseerr-chart
|
||||
# Jellyseerr
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Jellyseerr helm chart for Kubernetes
|
||||
|
||||
@@ -25,6 +25,10 @@ Kubernetes: `>=1.23.0-0`
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| 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.persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes of persistent disk |
|
||||
| config.persistence.annotations | object | `{}` | Annotations for PVCs |
|
||||
@@ -35,7 +39,7 @@ Kubernetes: `>=1.23.0-0`
|
||||
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the jellyseerr pods |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.registry | string | `"ghcr.io"` | |
|
||||
| image.registry | string | `"docker.io"` | |
|
||||
| image.repository | string | `"fallenbagel/jellyseerr"` | |
|
||||
| image.sha | string | `""` | |
|
||||
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
|
||||
@@ -63,5 +67,3 @@ Kubernetes: `>=1.23.0-0`
|
||||
| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
|
||||
| strategy | object | `{"type":"Recreate"}` | Deployment strategy |
|
||||
| tolerations | list | `[]` | |
|
||||
| volumeMounts | list | `[]` | Additional volumeMounts on the output Deployment definition. |
|
||||
| volumes | list | `[]` | Additional volumes on the output Deployment definition. |
|
||||
@@ -5,7 +5,9 @@ metadata:
|
||||
labels:
|
||||
{{- include "jellyseerr.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
strategy:
|
||||
type: {{ .Values.strategy.type }}
|
||||
selector:
|
||||
@@ -65,16 +67,10 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /app/config
|
||||
{{- with .Values.volumeMounts }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "jellyseerr.configPersistenceName" . }}
|
||||
{{- with .Values.volumes }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
32
charts/jellyseerr/templates/hpa.yaml
Normal file
32
charts/jellyseerr/templates/hpa.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
{{- 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
|
||||
|
||||
image:
|
||||
registry: ghcr.io
|
||||
registry: docker.io
|
||||
repository: fallenbagel/jellyseerr
|
||||
pullPolicy: IfNotPresent
|
||||
# -- Overrides the image tag whose default is the chart appVersion.
|
||||
@@ -94,18 +94,12 @@ resources: {}
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
# -- Additional volumes on the output Deployment definition.
|
||||
volumes: []
|
||||
# - name: foo
|
||||
# secret:
|
||||
# secretName: mysecret
|
||||
# optional: false
|
||||
|
||||
# -- Additional volumeMounts on the output Deployment definition.
|
||||
volumeMounts: []
|
||||
# - name: foo
|
||||
# mountPath: "/etc/foo"
|
||||
# readOnly: true
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
"mediaServerType": 1,
|
||||
"partialRequestsEnabled": true,
|
||||
"enableSpecialEpisodes": false,
|
||||
"forceIpv4First": false,
|
||||
"dnsServers": "",
|
||||
"locale": "en"
|
||||
},
|
||||
"plex": {
|
||||
|
||||
@@ -6,6 +6,7 @@ Cypress.Commands.add('login', (email, password) => {
|
||||
[email, password],
|
||||
() => {
|
||||
cy.visit('/login');
|
||||
cy.contains('Use your Overseerr account').click();
|
||||
|
||||
cy.get('[data-testid=email]').type(email);
|
||||
cy.get('[data-testid=password]').type(password);
|
||||
|
||||
@@ -7,34 +7,31 @@ sidebar_position: 1
|
||||
|
||||
Welcome to the Jellyseerr Documentation.
|
||||
|
||||
**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/)**.
|
||||
|
||||
## Features
|
||||
|
||||
- **Full Jellyfin/Emby/Plex integration**. Login and manage user access with Jellyfin/Emby/Plex.
|
||||
- **Syncs to your Jellyfin/Emby/Plex library** to show what titles you already have.
|
||||
- Supports Movies, Shows and Mixed Libraries.
|
||||
- **Integrates with Sonarr and Radarr**. With more services to come in the future.
|
||||
- Optionally set **Override rules** for requests to match with your defined conditions.
|
||||
- **Easy to use request system** allowing users to request individual seasons or movies in a friendly, clean UI.
|
||||
- **Simple request management UI**. Don't dig through the app to approve recent requests.
|
||||
- **Mobile-friendly design**, for when you need to approve requests on the go.
|
||||
- Granular permission system.
|
||||
- Localization into other languages.
|
||||
- Support for **PostgreSQL** and **SQLite** databases.
|
||||
- Support for various notification agents.
|
||||
- Easily **Watchlist** or **Blacklist** media.
|
||||
- Support for PostgreSQL and SQLite databases.
|
||||
- More features to come!
|
||||
|
||||
## Motivation
|
||||
|
||||
The primary motivation for starting Jellyseerr was to bring Jellyfin and Emby support to Overseerr. However, over time, **Jellyseerr** has evolved into its own distinct application with unique features. Designed as a one-stop shop for media requests, it offers a simple, easy-to-use experience for managing requests on Jellyfin, Emby, and Plex servers.
|
||||
The primary motivation for starting this project was to add support for Jellyfin and Emby to Overseerr. As Overseerr is an incredibly performant and easy-to-use application, we wanted to bring that same experience to Jellyfin and Emby users. Thus, **Jellyseerr** was born.
|
||||
|
||||
This application is designed to be a **one-stop-shop** for all your media requests. It is designed to be a **simple, easy-to-use** application that allows users to request media to be added to your Jellyfin/Emby/Plex server.
|
||||
|
||||
## We need your help!
|
||||
|
||||
[Jellyseerr](https://github.com/Fallenbagel/jellyseerr) is an ambitious project where developers/contributors poured a lot of work into, and that builds on top of [Overseerr](https://github.com/sct/overseerr). And we have a lot more to do as well.
|
||||
[Jellyseerr](https://github.com/Fallenbagel/jellyseerr) is a fork of Overseerr, with a heavy focus on Jellyfin and Emby integration.
|
||||
[Overseerr](https://github.com/sct/overseerr) is an ambitious project where the original developers/contributors have already poured a lot of work into, and we wanted to build on top of that.
|
||||
|
||||
We value your feedback and support in identifying and fixing bugs to make Jellyseerr even better. As an open-source project, we welcome contributions from everyone. While Jellyseerr has diverged from Overseerr and evolved into its own unique application, we still encourage contributions to Overseerr, as it played a crucial role in inspiring what Jellyseerr has become today.
|
||||
We also have poured a lot of work into this project, and we have a lot more to do as well. We need your valuable feedback and help to find and fix bugs. Also, with Jellyseerr being an open-source project, anyone is welcome to contribute. We also encourage you to contribute to Overseerr as well.
|
||||
|
||||
Contribution includes building new features, patching bugs, translating the application, or even just writing documentation.
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ Jellyseerr supports SQLite and PostgreSQL. The database connection can be config
|
||||
|
||||
## SQLite Options
|
||||
|
||||
If you want to use SQLite, you can simply set the `DB_TYPE` environment variable to `sqlite`. This is the default configuration so even if you don't set any other options, SQLite will be used.
|
||||
|
||||
```dotenv
|
||||
DB_TYPE="sqlite" # Which DB engine to use, either "sqlite" or "postgres". The default is "sqlite".
|
||||
CONFIG_DIRECTORY="config" # (optional) The path to the config directory where the db file is stored. The default is "config".
|
||||
@@ -19,35 +17,17 @@ DB_LOG_QUERIES="false" # (optional) Whether to log the DB queries for debugging.
|
||||
|
||||
## PostgreSQL Options
|
||||
|
||||
### TCP Connection
|
||||
|
||||
If your PostgreSQL server is configured to accept TCP connections, you can specify the host and port using the `DB_HOST` and `DB_PORT` environment variables. This is useful for remote connections where the server uses a network host and port.
|
||||
|
||||
```dotenv
|
||||
DB_TYPE="postgres" # Which DB engine to use, either "sqlite" or "postgres". The default is "sqlite".
|
||||
DB_HOST="localhost" # (optional) The host (URL) of the database. The default is "localhost".
|
||||
DB_TYPE="postgres" # Which DB engine to use, either "sqlite" or "postgres". The default is "sqlite". To use postgres, this needs to be set to "postgres"
|
||||
DB_HOST="localhost" # (optional) The host (url) of the database. The default is "localhost".
|
||||
DB_PORT="5432" # (optional) The port to connect to. The default is "5432".
|
||||
DB_USER= # (required) Username used to connect to the database.
|
||||
DB_PASS= # (required) Password of the user used to connect to the database.
|
||||
DB_NAME="jellyseerr" # (optional) The name of the database to connect to. The default is "jellyseerr".
|
||||
DB_LOG_QUERIES="false" # (optional) Whether to log the DB queries for debugging. The default is "false".
|
||||
```
|
||||
|
||||
### Unix Socket Connection
|
||||
|
||||
If your PostgreSQL server is configured to accept Unix socket connections, you can specify the path to the socket directory using the `DB_SOCKET_PATH` environment variable. This is useful for local connections where the server uses a Unix socket.
|
||||
|
||||
```dotenv
|
||||
DB_TYPE="postgres" # Which DB engine to use, either "sqlite" or "postgres". The default is "sqlite".
|
||||
DB_SOCKET_PATH="/var/run/postgresql" # (required) The path to the PostgreSQL Unix socket directory.
|
||||
DB_USER= # (required) Username used to connect to the database.
|
||||
DB_PASS= # (optional) Password of the user used to connect to the database, depending on the server's authentication configuration.
|
||||
DB_USER= # (required) Username used to connect to the database
|
||||
DB_PASS= # (required) Password of the user used to connect to the database
|
||||
DB_NAME="jellyseerr" # (optional) The name of the database to connect to. The default is "jellyseerr".
|
||||
DB_LOG_QUERIES="false" # (optional) Whether to log the DB queries for debugging. The default is "false".
|
||||
```
|
||||
|
||||
### SSL configuration
|
||||
|
||||
The following options can be used to further configure ssl. Certificates can be provided as a string or a file path, with the string version taking precedence.
|
||||
|
||||
```dotenv
|
||||
@@ -60,7 +40,6 @@ DB_SSL_KEY_FILE= # (optinal) Path to the private key for the connection in PEM f
|
||||
DB_SSL_CERT= # (optional) Certificate chain in pem format for the private key, provided as a string. The default is "".
|
||||
DB_SSL_CERT_FILE= # (optional) Path to certificate chain in pem format for the private key. The default is "".
|
||||
```
|
||||
---
|
||||
|
||||
### Migrating from SQLite to PostgreSQL
|
||||
|
||||
@@ -69,7 +48,7 @@ DB_SSL_CERT_FILE= # (optional) Path to certificate chain in pem format for the p
|
||||
3. Stop Jellyseerr
|
||||
4. Run the following command to export the data from the SQLite database and import it into the PostgreSQL database:
|
||||
:::info
|
||||
Edit the postgres connection string to match your setup.
|
||||
Edit the postgres connection string to match your setup
|
||||
|
||||
If you don't have or don't want to use docker, you can build the working pgloader version [in this PR](https://github.com/dimitri/pgloader/pull/1531) from source and use the same options as below.
|
||||
:::
|
||||
@@ -77,6 +56,6 @@ If you don't have or don't want to use docker, you can build the working pgloade
|
||||
The most recent release of pgloader has an issue quoting the table columns. Use the version in the docker container to avoid this issue.
|
||||
:::
|
||||
```bash
|
||||
docker run --rm -v config/db.sqlite3:/db.sqlite3:ro ghcr.io/ralgar/pgloader:pr-1531 pgloader --with "quote identifiers" --with "data only" /db.sqlite3 postgresql://{{DB_USER}}:{{DB_PASS}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}
|
||||
docker run --rm -v config/db.sqlite3:/db.sqlite3:ro -v pgloader/pgloader.load:/pgloader.load ghcr.io/ralgar/pgloader:pr-1531 pgloader --with "quote identifiers" --with "data only" /db.sqlite3 postgresql://{{DB_USER}}:{{DB_PASS}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}
|
||||
```
|
||||
5. Start Jellyseerr
|
||||
|
||||
@@ -6,15 +6,13 @@ sidebar_position: 2
|
||||
# Build from Source (Advanced)
|
||||
:::warning
|
||||
This method is not recommended for most users. It is intended for advanced users who are familiar with managing their own server infrastructure.
|
||||
|
||||
Refer to [Configuring Databases](/extending-jellyseerr/database-config#postgresql-options) for details on how to configure your database.
|
||||
:::
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
### Prerequisites
|
||||
- [Node.js 22.x](https://nodejs.org/en/download/)
|
||||
- [Node.js 20.x](https://nodejs.org/en/download/)
|
||||
- [Pnpm 9.x](https://pnpm.io/installation)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ sidebar_position: 1
|
||||
:::info
|
||||
This is the recommended method for most users.
|
||||
Details on how to install Docker can be found on the [official Docker website](https://docs.docker.com/get-docker/).
|
||||
|
||||
Refer to [Configuring Databases](/extending-jellyseerr/database-config#postgresql-options) for details on how to configure your database.
|
||||
:::
|
||||
|
||||
## Unix (Linux, macOS)
|
||||
@@ -147,16 +145,6 @@ Then, create and start the Jellyseerr container:
|
||||
<TabItem value="docker-cli" label="Docker CLI">
|
||||
```bash
|
||||
docker run -d --name jellyseerr -e LOG_LEVEL=debug -e TZ=Asia/Tashkent -p 5055:5055 -v "jellyseerr-data:/app/config" --restart unless-stopped fallenbagel/jellyseerr:latest
|
||||
```
|
||||
|
||||
#### Updating:
|
||||
Pull the latest image:
|
||||
```bash
|
||||
docker compose pull jellyseerr
|
||||
```
|
||||
Then, restart all services defined in the Compose file:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
@@ -179,16 +167,6 @@ services:
|
||||
volumes:
|
||||
jellyseerr-data:
|
||||
external: true
|
||||
```
|
||||
|
||||
#### Updating:
|
||||
Pull the latest image:
|
||||
```bash
|
||||
docker compose pull jellyseerr
|
||||
```
|
||||
Then, restart all services defined in the Compose file:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -207,6 +185,3 @@ Docker on Windows works differently than it does on Linux; it runs Docker inside
|
||||
**If you must run Docker on Windows, you should put the `/app/config` directory mount inside the VM and not on the Windows host.** (This also applies to other containers with SQLite databases.)
|
||||
|
||||
Named volumes, like in the example commands above, are automatically mounted inside the VM. Therefore the warning on the setup about the `/app/config` folder being incorrectly mounted page should be ignored.
|
||||
:::
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
title: Kubernetes
|
||||
description: Install Jellyseerr in Kubernetes
|
||||
sidebar_position: 5
|
||||
---
|
||||
# Kubernetes
|
||||
:::info
|
||||
This method is not recommended for most users. It is intended for advanced users who are using Kubernetes.
|
||||
:::
|
||||
|
||||
## Installation
|
||||
```console
|
||||
helm install jellyseerr oci://ghcr.io/fallenbagel/jellyseerr/jellyseerr-chart
|
||||
```
|
||||
Helm values can be found in the Jellyseerr repository under [charts/jellyseerr-chart/README.md](https://github.com/Fallenbagel/jellyseerr/tree/develop/charts/jellyseerr-chart).
|
||||
|
||||
Verify the signature with [cosign](https://docs.sigstore.dev/cosign/system_config/installation/) (replace [tag], with the TAG you want to verify) :
|
||||
```console
|
||||
cosign verify ghcr.io/fallenbagel/jellyseerr/jellyseerr-chart:[tag] --certificate-identity=https://github.com/Fallenbagel/jellyseerr/.github/workflows/helm.yml@refs/heads/main --certificate-oidc-issuer=https://token.ac
|
||||
tions.githubusercontent.com
|
||||
```
|
||||
@@ -6,8 +6,6 @@ sidebar_position: 3
|
||||
|
||||
import { JellyseerrVersion, NixpkgVersion } from '@site/src/components/JellyseerrVersion';
|
||||
import Admonition from '@theme/Admonition';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Nix Package Manager (Advanced)
|
||||
:::info
|
||||
@@ -15,55 +13,22 @@ This method is not recommended for most users. It is intended for advanced users
|
||||
:::
|
||||
|
||||
export const VersionMismatchWarning = () => {
|
||||
let jellyseerrVersion = null;
|
||||
let nixpkgVersions = null;
|
||||
try {
|
||||
jellyseerrVersion = JellyseerrVersion();
|
||||
nixpkgVersions = NixpkgVersion();
|
||||
} catch (err) {
|
||||
return (
|
||||
<Admonition type="error">
|
||||
Failed to load version information. Error: {err.message || JSON.stringify(err)}
|
||||
</Admonition>
|
||||
);
|
||||
}
|
||||
const jellyseerrVersion = JellyseerrVersion();
|
||||
const nixpkgVersion = NixpkgVersion();
|
||||
|
||||
if (!nixpkgVersions || nixpkgVersions.error) {
|
||||
return (
|
||||
<Admonition type="error">
|
||||
Failed to fetch Nixpkg versions: {nixpkgVersions?.error || 'Unknown error'}
|
||||
</Admonition>
|
||||
);
|
||||
}
|
||||
|
||||
const isUnstableUpToDate = jellyseerrVersion === nixpkgVersions.unstable;
|
||||
const isStableUpToDate = jellyseerrVersion === nixpkgVersions.stable;
|
||||
const isUpToDate = jellyseerrVersion === nixpkgVersion;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isStableUpToDate ? (
|
||||
<Admonition type="warning">
|
||||
The{' '}
|
||||
<a href="https://github.com/NixOS/nixpkgs/blob/nixos-24.11/pkgs/servers/jellyseerr/default.nix#L14">
|
||||
upstream Jellyseerr Nix Package (v{nixpkgVersions.stable})
|
||||
</a>{' '}
|
||||
is not <b>up-to-date</b>. If you want to use <b>Jellyseerr v{jellyseerrVersion}</b>,{' '}
|
||||
{isUnstableUpToDate ? (
|
||||
<>
|
||||
consider using the{' '}
|
||||
<a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/by-name/je/jellyseerr/package.nix">
|
||||
unstable package
|
||||
</a>{' '}
|
||||
instead.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
you will need to{' '}
|
||||
<a href="#overriding-the-package-derivation">override the package derivation</a>.
|
||||
</>
|
||||
)}
|
||||
</Admonition>
|
||||
) : null}
|
||||
<>
|
||||
{!isUpToDate ? (
|
||||
<Admonition type="warning">
|
||||
The <a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/servers/jellyseerr/default.nix#L14">upstream Jellyseerr Nix Package (v{nixpkgVersion})</a> is not <b>up-to-date</b>. If you want to use <b>Jellyseerr v{jellyseerrVersion}</b>, you will need to <a href="#overriding-the-package-derivation">override the package derivation</a>.
|
||||
</Admonition>
|
||||
) : (
|
||||
<Admonition type="success">
|
||||
The <a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/servers/jellyseerr/default.nix#L14">upstream Jellyseerr Nix Package (v{nixpkgVersion})</a> is <b>up-to-date</b> with <b>Jellyseerr v{jellyseerrVersion}</b>.
|
||||
</Admonition>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -83,8 +48,6 @@ To get up and running with jellyseerr using Nix, you can add the following to yo
|
||||
|
||||
If you want more advanced configuration options, you can use the following:
|
||||
|
||||
<Tabs groupId="nixpkg-methods" queryString>
|
||||
<TabItem value="default" label="Default Configurations">
|
||||
```nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
@@ -93,20 +56,53 @@ If you want more advanced configuration options, you can use the following:
|
||||
enable = true;
|
||||
port = 5055;
|
||||
openFirewall = true;
|
||||
package = pkgs.jellyseerr; # Use the unstable package if stable is not up-to-date
|
||||
};
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="custom" label="Database Configurations">
|
||||
In order to use postgres, you will need to add override the default module of jellyseerr with the following as the current default module is not compatible with postgres:
|
||||
```nix
|
||||
|
||||
After adding the configuration to your `configuration.nix`, you can run the following command to install jellyseerr:
|
||||
|
||||
```bash
|
||||
nixos-rebuild switch
|
||||
```
|
||||
After rebuild is complete jellyseerr should be running, verify that it is with the following command.
|
||||
```bash
|
||||
systemctl status jellyseerr
|
||||
```
|
||||
|
||||
:::info
|
||||
You can now access Jellyseerr by visiting `http://localhost:5055` in your web browser.
|
||||
:::
|
||||
|
||||
|
||||
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
|
||||
## Overriding the package derivation
|
||||
export const VersionMatch = () => {
|
||||
const jellyseerrVersion = JellyseerrVersion();
|
||||
const nixpkgVersion = NixpkgVersion();
|
||||
|
||||
const code = `{ config, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
nixpkgs.config.packageOverrides = pkgs: {
|
||||
jellyseerr = pkgs.jellyseerr.overrideAttrs (oldAttrs: rec {
|
||||
version = "${jellyseerrVersion}";
|
||||
|
||||
src = pkgs.fetchFromGitHub {
|
||||
rev = "v\${version}";
|
||||
sha256 = pkgs.lib.fakeSha256;
|
||||
};
|
||||
|
||||
offlineCache = pkgs.fetchYarnDeps {
|
||||
sha256 = pkgs.lib.fakeSha256;
|
||||
};
|
||||
});
|
||||
};
|
||||
}`;
|
||||
|
||||
const module = `{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.jellyseerr;
|
||||
@@ -117,65 +113,28 @@ in
|
||||
disabledModules = [ "services/misc/jellyseerr.nix" ];
|
||||
|
||||
options.services.jellyseerr = {
|
||||
enable = mkEnableOption ''Jellyseerr, a requests manager for Jellyfin'';
|
||||
enable = mkEnableOption (mdDoc ''Jellyseerr, a requests manager for Jellyfin'');
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''Open port in the firewall for the Jellyseerr web interface.'';
|
||||
description = mdDoc ''Open port in the firewall for the Jellyseerr web interface.'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 5055;
|
||||
description = ''The port which the Jellyseerr web UI should listen to.'';
|
||||
description = mdDoc ''The port which the Jellyseerr web UI should listen to.'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jellyseerr;
|
||||
defaultText = literalExpression "pkgs.jellyseerr";
|
||||
description = ''
|
||||
Jellyseerr package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
databaseConfig = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {
|
||||
type = "sqlite";
|
||||
configDirectory = "config";
|
||||
logQueries = "false";
|
||||
type = types.package;
|
||||
default = pkgs.jellyseerr;
|
||||
defaultText = literalExpression "pkgs.jellyseerr";
|
||||
description = lib.mdDoc ''
|
||||
Jellyseerr package to use.
|
||||
'';
|
||||
};
|
||||
description = ''
|
||||
Database configuration. For "sqlite", only "type", "configDirectory", and "logQueries" are relevant.
|
||||
For "postgres", include host, port, user, pass, name, and optionally socket.
|
||||
Example:
|
||||
{
|
||||
type = "postgres";
|
||||
socket = "/run/postgresql";
|
||||
user = "jellyseerr";
|
||||
name = "jellyseerr";
|
||||
logQueries = "false";
|
||||
}
|
||||
or
|
||||
{
|
||||
type = "postgres";
|
||||
host = "localhost";
|
||||
port = "5432";
|
||||
user = "dbuser";
|
||||
pass = "password";
|
||||
name = "jellyseerr";
|
||||
logQueries = "false";
|
||||
}
|
||||
or
|
||||
{
|
||||
type = "sqlite";
|
||||
configDirectory = "config";
|
||||
logQueries = "false";
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@@ -183,29 +142,14 @@ in
|
||||
description = "Jellyseerr, a requests manager for Jellyfin";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment =
|
||||
let
|
||||
dbConfig = cfg.databaseConfig;
|
||||
in
|
||||
{
|
||||
PORT = toString cfg.port;
|
||||
DB_TYPE = toString dbConfig.type;
|
||||
CONFIG_DIRECTORY = toString dbConfig.configDirectory or "";
|
||||
DB_LOG_QUERIES = toString dbConfig.logQueries;
|
||||
DB_HOST = if dbConfig.type == "postgres" && !(hasAttr "socket" dbConfig) then toString dbConfig.host or "" else "";
|
||||
DB_PORT = if dbConfig.type == "postgres" && !(hasAttr "socket" dbConfig) then toString dbConfig.port or "" else "";
|
||||
DB_SOCKET_PATH = if dbConfig.type == "postgres" && hasAttr "socket" dbConfig then toString dbConfig.socket or "" else "";
|
||||
DB_USER = if dbConfig.type == "postgres" then toString dbConfig.user or "" else "";
|
||||
DB_PASS = if dbConfig.type == "postgres" then toString dbConfig.pass or "" else "";
|
||||
DB_NAME = if dbConfig.type == "postgres" then toString dbConfig.name or "" else "";
|
||||
};
|
||||
environment.PORT = toString cfg.port;
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
StateDirectory = "jellyseerr";
|
||||
WorkingDirectory = "${cfg.package}/libexec/jellyseerr";
|
||||
WorkingDirectory = "\${cfg.package}/libexec/jellyseerr/deps/jellyseerr";
|
||||
DynamicUser = true;
|
||||
ExecStart = "${cfg.package}/bin/jellyseerr";
|
||||
BindPaths = [ "/var/lib/jellyseerr/:${cfg.package}/libexec/jellyseerr/config/" ];
|
||||
ExecStart = "\${cfg.package}/bin/jellyseerr";
|
||||
BindPaths = [ "/var/lib/jellyseerr/:\${cfg.package}/libexec/jellyseerr/deps/jellyseerr/config/" ];
|
||||
Restart = "on-failure";
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "strict";
|
||||
@@ -225,47 +169,57 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
|
||||
};
|
||||
}
|
||||
```
|
||||
Then, import the module into your `configuration.nix`:
|
||||
```nix
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ./modules/jellyseerr.nix ];
|
||||
|
||||
services.jellyseerr = {
|
||||
enable = true;
|
||||
port = 5055;
|
||||
openFirewall = true;
|
||||
package = pkgs.unstable.jellyseerr; # use the unstable package if stable is not up-to-date
|
||||
databaseConfig = {
|
||||
type = "postgres";
|
||||
host = "localhost"; # or socket: "/run/postgresql"
|
||||
port = "5432"; # if using socket, this is not needed
|
||||
user = "jellyseerr";
|
||||
pass = "jellyseerr";
|
||||
name = "jellyseerr";
|
||||
logQueries = "false";
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
};
|
||||
}`;
|
||||
|
||||
After adding the configuration to your `configuration.nix`, you can run the following command to install jellyseerr:
|
||||
const configuration = `{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [ ./jellyseerr-module.nix ]
|
||||
|
||||
```bash
|
||||
nixos-rebuild switch
|
||||
```
|
||||
After rebuild is complete jellyseerr should be running, verify that it is with the following command.
|
||||
```bash
|
||||
systemctl status jellyseerr
|
||||
```
|
||||
services.jellyseerr = {
|
||||
enable = true;
|
||||
port = 5055;
|
||||
openFirewall = true;
|
||||
package = (pkgs.callPackage (import ../../../pkgs/jellyseerr) { });
|
||||
};
|
||||
}`;
|
||||
|
||||
:::info
|
||||
You can now access Jellyseerr by visiting `http://localhost:5055` in your web browser.
|
||||
:::
|
||||
const isUpToDate = jellyseerrVersion === nixpkgVersion;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isUpToDate ? (
|
||||
<>
|
||||
<p>The latest version of Jellyseerr <strong>({jellyseerrVersion})</strong> and the Jellyseerr nixpkg package version <strong>({nixpkgVersion})</strong> is <strong>up-to-date</strong>.</p>
|
||||
<p>There is no need to override the package derivation.</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>The latest version of Jellyseerr <strong>({jellyseerrVersion})</strong> and the Jellyseerr nixpkg version <strong>(v{nixpkgVersion})</strong> is <strong>out-of-date</strong>.
|
||||
If you want to use <b>Jellyseerr v{jellyseerrVersion}</b>, you will need to override the package derivation.</p>
|
||||
<p>In order to override the package derivation:</p>
|
||||
<ol>
|
||||
<li style={{ marginBottom: '1rem' }}>Grab the <a href="https://raw.githubusercontent.com/NixOS/nixpkgs/nixos-unstable/pkgs/servers/jellyseerr/default.nix">latest nixpkg derivation for Jellyseerr</a></li>
|
||||
<li style={{ marginBottom: '1rem' }}>Grab the latest <a href="https://raw.githubusercontent.com/Fallenbagel/jellyseerr/main/package.json">package.json</a> for Jellyseerr</li>
|
||||
<li style={{ marginBottom: '1rem' }}>Add it to the same directory as the nixpkg derivation</li>
|
||||
<li style={{ marginBottom: '1rem' }}>Update the `src` and `offlineCache` attributes in the nixpkg derivation:</li>
|
||||
<CodeBlock className="language-nix" style={{ marginBottom: '1rem' }}>{code}</CodeBlock>
|
||||
<Admonition type="tip" style={{ marginBottom: '1rem' }}>You can replace the <b>sha256</b> with the actual hash that <b>nixos-rebuild</b> outputs when you run the command.</Admonition>
|
||||
<li style={{ marginBottom: '1rem' }}>Grab this module and import it in your `configuration.nix`</li>
|
||||
<CodeBlock className="language-nix" style={{ marginBottom: '1rem' }}>{module}</CodeBlock>
|
||||
<Admonition type="tip" style={{ marginBottom: '1rem' }}>We are using a custom module because the upstream module does not have a package option.</Admonition>
|
||||
<li style={{ marginBottom: '1rem' }}>Call the new package in your `configuration.nix`</li>
|
||||
<CodeBlock className="language-nix" style={{ marginBottom: '1rem' }}>{configuration}</CodeBlock>
|
||||
</ol>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
<VersionMatch />
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
title: Backups
|
||||
description: Understand which data you should back up.
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Which data does Jellyseerr save and where?
|
||||
|
||||
## Settings
|
||||
|
||||
All configurations from the **Settings** panel in the Jellyseerr web UI are saved, including integrations with Radarr, Sonarr, Jellyfin, Plex, and notification settings.
|
||||
These settings are stored in the `settings.json` file located in the Jellyseerr data folder.
|
||||
|
||||
## User Data
|
||||
|
||||
Apart from the settings, all other data—including user accounts, media requests, blacklist etc. are stored in the database (either SQLite or PostgreSQL).
|
||||
|
||||
# Backup
|
||||
|
||||
### SQLite
|
||||
|
||||
If your backup system uses filesystem snapshots (such as Kubernetes with Volsync), you can directly back up the Jellyseerr data folder.
|
||||
Otherwise, you need to stop the Jellyseerr application and back up the `config` folder.
|
||||
|
||||
For advanced users, it's possible to back up the database without stopping the application by using the [SQLite CLI](https://www.sqlite.org/download.html). Run the following command to create a backup:
|
||||
|
||||
```bash
|
||||
sqlite3 db/db.sqlite3 ".backup '/tmp/jellyseerr_db.sqlite3.bak'"
|
||||
```
|
||||
|
||||
Then, copy the `/tmp/jellyseerr_dump.sqlite3.bak` file to your desired backup location.
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
You can back up the `config` folder and dump the PostgreSQL database without stopping the Jellyseerr application.
|
||||
|
||||
Install [postgresql-client](https://www.postgresql.org/download/) and run the following command to create a backup (just replace the placeholders):
|
||||
|
||||
:::info
|
||||
Depending on how your PostgreSQL instance is configured, you may need to add these options to the command below.
|
||||
|
||||
-h, --host=HOSTNAME database server host or socket directory
|
||||
|
||||
-p, --port=PORT database server port number
|
||||
:::
|
||||
|
||||
```bash
|
||||
pg_dump -U <database_user> -d <database_name> -f /tmp/jellyseerr_db.sql
|
||||
```
|
||||
|
||||
# Restore
|
||||
|
||||
### SQLite
|
||||
|
||||
After restoring your `db/db.sqlite3` file and, optionally, the `settings.json` file, the `config` folder structure should look like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── cache <-- Optional
|
||||
├── db
|
||||
│ └── db.sqlite3
|
||||
├── logs <-- Optional
|
||||
└── settings.json <-- Optional (required if you want to avoid reconfiguring Jellyseerr)
|
||||
```
|
||||
|
||||
Once the files are restored, start the Jellyseerr application.
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
Install the [PostgreSQL client](https://www.postgresql.org/download/) and restore the PostgreSQL database using the following command (replace the placeholders accordingly):
|
||||
|
||||
:::info
|
||||
Depending on how your PostgreSQL instance is configured, you may need to add these options to the command below.
|
||||
|
||||
-h, --host=HOSTNAME database server host or socket directory
|
||||
|
||||
-p, --port=PORT database server port number
|
||||
:::
|
||||
|
||||
```bash
|
||||
pg_restore -U <database_user> -d <database_name> /tmp/jellyseerr_db.sql
|
||||
```
|
||||
|
||||
Optionally, restore the `settings.json` file. The `config` folder structure should look like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── cache <-- Optional
|
||||
├── logs <-- Optional
|
||||
└── settings.json <-- Optional (required if you want to avoid reconfiguring Jellyseerr)
|
||||
```
|
||||
|
||||
Once the database and files are restored, start the Jellyseerr application.
|
||||
@@ -14,14 +14,6 @@ When disabled, your mediaserver OAuth becomes the only sign-in option, and any "
|
||||
|
||||
This setting is **enabled** by default.
|
||||
|
||||
## Enable Jellyfin/Emby/Plex Sign-In
|
||||
|
||||
When enabled, users will be able to sign in to Jellyseerr using their Jellyfin/Emby/Plex credentials, provided they have linked their media server accounts.
|
||||
|
||||
When disabled, users will only be able to sign in using their email address. Users without a password set will not be able to sign in to Jellyseerr.
|
||||
|
||||
This setting is **enabled** by default.
|
||||
|
||||
## Enable New Jellyfin/Emby/Plex Sign-In
|
||||
|
||||
When enabled, users with access to your media server will be able to sign in to Jellyseerr even if they have not yet been imported. Users will be automatically assigned the permissions configured in the [Default Permissions](#default-permissions) setting upon first sign-in.
|
||||
|
||||
@@ -11,7 +11,7 @@ const config: Config = {
|
||||
baseUrl: '/',
|
||||
trailingSlash: false,
|
||||
|
||||
organizationName: 'fallenbagel',
|
||||
organizationName: 'Fallenbagel',
|
||||
projectName: 'Jellyseerr',
|
||||
deploymentBranch: 'gh-pages',
|
||||
|
||||
@@ -32,7 +32,7 @@ const config: Config = {
|
||||
routeBasePath: '/',
|
||||
path: '../docs',
|
||||
editUrl:
|
||||
'https://github.com/fallenbagel/jellyseerr/edit/develop/docs/',
|
||||
'https://github.com/Fallenbagel/jellyseerr/edit/develop/docs/',
|
||||
},
|
||||
blog: false,
|
||||
pages: false,
|
||||
@@ -70,7 +70,7 @@ const config: Config = {
|
||||
},
|
||||
items: [
|
||||
{
|
||||
href: 'https://github.com/fallenbagel/jellyseerr',
|
||||
href: 'https://github.com/Fallenbagel/jellyseerr',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
|
||||
@@ -47,6 +47,6 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0"
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,37 +26,25 @@ export const JellyseerrVersion = () => {
|
||||
};
|
||||
|
||||
export const NixpkgVersion = () => {
|
||||
const [versions, setVersions] = useState(null);
|
||||
const [version, setVersion] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVersion = async () => {
|
||||
try {
|
||||
const unstableUrl =
|
||||
'https://raw.githubusercontent.com/NixOS/nixpkgs/refs/heads/nixos-unstable/pkgs/by-name/je/jellyseerr/package.nix';
|
||||
const stableUrl =
|
||||
'https://raw.githubusercontent.com/NixOS/nixpkgs/refs/heads/nixos-24.11/pkgs/servers/jellyseerr/default.nix';
|
||||
|
||||
const [unstableResponse, stableResponse] = await Promise.all([
|
||||
fetch(unstableUrl),
|
||||
fetch(stableUrl),
|
||||
]);
|
||||
|
||||
const unstableData = await unstableResponse.text();
|
||||
const stableData = await stableResponse.text();
|
||||
const url =
|
||||
'https://raw.githubusercontent.com/NixOS/nixpkgs/nixos-unstable/pkgs/servers/jellyseerr/default.nix';
|
||||
const response = await fetch(url);
|
||||
const data = await response.text();
|
||||
|
||||
const versionRegex = /version\s*=\s*"([^"]+)"/;
|
||||
|
||||
const unstableMatch = unstableData.match(versionRegex);
|
||||
const stableMatch = stableData.match(versionRegex);
|
||||
|
||||
const unstableVersion =
|
||||
unstableMatch && unstableMatch[1] ? unstableMatch[1] : '0.0.0';
|
||||
const stableVersion =
|
||||
stableMatch && stableMatch[1] ? stableMatch[1] : '0.0.0';
|
||||
|
||||
setVersions({ unstable: unstableVersion, stable: stableVersion });
|
||||
const match = data.match(versionRegex);
|
||||
if (match && match[1]) {
|
||||
setVersion(match[1]);
|
||||
} else {
|
||||
setError('0.0.0');
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
@@ -75,5 +63,5 @@ export const NixpkgVersion = () => {
|
||||
return { error };
|
||||
}
|
||||
|
||||
return versions;
|
||||
return version;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ module.exports = {
|
||||
{ hostname: 'gravatar.com' },
|
||||
{ hostname: 'image.tmdb.org' },
|
||||
{ hostname: 'artworks.thetvdb.com' },
|
||||
{ hostname: 'plex.tv' },
|
||||
],
|
||||
},
|
||||
webpack(config) {
|
||||
|
||||
@@ -191,12 +191,6 @@ components:
|
||||
enableSpecialEpisodes:
|
||||
type: boolean
|
||||
example: false
|
||||
forceIpv4First:
|
||||
type: boolean
|
||||
example: false
|
||||
dnsServers:
|
||||
type: string
|
||||
example: '1.1.1.1'
|
||||
PlexLibrary:
|
||||
type: object
|
||||
properties:
|
||||
@@ -7024,12 +7018,6 @@ paths:
|
||||
description: Updates an Override Rule from the request body.
|
||||
tags:
|
||||
- overriderule
|
||||
parameters:
|
||||
- in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: number
|
||||
responses:
|
||||
'200':
|
||||
description: 'Values were successfully updated'
|
||||
|
||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellyseerr",
|
||||
"version": "0.1.0",
|
||||
"version": "2.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -42,7 +42,6 @@
|
||||
"@supercharge/request-ip": "1.2.0",
|
||||
"@svgr/webpack": "6.5.1",
|
||||
"@tanem/react-nprogress": "5.0.30",
|
||||
"@types/wink-jaro-distance": "^2.0.2",
|
||||
"ace-builds": "1.15.2",
|
||||
"bcrypt": "5.1.0",
|
||||
"bowser": "2.11.0",
|
||||
@@ -86,7 +85,6 @@
|
||||
"react-spring": "9.7.1",
|
||||
"react-tailwindcss-datepicker-sct": "1.3.4",
|
||||
"react-toast-notifications": "2.5.1",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-truncate-markup": "5.1.2",
|
||||
"react-use-clipboard": "1.0.9",
|
||||
"reflect-metadata": "0.1.13",
|
||||
@@ -96,11 +94,9 @@
|
||||
"sqlite3": "5.1.4",
|
||||
"swagger-ui-express": "4.6.2",
|
||||
"swr": "2.2.5",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"typeorm": "0.3.11",
|
||||
"undici": "^6.20.1",
|
||||
"web-push": "3.5.0",
|
||||
"wink-jaro-distance": "^2.0.0",
|
||||
"winston": "3.8.2",
|
||||
"winston-daily-rotate-file": "4.7.1",
|
||||
"xml2js": "0.4.23",
|
||||
@@ -127,7 +123,7 @@
|
||||
"@types/express-session": "1.17.6",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/mime": "3",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "20.14.8",
|
||||
"@types/node-schedule": "2.1.0",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/react": "^18.3.3",
|
||||
@@ -173,7 +169,7 @@
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.0.0",
|
||||
"node": "^20.0.0",
|
||||
"pnpm": "^9.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
@@ -239,8 +235,7 @@
|
||||
"COMMIT_TAG": "$GIT_SHA"
|
||||
},
|
||||
"imageNames": [
|
||||
"fallenbagel/jellyseerr",
|
||||
"ghcr.io/fallenbagel/jellyseerr"
|
||||
"fallenbagel/jellyseerr"
|
||||
],
|
||||
"platforms": [
|
||||
"linux/amd64",
|
||||
|
||||
1856
pnpm-lock.yaml
generated
1856
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
@@ -69,13 +69,10 @@ class ExternalAPI {
|
||||
ttl?: number,
|
||||
config?: RequestInit
|
||||
): Promise<T> {
|
||||
const headers = { ...this.defaultHeaders, ...config?.headers };
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
...this.params,
|
||||
...params,
|
||||
headers,
|
||||
});
|
||||
|
||||
const cachedItem = this.cache?.get<T>(cacheKey);
|
||||
if (cachedItem) {
|
||||
return cachedItem;
|
||||
@@ -84,7 +81,10 @@ class ExternalAPI {
|
||||
const url = this.formatUrl(endpoint, params);
|
||||
const response = await this.fetch(url, {
|
||||
...config,
|
||||
headers,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...config?.headers,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -111,13 +111,10 @@ class ExternalAPI {
|
||||
ttl?: number,
|
||||
config?: RequestInit
|
||||
): Promise<T> {
|
||||
const headers = { ...this.defaultHeaders, ...config?.headers };
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
config: { ...this.params, ...params },
|
||||
headers,
|
||||
data,
|
||||
});
|
||||
|
||||
const cachedItem = this.cache?.get<T>(cacheKey);
|
||||
if (cachedItem) {
|
||||
return cachedItem;
|
||||
@@ -127,7 +124,10 @@ class ExternalAPI {
|
||||
const response = await this.fetch(url, {
|
||||
method: 'POST',
|
||||
...config,
|
||||
headers,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...config?.headers,
|
||||
},
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -155,13 +155,10 @@ class ExternalAPI {
|
||||
ttl?: number,
|
||||
config?: RequestInit
|
||||
): Promise<T> {
|
||||
const headers = { ...this.defaultHeaders, ...config?.headers };
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
config: { ...this.params, ...params },
|
||||
data,
|
||||
headers,
|
||||
});
|
||||
|
||||
const cachedItem = this.cache?.get<T>(cacheKey);
|
||||
if (cachedItem) {
|
||||
return cachedItem;
|
||||
@@ -171,7 +168,10 @@ class ExternalAPI {
|
||||
const response = await this.fetch(url, {
|
||||
method: 'PUT',
|
||||
...config,
|
||||
headers,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...config?.headers,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -227,11 +227,9 @@ class ExternalAPI {
|
||||
config?: RequestInit,
|
||||
overwriteBaseUrl?: string
|
||||
): Promise<T> {
|
||||
const headers = { ...this.defaultHeaders, ...config?.headers };
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
...this.params,
|
||||
...params,
|
||||
headers,
|
||||
});
|
||||
const cachedItem = this.cache?.get<T>(cacheKey);
|
||||
|
||||
@@ -246,7 +244,10 @@ class ExternalAPI {
|
||||
const url = this.formatUrl(endpoint, params, overwriteBaseUrl);
|
||||
this.fetch(url, {
|
||||
...config,
|
||||
headers,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...config?.headers,
|
||||
},
|
||||
}).then(async (response) => {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -269,7 +270,10 @@ class ExternalAPI {
|
||||
const url = this.formatUrl(endpoint, params, overwriteBaseUrl);
|
||||
const response = await this.fetch(url, {
|
||||
...config,
|
||||
headers,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...config?.headers,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
@@ -289,14 +293,6 @@ class ExternalAPI {
|
||||
return data;
|
||||
}
|
||||
|
||||
protected removeCache(endpoint: string, options?: Record<string, string>) {
|
||||
const cacheKey = this.serializeCacheKey(endpoint, {
|
||||
...this.params,
|
||||
...options,
|
||||
});
|
||||
this.cache?.del(cacheKey);
|
||||
}
|
||||
|
||||
private formatUrl(
|
||||
endpoint: string,
|
||||
params?: Record<string, string>,
|
||||
@@ -321,13 +317,13 @@ class ExternalAPI {
|
||||
|
||||
private serializeCacheKey(
|
||||
endpoint: string,
|
||||
options?: Record<string, unknown>
|
||||
params?: Record<string, unknown>
|
||||
) {
|
||||
if (!options) {
|
||||
if (!params) {
|
||||
return `${this.baseUrl}${endpoint}`;
|
||||
}
|
||||
|
||||
return `${this.baseUrl}${endpoint}${JSON.stringify(options)}`;
|
||||
return `${this.baseUrl}${endpoint}${JSON.stringify(params)}`;
|
||||
}
|
||||
|
||||
private async getDataFromResponse(response: Response) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ExternalAPI from '@server/api/externalapi';
|
||||
import cacheManager from '@server/lib/cache';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import jaro from 'wink-jaro-distance';
|
||||
|
||||
interface RTAlgoliaSearchResponse {
|
||||
results: {
|
||||
@@ -16,7 +15,7 @@ interface RTAlgoliaHit {
|
||||
tmsId: string;
|
||||
type: string;
|
||||
title: string;
|
||||
titles?: string[];
|
||||
titles: string[];
|
||||
description: string;
|
||||
releaseYear: number;
|
||||
rating: string;
|
||||
@@ -25,9 +24,9 @@ interface RTAlgoliaHit {
|
||||
isEmsSearchable: boolean;
|
||||
rtId: number;
|
||||
vanity: string;
|
||||
aka?: string[];
|
||||
aka: string[];
|
||||
posterImageUrl: string;
|
||||
rottenTomatoes?: {
|
||||
rottenTomatoes: {
|
||||
audienceScore: number;
|
||||
criticsIconUrl: string;
|
||||
wantToSeeCount: number;
|
||||
@@ -48,47 +47,6 @@ export interface RTRating {
|
||||
url: string;
|
||||
}
|
||||
|
||||
// Tunables
|
||||
const INEXACT_TITLE_FACTOR = 0.25;
|
||||
const ALTERNATE_TITLE_FACTOR = 0.8;
|
||||
const PER_YEAR_PENALTY = 0.4;
|
||||
const MINIMUM_SCORE = 0.175;
|
||||
|
||||
// Normalization for title comparisons.
|
||||
// Lowercase and strip non-alphanumeric (unicode-aware).
|
||||
const norm = (s: string): string =>
|
||||
s.toLowerCase().replace(/[^\p{L}\p{N} ]/gu, '');
|
||||
|
||||
// Title similarity. 1 if exact, quarter-jaro otherwise.
|
||||
const similarity = (a: string, b: string): number =>
|
||||
a === b ? 1 : jaro(a, b).similarity * INEXACT_TITLE_FACTOR;
|
||||
|
||||
// Gets the best similarity score between the searched title and all alternate
|
||||
// titles of the search result. Non-main titles are penalized.
|
||||
const t_score = ({ title, titles, aka }: RTAlgoliaHit, s: string): number => {
|
||||
const f = (t: string, i: number) =>
|
||||
similarity(norm(t), norm(s)) * (i ? ALTERNATE_TITLE_FACTOR : 1);
|
||||
return Math.max(...[title].concat(aka || [], titles || []).map(f));
|
||||
};
|
||||
|
||||
// Year difference to score: 0 -> 1.0, 1 -> 0.6, 2 -> 0.2, 3+ -> 0.0
|
||||
const y_score = (r: RTAlgoliaHit, y?: number): number =>
|
||||
y ? Math.max(0, 1 - Math.abs(r.releaseYear - y) * PER_YEAR_PENALTY) : 1;
|
||||
|
||||
// Cut score in half if result has no ratings.
|
||||
const extra_score = (r: RTAlgoliaHit): number => (r.rottenTomatoes ? 1 : 0.5);
|
||||
|
||||
// Score search result as product of all subscores
|
||||
const score = (r: RTAlgoliaHit, name: string, year?: number): number =>
|
||||
t_score(r, name) * y_score(r, year) * extra_score(r);
|
||||
|
||||
// Score each search result and return the highest scoring result, if any
|
||||
const best = (rs: RTAlgoliaHit[], name: string, year?: number): RTAlgoliaHit =>
|
||||
rs
|
||||
.map((r) => ({ score: score(r, name, year), result: r }))
|
||||
.filter(({ score }) => score > MINIMUM_SCORE)
|
||||
.sort(({ score: a }, { score: b }) => b - a)[0]?.result;
|
||||
|
||||
/**
|
||||
* This is a best-effort API. The Rotten Tomatoes API is technically
|
||||
* private and getting access costs money/requires approval.
|
||||
@@ -132,21 +90,47 @@ class RottenTomatoes extends ExternalAPI {
|
||||
year: number
|
||||
): Promise<RTRating | null> {
|
||||
try {
|
||||
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"movie"');
|
||||
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
||||
requests: [
|
||||
{
|
||||
indexName: 'content_rt',
|
||||
query: name.replace(/\bthe\b ?/gi, ''),
|
||||
params: `filters=${filters}&hitsPerPage=20`,
|
||||
query: name,
|
||||
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
||||
const movie = best(contentResults?.hits || [], name, year);
|
||||
|
||||
if (!movie?.rottenTomatoes) return null;
|
||||
if (!contentResults) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// First, attempt to match exact name and year
|
||||
let movie = contentResults.hits.find(
|
||||
(movie) => movie.releaseYear === year && movie.title === name
|
||||
);
|
||||
|
||||
// If we don't find a movie, try to match partial name and year
|
||||
if (!movie) {
|
||||
movie = contentResults.hits.find(
|
||||
(movie) => movie.releaseYear === year && movie.title.includes(name)
|
||||
);
|
||||
}
|
||||
|
||||
// If we still dont find a movie, try to match just on year
|
||||
if (!movie) {
|
||||
movie = contentResults.hits.find((movie) => movie.releaseYear === year);
|
||||
}
|
||||
|
||||
// One last try, try exact name match only
|
||||
if (!movie) {
|
||||
movie = contentResults.hits.find((movie) => movie.title === name);
|
||||
}
|
||||
|
||||
if (!movie?.rottenTomatoes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
title: movie.title,
|
||||
@@ -174,21 +158,33 @@ class RottenTomatoes extends ExternalAPI {
|
||||
year?: number
|
||||
): Promise<RTRating | null> {
|
||||
try {
|
||||
const filters = encodeURIComponent('isEmsSearchable=1 AND type:"tv"');
|
||||
const data = await this.post<RTAlgoliaSearchResponse>('/queries', {
|
||||
requests: [
|
||||
{
|
||||
indexName: 'content_rt',
|
||||
query: name,
|
||||
params: `filters=${filters}&hitsPerPage=20`,
|
||||
params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const contentResults = data.results.find((r) => r.index === 'content_rt');
|
||||
const tvshow = best(contentResults?.hits || [], name, year);
|
||||
|
||||
if (!tvshow?.rottenTomatoes) return null;
|
||||
if (!contentResults) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let tvshow: RTAlgoliaHit | undefined = contentResults.hits[0];
|
||||
|
||||
if (year) {
|
||||
tvshow = contentResults.hits.find(
|
||||
(series) => series.releaseYear === year
|
||||
);
|
||||
}
|
||||
|
||||
if (!tvshow || !tvshow.rottenTomatoes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
title: tvshow.title,
|
||||
|
||||
@@ -230,23 +230,6 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
|
||||
throw new Error(`[Radarr] Failed to remove movie: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
public clearCache = ({
|
||||
tmdbId,
|
||||
externalId,
|
||||
}: {
|
||||
tmdbId?: number | null;
|
||||
externalId?: number | null;
|
||||
}) => {
|
||||
if (tmdbId) {
|
||||
this.removeCache('/movie/lookup', {
|
||||
term: `tmdb:${tmdbId}`,
|
||||
});
|
||||
}
|
||||
if (externalId) {
|
||||
this.removeCache(`/movie/${externalId}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default RadarrAPI;
|
||||
|
||||
@@ -353,30 +353,6 @@ class SonarrAPI extends ServarrBase<{
|
||||
throw new Error(`[Radarr] Failed to remove serie: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
public clearCache = ({
|
||||
tvdbId,
|
||||
externalId,
|
||||
title,
|
||||
}: {
|
||||
tvdbId?: number | null;
|
||||
externalId?: number | null;
|
||||
title?: string | null;
|
||||
}) => {
|
||||
if (tvdbId) {
|
||||
this.removeCache('/series/lookup', {
|
||||
term: `tvdb:${tvdbId}`,
|
||||
});
|
||||
}
|
||||
if (externalId) {
|
||||
this.removeCache(`/series/${externalId}`);
|
||||
}
|
||||
if (title) {
|
||||
this.removeCache('/series/lookup', {
|
||||
term: title,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SonarrAPI;
|
||||
|
||||
@@ -108,7 +108,7 @@ class TheMovieDb extends ExternalAPI {
|
||||
super(
|
||||
'https://api.themoviedb.org/3',
|
||||
{
|
||||
api_key: '431a8708161bcd1f1fbe7536137e61ed',
|
||||
api_key: 'db55323b8d3e4154498498a75642b381',
|
||||
},
|
||||
{
|
||||
nodeCache: cacheManager.getCache('tmdb').data,
|
||||
|
||||
@@ -4,7 +4,6 @@ export enum ApiErrorCode {
|
||||
InvalidAuthToken = 'INVALID_AUTH_TOKEN',
|
||||
InvalidEmail = 'INVALID_EMAIL',
|
||||
NotAdmin = 'NOT_ADMIN',
|
||||
NoAdminUser = 'NO_ADMIN_USER',
|
||||
SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS',
|
||||
SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES',
|
||||
Unknown = 'UNKNOWN',
|
||||
|
||||
@@ -68,10 +68,8 @@ const prodConfig: DataSourceOptions = {
|
||||
|
||||
const postgresDevConfig: DataSourceOptions = {
|
||||
type: 'postgres',
|
||||
host: process.env.DB_SOCKET_PATH || process.env.DB_HOST,
|
||||
port: process.env.DB_SOCKET_PATH
|
||||
? undefined
|
||||
: parseInt(process.env.DB_PORT ?? '5432'),
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT ?? '5432'),
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME ?? 'jellyseerr',
|
||||
@@ -86,10 +84,8 @@ const postgresDevConfig: DataSourceOptions = {
|
||||
|
||||
const postgresProdConfig: DataSourceOptions = {
|
||||
type: 'postgres',
|
||||
host: process.env.DB_SOCKET_PATH || process.env.DB_HOST,
|
||||
port: process.env.DB_SOCKET_PATH
|
||||
? undefined
|
||||
: parseInt(process.env.DB_PORT ?? '5432'),
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT ?? '5432'),
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME ?? 'jellyseerr',
|
||||
|
||||
@@ -7,7 +7,6 @@ import type {
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/themoviedb/interfaces';
|
||||
import {
|
||||
MediaRequestStatus,
|
||||
MediaStatus,
|
||||
@@ -208,50 +207,28 @@ export class MediaRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply overrides if the user is not an admin or has the "advanced request" permission
|
||||
const useOverrides = !user.hasPermission([Permission.MANAGE_REQUESTS], {
|
||||
type: 'or',
|
||||
});
|
||||
// Apply overrides if the user is not an admin or has the "auto approve" permission
|
||||
const useOverrides = !user.hasPermission(
|
||||
[
|
||||
requestBody.is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE,
|
||||
Permission.MANAGE_REQUESTS,
|
||||
],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
let rootFolder = requestBody.rootFolder;
|
||||
let profileId = requestBody.profileId;
|
||||
let tags = requestBody.tags;
|
||||
|
||||
if (useOverrides) {
|
||||
const defaultRadarrId = requestBody.is4k
|
||||
? settings.radarr.findIndex((r) => r.is4k && r.isDefault)
|
||||
: settings.radarr.findIndex((r) => !r.is4k && r.isDefault);
|
||||
const defaultSonarrId = requestBody.is4k
|
||||
? settings.sonarr.findIndex((s) => s.is4k && s.isDefault)
|
||||
: settings.sonarr.findIndex((s) => !s.is4k && s.isDefault);
|
||||
|
||||
const overrideRuleRepository = getRepository(OverrideRule);
|
||||
const overrideRules = await overrideRuleRepository.find({
|
||||
where:
|
||||
requestBody.mediaType === MediaType.MOVIE
|
||||
? { radarrServiceId: defaultRadarrId }
|
||||
: { sonarrServiceId: defaultSonarrId },
|
||||
? { radarrServiceId: requestBody.serverId }
|
||||
: { sonarrServiceId: requestBody.serverId },
|
||||
});
|
||||
|
||||
const appliedOverrideRules = overrideRules.filter((rule) => {
|
||||
const hasAnimeKeyword =
|
||||
'results' in tmdbMedia.keywords &&
|
||||
tmdbMedia.keywords.results.some(
|
||||
(keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
);
|
||||
|
||||
// Skip override rules if the media is an anime TV show as anime TV
|
||||
// is handled by default and override rules do not explicitly include
|
||||
// the anime keyword
|
||||
if (
|
||||
requestBody.mediaType === MediaType.TV &&
|
||||
hasAnimeKeyword &&
|
||||
(!rule.keywords ||
|
||||
!rule.keywords.split(',').map(Number).includes(ANIME_KEYWORD_ID))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
rule.users &&
|
||||
!rule.users
|
||||
@@ -280,59 +257,31 @@ export class MediaRequest {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
rule.keywords &&
|
||||
!rule.keywords.split(',').some((keywordId) => {
|
||||
let keywordList: TmdbKeyword[] = [];
|
||||
|
||||
if ('keywords' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.keywords;
|
||||
} else if ('results' in tmdbMedia.keywords) {
|
||||
keywordList = tmdbMedia.keywords.results;
|
||||
}
|
||||
|
||||
return keywordList
|
||||
.map((keyword: TmdbKeyword) => keyword.id)
|
||||
.includes(Number(keywordId));
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// hacky way to prioritize rules
|
||||
// TODO: make this better
|
||||
const prioritizedRule = appliedOverrideRules.sort((a, b) => {
|
||||
const keys: (keyof OverrideRule)[] = ['genre', 'language', 'keywords'];
|
||||
const overrideRootFolder = appliedOverrideRules.find(
|
||||
(rule) => rule.rootFolder
|
||||
)?.rootFolder;
|
||||
if (overrideRootFolder) {
|
||||
rootFolder = overrideRootFolder;
|
||||
}
|
||||
|
||||
const aSpecificity = keys.filter((key) => a[key] !== null).length;
|
||||
const bSpecificity = keys.filter((key) => b[key] !== null).length;
|
||||
const overrideProfileId = appliedOverrideRules.find(
|
||||
(rule) => rule.profileId
|
||||
)?.profileId;
|
||||
if (overrideProfileId) {
|
||||
profileId = overrideProfileId;
|
||||
}
|
||||
|
||||
// Take the rule with the most specific condition first
|
||||
return bSpecificity - aSpecificity;
|
||||
})[0];
|
||||
|
||||
if (prioritizedRule) {
|
||||
if (prioritizedRule.rootFolder) {
|
||||
rootFolder = prioritizedRule.rootFolder;
|
||||
}
|
||||
if (prioritizedRule.profileId) {
|
||||
profileId = prioritizedRule.profileId;
|
||||
}
|
||||
if (prioritizedRule.tags) {
|
||||
tags = [
|
||||
...new Set([
|
||||
...(tags || []),
|
||||
...prioritizedRule.tags.split(',').map((tag) => Number(tag)),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
logger.debug('Override rule applied.', {
|
||||
label: 'Media Request',
|
||||
overrides: prioritizedRule,
|
||||
});
|
||||
const overrideTags = appliedOverrideRules.find((rule) => rule.tags)?.tags;
|
||||
if (overrideTags) {
|
||||
tags = [
|
||||
...new Set([
|
||||
...(tags || []),
|
||||
...overrideTags.split(',').map((tag) => Number(tag)),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,14 +335,14 @@ export class MediaRequest {
|
||||
const tmdbMediaShow = tmdbMedia as Awaited<
|
||||
ReturnType<typeof tmdb.getTvShow>
|
||||
>;
|
||||
let requestedSeasons =
|
||||
const requestedSeasons =
|
||||
requestBody.seasons === 'all'
|
||||
? tmdbMediaShow.seasons.map((season) => season.season_number)
|
||||
? settings.main.enableSpecialEpisodes
|
||||
? tmdbMediaShow.seasons.map((season) => season.season_number)
|
||||
: tmdbMediaShow.seasons
|
||||
.map((season) => season.season_number)
|
||||
.filter((sn) => sn > 0)
|
||||
: (requestBody.seasons as number[]);
|
||||
if (!settings.main.enableSpecialEpisodes) {
|
||||
requestedSeasons = requestedSeasons.filter((sn) => sn > 0);
|
||||
}
|
||||
|
||||
let existingSeasons: number[] = [];
|
||||
|
||||
// We need to check existing requests on this title to make sure we don't double up on seasons that were
|
||||
@@ -719,15 +668,10 @@ export class MediaRequest {
|
||||
// Do not update the status if the item is already partially available or available
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !==
|
||||
MediaStatus.PARTIALLY_AVAILABLE &&
|
||||
media[this.is4k ? 'status4k' : 'status'] !== MediaStatus.PROCESSING
|
||||
MediaStatus.PARTIALLY_AVAILABLE
|
||||
) {
|
||||
const statusField = this.is4k ? 'status4k' : 'status';
|
||||
|
||||
await mediaRepository.update(
|
||||
{ id: this.media.id },
|
||||
{ [statusField]: MediaStatus.PROCESSING }
|
||||
);
|
||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.PROCESSING;
|
||||
mediaRepository.save(media);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1010,14 +954,6 @@ export class MediaRequest {
|
||||
);
|
||||
|
||||
this.sendNotification(media, Notification.MEDIA_FAILED);
|
||||
})
|
||||
.finally(() => {
|
||||
radarr.clearCache({
|
||||
tmdbId: movie.id,
|
||||
externalId: this.is4k
|
||||
? media.externalServiceId4k
|
||||
: media.externalServiceId,
|
||||
});
|
||||
});
|
||||
logger.info('Sent request to Radarr', {
|
||||
label: 'Media Request',
|
||||
@@ -1275,23 +1211,19 @@ export class MediaRequest {
|
||||
throw new Error('Media data not found');
|
||||
}
|
||||
|
||||
const updateFields = {
|
||||
[this.is4k ? 'externalServiceId4k' : 'externalServiceId']:
|
||||
sonarrSeries.id,
|
||||
[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']:
|
||||
sonarrSeries.titleSlug,
|
||||
[this.is4k ? 'serviceId4k' : 'serviceId']: sonarrSettings?.id,
|
||||
};
|
||||
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
||||
sonarrSeries.id;
|
||||
media[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] =
|
||||
sonarrSeries.titleSlug;
|
||||
media[this.is4k ? 'serviceId4k' : 'serviceId'] = sonarrSettings?.id;
|
||||
|
||||
await mediaRepository.update({ id: this.media.id }, updateFields);
|
||||
await mediaRepository.save(media);
|
||||
})
|
||||
.catch(async () => {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
await requestRepository.update(
|
||||
{ id: this.id },
|
||||
{ status: MediaRequestStatus.FAILED }
|
||||
);
|
||||
this.status = MediaRequestStatus.FAILED;
|
||||
await requestRepository.save(this);
|
||||
|
||||
logger.warn(
|
||||
'Something went wrong sending series request to Sonarr, marking status as FAILED',
|
||||
@@ -1304,15 +1236,6 @@ export class MediaRequest {
|
||||
);
|
||||
|
||||
this.sendNotification(media, Notification.MEDIA_FAILED);
|
||||
})
|
||||
.finally(() => {
|
||||
sonarr.clearCache({
|
||||
tvdbId,
|
||||
externalId: this.is4k
|
||||
? media.externalServiceId4k
|
||||
: media.externalServiceId,
|
||||
title: series.name,
|
||||
});
|
||||
});
|
||||
logger.info('Sent request to Sonarr', {
|
||||
label: 'Media Request',
|
||||
|
||||
@@ -83,13 +83,13 @@ export class User {
|
||||
@Column({ nullable: true })
|
||||
public jellyfinUserId?: string;
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
@Column({ nullable: true })
|
||||
public jellyfinDeviceId?: string;
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
@Column({ nullable: true })
|
||||
public jellyfinAuthToken?: string;
|
||||
|
||||
@Column({ nullable: true, select: false })
|
||||
@Column({ nullable: true })
|
||||
public plexToken?: string;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
|
||||
@@ -41,6 +41,11 @@ import path from 'path';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import YAML from 'yamljs';
|
||||
|
||||
if (process.env.forceIpv4First === 'true') {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
net.setDefaultAutoSelectFamily(false);
|
||||
}
|
||||
|
||||
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
||||
|
||||
logger.info(`Starting Overseerr version ${getAppVersion()}`);
|
||||
@@ -74,18 +79,6 @@ app
|
||||
const settings = await getSettings().load();
|
||||
restartFlag.initializeSettings(settings.main);
|
||||
|
||||
// Check if we force IPv4 first
|
||||
if (process.env.forceIpv4First === 'true' || settings.main.forceIpv4First) {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
net.setDefaultAutoSelectFamily(false);
|
||||
}
|
||||
|
||||
if (settings.main.dnsServers.trim() !== '') {
|
||||
dns.setServers(
|
||||
settings.main.dnsServers.split(',').map((server) => server.trim())
|
||||
);
|
||||
}
|
||||
|
||||
// Register HTTP proxy
|
||||
if (settings.main.proxy.enabled) {
|
||||
await createCustomProxyAgent(settings.main.proxy);
|
||||
|
||||
@@ -30,7 +30,6 @@ export interface PublicSettingsResponse {
|
||||
applicationUrl: string;
|
||||
hideAvailable: boolean;
|
||||
localLogin: boolean;
|
||||
mediaServerLogin: boolean;
|
||||
movie4kEnabled: boolean;
|
||||
series4kEnabled: boolean;
|
||||
discoverRegion: string;
|
||||
|
||||
@@ -70,35 +70,6 @@ export const startJobs = (): void => {
|
||||
running: () => plexFullScanner.status().running,
|
||||
cancelFn: () => plexFullScanner.cancel(),
|
||||
});
|
||||
|
||||
scheduledJobs.push({
|
||||
id: 'plex-refresh-token',
|
||||
name: 'Plex Refresh Token',
|
||||
type: 'process',
|
||||
interval: 'fixed',
|
||||
cronSchedule: jobs['plex-refresh-token'].schedule,
|
||||
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
|
||||
logger.info('Starting scheduled job: Plex Refresh Token', {
|
||||
label: 'Jobs',
|
||||
});
|
||||
refreshToken.run();
|
||||
}),
|
||||
});
|
||||
|
||||
// Watchlist Sync
|
||||
scheduledJobs.push({
|
||||
id: 'plex-watchlist-sync',
|
||||
name: 'Plex Watchlist Sync',
|
||||
type: 'process',
|
||||
interval: 'seconds',
|
||||
cronSchedule: jobs['plex-watchlist-sync'].schedule,
|
||||
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
|
||||
logger.info('Starting scheduled job: Plex Watchlist Sync', {
|
||||
label: 'Jobs',
|
||||
});
|
||||
watchlistSync.syncWatchlist();
|
||||
}),
|
||||
});
|
||||
} else if (
|
||||
mediaServerType === MediaServerType.JELLYFIN ||
|
||||
mediaServerType === MediaServerType.EMBY
|
||||
@@ -141,6 +112,21 @@ export const startJobs = (): void => {
|
||||
});
|
||||
}
|
||||
|
||||
// Watchlist Sync
|
||||
scheduledJobs.push({
|
||||
id: 'plex-watchlist-sync',
|
||||
name: 'Plex Watchlist Sync',
|
||||
type: 'process',
|
||||
interval: 'seconds',
|
||||
cronSchedule: jobs['plex-watchlist-sync'].schedule,
|
||||
job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => {
|
||||
logger.info('Starting scheduled job: Plex Watchlist Sync', {
|
||||
label: 'Jobs',
|
||||
});
|
||||
watchlistSync.syncWatchlist();
|
||||
}),
|
||||
});
|
||||
|
||||
// Run full radarr scan every 24 hours
|
||||
scheduledJobs.push({
|
||||
id: 'radarr-scan',
|
||||
@@ -237,5 +223,19 @@ export const startJobs = (): void => {
|
||||
}),
|
||||
});
|
||||
|
||||
scheduledJobs.push({
|
||||
id: 'plex-refresh-token',
|
||||
name: 'Plex Refresh Token',
|
||||
type: 'process',
|
||||
interval: 'fixed',
|
||||
cronSchedule: jobs['plex-refresh-token'].schedule,
|
||||
job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => {
|
||||
logger.info('Starting scheduled job: Plex Refresh Token', {
|
||||
label: 'Jobs',
|
||||
});
|
||||
refreshToken.run();
|
||||
}),
|
||||
});
|
||||
|
||||
logger.info('Scheduled jobs loaded', { label: 'Jobs' });
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ class SonarrScanner
|
||||
const filteredSeasons = sonarrSeries.seasons.filter(
|
||||
(sn) =>
|
||||
tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) &&
|
||||
(!settings.main.enableSpecialEpisodes ? sn.seasonNumber !== 0 : true)
|
||||
(!settings.main.partialRequestsEnabled ? sn.seasonNumber !== 0 : true)
|
||||
);
|
||||
|
||||
for (const season of filteredSeasons) {
|
||||
|
||||
@@ -124,7 +124,6 @@ export interface MainSettings {
|
||||
};
|
||||
hideAvailable: boolean;
|
||||
localLogin: boolean;
|
||||
mediaServerLogin: boolean;
|
||||
newPlexLogin: boolean;
|
||||
discoverRegion: string;
|
||||
streamingRegion: string;
|
||||
@@ -133,8 +132,6 @@ export interface MainSettings {
|
||||
mediaServerType: number;
|
||||
partialRequestsEnabled: boolean;
|
||||
enableSpecialEpisodes: boolean;
|
||||
forceIpv4First: boolean;
|
||||
dnsServers: string;
|
||||
locale: string;
|
||||
proxy: ProxySettings;
|
||||
}
|
||||
@@ -148,7 +145,6 @@ interface FullPublicSettings extends PublicSettings {
|
||||
applicationUrl: string;
|
||||
hideAvailable: boolean;
|
||||
localLogin: boolean;
|
||||
mediaServerLogin: boolean;
|
||||
movie4kEnabled: boolean;
|
||||
series4kEnabled: boolean;
|
||||
discoverRegion: string;
|
||||
@@ -342,7 +338,6 @@ class Settings {
|
||||
},
|
||||
hideAvailable: false,
|
||||
localLogin: true,
|
||||
mediaServerLogin: true,
|
||||
newPlexLogin: true,
|
||||
discoverRegion: '',
|
||||
streamingRegion: '',
|
||||
@@ -351,8 +346,6 @@ class Settings {
|
||||
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
||||
partialRequestsEnabled: true,
|
||||
enableSpecialEpisodes: false,
|
||||
forceIpv4First: false,
|
||||
dnsServers: '',
|
||||
locale: 'en',
|
||||
proxy: {
|
||||
enabled: false,
|
||||
@@ -585,8 +578,6 @@ class Settings {
|
||||
applicationUrl: this.data.main.applicationUrl,
|
||||
hideAvailable: this.data.main.hideAvailable,
|
||||
localLogin: this.data.main.localLogin,
|
||||
mediaServerLogin: this.data.main.mediaServerLogin,
|
||||
jellyfinExternalHost: this.data.jellyfin.externalHostname,
|
||||
jellyfinForgotPasswordUrl: this.data.jellyfin.jellyfinForgotPasswordUrl,
|
||||
movie4kEnabled: this.data.radarr.some(
|
||||
(radarr) => radarr.is4k && radarr.isDefault
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import type { AllSettings } from '@server/lib/settings';
|
||||
|
||||
const migrateRegionSetting = (settings: any): AllSettings => {
|
||||
if (
|
||||
settings.main.discoverRegion !== undefined &&
|
||||
settings.main.streamingRegion !== undefined
|
||||
) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const oldRegion = settings.main.region;
|
||||
if (oldRegion) {
|
||||
settings.main.discoverRegion = oldRegion;
|
||||
|
||||
@@ -56,9 +56,8 @@ authRoutes.post('/plex', async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (
|
||||
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
|
||||
(settings.main.mediaServerLogin === false ||
|
||||
settings.main.mediaServerType != MediaServerType.PLEX)
|
||||
settings.main.mediaServerType != MediaServerType.PLEX &&
|
||||
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED
|
||||
) {
|
||||
return res.status(500).json({ error: 'Plex login is disabled' });
|
||||
}
|
||||
@@ -232,13 +231,10 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
|
||||
//Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured
|
||||
if (
|
||||
// media server not configured, allow login for setup
|
||||
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
||||
settings.main.mediaServerType !== MediaServerType.EMBY &&
|
||||
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
|
||||
(settings.main.mediaServerLogin === false ||
|
||||
// media server is neither jellyfin or emby
|
||||
(settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
||||
settings.main.mediaServerType !== MediaServerType.EMBY &&
|
||||
settings.jellyfin.ip !== ''))
|
||||
settings.jellyfin.ip !== ''
|
||||
) {
|
||||
return res.status(500).json({ error: 'Jellyfin login is disabled' });
|
||||
}
|
||||
@@ -267,7 +263,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
|
||||
let user = await userRepository.findOne({
|
||||
where: { jellyfinUsername: body.username },
|
||||
select: { id: true, jellyfinDeviceId: true },
|
||||
});
|
||||
|
||||
let deviceId = '';
|
||||
@@ -318,7 +313,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
body.serverType !== MediaServerType.JELLYFIN &&
|
||||
body.serverType !== MediaServerType.EMBY
|
||||
) {
|
||||
throw new ApiError(500, ApiErrorCode.NoAdminUser);
|
||||
throw new Error('select_server_type');
|
||||
}
|
||||
settings.main.mediaServerType = body.serverType;
|
||||
|
||||
@@ -538,22 +533,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
message: e.errorCode,
|
||||
});
|
||||
|
||||
case ApiErrorCode.NoAdminUser:
|
||||
logger.warn(
|
||||
'Failed login attempt from user without admin permissions and no admin user exists',
|
||||
{
|
||||
label: 'Auth',
|
||||
account: {
|
||||
ip: req.ip,
|
||||
email: body.username,
|
||||
},
|
||||
}
|
||||
);
|
||||
return next({
|
||||
status: e.statusCode,
|
||||
message: e.errorCode,
|
||||
});
|
||||
|
||||
default:
|
||||
logger.error(e.message, { label: 'Auth' });
|
||||
return next({
|
||||
|
||||
@@ -70,11 +70,11 @@ router.get('/', async (req, res, next) => {
|
||||
query = query
|
||||
.addSelect((subQuery) => {
|
||||
return subQuery
|
||||
.select('COUNT(request.id)', 'request_count')
|
||||
.select('COUNT(request.id)', 'requestCount')
|
||||
.from(MediaRequest, 'request')
|
||||
.where('request.requestedBy.id = user.id');
|
||||
}, 'request_count')
|
||||
.orderBy('request_count', 'DESC');
|
||||
}, 'requestCount')
|
||||
.orderBy('requestCount', 'DESC');
|
||||
break;
|
||||
default:
|
||||
query = query.orderBy('user.id', 'ASC');
|
||||
|
||||
@@ -14,9 +14,7 @@ class RestartFlag {
|
||||
return (
|
||||
this.settings.csrfProtection !== settings.csrfProtection ||
|
||||
this.settings.trustProxy !== settings.trustProxy ||
|
||||
this.settings.proxy.enabled !== settings.proxy.enabled ||
|
||||
this.settings.forceIpv4First !== settings.forceIpv4First ||
|
||||
this.settings.dnsServers !== settings.dnsServers
|
||||
this.settings.proxy.enabled !== settings.proxy.enabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- ***** BEGIN LICENSE BLOCK *****
|
||||
- Part of the Jellyfin project (https://jellyfin.media)
|
||||
-
|
||||
- All copyright belongs to the Jellyfin contributors; a full list can
|
||||
- be found in the file CONTRIBUTORS.md
|
||||
-
|
||||
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
|
||||
- ***** END LICENSE BLOCK ***** -->
|
||||
<svg version="1.1" id="icon-transparent" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" gradientUnits="userSpaceOnUse" x1="110.25" y1="213.3" x2="496.14" y2="436.09">
|
||||
<stop offset="0" style="stop-color:#AA5CC3"/>
|
||||
<stop offset="1" style="stop-color:#00A4DC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>icon-transparent</title>
|
||||
<g id="icon-transparent">
|
||||
<path id="inner-shape" d="M256,201.6c-20.4,0-86.2,119.3-76.2,139.4s142.5,19.9,152.4,0S276.5,201.6,256,201.6z" fill="url(#linear-gradient)"/>
|
||||
<path id="outer-shape" d="M256,23.3c-61.6,0-259.8,359.4-229.6,420.1s429.3,60,459.2,0S317.6,23.3,256,23.3z
|
||||
M406.5,390.8c-19.6,39.3-281.1,39.8-300.9,0s110.1-275.3,150.4-275.3S426.1,351.4,406.5,390.8z" fill="url(#linear-gradient)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -1,6 +1,5 @@
|
||||
import type { ForwardedRef } from 'react';
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type ButtonType =
|
||||
| 'default'
|
||||
@@ -98,7 +97,7 @@ function Button<P extends ElementTypes = 'button'>(
|
||||
if (as === 'a') {
|
||||
return (
|
||||
<a
|
||||
className={twMerge(buttonStyle)}
|
||||
className={buttonStyle.join(' ')}
|
||||
{...(props as React.ComponentProps<'a'>)}
|
||||
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||
>
|
||||
@@ -108,7 +107,7 @@ function Button<P extends ElementTypes = 'button'>(
|
||||
} else {
|
||||
return (
|
||||
<button
|
||||
className={twMerge(buttonStyle)}
|
||||
className={buttonStyle.join(' ')}
|
||||
{...(props as React.ComponentProps<'button'>)}
|
||||
ref={ref as ForwardedRef<HTMLButtonElement>}
|
||||
>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Field } from 'formik';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
interface LabeledCheckboxProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
label: string;
|
||||
description: string;
|
||||
onChange: () => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const LabeledCheckbox: React.FC<LabeledCheckboxProps> = ({
|
||||
id,
|
||||
className,
|
||||
label,
|
||||
description,
|
||||
onChange,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className={twMerge('relative flex items-start', className)}>
|
||||
<div className="flex h-6 items-center">
|
||||
<Field type="checkbox" id={id} name={id} onChange={onChange} />
|
||||
</div>
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<label htmlFor="localLogin" className="block">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-white">{label}</span>
|
||||
<span className="font-normal text-gray-400">{description}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
/* can hold child checkboxes */
|
||||
children && <div className="mt-4 pl-10">{children}</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabeledCheckbox;
|
||||
@@ -1,39 +1,62 @@
|
||||
import Button from '@app/components/Common/Button';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
|
||||
import { InformationCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { MediaServerType, ServerType } from '@server/constants/server';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
loginwithapp: 'Login with {appName}',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
hostname: '{mediaServerName} URL',
|
||||
port: 'Port',
|
||||
enablessl: 'Use SSL',
|
||||
urlBase: 'URL Base',
|
||||
email: 'Email',
|
||||
emailtooltip:
|
||||
'Address does not need to be associated with your {mediaServerName} instance.',
|
||||
validationhostrequired: '{mediaServerName} URL required',
|
||||
validationhostformat: 'Valid URL required',
|
||||
validationemailrequired: 'Email required',
|
||||
validationemailformat: 'Valid email required',
|
||||
validationusernamerequired: 'Username required',
|
||||
validationpasswordrequired: 'Password required',
|
||||
validationservertyperequired: 'Please select a server type',
|
||||
validationHostnameRequired: 'You must provide a valid hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
validationUrlBaseLeadingSlash: 'URL base must have a leading slash',
|
||||
validationUrlBaseTrailingSlash: 'URL base must not end in a trailing slash',
|
||||
loginerror: 'Something went wrong while trying to sign in.',
|
||||
adminerror: 'You must use an admin account to sign in.',
|
||||
noadminerror: 'No admin user found on the server.',
|
||||
credentialerror: 'The username or password is incorrect.',
|
||||
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
|
||||
signingin: 'Signing In…',
|
||||
signingin: 'Signing in…',
|
||||
signin: 'Sign In',
|
||||
initialsigningin: 'Connecting…',
|
||||
initialsignin: 'Connect',
|
||||
forgotpassword: 'Forgot Password?',
|
||||
servertype: 'Server Type',
|
||||
back: 'Go back',
|
||||
});
|
||||
|
||||
interface JellyfinLoginProps {
|
||||
revalidate: () => void;
|
||||
initial?: boolean;
|
||||
serverType?: MediaServerType;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
revalidate,
|
||||
initial,
|
||||
serverType,
|
||||
onCancel,
|
||||
}) => {
|
||||
const toasts = useToasts();
|
||||
const intl = useIntl();
|
||||
@@ -48,29 +71,56 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
: 'Media Server',
|
||||
};
|
||||
|
||||
const LoginSchema = Yup.object().shape({
|
||||
username: Yup.string().required(
|
||||
intl.formatMessage(messages.validationusernamerequired)
|
||||
),
|
||||
password: Yup.string(),
|
||||
});
|
||||
const baseUrl = settings.currentSettings.jellyfinExternalHost
|
||||
? settings.currentSettings.jellyfinExternalHost
|
||||
: settings.currentSettings.jellyfinHost;
|
||||
const jellyfinForgotPasswordUrl =
|
||||
settings.currentSettings.jellyfinForgotPasswordUrl;
|
||||
if (initial) {
|
||||
const LoginSchema = Yup.object().shape({
|
||||
hostname: Yup.string().required(
|
||||
intl.formatMessage(
|
||||
messages.validationhostrequired,
|
||||
mediaServerFormatValues
|
||||
)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
urlBase: Yup.string()
|
||||
.test(
|
||||
'leading-slash',
|
||||
intl.formatMessage(messages.validationUrlBaseLeadingSlash),
|
||||
(value) => !value || value.startsWith('/')
|
||||
)
|
||||
.test(
|
||||
'trailing-slash',
|
||||
intl.formatMessage(messages.validationUrlBaseTrailingSlash),
|
||||
(value) => !value || !value.endsWith('/')
|
||||
),
|
||||
email: Yup.string()
|
||||
.email(intl.formatMessage(messages.validationemailformat))
|
||||
.required(intl.formatMessage(messages.validationemailrequired)),
|
||||
username: Yup.string().required(
|
||||
intl.formatMessage(messages.validationusernamerequired)
|
||||
),
|
||||
password: Yup.string(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
hostname: '',
|
||||
port: 8096,
|
||||
useSsl: false,
|
||||
urlBase: '',
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={LoginSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
// Check if serverType is either 'Jellyfin' or 'Emby'
|
||||
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
|
||||
// throw new Error('Invalid serverType'); // You can customize the error message
|
||||
// }
|
||||
|
||||
const res = await fetch('/api/v1/auth/jellyfin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -79,7 +129,12 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
body: JSON.stringify({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
email: values.username,
|
||||
hostname: values.hostname,
|
||||
port: values.port,
|
||||
useSsl: values.useSsl,
|
||||
urlBase: values.urlBase,
|
||||
email: values.email,
|
||||
serverType: serverType,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
||||
@@ -102,13 +157,11 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
case ApiErrorCode.NotAdmin:
|
||||
errorMessage = messages.adminerror;
|
||||
break;
|
||||
case ApiErrorCode.NoAdminUser:
|
||||
errorMessage = messages.noadminerror;
|
||||
break;
|
||||
default:
|
||||
errorMessage = messages.loginerror;
|
||||
break;
|
||||
}
|
||||
|
||||
toasts.addToast(
|
||||
intl.formatMessage(errorMessage, mediaServerFormatValues),
|
||||
{
|
||||
@@ -121,51 +174,282 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, touched, isSubmitting, isValid }) => {
|
||||
return (
|
||||
<>
|
||||
<Form>
|
||||
<div>
|
||||
<h2 className="mb-6 -mt-1 text-center text-lg font-bold text-neutral-200">
|
||||
{intl.formatMessage(messages.loginwithapp, {
|
||||
appName: mediaServerFormatValues.mediaServerName,
|
||||
})}
|
||||
</h2>
|
||||
|
||||
<div className="mt-1 mb-4">
|
||||
<div className="form-input-field">
|
||||
{({
|
||||
errors,
|
||||
touched,
|
||||
values,
|
||||
setFieldValue,
|
||||
isSubmitting,
|
||||
isValid,
|
||||
}) => (
|
||||
<Form>
|
||||
<div className="sm:border-t sm:border-gray-800">
|
||||
<div className="flex flex-col sm:flex-row sm:gap-4">
|
||||
<div className="w-full">
|
||||
<label htmlFor="hostname" className="text-label">
|
||||
{intl.formatMessage(
|
||||
messages.hostname,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mb-0 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
|
||||
{values.useSsl ? 'https://' : 'http://'}
|
||||
</span>
|
||||
<Field
|
||||
id="username"
|
||||
name="username"
|
||||
id="hostname"
|
||||
name="hostname"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.username)}
|
||||
className="!bg-gray-700/80 placeholder:text-gray-400"
|
||||
className="rounded-r-only flex-1"
|
||||
placeholder={intl.formatMessage(
|
||||
messages.hostname,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{errors.username && touched.username && (
|
||||
<div className="error">{errors.username}</div>
|
||||
{errors.hostname && touched.hostname && (
|
||||
<div className="error">{errors.hostname}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-1 mb-2">
|
||||
<div className="form-input-field">
|
||||
<SensitiveInput
|
||||
as="field"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder={intl.formatMessage(messages.password)}
|
||||
className="!bg-gray-700/80 placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label htmlFor="port" className="text-label">
|
||||
{intl.formatMessage(messages.port)}
|
||||
</label>
|
||||
<div className="mt-1 sm:mt-0">
|
||||
<Field
|
||||
id="port"
|
||||
name="port"
|
||||
inputMode="numeric"
|
||||
type="text"
|
||||
className="short flex-1"
|
||||
placeholder={intl.formatMessage(messages.port)}
|
||||
/>
|
||||
{errors.port && touched.port && (
|
||||
<div className="error">{errors.port}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="useSsl" className="text-label mt-2">
|
||||
{intl.formatMessage(messages.enablessl)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="useSsl"
|
||||
name="useSsl"
|
||||
type="checkbox"
|
||||
onChange={() => {
|
||||
setFieldValue('useSsl', !values.useSsl);
|
||||
setFieldValue('port', values.useSsl ? 8096 : 443);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="urlBase" className="text-label mt-1">
|
||||
{intl.formatMessage(messages.urlBase)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
type="text"
|
||||
inputMode="url"
|
||||
id="urlBase"
|
||||
name="urlBase"
|
||||
placeholder={intl.formatMessage(messages.urlBase)}
|
||||
/>
|
||||
</div>
|
||||
{errors.urlBase && touched.urlBase && (
|
||||
<div className="error">{errors.urlBase}</div>
|
||||
)}
|
||||
</div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-label inline-flex gap-1 align-middle"
|
||||
>
|
||||
{intl.formatMessage(messages.email)}
|
||||
<span className="label-tip">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(
|
||||
messages.emailtooltip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
>
|
||||
<span className="tooltip-trigger">
|
||||
<InformationCircleIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</label>
|
||||
<div className="mt-1 sm:col-span-2 sm:mb-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.email)}
|
||||
/>
|
||||
</div>
|
||||
{errors.email && touched.email && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="username" className="text-label">
|
||||
{intl.formatMessage(messages.username)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.username)}
|
||||
/>
|
||||
</div>
|
||||
{errors.username && touched.username && (
|
||||
<div className="error">{errors.username}</div>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flexrounded-md shadow-sm">
|
||||
<Field
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={intl.formatMessage(messages.password)}
|
||||
/>
|
||||
</div>
|
||||
{errors.password && touched.password && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</Button>
|
||||
</span>
|
||||
{onCancel && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button buttonType="default" onClick={() => onCancel()}>
|
||||
<FormattedMessage {...messages.back} />
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
} else {
|
||||
const LoginSchema = Yup.object().shape({
|
||||
username: Yup.string().required(
|
||||
intl.formatMessage(messages.validationusernamerequired)
|
||||
),
|
||||
password: Yup.string(),
|
||||
});
|
||||
const baseUrl = settings.currentSettings.jellyfinExternalHost
|
||||
? settings.currentSettings.jellyfinExternalHost
|
||||
: settings.currentSettings.jellyfinHost;
|
||||
const jellyfinForgotPasswordUrl =
|
||||
settings.currentSettings.jellyfinForgotPasswordUrl;
|
||||
return (
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
}}
|
||||
validationSchema={LoginSchema}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const res = await fetch('/api/v1/auth/jellyfin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
email: values.username,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
} catch (e) {
|
||||
toasts.addToast(
|
||||
intl.formatMessage(
|
||||
e.message == 'Request failed with status code 401'
|
||||
? messages.credentialerror
|
||||
: messages.loginerror
|
||||
),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
revalidate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, touched, isSubmitting, isValid }) => {
|
||||
return (
|
||||
<>
|
||||
<Form>
|
||||
<div className="sm:border-t sm:border-gray-800">
|
||||
<label htmlFor="username" className="text-label">
|
||||
{intl.formatMessage(messages.username)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.username)}
|
||||
/>
|
||||
</div>
|
||||
{errors.username && touched.username && (
|
||||
<div className="error">{errors.username}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={intl.formatMessage(messages.password)}
|
||||
/>
|
||||
</div>
|
||||
{errors.password && touched.password && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
<div className="flex-grow"></div>
|
||||
{baseUrl && (
|
||||
<a
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
as="a"
|
||||
buttonType="ghost"
|
||||
href={
|
||||
jellyfinForgotPasswordUrl
|
||||
? `${jellyfinForgotPasswordUrl}`
|
||||
@@ -176,35 +460,31 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
: ''
|
||||
}forgotpassword.html`
|
||||
}
|
||||
className="pt-2 text-sm text-indigo-500 hover:text-indigo-400"
|
||||
>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
</a>
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
className="mt-2 w-full shadow-sm"
|
||||
>
|
||||
<ArrowLeftOnRectangleIcon />
|
||||
<span>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</span>
|
||||
</Button>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default JellyfinLogin;
|
||||
|
||||
@@ -2,7 +2,10 @@ import Button from '@app/components/Common/Button';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
|
||||
import {
|
||||
ArrowLeftOnRectangleIcon,
|
||||
LifebuoyIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
@@ -10,7 +13,6 @@ import { useIntl } from 'react-intl';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
loginwithapp: 'Login with {appName}',
|
||||
username: 'Username',
|
||||
email: 'Email Address',
|
||||
password: 'Password',
|
||||
@@ -51,7 +53,6 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
password: '',
|
||||
}}
|
||||
validationSchema={LoginSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const res = await fetch('/api/v1/auth/local', {
|
||||
@@ -77,24 +78,19 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
<>
|
||||
<Form>
|
||||
<div>
|
||||
<h2 className="mb-6 -mt-1 text-center text-lg font-bold text-neutral-200">
|
||||
{intl.formatMessage(messages.loginwithapp, {
|
||||
appName: settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
</h2>
|
||||
|
||||
<div className="mt-1 mb-4">
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email) +
|
||||
' / ' +
|
||||
intl.formatMessage(messages.username)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder={`${intl.formatMessage(
|
||||
messages.email
|
||||
)} / ${intl.formatMessage(messages.username)}`}
|
||||
type="text"
|
||||
inputMode="email"
|
||||
data-testid="email"
|
||||
className="!bg-gray-700/80 placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
{errors.email &&
|
||||
@@ -103,35 +99,25 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 mb-2">
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<SensitiveInput
|
||||
as="field"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={intl.formatMessage(messages.password)}
|
||||
autoComplete="current-password"
|
||||
data-testid="password"
|
||||
className="!bg-gray-700/80 placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex">
|
||||
{errors.password &&
|
||||
touched.password &&
|
||||
typeof errors.password === 'string' && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
<div className="flex-grow"></div>
|
||||
{passwordResetEnabled && (
|
||||
<Link
|
||||
href="/resetpassword"
|
||||
className="pt-2 text-sm text-indigo-500 hover:text-indigo-400"
|
||||
>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
</Link>
|
||||
{errors.password &&
|
||||
touched.password &&
|
||||
typeof errors.password === 'string' && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{loginError && (
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
@@ -139,21 +125,37 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
data-testid="local-signin-button"
|
||||
className="mt-2 w-full shadow-sm"
|
||||
>
|
||||
<ArrowLeftOnRectangleIcon />
|
||||
<span>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</span>
|
||||
</Button>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
data-testid="local-signin-button"
|
||||
>
|
||||
<ArrowLeftOnRectangleIcon />
|
||||
<span>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
{passwordResetEnabled && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Link href="/resetpassword" passHref legacyBehavior>
|
||||
<Button as="a" buttonType="ghost">
|
||||
<LifebuoyIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import PlexIcon from '@app/assets/services/plex.svg';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
|
||||
import usePlexLogin from '@app/hooks/usePlexLogin';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
loginwithapp: 'Login with {appName}',
|
||||
});
|
||||
|
||||
interface PlexLoginButtonProps {
|
||||
onAuthToken: (authToken: string) => void;
|
||||
isProcessing?: boolean;
|
||||
onError?: (message: string) => void;
|
||||
large?: boolean;
|
||||
}
|
||||
|
||||
const PlexLoginButton = ({
|
||||
onAuthToken,
|
||||
onError,
|
||||
isProcessing,
|
||||
large,
|
||||
}: PlexLoginButtonProps) => {
|
||||
const { loading, login } = usePlexLogin({ onAuthToken, onError });
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="relative flex-1 border-[#cc7b19] bg-[rgba(204,123,25,0.3)] hover:border-[#cc7b19] hover:bg-[rgba(204,123,25,0.7)] disabled:opacity-50"
|
||||
onClick={login}
|
||||
disabled={loading || isProcessing}
|
||||
data-testid="plex-login-button"
|
||||
>
|
||||
{loading && (
|
||||
<div className="absolute right-0 mr-4 h-4 w-4">
|
||||
<SmallLoadingSpinner />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{large ? (
|
||||
<FormattedMessage
|
||||
{...messages.loginwithapp}
|
||||
values={{
|
||||
appName: <PlexIcon className="mt-[2px] ml-[0.35em] w-8" />,
|
||||
}}
|
||||
>
|
||||
{(chunks) => (
|
||||
<>
|
||||
{chunks.map((c) =>
|
||||
typeof c === 'string' ? <span>{c}</span> : c
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
) : (
|
||||
<PlexIcon className="w-8" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlexLoginButton;
|
||||
@@ -1,13 +1,9 @@
|
||||
import EmbyLogo from '@app/assets/services/emby-icon-only.svg';
|
||||
import JellyfinLogo from '@app/assets/services/jellyfin-icon.svg';
|
||||
import PlexLogo from '@app/assets/services/plex.svg';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Accordion from '@app/components/Common/Accordion';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import LanguagePicker from '@app/components/Layout/LanguagePicker';
|
||||
import JellyfinLogin from '@app/components/Login/JellyfinLogin';
|
||||
import LocalLogin from '@app/components/Login/LocalLogin';
|
||||
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
|
||||
import PlexLoginButton from '@app/components/PlexLoginButton';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
@@ -16,10 +12,10 @@ import { XCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { useRouter } from 'next/dist/client/router';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { CSSTransition, SwitchTransition } from 'react-transition-group';
|
||||
import useSWR from 'swr';
|
||||
import JellyfinLogin from './JellyfinLogin';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
signin: 'Sign In',
|
||||
@@ -27,21 +23,16 @@ const messages = defineMessages('components.Login', {
|
||||
signinwithplex: 'Use your Plex account',
|
||||
signinwithjellyfin: 'Use your {mediaServerName} account',
|
||||
signinwithoverseerr: 'Use your {applicationTitle} account',
|
||||
orsigninwith: 'Or sign in with',
|
||||
});
|
||||
|
||||
const Login = () => {
|
||||
const intl = useIntl();
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
const { user, revalidate } = useUser();
|
||||
|
||||
const [error, setError] = useState('');
|
||||
const [isProcessing, setProcessing] = useState(false);
|
||||
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
||||
const [mediaServerLogin, setMediaServerLogin] = useState(
|
||||
settings.currentSettings.mediaServerLogin
|
||||
);
|
||||
const { user, revalidate } = useUser();
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
|
||||
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
||||
// We take the token and attempt to sign in. If we get a success message, we will
|
||||
@@ -95,73 +86,14 @@ const Login = () => {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const mediaServerName =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined;
|
||||
|
||||
const MediaServerLogo =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? PlexLogo
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? JellyfinLogo
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? EmbyLogo
|
||||
: undefined;
|
||||
|
||||
const isJellyfin =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN ||
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY;
|
||||
const mediaServerLoginRef = useRef<HTMLDivElement>(null);
|
||||
const localLoginRef = useRef<HTMLDivElement>(null);
|
||||
const loginRef = mediaServerLogin ? mediaServerLoginRef : localLoginRef;
|
||||
|
||||
const loginFormVisible =
|
||||
(isJellyfin && settings.currentSettings.mediaServerLogin) ||
|
||||
settings.currentSettings.localLogin;
|
||||
const additionalLoginOptions = [
|
||||
settings.currentSettings.mediaServerLogin &&
|
||||
(settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
|
||||
<PlexLoginButton
|
||||
key="plex"
|
||||
isProcessing={isProcessing}
|
||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||
large={!isJellyfin && !settings.currentSettings.localLogin}
|
||||
/>
|
||||
) : (
|
||||
settings.currentSettings.localLogin &&
|
||||
(mediaServerLogin ? (
|
||||
<Button
|
||||
key="jellyseerr"
|
||||
data-testid="jellyseerr-login-button"
|
||||
className="flex-1 bg-transparent"
|
||||
onClick={() => setMediaServerLogin(false)}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/os_icon.svg"
|
||||
alt={settings.currentSettings.applicationTitle}
|
||||
className="mr-2 h-5"
|
||||
/>
|
||||
<span>{settings.currentSettings.applicationTitle}</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
key="mediaserver"
|
||||
data-testid="mediaserver-login-button"
|
||||
className="flex-1 bg-transparent"
|
||||
onClick={() => setMediaServerLogin(true)}
|
||||
>
|
||||
<MediaServerLogo />
|
||||
<span>{mediaServerName}</span>
|
||||
</Button>
|
||||
))
|
||||
)),
|
||||
].filter((o): o is JSX.Element => !!o);
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
|
||||
@@ -180,6 +112,9 @@ const Login = () => {
|
||||
<div className="relative h-48 w-full max-w-full">
|
||||
<Image src="/logo_stacked.svg" alt="Logo" fill />
|
||||
</div>
|
||||
<h2 className="mt-12 text-center text-3xl font-extrabold leading-9 text-gray-100">
|
||||
{intl.formatMessage(messages.signinheader)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div
|
||||
@@ -210,71 +145,65 @@ const Login = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<div className="px-10 py-8">
|
||||
<SwitchTransition mode="out-in">
|
||||
<CSSTransition
|
||||
key={mediaServerLogin ? 'ms' : 'local'}
|
||||
nodeRef={loginRef}
|
||||
addEndListener={(done) => {
|
||||
loginRef.current?.addEventListener(
|
||||
'transitionend',
|
||||
done,
|
||||
false
|
||||
);
|
||||
}}
|
||||
onEntered={() => {
|
||||
document
|
||||
.querySelector<HTMLInputElement>('#email, #username')
|
||||
?.focus();
|
||||
}}
|
||||
classNames={{
|
||||
appear: 'opacity-0',
|
||||
appearActive: 'transition-opacity duration-500 opacity-100',
|
||||
enter: 'opacity-0',
|
||||
enterActive: 'transition-opacity duration-500 opacity-100',
|
||||
exitActive: 'transition-opacity duration-0 opacity-0',
|
||||
}}
|
||||
>
|
||||
<div ref={loginRef} className="button-container">
|
||||
{isJellyfin &&
|
||||
(mediaServerLogin ||
|
||||
!settings.currentSettings.localLogin) ? (
|
||||
<JellyfinLogin
|
||||
serverType={settings.currentSettings.mediaServerType}
|
||||
revalidate={revalidate}
|
||||
/>
|
||||
) : (
|
||||
settings.currentSettings.localLogin && (
|
||||
<LocalLogin revalidate={revalidate} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
|
||||
{additionalLoginOptions.length > 0 &&
|
||||
(loginFormVisible ? (
|
||||
<div className="flex items-center py-5">
|
||||
<div className="flex-grow border-t border-gray-600"></div>
|
||||
<span className="mx-2 flex-shrink text-sm text-gray-400">
|
||||
{intl.formatMessage(messages.orsigninwith)}
|
||||
</span>
|
||||
<div className="flex-grow border-t border-gray-600"></div>
|
||||
</div>
|
||||
) : (
|
||||
<h2 className="mb-6 text-center text-lg font-bold text-neutral-200">
|
||||
{intl.formatMessage(messages.signinheader)}
|
||||
</h2>
|
||||
))}
|
||||
|
||||
<div
|
||||
className={`flex w-full flex-wrap gap-2 ${
|
||||
!loginFormVisible ? 'flex-col' : ''
|
||||
}`}
|
||||
>
|
||||
{additionalLoginOptions}
|
||||
</div>
|
||||
</div>
|
||||
<Accordion single atLeastOne>
|
||||
{({ openIndexes, handleClick, AccordionContent }) => (
|
||||
<>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 focus:outline-none sm:rounded-t-lg ${
|
||||
openIndexes.includes(0) && 'text-indigo-500'
|
||||
} ${
|
||||
settings.currentSettings.localLogin &&
|
||||
'hover:cursor-pointer hover:bg-gray-700'
|
||||
}`}
|
||||
onClick={() => handleClick(0)}
|
||||
disabled={!settings.currentSettings.localLogin}
|
||||
>
|
||||
{settings.currentSettings.mediaServerType ==
|
||||
MediaServerType.PLEX
|
||||
? intl.formatMessage(messages.signinwithplex)
|
||||
: intl.formatMessage(
|
||||
messages.signinwithjellyfin,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||
<div className="px-10 py-8">
|
||||
{settings.currentSettings.mediaServerType ==
|
||||
MediaServerType.PLEX ? (
|
||||
<PlexLoginButton
|
||||
isProcessing={isProcessing}
|
||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||
/>
|
||||
) : (
|
||||
<JellyfinLogin revalidate={revalidate} />
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
{settings.currentSettings.localLogin && (
|
||||
<div>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none ${
|
||||
openIndexes.includes(1)
|
||||
? 'text-indigo-500'
|
||||
: 'sm:rounded-b-lg'
|
||||
}`}
|
||||
onClick={() => handleClick(1)}
|
||||
>
|
||||
{intl.formatMessage(messages.signinwithoverseerr, {
|
||||
applicationTitle:
|
||||
settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(1)}>
|
||||
<div className="px-10 py-8">
|
||||
<LocalLogin revalidate={revalidate} />
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Accordion>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,14 +38,14 @@ import {
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
MinusCircleIcon,
|
||||
PlayIcon,
|
||||
StarIcon,
|
||||
TicketIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import {
|
||||
ChevronDoubleDownIcon,
|
||||
ChevronDoubleUpIcon,
|
||||
MinusCircleIcon,
|
||||
StarIcon,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import { type RatingResponse } from '@server/api/ratings';
|
||||
import { IssueStatus } from '@server/constants/issue';
|
||||
@@ -102,7 +102,7 @@ const messages = defineMessages('components.MovieDetails', {
|
||||
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
||||
watchlistDeleted:
|
||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||
watchlistError: 'Something went wrong. Please try again.',
|
||||
watchlistError: 'Something went wrong try again.',
|
||||
removefromwatchlist: 'Remove From Watchlist',
|
||||
addtowatchlist: 'Add To Watchlist',
|
||||
});
|
||||
@@ -1063,26 +1063,14 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
</div>
|
||||
)}
|
||||
{!!streamingProviders.length && (
|
||||
<div className="media-fact flex-col gap-1">
|
||||
<div className="media-fact">
|
||||
<span>{intl.formatMessage(messages.streamingproviders)}</span>
|
||||
<span className="media-fact-value flex flex-row flex-wrap gap-5">
|
||||
<span className="media-fact-value">
|
||||
{streamingProviders.map((p) => {
|
||||
return (
|
||||
<Tooltip content={p.name}>
|
||||
<span
|
||||
className="opacity-50 transition duration-300 hover:opacity-100"
|
||||
key={`provider-${p.id}`}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={'https://image.tmdb.org/t/p/w45/' + p.logoPath}
|
||||
alt={p.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-md"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="block" key={`provider-${p.id}`}>
|
||||
{p.name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
|
||||
66
src/components/PlexLoginButton/index.tsx
Normal file
66
src/components/PlexLoginButton/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import PlexOAuth from '@app/utils/plex';
|
||||
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.PlexLoginButton', {
|
||||
signinwithplex: 'Sign In',
|
||||
signingin: 'Signing In…',
|
||||
});
|
||||
|
||||
const plexOAuth = new PlexOAuth();
|
||||
|
||||
interface PlexLoginButtonProps {
|
||||
onAuthToken: (authToken: string) => void;
|
||||
isProcessing?: boolean;
|
||||
onError?: (message: string) => void;
|
||||
}
|
||||
|
||||
const PlexLoginButton = ({
|
||||
onAuthToken,
|
||||
onError,
|
||||
isProcessing,
|
||||
}: PlexLoginButtonProps) => {
|
||||
const intl = useIntl();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getPlexLogin = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const authToken = await plexOAuth.login();
|
||||
setLoading(false);
|
||||
onAuthToken(authToken);
|
||||
} catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<span className="block w-full rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
plexOAuth.preparePopup();
|
||||
setTimeout(() => getPlexLogin(), 1500);
|
||||
}}
|
||||
disabled={loading || isProcessing}
|
||||
className="plex-button"
|
||||
>
|
||||
<ArrowLeftOnRectangleIcon />
|
||||
<span>
|
||||
{loading
|
||||
? intl.formatMessage(globalMessages.loading)
|
||||
: isProcessing
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signinwithplex)}
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlexLoginButton;
|
||||
@@ -220,8 +220,8 @@ const RequestList = () => {
|
||||
</select>
|
||||
<Tooltip content={intl.formatMessage(messages.sortDirection)}>
|
||||
<Button
|
||||
buttonType="default"
|
||||
className="z-40 mr-2 rounded-l-none border !border-gray-500 !bg-gray-800 !px-3 !text-gray-500 hover:!bg-gray-400 hover:!text-white"
|
||||
buttonType="ghost"
|
||||
className="z-40 mr-2 rounded-l-none"
|
||||
buttonSize="md"
|
||||
onClick={() =>
|
||||
setCurrentSortDirection(
|
||||
@@ -230,9 +230,9 @@ const RequestList = () => {
|
||||
}
|
||||
>
|
||||
{currentSortDirection === 'asc' ? (
|
||||
<ArrowUpIcon className="h-6 w-6" />
|
||||
<ArrowUpIcon className="h-3" />
|
||||
) : (
|
||||
<ArrowDownIcon className="h-6 w-6" />
|
||||
<ArrowDownIcon className="h-3" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -256,8 +256,8 @@ const TvRequestModal = ({
|
||||
let allSeasons = (data?.seasons ?? []).filter(
|
||||
(season) => season.episodeCount !== 0
|
||||
);
|
||||
if (!settings.currentSettings.enableSpecialEpisodes) {
|
||||
allSeasons = allSeasons.filter((season) => season.seasonNumber > 0);
|
||||
if (!settings.currentSettings.partialRequestsEnabled) {
|
||||
allSeasons = allSeasons.filter((season) => season.seasonNumber !== 0);
|
||||
}
|
||||
return allSeasons.map((season) => season.seasonNumber);
|
||||
};
|
||||
|
||||
@@ -773,42 +773,38 @@ const RadarrModal = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{radarr && (
|
||||
<>
|
||||
<h3 className="mb-4 text-xl font-bold leading-8 text-gray-100">
|
||||
{intl.formatMessage(messages.overrideRules)}
|
||||
</h3>
|
||||
<ul className="grid gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 sm:gap-y-6 lg:grid-cols-2">
|
||||
{rules && (
|
||||
<OverrideRuleTile
|
||||
rules={rules}
|
||||
setOverrideRuleModal={setOverrideRuleModal}
|
||||
testResponse={testResponse}
|
||||
radarr={radarr}
|
||||
revalidate={revalidate}
|
||||
/>
|
||||
)}
|
||||
<li className="min-h-[8rem] rounded-lg border-2 border-dashed border-gray-400 shadow sm:min-h-[11rem]">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
onClick={() =>
|
||||
setOverrideRuleModal({
|
||||
open: true,
|
||||
rule: null,
|
||||
testResponse,
|
||||
})
|
||||
}
|
||||
disabled={!isValidated}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{intl.formatMessage(messages.addrule)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
<h3 className="mb-4 text-xl font-bold leading-8 text-gray-100">
|
||||
{intl.formatMessage(messages.overrideRules)}
|
||||
</h3>
|
||||
<ul className="grid grid-cols-2 gap-6">
|
||||
{rules && (
|
||||
<OverrideRuleTile
|
||||
rules={rules}
|
||||
setOverrideRuleModal={setOverrideRuleModal}
|
||||
testResponse={testResponse}
|
||||
radarr={radarr}
|
||||
revalidate={revalidate}
|
||||
/>
|
||||
)}
|
||||
<li className="min-h-[8rem] rounded-lg border-2 border-dashed border-gray-400 shadow sm:min-h-[11rem]">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
onClick={() =>
|
||||
setOverrideRuleModal({
|
||||
open: true,
|
||||
rule: null,
|
||||
testResponse,
|
||||
})
|
||||
}
|
||||
disabled={!isValidated}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{intl.formatMessage(messages.addrule)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -245,9 +245,7 @@ const SettingsLogs = () => {
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.logsDescription, {
|
||||
code: (msg: React.ReactNode) => (
|
||||
<code className="whitespace-normal break-words bg-opacity-50">
|
||||
{msg}
|
||||
</code>
|
||||
<code className="bg-opacity-50">{msg}</code>
|
||||
),
|
||||
appDataPath: appData ? appData.appDataPath : '/app/config',
|
||||
})}
|
||||
|
||||
@@ -57,12 +57,6 @@ const messages = defineMessages('components.Settings.SettingsMain', {
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
partialRequestsEnabled: 'Allow Partial Series Requests',
|
||||
enableSpecialEpisodes: 'Allow Special Episodes Requests',
|
||||
forceIpv4First: 'IPv4 Resolution First',
|
||||
forceIpv4FirstTip:
|
||||
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
|
||||
dnsServers: 'Custom DNS Servers',
|
||||
dnsServersTip:
|
||||
'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"',
|
||||
locale: 'Display Language',
|
||||
proxyEnabled: 'HTTP(S) Proxy',
|
||||
proxyHostname: 'Proxy Hostname',
|
||||
@@ -163,11 +157,9 @@ const SettingsMain = () => {
|
||||
locale: data?.locale ?? 'en',
|
||||
discoverRegion: data?.discoverRegion,
|
||||
originalLanguage: data?.originalLanguage,
|
||||
streamingRegion: data?.streamingRegion || 'US',
|
||||
streamingRegion: data?.streamingRegion,
|
||||
partialRequestsEnabled: data?.partialRequestsEnabled,
|
||||
enableSpecialEpisodes: data?.enableSpecialEpisodes,
|
||||
forceIpv4First: data?.forceIpv4First,
|
||||
dnsServers: data?.dnsServers,
|
||||
trustProxy: data?.trustProxy,
|
||||
cacheImages: data?.cacheImages,
|
||||
proxyEnabled: data?.proxy?.enabled,
|
||||
@@ -199,8 +191,6 @@ const SettingsMain = () => {
|
||||
originalLanguage: values.originalLanguage,
|
||||
partialRequestsEnabled: values.partialRequestsEnabled,
|
||||
enableSpecialEpisodes: values.enableSpecialEpisodes,
|
||||
forceIpv4First: values.forceIpv4First,
|
||||
dnsServers: values.dnsServers,
|
||||
trustProxy: values.trustProxy,
|
||||
cacheImages: values.cacheImages,
|
||||
proxy: {
|
||||
@@ -443,7 +433,7 @@ const SettingsMain = () => {
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field relative z-30">
|
||||
<div className="form-input-field">
|
||||
<LanguageSelector
|
||||
setFieldValue={setFieldValue}
|
||||
value={values.originalLanguage}
|
||||
@@ -459,9 +449,9 @@ const SettingsMain = () => {
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field relative z-20">
|
||||
<div className="form-input-field">
|
||||
<RegionSelector
|
||||
value={values.streamingRegion}
|
||||
value={values.streamingRegion || 'US'}
|
||||
name="streamingRegion"
|
||||
onChange={setFieldValue}
|
||||
regionType="streaming"
|
||||
@@ -534,55 +524,6 @@ const SettingsMain = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="forceIpv4First" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.forceIpv4First)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" className="mr-2" />
|
||||
<SettingsBadge badgeType="restartRequired" />
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.forceIpv4FirstTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="forceIpv4First"
|
||||
name="forceIpv4First"
|
||||
onChange={() => {
|
||||
setFieldValue('forceIpv4First', !values.forceIpv4First);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="dnsServers" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.dnsServers)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" className="mr-2" />
|
||||
<SettingsBadge badgeType="restartRequired" />
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.dnsServersTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="dnsServers"
|
||||
name="dnsServers"
|
||||
type="text"
|
||||
inputMode="url"
|
||||
/>
|
||||
</div>
|
||||
{errors.dnsServers &&
|
||||
touched.dnsServers &&
|
||||
typeof errors.dnsServers === 'string' && (
|
||||
<div className="error">{errors.dnsServers}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyEnabled" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
|
||||
@@ -350,10 +350,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
);
|
||||
if (!res.ok) throw new Error();
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
};
|
||||
@@ -439,6 +435,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
autoDismiss: true,
|
||||
appearance: 'success',
|
||||
});
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
if (toastId) {
|
||||
removeToast(toastId);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Button from '@app/components/Common/Button';
|
||||
import LabeledCheckbox from '@app/components/Common/LabeledCheckbox';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import PermissionEdit from '@app/components/PermissionEdit';
|
||||
@@ -14,7 +13,6 @@ 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.SettingsUsers', {
|
||||
users: 'Users',
|
||||
@@ -22,15 +20,9 @@ const messages = defineMessages('components.Settings.SettingsUsers', {
|
||||
userSettingsDescription: 'Configure global and default user settings.',
|
||||
toastSettingsSuccess: 'User settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
loginMethods: 'Login Methods',
|
||||
loginMethodsTip: 'Configure login methods for users.',
|
||||
localLogin: 'Enable Local Sign-In',
|
||||
localLoginTip:
|
||||
'Allow users to sign in using their email address and password',
|
||||
mediaServerLogin: 'Enable {mediaServerName} Sign-In',
|
||||
mediaServerLoginTip:
|
||||
'Allow users to sign in using their {mediaServerName} account',
|
||||
atLeastOneAuth: 'At least one authentication method must be selected.',
|
||||
'Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth',
|
||||
newPlexLogin: 'Enable New {mediaServerName} Sign-In',
|
||||
newPlexLoginTip:
|
||||
'Allow {mediaServerName} users to sign in without first being imported',
|
||||
@@ -50,27 +42,6 @@ const SettingsUsers = () => {
|
||||
} = useSWR<MainSettings>('/api/v1/settings/main');
|
||||
const settings = useSettings();
|
||||
|
||||
const schema = yup
|
||||
.object()
|
||||
.shape({
|
||||
localLogin: yup.boolean(),
|
||||
mediaServerLogin: yup.boolean(),
|
||||
})
|
||||
.test({
|
||||
name: 'atLeastOneAuth',
|
||||
test: function (values) {
|
||||
const isValid = ['localLogin', 'mediaServerLogin'].some(
|
||||
(field) => !!values[field]
|
||||
);
|
||||
|
||||
if (isValid) return true;
|
||||
return this.createError({
|
||||
path: 'localLogin | mediaServerLogin',
|
||||
message: intl.formatMessage(messages.atLeastOneAuth),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
@@ -81,8 +52,6 @@ const SettingsUsers = () => {
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
@@ -104,7 +73,6 @@ const SettingsUsers = () => {
|
||||
<Formik
|
||||
initialValues={{
|
||||
localLogin: data?.localLogin,
|
||||
mediaServerLogin: data?.mediaServerLogin,
|
||||
newPlexLogin: data?.newPlexLogin,
|
||||
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
|
||||
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
|
||||
@@ -112,7 +80,6 @@ const SettingsUsers = () => {
|
||||
tvQuotaDays: data?.defaultQuotas.tv.quotaDays ?? 7,
|
||||
defaultPermissions: data?.defaultPermissions ?? 0,
|
||||
}}
|
||||
validationSchema={schema}
|
||||
enableReinitialize
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
@@ -123,7 +90,6 @@ const SettingsUsers = () => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
localLogin: values.localLogin,
|
||||
mediaServerLogin: values.mediaServerLogin,
|
||||
newPlexLogin: values.newPlexLogin,
|
||||
defaultQuotas: {
|
||||
movie: {
|
||||
@@ -155,61 +121,30 @@ const SettingsUsers = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, isValid, values, errors, setFieldValue }) => {
|
||||
{({ isSubmitting, values, setFieldValue }) => {
|
||||
return (
|
||||
<Form className="section">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="group-label"
|
||||
className="form-group"
|
||||
>
|
||||
<div className="form-row">
|
||||
<span id="group-label" className="group-label">
|
||||
{intl.formatMessage(messages.loginMethods)}
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.loginMethodsTip)}
|
||||
</span>
|
||||
{'localLogin | mediaServerLogin' in errors && (
|
||||
<span className="error">
|
||||
{errors['localLogin | mediaServerLogin'] as string}
|
||||
</span>
|
||||
<div className="form-row">
|
||||
<label htmlFor="localLogin" className="checkbox-label">
|
||||
{intl.formatMessage(messages.localLogin)}
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(
|
||||
messages.localLoginTip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
</span>
|
||||
|
||||
<div className="form-input-area max-w-lg">
|
||||
<LabeledCheckbox
|
||||
id="localLogin"
|
||||
label={intl.formatMessage(messages.localLogin)}
|
||||
description={intl.formatMessage(
|
||||
messages.localLoginTip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
onChange={() =>
|
||||
setFieldValue('localLogin', !values.localLogin)
|
||||
}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
id="mediaServerLogin"
|
||||
className="mt-4"
|
||||
label={intl.formatMessage(
|
||||
messages.mediaServerLogin,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
description={intl.formatMessage(
|
||||
messages.mediaServerLoginTip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
onChange={() =>
|
||||
setFieldValue(
|
||||
'mediaServerLogin',
|
||||
!values.mediaServerLogin
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="localLogin"
|
||||
name="localLogin"
|
||||
onChange={() => {
|
||||
setFieldValue('localLogin', !values.localLogin);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="newPlexLogin" className="checkbox-label">
|
||||
{intl.formatMessage(
|
||||
@@ -294,7 +229,7 @@ const SettingsUsers = () => {
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<ArrowDownOnSquareIcon />
|
||||
<span>
|
||||
|
||||
@@ -1070,42 +1070,38 @@ const SonarrModal = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{sonarr && (
|
||||
<>
|
||||
<h3 className="mb-4 text-xl font-bold leading-8 text-gray-100">
|
||||
{intl.formatMessage(messages.overrideRules)}
|
||||
</h3>
|
||||
<ul className="grid gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 sm:gap-y-6 lg:grid-cols-2">
|
||||
{rules && (
|
||||
<OverrideRuleTile
|
||||
rules={rules}
|
||||
setOverrideRuleModal={setOverrideRuleModal}
|
||||
testResponse={testResponse}
|
||||
sonarr={sonarr}
|
||||
revalidate={revalidate}
|
||||
/>
|
||||
)}
|
||||
<li className="min-h-[8rem] rounded-lg border-2 border-dashed border-gray-400 shadow sm:min-h-[11rem]">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
onClick={() =>
|
||||
setOverrideRuleModal({
|
||||
open: true,
|
||||
rule: null,
|
||||
testResponse,
|
||||
})
|
||||
}
|
||||
disabled={!isValidated}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{intl.formatMessage(messages.addrule)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
<h3 className="mb-4 text-xl font-bold leading-8 text-gray-100">
|
||||
{intl.formatMessage(messages.overrideRules)}
|
||||
</h3>
|
||||
<ul className="grid grid-cols-2 gap-6">
|
||||
{rules && (
|
||||
<OverrideRuleTile
|
||||
rules={rules}
|
||||
setOverrideRuleModal={setOverrideRuleModal}
|
||||
testResponse={testResponse}
|
||||
sonarr={sonarr}
|
||||
revalidate={revalidate}
|
||||
/>
|
||||
)}
|
||||
<li className="min-h-[8rem] rounded-lg border-2 border-dashed border-gray-400 shadow sm:min-h-[11rem]">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Button
|
||||
buttonType="ghost"
|
||||
onClick={() =>
|
||||
setOverrideRuleModal({
|
||||
open: true,
|
||||
rule: null,
|
||||
testResponse,
|
||||
})
|
||||
}
|
||||
disabled={!isValidated}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{intl.formatMessage(messages.addrule)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { InformationCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { MediaServerType, ServerType } from '@server/constants/server';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
hostname: '{mediaServerName} URL',
|
||||
port: 'Port',
|
||||
enablessl: 'Use SSL',
|
||||
urlBase: 'URL Base',
|
||||
email: 'Email Address',
|
||||
emailtooltip:
|
||||
'Address does not need to be associated with your {mediaServerName} instance.',
|
||||
validationhostrequired: '{mediaServerName} URL required',
|
||||
validationhostformat: 'Valid URL required',
|
||||
validationemailrequired: 'You must provide a valid email address',
|
||||
validationemailformat: 'Valid email required',
|
||||
validationusernamerequired: 'Username required',
|
||||
validationpasswordrequired: 'You must provide a password',
|
||||
validationservertyperequired: 'Please select a server type',
|
||||
validationHostnameRequired: 'You must provide a valid hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
validationUrlBaseLeadingSlash: 'URL base must have a leading slash',
|
||||
validationUrlBaseTrailingSlash: 'URL base must not end in a trailing slash',
|
||||
loginerror: 'Something went wrong while trying to sign in.',
|
||||
adminerror: 'You must use an admin account to sign in.',
|
||||
noadminerror: 'No admin user found on the server.',
|
||||
credentialerror: 'The username or password is incorrect.',
|
||||
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
|
||||
signingin: 'Signing In…',
|
||||
signin: 'Sign In',
|
||||
initialsigningin: 'Connecting…',
|
||||
initialsignin: 'Connect',
|
||||
forgotpassword: 'Forgot Password?',
|
||||
servertype: 'Server Type',
|
||||
back: 'Go back',
|
||||
});
|
||||
|
||||
interface JellyfinSetupProps {
|
||||
revalidate: () => void;
|
||||
serverType?: MediaServerType;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
function JellyfinSetup({
|
||||
revalidate,
|
||||
serverType,
|
||||
onCancel,
|
||||
}: JellyfinSetupProps) {
|
||||
const toasts = useToasts();
|
||||
const intl = useIntl();
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
serverType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: serverType === MediaServerType.EMBY
|
||||
? ServerType.EMBY
|
||||
: 'Media Server',
|
||||
};
|
||||
|
||||
const LoginSchema = Yup.object().shape({
|
||||
hostname: Yup.string().required(
|
||||
intl.formatMessage(
|
||||
messages.validationhostrequired,
|
||||
mediaServerFormatValues
|
||||
)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
urlBase: Yup.string()
|
||||
.test(
|
||||
'leading-slash',
|
||||
intl.formatMessage(messages.validationUrlBaseLeadingSlash),
|
||||
(value) => !value || value.startsWith('/')
|
||||
)
|
||||
.test(
|
||||
'trailing-slash',
|
||||
intl.formatMessage(messages.validationUrlBaseTrailingSlash),
|
||||
(value) => !value || !value.endsWith('/')
|
||||
),
|
||||
email: Yup.string()
|
||||
.email(intl.formatMessage(messages.validationemailformat))
|
||||
.required(intl.formatMessage(messages.validationemailrequired)),
|
||||
username: Yup.string().required(
|
||||
intl.formatMessage(messages.validationusernamerequired)
|
||||
),
|
||||
password: Yup.string(),
|
||||
});
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
hostname: '',
|
||||
port: 8096,
|
||||
useSsl: false,
|
||||
urlBase: '',
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={LoginSchema}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
// Check if serverType is either 'Jellyfin' or 'Emby'
|
||||
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
|
||||
// throw new Error('Invalid serverType'); // You can customize the error message
|
||||
// }
|
||||
|
||||
const res = await fetch('/api/v1/auth/jellyfin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
hostname: values.hostname,
|
||||
port: values.port,
|
||||
useSsl: values.useSsl,
|
||||
urlBase: values.urlBase,
|
||||
email: values.email,
|
||||
serverType: serverType,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
let errorMessage = null;
|
||||
switch (errorData?.message) {
|
||||
case ApiErrorCode.InvalidUrl:
|
||||
errorMessage = messages.invalidurlerror;
|
||||
break;
|
||||
case ApiErrorCode.InvalidCredentials:
|
||||
errorMessage = messages.credentialerror;
|
||||
break;
|
||||
case ApiErrorCode.NotAdmin:
|
||||
errorMessage = messages.adminerror;
|
||||
break;
|
||||
case ApiErrorCode.NoAdminUser:
|
||||
errorMessage = messages.noadminerror;
|
||||
break;
|
||||
default:
|
||||
errorMessage = messages.loginerror;
|
||||
break;
|
||||
}
|
||||
|
||||
toasts.addToast(
|
||||
intl.formatMessage(errorMessage, mediaServerFormatValues),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
revalidate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, touched, values, setFieldValue, isSubmitting, isValid }) => (
|
||||
<Form>
|
||||
<div className="sm:border-t sm:border-gray-800">
|
||||
<div className="flex flex-col sm:flex-row sm:gap-4">
|
||||
<div className="w-full">
|
||||
<label htmlFor="hostname" className="text-label">
|
||||
{intl.formatMessage(
|
||||
messages.hostname,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mb-0 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
|
||||
{values.useSsl ? 'https://' : 'http://'}
|
||||
</span>
|
||||
<Field
|
||||
id="hostname"
|
||||
name="hostname"
|
||||
type="text"
|
||||
className="rounded-r-only flex-1"
|
||||
placeholder={intl.formatMessage(
|
||||
messages.hostname,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{errors.hostname && touched.hostname && (
|
||||
<div className="error">{errors.hostname}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label htmlFor="port" className="text-label">
|
||||
{intl.formatMessage(messages.port)}
|
||||
</label>
|
||||
<div className="mt-1 sm:mt-0">
|
||||
<Field
|
||||
id="port"
|
||||
name="port"
|
||||
inputMode="numeric"
|
||||
type="text"
|
||||
className="short flex-1"
|
||||
placeholder={intl.formatMessage(messages.port)}
|
||||
/>
|
||||
{errors.port && touched.port && (
|
||||
<div className="error">{errors.port}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="useSsl" className="text-label mt-2">
|
||||
{intl.formatMessage(messages.enablessl)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="useSsl"
|
||||
name="useSsl"
|
||||
type="checkbox"
|
||||
onChange={() => {
|
||||
setFieldValue('useSsl', !values.useSsl);
|
||||
setFieldValue('port', values.useSsl ? 8096 : 443);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="urlBase" className="text-label mt-1">
|
||||
{intl.formatMessage(messages.urlBase)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
type="text"
|
||||
inputMode="url"
|
||||
id="urlBase"
|
||||
name="urlBase"
|
||||
placeholder={intl.formatMessage(messages.urlBase)}
|
||||
/>
|
||||
</div>
|
||||
{errors.urlBase && touched.urlBase && (
|
||||
<div className="error">{errors.urlBase}</div>
|
||||
)}
|
||||
</div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-label inline-flex gap-1 align-middle"
|
||||
>
|
||||
{intl.formatMessage(messages.email)}
|
||||
<span className="label-tip">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(
|
||||
messages.emailtooltip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
>
|
||||
<span className="tooltip-trigger">
|
||||
<InformationCircleIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</label>
|
||||
<div className="mt-1 sm:col-span-2 sm:mb-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.email)}
|
||||
/>
|
||||
</div>
|
||||
{errors.email && touched.email && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="username" className="text-label">
|
||||
{intl.formatMessage(messages.username)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.username)}
|
||||
/>
|
||||
</div>
|
||||
{errors.username && touched.username && (
|
||||
<div className="error">{errors.username}</div>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="flexrounded-md shadow-sm">
|
||||
<Field
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={intl.formatMessage(messages.password)}
|
||||
/>
|
||||
</div>
|
||||
{errors.password && touched.password && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</Button>
|
||||
</span>
|
||||
{onCancel && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button buttonType="default" onClick={() => onCancel()}>
|
||||
<FormattedMessage {...messages.back} />
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export default JellyfinSetup;
|
||||
@@ -1,4 +1,4 @@
|
||||
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
|
||||
import PlexLoginButton from '@app/components/PlexLoginButton';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Button from '@app/components/Common/Button';
|
||||
import PlexLoginButton from '@app/components/Login/PlexLoginButton';
|
||||
import JellyfinSetup from '@app/components/Setup/JellyfinSetup';
|
||||
import JellyfinLogin from '@app/components/Login/JellyfinLogin';
|
||||
import PlexLoginButton from '@app/components/PlexLoginButton';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
@@ -83,9 +83,11 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
|
||||
</div>
|
||||
{serverType === MediaServerType.PLEX && (
|
||||
<>
|
||||
<div className="flex justify-center bg-black/30 px-10 py-8">
|
||||
<div
|
||||
className="px-10 py-8"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
|
||||
>
|
||||
<PlexLoginButton
|
||||
large
|
||||
onAuthToken={(authToken) => {
|
||||
setMediaServerType(MediaServerType.PLEX);
|
||||
setAuthToken(authToken);
|
||||
@@ -100,14 +102,16 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
|
||||
</>
|
||||
)}
|
||||
{serverType === MediaServerType.JELLYFIN && (
|
||||
<JellyfinSetup
|
||||
<JellyfinLogin
|
||||
initial={true}
|
||||
revalidate={revalidate}
|
||||
serverType={serverType}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
)}
|
||||
{serverType === MediaServerType.EMBY && (
|
||||
<JellyfinSetup
|
||||
<JellyfinLogin
|
||||
initial={true}
|
||||
revalidate={revalidate}
|
||||
serverType={serverType}
|
||||
onCancel={onCancel}
|
||||
|
||||
@@ -14,12 +14,10 @@ import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { Library } from '@server/lib/settings';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import SetupLogin from './SetupLogin';
|
||||
|
||||
@@ -37,8 +35,6 @@ const messages = defineMessages('components.Setup', {
|
||||
signin: 'Sign In',
|
||||
configuremediaserver: 'Configure Media Server',
|
||||
configureservices: 'Configure Services',
|
||||
librarieserror:
|
||||
'Validation failed. Please toggle the libraries again to continue.',
|
||||
});
|
||||
|
||||
const Setup = () => {
|
||||
@@ -53,7 +49,6 @@ const Setup = () => {
|
||||
const router = useRouter();
|
||||
const { locale } = useLocale();
|
||||
const settings = useSettings();
|
||||
const toasts = useToasts();
|
||||
|
||||
const finishSetup = async () => {
|
||||
setIsUpdating(true);
|
||||
@@ -82,37 +77,6 @@ const Setup = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const validateLibraries = useCallback(async () => {
|
||||
try {
|
||||
const endpointMap: Record<MediaServerType, string> = {
|
||||
[MediaServerType.JELLYFIN]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.EMBY]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.PLEX]: '/api/v1/settings/plex',
|
||||
[MediaServerType.NOT_CONFIGURED]: '',
|
||||
};
|
||||
|
||||
const endpoint = endpointMap[mediaServerType];
|
||||
if (!endpoint) return;
|
||||
|
||||
const res = await fetch(endpoint);
|
||||
if (!res.ok) throw new Error('Fetch failed');
|
||||
const data = await res.json();
|
||||
|
||||
const hasEnabledLibraries = data?.libraries?.some(
|
||||
(library: Library) => library.enabled
|
||||
);
|
||||
|
||||
setMediaServerSettingsComplete(hasEnabledLibraries);
|
||||
} catch (e) {
|
||||
toasts.addToast(intl.formatMessage(messages.librarieserror), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
|
||||
setMediaServerSettingsComplete(false);
|
||||
}
|
||||
}, [intl, mediaServerType, toasts]);
|
||||
|
||||
const { data: backdrops } = useSWR<string[]>('/api/v1/backdrops', {
|
||||
refreshInterval: 0,
|
||||
refreshWhenHidden: false,
|
||||
@@ -123,38 +87,19 @@ const Setup = () => {
|
||||
if (settings.currentSettings.initialized) {
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
if (
|
||||
settings.currentSettings.mediaServerType !==
|
||||
MediaServerType.NOT_CONFIGURED
|
||||
) {
|
||||
setCurrentStep(3);
|
||||
setMediaServerType(settings.currentSettings.mediaServerType);
|
||||
if (currentStep < 3) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
settings.currentSettings.mediaServerType,
|
||||
settings.currentSettings.initialized,
|
||||
router,
|
||||
toasts,
|
||||
intl,
|
||||
currentStep,
|
||||
mediaServerType,
|
||||
validateLibraries,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep === 3) {
|
||||
validateLibraries();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentStep]);
|
||||
|
||||
const handleComplete = () => {
|
||||
validateLibraries();
|
||||
};
|
||||
|
||||
if (settings.currentSettings.initialized) return <></>;
|
||||
|
||||
return (
|
||||
@@ -280,9 +225,14 @@ const Setup = () => {
|
||||
{currentStep === 3 && (
|
||||
<div className="p-2">
|
||||
{mediaServerType === MediaServerType.PLEX ? (
|
||||
<SettingsPlex onComplete={handleComplete} />
|
||||
<SettingsPlex
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
) : (
|
||||
<SettingsJellyfin isSetupSettings onComplete={handleComplete} />
|
||||
<SettingsJellyfin
|
||||
isSetupSettings
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
)}
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
|
||||
@@ -51,7 +51,7 @@ const messages = defineMessages('components.TitleCard', {
|
||||
watchlistDeleted:
|
||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||
watchlistCancel: 'watchlist for <strong>{title}</strong> canceled.',
|
||||
watchlistError: 'Something went wrong. Please try again.',
|
||||
watchlistError: 'Something went wrong try again.',
|
||||
});
|
||||
|
||||
const TitleCard = ({
|
||||
|
||||
@@ -41,11 +41,13 @@ import {
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
MinusCircleIcon,
|
||||
PlayIcon,
|
||||
StarIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
MinusCircleIcon,
|
||||
StarIcon,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import type { RTRating } from '@server/api/rating/rottentomatoes';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import { IssueStatus } from '@server/constants/issue';
|
||||
@@ -101,7 +103,7 @@ const messages = defineMessages('components.TvDetails', {
|
||||
watchlistSuccess: '<strong>{title}</strong> added to watchlist successfully!',
|
||||
watchlistDeleted:
|
||||
'<strong>{title}</strong> Removed from watchlist successfully!',
|
||||
watchlistError: 'Something went wrong. Please try again.',
|
||||
watchlistError: 'Something went wrong try again.',
|
||||
removefromwatchlist: 'Remove From Watchlist',
|
||||
addtowatchlist: 'Add To Watchlist',
|
||||
});
|
||||
@@ -301,7 +303,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
const showHasSpecials = data.seasons.some(
|
||||
(season) =>
|
||||
season.seasonNumber === 0 &&
|
||||
settings.currentSettings.enableSpecialEpisodes
|
||||
settings.currentSettings.partialRequestsEnabled
|
||||
);
|
||||
|
||||
const isComplete =
|
||||
@@ -1241,26 +1243,14 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
</div>
|
||||
)}
|
||||
{!!streamingProviders.length && (
|
||||
<div className="media-fact flex-col gap-1">
|
||||
<div className="media-fact">
|
||||
<span>{intl.formatMessage(messages.streamingproviders)}</span>
|
||||
<span className="media-fact-value flex flex-row flex-wrap gap-5">
|
||||
<span className="media-fact-value">
|
||||
{streamingProviders.map((p) => {
|
||||
return (
|
||||
<Tooltip content={p.name}>
|
||||
<span
|
||||
className="opacity-50 transition duration-300 hover:opacity-100"
|
||||
key={`provider-${p.id}`}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={'https://image.tmdb.org/t/p/w45/' + p.logoPath}
|
||||
alt={p.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-md"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="block" key={`provider-${p.id}`}>
|
||||
{p.name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import PermissionEdit from '@app/components/PermissionEdit';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { hasPermission } from '@server/lib/permissions';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
@@ -80,10 +79,7 @@ const BulkEditModal = ({
|
||||
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
|
||||
({ permissions: aPerms }, { permissions: bPerms }) => {
|
||||
return {
|
||||
permissions:
|
||||
aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms)
|
||||
? aPerms
|
||||
: NaN,
|
||||
permissions: aPerms === bPerms ? aPerms : NaN,
|
||||
};
|
||||
},
|
||||
{ permissions: selectedUsers[0].permissions }
|
||||
|
||||
@@ -14,7 +14,6 @@ const defaultSettings = {
|
||||
applicationUrl: '',
|
||||
hideAvailable: false,
|
||||
localLogin: true,
|
||||
mediaServerLogin: true,
|
||||
movie4kEnabled: false,
|
||||
series4kEnabled: false,
|
||||
discoverRegion: '',
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import PlexOAuth from '@app/utils/plex';
|
||||
import { useState } from 'react';
|
||||
|
||||
const plexOAuth = new PlexOAuth();
|
||||
|
||||
function usePlexLogin({
|
||||
onAuthToken,
|
||||
onError,
|
||||
}: {
|
||||
onAuthToken: (authToken: string) => void;
|
||||
onError?: (err: string) => void;
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getPlexLogin = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const authToken = await plexOAuth.login();
|
||||
setLoading(false);
|
||||
onAuthToken(authToken);
|
||||
} catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const login = () => {
|
||||
plexOAuth.preparePopup();
|
||||
setTimeout(() => getPlexLogin(), 1500);
|
||||
};
|
||||
|
||||
return { loading, login };
|
||||
}
|
||||
|
||||
export default usePlexLogin;
|
||||
@@ -58,7 +58,7 @@ const globalMessages = defineMessages('i18n', {
|
||||
blacklist: 'Blacklist',
|
||||
blacklisted: 'Blacklisted',
|
||||
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
|
||||
blacklistError: 'Something went wrong. Please try again.',
|
||||
blacklistError: 'Something went wrong try again.',
|
||||
blacklistDuplicateError:
|
||||
'<strong>{title}</strong> has already been blacklisted.',
|
||||
removeFromBlacklistSuccess:
|
||||
|
||||
@@ -175,6 +175,8 @@
|
||||
"components.MovieDetails.originallanguage": "اللغة الأصلية",
|
||||
"components.MovieDetails.originaltitle": "الإسم الأصلي",
|
||||
"components.MovieDetails.overviewunavailable": "النظرة العامة غير متوفرة.",
|
||||
"components.MovieDetails.play4konplex": "تشغيل بجودة فور كي في بليكس",
|
||||
"components.MovieDetails.playonplex": "تشغيل في بليكس",
|
||||
"components.MovieDetails.productioncountries": "إنتاج {countryCount, plural, one {الدولة} other {الدول}}",
|
||||
"components.MovieDetails.recommendations": "توصيات",
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {تاريخ الاصدار other {تواريخ الاصدار}}",
|
||||
@@ -327,6 +329,7 @@
|
||||
"components.RequestModal.cancel": "إلغاء الطلب",
|
||||
"components.RequestModal.edit": "تعديل الطلب",
|
||||
"components.RequestModal.errorediting": "حدث خطأ ما أثناء محاولة تعديل الطلب.",
|
||||
"components.RequestModal.extras": "إضافات",
|
||||
"components.RequestModal.numberofepisodes": "# حلقات",
|
||||
"components.RequestModal.pending4krequest": "طلب فور كي مُعلّق",
|
||||
"components.RequestModal.pendingapproval": "طلبك معلق بانتظار الموافقة.",
|
||||
@@ -676,6 +679,7 @@
|
||||
"components.Settings.tautulliSettingsDescription": "بشكل إختياري أضبط إعدادات سيرفرك الخاص بـ Tautulli.أوفرسيرر سيقوم بجلب بيانات سجل المشاهدة لمحتوى بليكس من Tautulli.",
|
||||
"components.Settings.webhook": "ويب هوك Webhook",
|
||||
"components.Settings.webpush": "ويب بوش Web Push",
|
||||
"components.Setup.configureplex": "إعداد بليكس",
|
||||
"components.Settings.SonarrModal.server4k": "سيرفر جودة الفور كي",
|
||||
"components.Settings.SonarrModal.servername": "إسم السيرفر",
|
||||
"components.Settings.SonarrModal.ssl": "إستخدم SSL",
|
||||
@@ -771,10 +775,12 @@
|
||||
"components.Setup.finishing": "جاري الإنهاء…",
|
||||
"components.UserList.userdeleted": "تم حذف المستخدم بنجاح!",
|
||||
"components.UserList.users": "المستخدمين",
|
||||
"components.TvDetails.play4konplex": "تشغيل بجودة فور كي في بليكس",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# موسم} other {# مواسم}}",
|
||||
"components.TvDetails.showtype": "نوُع المسلسل",
|
||||
"components.UserList.accounttype": "نوع العضويّة",
|
||||
"components.UserList.admin": "مسؤول",
|
||||
"components.UserList.displayName": "إسم العرض",
|
||||
"components.TvDetails.cast": "الطاقم",
|
||||
"components.TvDetails.streamingproviders": "يعرض حاليا على",
|
||||
"components.UserList.deleteuser": "حذف المستخدم",
|
||||
@@ -804,8 +810,11 @@
|
||||
"i18n.delete": "حذف",
|
||||
"i18n.requesting": "جاري الطلب…",
|
||||
"pages.somethingwentwrong": "حدث خطأ ما",
|
||||
"components.Setup.loginwithplex": "تسجيل دخول بواسطة بليكس",
|
||||
"components.Setup.scanbackground": "الفحص سيستمر بالخلفية. تستطيع إكمال عملية الإعداد في الوقت الحالي.",
|
||||
"components.Setup.setup": "الإعداد",
|
||||
"components.Setup.signinMessage": "إبدأ من خلال تسجيل دخولك بحساب بليكس",
|
||||
"components.Setup.tip": "تلميحات",
|
||||
"components.Setup.welcome": "مرحبا بك في أوفرسيرر",
|
||||
"components.StatusBadge.status": "{status}",
|
||||
"components.StatusBadge.status4k": "فور كي {status}",
|
||||
@@ -821,6 +830,7 @@
|
||||
"components.TvDetails.originaltitle": "العنوان الأصلي",
|
||||
"components.TvDetails.overview": "نظرة عامة",
|
||||
"components.TvDetails.overviewunavailable": "النظرة العامة غير متاحة.",
|
||||
"components.TvDetails.playonplex": "تشغيل في بليكس",
|
||||
"components.TvDetails.productioncountries": "إنتاج {countryCount, plural, one {الدولة} other {الدول}}",
|
||||
"components.TvDetails.recommendations": "التوصيات",
|
||||
"components.TvDetails.similar": "مسلسلات مشابهه",
|
||||
@@ -1145,6 +1155,8 @@
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "إعداد خارجي بمفتاح API بصلاحية القراءة فقط (هذا الخيار يتطلب إتصال مُشفر HTTP)",
|
||||
"components.Settings.SettingsMain.generalsettings": "إعدادات عامة",
|
||||
"components.Settings.SettingsMain.locale": "لغة العرض",
|
||||
"components.Settings.SettingsMain.region": "المنطقة/الإقليم الخاص بمحتوى إكتشف",
|
||||
"components.Settings.SettingsMain.regionTip": "فلترة المحتوى بحسب توفّره بالإقليم/المنطقة",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "تم حفظ الإعدادات!",
|
||||
"components.Settings.SettingsMain.trustProxy": "تفعيل دعم البروكسي",
|
||||
"components.StatusChecker.appUpdatedDescription": "الرجاء النقر على الزر بالإسفل لإعادة تحميل الصفحة.",
|
||||
@@ -1226,15 +1238,5 @@
|
||||
"components.Settings.SonarrModal.animeSeriesType": "نوع مسلسل الإنمي",
|
||||
"components.Settings.SonarrModal.seriesType": "نوع المسلسل",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "صوت التنبيه",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "الجهاز الإفتراضي",
|
||||
"component.BlacklistBlock.blacklistedby": "مدرج في القائمة السوداء من قبل",
|
||||
"component.BlacklistBlock.blacklistdate": "تاريخ الإدراج في القائمة السوداء",
|
||||
"component.BlacklistModal.blacklisting": "القائمة السوداء",
|
||||
"components.Blacklist.blacklistNotFoundError": "<strong>{title}</strong> غير مدرج في القائمة السوداء.",
|
||||
"components.Blacklist.blacklistSettingsDescription": "إدارة الوسائط المدرجة في القائمة السوداء.",
|
||||
"components.Blacklist.mediaType": "النوع",
|
||||
"components.Blacklist.mediaName": "الاسم",
|
||||
"components.Blacklist.blacklistdate": "التاريخ",
|
||||
"components.Blacklist.blacklistsettings": "إعدادات القائمة السوداء",
|
||||
"components.Blacklist.blacklistedby": "{date} بواسطة {user}"
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "الجهاز الإفتراضي"
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@
|
||||
"components.PermissionEdit.viewrequests": "Преглед на заявките",
|
||||
"components.RequestCard.failedretry": "Нещо се обърка при повторен опит за заявка.",
|
||||
"components.PermissionEdit.requestMovies": "Заявка за филми",
|
||||
"components.RequestModal.extras": "Екстри",
|
||||
"components.RequestModal.QuotaDisplay.quotaLinkUser": "Можете да прегледате обобщение на ограниченията на заявки от потребителя на неговата <Profile Link>профилна страница</Profile Link>.",
|
||||
"components.Discover.StudioSlider.studios": "Студия",
|
||||
"components.ManageSlideOver.manageModalRequests": "Заявки",
|
||||
@@ -711,6 +712,7 @@
|
||||
"components.Discover.FilterSlideover.voteCount": "Брой гласове между {minValue} и {maxValue}",
|
||||
"components.NotificationTypeSelector.issuecreated": "Проблемът е докладван",
|
||||
"components.RequestModal.alreadyrequested": "Вече е заявено",
|
||||
"components.MovieDetails.play4konplex": "Пусни в 4K в Plex",
|
||||
"components.NotificationTypeSelector.mediaavailable": "Налична заявка",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Настройките за известяване към Pushbullet не успяха да бъдат запазени.",
|
||||
"components.PermissionEdit.autoapprove4kSeriesDescription": "Гарантирано автоматично одобрение за заявки за 4K сериали.",
|
||||
@@ -722,6 +724,7 @@
|
||||
"components.NotificationTypeSelector.mediadeclinedDescription": "Изпращайте известия, когато медийните заявки бъдат отхвърлени.",
|
||||
"components.Settings.Notifications.validationSmtpPortRequired": "Трябва да предоставите валиден номер на порт",
|
||||
"components.NotificationTypeSelector.mediaautorequested": "Заявката е изпратена автоматично",
|
||||
"components.MovieDetails.playonplex": "Пусни в Plex",
|
||||
"components.ManageSlideOver.manageModalClearMedia": "Изчистване на данните",
|
||||
"components.Settings.RadarrModal.apiKey": "API ключ",
|
||||
"components.MovieDetails.streamingproviders": "В момента се излъчва по",
|
||||
@@ -739,6 +742,7 @@
|
||||
"components.Settings.SettingsMain.hideAvailable": "Скриване на наличните медии",
|
||||
"components.Settings.SettingsLogs.logDetails": "Подробности за лог файл",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Настройките са запазени успешно!",
|
||||
"components.Settings.SettingsMain.regionTip": "Филтрирайте съдържанието по регионална наличност",
|
||||
"components.UserList.sortRequests": "Брой заявки",
|
||||
"components.Settings.SonarrModal.edit4ksonarr": "Редактирай 4К Sonarr сървър",
|
||||
"components.Settings.SettingsJobsCache.imagecache": "Кеш изображения",
|
||||
@@ -766,6 +770,7 @@
|
||||
"components.Settings.SettingsLogs.logsDescription": "Можете също да видите тези лог файлове директно чрез <code>stdout</code> или в <code>{appDataPath}/logs/overseerr.log</code>.",
|
||||
"components.TvDetails.episodeCount": "{episodeCount, plural, one {# епизод} other {# епизоди}}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.discordId": "Discord User ID",
|
||||
"components.Settings.SettingsMain.region": "Регион в Открийте",
|
||||
"components.TvDetails.firstAirDate": "Първа дата за ефир",
|
||||
"pages.errormessagewithcode": "{statusCode} - {error}",
|
||||
"components.Settings.SettingsMain.trustProxy": "Активирайте поддръжката на прокси",
|
||||
@@ -823,6 +828,7 @@
|
||||
"components.Settings.SettingsLogs.filterInfo": "Информация",
|
||||
"i18n.view": "Преглед",
|
||||
"components.Settings.scan": "Синхронизиране на библиотеки",
|
||||
"components.TvDetails.playonplex": "Пусни в Plex",
|
||||
"components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Заданието редактирана успешно!",
|
||||
"components.UserList.usercreatedsuccess": "Потребителят е създаден успешно!",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Настройките за известяване по имейл са запазени успешно!",
|
||||
@@ -857,6 +863,7 @@
|
||||
"components.Settings.SonarrModal.selectRootFolder": "Изберете главна папка",
|
||||
"i18n.delimitedlist": "{a}, {b}",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push",
|
||||
"components.Setup.scanbackground": "Сканирането ще работи във фонов режим. Междувременно можете да продължите процеса на настройка.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Известия",
|
||||
"components.Settings.SonarrModal.validationApplicationUrl": "Трябва да предоставите валиден URL адрес",
|
||||
"components.Settings.SonarrModal.seasonfolders": "Папки по сезони",
|
||||
@@ -894,6 +901,7 @@
|
||||
"components.Settings.plexsettingsDescription": "Конфигурирайте настройките за вашия Plex сървър. Overseerr сканира вашите Plex библиотеки, за да определи наличното съдържанието.",
|
||||
"i18n.import": "Импорт",
|
||||
"components.Settings.SettingsMain.applicationTitle": "Заглавие на приложението",
|
||||
"components.TvDetails.play4konplex": "Пусни в 4K в Plex",
|
||||
"components.StatusBadge.playonplex": "Пусни в Plex",
|
||||
"i18n.open": "Отвори",
|
||||
"components.Settings.port": "Порт",
|
||||
@@ -937,6 +945,7 @@
|
||||
"components.UserList.accounttype": "Тип",
|
||||
"components.Settings.webAppUrl": "<WebAppLink>Web App</WebAppLink> URL",
|
||||
"components.TvDetails.manageseries": "Управление на сериали",
|
||||
"components.Setup.tip": "Съвет",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Настройките за известяване към Discord не успяха да бъдат запазени.",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Вашият акаунт в момента няма зададена парола. Конфигурирайте парола по-долу, за да разрешите влизане като „локален потребител“, използвайки своя имейл адрес.",
|
||||
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Нова честота",
|
||||
@@ -946,6 +955,7 @@
|
||||
"i18n.resolved": "Разрешен",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Нещо се обърка при извличане на данни за сезона.",
|
||||
"components.StatusChecker.appUpdatedDescription": "Моля, щракнете върху бутона по-долу, за да презаредите приложението.",
|
||||
"components.UserList.displayName": "Показвано име",
|
||||
"components.UserList.bulkedit": "Групово редактиране",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Трябва да предоставите валиден потребителски или групов ключ",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Нещо се обърка при генерирането на нов API ключ.",
|
||||
@@ -970,6 +980,7 @@
|
||||
"components.Settings.SonarrModal.loadingprofiles": "Зареждат се профилите за качество…",
|
||||
"i18n.testing": "Тествам…",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Нещо се обърка при запазването на паролата. Вашата текуща парола правилно ли е въведена?",
|
||||
"components.Setup.loginwithplex": "Влезте с Plex",
|
||||
"components.Settings.SettingsUsers.userSettingsDescription": "Конфигурирайте глобалните потребителски настройки и настройките по подразбиране.",
|
||||
"i18n.noresults": "Няма резултати.",
|
||||
"components.Settings.notificationAgentSettingsDescription": "Конфигурирайте и активирайте агенти за уведомяване.",
|
||||
@@ -1115,6 +1126,7 @@
|
||||
"components.UserProfile.ProfileHeader.userid": "Потребителски идентификатор: {userid}",
|
||||
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr сканиране",
|
||||
"components.UserList.importfromplex": "Импортиране на потребители на Plex",
|
||||
"components.Setup.configureplex": "Конфигурирайте Plex",
|
||||
"components.Settings.SonarrModal.port": "Порт",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Създайте токен от вашите <PushbulletSettingsLink>Настройки на акаунта</PushbulletSettingsLink>",
|
||||
"components.UserList.userdeleted": "Потребителят е изтрит успешно!",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"components.RequestModal.pendingrequest": "Sol·licitud pendent",
|
||||
"components.RequestModal.pending4krequest": "Sol·licitud en 4K pendent",
|
||||
"components.RequestModal.numberofepisodes": "# d'episodis",
|
||||
"components.RequestModal.extras": "Extres",
|
||||
"components.RequestModal.errorediting": "S'ha produït un error en editar la sol·licitud.",
|
||||
"components.RequestModal.cancel": "Cancel·la la sol·licitud",
|
||||
"components.RequestModal.autoapproval": "Aprovació automàtica",
|
||||
@@ -129,6 +130,8 @@
|
||||
"components.MovieDetails.runtime": "Ingressos",
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Data} other {Dates}} de llançament",
|
||||
"components.MovieDetails.recommendations": "Recomanacions",
|
||||
"components.MovieDetails.play4konplex": "Reprodueix en 4K a Plex",
|
||||
"components.MovieDetails.playonplex": "Reprodueix a Plex",
|
||||
"components.MovieDetails.overviewunavailable": "Descripció general no disponible.",
|
||||
"components.MovieDetails.overview": "Visió general",
|
||||
"components.MovieDetails.originaltitle": "Títol original",
|
||||
@@ -402,18 +405,30 @@
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Equip complet de la sèrie",
|
||||
"components.TvDetails.TvCast.fullseriescast": "Repartiment complet de la sèrie",
|
||||
"components.StatusChacker.newversionavailable": "Actualització de l'aplicació",
|
||||
"components.StatusChacker.newversionDescription": "Jellyseerr s'ha actualitzat! Feu clic al botó següent per tornar a carregar la pàgina.",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.Setup.welcome": "Benvingut a Jellyseerr",
|
||||
"components.Setup.tip": "Consell",
|
||||
"components.Setup.signinMessage": "Comenceu iniciant sessió amb el vostre compte de Plex",
|
||||
"components.Setup.setup": "Configuració",
|
||||
"components.Setup.scanbackground": "L’exploració s’executarà en segon pla. Mentrestant, podeu continuar el procés de configuració.",
|
||||
"components.Setup.loginwithplex": "Inicieu sessió amb Plex",
|
||||
"components.Setup.finishing": "S'està acabant…",
|
||||
"components.Setup.finish": "Finalitza la configuració",
|
||||
"components.Setup.continue": "Continua",
|
||||
"components.Setup.configureservices": "Configureu els serveis",
|
||||
"components.Settings.toastPlexRefresh": "S'està recuperant la llista de servidors de Plex…",
|
||||
"components.Setup.configureplex": "Configureu Plex",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.validationPortRequired": "Heu de proporcionar un número de port vàlid",
|
||||
"components.Settings.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP vàlida",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "L'URL no pot acabar amb una barra inclinada final",
|
||||
"components.Settings.validationApplicationUrl": "Heu de proporcionar un URL vàlid",
|
||||
"components.Settings.validationApplicationTitle": "Heu de proporcionar un títol d'aplicació",
|
||||
"components.Settings.trustProxyTip": "Permet a Jellyseerr registrar correctament les adreces IP del client darrere d’un servidor intermediari (s’ha de tornar a carregar Jellyseerr perquè els canvis tinguin efecte)",
|
||||
"components.Settings.toastSettingsSuccess": "La configuració s'ha desat correctament!",
|
||||
"components.Settings.toastSettingsFailure": "S'ha produït un error en desar la configuració.",
|
||||
"components.Settings.toastPlexRefreshSuccess": "La llista de servidors Plex s'ha recuperat correctament!",
|
||||
"components.Settings.toastPlexRefreshFailure": "No s'ha pogut recuperar la llista de servidors Plex.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "La connexió amb Plex s'ha establert correctament!",
|
||||
@@ -439,6 +454,9 @@
|
||||
"components.Settings.plexlibrariesDescription": "Les biblioteques en les que Jellyseerr explora títols. Configureu i deseu la configuració de la connexió Plex i feu clic al botó següent si no apareix cap.",
|
||||
"components.Settings.plexlibraries": "Biblioteques Plex",
|
||||
"components.Settings.plex": "Plex",
|
||||
"components.Settings.partialRequestsEnabled": "Permet sol·licituds parcials de Sèries",
|
||||
"components.Settings.originallanguageTip": "Filtra el contingut per l'idioma original",
|
||||
"components.Settings.originallanguage": "Idioma per a la secció \"Descobriu\"",
|
||||
"components.Settings.manualscanDescription": "Normalment, només s’executarà una vegada cada 24 hores. Jellyseerr comprovarà de forma més agressiva el contingut afegit recentment del seu servidor Plex. Si és la primera vegada que configureu Plex, es recomana fer una exploració manual completa de la biblioteca!",
|
||||
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Exploració d'elements de Plex afegits recentment",
|
||||
"components.Settings.notrunning": "No s'està executant",
|
||||
@@ -451,6 +469,8 @@
|
||||
"components.Settings.menuNotifications": "Notificacions",
|
||||
"components.Settings.menuLogs": "Registres",
|
||||
"components.Settings.menuJobs": "Tasques programades i memòria cau",
|
||||
"components.Settings.hideAvailable": "Amaga els suports disponibles",
|
||||
"components.Settings.generalsettingsDescription": "Configureu els paràmetres globals i predeterminats per a Jellyseerr.",
|
||||
"components.Settings.menuGeneralSettings": "General",
|
||||
"components.Settings.menuAbout": "Quant a",
|
||||
"components.Settings.manualscan": "Exploració manual de la biblioteca",
|
||||
@@ -651,6 +671,8 @@
|
||||
"components.TvDetails.showtype": "Tipus de sèrie",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# Temporada} other {# Temporades}}",
|
||||
"components.TvDetails.recommendations": "Recomanacions",
|
||||
"components.TvDetails.playonplex": "Reprodueix a Plex",
|
||||
"components.TvDetails.play4konplex": "Reprodueix a Plex en 4K",
|
||||
"components.Settings.SonarrModal.testFirstTags": "Proveu la connexió per carregar etiquetes",
|
||||
"components.Settings.SonarrModal.tags": "Etiquetes",
|
||||
"components.Settings.SonarrModal.selecttags": "Seleccioneu les etiquetes",
|
||||
@@ -782,6 +804,7 @@
|
||||
"components.Settings.Notifications.webhookUrlTip": "Creeu una <DiscordWebhookLink>integració de webhook</DiscordWebhookLink> al vostre servidor",
|
||||
"components.Settings.Notifications.encryptionOpportunisticTls": "Utilitzeu sempre STARTTLS",
|
||||
"components.Settings.Notifications.encryptionNone": "Cap",
|
||||
"components.UserList.displayName": "Nom de visualització",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Permetre als usuaris iniciar la sessió mitjançant la seva adreça de correu electrònic i contrasenya, en lloc de l'autenticació de Plex",
|
||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Permisos inicials assignats a usuaris nous",
|
||||
"components.RequestList.RequestItem.requesteddate": "Sol·licitat",
|
||||
@@ -1202,10 +1225,12 @@
|
||||
"components.Settings.SettingsMain.locale": "Idioma de visualització",
|
||||
"components.Settings.SettingsMain.originallanguage": "Idioma a Descobriu",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtrar el contingut per idioma original",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrar continguts per disponibilitat regional",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "S'ha produït un error en generar una clau API nova.",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "La clau API s'ha generat correctament!",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "S'ha produït un error en desar la configuració.",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Permet sol·licituds parcials de sèries",
|
||||
"components.Settings.SettingsMain.region": "Regió de Descobriu",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "La configuració s'ha desat correctament!",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "L'URL no ha d'acabar amb una barra inclinada final",
|
||||
"components.Settings.SettingsMain.trustProxy": "Habilitar la compatibilitat amb proxy",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"components.Settings.notificationsettings": "Nastavení oznámení",
|
||||
"components.Settings.locale": "Jazyk zobrazení",
|
||||
"components.Settings.generalsettings": "Obecná nastavení",
|
||||
"components.Settings.enablessl": "Použít SSL",
|
||||
"components.Settings.default4k": "Výchozí 4K",
|
||||
"components.Settings.cancelscan": "Zrušit skenování",
|
||||
"components.Settings.apikey": "API klíč",
|
||||
"components.Settings.activeProfile": "Aktivní profil",
|
||||
"components.Settings.SonarrModal.syncEnabled": "Povolit skenování",
|
||||
"components.Settings.SonarrModal.ssl": "Použít SSL",
|
||||
@@ -123,6 +126,7 @@
|
||||
"components.PersonDetails.ascharacter": "jako {character}",
|
||||
"components.PermissionEdit.viewrequests": "Zobrazit žádosti",
|
||||
"components.PermissionEdit.users": "Spravovat uživatele",
|
||||
"components.PermissionEdit.settings": "Spravovat nastavení",
|
||||
"components.PermissionEdit.requestTv": "Žádat seriály",
|
||||
"components.PermissionEdit.requestMovies": "Vyžádat filmy",
|
||||
"components.PermissionEdit.request4k": "Vyžádat ve 4K",
|
||||
@@ -151,6 +155,8 @@
|
||||
"components.TvDetails.overview": "Přehled",
|
||||
"components.TvDetails.cast": "Obsazení",
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.StatusChacker.reloadOverseerr": "Znovu načíst",
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Setup.setup": "Konfigurace",
|
||||
"components.Setup.finishing": "Dokončování…",
|
||||
"components.Setup.continue": "Pokračovat",
|
||||
@@ -175,6 +181,7 @@
|
||||
"components.Settings.mediaTypeSeries": "seriál",
|
||||
"components.Settings.mediaTypeMovie": "film",
|
||||
"components.Settings.is4k": "4K",
|
||||
"components.Settings.general": "Obecné",
|
||||
"components.Settings.email": "E-mail",
|
||||
"components.Settings.default": "Výchozí",
|
||||
"components.Settings.address": "Adresy",
|
||||
@@ -210,6 +217,7 @@
|
||||
"components.Search.search": "Vyhledat",
|
||||
"components.ResetPassword.password": "Heslo",
|
||||
"components.RequestModal.season": "Série",
|
||||
"components.RequestModal.extras": "Extra",
|
||||
"components.RequestModal.QuotaDisplay.season": "série",
|
||||
"components.RequestModal.QuotaDisplay.movie": "film",
|
||||
"components.RequestModal.AdvancedRequester.tags": "Značky",
|
||||
@@ -398,12 +406,17 @@
|
||||
"components.NotificationTypeSelector.usermediadeclinedDescription": "Dostat oznámení o odmítnutí vašich požadavků na média.",
|
||||
"components.NotificationTypeSelector.usermediaavailableDescription": "Dostat oznámení, jakmile budou k dispozici žádosti o média.",
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {potvrzení} few {potvrzení} other {potvrzení}} za",
|
||||
"components.Setup.configureplex": "Konfigurovat Plex",
|
||||
"components.Settings.serverpresetRefreshing": "Načítání serverů…",
|
||||
"components.Settings.applicationTitle": "Název aplikace",
|
||||
"components.Settings.originallanguage": "Jazyk pro vyhledávání",
|
||||
"components.Settings.plexsettings": "Nastavení Plexu",
|
||||
"components.Settings.scan": "Synchronizovat knihovny",
|
||||
"components.Settings.plexlibraries": "Plex knihovny",
|
||||
"components.Settings.applicationurl": "Adresa URL aplikace",
|
||||
"components.Settings.notrunning": "Není spuštěno",
|
||||
"components.Settings.radarrsettings": "Nastavení Radarru",
|
||||
"components.Settings.region": "Region pro vyhledávání",
|
||||
"components.Settings.startscan": "Spustit skenování",
|
||||
"components.Settings.serverpresetManualMessage": "Manuální konfigurace",
|
||||
"components.Settings.sonarrsettings": "Nastavení Sonarru",
|
||||
@@ -474,6 +487,7 @@
|
||||
"components.RequestModal.requestSuccess": "<strong>{title}</strong> bylo úspěšně zažádáno!",
|
||||
"components.Settings.SettingsLogs.logDetails": "Podrobnosti o záznamu",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.StatusChacker.newversionavailable": "Aktualizace aplikace",
|
||||
"components.TvDetails.episodeRuntime": "Délka epizody",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minut",
|
||||
"components.TvDetails.originallanguage": "Původní jazyk",
|
||||
@@ -588,6 +602,7 @@
|
||||
"components.Settings.notificationAgentSettingsDescription": "Konfigurace a povolení agentů pro oznámení.",
|
||||
"components.Settings.settingUpPlexDescription": "Chcete-li nastavit službu Plex, můžete buď zadat údaje ručně, nebo vybrat server získaný z adresy <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Stisknutím tlačítka vpravo od rozevírací nabídky načtete seznam dostupných serverů.",
|
||||
"components.Settings.toastPlexRefreshFailure": "Nepodařilo se načíst seznam serverů Plex.",
|
||||
"components.StatusChacker.newversionDescription": "Jellyseerr byl aktualizován! Kliknutím na tlačítko níže stránku znovu načtete.",
|
||||
"components.TvDetails.play4k": "Přehrát v {mediaServerName} ve 4K",
|
||||
"components.TvDetails.play": "Přehrát v {mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filtrování obsahu podle regionální dostupnosti",
|
||||
@@ -616,15 +631,20 @@
|
||||
"components.Settings.SonarrModal.testFirstRootFolders": "Test připojení pro načtení kořenových složek",
|
||||
"components.Settings.SonarrModal.validationApiKeyRequired": "Musíte zadat klíč API",
|
||||
"components.Settings.SonarrModal.validationApplicationUrl": "Musíte zadat platnou adresu URL",
|
||||
"components.Settings.csrfProtectionTip": "Nastavení externího přístupu k rozhraní API pouze pro čtení (vyžaduje protokol HTTPS a aby se změny projevily, musí být znovu načtena aplikace Jellyseerr)",
|
||||
"components.Settings.deleteserverconfirm": "Opravdu chcete tento server odstranit?",
|
||||
"components.Settings.menuJobs": "Práce a mezipaměť",
|
||||
"components.Settings.toastApiKeyFailure": "Při generování nového klíče API se něco pokazilo.",
|
||||
"components.Settings.toastApiKeySuccess": "Nový klíč API byl úspěšně vygenerován!",
|
||||
"components.TvDetails.similar": "Podobné série",
|
||||
"components.TvDetails.streamingproviders": "V současné době streamuje na",
|
||||
"components.UserList.nouserstoimport": "Neexistují žádní uživatelé systému Plex, které by bylo možné importovat.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Nastavení úspěšně uloženo!",
|
||||
"components.Settings.SettingsJobsCache.nextexecution": "Další spuštení",
|
||||
"components.Settings.generalsettingsDescription": "Konfigurace globálních a výchozích nastavení pro Jellyseerr.",
|
||||
"components.TvDetails.nextAirDate": "Další datum vysílání",
|
||||
"components.TvDetails.viewfullcrew": "Zobrazit celé obsazení",
|
||||
"components.UserList.displayName": "Zobrazené jméno",
|
||||
"components.UserList.edituser": "Úprava oprávnění uživatele",
|
||||
"components.NotificationTypeSelector.issuecreatedDescription": "Odesílat upozornění, když jsou nahlášeny problémy.",
|
||||
"components.NotificationTypeSelector.issuereopenedDescription": "Odesílat upozornění, když se problémy znovu otevřou.",
|
||||
@@ -664,6 +684,7 @@
|
||||
"components.PermissionEdit.request4kTv": "Vyžádat si sérii 4K",
|
||||
"components.PermissionEdit.requestMoviesDescription": "Udělit povolení k předkládání žádostí o filmy jiné než 4K.",
|
||||
"components.PermissionEdit.requestTvDescription": "Udělit povolení k předkládání žádostí pro jiné série než 4K.",
|
||||
"components.PermissionEdit.settingsDescription": "Udělení oprávnění ke změně globálního nastavení. Uživatel musí mít toto oprávnění, aby je mohl udělit ostatním.",
|
||||
"components.PermissionEdit.viewissuesDescription": "Udělit oprávnění k zobrazení problémů s médii nahlášených jinými uživateli.",
|
||||
"components.PermissionEdit.viewrequestsDescription": "Udělit oprávnění k zobrazení požadavků na média zadaných jinými uživateli.",
|
||||
"components.PersonDetails.alsoknownas": "Známý také jako: {names}",
|
||||
@@ -686,11 +707,15 @@
|
||||
"components.Settings.SonarrModal.toastSonarrTestFailure": "Nepodařilo se připojit k systému Sonarr.",
|
||||
"components.Settings.SonarrModal.toastSonarrTestSuccess": "Připojení Sonarr úspěšně navázáno!",
|
||||
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Základní adresa URL musí mít na začátku lomítko",
|
||||
"components.Settings.cacheImages": "Povolení ukládání obrázků do mezipaměti",
|
||||
"components.Settings.cacheImagesTip": "Ukládat do mezipaměti a poskytovat optimalizované obrazy (vyžaduje značné množství místa na disku)",
|
||||
"components.Settings.manualscanDescription": "Obvykle se provádí pouze jednou za 24 hodin. Jellyseerr bude kontrolovat nedávno přidané položky vašeho serveru Plex agresivněji. Pokud Plex konfigurujete poprvé, doporučujeme provést jednorázovou úplnou ruční kontrolu knihovny!",
|
||||
"components.Settings.originallanguageTip": "Filtrování obsahu podle původního jazyka",
|
||||
"components.Settings.urlBase": "Základní adresa URL",
|
||||
"components.Settings.tautulliSettingsDescription": "Volitelně nakonfigurujte nastavení serveru Tautulli. Jellyseerr načte data historie sledování pro vaše média Plex z Tautulli.",
|
||||
"components.Settings.toastPlexConnecting": "Pokus o připojení k systému Plex…",
|
||||
"components.Settings.validationApiKey": "Musíte zadat klíč API",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "Adresa URL nesmí končit koncovým lomítkem",
|
||||
"components.Settings.validationHostnameRequired": "Musíte zadat platný název hostitele nebo IP adresu",
|
||||
"components.Settings.validationPortRequired": "Musíte zadat platné číslo portu",
|
||||
"components.Settings.validationUrl": "Musíte zadat platnou adresu URL",
|
||||
@@ -778,15 +803,23 @@
|
||||
"components.Settings.addradarr": "Přidání serveru Radarr",
|
||||
"components.Settings.addsonarr": "Adding a Radarr server",
|
||||
"components.Settings.copied": "Zkopírování klíče API do schránky.",
|
||||
"components.Settings.csrfProtection": "Povolení ochrany CSRF",
|
||||
"components.Settings.externalUrl": "Externí adresa URL",
|
||||
"components.Settings.hideAvailable": "Skrýt dostupná média",
|
||||
"components.Settings.hostname": "Název hostitele nebo IP adresa",
|
||||
"components.Settings.manualscan": "Manuální skenování knihovny",
|
||||
"components.Settings.partialRequestsEnabled": "Povolení požadavků na částečné série",
|
||||
"components.Settings.plexlibrariesDescription": "Knihovny Jellyseerr vyhledává tituly. Nastavte a uložte nastavení připojení k systému Plex a poté klikněte na tlačítko níže, pokud nejsou v seznamu uvedeny žádné knihovny.",
|
||||
"components.Settings.serverpresetLoad": "Stisknutím tlačítka načtete dostupné servery",
|
||||
"components.Settings.toastSettingsSuccess": "Nastavení úspěšně uloženo!",
|
||||
"components.Settings.toastTautulliSettingsFailure": "Při ukládání nastavení Tautulli se něco pokazilo.",
|
||||
"components.Settings.validationApplicationTitle": "Musíte uvést název žádosti",
|
||||
"components.Settings.validationApplicationUrl": "Musíte zadat platnou adresu URL",
|
||||
"components.Settings.webAppUrl": "<WebAppLink>Webová aplikace</WebAppLink> Adresa URL",
|
||||
"components.Settings.validationUrlTrailingSlash": "Adresa URL nesmí končit koncovým lomítkem",
|
||||
"components.Settings.webAppUrlTip": "Volitelné přesměrování uživatelů na webovou aplikaci na vašem serveru namísto hostované webové aplikace",
|
||||
"components.Setup.loginwithplex": "Přihlášení pomocí služby Plex",
|
||||
"components.Setup.scanbackground": "Skenování bude probíhat na pozadí. Mezitím můžete pokračovat v procesu nastavení.",
|
||||
"components.Setup.welcome": "Vítejte v Jellyseerr",
|
||||
"components.Setup.signinMessage": "Skenování bude probíhat na pozadí. Mezitím můžete pokračovat v procesu nastavení",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Posádka celé série",
|
||||
@@ -831,7 +864,10 @@
|
||||
"components.Settings.noDefault4kServer": "Server 4K {serverType} musí být označen jako výchozí, aby uživatelé mohli odesílat požadavky 4K {mediaType}.",
|
||||
"components.Settings.noDefaultNon4kServer": "Pokud máte pouze jeden server {serverType} pro obsah jiný než 4K i 4K (nebo pokud stahujete pouze obsah 4K), váš server {serverType} by <strong>neměl</strong> být označen jako server 4K.",
|
||||
"components.Settings.noDefaultServer": "Aby mohly být zpracovány požadavky typu {mediaType}, musí být alespoň jeden server typu {serverType} označen jako výchozí.",
|
||||
"components.Settings.regionTip": "Filtrování obsahu podle regionální dostupnosti",
|
||||
"components.Settings.plexsettingsDescription": "Knihovny Jellyseerr vyhledává tituly. Nastavte a uložte nastavení připojení k systému Plex a poté klikněte na tlačítko níže, pokud nejsou v seznamu uvedeny žádné knihovny.",
|
||||
"components.Settings.toastSettingsFailure": "Při ukládání nastavení se něco pokazilo.",
|
||||
"components.Settings.trustProxy": "Povolení podpory proxy serveru",
|
||||
"components.Settings.toastPlexRefresh": "Získání seznamu serverů z aplikace Plex…",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Seznam serverů Plex úspěšně načten!",
|
||||
"components.UserList.passwordinfodescription": "Nakonfigurujte adresu URL aplikace a povolte e-mailová oznámení, která umožní automatické generování hesla.",
|
||||
@@ -857,6 +893,7 @@
|
||||
"components.RequestCard.failedretry": "Při opakovaném pokusu o zadání požadavku se něco pokazilo.",
|
||||
"components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Vyžaduje se pouze v případě, že nepoužíváte profil <code>default</code>",
|
||||
"components.RequestCard.mediaerror": "{mediaType} Nenalezeno",
|
||||
"components.Settings.trustProxyTip": "Povolit Jellyseerr správně registrovat IP adresy klientů za proxy serverem",
|
||||
"components.RequestList.RequestItem.mediaerror": "{mediaType} Nenalezeno",
|
||||
"components.RequestModal.QuotaDisplay.allowedRequests": "Můžete požádat o <strong>{limit}</strong> {type} každé <strong>{days}</strong> dny.",
|
||||
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Tuto sérii jsme nemohli automaticky spárovat. Níže prosím vyberte správnou shodu.",
|
||||
@@ -938,6 +975,7 @@
|
||||
"components.Settings.SonarrModal.validationLanguageProfileRequired": "Je třeba vybrat jazykový profil",
|
||||
"components.Settings.SonarrModal.validationNameRequired": "Je třeba zadat název serveru",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Zobrazované jméno",
|
||||
"components.Settings.csrfProtectionHoverTip": "Toto nastavení NEPOVOLUJTE, pokud nerozumíte tomu, co děláte!",
|
||||
"components.Settings.tautulliSettings": "Tautulli Nastavení",
|
||||
"components.Settings.toastTautulliSettingsSuccess": "Nastavení Tautulli úspěšně uloženo!",
|
||||
"components.UserProfile.UserSettings.unauthorizedDescription": "Nemáte oprávnění měnit nastavení tohoto uživatele.",
|
||||
@@ -1200,6 +1238,8 @@
|
||||
"components.Settings.SettingsMain.originallanguage": "Objevte jazyk",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtrování obsahu podle původního jazyka",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Povolení požadavků na částečné série",
|
||||
"components.Settings.SettingsMain.region": "Objevte region",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrování obsahu podle regionální dostupnosti",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Umožnit Jellyseerru správně registrovat klientské IP adresy za proxy serverem",
|
||||
"components.Settings.SettingsJobsCache.imagecachecount": "Obrázky v mezipaměti",
|
||||
"components.Settings.SettingsJobsCache.imagecache": "Vyrovnávací paměť obrázků",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"components.Discover.discovermovies": "Populære Film",
|
||||
"components.MediaSlider.ShowMoreCard.seemore": "Se Mere",
|
||||
"components.Login.validationpasswordrequired": "Angiv et kodeord",
|
||||
"components.Login.validationemailrequired": "Angiv en gyldig email adresse",
|
||||
@@ -31,6 +32,7 @@
|
||||
"components.Discover.recentlyAdded": "Nyligt tilføjet",
|
||||
"components.Discover.populartv": "Populære Serier",
|
||||
"components.Discover.popularmovies": "Populære Film",
|
||||
"components.Discover.discovertv": "Populære Serier",
|
||||
"components.Discover.discover": "Udforsk",
|
||||
"components.Discover.TvGenreSlider.tvgenres": "Seriegenrer",
|
||||
"components.Discover.TvGenreList.seriesgenres": "Seriegenrer",
|
||||
@@ -67,8 +69,10 @@
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Udgivelsesdato} other {Udgivelsesdatoer}}",
|
||||
"components.MovieDetails.revenue": "Omsætning",
|
||||
"components.MovieDetails.runtime": "{minutes} minutter",
|
||||
"components.MovieDetails.play4konplex": "Afspil i 4K i Plex",
|
||||
"components.MovieDetails.similar": "Lignende Titler",
|
||||
"components.MovieDetails.streamingproviders": "Kan I Øjeblikket Streames På",
|
||||
"components.MovieDetails.playonplex": "Afspil i Plex",
|
||||
"components.MovieDetails.viewfullcrew": "Vis Filmstab",
|
||||
"components.MovieDetails.watchtrailer": "Se Trailer",
|
||||
"components.PermissionEdit.advancedrequestDescription": "Giv tilladelse til at modificere avancerede medieforespørgselsindstillinger.",
|
||||
@@ -218,6 +222,7 @@
|
||||
"components.PermissionEdit.requestTv": "Forespørg Serier",
|
||||
"components.PermissionEdit.autoapproveMoviesDescription": "Giv automatisk godkendelse for alle ikke-4K filmforespørgsler.",
|
||||
"components.PermissionEdit.requestTvDescription": "Bliv notificeret når problemer er genåbnet af andre brugere.",
|
||||
"components.PermissionEdit.settings": "Administrér Indstillinger",
|
||||
"components.RegionSelector.regionServerDefault": "Standard ({region})",
|
||||
"components.RequestCard.deleterequest": "Slet Forespørgsel",
|
||||
"components.RequestCard.mediaerror": "Den forbundne titel for denne forespørgsel er ikke længere tilgængelig.",
|
||||
@@ -246,6 +251,7 @@
|
||||
"components.RequestModal.QuotaDisplay.allowedRequests": "Du kan forespørge om <strong>{limit}</strong> {type} hver <strong>{days}</strong> dag.",
|
||||
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "Denne bruger kan forespørge om <strong>{limit}</strong> {type} hver <strong>{days}</strong> dag.",
|
||||
"components.IssueDetails.IssueComment.validationComment": "Du skal skrive en besked",
|
||||
"components.PermissionEdit.settingsDescription": "Giv tilladelse til at modificere globale indstillinger. En bruger skal have denne rettighed for at kunne give den til andre.",
|
||||
"components.ManageSlideOver.manageModalIssues": "Åbne Problemer",
|
||||
"components.MovieDetails.showless": "Vis Mindre",
|
||||
"components.MovieDetails.cast": "Medvirkende",
|
||||
@@ -284,6 +290,7 @@
|
||||
"components.RequestModal.cancel": "Annullér Forespørgsel",
|
||||
"components.RequestModal.edit": "Redigér Forespørgsel",
|
||||
"components.RequestModal.errorediting": "Noget gik galt under redigeringen af forespørgslen.",
|
||||
"components.RequestModal.extras": "Ekstra",
|
||||
"components.RequestModal.numberofepisodes": "Antal Episoder",
|
||||
"components.RequestModal.pending4krequest": "Afventende 4K Forespørgsler",
|
||||
"components.RequestModal.pendingapproval": "Din forespørgsel afventer godkendelse.",
|
||||
@@ -597,7 +604,13 @@
|
||||
"components.Settings.SonarrModal.validationRootFolderRequired": "Du skal angive en rodmappe",
|
||||
"components.Settings.address": "Adresse",
|
||||
"components.Settings.addsonarr": "Tilføj Sonarr Server",
|
||||
"components.Settings.apikey": "API-nøgle",
|
||||
"components.Settings.applicationTitle": "Applikationstitel",
|
||||
"components.Settings.applicationurl": "Applikations-URL",
|
||||
"components.Settings.cacheImagesTip": "Optimér og gem alle billeder lokalt (anvender en betydelig mængde diskplads)",
|
||||
"components.Settings.copied": "API-nøgle er kopieret til udklipsholder.",
|
||||
"components.Settings.csrfProtection": "Aktivér CSRF Beskyttelse",
|
||||
"components.Settings.csrfProtectionHoverTip": "Aktivér IKKE denne indstilling hvis ikke du forstår hvad du gør!",
|
||||
"components.Settings.currentlibrary": "Nuværende Bibliotek: {name}",
|
||||
"components.Settings.email": "Email",
|
||||
"components.Settings.enablessl": "Benyt SSL",
|
||||
@@ -615,9 +628,17 @@
|
||||
"components.Settings.noDefaultServer": "Mindst én {serverType}server skal markeres som standard for at {mediaType}forespørgsler kan afvikles.",
|
||||
"components.Settings.notificationAgentSettingsDescription": "Konfigurér og aktivér notifikationsagenter.",
|
||||
"components.Settings.notifications": "Notifikationer",
|
||||
"components.Settings.originallanguage": "Udforsk Sprog",
|
||||
"components.Settings.originallanguageTip": "Filtrer indhold efter originalsprog",
|
||||
"components.Settings.partialRequestsEnabled": "Tillad Delvise Serieforespørgsler",
|
||||
"components.Settings.plex": "Plex",
|
||||
"components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Jobbet er blevet redigeret!",
|
||||
"components.Settings.csrfProtectionTip": "Sæt ekstern API-adgang til skrivebeskyttet (kræver HTTPS samt at Jellyseerr genstartes for at ændringerne vil træde i kraft)",
|
||||
"components.Settings.generalsettingsDescription": "Konfigurér global- og standardindstillinger for Jellyseerr.",
|
||||
"components.Settings.general": "Generelt",
|
||||
"components.Settings.hideAvailable": "Skjul Tilgængelige Medier",
|
||||
"components.Settings.hostname": "Domænenavn eller IP Adresse",
|
||||
"components.Settings.generalsettings": "Generelle Indstillinger",
|
||||
"components.Settings.menuAbout": "Om",
|
||||
"components.Settings.SettingsLogs.label": "Label",
|
||||
"components.Settings.SettingsLogs.level": "Alvorlighed",
|
||||
@@ -625,6 +646,8 @@
|
||||
"components.Settings.SonarrModal.baseUrl": "URL Kilde",
|
||||
"components.Settings.SonarrModal.create4ksonarr": "Tilføj Ny 4K Sonarr Server",
|
||||
"components.Settings.SonarrModal.loadingprofiles": "Indlæser kvalitetsprofiler…",
|
||||
"components.Settings.cacheImages": "Aktivér billede-caching",
|
||||
"components.Settings.locale": "Grænsefladesprog",
|
||||
"components.Settings.manualscan": "Manuel Biblioteksskanning",
|
||||
"components.Settings.mediaTypeMovie": "film",
|
||||
"components.Settings.mediaTypeSeries": "serier",
|
||||
@@ -693,11 +716,15 @@
|
||||
"components.Settings.sonarrsettings": "Sonarr-indstillinger",
|
||||
"components.Settings.ssl": "SSL",
|
||||
"components.Settings.startscan": "Start Skanning",
|
||||
"components.Settings.toastApiKeySuccess": "Ny API-nøgle er blevet genereret!",
|
||||
"components.Settings.validationApplicationUrl": "Du skal angive en gyldig URL",
|
||||
"components.Settings.webAppUrlTip": "Brugere dirigeres som alternativ til web-app'en på din server i stedet for den \"hostede\" web-app",
|
||||
"components.Settings.webAppUrl": "<WebAppLink>Web-App</WebAppLink>-URL",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Setup.welcome": "Velkommen til Jellyseerr",
|
||||
"components.StatusChacker.reloadOverseerr": "Genindlæs",
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.TvDetails.cast": "Roller",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutter",
|
||||
@@ -727,7 +754,11 @@
|
||||
"components.Settings.plexlibrariesDescription": "Bibliotekerne Jellyseerr skanner for titler. Konfigurér og gem dine Plex-forbindelsesindstillinger og klik på knappen nedenfor hvis der ikke er vist nogle biblioteker.",
|
||||
"components.Settings.plexsettingsDescription": "Konfigurér indstillingerne for din Plex server. Jellyseerr skanner dine Plex-biblioteker for at afgøre tilgængeligheden af indhold.",
|
||||
"components.Settings.radarrsettings": "Radarr-indstillinger",
|
||||
"components.Settings.region": "Udforsk Region",
|
||||
"components.Settings.trustProxy": "Aktivér Proxy-understøttelse",
|
||||
"components.Settings.trustProxyTip": "Tillad Jellyseerr at registrere klienters IP addresser korrekt bag en proxy (Jellyseerr skal genstartes for at ændringerne træder i kraft)",
|
||||
"components.TvDetails.nextAirDate": "Næste Udsendelsesdato",
|
||||
"components.TvDetails.playonplex": "Afspil i Plex",
|
||||
"components.TvDetails.recommendations": "Anbefalinger",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# Sæson} other {# Sæsoner}}",
|
||||
"components.TvDetails.streamingproviders": "Kan I Øjeblikket Streames På",
|
||||
@@ -764,6 +795,7 @@
|
||||
"components.Settings.serverSecure": "sikker",
|
||||
"components.Settings.serverpresetLoad": "Klik på knappen for at indlæse tilgængelige servere",
|
||||
"components.Settings.services": "Tjenester",
|
||||
"components.Settings.toastSettingsSuccess": "Indstillingerne er blevet gemt!",
|
||||
"components.Settings.webpush": "Web Push",
|
||||
"components.UserList.userlist": "Brugerliste",
|
||||
"components.Settings.plexsettings": "Plex-indstillinger",
|
||||
@@ -776,28 +808,40 @@
|
||||
"components.TvDetails.showtype": "Serietype",
|
||||
"components.UserList.sortCreated": "Tilmeldingsdato",
|
||||
"components.UserList.deleteconfirm": "Er du sikker på at du vil slette denne bruger? Alle deres forespørgselsdata vil blive slettet permanent.",
|
||||
"components.Settings.regionTip": "Filtrer indhold efter regional tilgængelighed",
|
||||
"components.Settings.serverpresetManualMessage": "Manuel konfiguration",
|
||||
"components.Settings.serverpresetRefreshing": "Henter servere…",
|
||||
"components.Settings.serviceSettingsDescription": "Konfigurér dine {serverType}server(e) nedenfor. Du kan forbinde til flere forskellige {serverType}servere men kun to af dem kan markeres som standard (én ikke-4K og én 4K). Administratorer kan ændre på serveren der bruges til at behandle nye forespørgsler inden godkendelse.",
|
||||
"components.Settings.settingUpPlexDescription": "For at sætte Plex op skal du enten indtaste oplysningerne manuelt eller vælge en server som hentes fra <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Klik på knappen til højre for rullemenuen for at hente en liste af tilgængelige servere.",
|
||||
"components.Settings.toastApiKeyFailure": "Noget gik galt under genereringen af en nye API-nøgle.",
|
||||
"components.Settings.toastPlexConnectingFailure": "Kunne ikke forbinde til Plex.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Plex forbindelse er etableret!",
|
||||
"components.Settings.toastPlexRefresh": "Henter serverliste fra Plex…",
|
||||
"components.Settings.toastPlexRefreshFailure": "Kunne ikke hente Plex-serverliste.",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Plex-serverliste er blevet hentet!",
|
||||
"components.Settings.toastSettingsFailure": "Noget gik galt. Indstillingerne kunne ikke gemmes.",
|
||||
"components.Settings.validationApplicationTitle": "Du skal angive en applikationstitel",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "URL'en må ikke afsluttes med en skråstreg",
|
||||
"components.Settings.validationHostnameRequired": "Du skal angive et gyldigt domænenavn eller en gyldig IP adresse",
|
||||
"components.Setup.configureplex": "Konfigurér Plex",
|
||||
"components.Settings.validationPortRequired": "Du skal angive et gyldigt port-nummer",
|
||||
"components.Setup.configureservices": "Konfigurér Tjenester",
|
||||
"components.Setup.continue": "Videre",
|
||||
"components.Setup.finish": "Fuldfør Opsætning",
|
||||
"components.Setup.finishing": "Færdiggører…",
|
||||
"components.Setup.loginwithplex": "Log in med Plex",
|
||||
"components.Setup.scanbackground": "Skanningen wil køre i baggrunden. Du kan fortsætte opsætningsprocessen i mellemtiden.",
|
||||
"components.Setup.setup": "Opsætning",
|
||||
"components.StatusChacker.newversionDescription": "Jellyseerr er blevet opdateret! Klik venligst på knappen nedenfor for at genindlæse siden.",
|
||||
"components.TvDetails.firstAirDate": "Første Udsendelsesdato",
|
||||
"components.TvDetails.overview": "Oversigt",
|
||||
"components.StatusChacker.newversionavailable": "Applikationsopdatering",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Komplet Rolleliste",
|
||||
"components.TvDetails.overviewunavailable": "Oversigt utilgængelig.",
|
||||
"components.TvDetails.play4konplex": "Afspil i 4K i Plex",
|
||||
"components.UserList.localuser": "Lokal Bruger",
|
||||
"components.UserList.deleteuser": "Slet Bruger",
|
||||
"components.UserList.displayName": "Kaldenavn",
|
||||
"components.UserList.nouserstoimport": "Ingen nye brugere som kan importeres fra Plex.",
|
||||
"components.UserList.edituser": "Redigér Brugertilladelser",
|
||||
"components.UserList.email": "Email Adresse",
|
||||
@@ -1159,6 +1203,8 @@
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Konfigurér global- og standardindstillinger for Jellyseerr.",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Skjul Tilgængelige Medier",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Tillad delvise serieanmodninger",
|
||||
"components.Settings.SettingsMain.region": "Discover region",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrér indhold efter regional tilgængelighed",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Noget gik galt under genereringen af en nye API-nøgle.",
|
||||
"components.Settings.SettingsMain.trustProxy": "Aktivér Proxy-understøttelse",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Tillad Jellyseerr at registrere klienters IP addresser korrekt bag en proxy",
|
||||
|
||||
@@ -21,8 +21,11 @@
|
||||
"components.Discover.TvGenreList.seriesgenres": "Seriengenres",
|
||||
"components.Discover.TvGenreSlider.tvgenres": "Seriengenres",
|
||||
"components.Discover.discover": "Entdecken",
|
||||
"components.Discover.discovermovies": "Beliebte Filme",
|
||||
"components.Discover.discovertv": "Beliebte Serien",
|
||||
"components.Discover.emptywatchlist": "Hier erscheinen deine zur <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> hinzugefügte Medien.",
|
||||
"components.Discover.plexwatchlist": "Deine Watchlist",
|
||||
"components.Discover.noRequests": "Keine Anfragen.",
|
||||
"components.Discover.plexwatchlist": "Deine Plex Watchlist",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Kürzlich hinzugefügt",
|
||||
"components.Discover.popularmovies": "Beliebte Filme",
|
||||
"components.Discover.populartv": "Beliebte Serien",
|
||||
@@ -124,7 +127,7 @@
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {Version} other {Versionen}} hinterher",
|
||||
"components.Layout.VersionStatus.outofdate": "Veraltet",
|
||||
"components.Layout.VersionStatus.streamdevelop": "Jellyseerr Entwicklung",
|
||||
"components.Layout.VersionStatus.streamstable": "Jellyseerr stabil",
|
||||
"components.Layout.VersionStatus.streamstable": "Jellyseerr Stable",
|
||||
"components.Login.email": "E-Mail-Adresse",
|
||||
"components.Login.forgotpassword": "Passwort vergessen?",
|
||||
"components.Login.loginerror": "Beim Anmelden ist etwas schief gelaufen.",
|
||||
@@ -173,6 +176,8 @@
|
||||
"components.MovieDetails.overview": "Übersicht",
|
||||
"components.MovieDetails.overviewunavailable": "Übersicht nicht verfügbar.",
|
||||
"components.MovieDetails.physicalrelease": "DVD/Bluray-Veröffentlichungen",
|
||||
"components.MovieDetails.play4konplex": "In 4K auf Plex abspielen",
|
||||
"components.MovieDetails.playonplex": "Auf Plex abspielen",
|
||||
"components.MovieDetails.productioncountries": "Produktions {countryCount, plural, one {Land} other {Länder}}",
|
||||
"components.MovieDetails.recommendations": "Empfehlungen",
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Veröffentlichungstermin} other {Veröffentlichungstermine}}",
|
||||
@@ -206,7 +211,7 @@
|
||||
"components.NotificationTypeSelector.mediaapproved": "Anfrage genehmigt",
|
||||
"components.NotificationTypeSelector.mediaapprovedDescription": "Sende Benachrichtigungen, wenn angeforderte Medien manuell genehmigt wurden.",
|
||||
"components.NotificationTypeSelector.mediaautorequested": "Automatisch übermittelte Anfrage",
|
||||
"components.NotificationTypeSelector.mediaautorequestedDescription": "Erhalten eine Benachrichtigung, wenn neue Medienanfragen für Objekte auf deiner Watchlist automatisch übermittelt werden.",
|
||||
"components.NotificationTypeSelector.mediaautorequestedDescription": "Erhalten eine Benachrichtigung, wenn neue Medienanfragen für Objekte auf deiner Plex Watchlist automatisch übermittelt werden.",
|
||||
"components.NotificationTypeSelector.mediaavailable": "Anfrage verfügbar",
|
||||
"components.NotificationTypeSelector.mediaavailableDescription": "Sendet Benachrichtigungen, wenn angeforderte Medien verfügbar werden.",
|
||||
"components.NotificationTypeSelector.mediadeclined": "Anfrage abgelehnt",
|
||||
@@ -266,6 +271,8 @@
|
||||
"components.PermissionEdit.requestMoviesDescription": "Autorisierung zur Übermittlung von Anfragen für nicht-4K-Filme.",
|
||||
"components.PermissionEdit.requestTv": "Serien anfragen",
|
||||
"components.PermissionEdit.requestTvDescription": "Autorisierung zur Übermittlung von Anfragen für nicht-4K-Serien.",
|
||||
"components.PermissionEdit.settings": "Einstellungen verwalten",
|
||||
"components.PermissionEdit.settingsDescription": "Gewähre Berechtigung um globale Einstellungen zu ändern. Ein Benutzer muss über diese Berechtigung verfügen, um sie anderen Benutzern erteilen zu können.",
|
||||
"components.PermissionEdit.users": "Benutzer verwalten",
|
||||
"components.PermissionEdit.usersDescription": "Autorisierung zur Benutzerverwaltung erteilen. Benutzer mit dieser Berechtigung können keine Benutzer mit Admin-Recht ändern oder das Admin-Recht gewähren.",
|
||||
"components.PermissionEdit.viewissues": "Probleme ansehen",
|
||||
@@ -274,8 +281,8 @@
|
||||
"components.PermissionEdit.viewrecentDescription": "Autorisierung zur Anzeige der Liste der kürzlich hinzugefügten Medien.",
|
||||
"components.PermissionEdit.viewrequests": "Anfragen anzeigen",
|
||||
"components.PermissionEdit.viewrequestsDescription": "Autorisierung zur Anzeige der von anderen Benutzern eingereichten Medienanfragen.",
|
||||
"components.PermissionEdit.viewwatchlists": "{mediaServerName} Watchlists anzeigen",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Autorisierung zur Anzeige von {mediaServerName} Watchlists anderer Benutzer.",
|
||||
"components.PermissionEdit.viewwatchlists": "Plex Watchlist anzeigen",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Autorisierung zur Anzeige von Plex Watchlists anderer Benutzer.",
|
||||
"components.PersonDetails.alsoknownas": "Auch bekannt unter: {names}",
|
||||
"components.PersonDetails.appearsin": "Auftritte",
|
||||
"components.PersonDetails.ascharacter": "als {character}",
|
||||
@@ -377,6 +384,7 @@
|
||||
"components.RequestModal.cancel": "Anfrage abbrechen",
|
||||
"components.RequestModal.edit": "Anfrage bearbeiten",
|
||||
"components.RequestModal.errorediting": "Beim Bearbeiten der Anfrage ist etwas schief gelaufen.",
|
||||
"components.RequestModal.extras": "Extras",
|
||||
"components.RequestModal.numberofepisodes": "Anzahl der Folgen",
|
||||
"components.RequestModal.pending4krequest": "Ausstehende 4K Anfrage",
|
||||
"components.RequestModal.pendingapproval": "Deine Anfrage steht noch aus.",
|
||||
@@ -454,7 +462,7 @@
|
||||
"components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Du musst ein Zugangstoken angeben",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Sie müssen mindestens einen Benachrichtigungstypen auswählen",
|
||||
"components.Settings.Notifications.NotificationsPushover.accessToken": "Anwendungs API-Token",
|
||||
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Registriere eine Anwendung</ApplicationRegistrationLink> , um diese mit Jellyseerr benutzen zu können",
|
||||
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Registriere eine Anwendung</ApplicationRegistrationLink> für die Benutzung mit Jellyseerr",
|
||||
"components.Settings.Notifications.NotificationsPushover.agentenabled": "Agent aktivieren",
|
||||
"components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover-Benachrichtigungseinstellungen konnten nicht gespeichert werden.",
|
||||
"components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover-Benachrichtigungseinstellungen erfolgreich gespeichert!",
|
||||
@@ -477,7 +485,7 @@
|
||||
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
|
||||
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Erstelle eine <WebhookLink>Eingehende Webhook</WebhookLink> integration",
|
||||
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Agent aktivieren",
|
||||
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Jellyseerr muss via HTTPS bereitgestellt werden, um Web-Push Benachrichtigungen empfangen zu können.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Um web push Benachrichtigungen zu erhalten, muss Jellyseerr über HTTPS gehen.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push Test Benachrichtigung fehlgeschlagen.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Web push test Benachrichtigung wird gesendet…",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test Benachrichtigung gesendet!",
|
||||
@@ -503,7 +511,7 @@
|
||||
"components.Settings.Notifications.authPass": "SMTP-Passwort",
|
||||
"components.Settings.Notifications.authUser": "SMTP-Benutzername",
|
||||
"components.Settings.Notifications.botAPI": "Bot-Autorisierungstoken",
|
||||
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Bot erstellen</CreateBotLink> für die Verwendung mit Jellyseerr",
|
||||
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Erstelle einen Bot</CreateBotLink> für die Verwendung mit Jellyseerr",
|
||||
"components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL",
|
||||
"components.Settings.Notifications.botUsername": "Bot Benutzername",
|
||||
"components.Settings.Notifications.botUsernameTip": "Benutzern erlauben, einen Chat mit dem Bot zu starten und ihre eigenen Benachrichtigungen konfigurieren",
|
||||
@@ -617,7 +625,7 @@
|
||||
"components.Settings.SettingsAbout.outofdate": "Veraltet",
|
||||
"components.Settings.SettingsAbout.overseerrinformation": "Über Jellyseerr",
|
||||
"components.Settings.SettingsAbout.preferredmethod": "Bevorzugt",
|
||||
"components.Settings.SettingsAbout.runningDevelop": "Sie benutzen den Branch<code>develop</code> von Jellyseerr, welcher nur für Entwickler, bzw. \"Bleeding-Edge\" Tests empfohlen wird.",
|
||||
"components.Settings.SettingsAbout.runningDevelop": "Sie benutzen den <code>develop</code> von Jellyseerr, der nur für diejenigen empfohlen wird, die an der Entwicklung mitwirken oder bei den neuesten Tests helfen.",
|
||||
"components.Settings.SettingsAbout.supportoverseerr": "Unterstütze Jellyseerr",
|
||||
"components.Settings.SettingsAbout.timezone": "Zeitzone",
|
||||
"components.Settings.SettingsAbout.totalmedia": "Medien insgesamt",
|
||||
@@ -625,7 +633,7 @@
|
||||
"components.Settings.SettingsAbout.uptodate": "Auf dem neusten Stand",
|
||||
"components.Settings.SettingsAbout.version": "Version",
|
||||
"components.Settings.SettingsJobsCache.cache": "Cache",
|
||||
"components.Settings.SettingsJobsCache.cacheDescription": "Zur Leistungsoptimierung und um unnötige Anfragen zu minimieren, speichert Jellyseerr Anfragen zwischen.",
|
||||
"components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr speichert Anfragen an externe API Endpunkte zwischen, um die Leistung zu optimieren und unnötige API Aufrufe zu minimieren.",
|
||||
"components.Settings.SettingsJobsCache.cacheflushed": "{cachename} Cache geleert.",
|
||||
"components.Settings.SettingsJobsCache.cachehits": "Treffer",
|
||||
"components.Settings.SettingsJobsCache.cachekeys": "Schlüssel insgesamt",
|
||||
@@ -645,7 +653,7 @@
|
||||
"components.Settings.SettingsJobsCache.flushcache": "Cache leeren",
|
||||
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Bild-Cache-Bereinigung",
|
||||
"components.Settings.SettingsJobsCache.imagecache": "Bild-Cache",
|
||||
"components.Settings.SettingsJobsCache.imagecacheDescription": "Wenn diese Einstellung aktiviert ist, wird Jellyseerr Bilder aus vorkonfigurierten externen Quellen im Proxy-Cache zwischenspeichern. Bilder im Zwischenspeicher werden in deinem Konfigurationsordner gespeichert: <code>{appDataPath}/cache/images</code>.",
|
||||
"components.Settings.SettingsJobsCache.imagecacheDescription": "Wenn diese Funktion in den Einstellungen aktiviert ist, wird Jellyseerr Bilder aus vorkonfigurierten externen Quellen im Proxy-Cache zwischenspeichern. Bilder im Zwischenspeicher werden in deinem Konfigurationsordner gespeichert. Du findest die Dateien unter <code>{appDataPath}/cache/images</code>.",
|
||||
"components.Settings.SettingsJobsCache.imagecachecount": "Bilder im Cache",
|
||||
"components.Settings.SettingsJobsCache.imagecachesize": "Gesamtgröße des Caches",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Scan der zuletzt hinzugefügten Jellyfin Medien",
|
||||
@@ -655,7 +663,7 @@
|
||||
"components.Settings.SettingsJobsCache.jobcancelled": "{jobname} abgebrochen.",
|
||||
"components.Settings.SettingsJobsCache.jobname": "Aufgabenname",
|
||||
"components.Settings.SettingsJobsCache.jobs": "Aufgaben",
|
||||
"components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr führt bestimmte Wartungsaufgaben als regulär geplante Aufgaben durch. Diese können allerdings auch manuell ausgeführt werden, ohne dabei den regulären Zeitplan abzuändern.",
|
||||
"components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr führt bestimmte Wartungsaufgaben als regulär geplante Aufgaben durch, aber sie können auch manuell ausgeführt werden. Manuelles Ausführen einer Aufgabe ändert ihren Zeitplan nicht.",
|
||||
"components.Settings.SettingsJobsCache.jobsandcache": "Aufgaben und Cache",
|
||||
"components.Settings.SettingsJobsCache.jobstarted": "{jobname} gestartet.",
|
||||
"components.Settings.SettingsJobsCache.jobtype": "Art",
|
||||
@@ -756,8 +764,16 @@
|
||||
"components.Settings.address": "Adresse",
|
||||
"components.Settings.addsonarr": "Sonarr-Server hinzufügen",
|
||||
"components.Settings.advancedTooltip": "Bei falscher Konfiguration dieser Einstellung, kann dies zu einer Funktionsstörung führen",
|
||||
"components.Settings.apikey": "API-Schlüssel",
|
||||
"components.Settings.applicationTitle": "Anwendungstitel",
|
||||
"components.Settings.applicationurl": "Anwendungs-URL",
|
||||
"components.Settings.cacheImages": "Bild-Caching aktivieren",
|
||||
"components.Settings.cacheImagesTip": "Alle Bilder Optimieren und lokal speichern (verbraucht viel Speicherplatz)",
|
||||
"components.Settings.cancelscan": "Durchsuchung abbrechen",
|
||||
"components.Settings.copied": "API-Schlüssel in die Zwischenablage kopiert.",
|
||||
"components.Settings.csrfProtection": "Aktiviere CSRF Schutz",
|
||||
"components.Settings.csrfProtectionHoverTip": "Aktiviere diese Option NICHT, es sei denn du weißt, was du tust!",
|
||||
"components.Settings.csrfProtectionTip": "Macht den externen API Zugang schreibgeschützt (setzt HTTPS voraus und Jellyseerr muss neu gestartet werden, damit die Änderungen wirksam werden)",
|
||||
"components.Settings.currentlibrary": "Aktuelle Bibliothek: {name}",
|
||||
"components.Settings.default": "Standardmäßig",
|
||||
"components.Settings.default4k": "Standard-4K",
|
||||
@@ -767,11 +783,16 @@
|
||||
"components.Settings.enablessl": "SSL aktivieren",
|
||||
"components.Settings.experimentalTooltip": "Die Aktivierung dieser Einstellung kann zu einem unerwarteten Verhalten der Anwendung führen",
|
||||
"components.Settings.externalUrl": "Externe URL",
|
||||
"components.Settings.general": "Allgemein",
|
||||
"components.Settings.generalsettings": "Allgemeine Einstellungen",
|
||||
"components.Settings.generalsettingsDescription": "Konfiguriere Globale und Standard Jellyseerr-Einstellungen.",
|
||||
"components.Settings.hideAvailable": "Verfügbare Medien ausblenden",
|
||||
"components.Settings.hostname": "Hostname oder IP-Adresse",
|
||||
"components.Settings.is4k": "4K",
|
||||
"components.Settings.librariesRemaining": "Verbleibende Bibliotheken: {count}",
|
||||
"components.Settings.locale": "Sprache darstellen",
|
||||
"components.Settings.manualscan": "Manuelle Bibliotheksdurchsuchung",
|
||||
"components.Settings.manualscanDescription": "Normalerweise wird dies nur einmal alle 24 Stunden ausgeführt. Jellyseerr überprüft die kürzlich hinzugefügten Plex-Server aggressiver. Falls du Plex zum ersten Mal konfigurierst, wird empfohlen, einmalig eine manuelle, komplette Bibliotheksdurchsuchung anzustoßen!",
|
||||
"components.Settings.manualscanDescription": "Normalerweise wird dies nur einmal alle 24 Stunden ausgeführt. Jellyseerr überprüft die kürzlich hinzugefügten Plex-Server aggressiver. Falls du Plex zum ersten Mal konfigurierst, wird eine einmalige vollständige manuelle Bibliotheksdurchsuchung empfohlen!",
|
||||
"components.Settings.mediaTypeMovie": "Film",
|
||||
"components.Settings.mediaTypeSeries": "Serie",
|
||||
"components.Settings.menuAbout": "Über",
|
||||
@@ -789,14 +810,19 @@
|
||||
"components.Settings.notifications": "Benachrichtigungen",
|
||||
"components.Settings.notificationsettings": "Benachrichtigungseinstellungen",
|
||||
"components.Settings.notrunning": "Nicht aktiv",
|
||||
"components.Settings.originallanguage": "Sprache Entdecken",
|
||||
"components.Settings.originallanguageTip": "Filtere Inhalte nach Originalsprache",
|
||||
"components.Settings.partialRequestsEnabled": "Teilserienanfragen erlauben",
|
||||
"components.Settings.plex": "Plex",
|
||||
"components.Settings.plexlibraries": "Plex-Bibliotheken",
|
||||
"components.Settings.plexlibrariesDescription": "Die Bibliotheken, welche Jellyseerr nach Titeln durchsucht. Richte deine Plex Verbindungseinstellungen ein und speichere sie. Sollten keine aufgelistet sein, klicke auf den Button weiter unten.",
|
||||
"components.Settings.plexlibrariesDescription": "Die Bibliotheken, welche Jellyseerr nach Titeln durchsucht. Richte deine Plex-Verbindungseinstellungen ein und speichere sie, klicke auf die Schaltfläche unten, wenn keine aufgeführt sind.",
|
||||
"components.Settings.plexsettings": "Plex-Einstellungen",
|
||||
"components.Settings.plexsettingsDescription": "Konfiguriere die Einstellungen deines Plex Servers. Jellyseerr durchsucht deine Plex Bibliotheken zur Feststellung der verfügbaren Inhalte.",
|
||||
"components.Settings.plexsettingsDescription": "Konfiguriere die Einstellungen für deinen Plex-Server. Jellyseerr durchsucht deine Plex-Bibliotheken, um festzustellen welche Inhalte verfügbar sind.",
|
||||
"components.Settings.port": "Port",
|
||||
"components.Settings.radarrsettings": "Radarr-Einstellungen",
|
||||
"components.Settings.restartrequiredTooltip": "Jellyseerr muss neu gestartet werden, damit Änderungen angewendet werden können",
|
||||
"components.Settings.region": "Region Entdecken",
|
||||
"components.Settings.regionTip": "Filtere Inhalte nach regionaler Verfügbarkeit",
|
||||
"components.Settings.restartrequiredTooltip": "Jellyseerr muss neu gestartet werden, damit Änderungen an dieser Einstellung wirksam werden",
|
||||
"components.Settings.scan": "Bibliotheken synchronisieren",
|
||||
"components.Settings.scanning": "Synchronisieren…",
|
||||
"components.Settings.serverLocal": "lokal",
|
||||
@@ -814,17 +840,26 @@
|
||||
"components.Settings.startscan": "Durchsuchung starten",
|
||||
"components.Settings.tautulliApiKey": "API-Schlüssel",
|
||||
"components.Settings.tautulliSettings": "Tautulli Einstellungen",
|
||||
"components.Settings.tautulliSettingsDescription": "Optionale Einstellungen für den Tautulli-Server konfigurieren. Jellyseerr holt die Überwachungsdaten für Ihre Plex-Medien von Tautulli.",
|
||||
"components.Settings.tautulliSettingsDescription": "Optional die Einstellungen für den Tautulli-Server konfigurieren. Jellyseerr holt die Überwachungsdaten für Ihre Plex-Medien von Tautulli.",
|
||||
"components.Settings.toastApiKeyFailure": "Bei der Generierung eines neuen API-Schlüssels ist etwas schief gelaufen.",
|
||||
"components.Settings.toastApiKeySuccess": "Neuer API-Schlüssel erfolgreich generiert!",
|
||||
"components.Settings.toastPlexConnecting": "Versuche mit Plex zu verbinden …",
|
||||
"components.Settings.toastPlexConnectingFailure": "Verbindung zu Plex fehlgeschlagen.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Plex-Verbindung erfolgreich hergestellt!",
|
||||
"components.Settings.toastPlexRefresh": "Abrufen der Serverliste von Plex …",
|
||||
"components.Settings.toastPlexRefreshFailure": "Fehler beim Abrufen der Plex-Serverliste.",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Plex-Serverliste erfolgreich abgerufen!",
|
||||
"components.Settings.toastSettingsFailure": "Beim Speichern der Einstellungen ist etwas schief gelaufen.",
|
||||
"components.Settings.toastSettingsSuccess": "Einstellungen erfolgreich gespeichert!",
|
||||
"components.Settings.toastTautulliSettingsFailure": "Beim Speichern der Tautulli-Einstellungen ist etwas schief gegangen.",
|
||||
"components.Settings.toastTautulliSettingsSuccess": "Tautulli-Einstellungen erfolgreich gespeichert!",
|
||||
"components.Settings.trustProxy": "Proxy-Unterstützung aktivieren",
|
||||
"components.Settings.trustProxyTip": "Erlaubt es Jellyseerr Client IP Adressen hinter einem Proxy korrekt zu registrieren (Jellyseerr muss neu gestartet werden, damit die Änderungen wirksam werden)",
|
||||
"components.Settings.urlBase": "URL-Basis",
|
||||
"components.Settings.validationApiKey": "Die Angabe eines API-Schlüssels ist erforderlich",
|
||||
"components.Settings.validationApplicationTitle": "Du musst einen Anwendungstitel angeben",
|
||||
"components.Settings.validationApplicationUrl": "Du musst eine gültige URL angeben",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "Die URL darf nicht mit einem abschließenden Schrägstrich enden",
|
||||
"components.Settings.validationHostnameRequired": "Ein gültiger Hostnamen oder eine IP-Adresse muss angeben werden",
|
||||
"components.Settings.validationPortRequired": "Du musst einen gültigen Port angeben",
|
||||
"components.Settings.validationUrl": "Die Angabe einer gültigen URL ist erforderlich",
|
||||
@@ -835,19 +870,26 @@
|
||||
"components.Settings.webAppUrlTip": "Leite Benutzer optional zur Web-App auf deinen Server statt zur „gehosteten“ Web-App weiter",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.webpush": "Web Push",
|
||||
"components.Setup.configureplex": "Plex konfigurieren",
|
||||
"components.Setup.configureservices": "Dienste konfigurieren",
|
||||
"components.Setup.continue": "Fortfahren",
|
||||
"components.Setup.finish": "Konfiguration beenden",
|
||||
"components.Setup.finishing": "Fertigstellung …",
|
||||
"components.Setup.loginwithplex": "Mit Plex anmelden",
|
||||
"components.Setup.scanbackground": "Das Scannen läuft im Hintergrund. Du kannst in der Zwischenzeit das Setup fortsetzen.",
|
||||
"components.Setup.setup": "Einrichtung",
|
||||
"components.Setup.signinMessage": "Melde dich zunächst an",
|
||||
"components.Setup.signinMessage": "Melde dich zunächst mit deinem Plex-Konto an",
|
||||
"components.Setup.tip": "Tipp",
|
||||
"components.Setup.welcome": "Willkommen bei Jellyseerr",
|
||||
"components.StatusBadge.managemedia": "{mediaType} verwalten",
|
||||
"components.StatusBadge.openinarr": "In {arr} öffnen",
|
||||
"components.StatusBadge.playonplex": "Auf {mediaServerName} abspielen",
|
||||
"components.StatusBadge.playonplex": "Auf Plex abspielen",
|
||||
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}F{episodeNumber}",
|
||||
"components.StatusBadge.status": "{status}",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.StatusChacker.newversionDescription": "Jellyseerr wurde aktualisiert! Bitte klicke auf die Schaltfläche unten, um die Seite neu zu laden.",
|
||||
"components.StatusChacker.newversionavailable": "Anwendungsaktualisierung",
|
||||
"components.StatusChacker.reloadJellyseerr": "Jellyseerr neu laden",
|
||||
"components.StatusChecker.appUpdated": "{applicationTitle} aktualisiert",
|
||||
"components.StatusChecker.appUpdatedDescription": "Klicke bitte auf die Schaltfläche unten, um die Anwendung neu zu laden.",
|
||||
"components.StatusChecker.reloadApp": "{applicationTitle} neu laden",
|
||||
@@ -874,6 +916,8 @@
|
||||
"components.TvDetails.originaltitle": "Originaltitel",
|
||||
"components.TvDetails.overview": "Übersicht",
|
||||
"components.TvDetails.overviewunavailable": "Übersicht nicht verfügbar.",
|
||||
"components.TvDetails.play4konplex": "In 4K auf Plex abspielen",
|
||||
"components.TvDetails.playonplex": "Auf Plex abspielen",
|
||||
"components.TvDetails.productioncountries": "Produktions {countryCount, plural, one {Land} other {Länder}}",
|
||||
"components.TvDetails.recommendations": "Empfehlungen",
|
||||
"components.TvDetails.reportissue": "Problem melden",
|
||||
@@ -900,6 +944,7 @@
|
||||
"components.UserList.creating": "Erstelle…",
|
||||
"components.UserList.deleteconfirm": "Möchtest du diesen Benutzer wirklich löschen? Alle seine Anfragendaten werden dauerhaft entfernt.",
|
||||
"components.UserList.deleteuser": "Benutzer löschen",
|
||||
"components.UserList.displayName": "Anzeigename",
|
||||
"components.UserList.edituser": "Benutzerberechtigungen Bearbeiten",
|
||||
"components.UserList.email": "E-Mail-Adresse",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, Plural, ein {Benutzer} other {Benutzer}} Plex-Benutzer erfolgreich importiert!",
|
||||
@@ -925,7 +970,7 @@
|
||||
"i18n.experimental": "Experimentell",
|
||||
"components.UserList.userssaved": "Benutzerberechtigungen erfolgreich gespeichert!",
|
||||
"i18n.advanced": "Erweitert",
|
||||
"components.UserList.validationEmail": "E-Mail-Adresse benötigt",
|
||||
"components.UserList.validationEmail": "Du musst eine gültige E-Mail-Adresse angeben",
|
||||
"components.UserList.users": "Benutzer",
|
||||
"components.UserProfile.recentrequests": "Kürzliche Anfragen",
|
||||
"components.UserProfile.UserSettings.menuPermissions": "Berechtigungen",
|
||||
@@ -1084,7 +1129,7 @@
|
||||
"components.Settings.SettingsMain.validationApplicationUrl": "Du musst eine valide URL spezifizieren",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "Die URL darf nicht mit einem Slash \"/\" enden",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} Filme",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Deine Watchlist",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Deine Plex Watchlist",
|
||||
"components.Discover.tmdbsearch": "TMDB Suche",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Etwas ist schiefgelaufen während der Generierung eines neuen API Schlüssels.",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Einstellungen erfolgreich gespeichert!",
|
||||
@@ -1097,13 +1142,15 @@
|
||||
"components.Discover.tmdbstudio": "TMDB Studio",
|
||||
"components.Settings.SettingsMain.applicationurl": "Anwendung URL",
|
||||
"components.Settings.SettingsMain.cacheImages": "Bild-Caching aktivieren",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Globale- und Standardeinstellungen für Jellyseerr konfigurieren.",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Konfiguration der globalen und Standardeinstellungen für Jellyseerr.",
|
||||
"components.Settings.SettingsMain.originallanguage": "\"Entdecken\" Sprache",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Teilweise Serienanfragen zulassen",
|
||||
"components.Settings.SettingsMain.region": "\"Entdecken\" Region",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Beim Speichern der Einstellungen ist ein Fehler aufgetreten.",
|
||||
"components.Settings.SettingsMain.regionTip": "Inhalte nach regionaler Verfügbarkeit filtern",
|
||||
"components.Discover.tmdbnetwork": "TMDB Sender",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Inhalt nach Originalsprache filtern",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Erlaube Jellyseerr die Client-IP-Adressen hinter einem Proxy korrekt zu erfassen",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Erlaube Jellyseerr, Client-IP-Adressen hinter einem Proxy korrekt zu registrieren",
|
||||
"components.Discover.CreateSlider.addSlider": "Slider hinzufügen",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Benutzerdefinierten Slider erstellen",
|
||||
"components.Discover.CreateSlider.addfail": "Neuer Slider konnte nicht erstellt werden.",
|
||||
@@ -1200,6 +1247,7 @@
|
||||
"components.Layout.UserWarnings.emailInvalid": "E-Mail ist nicht valide.",
|
||||
"components.Login.credentialerror": "Der Benutzername oder das Passwort ist falsch.",
|
||||
"components.Login.emailtooltip": "Die Adresse muss nicht mit Ihrer {mediaServerName}-Instanz verbunden sein.",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Verbinde",
|
||||
"components.Login.initialsigningin": "Verbinden…",
|
||||
"components.Login.save": "hinzufügen",
|
||||
@@ -1231,8 +1279,8 @@
|
||||
"components.Settings.syncing": "Synchronisierung",
|
||||
"components.Settings.timeout": "Zeitüberschreitung",
|
||||
"components.Setup.signin": "Anmelden",
|
||||
"components.Setup.signinWithJellyfin": "Gib deine Jellyfin Daten ein",
|
||||
"components.Setup.signinWithPlex": "Gib deine Plex Daten ein",
|
||||
"components.Setup.signinWithJellyfin": "Verwende dein {mediaServerName} Konto",
|
||||
"components.Setup.signinWithPlex": "Verwende dein Plex-Konto",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Erfolgreich von der Beobachtungsliste entfernt!",
|
||||
"components.TitleCard.watchlistError": "Etwas ist schief gelaufen, versuche es noch einmal.",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> erfolgreich zur Beobachtungsliste hinzugefügt!",
|
||||
@@ -1255,7 +1303,7 @@
|
||||
"i18n.deleting": "Löschen…",
|
||||
"i18n.failed": "Fehlgeschlagen",
|
||||
"i18n.movies": "Filme",
|
||||
"i18n.open": "Offen",
|
||||
"i18n.open": "Öffnen",
|
||||
"i18n.pending": "Ausstehend",
|
||||
"i18n.processing": "Verarbeitung",
|
||||
"i18n.request": "Anfrage",
|
||||
@@ -1268,7 +1316,8 @@
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Gerätestandard",
|
||||
"components.Settings.RadarrModal.tagRequests": "Tag-Anfragen",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Jellyseerr unterstützen",
|
||||
"components.Settings.jellyfinSettingsDescription": "Konfiguriere optional die internen und externen Endpunkte für deinen {mediaServerName} Server. In den meisten Fällen ist die externe URL eine andere als die interne URL. Für die Anmeldung bei {mediaServerName} kann auch eine benutzerdefinierte URL zum Zurücksetzen des Passworts festgelegt werden, falls du auf eine andere Seite zum Zurücksetzen des Passworts umleiten möchtest. Du kannst auch selber einen API Key für Jellyfin anlegen, was bisher automatisch geschah.",
|
||||
"components.Settings.internalUrl": "Interne URL",
|
||||
"components.Settings.jellyfinSettingsDescription": "Konfiguriere optional die internen und externen Endpunkte für deinen {mediaServerName} Server. In den meisten Fällen ist die externe URL eine andere als die interne URL. Für die Anmeldung bei {mediaServerName} kann auch eine benutzerdefinierte URL zum Zurücksetzen des Passworts festgelegt werden, falls du auf eine andere Seite zum Zurücksetzen des Passworts umleiten möchtest.",
|
||||
"components.Settings.jellyfinSettingsFailure": "Beim Speichern der Einstellungen von {mediaServerName} ist ein Fehler aufgetreten.",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Normalerweise wird dieser Vorgang nur einmal alle 24 Stunden durchgeführt. Jellyseerr wird die kürzlich hinzugefügten Bibliotheken deines {mediaServerName} Servers aggressiver überprüfen. Wenn dies das erste Mal ist, dass du Jellyseerr konfigurierst, wird ein einmaliger vollständiger manueller Bibliotheks-Scan empfohlen!",
|
||||
"components.Settings.save": "Änderungen speichern",
|
||||
@@ -1296,55 +1345,5 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Die Einstellungen für Web-Push-Benachrichtigungen konnten nicht gespeichert werden.",
|
||||
"components.UserProfile.localWatchlist": "Beobachtungsliste von {username}",
|
||||
"i18n.approved": "Genehmigt",
|
||||
"pages.returnHome": "Zurück nach Hause",
|
||||
"components.Discover.FilterSlideover.status": "Status",
|
||||
"components.UserList.username": "Benutzername",
|
||||
"components.Login.adminerror": "Du musst einen Adminaccount für den Zugang benutzen.",
|
||||
"components.MovieDetails.watchlistError": "Da ist was schief gelaufen - bitte versuche es noch einmal.",
|
||||
"components.RequestList.RequestItem.profileName": "Profil",
|
||||
"components.Selector.searchStatus": "Status auswählen...",
|
||||
"components.Settings.invalidurlerror": "Es kann keine Verbindung zu {mediaServerName} hergestellt werden.",
|
||||
"components.Settings.jellyfinSyncFailedGenericError": "Es trat ein unbekannter Fehler während der Bibliothekssynchronisation auf",
|
||||
"components.UserList.validationUsername": "Du musst einen Benutzernamen angeben",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "Email benötigt",
|
||||
"components.Login.invalidurlerror": "Es kann keine Verbindung zu {mediaServerName} hergestellt werden.",
|
||||
"components.MovieDetails.removefromwatchlist": "Von der Watchlist entfernen",
|
||||
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> erfolgreich aus der Watchlist entfernt!",
|
||||
"components.Login.back": "Zurück",
|
||||
"components.Login.servertype": "Servertyp",
|
||||
"components.Login.validationHostnameRequired": "Du musst eine gültige IP-Adresse oder einen gültigen Hostnamen angeben",
|
||||
"components.Login.validationPortRequired": "Du musst einen gültigen Port angeben",
|
||||
"components.Login.validationUrlBaseLeadingSlash": "Der URL muss ein Slash vorangestellt sein",
|
||||
"components.Login.validationUrlBaseTrailingSlash": "Die URL-Basis darf nicht auf einem Slash enden",
|
||||
"components.Login.validationUrlTrailingSlash": "Die URL darf nicht auf einem Slash enden",
|
||||
"components.Login.validationservertyperequired": "Bitte wähle einen Servertypen",
|
||||
"components.MovieDetails.addtowatchlist": "Zur Watchlist hinzufügen",
|
||||
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> erfolgreich aus der Watchlist entfernt!",
|
||||
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> erfolgreich zur Watchlist hinzugefügt!",
|
||||
"components.Selector.canceled": "Abgebrochen",
|
||||
"components.Selector.ended": "Beendet",
|
||||
"components.Selector.inProduction": "In Produktion",
|
||||
"components.Selector.pilot": "Pilotfolge",
|
||||
"components.Selector.planned": "Geplant",
|
||||
"components.Selector.returningSeries": "Wiederkehrende Serie",
|
||||
"components.Setup.back": "Zurückgehen",
|
||||
"components.Setup.configemby": "Emby konfigurieren",
|
||||
"components.Setup.configjellyfin": "Jellyfin konfigurieren",
|
||||
"components.Setup.configplex": "Plex konfigurieren",
|
||||
"components.Setup.servertype": "Servertyp auswählen",
|
||||
"components.Setup.signinWithEmby": "Emby-Daten eintragen",
|
||||
"components.Setup.subtitle": "Leg los, indem du einen Medienserver auswählst",
|
||||
"components.StatusBadge.seasonnumber": "S{seasonNumber}",
|
||||
"components.TvDetails.addtowatchlist": "Zur Watchlist hinzufügen",
|
||||
"components.TvDetails.removefromwatchlist": "Von der Watchlist entfernen",
|
||||
"components.TvDetails.watchlistError": "Da lief etwas falsch, versuch es noch einmal.",
|
||||
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> erfolgreich zur Watchlist hinzugefügt!",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Gültige email benötigt",
|
||||
"components.Login.hostname": "{mediaServerName} URL",
|
||||
"components.Login.port": "Port",
|
||||
"components.Login.urlBase": "URL-Basis",
|
||||
"components.Login.enablessl": "Benutze SSL",
|
||||
"components.Settings.jellyfinForgotPasswordUrl": "Passwort vergessen URL",
|
||||
"components.Settings.jellyfinSyncFailedAutomaticGroupedFolders": "Eine benutzerdefinierte Authentifizierung mit automatischer Bibliotheksbündelung wird nicht unterstützt",
|
||||
"components.Settings.jellyfinSyncFailedNoLibrariesFound": "Es wurden keine Bibliotheken gefunden"
|
||||
"pages.returnHome": "Zurück nach Hause"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"components.PermissionEdit.users": "Διαχείριση Χρηστών",
|
||||
"components.PermissionEdit.settingsDescription": "Εκχώρηση άδειας για τροποποίηση των ρυθμίσεων Jellyseerr. Ένας χρήστης χρειάζεται να έχει αυτό το δικαίωμα για να το εκχωρήσει σε άλλους.",
|
||||
"components.PermissionEdit.settings": "Διαχείριση Ρυθμίσεων",
|
||||
"components.PermissionEdit.requestTvDescription": "Χορήγηση άδειας για υποβολής αιτημάτων σειρών που δεν είναι 4K.",
|
||||
"components.PermissionEdit.requestTv": "Αιτήματα για Σειρές",
|
||||
"components.PermissionEdit.requestMoviesDescription": "Χορήγηση άδειας για υποβολή αιτημάτων ταινιών που δεν είναι 4K.",
|
||||
@@ -50,6 +52,8 @@
|
||||
"components.MovieDetails.revenue": "Έσοδα",
|
||||
"components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}",
|
||||
"components.MovieDetails.recommendations": "Προτάσεις",
|
||||
"components.MovieDetails.playonplex": "Αναπαραγωγή στο Plex",
|
||||
"components.MovieDetails.play4konplex": "Αναπαραγωγή σε 4K στο Plex",
|
||||
"components.MovieDetails.overviewunavailable": "Επισκόπηση μη διαθέσιμη.",
|
||||
"components.MovieDetails.overview": "Επισκόπηση",
|
||||
"components.MovieDetails.originaltitle": "Αρχικός Τίτλος",
|
||||
@@ -74,6 +78,8 @@
|
||||
"components.Login.email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
||||
"components.Layout.VersionStatus.streamstable": "Overseer Σταθερή Έκδοση",
|
||||
"components.Discover.popularmovies": "Δημοφιλείς Ταινίες",
|
||||
"components.Discover.discovertv": "Δημοφιλείς Σειρές",
|
||||
"components.Discover.discovermovies": "Δημοφιλείς Ταινίες",
|
||||
"components.Discover.discover": "Ανακάλυψε",
|
||||
"components.Discover.TvGenreSlider.tvgenres": "Είδη Σειρών",
|
||||
"components.Discover.TvGenreList.seriesgenres": "Είδη Σειρών",
|
||||
@@ -613,6 +619,8 @@
|
||||
"components.TvDetails.showtype": "Τύπος σειράς",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# Σεζόν} other {# Σεζόν}}",
|
||||
"components.TvDetails.recommendations": "Προτάσεις",
|
||||
"components.TvDetails.playonplex": "Αναπαραγωγή στο Plex",
|
||||
"components.TvDetails.play4konplex": "Αναπαραγωγή σε 4K στο Plex",
|
||||
"components.TvDetails.overviewunavailable": "Επισκόπηση μη διαθέσιμη.",
|
||||
"components.TvDetails.overview": "Επισκόπηση",
|
||||
"components.TvDetails.originaltitle": "Αρχικός τίτλος",
|
||||
@@ -626,25 +634,41 @@
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Όλο το Πλήρωμα της Σειράς",
|
||||
"components.TvDetails.TvCast.fullseriescast": "Όλοι οι Ηθοποιοί της Σειράς",
|
||||
"components.StatusChacker.reloadOverseerr": "Επαναφόρτωση",
|
||||
"components.StatusChacker.newversionavailable": "Ενημέρωση εφαρμογής",
|
||||
"components.StatusChacker.newversionDescription": "Το Jellyseerr έχει ενημερωθεί! Κάνε κλικ στο παρακάτω κουμπί για να φορτώσει ξανά η σελίδα.",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.Setup.welcome": "Καλώς ήρθες στο Jellyseerr",
|
||||
"components.Setup.tip": "Συμβουλή",
|
||||
"components.Setup.signinMessage": "Ξεκίνα με την σύνδεση στον λογαριασμό του Plex σου",
|
||||
"components.Setup.setup": "Εγκατάσταση",
|
||||
"components.Setup.scanbackground": "Η σάρωση θα εκτελείται στο παρασκήνιο. Εν τω μεταξύ, μπορείς να συνεχίσεις την διαδικασία εγκατάστασης.",
|
||||
"components.Setup.loginwithplex": "Συνδέσου με το Plex",
|
||||
"components.Setup.finishing": "Ολοκληρώνει…",
|
||||
"components.Setup.finish": "Ολοκλήρωση εγκατάστασης",
|
||||
"components.Setup.continue": "Συνέχεια",
|
||||
"components.Setup.configureservices": "Διαμόρφωση υπηρεσιών",
|
||||
"components.Setup.configureplex": "Διαμόρφωση του Plex",
|
||||
"components.Settings.webpush": "Web Push",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.webAppUrlTip": "Προαιρετικά κατεύθυνε τους χρήστες στην εφαρμογή ιστού στον διακομιστή σας αντί για την εφαρμογή ιστού που \"φιλοξενείται\"",
|
||||
"components.Settings.webAppUrl": "<WebAppLink>Web App</WebAppLink> διεύθυνση URL",
|
||||
"components.Settings.validationPortRequired": "Πρέπει να δώσεις έναν έγκυρο αριθμό θύρας",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "Η διεύθυνση URL δεν πρέπει να τελειώνει με κάθετο",
|
||||
"components.Settings.validationApplicationUrl": "Πρέπει να βάλεις μια έγκυρη διεύθυνση URL",
|
||||
"components.Settings.validationApplicationTitle": "Πρέπει να δώσεις έναν τίτλο εφαρμογής",
|
||||
"components.Settings.trustProxyTip": "Επίτρεψε στο Jellyseerr να καταχωρίζει σωστά τις διευθύνσεις IP του πελάτη πίσω από έναν διακομιστή μεσολάβησης (το Jellyseerr πρέπει να φορτωθεί ξανά για να εφαρμοστούν οι αλλαγές)",
|
||||
"components.Settings.trustProxy": "Ενεργοποίηση υποστήριξης διαμεσολαβητή",
|
||||
"components.Settings.toastSettingsSuccess": "Οι ρυθμίσεις αποθηκεύτηκαν με επιτυχία!",
|
||||
"components.Settings.toastSettingsFailure": "Κάτι πήγε στραβά κατά την αποθήκευση των ρυθμίσεων.",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Η λίστα διακομιστών Plex ανακτήθηκε με επιτυχία!",
|
||||
"components.Settings.toastPlexRefreshFailure": "Απέτυχε η ανάκτηση της λίστας με τους διακομιστές Plex.",
|
||||
"components.Settings.toastPlexRefresh": "Ανάκτηση λίστας διακομιστών από το Plex…",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Η σύνδεση Plex δημιουργήθηκε με επιτυχία!",
|
||||
"components.Settings.toastPlexConnectingFailure": "Απέτυχε να συνδεθεί στο Plex.",
|
||||
"components.Settings.toastPlexConnecting": "Προσπάθεια σύνδεσης στο Plex…",
|
||||
"components.Settings.toastApiKeySuccess": "Δημιουργήθηκε νέο κλειδί API με επιτυχία!",
|
||||
"components.Settings.toastApiKeyFailure": "Κάτι πήγε στραβά κατά τη δημιουργία νέου κλειδιού API.",
|
||||
"components.Settings.startscan": "Έναρξη σάρωσης",
|
||||
"components.Settings.ssl": "SSL",
|
||||
"components.Settings.sonarrsettings": "Ρυθμίσεις Sonarr",
|
||||
@@ -660,6 +684,8 @@
|
||||
"components.Settings.serverLocal": "τοπικά",
|
||||
"components.Settings.scanning": "Συγχρονίζει…",
|
||||
"components.Settings.scan": "Συγχρονισμός των βιβλιοθήκων",
|
||||
"components.Settings.regionTip": "Φιλτράρει το περιεχόμενο βάσει της διαθεσιμότητας του περιεχομένου",
|
||||
"components.Settings.region": "Ανακάλυψε βάσει την περιοχή",
|
||||
"components.Settings.radarrsettings": "Ρυθμίσεις Radarr",
|
||||
"components.Settings.port": "Θύρα",
|
||||
"components.Settings.plexsettingsDescription": "Διαμόρφωσε τις ρυθμίσεις του Plex διακομιστή σου. Το Jellyseerr σαρώνει τις βιβλιοθήκες του Plex για να προσδιορίσει τη διαθεσιμότητα περιεχομένου.",
|
||||
@@ -667,6 +693,9 @@
|
||||
"components.Settings.plexlibrariesDescription": "Οι βιβλιοθήκες που το Jellyseerr θα σαρώνει για τίτλους. Ρύθμισε και αποθήκευσε τις ρυθμίσεις της σύνδεσης του Plex και, στη συνέχεια, κάνε κλικ στο παρακάτω κουμπί, εάν δεν εμφανιστύν οι βιβλιοθήκες.",
|
||||
"components.Settings.plexlibraries": "Βιβλιοθήκες Plex",
|
||||
"components.Settings.plex": "Plex",
|
||||
"components.Settings.partialRequestsEnabled": "Να επιτρέπονται τα εν μέρει αιτήματα για τις Σειρές",
|
||||
"components.Settings.originallanguage": "Ανακάλυψη με βάση την γλώσσα",
|
||||
"components.Settings.originallanguageTip": "Φίλτραρε το περιεχόμενο με βάση την αρχική γλώσσα",
|
||||
"components.Settings.notrunning": "Δεν τρέχει",
|
||||
"components.Settings.notificationsettings": "Ρυθμίσεις Ειδοποιήσεων",
|
||||
"components.Settings.notifications": "Ειδοποιήσεις",
|
||||
@@ -686,17 +715,29 @@
|
||||
"components.Settings.mediaTypeMovie": "ταινία",
|
||||
"components.Settings.manualscanDescription": "Κανονικά, αυτό εκτελείται μόνο μία φορά κάθε 24 ώρες. Το Jellyseerr θα ελέγχει πιο επιθετικά τις προσθήκες που έγιναν πρόσφατα στον διακομιστή Plex. Εάν αυτή είναι η πρώτη φορά που ρυθμίζεις το Plex, συνιστάται μια εφάπαξ πλήρης χειροκίνητη σάρωση βιβλιοθήκης!",
|
||||
"components.Settings.manualscan": "Χειροκίνητη σάρωση βιβλιοθήκης",
|
||||
"components.Settings.locale": "Προβολή Γλώσσας",
|
||||
"components.Settings.librariesRemaining": "Βιβλιοθήκες που απομένουν: {count}",
|
||||
"components.Settings.is4k": "4K",
|
||||
"components.Settings.hostname": "Όνομα κεντρικού υπολογιστή ή διεύθυνση IP",
|
||||
"components.Settings.hideAvailable": "Απόκρυψη διαθέσιμων μέσων",
|
||||
"components.Settings.generalsettings": "Γενικές Ρυθμίσεις",
|
||||
"components.Settings.general": "Γενικές",
|
||||
"components.Settings.enablessl": "Χρήση SSL",
|
||||
"components.Settings.email": "Email",
|
||||
"components.Settings.deleteserverconfirm": "Είσαι σίγουρος ότι θες να διαγράψεις αυτόν τον διακομιστή;",
|
||||
"components.Settings.default4k": "Προεπιλεγμένο 4K",
|
||||
"components.Settings.default": "Προκαθορισμένο",
|
||||
"components.Settings.currentlibrary": "Τρέχουσα βιβλιοθήκη: {name}",
|
||||
"components.Settings.csrfProtectionTip": "Ορισμός εξωτερικής πρόσβασης API σε μόνο για ανάγνωση (απαιτείται HTTPS και το Jellyseerr πρέπει να φορτωθεί ξανά για να εφαρμοστούν οι αλλαγές)",
|
||||
"components.Settings.csrfProtectionHoverTip": "ΜΗΝ ενεργοποιήσεις αυτή τη ρύθμιση αν δεν καταλαβαίνεις τι κάνεις!",
|
||||
"components.Settings.csrfProtection": "Ενεργοποίηση της προστασίας CSRF",
|
||||
"components.Settings.copied": "Αντιγράφηκε το κλειδί API στο πρόχειρο.",
|
||||
"components.Settings.cancelscan": "Ακύρωση σάρωσης",
|
||||
"components.Settings.cacheImagesTip": "Βελτιστοποίηση και αποθήκευση όλων των εικόνων τοπικά (καταναλώνει σημαντικό χώρο στο δίσκο)",
|
||||
"components.Settings.cacheImages": "Ενεργοποίηση προσωρινής αποθήκευσης εικόνων",
|
||||
"components.Settings.applicationurl": "Διεύθυνση URL εφαρμογής",
|
||||
"components.Settings.applicationTitle": "Τίτλος εφαρμογής",
|
||||
"components.Settings.apikey": "Κλειδί API",
|
||||
"components.Settings.addsonarr": "Προσθήκη διακομιστή Sonarr",
|
||||
"components.Settings.address": "Διεύθυνση",
|
||||
"components.Settings.addradarr": "Προσθήκη διακομιστή Radarr",
|
||||
@@ -755,6 +796,7 @@
|
||||
"components.Settings.SonarrModal.add": "Προσθήκη διακομιστή",
|
||||
"components.Settings.SettingsUsers.users": "Χρήστες",
|
||||
"components.Settings.SettingsUsers.userSettings": "Ρυθμίσεις χρήστη",
|
||||
"components.Settings.generalsettingsDescription": "Διαμόρφωση των γενικών και προεπιλεγμένων ρυθμίσεων του Jellyseerr.",
|
||||
"components.Settings.SettingsUsers.userSettingsDescription": "Διαμόρφωση των γενικών και προεπιλεγμένων ρυθμίσεων του χρήστη.",
|
||||
"components.Settings.SettingsUsers.tvRequestLimitLabel": "Καθολικό όριο των αιτημάτων στις Σειρές",
|
||||
"components.Settings.SettingsUsers.toastSettingsSuccess": "Οι ρυθμίσεις του χρήστη αποθηκεύτηκαν επιτυχώς!",
|
||||
@@ -765,6 +807,7 @@
|
||||
"components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help",
|
||||
"components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Ο χρήστης σου ή η συσκευή <LunaSeaLink>ειδοποίηση webhook URL</LunaSeaLink>",
|
||||
"components.RequestModal.numberofepisodes": "# Αριθμός Επεισοδίων",
|
||||
"components.RequestModal.extras": "Έξτρας",
|
||||
"components.MovieDetails.studio": "{studioCount, plural, one {Στούντιο} other {Στούντιο}}",
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} πίσω",
|
||||
"pages.serviceunavailable": "Η υπηρεσία δεν είναι διαθέσιμη",
|
||||
@@ -844,6 +887,7 @@
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Ορισμός εξωτερικής πρόσβασης API σε read-only (απαιτεί HTTPS)",
|
||||
"components.Settings.SettingsMain.general": "Γενικά",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Διαμόρφωση των γενικών και προεπιλεγμένων ρυθμίσεων για το Jellyseerr.",
|
||||
"components.Settings.SettingsMain.regionTip": "Φιλτράρετε το περιεχόμενο βάσει διαθεσιμότητας ανά περιοχή",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Κάτι πήγε στραβά κατά την αποθήκευση των ρυθμίσεων.",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "Το URL δε μπορεί να τελειώνει με κάθετο",
|
||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Αρχικά δικαιώματα που ορίζονται σε νέους χρήστες",
|
||||
@@ -977,6 +1021,7 @@
|
||||
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Συγχρονισμός λίστας παρακολούθησης Plex",
|
||||
"components.Settings.SettingsMain.generalsettings": "Γενικές ρυθμίσεις",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Απόκρυψη διαθέσιμου περιεχομένου",
|
||||
"components.Settings.SettingsMain.region": "Περιοχή σελίδας ανακάλυψης",
|
||||
"components.Settings.restartrequiredTooltip": "Το Jellyseerr πρέπει να κάνει επανεκκίνηση ώστε να εφαρμοστεί αυτή η ρύθμιση",
|
||||
"components.Settings.tautulliSettings": "Ρυθμίσεις Tautulli",
|
||||
"components.Settings.toastTautulliSettingsSuccess": "Οι ρυθμίσεις του Tautulli αποθηκεύτηκαν επιτυχώς!",
|
||||
@@ -1065,6 +1110,7 @@
|
||||
"components.Settings.validationUrl": "Πρέπει να δώσετε ένα έγκυρο URL",
|
||||
"components.StatusBadge.managemedia": "Διαχείριση {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Άνοιγμα σε {arr}",
|
||||
"components.UserList.displayName": "Εμφανιζόμενο όνομα",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Αυτόματη αίτηση σειρών",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Δημιουργήστε ένα token από τις <PushbulletSettingsLink>Ρυθμίσεις Λογαριασμού</PushbulletSettingsLink>",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "<ApplicationRegistrationLink>Καταχωρήστε μια εφαρμογή</ApplicationRegistrationLink> για χρήση με το {applicationTitle}",
|
||||
|
||||
@@ -246,9 +246,6 @@
|
||||
"components.Login.initialsigningin": "Connecting…",
|
||||
"components.Login.invalidurlerror": "Unable to connect to {mediaServerName} server.",
|
||||
"components.Login.loginerror": "Something went wrong while trying to sign in.",
|
||||
"components.Login.loginwithapp": "Login with {appName}",
|
||||
"components.Login.noadminerror": "No admin user found on the server.",
|
||||
"components.Login.orsigninwith": "Or sign in with",
|
||||
"components.Login.password": "Password",
|
||||
"components.Login.port": "Port",
|
||||
"components.Login.save": "Add",
|
||||
@@ -342,7 +339,7 @@
|
||||
"components.MovieDetails.tmdbuserscore": "TMDB User Score",
|
||||
"components.MovieDetails.viewfullcrew": "View Full Crew",
|
||||
"components.MovieDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||
"components.MovieDetails.watchlistError": "Something went wrong. Please try again.",
|
||||
"components.MovieDetails.watchlistError": "Something went wrong try again.",
|
||||
"components.MovieDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||
"components.MovieDetails.watchtrailer": "Watch Trailer",
|
||||
"components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.",
|
||||
@@ -443,6 +440,8 @@
|
||||
"components.PersonDetails.birthdate": "Born {birthdate}",
|
||||
"components.PersonDetails.crewmember": "Crew",
|
||||
"components.PersonDetails.lifespan": "{birthdate} – {deathdate}",
|
||||
"components.PlexLoginButton.signingin": "Signing In…",
|
||||
"components.PlexLoginButton.signinwithplex": "Sign In",
|
||||
"components.QuotaSelector.days": "{count, plural, one {day} other {days}}",
|
||||
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} per {quotaDays} {days}</quotaUnits>",
|
||||
"components.QuotaSelector.movies": "{count, plural, one {movie} other {movies}}",
|
||||
@@ -920,11 +919,7 @@
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
|
||||
"components.Settings.SettingsMain.discoverRegion": "Discover Region",
|
||||
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
|
||||
"components.Settings.SettingsMain.dnsServers": "Custom DNS Servers",
|
||||
"components.Settings.SettingsMain.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"",
|
||||
"components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests",
|
||||
"components.Settings.SettingsMain.forceIpv4First": "IPv4 Resolution First",
|
||||
"components.Settings.SettingsMain.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
|
||||
"components.Settings.SettingsMain.general": "General",
|
||||
"components.Settings.SettingsMain.generalsettings": "General Settings",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
|
||||
@@ -954,15 +949,10 @@
|
||||
"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.validationProxyPort": "You must provide a valid port",
|
||||
"components.Settings.SettingsUsers.atLeastOneAuth": "At least one authentication method must be selected.",
|
||||
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
||||
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password",
|
||||
"components.Settings.SettingsUsers.loginMethods": "Login Methods",
|
||||
"components.Settings.SettingsUsers.loginMethodsTip": "Configure login methods for users.",
|
||||
"components.Settings.SettingsUsers.mediaServerLogin": "Enable {mediaServerName} Sign-In",
|
||||
"components.Settings.SettingsUsers.mediaServerLoginTip": "Allow users to sign in using their {mediaServerName} account",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth",
|
||||
"components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit",
|
||||
"components.Settings.SettingsUsers.newPlexLogin": "Enable New {mediaServerName} Sign-In",
|
||||
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow {mediaServerName} users to sign in without first being imported",
|
||||
@@ -1147,7 +1137,6 @@
|
||||
"components.Setup.continue": "Continue",
|
||||
"components.Setup.finish": "Finish Setup",
|
||||
"components.Setup.finishing": "Finishing…",
|
||||
"components.Setup.librarieserror": "Validation failed. Please toggle the libraries again to continue.",
|
||||
"components.Setup.servertype": "Choose Server Type",
|
||||
"components.Setup.setup": "Setup",
|
||||
"components.Setup.signin": "Sign In",
|
||||
@@ -1176,7 +1165,7 @@
|
||||
"components.TitleCard.tvdbid": "TheTVDB ID",
|
||||
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||
"components.TitleCard.watchlistError": "Something went wrong. Please try again.",
|
||||
"components.TitleCard.watchlistError": "Something went wrong try again.",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||
"components.TvDetails.Season.noepisodes": "Episode list unavailable.",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
|
||||
@@ -1214,7 +1203,7 @@
|
||||
"components.TvDetails.tmdbuserscore": "TMDB User Score",
|
||||
"components.TvDetails.viewfullcrew": "View Full Crew",
|
||||
"components.TvDetails.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
|
||||
"components.TvDetails.watchlistError": "Something went wrong. Please try again.",
|
||||
"components.TvDetails.watchlistError": "Something went wrong try again.",
|
||||
"components.TvDetails.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
|
||||
"components.TvDetails.watchtrailer": "Watch Trailer",
|
||||
"components.UserList.accounttype": "Type",
|
||||
@@ -1398,7 +1387,7 @@
|
||||
"i18n.back": "Back",
|
||||
"i18n.blacklist": "Blacklist",
|
||||
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
|
||||
"i18n.blacklistError": "Something went wrong. Please try again.",
|
||||
"i18n.blacklistError": "Something went wrong try again.",
|
||||
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
|
||||
"i18n.blacklisted": "Blacklisted",
|
||||
"i18n.cancel": "Cancel",
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"components.RequestModal.requestCancel": "Solicitud para <strong>{title}</strong> cancelada.",
|
||||
"components.RequestModal.pendingrequest": "Solicitud pendiente",
|
||||
"components.RequestModal.numberofepisodes": "# de Episodios",
|
||||
"components.RequestModal.extras": "Extras",
|
||||
"components.RequestModal.cancel": "Cancelar Petición",
|
||||
"components.RequestList.requests": "Solicitudes",
|
||||
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}",
|
||||
@@ -143,9 +144,11 @@
|
||||
"components.TvDetails.TvCast.fullseriescast": "Reparto completo de la serie",
|
||||
"components.Setup.welcome": "Bienvenido a Jellyseerr",
|
||||
"components.Setup.signinMessage": "Comience iniciando sesión con su cuenta de Plex",
|
||||
"components.Setup.loginwithplex": "Iniciar sesión con Plex",
|
||||
"components.Setup.finishing": "Finalizando…",
|
||||
"components.Setup.finish": "Finalizar configuración",
|
||||
"components.Setup.continue": "Continuar",
|
||||
"components.Setup.configureplex": "Configurar Plex",
|
||||
"components.Setup.configureservices": "Configurar servicios",
|
||||
"components.Settings.validationPortRequired": "Debes proporcionar un número de puerto válido",
|
||||
"components.Settings.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP válido",
|
||||
@@ -176,6 +179,7 @@
|
||||
"components.Settings.currentlibrary": "Biblioteca actual: {name}",
|
||||
"components.Settings.copied": "Clave API copiada en el portapapeles.",
|
||||
"components.Settings.cancelscan": "Cancelar Escaneo",
|
||||
"components.Setup.tip": "Consejo",
|
||||
"i18n.deleting": "Eliminando…",
|
||||
"components.UserList.userdeleteerror": "Algo salió mal al eliminar al usuario.",
|
||||
"components.UserList.userdeleted": "¡Usuario eliminado con éxito!",
|
||||
@@ -378,6 +382,8 @@
|
||||
"components.PermissionEdit.adminDescription": "Acceso completo de administrador. Ignora otras comprobaciones de permisos.",
|
||||
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envía notificaciones cuando los usuarios solicitan nuevos contenidos que se aprueban automáticamente.",
|
||||
"components.NotificationTypeSelector.mediaAutoApproved": "Petición Aprobada Automáticamente",
|
||||
"components.MovieDetails.playonplex": "Ver en Plex",
|
||||
"components.MovieDetails.play4konplex": "Ver en Plex en 4K",
|
||||
"components.MovieDetails.markavailable": "Marcar como Disponible",
|
||||
"components.MovieDetails.mark4kavailable": "Marcar como Disponible en 4K",
|
||||
"components.Login.forgotpassword": "¿Contraseña olvidada?",
|
||||
@@ -480,10 +486,13 @@
|
||||
"components.UserList.edituser": "Editar Permisos de Usuario",
|
||||
"components.UserList.bulkedit": "Edición Masiva",
|
||||
"components.UserList.accounttype": "Tipo",
|
||||
"components.TvDetails.playonplex": "Ver en Plex",
|
||||
"components.TvDetails.play4konplex": "Ver en Plex en 4K",
|
||||
"components.TvDetails.nextAirDate": "Próxima Emisión",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutos",
|
||||
"components.TvDetails.episodeRuntime": "Duración del Episodio",
|
||||
"components.Setup.setup": "Configuración",
|
||||
"components.Setup.scanbackground": "El escaneo seguirá en segundo plano. Puedes continuar mientras el proceso de configuración.",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.toastPlexRefreshSuccess": "¡Recibida la lista de servidores de Plex con éxito!",
|
||||
"components.Settings.toastPlexRefreshFailure": "Fallo al obtener la lista de servidores de Plex.",
|
||||
@@ -781,6 +790,7 @@
|
||||
"components.Settings.Notifications.validationTypes": "Debes seleccionar, al menos, un tipo de notificación",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Permite a los usuarios registrarse consumo email y password, en lugar de la OAuth de Plex",
|
||||
"components.Settings.webAppUrl": "Url de la <WebAppLink>Web App</WebAppLink>",
|
||||
"components.UserList.displayName": "Nombre en Pantalla",
|
||||
"components.Settings.Notifications.encryption": "Método de Encriptación",
|
||||
"components.Settings.Notifications.encryptionDefault": "Usa STARTTLS si está disponible",
|
||||
"components.Settings.Notifications.encryptionNone": "Ninguna",
|
||||
@@ -1125,6 +1135,8 @@
|
||||
"components.Settings.SettingsMain.locale": "Idioma de visualización",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtrar contenidos por idioma original",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Permitir Solicitudes Parciales de Series",
|
||||
"components.Settings.SettingsMain.region": "Región de Descubre",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrar contenidos por disponibilidad regional",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Algo ha ido mal al generar una nueva clave API.",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "¡Nueva clave API generada con éxito!",
|
||||
"components.Settings.SettingsMain.trustProxy": "Activar la compatibilidad con proxy",
|
||||
@@ -1195,6 +1207,7 @@
|
||||
"components.MovieDetails.imdbuserscore": "Puntuación de los usuarios de IMDB",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Se requiere una contraseña.",
|
||||
"components.Login.credentialerror": "El usuario o contraseña es incorrecto.",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Conectar",
|
||||
"components.Login.initialsigningin": "Conectando…",
|
||||
"components.Login.emailtooltip": "No es necesario asociar la dirección con su instancia de {mediaServerName}.",
|
||||
@@ -1277,6 +1290,7 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Debes indicar un token de acceso",
|
||||
"components.Login.signinwithjellyfin": "Utiliza tu cuenta de {mediaServerName}",
|
||||
"components.ManageSlideOver.removearr4k": "Eliminar de {arr} 4K",
|
||||
"components.Settings.internalUrl": "URL Interna",
|
||||
"components.TvDetails.play4k": "Reproducir 4K en {mediaServerName}",
|
||||
"components.Setup.signin": "Iniciar Sesión",
|
||||
"components.Setup.signinWithJellyfin": "Utiliza tu cuenta de {mediaServerName}",
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"components.RequestModal.requestCancel": "Solicitud para <strong>{title}</strong> cancelada.",
|
||||
"components.RequestModal.pendingrequest": "Solicitud pendiente",
|
||||
"components.RequestModal.numberofepisodes": "# de Episodios",
|
||||
"components.RequestModal.extras": "Extras",
|
||||
"components.RequestModal.cancel": "Cancelar Petición",
|
||||
"components.RequestList.requests": "Solicitudes",
|
||||
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Temporada} other {Temporadas}}",
|
||||
@@ -143,9 +144,11 @@
|
||||
"components.TvDetails.TvCast.fullseriescast": "Reparto completo de la serie",
|
||||
"components.Setup.welcome": "Bienvenido a Jellyseerr",
|
||||
"components.Setup.signinMessage": "Comience iniciando sesión con su cuenta de Plex",
|
||||
"components.Setup.loginwithplex": "Iniciar sesión con Plex",
|
||||
"components.Setup.finishing": "Finalizando…",
|
||||
"components.Setup.finish": "Finalizar configuración",
|
||||
"components.Setup.continue": "Continuar",
|
||||
"components.Setup.configureplex": "Configurar Plex",
|
||||
"components.Setup.configureservices": "Configurar servicios",
|
||||
"components.Settings.validationPortRequired": "Debes proporcionar un número de puerto válido",
|
||||
"components.Settings.validationHostnameRequired": "Debes proporcionar un nombre de host o dirección IP válido",
|
||||
@@ -176,6 +179,7 @@
|
||||
"components.Settings.currentlibrary": "Biblioteca actual: {name}",
|
||||
"components.Settings.copied": "Clave API copiada en el portapapeles.",
|
||||
"components.Settings.cancelscan": "Cancelar Escaneo",
|
||||
"components.Setup.tip": "Consejo",
|
||||
"i18n.deleting": "Eliminando…",
|
||||
"components.UserList.userdeleteerror": "Algo salió mal al eliminar al usuario.",
|
||||
"components.UserList.userdeleted": "¡Usuario eliminado con éxito!",
|
||||
@@ -378,6 +382,8 @@
|
||||
"components.PermissionEdit.adminDescription": "Acceso completo de administrador. Ignora otras comprobaciones de permisos.",
|
||||
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envía notificaciones cuando los usuarios solicitan nuevos contenidos que se aprueban automáticamente.",
|
||||
"components.NotificationTypeSelector.mediaAutoApproved": "Petición Aprobada Automáticamente",
|
||||
"components.MovieDetails.playonplex": "Ver en Plex",
|
||||
"components.MovieDetails.play4konplex": "Ver en Plex en 4K",
|
||||
"components.MovieDetails.markavailable": "Marcar como Disponible",
|
||||
"components.MovieDetails.mark4kavailable": "Marcar como Disponible en 4K",
|
||||
"components.Login.forgotpassword": "¿Contraseña olvidada?",
|
||||
@@ -480,10 +486,13 @@
|
||||
"components.UserList.edituser": "Editar Permisos de Usuario",
|
||||
"components.UserList.bulkedit": "Edición Masiva",
|
||||
"components.UserList.accounttype": "Tipo",
|
||||
"components.TvDetails.playonplex": "Ver en Plex",
|
||||
"components.TvDetails.play4konplex": "Ver en Plex en 4K",
|
||||
"components.TvDetails.nextAirDate": "Próxima Emisión",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutos",
|
||||
"components.TvDetails.episodeRuntime": "Duración del Episodio",
|
||||
"components.Setup.setup": "Configuración",
|
||||
"components.Setup.scanbackground": "El escaneo seguirá en segundo plano. Puedes continuar mientras el proceso de configuración.",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.toastPlexRefreshSuccess": "¡Recibida la lista de servidores de Plex con éxito!",
|
||||
"components.Settings.toastPlexRefreshFailure": "Fallo al obtener la lista de servidores de Plex.",
|
||||
@@ -781,6 +790,7 @@
|
||||
"components.Settings.Notifications.validationTypes": "Debes seleccionar, al menos, un tipo de notificación",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Permite a los usuarios registrarse consumo email y password, en lugar de la OAuth de Plex",
|
||||
"components.Settings.webAppUrl": "Url de la <WebAppLink>Web App</WebAppLink>",
|
||||
"components.UserList.displayName": "Nombre en Pantalla",
|
||||
"components.Settings.Notifications.encryption": "Método de Encriptación",
|
||||
"components.Settings.Notifications.encryptionDefault": "Usa STARTTLS si está disponible",
|
||||
"components.Settings.Notifications.encryptionNone": "Ninguna",
|
||||
@@ -1125,6 +1135,8 @@
|
||||
"components.Settings.SettingsMain.locale": "Idioma de visualización",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtrar contenidos por idioma original",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Permitir Solicitudes Parciales de Series",
|
||||
"components.Settings.SettingsMain.region": "Región de Descubre",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrar contenidos por disponibilidad regional",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Algo ha ido mal al generar una nueva clave API.",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "¡Nueva clave API generada con éxito!",
|
||||
"components.Settings.SettingsMain.trustProxy": "Activar la compatibilidad con proxy",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user