From 0ee3e69a6101f5a8818b6d4c5654d84f6aac322b Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Fri, 17 Jan 2025 11:32:20 +0100 Subject: [PATCH 01/65] feat: upgrade chart to 2.0.0 (#1268) - upgrade jellyseerr to 2.3.0 - disable HPA as it's not documented and not useful - migrate container image to ghcr.io registry Signed-off-by: Ludovic Ortega --- charts/jellyseerr-chart/Chart.yaml | 4 +-- charts/jellyseerr-chart/README.md | 8 ++--- .../templates/deployment.yaml | 2 -- charts/jellyseerr-chart/templates/hpa.yaml | 32 ------------------- charts/jellyseerr-chart/values.yaml | 9 +----- 5 files changed, 5 insertions(+), 50 deletions(-) delete mode 100644 charts/jellyseerr-chart/templates/hpa.yaml diff --git a/charts/jellyseerr-chart/Chart.yaml b/charts/jellyseerr-chart/Chart.yaml index 3e488f0f..b1eeeede 100644 --- a/charts/jellyseerr-chart/Chart.yaml +++ b/charts/jellyseerr-chart/Chart.yaml @@ -3,8 +3,8 @@ kubeVersion: ">=1.23.0-0" name: jellyseerr-chart description: Jellyseerr helm chart for Kubernetes type: application -version: 1.3.0 -appVersion: "2.2.3" +version: 2.0.0 +appVersion: "2.3.0" maintainers: - name: Jellyseerr url: https://github.com/Fallenbagel/jellyseerr diff --git a/charts/jellyseerr-chart/README.md b/charts/jellyseerr-chart/README.md index f587aacb..8db685ac 100644 --- a/charts/jellyseerr-chart/README.md +++ b/charts/jellyseerr-chart/README.md @@ -1,6 +1,6 @@ # jellyseerr-chart -![Version: 1.3.0](https://img.shields.io/badge/Version-1.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.2.3](https://img.shields.io/badge/AppVersion-2.2.3-informational?style=flat-square) +![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square) Jellyseerr helm chart for Kubernetes @@ -25,10 +25,6 @@ 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 | @@ -39,7 +35,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 | `"docker.io"` | | +| image.registry | string | `"ghcr.io"` | | | image.repository | string | `"fallenbagel/jellyseerr"` | | | image.sha | string | `""` | | | image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | diff --git a/charts/jellyseerr-chart/templates/deployment.yaml b/charts/jellyseerr-chart/templates/deployment.yaml index 8c9c57a1..e31c161b 100644 --- a/charts/jellyseerr-chart/templates/deployment.yaml +++ b/charts/jellyseerr-chart/templates/deployment.yaml @@ -5,9 +5,7 @@ metadata: labels: {{- include "jellyseerr.labels" . | nindent 4 }} spec: - {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} - {{- end }} strategy: type: {{ .Values.strategy.type }} selector: diff --git a/charts/jellyseerr-chart/templates/hpa.yaml b/charts/jellyseerr-chart/templates/hpa.yaml deleted file mode 100644 index 291f83de..00000000 --- a/charts/jellyseerr-chart/templates/hpa.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "jellyseerr.fullname" . }} - labels: - {{- include "jellyseerr.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "jellyseerr.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} diff --git a/charts/jellyseerr-chart/values.yaml b/charts/jellyseerr-chart/values.yaml index 50c7865f..3fe78056 100644 --- a/charts/jellyseerr-chart/values.yaml +++ b/charts/jellyseerr-chart/values.yaml @@ -1,7 +1,7 @@ replicaCount: 1 image: - registry: docker.io + registry: ghcr.io repository: fallenbagel/jellyseerr pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion. @@ -94,13 +94,6 @@ resources: {} # cpu: 100m # memory: 128Mi -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - nodeSelector: {} tolerations: [] From 2f4b848b2c0fb61711a0e8e0dbb775d27dc1157c Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 05:14:33 +0800 Subject: [PATCH 02/65] ci: utilise the linux arm64 hosted runners (#1271) * ci: utilise the linux arm64 hosted runners This is an attempt to utilise the linux arm64 hosted runners which should reduce the build times significantly. In addition, this should leverage the github's built-in caching. * ci: fix typo --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f39fe4e..91123e05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,12 +46,13 @@ jobs: build_and_push: name: Build & Publish Docker Images if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]') - runs-on: ubuntu-22.04 + strategy: + matrix: + runner: [ubuntu-22.04, ubuntu-22.04-arm64] + runs-on: ${{ matrix.runner }} 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 @@ -75,13 +76,16 @@ jobs: with: context: . file: ./Dockerfile - platforms: linux/amd64,linux/arm64 + # platforms: linux/amd64,linux/arm64 + platforms: ${{ matrix.runner == 'ubuntu-22.04' && 'linux/amd64' || 'linux/arm64' }} push: true build-args: | COMMIT_TAG=${{ github.sha }} tags: | fallenbagel/jellyseerr:develop ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop + cache-from: type=gha + cache-to: type=gha,mode=max discord: name: Send Discord Notification From 2d814c14161cc27987e9e557a47a28ff60a74d43 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:49:35 +0800 Subject: [PATCH 03/65] ci: attempt to fix arm64 runners with proper scoped caching (#1275) Added platform specific cache scoping and turned off provenance to prevent manifest merging. In addition we are now using ubuntu24.04 in an attempt to get the job to run as ubuntu-22.04 were stalled for more than 18 hours. --- .github/workflows/ci.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91123e05..267dec62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,11 @@ jobs: if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]') strategy: matrix: - runner: [ubuntu-22.04, ubuntu-22.04-arm64] + include: + - runner: ubuntu-22.04 + platform: linux/amd64 + - runner: ubuntu-22.04-arm64 + platform: linux/arm64 runs-on: ${{ matrix.runner }} steps: - name: Checkout @@ -77,15 +81,16 @@ jobs: context: . file: ./Dockerfile # platforms: linux/amd64,linux/arm64 - platforms: ${{ matrix.runner == 'ubuntu-22.04' && 'linux/amd64' || 'linux/arm64' }} + platforms: ${{ matrix.platform }} push: true build-args: | COMMIT_TAG=${{ github.sha }} tags: | fallenbagel/jellyseerr:develop ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.platform }} + provenance: false discord: name: Send Discord Notification From 88e96fa1633abc2bd73b3c0cd7d995852e76b2d3 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:57:07 +0800 Subject: [PATCH 04/65] ci: upgrade runner to ubuntu 24.04 to get arm64 runner working (#1276) This is another attempt to get arm64 runner working by upgrading the runner to ubuntu-24-04, hoping it works better. Related to #1275 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 267dec62..051f89c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: test: name: Lint & Test Build if: github.event_name == 'pull_request' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: node:22-alpine steps: - name: Checkout @@ -49,9 +49,9 @@ jobs: strategy: matrix: include: - - runner: ubuntu-22.04 + - runner: ubuntu-24.04 platform: linux/amd64 - - runner: ubuntu-22.04-arm64 + - runner: ubuntu-24.04-arm64 platform: linux/arm64 runs-on: ${{ matrix.runner }} steps: @@ -96,7 +96,7 @@ jobs: name: Send Discord Notification needs: build_and_push if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]') - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Get Build Job Status uses: technote-space/workflow-conclusion-action@v3 From a8f84d4f744ad4260d7952d11f71c2c40ee72dfe Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:07:45 +0800 Subject: [PATCH 05/65] ci: correct arm runner label (#1277) The issue was all because of using the wrong label. 18 hours wasted waiting for a non-existent runner to start. It is `ubuntu-24.04-arm` https://github.com/orgs/community/discussions/148648#discussion-7793082 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 051f89c1..533dec2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: include: - runner: ubuntu-24.04 platform: linux/amd64 - - runner: ubuntu-24.04-arm64 + - runner: ubuntu-24.04-arm platform: linux/arm64 runs-on: ${{ matrix.runner }} steps: From f09a4326353153143ed9f18c5644b5ccdf242335 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:34:36 +0800 Subject: [PATCH 06/65] ci: add a job to merge and create multi-arch image (#1278) This has to be done now that arm64 and amd64 runs as two seperate jobs. Otherwise, whichever finishes the last would override the other one when pushed --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 533dec2a..6d470874 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,23 +75,47 @@ jobs: echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - - name: Build and push + - name: Build architecture specific images uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile # platforms: linux/amd64,linux/arm64 platforms: ${{ matrix.platform }} - push: true + # dont push until merged + push: false build-args: | COMMIT_TAG=${{ github.sha }} tags: | - fallenbagel/jellyseerr:develop - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop + fallenbagel/jellyseerr:develop-${{ matrix.platform }} + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform }} cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} provenance: false + create_manifest: + name: Create Multi-Architecture Image + needs: build_and_push + runs-on: ubuntu-24.04 + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Create and Push Multi-Architecture Image + uses: docker/build-push-action@v5 + with: + images: | + fallenbagel/jellyseerr:develop-linux-amd64 + fallenbagel/jellyseerr:develop-linux-arm64 + ghcr.io/${{ github.repository_owner }}/jellyseerr:develop-linux-amd64 + ghcr.io/${{ github.repository_owner }}/jellyseerr:develop-linux-arm64 + target: | + fallenbagel/jellyseerr:develop + ghcr.io/${{ github.repository_owner }}/jellyseerr:develop + - name: Inspect Manifest + run: | + docker buildx imagetools inspect fallenbagel/jellyseerr:develop + docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/jellyseerr:develop + discord: name: Send Discord Notification needs: build_and_push From 93d2e26ae902a5a4d4ea0755a249fbfa18be9046 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:42:05 +0800 Subject: [PATCH 07/65] ci: sanitise container tag (#1279) * ci: sanitise container tag Tags cant container `/` so this should sanitise them * ci: use simple case owner name --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d470874..cde6ff0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,8 +87,8 @@ jobs: build-args: | COMMIT_TAG=${{ github.sha }} tags: | - fallenbagel/jellyseerr:develop-${{ matrix.platform }} - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform }} + fallenbagel/jellyseerr:develop-${{ matrix.platform // '/' / '-' }} + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform // '/' / '-' }} cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} provenance: false @@ -110,11 +110,11 @@ jobs: ghcr.io/${{ github.repository_owner }}/jellyseerr:develop-linux-arm64 target: | fallenbagel/jellyseerr:develop - ghcr.io/${{ github.repository_owner }}/jellyseerr:develop + ghcr.io/fallenbagel/jellyseerr:develop - name: Inspect Manifest run: | docker buildx imagetools inspect fallenbagel/jellyseerr:develop - docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/jellyseerr:develop + docker buildx imagetools inspect ghcr.io/fallenbagel/jellyseerr:develop discord: name: Send Discord Notification From fbef7e2c72195210b487b3661b27b982adf25949 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:49:58 +0800 Subject: [PATCH 08/65] ci: seperate job to pass in sanitised platform (#1280) This is done as github actions doesnt support inline replacement that was done on #1279 --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cde6ff0e..0ae6c83e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,11 +70,13 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set lower case owner name + - name: Sanitize Owner and Platform name run: | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} + echo "PLATFORM=${PLATFORM,,}' | sed 's|/|-|g')" >> $GITHUB_ENV env: OWNER: ${{ github.repository_owner }} + PLATFORM: ${{ matrix.platform }} - name: Build architecture specific images uses: docker/build-push-action@v5 with: @@ -87,8 +89,8 @@ jobs: build-args: | COMMIT_TAG=${{ github.sha }} tags: | - fallenbagel/jellyseerr:develop-${{ matrix.platform // '/' / '-' }} - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform // '/' / '-' }} + fallenbagel/jellyseerr:develop-${{ env.PLATFORM }} + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ env.PLATFORM }} cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} provenance: false From 549082c53ed6422e752e817f0ad0186b725cd067 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:55:36 +0800 Subject: [PATCH 09/65] ci: fix typo when sanitising platform (#1281) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ae6c83e..64b33c70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: - name: Sanitize Owner and Platform name run: | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} - echo "PLATFORM=${PLATFORM,,}' | sed 's|/|-|g')" >> $GITHUB_ENV + echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >> $GITHUB_ENV env: OWNER: ${{ github.repository_owner }} PLATFORM: ${{ matrix.platform }} From 17d93a8cb93864e3d58358f9c022cfe464f369c6 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:23:03 +0800 Subject: [PATCH 10/65] ci: fix typo when passing sanitised platform to github env (#1282) * ci: fix typo when passing sanitised platform to github env * ci: hardcode a platform & owner for testing sanitation * ci: fix typo * ci: fix typo * ci: fix yet another typo * ci: fix yet another typo * ci: another typo when echoing the tested variables fixed * ci: properly echo the values from github env * ci: attempt to echo out the sanitised variables * ci: finalise the sanitation test and remove it from lint & test build --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64b33c70..6319ea4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: - name: Sanitize Owner and Platform name run: | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} - echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >> $GITHUB_ENV + echo "PLATFORM=${PLATFORM,,} | sed 's|/|-|g')" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} PLATFORM: ${{ matrix.platform }} From 8afcf5a8d8adb3a6b1663387b38daf693b88ce62 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:25:06 +0800 Subject: [PATCH 11/65] ci: attempt to sanitise the platfom and add to gh env --- .github/workflows/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6319ea4a..2b5fc808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,17 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- + - name: Sanitize Owner and Platform name + run: | + echo "OWNER_LC=$(echo '${OWNER}' | tr '[:upper:]' '[:lower:]')" >>${GITHUB_ENV} + echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >>${GITHUB_ENV} + env: + OWNER: 'Fallenbagel' + PLATFORM: 'linux/amd64' + - name: Test Sanitized Variables + run: | + echo "Sanitized Owner: $OWNER_LC" + echo "Sanitized Platform: $PLATFORM" - name: Install dependencies env: HUSKY: 0 @@ -73,7 +84,7 @@ jobs: - name: Sanitize Owner and Platform name run: | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} - echo "PLATFORM=${PLATFORM,,} | sed 's|/|-|g')" >>${GITHUB_ENV} + echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} PLATFORM: ${{ matrix.platform }} From 5ffe6419eec44bc6b8bb514e2bcb608176cd23eb Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:31:18 +0800 Subject: [PATCH 12/65] ci: remove sanitisation and hardcode platform tag depending on platform --- .github/workflows/ci.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b5fc808..84f76de5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,13 +81,11 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Sanitize Owner and Platform name + - name: Set lower case owner name run: | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} - echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - PLATFORM: ${{ matrix.platform }} - name: Build architecture specific images uses: docker/build-push-action@v5 with: @@ -100,8 +98,8 @@ jobs: build-args: | COMMIT_TAG=${{ github.sha }} tags: | - fallenbagel/jellyseerr:develop-${{ env.PLATFORM }} - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ env.PLATFORM }} + fallenbagel/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }} + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }} cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} provenance: false From ff9af866f83facaa4ad0ed22b22124526226cf4b Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:40:03 +0800 Subject: [PATCH 13/65] ci: use the proper action to merge manifests & pass in lowercase owner --- .github/workflows/ci.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84f76de5..df513cae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,17 +111,24 @@ jobs: steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + + - name: Set lower case owner name + run: | + echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} + env: + OWNER: ${{ github.repository_owner }} + - name: Create and Push Multi-Architecture Image - uses: docker/build-push-action@v5 + uses: docker/manifest-action@v5 with: images: | fallenbagel/jellyseerr:develop-linux-amd64 fallenbagel/jellyseerr:develop-linux-arm64 - ghcr.io/${{ github.repository_owner }}/jellyseerr:develop-linux-amd64 - ghcr.io/${{ github.repository_owner }}/jellyseerr:develop-linux-arm64 + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-amd64 + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-arm64 target: | fallenbagel/jellyseerr:develop - ghcr.io/fallenbagel/jellyseerr:develop + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop - name: Inspect Manifest run: | docker buildx imagetools inspect fallenbagel/jellyseerr:develop From 7cee9b475df0e3757604900b1317d294ca1409f4 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:50:22 +0800 Subject: [PATCH 14/65] ci: fix multi-arch image creation workflow use int128/docker-manifest-create-action to combine the images --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df513cae..33721e7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,16 +119,16 @@ jobs: OWNER: ${{ github.repository_owner }} - name: Create and Push Multi-Architecture Image - uses: docker/manifest-action@v5 + uses: int128/docker-manifest-create-action with: - images: | + tags: | + fallenbagel/jellyseerr:develop + ghcr.io/${{ github.repository_owner }}/jellyseerr:develop + sourceas: | fallenbagel/jellyseerr:develop-linux-amd64 fallenbagel/jellyseerr:develop-linux-arm64 ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-amd64 ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-arm64 - target: | - fallenbagel/jellyseerr:develop - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop - name: Inspect Manifest run: | docker buildx imagetools inspect fallenbagel/jellyseerr:develop From 0fd6ca85a49ef3b68a77025f49564d270d3428bf Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:53:09 +0800 Subject: [PATCH 15/65] ci: fix missing version for create_manifest & discord notification after create_manifest --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33721e7a..31718e62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,7 +119,7 @@ jobs: OWNER: ${{ github.repository_owner }} - name: Create and Push Multi-Architecture Image - uses: int128/docker-manifest-create-action + uses: int128/docker-manifest-create-action@v2 with: tags: | fallenbagel/jellyseerr:develop @@ -136,7 +136,7 @@ jobs: discord: name: Send Discord Notification - needs: build_and_push + needs: create_manifest if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]') runs-on: ubuntu-24.04 steps: From 95737d36e6481155504197a5d69f001dd631ef97 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:59:29 +0800 Subject: [PATCH 16/65] ci: fix typo in create_manifest --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31718e62..e7957f18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,7 +124,7 @@ jobs: tags: | fallenbagel/jellyseerr:develop ghcr.io/${{ github.repository_owner }}/jellyseerr:develop - sourceas: | + sources: | fallenbagel/jellyseerr:develop-linux-amd64 fallenbagel/jellyseerr:develop-linux-arm64 ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-amd64 From 80fc5c1a786c517c10be6ce79726f16310ea87e2 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:43:14 +0800 Subject: [PATCH 17/65] ci: better manifest merging to only push final multi-arch manifest --- .github/workflows/ci.yml | 80 ++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7957f18..ee0618d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: - name: Build run: pnpm build - build_and_push: + build: name: Build & Publish Docker Images if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]') strategy: @@ -65,11 +65,39 @@ jobs: - runner: ubuntu-24.04-arm platform: linux/arm64 runs-on: ${{ matrix.runner }} + outputs: + image-amd64: ${{ steps.meta.outputs.image-amd64 }} + image-arm64: ${{ steps.meta.outputs.image-arm64 }} steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Generate image metadata + id: meta + run: | + echo "image-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}=localhost/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}" >> $GITHUB_OUTPUT + - name: Build platform image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + # dont push until merged + push: false + load: true + build-args: | + COMMIT_TAG=${{ github.sha }} + tags: ${{ steps.meta.outputs[format('image-{0}', matrix.platform == 'linux/amd64' && 'amd64' || 'arm64')] }} + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.platform }} + provenance: false + + 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: @@ -86,57 +114,19 @@ jobs: echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - - name: Build architecture specific images + - name: Create and push manifest uses: docker/build-push-action@v5 with: - context: . - file: ./Dockerfile - # platforms: linux/amd64,linux/arm64 - platforms: ${{ matrix.platform }} - # dont push until merged - push: false - build-args: | - COMMIT_TAG=${{ github.sha }} - tags: | - fallenbagel/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }} - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }} - cache-from: type=gha,scope=${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=${{ matrix.platform }} - provenance: false - - create_manifest: - name: Create Multi-Architecture Image - needs: build_and_push - runs-on: ubuntu-24.04 - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set lower case owner name - run: | - echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} - env: - OWNER: ${{ github.repository_owner }} - - - name: Create and Push Multi-Architecture Image - uses: int128/docker-manifest-create-action@v2 - with: + push: true tags: | fallenbagel/jellyseerr:develop - ghcr.io/${{ github.repository_owner }}/jellyseerr:develop - sources: | - fallenbagel/jellyseerr:develop-linux-amd64 - fallenbagel/jellyseerr:develop-linux-arm64 - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-amd64 - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop-linux-arm64 - - name: Inspect Manifest - run: | - docker buildx imagetools inspect fallenbagel/jellyseerr:develop - docker buildx imagetools inspect ghcr.io/fallenbagel/jellyseerr:develop + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop + platforms: linux/amd64,linux/arm64 + inputs: ${{ needs.build.outputs.image-amd64 }},${{ needs.build.outputs.image-arm64 }} discord: name: Send Discord Notification - needs: create_manifest + needs: merge_and_push if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]') runs-on: ubuntu-24.04 steps: From d7fc03650f39baea6dd64c68d8560eaebdf677ca Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:54:09 +0800 Subject: [PATCH 18/65] ci: use direct docker manifest command to create and push manifest --- .github/workflows/ci.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee0618d8..48c43628 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,15 +114,20 @@ jobs: echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - - name: Create and push manifest - uses: docker/build-push-action@v5 - with: - push: true - tags: | - fallenbagel/jellyseerr:develop - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop - platforms: linux/amd64,linux/arm64 - inputs: ${{ needs.build.outputs.image-amd64 }},${{ needs.build.outputs.image-arm64 }} + - name: Create and push Docker Hub manifest + run: | + docker manifest create fallenbagel/jellyseerr:develop \ + ${{ needs.build.outputs.image-amd64 }} \ + ${{ needs.build.outputs.image-arm64 }} + + docker manifest push fallenbagel/jellyseerr:develop + - name: Create and push GHCR manifest + run: | + docker manifest create ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop \ + ${{ needs.build.outputs.image-amd64 }} \ + ${{ needs.build.outputs.image-arm64 }} + + docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop discord: name: Send Discord Notification From 9143a6c027c0bce5dff9ae97584619764c47e702 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 22:19:39 +0800 Subject: [PATCH 19/65] ci: push temp tags and create multi-arch from them and cleanup --- .github/workflows/ci.yml | 53 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48c43628..c3a8f711 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,30 +65,39 @@ jobs: - runner: ubuntu-24.04-arm platform: linux/arm64 runs-on: ${{ matrix.runner }} - outputs: - image-amd64: ${{ steps.meta.outputs.image-amd64 }} - image-arm64: ${{ steps.meta.outputs.image-arm64 }} steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Generate image metadata - id: meta + - 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 "image-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}=localhost/jellyseerr:develop-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}" >> $GITHUB_OUTPUT - - name: Build platform image + echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} + env: + OWNER: ${{ github.repository_owner }} + - name: Build and push temp platform image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile platforms: ${{ matrix.platform }} - # dont push until merged - push: false - load: true + push: true build-args: | COMMIT_TAG=${{ github.sha }} - tags: ${{ steps.meta.outputs[format('image-{0}', matrix.platform == 'linux/amd64' && 'amd64' || 'arm64')] }} + tags: | + fallenbagel/jellyseerr:temp-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} provenance: false @@ -114,21 +123,25 @@ jobs: echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - - name: Create and push Docker Hub manifest + - name: Create and push manifest run: | docker manifest create fallenbagel/jellyseerr:develop \ - ${{ needs.build.outputs.image-amd64 }} \ - ${{ needs.build.outputs.image-arm64 }} - + fallenbagel/jellyseerr:temp-amd64 \ + fallenbagel/jellyseerr:temp-arm64 docker manifest push fallenbagel/jellyseerr:develop - - name: Create and push GHCR manifest - run: | - docker manifest create ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop \ - ${{ needs.build.outputs.image-amd64 }} \ - ${{ needs.build.outputs.image-arm64 }} + # GHCR manifest + docker manifest create ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop \ + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-amd64 \ + ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-arm64 docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop + # Cleanup temporary tags + docker manifest rm fallenbagel/jellyseerr:temp-amd64 + docker manifest rm fallenbagel/jellyseerr:temp-arm64 + docker manifest rm ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-amd64 + docker manifest rm ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-arm64 + discord: name: Send Discord Notification needs: merge_and_push From ca739315b2f0fc6c48b14ed6f1c59a32e6f4ead0 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 18 Jan 2025 22:28:58 +0800 Subject: [PATCH 20/65] ci: remove cleanup steps since docker registry v2 doesnt support it --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3a8f711..1eb80e3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,12 +136,6 @@ jobs: ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-arm64 docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop - # Cleanup temporary tags - docker manifest rm fallenbagel/jellyseerr:temp-amd64 - docker manifest rm fallenbagel/jellyseerr:temp-arm64 - docker manifest rm ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-amd64 - docker manifest rm ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-arm64 - discord: name: Send Discord Notification needs: merge_and_push From 396cd968efa49cc369dc255e7d0ff57ca8963203 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sun, 19 Jan 2025 03:41:25 +0800 Subject: [PATCH 21/65] ci: push individual arch digest only and merge from digest (#1284) * ci: push individual arch digest only and merge from digest * ci: correct syntax for docker manifest * ci: add the missing id to the build step * ci: set proper ids and output digest that is dependant on matrix.id * ci: proper dynamic outputs by manually echoing it out * ci: remove unnecessary test step --- .github/workflows/ci.yml | 46 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb80e3d..44b07fd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,17 +32,6 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Sanitize Owner and Platform name - run: | - echo "OWNER_LC=$(echo '${OWNER}' | tr '[:upper:]' '[:lower:]')" >>${GITHUB_ENV} - echo "PLATFORM=$(echo '${PLATFORM}' | sed 's|/|-|g')" >>${GITHUB_ENV} - env: - OWNER: 'Fallenbagel' - PLATFORM: 'linux/amd64' - - name: Test Sanitized Variables - run: | - echo "Sanitized Owner: $OWNER_LC" - echo "Sanitized Platform: $PLATFORM" - name: Install dependencies env: HUSKY: 0 @@ -65,6 +54,9 @@ jobs: - runner: ubuntu-24.04-arm platform: linux/arm64 runs-on: ${{ matrix.runner }} + outputs: + digest-amd64: ${{ steps.set_outputs.outputs.digest-amd64 }} + digest-arm64: ${{ steps.set_outputs.outputs.digest-arm64 }} steps: - name: Checkout uses: actions/checkout@v4 @@ -86,7 +78,18 @@ jobs: echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} env: OWNER: ${{ github.repository_owner }} - - name: Build and push temp platform image + - name: Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + fallenbagel/jellyseerr + ghcr.io/${{ env.OWNER_LC }}/jellyseerr + tags: | + type=ref,event=branch + type=sha,prefix=,suffix=,format=short + - name: Build and push by digest + id: build uses: docker/build-push-action@v5 with: context: . @@ -95,12 +98,17 @@ jobs: push: true build-args: | COMMIT_TAG=${{ github.sha }} - tags: | - fallenbagel/jellyseerr:temp-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} + 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 @@ -126,14 +134,14 @@ jobs: - name: Create and push manifest run: | docker manifest create fallenbagel/jellyseerr:develop \ - fallenbagel/jellyseerr:temp-amd64 \ - fallenbagel/jellyseerr:temp-arm64 + --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 \ - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-amd64 \ - ghcr.io/${{ env.OWNER_LC }}/jellyseerr:temp-arm64 + --amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-amd64 }} \ + --amend ghcr.io/${{ env.OWNER_LC }}/jellyseerr@${{ needs.build.outputs.digest-arm64 }} docker manifest push ghcr.io/${{ env.OWNER_LC }}/jellyseerr:develop discord: From f247642b76ebefd9eeb8aed485573b5d6b133673 Mon Sep 17 00:00:00 2001 From: andrewkolda <158614532+andrewkolda@users.noreply.github.com> Date: Sun, 19 Jan 2025 15:15:54 -0700 Subject: [PATCH 22/65] fix: make watchlist buttons consistent (#1272) fix #1270 Co-authored-by: andrewkolda --- src/components/MovieDetails/index.tsx | 4 ++-- src/components/TvDetails/index.tsx | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 8a78f994..e4087f20 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -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'; diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 0c10ed10..e91d1a5f 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -41,13 +41,11 @@ import { ExclamationTriangleIcon, EyeSlashIcon, FilmIcon, - PlayIcon, -} from '@heroicons/react/24/outline'; -import { - ChevronDownIcon, MinusCircleIcon, + PlayIcon, StarIcon, -} from '@heroicons/react/24/solid'; +} from '@heroicons/react/24/outline'; +import { ChevronDownIcon } from '@heroicons/react/24/solid'; import type { RTRating } from '@server/api/rating/rottentomatoes'; import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; import { IssueStatus } from '@server/constants/issue'; From 1b325e7c3229b84cada73e7a5e3500e5f3760470 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 06:41:25 +0800 Subject: [PATCH 23/65] docs: add andrewkolda as a contributor for design (#1293) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index cf1a568f..0b237554 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -592,6 +592,15 @@ "contributions": [ "infra" ] + }, + { + "login": "andrewkolda", + "name": "andrewkolda", + "avatar_url": "https://avatars.githubusercontent.com/u/158614532?v=4", + "profile": "https://github.com/andrewkolda", + "contributions": [ + "design" + ] } ] } diff --git a/README.md b/README.md index c93939eb..cb4389af 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Translation status GitHub -All Contributors +All Contributors **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/)**. @@ -168,6 +168,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon Metin Bektas
Metin Bektas

🚇 + andrewkolda
andrewkolda

🎨 From 62c1a70b373ee574ad9ff98d322085976dbc7868 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Mon, 20 Jan 2025 13:20:27 +0100 Subject: [PATCH 24/65] feat(helm): Add possibility to pass volumes and volume mounts (#1291) Signed-off-by: Ludovic Ortega --- charts/jellyseerr-chart/Chart.yaml | 2 +- charts/jellyseerr-chart/README.md | 4 +++- charts/jellyseerr-chart/templates/deployment.yaml | 6 ++++++ charts/jellyseerr-chart/values.yaml | 13 +++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/charts/jellyseerr-chart/Chart.yaml b/charts/jellyseerr-chart/Chart.yaml index b1eeeede..5c95ae74 100644 --- a/charts/jellyseerr-chart/Chart.yaml +++ b/charts/jellyseerr-chart/Chart.yaml @@ -3,7 +3,7 @@ kubeVersion: ">=1.23.0-0" name: jellyseerr-chart description: Jellyseerr helm chart for Kubernetes type: application -version: 2.0.0 +version: 2.1.0 appVersion: "2.3.0" maintainers: - name: Jellyseerr diff --git a/charts/jellyseerr-chart/README.md b/charts/jellyseerr-chart/README.md index 8db685ac..01844e53 100644 --- a/charts/jellyseerr-chart/README.md +++ b/charts/jellyseerr-chart/README.md @@ -1,6 +1,6 @@ # jellyseerr-chart -![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square) +![Version: 2.1.0](https://img.shields.io/badge/Version-2.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square) Jellyseerr helm chart for Kubernetes @@ -63,3 +63,5 @@ Kubernetes: `>=1.23.0-0` | serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template | | 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. | diff --git a/charts/jellyseerr-chart/templates/deployment.yaml b/charts/jellyseerr-chart/templates/deployment.yaml index e31c161b..447ecca2 100644 --- a/charts/jellyseerr-chart/templates/deployment.yaml +++ b/charts/jellyseerr-chart/templates/deployment.yaml @@ -65,10 +65,16 @@ 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 }} diff --git a/charts/jellyseerr-chart/values.yaml b/charts/jellyseerr-chart/values.yaml index 3fe78056..861ad361 100644 --- a/charts/jellyseerr-chart/values.yaml +++ b/charts/jellyseerr-chart/values.yaml @@ -94,6 +94,19 @@ 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 + nodeSelector: {} tolerations: [] From 002557d2d0edaa44452a4640624638971fd22d36 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:20:33 +0800 Subject: [PATCH 25/65] docs: changed the name to lowercase (#1296) --- gen-docs/docusaurus.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gen-docs/docusaurus.config.ts b/gen-docs/docusaurus.config.ts index 1aed7043..637741b0 100644 --- a/gen-docs/docusaurus.config.ts +++ b/gen-docs/docusaurus.config.ts @@ -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', }, From 418f0c2eb844e8814aca0d280292e9fb372cc118 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Mon, 27 Jan 2025 15:49:24 +0100 Subject: [PATCH 26/65] fix(helm): no change, fixing OCI manifest corruption (#1310) Signed-off-by: Ludovic Ortega --- charts/jellyseerr-chart/Chart.yaml | 2 +- charts/jellyseerr-chart/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/jellyseerr-chart/Chart.yaml b/charts/jellyseerr-chart/Chart.yaml index 5c95ae74..dd595adf 100644 --- a/charts/jellyseerr-chart/Chart.yaml +++ b/charts/jellyseerr-chart/Chart.yaml @@ -3,7 +3,7 @@ kubeVersion: ">=1.23.0-0" name: jellyseerr-chart description: Jellyseerr helm chart for Kubernetes type: application -version: 2.1.0 +version: 2.1.1 appVersion: "2.3.0" maintainers: - name: Jellyseerr diff --git a/charts/jellyseerr-chart/README.md b/charts/jellyseerr-chart/README.md index 01844e53..678ba00f 100644 --- a/charts/jellyseerr-chart/README.md +++ b/charts/jellyseerr-chart/README.md @@ -1,6 +1,6 @@ # jellyseerr-chart -![Version: 2.1.0](https://img.shields.io/badge/Version-2.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square) +![Version: 2.1.1](https://img.shields.io/badge/Version-2.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.3.0](https://img.shields.io/badge/AppVersion-2.3.0-informational?style=flat-square) Jellyseerr helm chart for Kubernetes From 6ab463285d566c18ef0b4034fbfd0b5863a4f7a5 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Thu, 30 Jan 2025 11:22:23 +0100 Subject: [PATCH 27/65] fix(setup): resolve looping library validation error message (#1316) This PR fixes a bug where the validation error message is displayed over and over because of a React useEffect dependency issue. Previously, the `validateLibraries()` function was being called inside a useEffect that depended on a state that this function was updating. --- src/components/Setup/index.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/Setup/index.tsx b/src/components/Setup/index.tsx index 8a747c6f..8a3d9e66 100644 --- a/src/components/Setup/index.tsx +++ b/src/components/Setup/index.tsx @@ -133,10 +133,6 @@ const Setup = () => { setCurrentStep(3); } } - - if (currentStep === 3) { - validateLibraries(); - } }, [ settings.currentSettings.mediaServerType, settings.currentSettings.initialized, @@ -148,6 +144,13 @@ const Setup = () => { validateLibraries, ]); + useEffect(() => { + if (currentStep === 3) { + validateLibraries(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentStep]); + const handleComplete = () => { validateLibraries(); }; From efaad21554a917bd17bb87d536646b2acf978fbc Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:48:16 +0800 Subject: [PATCH 28/65] build: remove unnecessary files from final docker image (#1314) * build: remove charts from final docker image fix #1313 * build: remove docs too --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4903b48c..91f70963 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN pnpm build # remove development dependencies RUN pnpm prune --prod --ignore-scripts -RUN rm -rf src server .next/cache +RUN rm -rf src server .next/cache charts gen-docs docs RUN touch config/DOCKER From 907ba6fdea0341e8d0f429eaf6aaa404dbc7daff Mon Sep 17 00:00:00 2001 From: Ben Haney <31331498+benhaney@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:04:34 -0600 Subject: [PATCH 29/65] feat(api): make rottentomatoes matching more robust (#1265) --- package.json | 2 + pnpm-lock.yaml | 16 +++++ server/api/rating/rottentomatoes.ts | 104 +++++++++++++++------------- 3 files changed, 72 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index cba4d61c..6e6500ed 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@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", @@ -97,6 +98,7 @@ "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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9944943..de1247df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@tanem/react-nprogress': specifier: 5.0.30 version: 5.0.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/wink-jaro-distance': + specifier: ^2.0.2 + version: 2.0.2 ace-builds: specifier: 1.15.2 version: 1.15.2 @@ -203,6 +206,9 @@ importers: web-push: specifier: 3.5.0 version: 3.5.0 + wink-jaro-distance: + specifier: ^2.0.0 + version: 2.0.0 winston: specifier: 3.8.2 version: 3.8.2 @@ -3250,6 +3256,9 @@ packages: '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + '@types/wink-jaro-distance@2.0.2': + resolution: {integrity: sha512-Q79orp7qA/g/uLdFmqd5MtEa0ZfJW5X1WXikAu8IVHt24IrHWrcTNYNdPpLK5mwVg34C6FQnrv/DMtcUhjE/zA==} + '@types/xml2js@0.4.11': resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==} @@ -9467,6 +9476,9 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + wink-jaro-distance@2.0.0: + resolution: {integrity: sha512-9bcUaXCi9N8iYpGWbFkf83OsBkg17r4hEyxusEzl+nnReLRPqxhB9YNeRn3g54SYnVRNXP029lY3HDsbdxTAuA==} + winston-daily-rotate-file@4.7.1: resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==} engines: {node: '>=8'} @@ -13737,6 +13749,8 @@ snapshots: '@types/webxr@0.5.20': {} + '@types/wink-jaro-distance@2.0.2': {} + '@types/xml2js@0.4.11': dependencies: '@types/node': 22.10.5 @@ -20905,6 +20919,8 @@ snapshots: dependencies: string-width: 4.2.3 + wink-jaro-distance@2.0.0: {} + winston-daily-rotate-file@4.7.1(winston@3.8.2): dependencies: file-stream-rotator: 0.6.1 diff --git a/server/api/rating/rottentomatoes.ts b/server/api/rating/rottentomatoes.ts index 170cbb64..bfded767 100644 --- a/server/api/rating/rottentomatoes.ts +++ b/server/api/rating/rottentomatoes.ts @@ -1,6 +1,7 @@ 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: { @@ -15,7 +16,7 @@ interface RTAlgoliaHit { tmsId: string; type: string; title: string; - titles: string[]; + titles?: string[]; description: string; releaseYear: number; rating: string; @@ -24,9 +25,9 @@ interface RTAlgoliaHit { isEmsSearchable: boolean; rtId: number; vanity: string; - aka: string[]; + aka?: string[]; posterImageUrl: string; - rottenTomatoes: { + rottenTomatoes?: { audienceScore: number; criticsIconUrl: string; wantToSeeCount: number; @@ -47,6 +48,47 @@ 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. @@ -90,47 +132,21 @@ class RottenTomatoes extends ExternalAPI { year: number ): Promise { try { + const filters = encodeURIComponent('isEmsSearchable=1 AND type:"movie"'); const data = await this.post('/queries', { requests: [ { indexName: 'content_rt', - query: name, - params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20', + query: name.replace(/\bthe\b ?/gi, ''), + params: `filters=${filters}&hitsPerPage=20`, }, ], }); const contentResults = data.results.find((r) => r.index === 'content_rt'); + const movie = best(contentResults?.hits || [], name, year); - 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; - } + if (!movie?.rottenTomatoes) return null; return { title: movie.title, @@ -158,33 +174,21 @@ class RottenTomatoes extends ExternalAPI { year?: number ): Promise { try { + const filters = encodeURIComponent('isEmsSearchable=1 AND type:"tv"'); const data = await this.post('/queries', { requests: [ { indexName: 'content_rt', query: name, - params: 'filters=isEmsSearchable%20%3D%201&hitsPerPage=20', + params: `filters=${filters}&hitsPerPage=20`, }, ], }); const contentResults = data.results.find((r) => r.index === 'content_rt'); + const tvshow = best(contentResults?.hits || [], name, year); - 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; - } + if (!tvshow?.rottenTomatoes) return null; return { title: tvshow.title, From 2b7974fa06f196b40de270ad24e54b227143b081 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Tue, 4 Feb 2025 22:01:02 +0100 Subject: [PATCH 30/65] fix(jobs): run plex/jellyfin jobs only for the relevant media server (#1331) Due to merging issues with upstream, some jobs for the Plex media server where also running on Jellyfin/Emby instances. This PR makes them run only when the media server is Plex. fix #1329 --- server/job/schedule.ts | 58 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/server/job/schedule.ts b/server/job/schedule.ts index ffc19daa..df0cd917 100644 --- a/server/job/schedule.ts +++ b/server/job/schedule.ts @@ -70,6 +70,35 @@ 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 @@ -112,21 +141,6 @@ export const startJobs = (): void => { }); } - // Watchlist Sync - scheduledJobs.push({ - id: 'plex-watchlist-sync', - name: 'Plex Watchlist Sync', - type: 'process', - interval: 'seconds', - cronSchedule: jobs['plex-watchlist-sync'].schedule, - job: schedule.scheduleJob(jobs['plex-watchlist-sync'].schedule, () => { - logger.info('Starting scheduled job: Plex Watchlist Sync', { - label: 'Jobs', - }); - watchlistSync.syncWatchlist(); - }), - }); - // Run full radarr scan every 24 hours scheduledJobs.push({ id: 'radarr-scan', @@ -223,19 +237,5 @@ export const startJobs = (): void => { }), }); - scheduledJobs.push({ - id: 'plex-refresh-token', - name: 'Plex Refresh Token', - type: 'process', - interval: 'fixed', - cronSchedule: jobs['plex-refresh-token'].schedule, - job: schedule.scheduleJob(jobs['plex-refresh-token'].schedule, () => { - logger.info('Starting scheduled job: Plex Refresh Token', { - label: 'Jobs', - }); - refreshToken.run(); - }), - }); - logger.info('Scheduled jobs loaded', { label: 'Jobs' }); }; From 24d3f523fc07ff4b28d041b2a74cfb5ab0a788a7 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Fri, 7 Feb 2025 19:23:37 +0100 Subject: [PATCH 31/65] feat: add a robots.txt file (#1335) This PR adds a `robots.txt` file to prevent crawlers to index the website re #1323 --- public/robots.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 public/robots.txt diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..1f53798b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / From 2dbd1096d2756a7213209419d1d4da36e7267959 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 8 Feb 2025 18:12:54 +0100 Subject: [PATCH 32/65] fix: disallow admins to edit other admins in bulk edit (#1340) This PR fixes a bug where admin users could edit the permissions of other admins in the bulk edit modal. fix #1309 --- src/components/UserList/BulkEditModal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/UserList/BulkEditModal.tsx b/src/components/UserList/BulkEditModal.tsx index 3706d2d1..ef33792e 100644 --- a/src/components/UserList/BulkEditModal.tsx +++ b/src/components/UserList/BulkEditModal.tsx @@ -1,9 +1,10 @@ import Modal from '@app/components/Common/Modal'; import PermissionEdit from '@app/components/PermissionEdit'; import type { User } from '@app/hooks/useUser'; -import { useUser } from '@app/hooks/useUser'; +import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import 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'; @@ -79,7 +80,10 @@ const BulkEditModal = ({ const { permissions: allPermissionsEqual } = selectedUsers.reduce( ({ permissions: aPerms }, { permissions: bPerms }) => { return { - permissions: aPerms === bPerms ? aPerms : NaN, + permissions: + aPerms === bPerms || hasPermission(Permission.ADMIN, aPerms) + ? aPerms + : NaN, }; }, { permissions: selectedUsers[0].permissions } From 620135aeac6d9fc284a3daddcafd1964474d2789 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Mon, 10 Feb 2025 00:17:11 +0100 Subject: [PATCH 33/65] fix: resolve a vulnerability with admin token (#1345) By default, the jellyfinAuthToken of every user was always retrieved from the database, and sometimes sent back to the client. Any logged-in user could retrieve this token via a request containing admin user information, and use it to gain full access to Jellyfin. This PR removes the auth token and the device ID from the fields selected by default by TypeORM. --- server/entity/User.ts | 6 +++--- server/routes/auth.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/server/entity/User.ts b/server/entity/User.ts index e4c8314c..c8753bfe 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -83,13 +83,13 @@ export class User { @Column({ nullable: true }) public jellyfinUserId?: string; - @Column({ nullable: true }) + @Column({ nullable: true, select: false }) public jellyfinDeviceId?: string; - @Column({ nullable: true }) + @Column({ nullable: true, select: false }) public jellyfinAuthToken?: string; - @Column({ nullable: true }) + @Column({ nullable: true, select: false }) public plexToken?: string; @Column({ type: 'integer', default: 0 }) diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 5fe0174e..cbfbc3f7 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -263,6 +263,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { // Try to find deviceId that corresponds to jellyfin user, else generate a new one let user = await userRepository.findOne({ where: { jellyfinUsername: body.username }, + select: { id: true, jellyfinDeviceId: true }, }); let deviceId = ''; From 9a2c12e51ce6f89b143ebbeb4f003b4b58027b49 Mon Sep 17 00:00:00 2001 From: "Bryan J." <132493975+chkpwd@users.noreply.github.com> Date: Fri, 14 Feb 2025 05:47:47 -0500 Subject: [PATCH 34/65] docs: update error message for various components (#1354) --- src/components/MovieDetails/index.tsx | 2 +- src/components/TitleCard/index.tsx | 2 +- src/components/TvDetails/index.tsx | 2 +- src/i18n/globalMessages.ts | 2 +- src/i18n/locale/en.json | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index e4087f20..64b88748 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -102,7 +102,7 @@ const messages = defineMessages('components.MovieDetails', { watchlistSuccess: '{title} added to watchlist successfully!', watchlistDeleted: '{title} Removed from watchlist successfully!', - watchlistError: 'Something went wrong try again.', + watchlistError: 'Something went wrong. Please try again.', removefromwatchlist: 'Remove From Watchlist', addtowatchlist: 'Add To Watchlist', }); diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 9a0dafec..6312994e 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -51,7 +51,7 @@ const messages = defineMessages('components.TitleCard', { watchlistDeleted: '{title} Removed from watchlist successfully!', watchlistCancel: 'watchlist for {title} canceled.', - watchlistError: 'Something went wrong try again.', + watchlistError: 'Something went wrong. Please try again.', }); const TitleCard = ({ diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index e91d1a5f..96ba8a9f 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -101,7 +101,7 @@ const messages = defineMessages('components.TvDetails', { watchlistSuccess: '{title} added to watchlist successfully!', watchlistDeleted: '{title} Removed from watchlist successfully!', - watchlistError: 'Something went wrong try again.', + watchlistError: 'Something went wrong. Please try again.', removefromwatchlist: 'Remove From Watchlist', addtowatchlist: 'Add To Watchlist', }); diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts index 1765f193..4fb3e0e3 100644 --- a/src/i18n/globalMessages.ts +++ b/src/i18n/globalMessages.ts @@ -58,7 +58,7 @@ const globalMessages = defineMessages('i18n', { blacklist: 'Blacklist', blacklisted: 'Blacklisted', blacklistSuccess: '{title} was successfully blacklisted.', - blacklistError: 'Something went wrong try again.', + blacklistError: 'Something went wrong. Please try again.', blacklistDuplicateError: '{title} has already been blacklisted.', removeFromBlacklistSuccess: diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 9704b14b..708aee65 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -340,7 +340,7 @@ "components.MovieDetails.tmdbuserscore": "TMDB User Score", "components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.watchlistDeleted": "{title} Removed from watchlist successfully!", - "components.MovieDetails.watchlistError": "Something went wrong try again.", + "components.MovieDetails.watchlistError": "Something went wrong. Please try again.", "components.MovieDetails.watchlistSuccess": "{title} added to watchlist successfully!", "components.MovieDetails.watchtrailer": "Watch Trailer", "components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when other users comment on issues.", @@ -1171,7 +1171,7 @@ "components.TitleCard.tvdbid": "TheTVDB ID", "components.TitleCard.watchlistCancel": "watchlist for {title} canceled.", "components.TitleCard.watchlistDeleted": "{title} Removed from watchlist successfully!", - "components.TitleCard.watchlistError": "Something went wrong try again.", + "components.TitleCard.watchlistError": "Something went wrong. Please try again.", "components.TitleCard.watchlistSuccess": "{title} added to watchlist successfully!", "components.TvDetails.Season.noepisodes": "Episode list unavailable.", "components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.", @@ -1209,7 +1209,7 @@ "components.TvDetails.tmdbuserscore": "TMDB User Score", "components.TvDetails.viewfullcrew": "View Full Crew", "components.TvDetails.watchlistDeleted": "{title} Removed from watchlist successfully!", - "components.TvDetails.watchlistError": "Something went wrong try again.", + "components.TvDetails.watchlistError": "Something went wrong. Please try again.", "components.TvDetails.watchlistSuccess": "{title} added to watchlist successfully!", "components.TvDetails.watchtrailer": "Watch Trailer", "components.UserList.accounttype": "Type", @@ -1393,7 +1393,7 @@ "i18n.back": "Back", "i18n.blacklist": "Blacklist", "i18n.blacklistDuplicateError": "{title} has already been blacklisted.", - "i18n.blacklistError": "Something went wrong try again.", + "i18n.blacklistError": "Something went wrong. Please try again.", "i18n.blacklistSuccess": "{title} was successfully blacklisted.", "i18n.blacklisted": "Blacklisted", "i18n.cancel": "Cancel", From b29959b0637fd8add9598d2a3d05f9a0972b65df Mon Sep 17 00:00:00 2001 From: 0xsysr3ll <31414959+0xSysR3ll@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:28:26 +0100 Subject: [PATCH 35/65] fix: missing plex.tv url in images remotePatterns (#1356) This fixes a 400 error while fetching user's avatar from plex api during user import. --- next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/next.config.js b/next.config.js index 7bb57730..597cba32 100644 --- a/next.config.js +++ b/next.config.js @@ -11,6 +11,7 @@ module.exports = { { hostname: 'gravatar.com' }, { hostname: 'image.tmdb.org' }, { hostname: 'artworks.thetvdb.com' }, + { hostname: 'plex.tv' }, ], }, webpack(config) { From 98a5b0581604ed15363008b70732aad04cc1ae4d Mon Sep 17 00:00:00 2001 From: Gauthier Date: Fri, 14 Feb 2025 17:21:08 +0100 Subject: [PATCH 36/65] ci: set the pnpm version number explicitly (#1357) * ci: set the pnpm version number explicitly pnpm latest version is now v10. We still use v9, and the latest version of pnpm was used in Dockerfile instead of v9. * ci: add missing pnpm v9 --- Dockerfile | 4 ++-- Dockerfile.local | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 91f70963..96fecbe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN \ ;; \ esac -RUN npm install --global pnpm +RUN npm install --global pnpm@9 COPY package.json pnpm-lock.yaml postinstall-win.js ./ RUN CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile @@ -45,7 +45,7 @@ WORKDIR /app RUN apk add --no-cache tzdata tini && rm -rf /tmp/* -RUN npm install -g pnpm +RUN npm install -g pnpm@9 # copy from build image COPY --from=BUILD_IMAGE /app ./ diff --git a/Dockerfile.local b/Dockerfile.local index a60248b9..666fa74f 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -3,7 +3,7 @@ FROM node:22-alpine COPY . /app WORKDIR /app -Run npm install --global pnpm +RUN npm install --global pnpm@9 RUN pnpm install From c181cee328eb867f90d906757b8bddaeb74ba9f2 Mon Sep 17 00:00:00 2001 From: 854562 <44002186+854562@users.noreply.github.com> Date: Sun, 16 Feb 2025 11:37:59 +0100 Subject: [PATCH 37/65] feat: update Jellyfin logo (#1359) * Replaced Jellyfin logo with new version * Update jellyfin.svg --- src/assets/services/jellyfin.svg | 81 +++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/assets/services/jellyfin.svg b/src/assets/services/jellyfin.svg index 4c93218a..7983d340 100644 --- a/src/assets/services/jellyfin.svg +++ b/src/assets/services/jellyfin.svg @@ -1,24 +1,61 @@ From 438ccfe9c37f4848b84e60a2ce64687e0b4e4dc0 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Mon, 17 Feb 2025 20:48:37 +0100 Subject: [PATCH 38/65] fix: assign the keep-alive value explicitly (#1368) * fix: assign the keep-alive value explicitly The Node.js documentation mentions that the default keep-alive value is not used when creating a global agent manually, which is done in customProxyAgent.ts. re #1365 * fix: typo --- server/utils/customProxyAgent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/utils/customProxyAgent.ts b/server/utils/customProxyAgent.ts index 3b622368..96ea7fed 100644 --- a/server/utils/customProxyAgent.ts +++ b/server/utils/customProxyAgent.ts @@ -6,7 +6,7 @@ import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici'; export default async function createCustomProxyAgent( proxySettings: ProxySettings ) { - const defaultAgent = new Agent(); + const defaultAgent = new Agent({ keepAliveTimeout: 5000 }); const skipUrl = (url: string) => { const hostname = new URL(url).hostname; @@ -63,6 +63,7 @@ export default async function createCustomProxyAgent( interceptors: { Client: [noProxyInterceptor], }, + keepAliveTimeout: 5000, }); setGlobalDispatcher(proxyAgent); From e035cd84ae24502f43cf842d6d10621f28719682 Mon Sep 17 00:00:00 2001 From: Ishan Jain Date: Tue, 18 Feb 2025 02:22:05 +0530 Subject: [PATCH 39/65] fix: corrected spelling errors in function names (#1366) --- src/components/MovieDetails/index.tsx | 8 ++++---- src/components/TvDetails/index.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 64b88748..72a598f0 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -190,7 +190,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { }) ) { mediaLinks.push({ - text: getAvalaibleMediaServerName(), + text: getAvailableMediaServerName(), url: plexUrl, svg: , }); @@ -204,7 +204,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { }) ) { mediaLinks.push({ - text: getAvalaible4kMediaServerName(), + text: getAvailable4kMediaServerName(), url: plexUrl4k, svg: , }); @@ -292,7 +292,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { (provider) => provider.iso_3166_1 === streamingRegion )?.flatrate ?? []; - function getAvalaibleMediaServerName() { + function getAvailableMediaServerName() { if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) { return intl.formatMessage(messages.play, { mediaServerName: 'Emby' }); } @@ -304,7 +304,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' }); } - function getAvalaible4kMediaServerName() { + function getAvailable4kMediaServerName() { if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) { return intl.formatMessage(messages.play, { mediaServerName: 'Emby' }); } diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 96ba8a9f..77349b5e 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -187,7 +187,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { }) ) { mediaLinks.push({ - text: getAvalaibleMediaServerName(), + text: getAvailableMediaServerName(), url: plexUrl, svg: , }); @@ -201,7 +201,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { }) ) { mediaLinks.push({ - text: getAvalaible4kMediaServerName(), + text: getAvailable4kMediaServerName(), url: plexUrl4k, svg: , }); @@ -322,7 +322,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { (provider) => provider.iso_3166_1 === streamingRegion )?.flatrate ?? []; - function getAvalaibleMediaServerName() { + function getAvailableMediaServerName() { if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) { return intl.formatMessage(messages.play, { mediaServerName: 'Emby' }); } @@ -334,7 +334,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' }); } - function getAvalaible4kMediaServerName() { + function getAvailable4kMediaServerName() { if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) { return intl.formatMessage(messages.play, { mediaServerName: 'Emby' }); } From 0d2273ff6ea7044abc4642782a8d87c2bddacad0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 05:00:10 +0800 Subject: [PATCH 40/65] docs: add ishanjain28 as a contributor for code (#1369) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 11 ++++- README.md | 101 ++++++++++++++++++++++---------------------- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 0b237554..8779bc16 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -7,7 +7,7 @@ "badgeTemplate": "\"All-orange.svg\"/>", "contributorsPerLine": 7, "projectName": "jellyseerr", - "projectOwner": "Fallenbagel", + "projectOwner": "fallenbagel", "repoType": "github", "repoHost": "https://github.com", "skipCi": true, @@ -601,6 +601,15 @@ "contributions": [ "design" ] + }, + { + "login": "ishanjain28", + "name": "Ishan Jain", + "avatar_url": "https://avatars.githubusercontent.com/u/7921368?v=4", + "profile": "https://ishanjain.me", + "contributions": [ + "code" + ] } ] } diff --git a/README.md b/README.md index cb4389af..70463b60 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Translation status GitHub -All Contributors +All Contributors **Jellyseerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**. @@ -86,89 +86,90 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon - - - - - - - + + + + + + + - - - + + + - - + + - + - - - + + + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - - - + + + - + - - - - - - - + + + + + + + - - - - - - - + + + + + + + +
Fallenbagel
Fallenbagel

💻 🚧
Sean
Sean

🌍 💻
notfakie
notfakie

💻
Mohamed Jumail
Mohamed Jumail

👀
Shilong Jiang
Shilong Jiang

💻
Boring Dragon
Boring Dragon

💻
Samuel Bartík
Samuel Bartík

💻
Fallenbagel
Fallenbagel

💻 🚧
Sean
Sean

🌍 💻
notfakie
notfakie

💻
Mohamed Jumail
Mohamed Jumail

👀
Shilong Jiang
Shilong Jiang

💻
Boring Dragon
Boring Dragon

💻
Samuel Bartík
Samuel Bartík

💻
Thegan Govender
Thegan Govender

💻
jab416171
jab416171

📖
Nicolai Van der Storm
Nicolai Van der Storm

💻
Thegan Govender
Thegan Govender

💻
jab416171
jab416171

📖
Nicolai Van der Storm
Nicolai Van der Storm

💻
Smexhy
Smexhy

🌍
dd060606
dd060606

💻
Daniel
Daniel

💻
dd060606
dd060606

💻
Daniel
Daniel

💻
undone37
undone37

🌍
Chechu García
Chechu García

🌍
Dimitri
Dimitri

🌍
andrey4korop
andrey4korop

💻 🌍
andrey4korop
andrey4korop

💻 🌍
Geoffrey Coulaud
Geoffrey Coulaud

🌍
Pikachu920
Pikachu920

💻
Maxim Yalagin
Maxim Yalagin

💻
Jesse Boswell
Jesse Boswell

💻
Pikachu920
Pikachu920

💻
Maxim Yalagin
Maxim Yalagin

💻
Jesse Boswell
Jesse Boswell

💻
d-fendrich
d-fendrich

🌍
David Fernández Alcoba
David Fernández Alcoba

💻
David Fernández Alcoba
David Fernández Alcoba

💻
Gauvino
Gauvino

🌍
EthanArmbrust
EthanArmbrust

💻
Eduardo
Eduardo

📖
RickLuiken
RickLuiken

💻
EthanArmbrust
EthanArmbrust

💻
Eduardo
Eduardo

📖
RickLuiken
RickLuiken

💻
Br33ce
Br33ce

🌍
Athfan Khaleel
Athfan Khaleel

📖
Athfan Khaleel
Athfan Khaleel

📖
Michael Dallinger
Michael Dallinger

🌍
Janek
Janek

📖
Janek
Janek

📖
Aleksa Siriški
Aleksa Siriški

🚇
Danish Humair
Danish Humair

💻
Stephen Harris
Stephen Harris

📖
Joshua M. Boniface
Joshua M. Boniface

💻
Danish Humair
Danish Humair

💻
Stephen Harris
Stephen Harris

📖
Joshua M. Boniface
Joshua M. Boniface

💻
Gauthier
Gauthier

💻
Gauthier
Gauthier

💻
Kara
Kara

🚇
Joaquin Olivero
Joaquin Olivero

💻
Joaquin Olivero
Joaquin Olivero

💻
Julian Behr
Julian Behr

🌍
ThowZzy
ThowZzy

💻
Joseph Risk
Joseph Risk

💻
Loetwiek
Loetwiek

💻
ThowZzy
ThowZzy

💻
Joseph Risk
Joseph Risk

💻
Loetwiek
Loetwiek

💻
Fuochi
Fuochi

📖
Fuochi
Fuochi

📖
Nir Israel Hen
Nir Israel Hen

🌍
Baraa
Baraa

💻
Francisco Sales
Francisco Sales

💻
Oliver Laing
Oliver Laing

💻
Baraa
Baraa

💻
Francisco Sales
Francisco Sales

💻
Oliver Laing
Oliver Laing

💻
Ludovic Ortega
Ludovic Ortega

🛡️
Joseph Risk
Joseph Risk

💻
Joseph Risk
Joseph Risk

💻
Loetwiek
Loetwiek

💻
Fuochi
Fuochi

📖
David Emrich
David Emrich

💻
Max T. Kristiansen
Max T. Kristiansen

💻
Damien Fajole
Damien Fajole

💻
Ahmed Siddiqui
Ahmed Siddiqui

💻
Chris Bannister
Chris Bannister

💻
Loetwiek
Loetwiek

💻
Fuochi
Fuochi

📖
David Emrich
David Emrich

💻
Max T. Kristiansen
Max T. Kristiansen

💻
Damien Fajole
Damien Fajole

💻
Ahmed Siddiqui
Ahmed Siddiqui

💻
Chris Bannister
Chris Bannister

💻
Joe
Joe

📖
Guillaume ARNOUX
Guillaume ARNOUX

💻
dr-carrot
dr-carrot

💻
Gage Orsburn
Gage Orsburn

💻
GkhnGRBZ
GkhnGRBZ

💻
Ben Haney
Ben Haney

💻
Wunderharke
Wunderharke

📖
Joe
Joe

📖
Guillaume ARNOUX
Guillaume ARNOUX

💻
dr-carrot
dr-carrot

💻
Gage Orsburn
Gage Orsburn

💻
GkhnGRBZ
GkhnGRBZ

💻
Ben Haney
Ben Haney

💻
Wunderharke
Wunderharke

📖
Metin Bektas
Metin Bektas

🚇
andrewkolda
andrewkolda

🎨
Ishan Jain
Ishan Jain

💻
From 525a538f34dfec3bd41974f6b37c1077242f1987 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Thu, 20 Feb 2025 18:27:18 +0100 Subject: [PATCH 41/65] refactor(settings): move network settings to their own settings tab (#1287) * refactor(settings): move network settings to their own settings tab This PR moves the network settings out of the General Settings section to a new Netowrk Settings tab. * fix: add missing translations * fix: fix cypress tests for network settings * refactor: create a separate section for network settings --- cypress/e2e/settings/general-settings.cy.ts | 6 +- overseerr-api.yml | 46 +- server/index.ts | 21 +- server/lib/settings/index.ts | 48 +- .../0005_migrate_network_settings.ts | 33 ++ server/routes/settings/index.ts | 15 + server/utils/restartFlag.ts | 23 +- src/components/Settings/SettingsLayout.tsx | 6 + .../Settings/SettingsMain/index.tsx | 334 ------------- .../Settings/SettingsNetwork/index.tsx | 461 ++++++++++++++++++ src/i18n/locale/en.json | 44 +- src/pages/settings/network.tsx | 16 + 12 files changed, 655 insertions(+), 398 deletions(-) create mode 100644 server/lib/settings/migrations/0005_migrate_network_settings.ts create mode 100644 src/components/Settings/SettingsNetwork/index.tsx create mode 100644 src/pages/settings/network.tsx diff --git a/cypress/e2e/settings/general-settings.cy.ts b/cypress/e2e/settings/general-settings.cy.ts index bcfce1a3..59649ed6 100644 --- a/cypress/e2e/settings/general-settings.cy.ts +++ b/cypress/e2e/settings/general-settings.cy.ts @@ -13,10 +13,10 @@ describe('General Settings', () => { }); it('modifies setting that requires restart', () => { - cy.visit('/settings'); + cy.visit('/settings/network'); cy.get('#trustProxy').click(); - cy.get('[data-testid=settings-main-form]').submit(); + cy.get('[data-testid=settings-network-form]').submit(); cy.get('[data-testid=modal-title]').should( 'contain', 'Server Restart Required' @@ -26,7 +26,7 @@ describe('General Settings', () => { cy.get('[data-testid=modal-title]').should('not.exist'); cy.get('[type=checkbox]#trustProxy').click(); - cy.get('[data-testid=settings-main-form]').submit(); + cy.get('[data-testid=settings-network-form]').submit(); cy.get('[data-testid=modal-title]').should('not.exist'); }); }); diff --git a/overseerr-api.yml b/overseerr-api.yml index f3be28ad..641ce5d7 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -164,12 +164,6 @@ components: applicationUrl: type: string example: https://os.example.com - trustProxy: - type: boolean - example: true - csrfProtection: - type: boolean - example: false hideAvailable: type: boolean example: false @@ -191,12 +185,21 @@ components: enableSpecialEpisodes: type: boolean example: false + NetworkSettings: + type: object + properties: + csrfProtection: + type: boolean + example: false forceIpv4First: type: boolean example: false dnsServers: type: string example: '1.1.1.1' + trustProxy: + type: boolean + example: true PlexLibrary: type: object properties: @@ -2045,6 +2048,37 @@ paths: application/json: schema: $ref: '#/components/schemas/MainSettings' + /settings/network: + get: + summary: Get network settings + description: Retrieves all network settings in a JSON object. + tags: + - settings + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MainSettings' + post: + summary: Update network settings + description: Updates network settings with the provided values. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkSettings' + responses: + '200': + description: 'Values were sucessfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/NetworkSettings' /settings/main/regenerate: post: summary: Get main settings with newly-generated API key diff --git a/server/index.ts b/server/index.ts index dde0d508..e4e872ab 100644 --- a/server/index.ts +++ b/server/index.ts @@ -72,23 +72,26 @@ app // Load Settings const settings = await getSettings().load(); - restartFlag.initializeSettings(settings.main); + restartFlag.initializeSettings(settings); // Check if we force IPv4 first - if (process.env.forceIpv4First === 'true' || settings.main.forceIpv4First) { + if ( + process.env.forceIpv4First === 'true' || + settings.network.forceIpv4First + ) { dns.setDefaultResultOrder('ipv4first'); net.setDefaultAutoSelectFamily(false); } - if (settings.main.dnsServers.trim() !== '') { + if (settings.network.dnsServers.trim() !== '') { dns.setServers( - settings.main.dnsServers.split(',').map((server) => server.trim()) + settings.network.dnsServers.split(',').map((server) => server.trim()) ); } // Register HTTP proxy - if (settings.main.proxy.enabled) { - await createCustomProxyAgent(settings.main.proxy); + if (settings.network.proxy.enabled) { + await createCustomProxyAgent(settings.network.proxy); } // Migrate library types @@ -143,7 +146,7 @@ app await DiscoverSlider.bootstrapSliders(); const server = express(); - if (settings.main.trustProxy) { + if (settings.network.trustProxy) { server.enable('trust proxy'); } server.use(cookieParser()); @@ -164,7 +167,7 @@ app next(); } }); - if (settings.main.csrfProtection) { + if (settings.network.csrfProtection) { server.use( csurf({ cookie: { @@ -194,7 +197,7 @@ app cookie: { maxAge: 1000 * 60 * 60 * 24 * 30, httpOnly: true, - sameSite: settings.main.csrfProtection ? 'strict' : 'lax', + sameSite: settings.network.csrfProtection ? 'strict' : 'lax', secure: 'auto', }, store: new TypeormStore({ diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 343c01e2..258dfe2f 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -115,7 +115,6 @@ export interface MainSettings { apiKey: string; applicationTitle: string; applicationUrl: string; - csrfProtection: boolean; cacheImages: boolean; defaultPermissions: number; defaultQuotas: { @@ -128,13 +127,17 @@ export interface MainSettings { discoverRegion: string; streamingRegion: string; originalLanguage: string; - trustProxy: boolean; mediaServerType: number; partialRequestsEnabled: boolean; enableSpecialEpisodes: boolean; + locale: string; +} + +export interface NetworkSettings { + csrfProtection: boolean; forceIpv4First: boolean; dnsServers: string; - locale: string; + trustProxy: boolean; proxy: ProxySettings; } @@ -313,6 +316,7 @@ export interface AllSettings { public: PublicSettings; notifications: NotificationSettings; jobs: Record; + network: NetworkSettings; } const SETTINGS_PATH = process.env.CONFIG_DIRECTORY @@ -331,7 +335,6 @@ class Settings { apiKey: '', applicationTitle: 'Jellyseerr', applicationUrl: '', - csrfProtection: false, cacheImages: false, defaultPermissions: Permission.REQUEST, defaultQuotas: { @@ -344,23 +347,10 @@ class Settings { discoverRegion: '', streamingRegion: '', originalLanguage: '', - trustProxy: false, mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, enableSpecialEpisodes: false, - forceIpv4First: false, - dnsServers: '', locale: 'en', - proxy: { - enabled: false, - hostname: '', - port: 8080, - useSsl: false, - user: '', - password: '', - bypassFilter: '', - bypassLocalAddresses: true, - }, }, plex: { name: '', @@ -513,6 +503,22 @@ class Settings { schedule: '0 0 5 * * *', }, }, + network: { + csrfProtection: false, + trustProxy: false, + forceIpv4First: false, + dnsServers: '', + proxy: { + enabled: false, + hostname: '', + port: 8080, + useSsl: false, + user: '', + password: '', + bypassFilter: '', + bypassLocalAddresses: true, + }, + }, }; if (initialSettings) { this.data = merge(this.data, initialSettings); @@ -622,6 +628,14 @@ class Settings { this.data.jobs = data; } + get network(): NetworkSettings { + return this.data.network; + } + + set network(data: NetworkSettings) { + this.data.network = data; + } + get clientId(): string { return this.data.clientId; } diff --git a/server/lib/settings/migrations/0005_migrate_network_settings.ts b/server/lib/settings/migrations/0005_migrate_network_settings.ts new file mode 100644 index 00000000..a6ad4844 --- /dev/null +++ b/server/lib/settings/migrations/0005_migrate_network_settings.ts @@ -0,0 +1,33 @@ +import type { AllSettings } from '@server/lib/settings'; + +const migrateNetworkSettings = (settings: any): AllSettings => { + if (settings.network) { + return settings; + } + const newSettings = { ...settings }; + newSettings.network = { + ...settings.network, + csrfProtection: settings.main.csrfProtection ?? false, + trustProxy: settings.main.trustProxy ?? false, + forceIpv4First: settings.main.forceIpv4First ?? false, + dnsServers: settings.main.dnsServers ?? '', + proxy: settings.main.proxy ?? { + enabled: false, + hostname: '', + port: 8080, + useSsl: false, + user: '', + password: '', + bypassFilter: '', + bypassLocalAddresses: true, + }, + }; + delete settings.main.csrfProtection; + delete settings.main.trustProxy; + delete settings.main.forceIpv4First; + delete settings.main.dnsServers; + delete settings.main.proxy; + return newSettings; +}; + +export default migrateNetworkSettings; diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index bc8c5ef7..018f7b46 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -78,6 +78,21 @@ settingsRoutes.post('/main', async (req, res) => { return res.status(200).json(settings.main); }); +settingsRoutes.get('/network', (req, res) => { + const settings = getSettings(); + + res.status(200).json(settings.network); +}); + +settingsRoutes.post('/network', async (req, res) => { + const settings = getSettings(); + + settings.network = merge(settings.network, req.body); + await settings.save(); + + return res.status(200).json(settings.network); +}); + settingsRoutes.post('/main/regenerate', async (req, res, next) => { const settings = getSettings(); diff --git a/server/utils/restartFlag.ts b/server/utils/restartFlag.ts index 6b364d1f..24282a09 100644 --- a/server/utils/restartFlag.ts +++ b/server/utils/restartFlag.ts @@ -1,22 +1,25 @@ -import type { MainSettings } from '@server/lib/settings'; +import type { AllSettings, NetworkSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; class RestartFlag { - private settings: MainSettings; + private networkSettings: NetworkSettings; - public initializeSettings(settings: MainSettings): void { - this.settings = { ...settings }; + public initializeSettings(settings: AllSettings): void { + this.networkSettings = { + ...settings.network, + proxy: { ...settings.network.proxy }, + }; } public isSet(): boolean { - const settings = getSettings().main; + const networkSettings = getSettings().network; 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.networkSettings.csrfProtection !== networkSettings.csrfProtection || + this.networkSettings.trustProxy !== networkSettings.trustProxy || + this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled || + this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First || + this.networkSettings.dnsServers !== networkSettings.dnsServers ); } } diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx index 6336bad0..dd7cd6fa 100644 --- a/src/components/Settings/SettingsLayout.tsx +++ b/src/components/Settings/SettingsLayout.tsx @@ -13,6 +13,7 @@ const messages = defineMessages('components.Settings', { menuPlexSettings: 'Plex', menuJellyfinSettings: '{mediaServerName}', menuServices: 'Services', + menuNetwork: 'Network', menuNotifications: 'Notifications', menuLogs: 'Logs', menuJobs: 'Jobs & Cache', @@ -53,6 +54,11 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => { route: '/settings/services', regex: /^\/settings\/services/, }, + { + text: intl.formatMessage(messages.menuNetwork), + route: '/settings/network', + regex: /^\/settings\/network/, + }, { text: intl.formatMessage(messages.menuNotifications), route: '/settings/notifications/email', diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index fb1df6b5..5ccfaaa2 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -2,7 +2,6 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; import SensitiveInput from '@app/components/Common/SensitiveInput'; -import Tooltip from '@app/components/Common/Tooltip'; import LanguageSelector from '@app/components/LanguageSelector'; import RegionSelector from '@app/components/RegionSelector'; import CopyButton from '@app/components/Settings/CopyButton'; @@ -42,39 +41,15 @@ const messages = defineMessages('components.Settings.SettingsMain', { toastSettingsSuccess: 'Settings saved successfully!', toastSettingsFailure: 'Something went wrong while saving settings.', hideAvailable: 'Hide Available Media', - csrfProtection: 'Enable CSRF Protection', - csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)', - csrfProtectionHoverTip: - 'Do NOT enable this setting unless you understand what you are doing!', cacheImages: 'Enable Image Caching', cacheImagesTip: 'Cache externally sourced images (requires a significant amount of disk space)', - trustProxy: 'Enable Proxy Support', - trustProxyTip: - 'Allow Jellyseerr to correctly register client IP addresses behind a proxy', validationApplicationTitle: 'You must provide an application title', validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', partialRequestsEnabled: 'Allow Partial Series Requests', enableSpecialEpisodes: 'Allow Special Episodes Requests', - forceIpv4First: 'IPv4 Resolution First', - forceIpv4FirstTip: - 'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6', - dnsServers: 'Custom DNS Servers', - dnsServersTip: - 'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"', locale: 'Display Language', - proxyEnabled: 'HTTP(S) Proxy', - proxyHostname: 'Proxy Hostname', - proxyPort: 'Proxy Port', - proxySsl: 'Use SSL For Proxy', - proxyUser: 'Proxy Username', - proxyPassword: 'Proxy Password', - proxyBypassFilter: 'Proxy Ignored Addresses', - proxyBypassFilterTip: - "Use ',' as a separator, and '*.' as a wildcard for subdomains", - proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses', - validationProxyPort: 'You must provide a valid port', }); const SettingsMain = () => { @@ -105,12 +80,6 @@ const SettingsMain = () => { intl.formatMessage(messages.validationApplicationUrlTrailingSlash), (value) => !value || !value.endsWith('/') ), - proxyPort: Yup.number().when('proxyEnabled', { - is: (proxyEnabled: boolean) => proxyEnabled, - then: Yup.number().required( - intl.formatMessage(messages.validationProxyPort) - ), - }), }); const regenerate = async () => { @@ -158,7 +127,6 @@ const SettingsMain = () => { initialValues={{ applicationTitle: data?.applicationTitle, applicationUrl: data?.applicationUrl, - csrfProtection: data?.csrfProtection, hideAvailable: data?.hideAvailable, locale: data?.locale ?? 'en', discoverRegion: data?.discoverRegion, @@ -166,18 +134,7 @@ const SettingsMain = () => { streamingRegion: data?.streamingRegion || 'US', partialRequestsEnabled: data?.partialRequestsEnabled, enableSpecialEpisodes: data?.enableSpecialEpisodes, - forceIpv4First: data?.forceIpv4First, - dnsServers: data?.dnsServers, - trustProxy: data?.trustProxy, cacheImages: data?.cacheImages, - proxyEnabled: data?.proxy?.enabled, - proxyHostname: data?.proxy?.hostname, - proxyPort: data?.proxy?.port, - proxySsl: data?.proxy?.useSsl, - proxyUser: data?.proxy?.user, - proxyPassword: data?.proxy?.password, - proxyBypassFilter: data?.proxy?.bypassFilter, - proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -191,7 +148,6 @@ const SettingsMain = () => { body: JSON.stringify({ applicationTitle: values.applicationTitle, applicationUrl: values.applicationUrl, - csrfProtection: values.csrfProtection, hideAvailable: values.hideAvailable, locale: values.locale, discoverRegion: values.discoverRegion, @@ -199,20 +155,7 @@ const SettingsMain = () => { originalLanguage: values.originalLanguage, partialRequestsEnabled: values.partialRequestsEnabled, enableSpecialEpisodes: values.enableSpecialEpisodes, - forceIpv4First: values.forceIpv4First, - dnsServers: values.dnsServers, - trustProxy: values.trustProxy, cacheImages: values.cacheImages, - proxy: { - enabled: values.proxyEnabled, - hostname: values.proxyHostname, - port: values.proxyPort, - useSsl: values.proxySsl, - user: values.proxyUser, - password: values.proxyPassword, - bypassFilter: values.proxyBypassFilter, - bypassLocalAddresses: values.proxyBypassLocalAddresses, - }, }), }); if (!res.ok) throw new Error(); @@ -321,58 +264,6 @@ const SettingsMain = () => { )} -
- -
- { - setFieldValue('trustProxy', !values.trustProxy); - }} - /> -
-
-
- -
- - { - setFieldValue( - 'csrfProtection', - !values.csrfProtection - ); - }} - /> - -
-
-
- -
- { - setFieldValue('forceIpv4First', !values.forceIpv4First); - }} - /> -
-
-
- -
-
- -
- {errors.dnsServers && - touched.dnsServers && - typeof errors.dnsServers === 'string' && ( -
{errors.dnsServers}
- )} -
-
-
- -
- { - setFieldValue('proxyEnabled', !values.proxyEnabled); - }} - /> -
-
- {values.proxyEnabled && ( - <> -
-
- -
-
- -
- {errors.proxyHostname && - touched.proxyHostname && - typeof errors.proxyHostname === 'string' && ( -
- {errors.proxyHostname} -
- )} -
-
-
- -
-
- -
- {errors.proxyPort && - touched.proxyPort && - typeof errors.proxyPort === 'string' && ( -
{errors.proxyPort}
- )} -
-
-
- -
- { - setFieldValue('proxySsl', !values.proxySsl); - }} - /> -
-
-
- -
-
- -
- {errors.proxyUser && - touched.proxyUser && - typeof errors.proxyUser === 'string' && ( -
{errors.proxyUser}
- )} -
-
-
- -
-
- -
- {errors.proxyPassword && - touched.proxyPassword && - typeof errors.proxyPassword === 'string' && ( -
- {errors.proxyPassword} -
- )} -
-
-
- -
-
- -
- {errors.proxyBypassFilter && - touched.proxyBypassFilter && - typeof errors.proxyBypassFilter === 'string' && ( -
- {errors.proxyBypassFilter} -
- )} -
-
-
- -
- { - setFieldValue( - 'proxyBypassLocalAddresses', - !values.proxyBypassLocalAddresses - ); - }} - /> -
-
-
- - )}
diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx new file mode 100644 index 00000000..82701cda --- /dev/null +++ b/src/components/Settings/SettingsNetwork/index.tsx @@ -0,0 +1,461 @@ +import Button from '@app/components/Common/Button'; +import LoadingSpinner from '@app/components/Common/LoadingSpinner'; +import PageTitle from '@app/components/Common/PageTitle'; +import Tooltip from '@app/components/Common/Tooltip'; +import SettingsBadge from '@app/components/Settings/SettingsBadge'; +import globalMessages from '@app/i18n/globalMessages'; +import defineMessages from '@app/utils/defineMessages'; +import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; +import type { NetworkSettings } from '@server/lib/settings'; +import { Field, Form, Formik } from 'formik'; +import { useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR, { mutate } from 'swr'; +import * as Yup from 'yup'; + +const messages = defineMessages('components.Settings.SettingsNetwork', { + toastSettingsSuccess: 'Settings saved successfully!', + toastSettingsFailure: 'Something went wrong while saving settings.', + network: 'Network', + networksettings: 'Network Settings', + networksettingsDescription: + 'Configure network settings for your Jellyseerr instance.', + csrfProtection: 'Enable CSRF Protection', + csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)', + csrfProtectionHoverTip: + 'Do NOT enable this setting unless you understand what you are doing!', + trustProxy: 'Enable Proxy Support', + trustProxyTip: + 'Allow Jellyseerr to correctly register client IP addresses behind a proxy', + forceIpv4First: 'IPv4 Resolution First', + forceIpv4FirstTip: + 'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6', + dnsServers: 'Custom DNS Servers', + dnsServersTip: + 'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"', + proxyEnabled: 'HTTP(S) Proxy', + proxyHostname: 'Proxy Hostname', + proxyPort: 'Proxy Port', + proxySsl: 'Use SSL For Proxy', + proxyUser: 'Proxy Username', + proxyPassword: 'Proxy Password', + proxyBypassFilter: 'Proxy Ignored Addresses', + proxyBypassFilterTip: + "Use ',' as a separator, and '*.' as a wildcard for subdomains", + proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses', + validationProxyPort: 'You must provide a valid port', +}); + +const SettingsMain = () => { + const { addToast } = useToasts(); + const intl = useIntl(); + const { + data, + error, + mutate: revalidate, + } = useSWR('/api/v1/settings/network'); + + const NetworkSettingsSchema = Yup.object().shape({ + proxyPort: Yup.number().when('proxyEnabled', { + is: (proxyEnabled: boolean) => proxyEnabled, + then: Yup.number().required( + intl.formatMessage(messages.validationProxyPort) + ), + }), + }); + + if (!data && !error) { + return ; + } + + return ( + <> + +
+

+ {intl.formatMessage(messages.networksettings)} +

+

+ {intl.formatMessage(messages.networksettingsDescription)} +

+
+
+ { + try { + const res = await fetch('/api/v1/settings/network', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + csrfProtection: values.csrfProtection, + forceIpv4First: values.forceIpv4First, + dnsServers: values.dnsServers, + trustProxy: values.trustProxy, + proxy: { + enabled: values.proxyEnabled, + hostname: values.proxyHostname, + port: values.proxyPort, + useSsl: values.proxySsl, + user: values.proxyUser, + password: values.proxyPassword, + bypassFilter: values.proxyBypassFilter, + bypassLocalAddresses: values.proxyBypassLocalAddresses, + }, + }), + }); + if (!res.ok) throw new Error(); + mutate('/api/v1/settings/public'); + mutate('/api/v1/status'); + + addToast(intl.formatMessage(messages.toastSettingsSuccess), { + autoDismiss: true, + appearance: 'success', + }); + } catch (e) { + addToast(intl.formatMessage(messages.toastSettingsFailure), { + autoDismiss: true, + appearance: 'error', + }); + } finally { + revalidate(); + } + }} + > + {({ + errors, + touched, + isSubmitting, + isValid, + values, + setFieldValue, + }) => { + return ( +
+
+ +
+ { + setFieldValue('trustProxy', !values.trustProxy); + }} + /> +
+
+
+ +
+ + { + setFieldValue( + 'csrfProtection', + !values.csrfProtection + ); + }} + /> + +
+
+
+ +
+ { + setFieldValue('forceIpv4First', !values.forceIpv4First); + }} + /> +
+
+
+ +
+
+ +
+ {errors.dnsServers && + touched.dnsServers && + typeof errors.dnsServers === 'string' && ( +
{errors.dnsServers}
+ )} +
+
+
+ +
+ { + setFieldValue('proxyEnabled', !values.proxyEnabled); + }} + /> +
+
+ {values.proxyEnabled && ( + <> +
+
+ +
+
+ +
+ {errors.proxyHostname && + touched.proxyHostname && + typeof errors.proxyHostname === 'string' && ( +
+ {errors.proxyHostname} +
+ )} +
+
+
+ +
+
+ +
+ {errors.proxyPort && + touched.proxyPort && + typeof errors.proxyPort === 'string' && ( +
{errors.proxyPort}
+ )} +
+
+
+ +
+ { + setFieldValue('proxySsl', !values.proxySsl); + }} + /> +
+
+
+ +
+
+ +
+ {errors.proxyUser && + touched.proxyUser && + typeof errors.proxyUser === 'string' && ( +
{errors.proxyUser}
+ )} +
+
+
+ +
+
+ +
+ {errors.proxyPassword && + touched.proxyPassword && + typeof errors.proxyPassword === 'string' && ( +
+ {errors.proxyPassword} +
+ )} +
+
+
+ +
+
+ +
+ {errors.proxyBypassFilter && + touched.proxyBypassFilter && + typeof errors.proxyBypassFilter === 'string' && ( +
+ {errors.proxyBypassFilter} +
+ )} +
+
+
+ +
+ { + setFieldValue( + 'proxyBypassLocalAddresses', + !values.proxyBypassLocalAddresses + ); + }} + /> +
+
+
+ + )} +
+
+ + + +
+
+
+ ); + }} +
+
+ + ); +}; + +export default SettingsMain; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 708aee65..1bd66d84 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -915,16 +915,9 @@ "components.Settings.SettingsMain.applicationurl": "Application URL", "components.Settings.SettingsMain.cacheImages": "Enable Image Caching", "components.Settings.SettingsMain.cacheImagesTip": "Cache externally sourced images (requires a significant amount of disk space)", - "components.Settings.SettingsMain.csrfProtection": "Enable CSRF Protection", - "components.Settings.SettingsMain.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!", - "components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", "components.Settings.SettingsMain.discoverRegion": "Discover Region", "components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability", - "components.Settings.SettingsMain.dnsServers": "Custom DNS Servers", - "components.Settings.SettingsMain.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"", "components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests", - "components.Settings.SettingsMain.forceIpv4First": "IPv4 Resolution First", - "components.Settings.SettingsMain.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6", "components.Settings.SettingsMain.general": "General", "components.Settings.SettingsMain.generalsettings": "General Settings", "components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.", @@ -933,27 +926,39 @@ "components.Settings.SettingsMain.originallanguage": "Discover Language", "components.Settings.SettingsMain.originallanguageTip": "Filter content by original language", "components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests", - "components.Settings.SettingsMain.proxyBypassFilter": "Proxy Ignored Addresses", - "components.Settings.SettingsMain.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains", - "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses", - "components.Settings.SettingsMain.proxyEnabled": "HTTP(S) Proxy", - "components.Settings.SettingsMain.proxyHostname": "Proxy Hostname", - "components.Settings.SettingsMain.proxyPassword": "Proxy Password", - "components.Settings.SettingsMain.proxyPort": "Proxy Port", - "components.Settings.SettingsMain.proxySsl": "Use SSL For Proxy", - "components.Settings.SettingsMain.proxyUser": "Proxy Username", "components.Settings.SettingsMain.streamingRegion": "Streaming Region", "components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability", "components.Settings.SettingsMain.toastApiKeyFailure": "Something went wrong while generating a new API key.", "components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!", "components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.", "components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!", - "components.Settings.SettingsMain.trustProxy": "Enable Proxy Support", - "components.Settings.SettingsMain.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy", "components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title", "components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL", "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", - "components.Settings.SettingsMain.validationProxyPort": "You must provide a valid port", + "components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection", + "components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!", + "components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", + "components.Settings.SettingsNetwork.dnsServers": "Custom DNS Servers", + "components.Settings.SettingsNetwork.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"", + "components.Settings.SettingsNetwork.forceIpv4First": "IPv4 Resolution First", + "components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6", + "components.Settings.SettingsNetwork.network": "Network", + "components.Settings.SettingsNetwork.networksettings": "Network Settings", + "components.Settings.SettingsNetwork.networksettingsDescription": "Configure network settings for your Jellyseerr instance.", + "components.Settings.SettingsNetwork.proxyBypassFilter": "Proxy Ignored Addresses", + "components.Settings.SettingsNetwork.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains", + "components.Settings.SettingsNetwork.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses", + "components.Settings.SettingsNetwork.proxyEnabled": "HTTP(S) Proxy", + "components.Settings.SettingsNetwork.proxyHostname": "Proxy Hostname", + "components.Settings.SettingsNetwork.proxyPassword": "Proxy Password", + "components.Settings.SettingsNetwork.proxyPort": "Proxy Port", + "components.Settings.SettingsNetwork.proxySsl": "Use SSL For Proxy", + "components.Settings.SettingsNetwork.proxyUser": "Proxy Username", + "components.Settings.SettingsNetwork.toastSettingsFailure": "Something went wrong while saving settings.", + "components.Settings.SettingsNetwork.toastSettingsSuccess": "Settings saved successfully!", + "components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support", + "components.Settings.SettingsNetwork.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy", + "components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port", "components.Settings.SettingsUsers.defaultPermissions": "Default Permissions", "components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users", "components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In", @@ -1069,6 +1074,7 @@ "components.Settings.menuJellyfinSettings": "{mediaServerName}", "components.Settings.menuJobs": "Jobs & Cache", "components.Settings.menuLogs": "Logs", + "components.Settings.menuNetwork": "Network", "components.Settings.menuNotifications": "Notifications", "components.Settings.menuPlexSettings": "Plex", "components.Settings.menuServices": "Services", diff --git a/src/pages/settings/network.tsx b/src/pages/settings/network.tsx new file mode 100644 index 00000000..f5aeeaa9 --- /dev/null +++ b/src/pages/settings/network.tsx @@ -0,0 +1,16 @@ +import SettingsLayout from '@app/components/Settings/SettingsLayout'; +import SettingsNetwork from '@app/components/Settings/SettingsNetwork'; +import useRouteGuard from '@app/hooks/useRouteGuard'; +import { Permission } from '@app/hooks/useUser'; +import type { NextPage } from 'next'; + +const SettingsNetworkPage: NextPage = () => { + useRouteGuard(Permission.ADMIN); + return ( + + + + ); +}; + +export default SettingsNetworkPage; From 117617188ed988bd8a90e9fbe8bada08d5b14513 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 22 Feb 2025 16:46:47 +0100 Subject: [PATCH 42/65] feat(settings): add a disclaimer for dns servers and ipv4 first settings (#1375) * feat(settings): add a disclaimer for dns servers and ipv4 first settings This PR adds a disclaimer to warn the user that he should use the network settings of his containers/system first instead of this one. * fix: add missing translations * feat: create a new Advanced Network Settings section * Update src/components/Settings/SettingsNetwork/index.tsx Co-authored-by: fallenbagel <98979876+fallenbagel@users.noreply.github.com> * Update src/i18n/locale/en.json Co-authored-by: fallenbagel <98979876+fallenbagel@users.noreply.github.com> * fix: rename to Force IPv4 Resolution First --------- Co-authored-by: fallenbagel <98979876+fallenbagel@users.noreply.github.com> --- .../Settings/SettingsNetwork/index.tsx | 137 ++++++++++-------- src/i18n/locale/en.json | 5 +- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx index 82701cda..befed1fb 100644 --- a/src/components/Settings/SettingsNetwork/index.tsx +++ b/src/components/Settings/SettingsNetwork/index.tsx @@ -27,12 +27,6 @@ const messages = defineMessages('components.Settings.SettingsNetwork', { trustProxy: 'Enable Proxy Support', trustProxyTip: 'Allow Jellyseerr to correctly register client IP addresses behind a proxy', - forceIpv4First: 'IPv4 Resolution First', - forceIpv4FirstTip: - 'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6', - dnsServers: 'Custom DNS Servers', - dnsServersTip: - 'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"', proxyEnabled: 'HTTP(S) Proxy', proxyHostname: 'Proxy Hostname', proxyPort: 'Proxy Port', @@ -44,9 +38,19 @@ const messages = defineMessages('components.Settings.SettingsNetwork', { "Use ',' as a separator, and '*.' as a wildcard for subdomains", proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses', validationProxyPort: 'You must provide a valid port', + advancedNetworkSettings: 'Advanced Network Settings', + networkDisclaimer: + 'Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.', + docs: 'documentation', + forceIpv4First: 'Force 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]"', }); -const SettingsMain = () => { +const SettingsNetwork = () => { const { addToast } = useToasts(); const intl = useIntl(); const { @@ -206,55 +210,6 @@ const SettingsMain = () => {
-
- -
- { - setFieldValue('forceIpv4First', !values.forceIpv4First); - }} - /> -
-
-
- -
-
- -
- {errors.dnsServers && - touched.dnsServers && - typeof errors.dnsServers === 'string' && ( -
{errors.dnsServers}
- )} -
-
)} +

+ {intl.formatMessage(messages.advancedNetworkSettings)} +

+

+ {intl.formatMessage(messages.networkDisclaimer, { + docs: ( + + {intl.formatMessage(messages.docs)} + + ), + })} +

+
+ +
+ { + setFieldValue('forceIpv4First', !values.forceIpv4First); + }} + /> +
+
+
+ +
+
+ +
+ {errors.dnsServers && + touched.dnsServers && + typeof errors.dnsServers === 'string' && ( +
{errors.dnsServers}
+ )} +
+
@@ -458,4 +481,4 @@ const SettingsMain = () => { ); }; -export default SettingsMain; +export default SettingsNetwork; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 1bd66d84..e38c70c6 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -935,14 +935,17 @@ "components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title", "components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL", "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.SettingsNetwork.advancedNetworkSettings": "Advanced Network Settings", "components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection", "components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!", "components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", "components.Settings.SettingsNetwork.dnsServers": "Custom DNS Servers", "components.Settings.SettingsNetwork.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"", - "components.Settings.SettingsNetwork.forceIpv4First": "IPv4 Resolution First", + "components.Settings.SettingsNetwork.docs": "documentation", + "components.Settings.SettingsNetwork.forceIpv4First": "Force IPv4 Resolution First", "components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6", "components.Settings.SettingsNetwork.network": "Network", + "components.Settings.SettingsNetwork.networkDisclaimer": "Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.", "components.Settings.SettingsNetwork.networksettings": "Network Settings", "components.Settings.SettingsNetwork.networksettingsDescription": "Configure network settings for your Jellyseerr instance.", "components.Settings.SettingsNetwork.proxyBypassFilter": "Proxy Ignored Addresses", From d563b361869d8183041cb6aea91279e17a513070 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 22 Feb 2025 16:46:58 +0100 Subject: [PATCH 43/65] fix: disable first page revalidation in useSWRInfinite (#1386) By default, useSWRInfinite revalidates the first page every time a new page is loaded, resulting in additional requests being sent. This PR disables this behavior. fix #1380 --- src/components/MediaSlider/index.tsx | 1 + src/hooks/useDiscover.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/MediaSlider/index.tsx b/src/components/MediaSlider/index.tsx index 006f0df9..b330798f 100644 --- a/src/components/MediaSlider/index.tsx +++ b/src/components/MediaSlider/index.tsx @@ -56,6 +56,7 @@ const MediaSlider = ({ }, { initialSize: 2, + revalidateFirstPage: false, } ); diff --git a/src/hooks/useDiscover.ts b/src/hooks/useDiscover.ts index 8e53494f..1f32e170 100644 --- a/src/hooks/useDiscover.ts +++ b/src/hooks/useDiscover.ts @@ -80,6 +80,7 @@ const useDiscover = < }, { initialSize: 3, + revalidateFirstPage: false, } ); From 80927b97058a219fca9fa580243cb3f966fb0b37 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Sun, 23 Feb 2025 00:16:03 +0800 Subject: [PATCH 44/65] fix(mediarequest): optimise more typeorm lifecycle triggers (#1376) This is related to #1218. This should fix more typeorm lifcycle trigger issues fix #513 --- server/entity/MediaRequest.ts | 45 ++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index b758da97..8ef614b1 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -734,8 +734,11 @@ export class MediaRequest { media.mediaType === MediaType.MOVIE && this.status === MediaRequestStatus.DECLINED ) { - media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; - mediaRepository.save(media); + const statusField = this.is4k ? 'status4k' : 'status'; + await mediaRepository.update( + { id: this.media.id }, + { [statusField]: MediaStatus.UNKNOWN } + ); } /** @@ -752,8 +755,11 @@ export class MediaRequest { ).length === 0 && media[this.is4k ? 'status4k' : 'status'] === MediaStatus.PENDING ) { - media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; - mediaRepository.save(media); + const statusField = this.is4k ? 'status4k' : 'status'; + mediaRepository.update( + { id: this.media.id }, + { [statusField]: MediaStatus.UNKNOWN } + ); } // Approve child seasons if parent is approved @@ -955,8 +961,10 @@ export class MediaRequest { }); const requestRepository = getRepository(MediaRequest); - this.status = MediaRequestStatus.APPROVED; - await requestRepository.save(this); + + await requestRepository.update(this.id, { + status: MediaRequestStatus.APPROVED, + }); return; } @@ -986,18 +994,22 @@ export class MediaRequest { throw new Error('Media data not found'); } - media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] = - radarrMovie.id; - media[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] = - radarrMovie.titleSlug; - media[this.is4k ? 'serviceId4k' : 'serviceId'] = radarrSettings?.id; - await mediaRepository.save(media); + const updateFields = { + [this.is4k ? 'externalServiceId4k' : 'externalServiceId']: + radarrMovie.id, + [this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']: + radarrMovie.titleSlug, + [this.is4k ? 'serviceId4k' : 'serviceId']: radarrMovie?.id, + }; + + await mediaRepository.update({ id: this.media.id }, updateFields); }) .catch(async () => { const requestRepository = getRepository(MediaRequest); - this.status = MediaRequestStatus.FAILED; - await requestRepository.save(this); + await requestRepository.update(this.id, { + status: MediaRequestStatus.FAILED, + }); logger.warn( 'Something went wrong sending movie request to Radarr, marking status as FAILED', @@ -1113,8 +1125,9 @@ export class MediaRequest { }); const requestRepository = getRepository(MediaRequest); - this.status = MediaRequestStatus.APPROVED; - await requestRepository.save(this); + await requestRepository.update(this.id, { + status: MediaRequestStatus.APPROVED, + }); return; } From 64f05bcad6956f7e8cbe3fdf5f430af1f30ddd6d Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Sat, 22 Feb 2025 11:16:25 -0500 Subject: [PATCH 45/65] feat: add linked accounts page (#883) * feat(linked-accounts): create page and display linked media server accounts * feat(dropdown): add new shared Dropdown component Adds a shared component for plain dropdown menus, based on the headlessui Menu component. Updates the `ButtonWithDropdown` component to use the same inner components, ensuring that the only difference between the two components is the trigger button, and both use the same components for the actual dropdown menu. * refactor(modal): add support for configuring button props * feat(linked-accounts): add support for linking/unlinking jellyfin accounts * feat(linked-accounts): support linking/unlinking plex accounts * fix(linked-accounts): probibit unlinking accounts in certain cases Prevents the primary administrator from unlinking their media server account (which would break sync). Additionally, prevents users without a configured local email and password from unlinking their accounts, which would render them unable to log in. * feat(linked-accounts): support linking/unlinking emby accounts * style(dropdown): improve style class application * fix(server): improve error handling and API spec * style(usersettings): improve syntax & performance of user password checks * style(linkedaccounts): use applicationName in page description * fix(linkedaccounts): resolve typo * refactor(app): remove RequestError class --- overseerr-api.yml | 98 +++++++ server/api/jellyfin.ts | 6 +- server/api/plexapi.ts | 4 +- server/constants/error.ts | 1 + server/entity/User.ts | 28 +- server/routes/user/usersettings.ts | 274 ++++++++++++++++- src/assets/services/jellyfin-icon.svg | 24 ++ .../Common/ButtonWithDropdown/index.tsx | 132 ++------- src/components/Common/Dropdown/index.tsx | 117 ++++++++ src/components/Common/Modal/index.tsx | 16 +- .../LinkJellyfinModal.tsx | 188 ++++++++++++ .../UserLinkedAccountsSettings/index.tsx | 276 ++++++++++++++++++ .../UserProfile/UserSettings/index.tsx | 6 + src/hooks/useUser.ts | 4 +- src/i18n/locale/en.json | 20 ++ .../profile/settings/linked-accounts.tsx | 13 + .../[userId]/settings/linked-accounts.tsx | 16 + 17 files changed, 1095 insertions(+), 128 deletions(-) create mode 100644 src/assets/services/jellyfin-icon.svg create mode 100644 src/components/Common/Dropdown/index.tsx create mode 100644 src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx create mode 100644 src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx create mode 100644 src/pages/profile/settings/linked-accounts.tsx create mode 100644 src/pages/users/[userId]/settings/linked-accounts.tsx diff --git a/overseerr-api.yml b/overseerr-api.yml index 641ce5d7..a713a5a1 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -4423,6 +4423,104 @@ paths: responses: '204': description: User password updated + /user/{userId}/settings/linked-accounts/plex: + post: + summary: Link the provided Plex account to the current user + description: Logs in to Plex with the provided auth token, then links the associated Plex account with the user's account. Users can only link external accounts to their own account. + tags: + - users + parameters: + - in: path + name: userId + required: true + schema: + type: number + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + authToken: + type: string + required: + - authToken + responses: + '204': + description: Linking account succeeded + '403': + description: Invalid credentials + '422': + description: Account already linked to a user + delete: + summary: Remove the linked Plex account for a user + description: Removes the linked Plex account for a specific user. Requires `MANAGE_USERS` permission if editing other users. + tags: + - users + parameters: + - in: path + name: userId + required: true + schema: + type: number + responses: + '204': + description: Unlinking account succeeded + '400': + description: Unlink request invalid + '404': + description: User does not exist + /user/{userId}/settings/linked-accounts/jellyfin: + post: + summary: Link the provided Jellyfin account to the current user + description: Logs in to Jellyfin with the provided credentials, then links the associated Jellyfin account with the user's account. Users can only link external accounts to their own account. + tags: + - users + parameters: + - in: path + name: userId + required: true + schema: + type: number + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + example: 'Mr User' + password: + type: string + example: 'supersecret' + responses: + '204': + description: Linking account succeeded + '403': + description: Invalid credentials + '422': + description: Account already linked to a user + delete: + summary: Remove the linked Jellyfin account for a user + description: Removes the linked Jellyfin account for a specific user. Requires `MANAGE_USERS` permission if editing other users. + tags: + - users + parameters: + - in: path + name: userId + required: true + schema: + type: number + responses: + '204': + description: Unlinking account succeeded + '400': + description: Unlink request invalid + '404': + description: User does not exist /user/{userId}/settings/notifications: get: summary: Get notification settings for a user diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index d0c4d7c7..27a7cf40 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -95,7 +95,11 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem { class JellyfinAPI extends ExternalAPI { private userId?: string; - constructor(jellyfinHost: string, authToken?: string, deviceId?: string) { + constructor( + jellyfinHost: string, + authToken?: string | null, + deviceId?: string | null + ) { let authHeaderVal: string; if (authToken) { authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}", Token="${authToken}"`; diff --git a/server/api/plexapi.ts b/server/api/plexapi.ts index 10d5d1d2..977d367b 100644 --- a/server/api/plexapi.ts +++ b/server/api/plexapi.ts @@ -92,7 +92,7 @@ class PlexAPI { plexSettings, timeout, }: { - plexToken?: string; + plexToken?: string | null; plexSettings?: PlexSettings; timeout?: number; }) { @@ -107,7 +107,7 @@ class PlexAPI { port: settingsPlex.port, https: settingsPlex.useSsl, timeout: timeout, - token: plexToken, + token: plexToken ?? undefined, authenticator: { authenticate: ( _plexApi, diff --git a/server/constants/error.ts b/server/constants/error.ts index 664f02c9..daa02f1a 100644 --- a/server/constants/error.ts +++ b/server/constants/error.ts @@ -7,5 +7,6 @@ export enum ApiErrorCode { NoAdminUser = 'NO_ADMIN_USER', SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS', SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES', + Unauthorized = 'UNAUTHORIZED', Unknown = 'UNKNOWN', } diff --git a/server/entity/User.ts b/server/entity/User.ts index c8753bfe..91b66740 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -56,11 +56,11 @@ export class User { }) public email: string; - @Column({ nullable: true }) - public plexUsername?: string; + @Column({ type: 'varchar', nullable: true }) + public plexUsername?: string | null; - @Column({ nullable: true }) - public jellyfinUsername?: string; + @Column({ type: 'varchar', nullable: true }) + public jellyfinUsername?: string | null; @Column({ nullable: true }) public username?: string; @@ -77,20 +77,20 @@ export class User { @Column({ type: 'integer', default: UserType.PLEX }) public userType: UserType; - @Column({ nullable: true, select: true }) - public plexId?: number; + @Column({ type: 'integer', nullable: true, select: true }) + public plexId?: number | null; - @Column({ nullable: true }) - public jellyfinUserId?: string; + @Column({ type: 'varchar', nullable: true }) + public jellyfinUserId?: string | null; - @Column({ nullable: true, select: false }) - public jellyfinDeviceId?: string; + @Column({ type: 'varchar', nullable: true, select: false }) + public jellyfinDeviceId?: string | null; - @Column({ nullable: true, select: false }) - public jellyfinAuthToken?: string; + @Column({ type: 'varchar', nullable: true, select: false }) + public jellyfinAuthToken?: string | null; - @Column({ nullable: true, select: false }) - public plexToken?: string; + @Column({ type: 'varchar', nullable: true, select: false }) + public plexToken?: string | null; @Column({ type: 'integer', default: 0 }) public permissions = 0; diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 24ca976b..6ee0f893 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -1,4 +1,7 @@ +import JellyfinAPI from '@server/api/jellyfin'; +import PlexTvAPI from '@server/api/plextv'; import { ApiErrorCode } from '@server/constants/error'; +import { MediaServerType } from '@server/constants/server'; import { UserType } from '@server/constants/user'; import { getRepository } from '@server/datasource'; import { User } from '@server/entity/User'; @@ -12,9 +15,23 @@ import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import { isAuthenticated } from '@server/middleware/auth'; import { ApiError } from '@server/types/error'; +import { getHostname } from '@server/utils/getHostname'; import { Router } from 'express'; +import net from 'net'; import { canMakePermissionsChange } from '.'; +const isOwnProfile = (): Middleware => { + return (req, res, next) => { + if (req.user?.id !== Number(req.params.id)) { + return next({ + status: 403, + message: "You do not have permission to view this user's settings.", + }); + } + next(); + }; +}; + const isOwnProfileOrAdmin = (): Middleware => { const authMiddleware: Middleware = (req, res, next) => { if ( @@ -183,9 +200,8 @@ userSettingsRoutes.post< status: e.statusCode, message: e.errorCode, }); - } else { - return next({ status: 500, message: e.message }); } + return next({ status: 500, message: e.message }); } }); @@ -290,6 +306,260 @@ userSettingsRoutes.post< } }); +userSettingsRoutes.post<{ authToken: string }>( + '/linked-accounts/plex', + isOwnProfile(), + async (req, res) => { + const settings = getSettings(); + const userRepository = getRepository(User); + + if (!req.user) { + return res.status(404).json({ code: ApiErrorCode.Unauthorized }); + } + // Make sure Plex login is enabled + if (settings.main.mediaServerType !== MediaServerType.PLEX) { + return res.status(500).json({ message: 'Plex login is disabled' }); + } + + // First we need to use this auth token to get the user's email from plex.tv + const plextv = new PlexTvAPI(req.body.authToken); + const account = await plextv.getUser(); + + // Do not allow linking of an already linked account + if (await userRepository.exist({ where: { plexId: account.id } })) { + return res.status(422).json({ + message: 'This Plex account is already linked to a Jellyseerr user', + }); + } + + const user = req.user; + + // Emails do not match + if (user.email !== account.email) { + return res.status(422).json({ + message: + 'This Plex account is registered under a different email address.', + }); + } + + // valid plex user found, link to current user + user.userType = UserType.PLEX; + user.plexId = account.id; + user.plexUsername = account.username; + user.plexToken = account.authToken; + await userRepository.save(user); + + return res.status(204).send(); + } +); + +userSettingsRoutes.delete<{ id: string }>( + '/linked-accounts/plex', + isOwnProfileOrAdmin(), + async (req, res) => { + const settings = getSettings(); + const userRepository = getRepository(User); + + // Make sure Plex login is enabled + if (settings.main.mediaServerType !== MediaServerType.PLEX) { + return res.status(500).json({ message: 'Plex login is disabled' }); + } + + try { + const user = await userRepository + .createQueryBuilder('user') + .addSelect('user.password') + .where({ + id: Number(req.params.id), + }) + .getOne(); + + if (!user) { + return res.status(404).json({ message: 'User not found.' }); + } + + if (user.id === 1) { + return res.status(400).json({ + message: + 'Cannot unlink media server accounts for the primary administrator.', + }); + } + + if (!user.email || !user.password) { + return res.status(400).json({ + message: 'User does not have a local email or password set.', + }); + } + + user.userType = UserType.LOCAL; + user.plexId = null; + user.plexUsername = null; + user.plexToken = null; + await userRepository.save(user); + + return res.status(204).send(); + } catch (e) { + return res.status(500).json({ message: e.message }); + } + } +); + +userSettingsRoutes.post<{ username: string; password: string }>( + '/linked-accounts/jellyfin', + isOwnProfile(), + async (req, res) => { + const settings = getSettings(); + const userRepository = getRepository(User); + + if (!req.user) { + return res.status(401).json({ code: ApiErrorCode.Unauthorized }); + } + // Make sure jellyfin login is enabled + if ( + settings.main.mediaServerType !== MediaServerType.JELLYFIN && + settings.main.mediaServerType !== MediaServerType.EMBY + ) { + return res + .status(500) + .json({ message: 'Jellyfin/Emby login is disabled' }); + } + + // Do not allow linking of an already linked account + if ( + await userRepository.exist({ + where: { jellyfinUsername: req.body.username }, + }) + ) { + return res.status(422).json({ + message: 'The specified account is already linked to a Jellyseerr user', + }); + } + + const hostname = getHostname(); + const deviceId = Buffer.from( + `BOT_overseerr_${req.user.username ?? ''}` + ).toString('base64'); + + const jellyfinserver = new JellyfinAPI(hostname, undefined, deviceId); + + const ip = req.ip; + let clientIp: string | undefined; + if (ip) { + if (net.isIPv4(ip)) { + clientIp = ip; + } else if (net.isIPv6(ip)) { + clientIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip; + } + } + + try { + const account = await jellyfinserver.login( + req.body.username, + req.body.password, + clientIp + ); + + // Do not allow linking of an already linked account + if ( + await userRepository.exist({ + where: { jellyfinUserId: account.User.Id }, + }) + ) { + return res.status(422).json({ + message: + 'The specified account is already linked to a Jellyseerr user', + }); + } + + const user = req.user; + + // valid jellyfin user found, link to current user + user.userType = + settings.main.mediaServerType === MediaServerType.EMBY + ? UserType.EMBY + : UserType.JELLYFIN; + user.jellyfinUserId = account.User.Id; + user.jellyfinUsername = account.User.Name; + user.jellyfinAuthToken = account.AccessToken; + user.jellyfinDeviceId = deviceId; + await userRepository.save(user); + + return res.status(204).send(); + } catch (e) { + logger.error('Failed to link account to user.', { + label: 'API', + ip: req.ip, + error: e, + }); + if ( + e instanceof ApiError && + e.errorCode === ApiErrorCode.InvalidCredentials + ) { + return res.status(401).json({ code: e.errorCode }); + } + + return res.status(500).send(); + } + } +); + +userSettingsRoutes.delete<{ id: string }>( + '/linked-accounts/jellyfin', + isOwnProfileOrAdmin(), + async (req, res) => { + const settings = getSettings(); + const userRepository = getRepository(User); + + // Make sure jellyfin login is enabled + if ( + settings.main.mediaServerType !== MediaServerType.JELLYFIN && + settings.main.mediaServerType !== MediaServerType.EMBY + ) { + return res + .status(500) + .json({ message: 'Jellyfin/Emby login is disabled' }); + } + + try { + const user = await userRepository + .createQueryBuilder('user') + .addSelect('user.password') + .where({ + id: Number(req.params.id), + }) + .getOne(); + + if (!user) { + return res.status(404).json({ message: 'User not found.' }); + } + + if (user.id === 1) { + return res.status(400).json({ + message: + 'Cannot unlink media server accounts for the primary administrator.', + }); + } + + if (!user.email || !user.password) { + return res.status(400).json({ + message: 'User does not have a local email or password set.', + }); + } + + user.userType = UserType.LOCAL; + user.jellyfinUserId = null; + user.jellyfinUsername = null; + user.jellyfinAuthToken = null; + user.jellyfinDeviceId = null; + await userRepository.save(user); + + return res.status(204).send(); + } catch (e) { + return res.status(500).json({ message: e.message }); + } + } +); + userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>( '/notifications', isOwnProfileOrAdmin(), diff --git a/src/assets/services/jellyfin-icon.svg b/src/assets/services/jellyfin-icon.svg new file mode 100644 index 00000000..d4d7f017 --- /dev/null +++ b/src/assets/services/jellyfin-icon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + icon-transparent + + + + + diff --git a/src/components/Common/ButtonWithDropdown/index.tsx b/src/components/Common/ButtonWithDropdown/index.tsx index bf98cdae..36c79364 100644 --- a/src/components/Common/ButtonWithDropdown/index.tsx +++ b/src/components/Common/ButtonWithDropdown/index.tsx @@ -1,77 +1,29 @@ -import useClickOutside from '@app/hooks/useClickOutside'; +import Dropdown from '@app/components/Common/Dropdown'; import { withProperties } from '@app/utils/typeHelpers'; -import { Transition } from '@headlessui/react'; +import { Menu } from '@headlessui/react'; import { ChevronDownIcon } from '@heroicons/react/24/solid'; -import type { - AnchorHTMLAttributes, - ButtonHTMLAttributes, - RefObject, -} from 'react'; -import { Fragment, useRef, useState } from 'react'; +import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; -interface DropdownItemProps extends AnchorHTMLAttributes { - buttonType?: 'primary' | 'ghost'; -} - -const DropdownItem = ({ - children, - buttonType = 'primary', - ...props -}: DropdownItemProps) => { - let styleClass = 'button-md text-white'; - - switch (buttonType) { - case 'ghost': - styleClass += - ' bg-transparent rounded hover:bg-gradient-to-br from-indigo-600 to-purple-600 text-white focus:border-gray-500 focus:text-white'; - break; - default: - styleClass += - ' bg-indigo-600 rounded hover:bg-indigo-500 focus:border-indigo-700 focus:text-white'; - } - return ( - - {children} - - ); -}; - -interface ButtonWithDropdownProps { +type ButtonWithDropdownProps = { text: React.ReactNode; dropdownIcon?: React.ReactNode; buttonType?: 'primary' | 'ghost'; -} -interface ButtonProps - extends ButtonHTMLAttributes, - ButtonWithDropdownProps { - as?: 'button'; -} -interface AnchorProps - extends AnchorHTMLAttributes, - ButtonWithDropdownProps { - as: 'a'; -} +} & ( + | ({ as?: 'button' } & ButtonHTMLAttributes) + | ({ as: 'a' } & AnchorHTMLAttributes) +); const ButtonWithDropdown = ({ - as, text, children, dropdownIcon, className, buttonType = 'primary', ...props -}: ButtonProps | AnchorProps) => { - const [isOpen, setIsOpen] = useState(false); - const buttonRef = useRef(null); - useClickOutside(buttonRef, () => setIsOpen(false)); - +}: ButtonWithDropdownProps) => { const styleClasses = { mainButtonClasses: 'button-md text-white border', dropdownSideButtonClasses: 'button-md border', - dropdownClasses: 'button-md', }; switch (buttonType) { @@ -79,72 +31,40 @@ const ButtonWithDropdown = ({ styleClasses.mainButtonClasses += ' bg-transparent border-gray-600 hover:border-gray-200 focus:border-gray-100 active:border-gray-100'; styleClasses.dropdownSideButtonClasses = styleClasses.mainButtonClasses; - styleClasses.dropdownClasses += - ' bg-gray-800 border border-gray-700 bg-opacity-80 p-1 backdrop-blur'; break; default: styleClasses.mainButtonClasses += ' bg-indigo-600 border-indigo-500 bg-opacity-80 hover:bg-opacity-100 hover:border-indigo-500 active:bg-indigo-700 active:border-indigo-700 focus:ring-blue'; styleClasses.dropdownSideButtonClasses += ' bg-indigo-600 bg-opacity-80 border-indigo-500 hover:bg-opacity-100 active:bg-opacity-100 focus:ring-blue'; - styleClasses.dropdownClasses += ' bg-indigo-600 p-1'; } + const TriggerElement = props.as ?? 'button'; + return ( - - {as === 'a' ? ( - } - {...(props as AnchorHTMLAttributes)} - > - {text} - - ) : ( - - )} + + )} + > + {text} + {children && ( - - -
-
-
{children}
-
-
-
+ + {children}
)} - +
); }; -export default withProperties(ButtonWithDropdown, { Item: DropdownItem }); +export default withProperties(ButtonWithDropdown, { Item: Dropdown.Item }); diff --git a/src/components/Common/Dropdown/index.tsx b/src/components/Common/Dropdown/index.tsx new file mode 100644 index 00000000..74ce79f2 --- /dev/null +++ b/src/components/Common/Dropdown/index.tsx @@ -0,0 +1,117 @@ +import { withProperties } from '@app/utils/typeHelpers'; +import { Menu, Transition } from '@headlessui/react'; +import { ChevronDownIcon } from '@heroicons/react/24/solid'; +import { + Fragment, + useRef, + type AnchorHTMLAttributes, + type ButtonHTMLAttributes, + type HTMLAttributes, +} from 'react'; + +interface DropdownItemProps extends AnchorHTMLAttributes { + buttonType?: 'primary' | 'ghost'; +} + +const DropdownItem = ({ + children, + buttonType = 'primary', + ...props +}: DropdownItemProps) => { + return ( + + + {children} + + + ); +}; + +type DropdownItemsProps = HTMLAttributes & { + dropdownType: 'primary' | 'ghost'; +}; + +const DropdownItems = ({ + children, + className, + dropdownType, + ...props +}: DropdownItemsProps) => { + return ( + + +
{children}
+
+
+ ); +}; + +interface DropdownProps extends ButtonHTMLAttributes { + text: React.ReactNode; + dropdownIcon?: React.ReactNode; + buttonType?: 'primary' | 'ghost'; +} + +const Dropdown = ({ + text, + children, + dropdownIcon, + className, + buttonType = 'primary', + ...props +}: DropdownProps) => { + const buttonRef = useRef(null); + + return ( + + + {text} + {children && (dropdownIcon ? dropdownIcon : )} + + {children && ( + {children} + )} + + ); +}; +export default withProperties(Dropdown, { + Item: DropdownItem, + Items: DropdownItems, +}); diff --git a/src/components/Common/Modal/index.tsx b/src/components/Common/Modal/index.tsx index 8cebf06f..ca7be654 100644 --- a/src/components/Common/Modal/index.tsx +++ b/src/components/Common/Modal/index.tsx @@ -29,11 +29,16 @@ interface ModalProps { secondaryDisabled?: boolean; tertiaryDisabled?: boolean; tertiaryButtonType?: ButtonType; + okButtonProps?: React.ButtonHTMLAttributes; + cancelButtonProps?: React.ButtonHTMLAttributes; + secondaryButtonProps?: React.ButtonHTMLAttributes; + tertiaryButtonProps?: React.ButtonHTMLAttributes; disableScrollLock?: boolean; backgroundClickable?: boolean; loading?: boolean; backdrop?: string; children?: React.ReactNode; + dialogClass?: string; } const Modal = React.forwardRef( @@ -61,6 +66,11 @@ const Modal = React.forwardRef( loading = false, onTertiary, backdrop, + dialogClass, + okButtonProps, + cancelButtonProps, + secondaryButtonProps, + tertiaryButtonProps, }, parentRef ) => { @@ -106,7 +116,7 @@ const Modal = React.forwardRef(
( className="ml-3" disabled={okDisabled} data-testid="modal-ok-button" + {...okButtonProps} > {okText ? okText : 'Ok'} @@ -200,6 +211,7 @@ const Modal = React.forwardRef( className="ml-3" disabled={secondaryDisabled} data-testid="modal-secondary-button" + {...secondaryButtonProps} > {secondaryText} @@ -210,6 +222,7 @@ const Modal = React.forwardRef( onClick={onTertiary} className="ml-3" disabled={tertiaryDisabled} + {...tertiaryButtonProps} > {tertiaryText} @@ -220,6 +233,7 @@ const Modal = React.forwardRef( onClick={onCancel} className="ml-3 sm:ml-0" data-testid="modal-cancel-button" + {...cancelButtonProps} > {cancelText ? cancelText diff --git a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx new file mode 100644 index 00000000..4872e7c1 --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx @@ -0,0 +1,188 @@ +import Alert from '@app/components/Common/Alert'; +import Modal from '@app/components/Common/Modal'; +import useSettings from '@app/hooks/useSettings'; +import { useUser } from '@app/hooks/useUser'; +import defineMessages from '@app/utils/defineMessages'; +import { Transition } from '@headlessui/react'; +import { MediaServerType } from '@server/constants/server'; +import { Field, Form, Formik } from 'formik'; +import { useState } from 'react'; +import { useIntl } from 'react-intl'; +import * as Yup from 'yup'; + +const messages = defineMessages( + 'components.UserProfile.UserSettings.LinkJellyfinModal', + { + title: 'Link {mediaServerName} Account', + description: + 'Enter your {mediaServerName} credentials to link your account with {applicationName}.', + username: 'Username', + password: 'Password', + usernameRequired: 'You must provide a username', + passwordRequired: 'You must provide a password', + saving: 'Adding…', + save: 'Link', + errorUnauthorized: + 'Unable to connect to {mediaServerName} using your credentials', + errorExists: 'This account is already linked to a {applicationName} user', + errorUnknown: 'An unknown error occurred', + } +); + +interface LinkJellyfinModalProps { + show: boolean; + onClose: () => void; + onSave: () => void; +} + +const LinkJellyfinModal: React.FC = ({ + show, + onClose, + onSave, +}) => { + const intl = useIntl(); + const settings = useSettings(); + const { user } = useUser(); + const [error, setError] = useState(null); + + const JellyfinLoginSchema = Yup.object().shape({ + username: Yup.string().required( + intl.formatMessage(messages.usernameRequired) + ), + password: Yup.string().required( + intl.formatMessage(messages.passwordRequired) + ), + }); + + const applicationName = settings.currentSettings.applicationTitle; + const mediaServerName = + settings.currentSettings.mediaServerType === MediaServerType.EMBY + ? 'Emby' + : 'Jellyfin'; + + return ( + + { + try { + setError(null); + const res = await fetch( + `/api/v1/user/${user?.id}/settings/linked-accounts/jellyfin`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + }), + } + ); + if (!res.ok) { + if (res.status === 401) { + setError( + intl.formatMessage(messages.errorUnauthorized, { + mediaServerName, + }) + ); + } else if (res.status === 422) { + setError( + intl.formatMessage(messages.errorExists, { applicationName }) + ); + } else { + setError(intl.formatMessage(messages.errorUnknown)); + } + } else { + onSave(); + } + } catch (e) { + setError(intl.formatMessage(messages.errorUnknown)); + } + }} + > + {({ errors, touched, handleSubmit, isSubmitting, isValid }) => { + return ( + { + setError(null); + onClose(); + }} + okButtonType="primary" + okButtonProps={{ type: 'submit', form: 'link-jellyfin-account' }} + okText={ + isSubmitting + ? intl.formatMessage(messages.saving) + : intl.formatMessage(messages.save) + } + okDisabled={isSubmitting || !isValid} + onOk={() => handleSubmit()} + title={intl.formatMessage(messages.title, { mediaServerName })} + dialogClass="sm:max-w-lg" + > + + + ); + }} + + + ); +}; + +export default LinkJellyfinModal; diff --git a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx new file mode 100644 index 00000000..c83a1579 --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx @@ -0,0 +1,276 @@ +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 Alert from '@app/components/Common/Alert'; +import ConfirmButton from '@app/components/Common/ConfirmButton'; +import Dropdown from '@app/components/Common/Dropdown'; +import PageTitle from '@app/components/Common/PageTitle'; +import useSettings from '@app/hooks/useSettings'; +import { Permission, UserType, useUser } from '@app/hooks/useUser'; +import globalMessages from '@app/i18n/globalMessages'; +import defineMessages from '@app/utils/defineMessages'; +import PlexOAuth from '@app/utils/plex'; +import { TrashIcon } from '@heroicons/react/24/solid'; +import { MediaServerType } from '@server/constants/server'; +import { useRouter } from 'next/router'; +import { useMemo, useState } from 'react'; +import { useIntl } from 'react-intl'; +import useSWR from 'swr'; +import LinkJellyfinModal from './LinkJellyfinModal'; + +const messages = defineMessages( + 'components.UserProfile.UserSettings.UserLinkedAccountsSettings', + { + linkedAccounts: 'Linked Accounts', + linkedAccountsHint: + 'These external accounts are linked to your {applicationName} account.', + noLinkedAccounts: + 'You do not have any external accounts linked to your account.', + noPermissionDescription: + "You do not have permission to modify this user's linked accounts.", + plexErrorUnauthorized: 'Unable to connect to Plex using your credentials', + plexErrorExists: 'This account is already linked to a Plex user', + errorUnknown: 'An unknown error occurred', + deleteFailed: 'Unable to delete linked account.', + } +); + +const plexOAuth = new PlexOAuth(); + +enum LinkedAccountType { + Plex = 'Plex', + Jellyfin = 'Jellyfin', + Emby = 'Emby', +} + +type LinkedAccount = { + type: LinkedAccountType; + username: string; +}; + +const UserLinkedAccountsSettings = () => { + const intl = useIntl(); + const settings = useSettings(); + const router = useRouter(); + const { user: currentUser } = useUser(); + const { + user, + hasPermission, + revalidate: revalidateUser, + } = useUser({ id: Number(router.query.userId) }); + const { data: passwordInfo } = useSWR<{ hasPassword: boolean }>( + user ? `/api/v1/user/${user?.id}/settings/password` : null + ); + const [showJellyfinModal, setShowJellyfinModal] = useState(false); + const [error, setError] = useState(null); + + const applicationName = settings.currentSettings.applicationTitle; + + const accounts: LinkedAccount[] = useMemo(() => { + const accounts: LinkedAccount[] = []; + if (!user) return accounts; + if (user.userType === UserType.PLEX && user.plexUsername) + accounts.push({ + type: LinkedAccountType.Plex, + username: user.plexUsername, + }); + if (user.userType === UserType.EMBY && user.jellyfinUsername) + accounts.push({ + type: LinkedAccountType.Emby, + username: user.jellyfinUsername, + }); + if (user.userType === UserType.JELLYFIN && user.jellyfinUsername) + accounts.push({ + type: LinkedAccountType.Jellyfin, + username: user.jellyfinUsername, + }); + return accounts; + }, [user]); + + const linkPlexAccount = async () => { + setError(null); + try { + const authToken = await plexOAuth.login(); + const res = await fetch( + `/api/v1/user/${user?.id}/settings/linked-accounts/plex`, + { + method: 'POST', + body: JSON.stringify({ authToken }), + } + ); + if (!res.ok) { + if (res.status === 401) { + setError(intl.formatMessage(messages.plexErrorUnauthorized)); + } else if (res.status === 422) { + setError(intl.formatMessage(messages.plexErrorExists)); + } else { + setError(intl.formatMessage(messages.errorUnknown)); + } + } else { + await revalidateUser(); + } + } catch (e) { + setError(intl.formatMessage(messages.errorUnknown)); + } + }; + + const linkable = [ + { + name: 'Plex', + action: () => { + plexOAuth.preparePopup(); + setTimeout(() => linkPlexAccount(), 1500); + }, + hide: + settings.currentSettings.mediaServerType !== MediaServerType.PLEX || + accounts.some((a) => a.type === LinkedAccountType.Plex), + }, + { + name: 'Jellyfin', + action: () => setShowJellyfinModal(true), + hide: + settings.currentSettings.mediaServerType !== MediaServerType.JELLYFIN || + accounts.some((a) => a.type === LinkedAccountType.Jellyfin), + }, + { + name: 'Emby', + action: () => setShowJellyfinModal(true), + hide: + settings.currentSettings.mediaServerType !== MediaServerType.EMBY || + accounts.some((a) => a.type === LinkedAccountType.Emby), + }, + ].filter((l) => !l.hide); + + const deleteRequest = async (account: string) => { + try { + const res = await fetch( + `/api/v1/user/${user?.id}/settings/linked-accounts/${account}`, + { method: 'DELETE' } + ); + if (!res.ok) throw new Error(); + } catch { + setError(intl.formatMessage(messages.deleteFailed)); + } + + await revalidateUser(); + }; + + if ( + currentUser?.id !== user?.id && + hasPermission(Permission.ADMIN) && + currentUser?.id !== 1 + ) { + return ( + <> +
+

+ {intl.formatMessage(messages.linkedAccounts)} +

+
+ + + ); + } + + const enableMediaServerUnlink = user?.id !== 1 && passwordInfo?.hasPassword; + + return ( + <> + +
+
+

+ {intl.formatMessage(messages.linkedAccounts)} +

+
+ {intl.formatMessage(messages.linkedAccountsHint, { + applicationName, + })} +
+
+ {currentUser?.id === user?.id && !!linkable.length && ( +
+ + {linkable.map(({ name, action }) => ( + + {name} + + ))} + +
+ )} +
+ {error && } + {accounts.length ? ( +
    + {accounts.map((acct, i) => ( +
  • +
    + {acct.type === LinkedAccountType.Plex ? ( +
    + +
    + ) : acct.type === LinkedAccountType.Emby ? ( + + ) : ( + + )} +
    +
    +
    + {acct.type} +
    +
    + {acct.username} +
    +
    +
    + {enableMediaServerUnlink && ( + { + deleteRequest( + acct.type === LinkedAccountType.Plex ? 'plex' : 'jellyfin' + ); + }} + confirmText={intl.formatMessage(globalMessages.areyousure)} + > + + {intl.formatMessage(globalMessages.delete)} + + )} +
  • + ))} +
+ ) : ( +
+

+ {intl.formatMessage(messages.noLinkedAccounts)} +

+
+ )} + + setShowJellyfinModal(false)} + onSave={() => { + setShowJellyfinModal(false); + revalidateUser(); + }} + /> + + ); +}; + +export default UserLinkedAccountsSettings; diff --git a/src/components/UserProfile/UserSettings/index.tsx b/src/components/UserProfile/UserSettings/index.tsx index 72d237b9..2072285c 100644 --- a/src/components/UserProfile/UserSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/index.tsx @@ -18,6 +18,7 @@ import useSWR from 'swr'; const messages = defineMessages('components.UserProfile.UserSettings', { menuGeneralSettings: 'General', menuChangePass: 'Password', + menuLinkedAccounts: 'Linked Accounts', menuNotifications: 'Notifications', menuPermissions: 'Permissions', unauthorizedDescription: @@ -63,6 +64,11 @@ const UserSettings = ({ children }: UserSettingsProps) => { currentUser?.id !== user?.id && hasPermission(Permission.ADMIN, user?.permissions ?? 0)), }, + { + text: intl.formatMessage(messages.menuLinkedAccounts), + route: '/settings/linked-accounts', + regex: /\/settings\/linked-accounts/, + }, { text: intl.formatMessage(messages.menuNotifications), route: data?.emailEnabled diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index f60b402c..2a14ad1d 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -11,8 +11,8 @@ export type { PermissionCheckOptions }; export interface User { id: number; warnings: string[]; - plexUsername?: string; - jellyfinUsername?: string; + plexUsername?: string | null; + jellyfinUsername?: string | null; username?: string; displayName: string; email: string; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index e38c70c6..2b0b3681 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -1275,6 +1275,17 @@ "components.UserProfile.ProfileHeader.profile": "View Profile", "components.UserProfile.ProfileHeader.settings": "Edit Settings", "components.UserProfile.ProfileHeader.userid": "User ID: {userid}", + "components.UserProfile.UserSettings.LinkJellyfinModal.description": "Enter your {mediaServerName} credentials to link your account with {applicationName}.", + "components.UserProfile.UserSettings.LinkJellyfinModal.errorExists": "This account is already linked to a {applicationName} user", + "components.UserProfile.UserSettings.LinkJellyfinModal.errorUnauthorized": "Unable to connect to {mediaServerName} using your credentials", + "components.UserProfile.UserSettings.LinkJellyfinModal.errorUnknown": "An unknown error occurred", + "components.UserProfile.UserSettings.LinkJellyfinModal.password": "Password", + "components.UserProfile.UserSettings.LinkJellyfinModal.passwordRequired": "You must provide a password", + "components.UserProfile.UserSettings.LinkJellyfinModal.save": "Link", + "components.UserProfile.UserSettings.LinkJellyfinModal.saving": "Adding…", + "components.UserProfile.UserSettings.LinkJellyfinModal.title": "Link {mediaServerName} Account", + "components.UserProfile.UserSettings.LinkJellyfinModal.username": "Username", + "components.UserProfile.UserSettings.LinkJellyfinModal.usernameRequired": "You must provide a username", "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Account Type", "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Admin", "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Display Language", @@ -1315,6 +1326,14 @@ "components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID", "components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Valid email required", "components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "Email required", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.deleteFailed": "Unable to delete linked account.", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.errorUnknown": "An unknown error occurred", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.linkedAccounts": "Linked Accounts", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.linkedAccountsHint": "These external accounts are linked to your {applicationName} account.", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.noLinkedAccounts": "You do not have any external accounts linked to your account.", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.noPermissionDescription": "You do not have permission to modify this user's linked accounts.", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.plexErrorExists": "This account is already linked to a Plex user", + "components.UserProfile.UserSettings.UserLinkedAccountsSettings.plexErrorUnauthorized": "Unable to connect to Plex using your credentials", "components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Device Default", "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "User ID", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "The multi-digit ID number associated with your user account", @@ -1377,6 +1396,7 @@ "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "You cannot modify your own permissions.", "components.UserProfile.UserSettings.menuChangePass": "Password", "components.UserProfile.UserSettings.menuGeneralSettings": "General", + "components.UserProfile.UserSettings.menuLinkedAccounts": "Linked Accounts", "components.UserProfile.UserSettings.menuNotifications": "Notifications", "components.UserProfile.UserSettings.menuPermissions": "Permissions", "components.UserProfile.UserSettings.unauthorizedDescription": "You do not have permission to modify this user's settings.", diff --git a/src/pages/profile/settings/linked-accounts.tsx b/src/pages/profile/settings/linked-accounts.tsx new file mode 100644 index 00000000..cd752109 --- /dev/null +++ b/src/pages/profile/settings/linked-accounts.tsx @@ -0,0 +1,13 @@ +import UserSettings from '@app/components/UserProfile/UserSettings'; +import UserLinkedAccountsSettings from '@app/components/UserProfile/UserSettings/UserLinkedAccountsSettings'; +import type { NextPage } from 'next'; + +const UserSettingsLinkedAccountsPage: NextPage = () => { + return ( + + + + ); +}; + +export default UserSettingsLinkedAccountsPage; diff --git a/src/pages/users/[userId]/settings/linked-accounts.tsx b/src/pages/users/[userId]/settings/linked-accounts.tsx new file mode 100644 index 00000000..51b4ff24 --- /dev/null +++ b/src/pages/users/[userId]/settings/linked-accounts.tsx @@ -0,0 +1,16 @@ +import UserSettings from '@app/components/UserProfile/UserSettings'; +import UserLinkedAccountsSettings from '@app/components/UserProfile/UserSettings/UserLinkedAccountsSettings'; +import useRouteGuard from '@app/hooks/useRouteGuard'; +import { Permission } from '@app/hooks/useUser'; +import type { NextPage } from 'next'; + +const UserLinkedAccountsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_USERS); + return ( + + + + ); +}; + +export default UserLinkedAccountsPage; From b1f07f0eb20c6090eaa153b7ad2ef0142a84042a Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 22 Feb 2025 17:17:19 +0100 Subject: [PATCH 46/65] refactor(overriderules): move override rules out of the service modal (#1292) * refactor(overriderules): move override rules out of the service modal This PR moves override rules out of the service modal. This will make override rules more visible than inside the service modal popup. This will also avoid having a modal inside a modal (override rules modal inside of service modal) * fix: resolve typing error --- src/components/LanguageSelector/index.tsx | 3 + src/components/Selector/index.tsx | 12 + .../OverrideRule/OverrideRuleModal.tsx | 184 +++++++++- .../OverrideRule/OverrideRuleTile.tsx | 267 --------------- .../OverrideRule/OverrideRuleTiles.tsx | 318 ++++++++++++++++++ src/components/Settings/RadarrModal/index.tsx | 74 +--- src/components/Settings/SettingsServices.tsx | 82 +++-- src/components/Settings/SonarrModal/index.tsx | 74 +--- src/i18n/locale/en.json | 10 +- 9 files changed, 577 insertions(+), 447 deletions(-) delete mode 100644 src/components/Settings/OverrideRule/OverrideRuleTile.tsx create mode 100644 src/components/Settings/OverrideRule/OverrideRuleTiles.tsx diff --git a/src/components/LanguageSelector/index.tsx b/src/components/LanguageSelector/index.tsx index d7b9853c..083ecbc7 100644 --- a/src/components/LanguageSelector/index.tsx +++ b/src/components/LanguageSelector/index.tsx @@ -33,6 +33,7 @@ interface LanguageSelectorProps { setFieldValue: (property: string, value: string) => void; serverValue?: string; isUserSettings?: boolean; + isDisabled?: boolean; } const LanguageSelector = ({ @@ -40,6 +41,7 @@ const LanguageSelector = ({ setFieldValue, serverValue, isUserSettings = false, + isDisabled, }: LanguageSelectorProps) => { const intl = useIntl(); const { data: languages } = useSWR('/api/v1/languages'); @@ -96,6 +98,7 @@ const LanguageSelector = ({ options={options} isMulti + isDisabled={isDisabled} className="react-select-container" classNamePrefix="react-select" value={ diff --git a/src/components/Selector/index.tsx b/src/components/Selector/index.tsx index 6c831909..2098c935 100644 --- a/src/components/Selector/index.tsx +++ b/src/components/Selector/index.tsx @@ -52,18 +52,21 @@ type SingleVal = { type BaseSelectorMultiProps = { defaultValue?: string; isMulti: true; + isDisabled?: boolean; onChange: (value: MultiValue | null) => void; }; type BaseSelectorSingleProps = { defaultValue?: string; isMulti?: false; + isDisabled?: boolean; onChange: (value: SingleValue | null) => void; }; export const CompanySelector = ({ defaultValue, isMulti, + isDisabled, onChange, }: BaseSelectorSingleProps | BaseSelectorMultiProps) => { const intl = useIntl(); @@ -117,6 +120,7 @@ export const CompanySelector = ({ className="react-select-container" classNamePrefix="react-select" isMulti={isMulti} + isDisabled={isDisabled} defaultValue={defaultDataValue} defaultOptions cacheOptions @@ -143,6 +147,7 @@ type GenreSelectorProps = (BaseSelectorMultiProps | BaseSelectorSingleProps) & { export const GenreSelector = ({ isMulti, defaultValue, + isDisabled, onChange, type, }: GenreSelectorProps) => { @@ -203,6 +208,7 @@ export const GenreSelector = ({ defaultOptions cacheOptions isMulti={isMulti} + isDisabled={isDisabled} loadOptions={loadGenreOptions} placeholder={intl.formatMessage(messages.searchGenres)} onChange={(value) => { @@ -215,6 +221,7 @@ export const GenreSelector = ({ export const StatusSelector = ({ isMulti, + isDisabled, defaultValue, onChange, }: BaseSelectorMultiProps | BaseSelectorSingleProps) => { @@ -272,6 +279,7 @@ export const StatusSelector = ({ defaultValue={isMulti ? defaultDataValue : defaultDataValue?.[0]} defaultOptions isMulti={isMulti} + isDisabled={isDisabled} loadOptions={loadStatusOptions} placeholder={intl.formatMessage(messages.searchStatus)} onChange={(value) => { @@ -284,6 +292,7 @@ export const StatusSelector = ({ export const KeywordSelector = ({ isMulti, + isDisabled, defaultValue, onChange, }: BaseSelectorMultiProps | BaseSelectorSingleProps) => { @@ -341,6 +350,7 @@ export const KeywordSelector = ({ key={`keyword-select-${defaultDataValue}`} inputId="data" isMulti={isMulti} + isDisabled={isDisabled} className="react-select-container" classNamePrefix="react-select" noOptionsMessage={({ inputValue }) => @@ -551,6 +561,7 @@ export const WatchProviderSelector = ({ export const UserSelector = ({ isMulti, + isDisabled, defaultValue, onChange, }: BaseSelectorMultiProps | BaseSelectorSingleProps) => { @@ -613,6 +624,7 @@ export const UserSelector = ({ defaultOptions cacheOptions isMulti={isMulti} + isDisabled={isDisabled} loadOptions={loadUserOptions} placeholder={intl.formatMessage(messages.searchUsers)} onChange={(value) => { diff --git a/src/components/Settings/OverrideRule/OverrideRuleModal.tsx b/src/components/Settings/OverrideRule/OverrideRuleModal.tsx index becb1ee9..a2776bba 100644 --- a/src/components/Settings/OverrideRule/OverrideRuleModal.tsx +++ b/src/components/Settings/OverrideRule/OverrideRuleModal.tsx @@ -11,7 +11,13 @@ import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import type OverrideRule from '@server/entity/OverrideRule'; +import type { + DVRSettings, + RadarrSettings, + SonarrSettings, +} from '@server/lib/settings'; import { Field, Formik } from 'formik'; +import { useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import Select from 'react-select'; import { useToasts } from 'react-toast-notifications'; @@ -20,6 +26,9 @@ const messages = defineMessages('components.Settings.OverrideRuleModal', { createrule: 'New Override Rule', editrule: 'Edit Override Rule', create: 'Create rule', + service: 'Service', + serviceDescription: 'Apply this rule to the selected service.', + selectService: 'Select service', conditions: 'Conditions', conditionsDescription: 'Specifies conditions before applying parameter changes. Each field must be validated for the rules to be applied (AND operation). A field is considered verified if any of its properties match (OR operation).', @@ -49,21 +58,88 @@ type OptionType = { interface OverrideRuleModalProps { rule: OverrideRule | null; onClose: () => void; - testResponse: DVRTestResponse; - radarrId?: number; - sonarrId?: number; + radarrServices: RadarrSettings[]; + sonarrServices: SonarrSettings[]; } const OverrideRuleModal = ({ onClose, rule, - testResponse, - radarrId, - sonarrId, + radarrServices, + sonarrServices, }: OverrideRuleModalProps) => { const intl = useIntl(); const { addToast } = useToasts(); const { currentSettings } = useSettings(); + const [isValidated, setIsValidated] = useState(rule ? true : false); + const [isTesting, setIsTesting] = useState(false); + const [testResponse, setTestResponse] = useState({ + profiles: [], + rootFolders: [], + tags: [], + }); + + const getServiceInfos = useCallback( + async ({ + hostname, + port, + apiKey, + baseUrl, + useSsl = false, + }: { + hostname: string; + port: number; + apiKey: string; + baseUrl?: string; + useSsl?: boolean; + }) => { + setIsTesting(true); + try { + const res = await fetch('/api/v1/settings/sonarr/test', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + hostname, + apiKey, + port: Number(port), + baseUrl, + useSsl, + }), + }); + if (!res.ok) throw new Error(); + const data: DVRTestResponse = await res.json(); + + setIsValidated(true); + setTestResponse(data); + } catch (e) { + setIsValidated(false); + } finally { + setIsTesting(false); + } + }, + [] + ); + + useEffect(() => { + let service: DVRSettings | null = null; + if (rule?.radarrServiceId !== null && rule?.radarrServiceId !== undefined) { + service = radarrServices[rule?.radarrServiceId] || null; + } + if (rule?.sonarrServiceId !== null && rule?.sonarrServiceId !== undefined) { + service = sonarrServices[rule?.sonarrServiceId] || null; + } + if (service) { + getServiceInfos(service); + } + }, [ + getServiceInfos, + radarrServices, + rule?.radarrServiceId, + rule?.sonarrServiceId, + sonarrServices, + ]); return (
+

+ {intl.formatMessage(messages.service)} +

+

+ {intl.formatMessage(messages.serviceDescription)} +

+
+ +
+
+ +
+ {errors.rootFolder && + touched.rootFolder && + typeof errors.rootFolder === 'string' && ( +
{errors.rootFolder}
+ )} +
+

{intl.formatMessage(messages.conditions)}

@@ -184,6 +331,7 @@ const OverrideRuleModal = ({
{ setFieldValue( @@ -207,9 +355,10 @@ const OverrideRuleModal = ({
{ setFieldValue( 'genre', @@ -237,6 +386,7 @@ const OverrideRuleModal = ({ setFieldValue={(_key, value) => { setFieldValue('language', value); }} + isDisabled={!isValidated || isTesting} />
{errors.language && @@ -255,6 +405,7 @@ const OverrideRuleModal = ({ { setFieldValue( 'keywords', @@ -282,7 +433,12 @@ const OverrideRuleModal = ({
- + @@ -310,7 +466,12 @@ const OverrideRuleModal = ({
- + @@ -343,6 +504,7 @@ const OverrideRuleModal = ({ value: tag.id, }))} isMulti + isDisabled={!isValidated || isTesting} placeholder={intl.formatMessage(messages.selecttags)} className="react-select-container" classNamePrefix="react-select" diff --git a/src/components/Settings/OverrideRule/OverrideRuleTile.tsx b/src/components/Settings/OverrideRule/OverrideRuleTile.tsx deleted file mode 100644 index c5c0451a..00000000 --- a/src/components/Settings/OverrideRule/OverrideRuleTile.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import type { DVRTestResponse } from '@app/components/Settings/SettingsServices'; -import globalMessages from '@app/i18n/globalMessages'; -import defineMessages from '@app/utils/defineMessages'; -import { PencilIcon, TrashIcon } from '@heroicons/react/24/solid'; -import type { TmdbGenre } from '@server/api/themoviedb/interfaces'; -import type OverrideRule from '@server/entity/OverrideRule'; -import type { User } from '@server/entity/User'; -import type { - Language, - RadarrSettings, - SonarrSettings, -} from '@server/lib/settings'; -import type { Keyword } from '@server/models/common'; -import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; -import useSWR from 'swr'; - -const messages = defineMessages('components.Settings.OverrideRuleTile', { - qualityprofile: 'Quality Profile', - rootfolder: 'Root Folder', - tags: 'Tags', - users: 'Users', - genre: 'Genre', - language: 'Language', - keywords: 'Keywords', - conditions: 'Conditions', - settings: 'Settings', -}); - -interface OverrideRuleTileProps { - rules: OverrideRule[]; - setOverrideRuleModal: ({ - open, - rule, - testResponse, - }: { - open: boolean; - rule: OverrideRule | null; - testResponse: DVRTestResponse; - }) => void; - testResponse: DVRTestResponse; - radarr?: RadarrSettings | null; - sonarr?: SonarrSettings | null; - revalidate: () => void; -} - -const OverrideRuleTile = ({ - rules, - setOverrideRuleModal, - testResponse, - radarr, - sonarr, - revalidate, -}: OverrideRuleTileProps) => { - const intl = useIntl(); - const [users, setUsers] = useState(null); - const [keywords, setKeywords] = useState(null); - const { data: languages } = useSWR('/api/v1/languages'); - const { data: genres } = useSWR('/api/v1/genres/movie'); - - useEffect(() => { - (async () => { - const keywords = await Promise.all( - rules - .map((rule) => rule.keywords?.split(',')) - .flat() - .filter((keywordId) => keywordId) - .map(async (keywordId) => { - const res = await fetch(`/api/v1/keyword/${keywordId}`); - if (!res.ok) throw new Error(); - const keyword: Keyword = await res.json(); - return keyword; - }) - ); - setKeywords(keywords); - const users = await Promise.all( - rules - .map((rule) => rule.users?.split(',')) - .flat() - .filter((userId) => userId) - .map(async (userId) => { - const res = await fetch(`/api/v1/user/${userId}`); - if (!res.ok) throw new Error(); - const user: User = await res.json(); - return user; - }) - ); - setUsers(users); - })(); - }, [rules]); - - return ( - <> - {rules - .filter( - (rule) => - (rule.radarrServiceId !== null && - rule.radarrServiceId === radarr?.id) || - (rule.sonarrServiceId !== null && - rule.sonarrServiceId === sonarr?.id) - ) - .map((rule) => ( -
  • -
    -
    - - {intl.formatMessage(messages.conditions)} - - {rule.users && ( -

    - - {intl.formatMessage(messages.users)} - -

    - {rule.users.split(',').map((userId) => { - return ( - - { - users?.find((user) => user.id === Number(userId)) - ?.displayName - } - - ); - })} -
    -

    - )} - {rule.genre && ( -

    - - {intl.formatMessage(messages.genre)} - -

    - {rule.genre.split(',').map((genreId) => ( - - {genres?.find((g) => g.id === Number(genreId))?.name} - - ))} -
    -

    - )} - {rule.language && ( -

    - - {intl.formatMessage(messages.language)} - -

    - {rule.language - .split('|') - .filter((languageId) => languageId !== 'server') - .map((languageId) => { - const language = languages?.find( - (language) => language.iso_639_1 === languageId - ); - if (!language) return null; - const languageName = - intl.formatDisplayName(language.iso_639_1, { - type: 'language', - fallback: 'none', - }) ?? language.english_name; - return {languageName}; - })} -
    -

    - )} - {rule.keywords && ( -

    - - {intl.formatMessage(messages.keywords)} - -

    - {rule.keywords.split(',').map((keywordId) => { - return ( - - { - keywords?.find( - (keyword) => keyword.id === Number(keywordId) - )?.name - } - - ); - })} -
    -

    - )} - - {intl.formatMessage(messages.settings)} - - {rule.profileId && ( -

    - - {intl.formatMessage(messages.qualityprofile)} - - { - testResponse.profiles.find( - (profile) => rule.profileId === profile.id - )?.name - } -

    - )} - {rule.rootFolder && ( -

    - - {intl.formatMessage(messages.rootfolder)} - - {rule.rootFolder} -

    - )} - {rule.tags && rule.tags.length > 0 && ( -

    - - {intl.formatMessage(messages.tags)} - -

    - {rule.tags.split(',').map((tag) => ( - - { - testResponse.tags?.find((t) => t.id === Number(tag)) - ?.label - } - - ))} -
    -

    - )} -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
  • - ))} - - ); -}; - -export default OverrideRuleTile; diff --git a/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx b/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx new file mode 100644 index 00000000..a3b9aa37 --- /dev/null +++ b/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx @@ -0,0 +1,318 @@ +import type { DVRTestResponse } from '@app/components/Settings/SettingsServices'; +import globalMessages from '@app/i18n/globalMessages'; +import defineMessages from '@app/utils/defineMessages'; +import { PencilIcon, TrashIcon } from '@heroicons/react/24/solid'; +import type { TmdbGenre } from '@server/api/themoviedb/interfaces'; +import type OverrideRule from '@server/entity/OverrideRule'; +import type { User } from '@server/entity/User'; +import type { + DVRSettings, + Language, + RadarrSettings, + SonarrSettings, +} from '@server/lib/settings'; +import type { Keyword } from '@server/models/common'; +import { useCallback, useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import useSWR from 'swr'; + +const messages = defineMessages('components.Settings.OverrideRuleTile', { + qualityprofile: 'Quality Profile', + rootfolder: 'Root Folder', + tags: 'Tags', + users: 'Users', + genre: 'Genre', + language: 'Language', + keywords: 'Keywords', + conditions: 'Conditions', + settings: 'Settings', +}); + +interface OverrideRuleTilesProps { + rules: OverrideRule[]; + setOverrideRuleModal: ({ + open, + rule, + }: { + open: boolean; + rule: OverrideRule | null; + }) => void; + revalidate: () => void; + radarrServices: RadarrSettings[]; + sonarrServices: SonarrSettings[]; +} + +const OverrideRuleTiles = ({ + rules, + setOverrideRuleModal, + revalidate, + radarrServices, + sonarrServices, +}: OverrideRuleTilesProps) => { + const intl = useIntl(); + const [users, setUsers] = useState(null); + const [keywords, setKeywords] = useState(null); + const { data: languages } = useSWR('/api/v1/languages'); + const { data: genres } = useSWR('/api/v1/genres/movie'); + const [testResponses, setTestResponses] = useState< + (DVRTestResponse & { type: string; id: number })[] + >([]); + + const getServiceInfos = useCallback(async () => { + const results: (DVRTestResponse & { type: string; id: number })[] = []; + const services: DVRSettings[] = [...radarrServices, ...sonarrServices]; + for (const service of services) { + const { hostname, port, apiKey, baseUrl, useSsl = false } = service; + try { + const res = await fetch( + `/api/v1/settings/${ + radarrServices.includes(service as RadarrSettings) + ? 'radarr' + : 'sonarr' + }/test`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + hostname, + apiKey, + port: Number(port), + baseUrl, + useSsl, + }), + } + ); + if (!res.ok) throw new Error(); + const data: DVRTestResponse = await res.json(); + results.push({ + type: radarrServices.includes(service as RadarrSettings) + ? 'radarr' + : 'sonarr', + id: service.id, + ...data, + }); + } catch { + results.push({ + type: radarrServices.includes(service as RadarrSettings) + ? 'radarr' + : 'sonarr', + id: service.id, + profiles: [], + rootFolders: [], + tags: [], + }); + } + } + setTestResponses(results); + }, [radarrServices, sonarrServices]); + + useEffect(() => { + getServiceInfos(); + }, [getServiceInfos]); + + useEffect(() => { + (async () => { + const keywords = await Promise.all( + rules + .map((rule) => rule.keywords?.split(',')) + .flat() + .filter((keywordId) => keywordId) + .map(async (keywordId) => { + const res = await fetch(`/api/v1/keyword/${keywordId}`); + if (!res.ok) throw new Error(); + const keyword: Keyword = await res.json(); + return keyword; + }) + ); + setKeywords(keywords); + const users = await Promise.all( + rules + .map((rule) => rule.users?.split(',')) + .flat() + .filter((userId) => userId) + .map(async (userId) => { + const res = await fetch(`/api/v1/user/${userId}`); + if (!res.ok) throw new Error(); + const user: User = await res.json(); + return user; + }) + ); + setUsers(users); + })(); + }, [rules]); + + return ( + <> + {rules.map((rule) => ( +
  • +
    +
    + + {intl.formatMessage(messages.conditions)} + + {rule.users && ( +

    + + {intl.formatMessage(messages.users)} + +

    + {rule.users.split(',').map((userId) => { + return ( + + { + users?.find((user) => user.id === Number(userId)) + ?.displayName + } + + ); + })} +
    +

    + )} + {rule.genre && ( +

    + + {intl.formatMessage(messages.genre)} + +

    + {rule.genre.split(',').map((genreId) => ( + + {genres?.find((g) => g.id === Number(genreId))?.name} + + ))} +
    +

    + )} + {rule.language && ( +

    + + {intl.formatMessage(messages.language)} + +

    + {rule.language + .split('|') + .filter((languageId) => languageId !== 'server') + .map((languageId) => { + const language = languages?.find( + (language) => language.iso_639_1 === languageId + ); + if (!language) return null; + const languageName = + intl.formatDisplayName(language.iso_639_1, { + type: 'language', + fallback: 'none', + }) ?? language.english_name; + return {languageName}; + })} +
    +

    + )} + {rule.keywords && ( +

    + + {intl.formatMessage(messages.keywords)} + +

    + {rule.keywords.split(',').map((keywordId) => { + return ( + + { + keywords?.find( + (keyword) => keyword.id === Number(keywordId) + )?.name + } + + ); + })} +
    +

    + )} + + {intl.formatMessage(messages.settings)} + + {rule.profileId && ( +

    + + {intl.formatMessage(messages.qualityprofile)} + + {testResponses + .find( + (r) => + (r.id === rule.radarrServiceId && + r.type === 'radarr') || + (r.id === rule.sonarrServiceId && r.type === 'sonarr') + ) + ?.profiles.find((profile) => rule.profileId === profile.id) + ?.name || rule.profileId} +

    + )} + {rule.rootFolder && ( +

    + + {intl.formatMessage(messages.rootfolder)} + + {rule.rootFolder} +

    + )} + {rule.tags && rule.tags.length > 0 && ( +

    + + {intl.formatMessage(messages.tags)} + +

    + {rule.tags.split(',').map((tag) => ( + + {testResponses + .find( + (r) => + (r.id === rule.radarrServiceId && + r.type === 'radarr') || + (r.id === rule.sonarrServiceId && + r.type === 'sonarr') + ) + ?.tags?.find((t) => t.id === Number(tag))?.label || + tag} + + ))} +
    +

    + )} +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
  • + ))} + + ); +}; + +export default OverrideRuleTiles; diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index 5067c71b..fbeb2dec 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -1,24 +1,15 @@ -import Button from '@app/components/Common/Button'; import Modal from '@app/components/Common/Modal'; import SensitiveInput from '@app/components/Common/SensitiveInput'; -import OverrideRuleTile from '@app/components/Settings/OverrideRule/OverrideRuleTile'; -import type { - DVRTestResponse, - RadarrTestResponse, -} from '@app/components/Settings/SettingsServices'; +import type { RadarrTestResponse } from '@app/components/Settings/SettingsServices'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; -import { PlusIcon } from '@heroicons/react/24/solid'; -import type OverrideRule from '@server/entity/OverrideRule'; -import type { OverrideRuleResultsResponse } from '@server/interfaces/api/overrideRuleInterfaces'; import type { RadarrSettings } from '@server/lib/settings'; import { Field, Formik } from 'formik'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import Select from 'react-select'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; import * as Yup from 'yup'; type OptionType = { @@ -79,36 +70,16 @@ const messages = defineMessages('components.Settings.RadarrModal', { announced: 'Announced', inCinemas: 'In Cinemas', released: 'Released', - overrideRules: 'Override Rules', - addrule: 'New Override Rule', }); interface RadarrModalProps { radarr: RadarrSettings | null; onClose: () => void; onSave: () => void; - overrideRuleModal: { open: boolean; rule: OverrideRule | null }; - setOverrideRuleModal: ({ - open, - rule, - testResponse, - }: { - open: boolean; - rule: OverrideRule | null; - testResponse: DVRTestResponse; - }) => void; } -const RadarrModal = ({ - onClose, - radarr, - onSave, - overrideRuleModal, - setOverrideRuleModal, -}: RadarrModalProps) => { +const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { const intl = useIntl(); - const { data: rules, mutate: revalidate } = - useSWR('/api/v1/overrideRule'); const initialLoad = useRef(false); const { addToast } = useToasts(); const [isValidated, setIsValidated] = useState(radarr ? true : false); @@ -235,10 +206,6 @@ const RadarrModal = ({ } }, [radarr, testConnection]); - useEffect(() => { - revalidate(); - }, [overrideRuleModal, revalidate]); - return (
    @@ -773,42 +739,6 @@ const RadarrModal = ({
    - {radarr && ( - <> -

    - {intl.formatMessage(messages.overrideRules)} -

    -
      - {rules && ( - - )} -
    • -
      - -
      -
    • -
    - - )} ); }} diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx index 5e3871fc..fc058c0b 100644 --- a/src/components/Settings/SettingsServices.tsx +++ b/src/components/Settings/SettingsServices.tsx @@ -7,6 +7,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import Modal from '@app/components/Common/Modal'; import PageTitle from '@app/components/Common/PageTitle'; import OverrideRuleModal from '@app/components/Settings/OverrideRule/OverrideRuleModal'; +import OverrideRuleTiles from '@app/components/Settings/OverrideRule/OverrideRuleTiles'; import RadarrModal from '@app/components/Settings/RadarrModal'; import SonarrModal from '@app/components/Settings/SonarrModal'; import globalMessages from '@app/i18n/globalMessages'; @@ -14,6 +15,7 @@ import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid'; import type OverrideRule from '@server/entity/OverrideRule'; +import type { OverrideRuleResultsResponse } from '@server/interfaces/api/overrideRuleInterfaces'; import type { RadarrSettings, SonarrSettings } from '@server/lib/settings'; import { Fragment, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -43,6 +45,10 @@ const messages = defineMessages('components.Settings', { mediaTypeMovie: 'movie', mediaTypeSeries: 'series', deleteServer: 'Delete {serverType} Server', + overrideRules: 'Override Rules', + overrideRulesDescription: + 'Override rules allow you to specify properties that will be replaced if a request matches the rule.', + addrule: 'New Override Rule', }); interface ServerInstanceProps { @@ -199,6 +205,8 @@ const SettingsServices = () => { error: sonarrError, mutate: revalidateSonarr, } = useSWR('/api/v1/settings/sonarr'); + const { data: rules, mutate: revalidate } = + useSWR('/api/v1/overrideRule'); const [editRadarrModal, setEditRadarrModal] = useState<{ open: boolean; radarr: RadarrSettings | null; @@ -225,11 +233,9 @@ const SettingsServices = () => { const [overrideRuleModal, setOverrideRuleModal] = useState<{ open: boolean; rule: OverrideRule | null; - testResponse: DVRTestResponse | null; }>({ open: false, rule: null, - testResponse: null, }); const deleteServer = async () => { @@ -265,21 +271,6 @@ const SettingsServices = () => { })}

    - {overrideRuleModal.open && overrideRuleModal.testResponse && ( - - setOverrideRuleModal({ - open: false, - rule: null, - testResponse: null, - }) - } - testResponse={overrideRuleModal.testResponse} - radarrId={editRadarrModal.radarr?.id} - sonarrId={editSonarrModal.sonarr?.id} - /> - )} {editRadarrModal.open && ( { mutate('/api/v1/settings/public'); setEditRadarrModal({ open: false, radarr: null }); }} - overrideRuleModal={overrideRuleModal} - setOverrideRuleModal={setOverrideRuleModal} /> )} {editSonarrModal.open && ( @@ -308,8 +297,6 @@ const SettingsServices = () => { mutate('/api/v1/settings/public'); setEditSonarrModal({ open: false, sonarr: null }); }} - overrideRuleModal={overrideRuleModal} - setOverrideRuleModal={setOverrideRuleModal} /> )} { )}
    +
    +

    + {intl.formatMessage(messages.overrideRules)} +

    +

    + {intl.formatMessage(messages.overrideRulesDescription, { + serverType: 'Sonarr', + })} +

    +
    +
    +
      + {rules && radarrData && sonarrData && ( + + )} +
    • +
      + +
      +
    • +
    +
    + {overrideRuleModal.open && radarrData && sonarrData && ( + { + setOverrideRuleModal({ + open: false, + rule: null, + }); + revalidate(); + }} + radarrServices={radarrData} + sonarrServices={sonarrData} + /> + )} ); }; diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index 888dcc54..fedea2a6 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -1,17 +1,9 @@ -import Button from '@app/components/Common/Button'; import Modal from '@app/components/Common/Modal'; import SensitiveInput from '@app/components/Common/SensitiveInput'; -import OverrideRuleTile from '@app/components/Settings/OverrideRule/OverrideRuleTile'; -import type { - DVRTestResponse, - SonarrTestResponse, -} from '@app/components/Settings/SettingsServices'; +import type { SonarrTestResponse } from '@app/components/Settings/SettingsServices'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; -import { PlusIcon } from '@heroicons/react/24/solid'; -import type OverrideRule from '@server/entity/OverrideRule'; -import type { OverrideRuleResultsResponse } from '@server/interfaces/api/overrideRuleInterfaces'; import type { SonarrSettings } from '@server/lib/settings'; import { Field, Formik } from 'formik'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -19,7 +11,6 @@ import { useIntl } from 'react-intl'; import type { OnChangeValue } from 'react-select'; import Select from 'react-select'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; import * as Yup from 'yup'; type OptionType = { @@ -85,36 +76,16 @@ const messages = defineMessages('components.Settings.SonarrModal', { animeTags: 'Anime Tags', notagoptions: 'No tags.', selecttags: 'Select tags', - overrideRules: 'Override Rules', - addrule: 'New Override Rule', }); interface SonarrModalProps { sonarr: SonarrSettings | null; onClose: () => void; onSave: () => void; - overrideRuleModal: { open: boolean; rule: OverrideRule | null }; - setOverrideRuleModal: ({ - open, - rule, - testResponse, - }: { - open: boolean; - rule: OverrideRule | null; - testResponse: DVRTestResponse; - }) => void; } -const SonarrModal = ({ - onClose, - sonarr, - onSave, - overrideRuleModal, - setOverrideRuleModal, -}: SonarrModalProps) => { +const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { const intl = useIntl(); - const { data: rules, mutate: revalidate } = - useSWR('/api/v1/overrideRule'); const initialLoad = useRef(false); const { addToast } = useToasts(); const [isValidated, setIsValidated] = useState(sonarr ? true : false); @@ -244,10 +215,6 @@ const SonarrModal = ({ } }, [sonarr, testConnection]); - useEffect(() => { - revalidate(); - }, [overrideRuleModal, revalidate]); - return (
    @@ -1070,42 +1036,6 @@ const SonarrModal = ({
    - {sonarr && ( - <> -

    - {intl.formatMessage(messages.overrideRules)} -

    -
      - {rules && ( - - )} -
    • -
      - -
      -
    • -
    - - )} ); }} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 2b0b3681..1a18a3a4 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -753,7 +753,10 @@ "components.Settings.OverrideRuleModal.ruleUpdated": "Override rule updated successfully!", "components.Settings.OverrideRuleModal.selectQualityProfile": "Select quality profile", "components.Settings.OverrideRuleModal.selectRootFolder": "Select root folder", + "components.Settings.OverrideRuleModal.selectService": "Select service", "components.Settings.OverrideRuleModal.selecttags": "Select tags", + "components.Settings.OverrideRuleModal.service": "Service", + "components.Settings.OverrideRuleModal.serviceDescription": "Apply this rule to the selected service.", "components.Settings.OverrideRuleModal.settings": "Settings", "components.Settings.OverrideRuleModal.settingsDescription": "Specifies which settings will be changed when the above conditions are met.", "components.Settings.OverrideRuleModal.tags": "Tags", @@ -768,7 +771,6 @@ "components.Settings.OverrideRuleTile.tags": "Tags", "components.Settings.OverrideRuleTile.users": "Users", "components.Settings.RadarrModal.add": "Add Server", - "components.Settings.RadarrModal.addrule": "New Override Rule", "components.Settings.RadarrModal.announced": "Announced", "components.Settings.RadarrModal.apiKey": "API Key", "components.Settings.RadarrModal.baseUrl": "URL Base", @@ -787,7 +789,6 @@ "components.Settings.RadarrModal.loadingrootfolders": "Loading root folders…", "components.Settings.RadarrModal.minimumAvailability": "Minimum Availability", "components.Settings.RadarrModal.notagoptions": "No tags.", - "components.Settings.RadarrModal.overrideRules": "Override Rules", "components.Settings.RadarrModal.port": "Port", "components.Settings.RadarrModal.qualityprofile": "Quality Profile", "components.Settings.RadarrModal.released": "Released", @@ -976,7 +977,6 @@ "components.Settings.SettingsUsers.userSettingsDescription": "Configure global and default user settings.", "components.Settings.SettingsUsers.users": "Users", "components.Settings.SonarrModal.add": "Add Server", - "components.Settings.SonarrModal.addrule": "New Override Rule", "components.Settings.SonarrModal.animeSeriesType": "Anime Series Type", "components.Settings.SonarrModal.animeTags": "Anime Tags", "components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile", @@ -999,7 +999,6 @@ "components.Settings.SonarrModal.loadingprofiles": "Loading quality profiles…", "components.Settings.SonarrModal.loadingrootfolders": "Loading root folders…", "components.Settings.SonarrModal.notagoptions": "No tags.", - "components.Settings.SonarrModal.overrideRules": "Override Rules", "components.Settings.SonarrModal.port": "Port", "components.Settings.SonarrModal.qualityprofile": "Quality Profile", "components.Settings.SonarrModal.rootfolder": "Root Folder", @@ -1036,6 +1035,7 @@ "components.Settings.activeProfile": "Active Profile", "components.Settings.addradarr": "Add Radarr Server", "components.Settings.address": "Address", + "components.Settings.addrule": "New Override Rule", "components.Settings.addsonarr": "Add Sonarr Server", "components.Settings.advancedTooltip": "Incorrectly configuring this setting may result in broken functionality", "components.Settings.apiKey": "API key", @@ -1089,6 +1089,8 @@ "components.Settings.notifications": "Notifications", "components.Settings.notificationsettings": "Notification Settings", "components.Settings.notrunning": "Not Running", + "components.Settings.overrideRules": "Override Rules", + "components.Settings.overrideRulesDescription": "Override rules allow you to specify properties that will be replaced if a request matches the rule.", "components.Settings.plex": "Plex", "components.Settings.plexlibraries": "Plex Libraries", "components.Settings.plexlibrariesDescription": "The libraries Jellyseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.", From 9712f5605471a673edb3d25048dc08d1addd58db Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 22 Feb 2025 17:18:27 +0100 Subject: [PATCH 47/65] fix: fix remove from *arr in item details (#1387) Fix the "Remove From *arr" button in the slideover of the movie/series details page. The issue was caused by an attempt to delete a file that didn't exist. --- src/components/ManageSlideOver/index.tsx | 8 +++----- src/components/RequestList/RequestItem/index.tsx | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 205c9456..5c36e787 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -122,15 +122,13 @@ const ManageSlideOver = ({ const deleteMediaFile = async () => { if (data.mediaInfo) { - const res1 = await fetch(`/api/v1/media/${data.mediaInfo.id}/file`, { + // we don't check if the response is ok here because there may be no file to delete + await fetch(`/api/v1/media/${data.mediaInfo.id}/file`, { method: 'DELETE', }); - if (!res1.ok) throw new Error(); - - const res2 = await fetch(`/api/v1/media/${data.mediaInfo.id}`, { + await fetch(`/api/v1/media/${data.mediaInfo.id}`, { method: 'DELETE', }); - if (!res2.ok) throw new Error(); revalidate(); onClose(); diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 0f8a5a24..018fa915 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -348,6 +348,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const deleteMediaFile = async () => { if (request.media) { + // we don't check if the response is ok here because there may be no file to delete await fetch(`/api/v1/media/${request.media.id}/file`, { method: 'DELETE', }); From 73d8efaa54888b5282624e618c1461c23653f0b9 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Sat, 22 Feb 2025 11:40:38 -0500 Subject: [PATCH 48/65] feat: revamp login page and support disabling media server login (#1286) * feat: support disabling jellyfin login * feat: revamp login screen Update the login screen for better usability, especially with OpenID Connect and Plex login, allowing one-click login and removing the accordion layout. Additionally, ensures that media server login is hidden when disabled in the settings. * test: update cypress login command --- cypress/support/commands.ts | 1 - docs/using-jellyseerr/settings/users.md | 8 + package.json | 2 + pnpm-lock.yaml | 27 +- server/interfaces/api/settingsInterfaces.ts | 1 + server/lib/settings/index.ts | 5 + server/routes/auth.ts | 14 +- src/components/Common/Button/index.tsx | 5 +- .../Common/LabeledCheckbox/index.tsx | 44 ++ src/components/Login/JellyfinLogin.tsx | 465 +++--------------- src/components/Login/LocalLogin.tsx | 96 ++-- src/components/Login/PlexLoginButton.tsx | 62 +++ src/components/Login/index.tsx | 225 ++++++--- src/components/PlexLoginButton/index.tsx | 66 --- .../Settings/SettingsUsers/index.tsx | 105 +++- src/components/Setup/JellyfinSetup.tsx | 352 +++++++++++++ src/components/Setup/LoginWithPlex.tsx | 2 +- src/components/Setup/SetupLogin.tsx | 16 +- src/context/SettingsContext.tsx | 1 + src/hooks/usePlexLogin.ts | 37 ++ src/i18n/locale/en.json | 11 +- src/pages/_app.tsx | 1 + src/styles/globals.css | 14 +- 23 files changed, 921 insertions(+), 639 deletions(-) create mode 100644 src/components/Common/LabeledCheckbox/index.tsx create mode 100644 src/components/Login/PlexLoginButton.tsx delete mode 100644 src/components/PlexLoginButton/index.tsx create mode 100644 src/components/Setup/JellyfinSetup.tsx create mode 100644 src/hooks/usePlexLogin.ts diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 0eb9c869..a23cb5e6 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -6,7 +6,6 @@ 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); diff --git a/docs/using-jellyseerr/settings/users.md b/docs/using-jellyseerr/settings/users.md index ebe547ef..0fdeb7db 100644 --- a/docs/using-jellyseerr/settings/users.md +++ b/docs/using-jellyseerr/settings/users.md @@ -14,6 +14,14 @@ 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. diff --git a/package.json b/package.json index 6e6500ed..74512020 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "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", @@ -95,6 +96,7 @@ "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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de1247df..07a8ac57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,6 +170,9 @@ importers: react-toast-notifications: specifier: 2.5.1 version: 2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-transition-group: + specifier: ^4.4.5 + version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-truncate-markup: specifier: 5.1.2 version: 5.1.2(react@18.3.1) @@ -197,6 +200,9 @@ importers: swr: specifier: 2.2.5 version: 2.2.5(react@18.3.1) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 typeorm: specifier: 0.3.11 version: 0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) @@ -8844,6 +8850,9 @@ packages: peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + tailwindcss@3.2.7: resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} engines: {node: '>=12.13.0'} @@ -11293,7 +11302,7 @@ snapshots: '@emotion/babel-plugin@11.11.0': dependencies: '@babel/helper-module-imports': 7.24.7 - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.4 @@ -11323,7 +11332,7 @@ snapshots: '@emotion/core@10.3.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 '@emotion/cache': 10.0.29 '@emotion/css': 10.0.27 '@emotion/serialize': 0.11.16 @@ -11351,7 +11360,7 @@ snapshots: '@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.4 @@ -14254,13 +14263,13 @@ snapshots: babel-plugin-macros@2.8.0: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 cosmiconfig: 6.0.0 resolve: 1.22.8 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 cosmiconfig: 7.1.0 resolve: 1.22.8 @@ -15350,7 +15359,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 csstype: 3.1.3 dom-serializer@1.4.1: @@ -19366,7 +19375,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -19493,7 +19502,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 regexp.prototype.flags@1.5.2: dependencies: @@ -20274,6 +20283,8 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) + tailwind-merge@2.6.0: {} + tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): dependencies: arg: 5.0.2 diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 017eef85..0e97c2bf 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -30,6 +30,7 @@ export interface PublicSettingsResponse { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; discoverRegion: string; diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 258dfe2f..7fc09fb3 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -123,6 +123,7 @@ export interface MainSettings { }; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; newPlexLogin: boolean; discoverRegion: string; streamingRegion: string; @@ -150,6 +151,7 @@ interface FullPublicSettings extends PublicSettings { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; discoverRegion: string; @@ -343,6 +345,7 @@ class Settings { }, hideAvailable: false, localLogin: true, + mediaServerLogin: true, newPlexLogin: true, discoverRegion: '', streamingRegion: '', @@ -588,6 +591,8 @@ 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 diff --git a/server/routes/auth.ts b/server/routes/auth.ts index cbfbc3f7..31c846ad 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -56,8 +56,9 @@ authRoutes.post('/plex', async (req, res, next) => { } if ( - settings.main.mediaServerType != MediaServerType.PLEX && - settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED + settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED && + (settings.main.mediaServerLogin === false || + settings.main.mediaServerType != MediaServerType.PLEX) ) { return res.status(500).json({ error: 'Plex login is disabled' }); } @@ -231,10 +232,13 @@ authRoutes.post('/jellyfin', async (req, res, next) => { //Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured if ( - settings.main.mediaServerType !== MediaServerType.JELLYFIN && - settings.main.mediaServerType !== MediaServerType.EMBY && + // media server not configured, allow login for setup settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED && - settings.jellyfin.ip !== '' + (settings.main.mediaServerLogin === false || + // media server is neither jellyfin or emby + (settings.main.mediaServerType !== MediaServerType.JELLYFIN && + settings.main.mediaServerType !== MediaServerType.EMBY && + settings.jellyfin.ip !== '')) ) { return res.status(500).json({ error: 'Jellyfin login is disabled' }); } diff --git a/src/components/Common/Button/index.tsx b/src/components/Common/Button/index.tsx index a4df3115..ac1c330c 100644 --- a/src/components/Common/Button/index.tsx +++ b/src/components/Common/Button/index.tsx @@ -1,5 +1,6 @@ import type { ForwardedRef } from 'react'; import React from 'react'; +import { twMerge } from 'tailwind-merge'; export type ButtonType = | 'default' @@ -97,7 +98,7 @@ function Button

    ( if (as === 'a') { return ( )} ref={ref as ForwardedRef} > @@ -107,7 +108,7 @@ function Button

    ( } else { return ( - - {onCancel && ( - - - - )} -

    -
    - - )} - - ); - } 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 ( -
    - { - 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(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, isSubmitting, isValid }) => { - return ( - <> -
    -
    + + + + + ); + }} + +
    + ); }; export default JellyfinLogin; diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx index 2f2e00ed..2372bc7f 100644 --- a/src/components/Login/LocalLogin.tsx +++ b/src/components/Login/LocalLogin.tsx @@ -2,10 +2,7 @@ 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, - LifebuoyIcon, -} from '@heroicons/react/24/outline'; +import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; import Link from 'next/link'; import { useState } from 'react'; @@ -13,6 +10,7 @@ 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', @@ -53,6 +51,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { password: '', }} validationSchema={LoginSchema} + validateOnBlur={false} onSubmit={async (values) => { try { const res = await fetch('/api/v1/auth/local', { @@ -78,19 +77,24 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { <>
    - -
    +

    + {intl.formatMessage(messages.loginwithapp, { + appName: settings.currentSettings.applicationTitle, + })} +

    + +
    {errors.email && @@ -99,25 +103,35 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
    {errors.email}
    )}
    - -
    +
    - {errors.password && - touched.password && - typeof errors.password === 'string' && ( -
    {errors.password}
    +
    + {errors.password && + touched.password && + typeof errors.password === 'string' && ( +
    {errors.password}
    + )} +
    + {passwordResetEnabled && ( + + {intl.formatMessage(messages.forgotpassword)} + )} +
    {loginError && (
    @@ -125,37 +139,21 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
    )}
    -
    -
    - - - - {passwordResetEnabled && ( - - - - - - )} -
    -
    + + ); diff --git a/src/components/Login/PlexLoginButton.tsx b/src/components/Login/PlexLoginButton.tsx new file mode 100644 index 00000000..111b95d3 --- /dev/null +++ b/src/components/Login/PlexLoginButton.tsx @@ -0,0 +1,62 @@ +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 ( + + ); +}; + +export default PlexLoginButton; diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index 7b95b9fc..0b51e86f 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -1,9 +1,13 @@ -import Accordion from '@app/components/Common/Accordion'; +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 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/PlexLoginButton'; +import PlexLoginButton from '@app/components/Login/PlexLoginButton'; import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; import defineMessages from '@app/utils/defineMessages'; @@ -12,10 +16,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, useState } from 'react'; +import { useEffect, useRef, 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', @@ -23,16 +27,21 @@ 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(undefined); - const { user, revalidate } = useUser(); - const router = useRouter(); - const settings = useSettings(); + const [mediaServerLogin, setMediaServerLogin] = useState( + settings.currentSettings.mediaServerLogin + ); // 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 @@ -86,14 +95,73 @@ const Login = () => { revalidateOnFocus: false, }); - const mediaServerFormatValues = { - mediaServerName: - settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN - ? 'Jellyfin' - : settings.currentSettings.mediaServerType === MediaServerType.EMBY - ? 'Emby' - : undefined, - }; + 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(null); + const localLoginRef = useRef(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 ? ( + setAuthToken(authToken)} + large={!isJellyfin && !settings.currentSettings.localLogin} + /> + ) : ( + settings.currentSettings.localLogin && + (mediaServerLogin ? ( + + ) : ( + + )) + )), + ].filter((o): o is JSX.Element => !!o); return (
    @@ -112,9 +180,6 @@ const Login = () => {
    Logo
    -

    - {intl.formatMessage(messages.signinheader)} -

    {
    - - {({ openIndexes, handleClick, AccordionContent }) => ( - <> - - -
    - {settings.currentSettings.mediaServerType == - MediaServerType.PLEX ? ( - setAuthToken(authToken)} - /> - ) : ( - - )} -
    -
    - {settings.currentSettings.localLogin && ( -
    - - -
    - -
    -
    -
    - )} - - )} -
    +
    + + { + loginRef.current?.addEventListener( + 'transitionend', + done, + false + ); + }} + onEntered={() => { + document + .querySelector('#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', + }} + > +
    + {isJellyfin && + (mediaServerLogin || + !settings.currentSettings.localLogin) ? ( + + ) : ( + settings.currentSettings.localLogin && ( + + ) + )} +
    +
    +
    + + {additionalLoginOptions.length > 0 && + (loginFormVisible ? ( +
    +
    + + {intl.formatMessage(messages.orsigninwith)} + +
    +
    + ) : ( +

    + {intl.formatMessage(messages.signinheader)} +

    + ))} + +
    + {additionalLoginOptions} +
    +
    diff --git a/src/components/PlexLoginButton/index.tsx b/src/components/PlexLoginButton/index.tsx deleted file mode 100644 index 3cf1d3ee..00000000 --- a/src/components/PlexLoginButton/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -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 ( - - - - ); -}; - -export default PlexLoginButton; diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx index 7f6fa1fc..8203360b 100644 --- a/src/components/Settings/SettingsUsers/index.tsx +++ b/src/components/Settings/SettingsUsers/index.tsx @@ -1,4 +1,5 @@ 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'; @@ -13,6 +14,7 @@ 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', @@ -20,9 +22,15 @@ 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, instead of {mediaServerName} OAuth', + '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.', newPlexLogin: 'Enable New {mediaServerName} Sign-In', newPlexLoginTip: 'Allow {mediaServerName} users to sign in without first being imported', @@ -42,6 +50,27 @@ const SettingsUsers = () => { } = useSWR('/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 ; } @@ -52,6 +81,8 @@ const SettingsUsers = () => { ? 'Jellyfin' : settings.currentSettings.mediaServerType === MediaServerType.EMBY ? 'Emby' + : settings.currentSettings.mediaServerType === MediaServerType.PLEX + ? 'Plex' : undefined, }; @@ -73,6 +104,7 @@ const SettingsUsers = () => { { tvQuotaDays: data?.defaultQuotas.tv.quotaDays ?? 7, defaultPermissions: data?.defaultPermissions ?? 0, }} + validationSchema={schema} enableReinitialize onSubmit={async (values) => { try { @@ -90,6 +123,7 @@ const SettingsUsers = () => { }, body: JSON.stringify({ localLogin: values.localLogin, + mediaServerLogin: values.mediaServerLogin, newPlexLogin: values.newPlexLogin, defaultQuotas: { movie: { @@ -121,30 +155,61 @@ const SettingsUsers = () => { } }} > - {({ isSubmitting, values, setFieldValue }) => { + {({ isSubmitting, isValid, values, errors, setFieldValue }) => { return (
    -
    -
    {serverType === MediaServerType.PLEX && ( <> -
    +
    { setMediaServerType(MediaServerType.PLEX); setAuthToken(authToken); @@ -102,16 +100,14 @@ const SetupLogin: React.FC = ({ )} {serverType === MediaServerType.JELLYFIN && ( - )} {serverType === MediaServerType.EMBY && ( - 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; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 1a18a3a4..bd2ce864 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -246,7 +246,9 @@ "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", @@ -441,8 +443,6 @@ "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} {movies} per {quotaDays} {days}", "components.QuotaSelector.movies": "{count, plural, one {movie} other {movies}}", @@ -963,10 +963,15 @@ "components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support", "components.Settings.SettingsNetwork.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy", "components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port", + "components.Settings.SettingsUsers.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, instead of {mediaServerName} OAuth", + "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.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", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index facb3a44..3ab8ab13 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -194,6 +194,7 @@ CoreApp.getInitialProps = async (initialProps) => { movie4kEnabled: false, series4kEnabled: false, localLogin: true, + mediaServerLogin: true, discoverRegion: '', streamingRegion: '', originalLanguage: '', diff --git a/src/styles/globals.css b/src/styles/globals.css index 1e99d53d..28733658 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -74,15 +74,6 @@ top: env(safe-area-inset-top); } - .plex-button { - @apply flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-center text-sm font-medium text-white transition duration-150 ease-in-out disabled:opacity-50; - background-color: #cc7b19; - } - - .plex-button:hover { - background: #f19a30; - } - .server-type-button { @apply rounded-md border border-gray-500 bg-gray-700 px-4 py-2 text-white transition duration-150 ease-in-out hover:bg-gray-500; } @@ -354,9 +345,8 @@ @apply relative -ml-px inline-flex items-center border border-gray-500 bg-indigo-600 bg-opacity-80 px-3 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out last:rounded-r-md hover:bg-opacity-100 active:bg-gray-100 active:text-gray-700 sm:px-3.5; } - .button-md svg, - button.input-action svg, - .plex-button svg { + .button-md :where(svg), + button.input-action svg { @apply ml-2 mr-2 h-5 w-5 first:ml-0 last:mr-0; } From a3f4773a35a5e70c0234182aa445cde8513da7c2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 00:42:33 +0800 Subject: [PATCH 49/65] docs: add michaelhthomas as a contributor for code (#1388) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8779bc16..5468b39c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -610,6 +610,15 @@ "contributions": [ "code" ] + }, + { + "login": "michaelhthomas", + "name": "Michael Thomas", + "avatar_url": "https://avatars.githubusercontent.com/u/18223295?v=4", + "profile": "http://michaelt.xyz", + "contributions": [ + "code" + ] } ] } diff --git a/README.md b/README.md index 70463b60..6ed6bff7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Translation status GitHub -All Contributors +All Contributors **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/)**. @@ -170,6 +170,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon Metin Bektas
    Metin Bektas

    🚇 andrewkolda
    andrewkolda

    🎨 Ishan Jain
    Ishan Jain

    💻 + Michael Thomas
    Michael Thomas

    💻 From f0a605577469248a2a7c2170be8310e106131c59 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sun, 23 Feb 2025 11:25:25 +0100 Subject: [PATCH 50/65] fix: add email requirement for local users (#1389) * fix: add email requirement for local users Because of a misunderstanding, and the requirement to have a mandatory email for local users was removed, when it shouldn't have been. re #900 fix #1367 * fix: add missing check for Emby --- server/routes/user/usersettings.ts | 20 +------------------ src/components/UserList/index.tsx | 5 ++++- .../UserGeneralSettings/index.tsx | 4 +++- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 6ee0f893..ac89b048 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -119,28 +119,10 @@ userSettingsRoutes.post< } const oldEmail = user.email; - const oldUsername = user.username; user.username = req.body.username; - if (user.jellyfinUsername) { + if (user.userType !== UserType.PLEX) { user.email = req.body.email || user.jellyfinUsername || user.email; } - // Edge case for local users, because they have no Jellyfin username to fall back on - // if the email is not provided - if (user.userType === UserType.LOCAL) { - if (req.body.email) { - user.email = req.body.email; - if ( - !user.username && - user.email !== oldEmail && - !oldEmail.includes('@') - ) { - user.username = oldEmail; - } - } else if (req.body.username) { - user.email = oldUsername || user.email; - user.username = req.body.username; - } - } const existingUser = await userRepository.findOne({ where: { email: user.email }, diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 95a3c8a6..bc7c4441 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -210,7 +210,9 @@ const UserList = () => { username: Yup.string().required( intl.formatMessage(messages.validationUsername) ), - email: Yup.string().email(intl.formatMessage(messages.validationEmail)), + email: Yup.string() + .required() + .email(intl.formatMessage(messages.validationEmail)), password: Yup.lazy((value) => !value ? Yup.string() @@ -388,6 +390,7 @@ const UserList = () => {
    diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 4ee8a80f..d8f0ded0 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -100,7 +100,9 @@ const UserGeneralSettings = () => { const UserGeneralSettingsSchema = Yup.object().shape({ email: - user?.id === 1 + // email is required for everybody except non-admin jellyfin users + user?.id === 1 || + (user?.userType !== UserType.JELLYFIN && user?.userType !== UserType.EMBY) ? Yup.string() .email(intl.formatMessage(messages.validationemailformat)) .required(intl.formatMessage(messages.validationemailrequired)) From a790b1abccfa9c3f8272ade8cd055017905dd87f Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Mon, 24 Feb 2025 05:35:12 +0800 Subject: [PATCH 51/65] feat(airdatebadge): convert airDate from UTC to local timezone (#1390) This PR will ensure that the airdate is in the user's local timezone so that its more user friendly and the relative time calculation would be consistent fix #1373 --- src/components/AirDateBadge/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AirDateBadge/index.tsx b/src/components/AirDateBadge/index.tsx index d4e438a6..a51f39fc 100644 --- a/src/components/AirDateBadge/index.tsx +++ b/src/components/AirDateBadge/index.tsx @@ -14,6 +14,7 @@ type AirDateBadgeProps = { const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { const WEEK = 1000 * 60 * 60 * 24 * 8; const intl = useIntl(); + const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const dAirDate = new Date(airDate); const nowDate = new Date(); const alreadyAired = dAirDate.getTime() < nowDate.getTime(); @@ -38,7 +39,7 @@ const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { year: 'numeric', month: 'long', day: 'numeric', - timeZone: 'UTC', + timeZone, })} {showRelative && ( From 27112be9330eb271a2030848ae3f530715300ab4 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Wed, 26 Feb 2025 06:08:00 +0100 Subject: [PATCH 52/65] refactor: rename some remaining Overseerr occurrences (#1397) * refactor: rename some remaining Overseerr occurrences This PR renames some remaining occurrences of Overseerr to Jellyseerr. This includes the OpenAPI specification, log file names and some variables and console messages. * fix(i18n): add missing translation --- CONTRIBUTING.md | 2 +- cypress/config/settings.cypress.json | 4 ++-- overseerr-api.yml => jellyseerr-api.yml | 16 ++++++++-------- package.json | 2 +- ...ound.png => jellyseerr_poster_not_found.png} | Bin ...jellyseerr_poster_not_found_logo_center.png} | Bin ...=> jellyseerr_poster_not_found_logo_top.png} | Bin server/api/github.ts | 8 ++++---- server/api/plexapi.ts | 6 +++--- server/index.ts | 4 ++-- server/lib/notifications/agents/slack.ts | 2 +- server/lib/watchlistsync.ts | 2 +- server/logger.ts | 6 +++--- server/routes/auth.ts | 10 +++++----- server/routes/index.ts | 6 +++--- server/routes/user/usersettings.ts | 2 +- src/components/Blacklist/index.tsx | 2 +- src/components/CollectionDetails/index.tsx | 2 +- src/components/IssueDetails/index.tsx | 2 +- src/components/IssueList/IssueItem/index.tsx | 2 +- src/components/MovieDetails/index.tsx | 2 +- src/components/PWAHeader/index.tsx | 2 +- src/components/RequestCard/index.tsx | 2 +- .../RequestList/RequestItem/index.tsx | 2 +- .../RequestModal/CollectionRequestModal.tsx | 2 +- .../RequestModal/SearchByNameModal/index.tsx | 2 +- src/components/Settings/SettingsLogs/index.tsx | 2 +- src/components/TitleCard/index.tsx | 2 +- src/components/TvDetails/index.tsx | 2 +- src/context/SettingsContext.tsx | 2 +- src/i18n/locale/en.json | 2 +- src/utils/plex.ts | 4 ++-- 32 files changed, 52 insertions(+), 52 deletions(-) rename overseerr-api.yml => jellyseerr-api.yml (99%) rename public/images/{overseerr_poster_not_found.png => jellyseerr_poster_not_found.png} (100%) rename public/images/{overseerr_poster_not_found_logo_center.png => jellyseerr_poster_not_found_logo_center.png} (100%) rename public/images/{overseerr_poster_not_found_logo_top.png => jellyseerr_poster_not_found_logo_top.png} (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab5f59da..1835e949 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,7 +97,7 @@ When adding new UI text, please try to adhere to the following guidelines: ## Translation -We use [Weblate](https://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/) for our translations, and your help with localizing Overseerr would be greatly appreciated! If your language is not listed below, please [open a feature request](https://github.com/fallenbagel/jellyseerr/issues/new/choose). +We use [Weblate](https://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/) for our translations, and your help with localizing Jellyseerr would be greatly appreciated! If your language is not listed below, please [open a feature request](https://github.com/fallenbagel/jellyseerr/issues/new/choose). Translation status diff --git a/cypress/config/settings.cypress.json b/cypress/config/settings.cypress.json index e49d8888..f376d880 100644 --- a/cypress/config/settings.cypress.json +++ b/cypress/config/settings.cypress.json @@ -4,7 +4,7 @@ "vapidPublic": "BK_EpP8NDm9waor2zn6_S28o3ZYv4kCkJOfYpO3pt3W6jnPmxrgTLANUBNbbyaNatPnSQ12De9CeqSYQrqWzHTs", "main": { "apiKey": "testkey", - "applicationTitle": "Overseerr", + "applicationTitle": "Jellyseerr", "applicationUrl": "", "csrfProtection": false, "cacheImages": false, @@ -71,7 +71,7 @@ "ignoreTls": false, "requireTls": false, "allowSelfSigned": false, - "senderName": "Overseerr" + "senderName": "Jellyseerr" } }, "discord": { diff --git a/overseerr-api.yml b/jellyseerr-api.yml similarity index 99% rename from overseerr-api.yml rename to jellyseerr-api.yml index a713a5a1..ddd94202 100644 --- a/overseerr-api.yml +++ b/jellyseerr-api.yml @@ -1,19 +1,19 @@ openapi: '3.0.2' info: - title: 'Overseerr API' + title: 'Jellyseerr API' version: '1.0.0' description: | - This is the documentation for the Overseerr API backend. + This is the documentation for the Jellyseerr API backend. Two primary authentication methods are supported: - **Cookie Authentication**: A valid sign-in to the `/auth/plex` or `/auth/local` will generate a valid authentication cookie. - - **API Key Authentication**: Sign-in is also possible by passing an `X-Api-Key` header along with a valid API Key generated by Overseerr. + - **API Key Authentication**: Sign-in is also possible by passing an `X-Api-Key` header along with a valid API Key generated by Jellyseerr. tags: - name: public description: Public API endpoints requiring no authentication. - name: settings - description: Endpoints related to Overseerr's settings and configuration. + description: Endpoints related to Jellyseerr's settings and configuration. - name: auth description: Endpoints related to logging in or out, and the currently authenticated user. - name: users @@ -160,7 +160,7 @@ components: example: en applicationTitle: type: string - example: Overseerr + example: Jellyseerr applicationUrl: type: string example: https://os.example.com @@ -1438,7 +1438,7 @@ components: example: no-reply@example.com senderName: type: string - example: Overseerr + example: Jellyseerr smtpHost: type: string example: 127.0.0.1 @@ -1969,8 +1969,8 @@ components: paths: /status: get: - summary: Get Overseerr status - description: Returns the current Overseerr status in a JSON object. + summary: Get Jellyseerr status + description: Returns the current Jellyseerr status in a JSON object. security: [] tags: - public diff --git a/package.json b/package.json index 74512020..ce508dd3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "postinstall": "node postinstall-win.js", - "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node -r tsconfig-paths/register --files --project server/tsconfig.json server/index.ts", + "dev": "nodemon -e ts --watch server --watch jellyseerr-api.yml -e .json,.ts,.yml -x ts-node -r tsconfig-paths/register --files --project server/tsconfig.json server/index.ts", "build:server": "tsc --project server/tsconfig.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && tsc-alias -p server/tsconfig.json", "build:next": "next build", "build": "pnpm build:next && pnpm build:server", diff --git a/public/images/overseerr_poster_not_found.png b/public/images/jellyseerr_poster_not_found.png similarity index 100% rename from public/images/overseerr_poster_not_found.png rename to public/images/jellyseerr_poster_not_found.png diff --git a/public/images/overseerr_poster_not_found_logo_center.png b/public/images/jellyseerr_poster_not_found_logo_center.png similarity index 100% rename from public/images/overseerr_poster_not_found_logo_center.png rename to public/images/jellyseerr_poster_not_found_logo_center.png diff --git a/public/images/overseerr_poster_not_found_logo_top.png b/public/images/jellyseerr_poster_not_found_logo_top.png similarity index 100% rename from public/images/overseerr_poster_not_found_logo_top.png rename to public/images/jellyseerr_poster_not_found_logo_top.png diff --git a/server/api/github.ts b/server/api/github.ts index c2c3fe6a..50027218 100644 --- a/server/api/github.ts +++ b/server/api/github.ts @@ -72,7 +72,7 @@ class GithubAPI extends ExternalAPI { ); } - public async getOverseerrReleases({ + public async getJellyseerrReleases({ take = 20, }: { take?: number; @@ -88,14 +88,14 @@ class GithubAPI extends ExternalAPI { return data; } catch (e) { logger.warn( - "Failed to retrieve GitHub releases. This may be an issue on GitHub's end. Overseerr can't check if it's on the latest version.", + "Failed to retrieve GitHub releases. This may be an issue on GitHub's end. Jellyseerr can't check if it's on the latest version.", { label: 'GitHub API', errorMessage: e.message } ); return []; } } - public async getOverseerrCommits({ + public async getJellyseerrCommits({ take = 20, branch = 'develop', }: { @@ -114,7 +114,7 @@ class GithubAPI extends ExternalAPI { return data; } catch (e) { logger.warn( - "Failed to retrieve GitHub commits. This may be an issue on GitHub's end. Overseerr can't check if it's on the latest version.", + "Failed to retrieve GitHub commits. This may be an issue on GitHub's end. Jellyseerr can't check if it's on the latest version.", { label: 'GitHub API', errorMessage: e.message } ); return []; diff --git a/server/api/plexapi.ts b/server/api/plexapi.ts index 977d367b..5007fe05 100644 --- a/server/api/plexapi.ts +++ b/server/api/plexapi.ts @@ -124,9 +124,9 @@ class PlexAPI { // }, options: { identifier: settings.clientId, - product: 'Overseerr', - deviceName: 'Overseerr', - platform: 'Overseerr', + product: 'Jellyseerr', + deviceName: 'Jellyseerr', + platform: 'Jellyseerr', }, }); } diff --git a/server/index.ts b/server/index.ts index e4e872ab..88baedb8 100644 --- a/server/index.ts +++ b/server/index.ts @@ -41,9 +41,9 @@ import path from 'path'; import swaggerUi from 'swagger-ui-express'; import YAML from 'yamljs'; -const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml'); +const API_SPEC_PATH = path.join(__dirname, '../jellyseerr-api.yml'); -logger.info(`Starting Overseerr version ${getAppVersion()}`); +logger.info(`Starting Jellyseerr version ${getAppVersion()}`); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index 7a3b9790..1d6485cc 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -188,7 +188,7 @@ class SlackAgent type: 'actions', elements: [ { - action_id: 'open-in-overseerr', + action_id: 'open-in-jellyseerr', type: 'button', url, text: { diff --git a/server/lib/watchlistsync.ts b/server/lib/watchlistsync.ts index 4919bf70..ed488767 100644 --- a/server/lib/watchlistsync.ts +++ b/server/lib/watchlistsync.ts @@ -130,7 +130,7 @@ class WatchlistSync { switch (e.constructor) { // During watchlist sync, these errors aren't necessarily - // a problem with Overseerr. Since we are auto syncing these constantly, it's + // a problem with Jellyseerr. Since we are auto syncing these constantly, it's // possible they are unexpectedly at their quota limit, for example. So we'll // instead log these as debug messages. case RequestPermissionError: diff --git a/server/logger.ts b/server/logger.ts index d5809a0e..4708dd20 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -43,14 +43,14 @@ const logger = winston.createLogger({ }), new winston.transports.DailyRotateFile({ filename: process.env.CONFIG_DIRECTORY - ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr-%DATE%.log` - : path.join(__dirname, '../config/logs/overseerr-%DATE%.log'), + ? `${process.env.CONFIG_DIRECTORY}/logs/jellyseerr-%DATE%.log` + : path.join(__dirname, '../config/logs/jellyseerr-%DATE%.log'), datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '7d', createSymlink: true, - symlinkName: 'overseerr.log', + symlinkName: 'jellyseerr.log', }), new winston.transports.DailyRotateFile({ filename: process.env.CONFIG_DIRECTORY diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 31c846ad..4e470831 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -158,7 +158,7 @@ authRoutes.post('/plex', async (req, res, next) => { }); } else { logger.info( - 'Sign-in attempt from Plex user with access to the media server; creating new Overseerr user', + 'Sign-in attempt from Plex user with access to the media server; creating new Jellyseerr user', { label: 'API', ip: req.ip, @@ -274,7 +274,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { if (user) { deviceId = user.jellyfinDeviceId ?? ''; } else { - deviceId = Buffer.from(`BOT_overseerr_${body.username ?? ''}`).toString( + deviceId = Buffer.from(`BOT_jellyseerr_${body.username ?? ''}`).toString( 'base64' ); } @@ -446,7 +446,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { }); } else if (!user) { logger.info( - 'Sign-in attempt from Jellyfin user with access to the media server; creating new Overseerr user', + 'Sign-in attempt from Jellyfin user with access to the media server; creating new Jellyseerr user', { label: 'API', ip: req.ip, @@ -584,7 +584,7 @@ authRoutes.post('/local', async (req, res, next) => { .getOne(); if (!user || !(await user.passwordMatch(body.password))) { - logger.warn('Failed sign-in attempt using invalid Overseerr password', { + logger.warn('Failed sign-in attempt using invalid Jellyseerr password', { label: 'API', ip: req.ip, email: body.email, @@ -674,7 +674,7 @@ authRoutes.post('/local', async (req, res, next) => { return res.status(200).json(user?.filter() ?? {}); } catch (e) { logger.error( - 'Something went wrong authenticating with Overseerr password', + 'Something went wrong authenticating with Jellyseerr password', { label: 'API', errorMessage: e.message, diff --git a/server/routes/index.ts b/server/routes/index.ts index f064e603..7d0ad5d8 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -55,7 +55,7 @@ router.get('/status', async (req, res) => { let commitsBehind = 0; if (currentVersion.startsWith('develop-') && commitTag !== 'local') { - const commits = await githubApi.getOverseerrCommits(); + const commits = await githubApi.getJellyseerrCommits(); if (commits.length) { const filteredCommits = commits.filter( @@ -74,7 +74,7 @@ router.get('/status', async (req, res) => { } } } else if (commitTag !== 'local') { - const releases = await githubApi.getOverseerrReleases(); + const releases = await githubApi.getJellyseerrReleases(); if (releases.length) { const latestVersion = releases[0]; @@ -403,7 +403,7 @@ router.get('/watchproviders/tv', async (req, res, next) => { router.get('/', (_req, res) => { return res.status(200).json({ - api: 'Overseerr API', + api: 'Jellyseerr API', version: '1.0', }); }); diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index ac89b048..ab6bd737 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -419,7 +419,7 @@ userSettingsRoutes.post<{ username: string; password: string }>( const hostname = getHostname(); const deviceId = Buffer.from( - `BOT_overseerr_${req.user.username ?? ''}` + `BOT_jellyseerr_${req.user.username ?? ''}` ).toString('base64'); const jellyfinserver = new JellyfinAPI(hostname, undefined, deviceId); diff --git a/src/components/Blacklist/index.tsx b/src/components/Blacklist/index.tsx index a752e95f..a8b8dae7 100644 --- a/src/components/Blacklist/index.tsx +++ b/src/components/Blacklist/index.tsx @@ -298,7 +298,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => { src={ title?.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/CollectionDetails/index.tsx b/src/components/CollectionDetails/index.tsx index d9c9a813..c56781e8 100644 --- a/src/components/CollectionDetails/index.tsx +++ b/src/components/CollectionDetails/index.tsx @@ -233,7 +233,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => { src={ data.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 5ceb5796..d41a6684 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -240,7 +240,7 @@ const IssueDetails = () => { src={ data.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx index 7fd58b5e..53c618fe 100644 --- a/src/components/IssueList/IssueItem/index.tsx +++ b/src/components/IssueList/IssueItem/index.tsx @@ -142,7 +142,7 @@ const IssueItem = ({ issue }: IssueItemProps) => { src={ title.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 72a598f0..3fb88ab3 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -505,7 +505,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { src={ data.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/PWAHeader/index.tsx b/src/components/PWAHeader/index.tsx index 0dde7e42..777b63e0 100644 --- a/src/components/PWAHeader/index.tsx +++ b/src/components/PWAHeader/index.tsx @@ -2,7 +2,7 @@ interface PWAHeaderProps { applicationTitle?: string; } -const PWAHeader = ({ applicationTitle = 'Overseerr' }: PWAHeaderProps) => { +const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => { return ( <> { src={ title.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 018fa915..3d64941e 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -453,7 +453,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { src={ title.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 28aae73a..4dd79524 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -441,7 +441,7 @@ const CollectionRequestModal = ({ src={ part.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/components/RequestModal/SearchByNameModal/index.tsx b/src/components/RequestModal/SearchByNameModal/index.tsx index 1b86b614..1aa55f3f 100644 --- a/src/components/RequestModal/SearchByNameModal/index.tsx +++ b/src/components/RequestModal/SearchByNameModal/index.tsx @@ -92,7 +92,7 @@ const SearchByNameModal = ({ {item.title}stdout, or in {appDataPath}/logs/overseerr.log.', + 'You can also view these logs directly via stdout, or in {appDataPath}/logs/jellyseerr.log.', time: 'Timestamp', level: 'Severity', label: 'Label', diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 6312994e..8b6d6874 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -352,7 +352,7 @@ const TitleCard = ({ src={ image ? `https://image.tmdb.org/t/p/w300_and_h450_face${image}` - : `/images/overseerr_poster_not_found_logo_top.png` + : `/images/jellyseerr_poster_not_found_logo_top.png` } style={{ width: '100%', height: '100%', objectFit: 'cover' }} fill diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 77349b5e..b4f0389f 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -547,7 +547,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { src={ data.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}` - : '/images/overseerr_poster_not_found.png' + : '/images/jellyseerr_poster_not_found.png' } alt="" sizes="100vw" diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index ee94e092..a1b69ae2 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -10,7 +10,7 @@ export interface SettingsContextProps { const defaultSettings = { initialized: false, - applicationTitle: 'Overseerr', + applicationTitle: 'Jellyseerr', applicationUrl: '', hideAvailable: false, localLogin: true, diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index bd2ce864..73dfc7b8 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -904,7 +904,7 @@ "components.Settings.SettingsLogs.level": "Severity", "components.Settings.SettingsLogs.logDetails": "Log Details", "components.Settings.SettingsLogs.logs": "Logs", - "components.Settings.SettingsLogs.logsDescription": "You can also view these logs directly via stdout, or in {appDataPath}/logs/overseerr.log.", + "components.Settings.SettingsLogs.logsDescription": "You can also view these logs directly via stdout, or in {appDataPath}/logs/jellyseerr.log.", "components.Settings.SettingsLogs.message": "Message", "components.Settings.SettingsLogs.pauseLogs": "Pause", "components.Settings.SettingsLogs.resumeLogs": "Resume", diff --git a/src/utils/plex.ts b/src/utils/plex.ts index 448da94a..5c0d1906 100644 --- a/src/utils/plex.ts +++ b/src/utils/plex.ts @@ -57,14 +57,14 @@ class PlexOAuth { const browser = Bowser.getParser(window.navigator.userAgent); this.plexHeaders = { Accept: 'application/json', - 'X-Plex-Product': 'Overseerr', + 'X-Plex-Product': 'Jellyseerr', 'X-Plex-Version': 'Plex OAuth', 'X-Plex-Client-Identifier': clientId, 'X-Plex-Model': 'Plex OAuth', 'X-Plex-Platform': browser.getBrowserName(), 'X-Plex-Platform-Version': browser.getBrowserVersion(), 'X-Plex-Device': browser.getOSName(), - 'X-Plex-Device-Name': `${browser.getBrowserName()} (Overseerr)`, + 'X-Plex-Device-Name': `${browser.getBrowserName()} (Jellyseerr)`, 'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height, 'X-Plex-Language': 'en', From 4eddbaa71b7972b6db33976102501fb8b6333206 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Wed, 26 Feb 2025 10:47:11 +0100 Subject: [PATCH 53/65] fix(watchlist): disable Jellyseerr's watchlist for Plex users (#1398) This PR resolves a conflict between the Plex and Jellyseerr watchlists by deactivating the Jellyseerr watchlist for Plex. fix #1344 --- server/routes/discover.ts | 4 +- src/components/MovieDetails/index.tsx | 73 ++++++++++++++------------- src/components/TitleCard/index.tsx | 62 ++++++++++++----------- src/components/TvDetails/index.tsx | 73 ++++++++++++++------------- 4 files changed, 111 insertions(+), 101 deletions(-) diff --git a/server/routes/discover.ts b/server/routes/discover.ts index 4bb12740..79ae7f28 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -837,7 +837,8 @@ discoverRoutes.get, WatchlistResponse>( select: ['id', 'plexToken'], }); - if (activeUser) { + if (activeUser && !activeUser?.plexToken) { + // Non-Plex users can only see their own watchlist const [result, total] = await getRepository(Watchlist).findAndCount({ where: { requestedBy: { id: activeUser?.id } }, relations: { @@ -866,6 +867,7 @@ discoverRoutes.get, WatchlistResponse>( }); } + // List watchlist from Plex const plexTV = new PlexTvAPI(activeUser.plexToken); const watchlist = await plexTV.getWatchlist({ offset }); diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 3fb88ab3..2340cb2e 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -25,7 +25,7 @@ import StatusBadge from '@app/components/StatusBadge'; import useDeepLinks from '@app/hooks/useDeepLinks'; import useLocale from '@app/hooks/useLocale'; import useSettings from '@app/hooks/useSettings'; -import { Permission, useUser } from '@app/hooks/useUser'; +import { Permission, UserType, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; import { sortCrewPriority } from '@app/utils/creditHelpers'; @@ -594,42 +594,45 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { )} - {data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && ( - <> - {toggleWatchlist ? ( - - - - ) : ( - - + + ) : ( + - {isUpdating ? ( - - ) : ( - - )} - - - )} - - )} + + + )} + + )}
    - {showDetail && currentStatus !== MediaStatus.BLACKLISTED && ( -
    - {toggleWatchlist ? ( - - ) : ( - - )} - {showHideButton && - currentStatus !== MediaStatus.PROCESSING && - currentStatus !== MediaStatus.AVAILABLE && - currentStatus !== MediaStatus.PARTIALLY_AVAILABLE && - currentStatus !== MediaStatus.PENDING && ( + {showDetail && + currentStatus !== MediaStatus.BLACKLISTED && + user?.userType !== UserType.PLEX && ( +
    + {toggleWatchlist ? ( + ) : ( + )} -
    - )} + {showHideButton && + currentStatus !== MediaStatus.PROCESSING && + currentStatus !== MediaStatus.AVAILABLE && + currentStatus !== MediaStatus.PARTIALLY_AVAILABLE && + currentStatus !== MediaStatus.PENDING && ( + + )} +
    + )} {showDetail && showHideButton && currentStatus == MediaStatus.BLACKLISTED && ( diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index b4f0389f..ec27fba1 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -28,7 +28,7 @@ import Season from '@app/components/TvDetails/Season'; import useDeepLinks from '@app/hooks/useDeepLinks'; import useLocale from '@app/hooks/useLocale'; import useSettings from '@app/hooks/useSettings'; -import { Permission, useUser } from '@app/hooks/useUser'; +import { Permission, UserType, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import Error from '@app/pages/_error'; import { sortCrewPriority } from '@app/utils/creditHelpers'; @@ -636,42 +636,45 @@ const TvDetails = ({ tv }: TvDetailsProps) => { )} - {data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && ( - <> - {toggleWatchlist ? ( - - - - ) : ( - - + + ) : ( + - {isUpdating ? ( - - ) : ( - - )} - - - )} - - )} + + + )} + + )} Date: Thu, 27 Feb 2025 16:40:13 +0800 Subject: [PATCH 54/65] docs: add gauthier-th as a contributor for maintenance (#1408) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 5468b39c..610aebc9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -338,7 +338,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/37781713?v=4", "profile": "https://gauthierth.fr/", "contributions": [ - "code" + "code", + "maintenance" ] }, { diff --git a/README.md b/README.md index 6ed6bff7..4cc83f49 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon Joshua M. Boniface
    Joshua M. Boniface

    💻 - Gauthier
    Gauthier

    💻 + Gauthier
    Gauthier

    💻 🚧 Kara
    Kara

    🚇 Joaquin Olivero
    Joaquin Olivero

    💻 Julian Behr
    Julian Behr

    🌍 From 3b4d6bf5b8f3df295a4f204c56126aadd278e22f Mon Sep 17 00:00:00 2001 From: Gauthier Date: Thu, 27 Feb 2025 12:13:14 +0100 Subject: [PATCH 55/65] chore: merge upstream (#1404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(pushover): attach image to pushover notification payload (#3701) * fix: api language query parameter (#3720) * docs: add j0srisk as a contributor for code (#3745) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(tooltip): add tooltip to display exact time on date hover (#3773) Co-authored-by: Loetwiek * docs: add Loetwiek as a contributor for code (#3776) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix(ui): ensure title fits into the `view collection` box (#3696) * fix(docs): correct openapi docs minor issues (#3648) * docs: add Fuochi as a contributor for doc (#3826) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: translations update from Hosted Weblate (#3597) * feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (1232 of 1234 strings) Co-authored-by: Cleiton Carvalho Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (German) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (German) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate Co-authored-by: Nandor Rusz Co-authored-by: Thomas Schöneberg Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1236 of 1236 strings) feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Anders Ecklon Co-authored-by: Hosted Weblate Co-authored-by: Kenneth Hansen Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Greek) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Greek) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: BeardedWatermelon Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/el/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 99.5% (1234 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate Co-authored-by: SoundwaveUwU Co-authored-by: SoundwaveUwU Co-authored-by: Димитър Мазнеков (Topper) Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Romanian) Currently translated at 37.1% (461 of 1240 strings) feat(lang): translated using Weblate (Romanian) Currently translated at 37.0% (459 of 1240 strings) feat(lang): translated using Weblate (Romanian) Currently translated at 34.8% (432 of 1240 strings) Co-authored-by: Don Cezar Co-authored-by: Dragos Co-authored-by: Eduard Oancea Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ro/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 57.4% (712 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 13.2% (164 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 4.8% (60 of 1240 strings) feat(lang): added translation using Weblate (Bulgarian) Co-authored-by: Hosted Weblate Co-authored-by: sct Co-authored-by: Димитър Мазнеков (Topper) Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/bg/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 97.9% (1215 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 82.0% (1017 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 72.9% (905 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 72.9% (905 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 71.3% (885 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 64.9% (805 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 64.4% (799 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 63.8% (792 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 63.7% (791 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 57.5% (714 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 49.9% (619 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 35.9% (446 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 35.9% (446 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 32.1% (399 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 24.6% (306 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 18.9% (235 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 17.5% (217 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 17.3% (215 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 8.0% (100 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 3.3% (41 of 1240 strings) feat(lang): added translation using Weblate (Ukrainian) Co-authored-by: Hosted Weblate Co-authored-by: Michael Michael Co-authored-by: sct Co-authored-by: Сергій Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/uk/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: dtalens Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ca/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Czech) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Czech) Currently translated at 99.6% (1236 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Karel Krýda Co-authored-by: Smexhy Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/cs/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Croatian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.8% (1238 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.8% (1238 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.6% (1236 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.5% (1235 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.5% (1235 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 97.5% (1210 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.5% (1185 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.6% (1182 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.6% (1182 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.2% (1177 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.2% (1177 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 94.3% (1166 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 91.7% (1134 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 91.7% (1134 of 1236 strings) Co-authored-by: Bruno Ševčenko Co-authored-by: Hosted Weblate Co-authored-by: Milo Ivir Co-authored-by: Stjepan Co-authored-by: lpispek Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Hungarian) Currently translated at 91.3% (1133 of 1240 strings) feat(lang): translated using Weblate (Hungarian) Currently translated at 89.3% (1108 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Levente Szajkó Co-authored-by: Nandor Rusz Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hu/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Hebrew) Currently translated at 13.9% (172 of 1236 strings) Co-authored-by: Hosted Weblate Co-authored-by: osh Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/he/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Polish) Currently translated at 99.1% (1225 of 1236 strings) Co-authored-by: Eryk Michalak Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Italian) Currently translated at 92.8% (1148 of 1236 strings) Co-authored-by: Francesco Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Fhd-pro Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ar/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate Co-authored-by: Kobe Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: Hosted Weblate Co-authored-by: gallegonovato Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1236 of 1236 strings) feat(lang): translated using Weblate (French) Currently translated at 99.9% (1235 of 1236 strings) feat(lang): translated using Weblate (French) Currently translated at 99.9% (1235 of 1236 strings) Co-authored-by: Baptiste Co-authored-by: Dimitri Co-authored-by: Hosted Weblate Co-authored-by: Maxime Lafarie Co-authored-by: Miguel Co-authored-by: asurare Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: Hosted Weblate Co-authored-by: Per Erik Co-authored-by: Shjosan Co-authored-by: bittin1ddc447d824349b2 Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Finnish) Currently translated at 2.6% (33 of 1240 strings) feat(lang): added translation using Weblate (Finnish) Co-authored-by: Eero Konttaniemi Co-authored-by: Hosted Weblate Co-authored-by: sct Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fi/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Serbian) Currently translated at 50.8% (630 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Milan Smudja Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Developer J Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ko/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Haohao Zhang Co-authored-by: Hosted Weblate Co-authored-by: lkw123 Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/ Translation: Overseerr/Overseerr Frontend --------- Co-authored-by: Cleiton Carvalho Co-authored-by: Nandor Rusz Co-authored-by: Thomas Schöneberg Co-authored-by: Anders Ecklon Co-authored-by: Kenneth Hansen Co-authored-by: BeardedWatermelon Co-authored-by: SoundwaveUwU Co-authored-by: SoundwaveUwU Co-authored-by: Димитър Мазнеков (Topper) Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Co-authored-by: Don Cezar Co-authored-by: Dragos Co-authored-by: Eduard Oancea Co-authored-by: sct Co-authored-by: Michael Michael Co-authored-by: Сергій Co-authored-by: dtalens Co-authored-by: Karel Krýda Co-authored-by: Smexhy Co-authored-by: Bruno Ševčenko Co-authored-by: Milo Ivir Co-authored-by: Stjepan Co-authored-by: lpispek Co-authored-by: Levente Szajkó Co-authored-by: osh Co-authored-by: Eryk Michalak Co-authored-by: Francesco Co-authored-by: Fhd-pro Co-authored-by: Kobe Co-authored-by: gallegonovato Co-authored-by: Baptiste Co-authored-by: Dimitri Co-authored-by: Maxime Lafarie Co-authored-by: Miguel Co-authored-by: asurare Co-authored-by: Per Erik Co-authored-by: Shjosan Co-authored-by: bittin1ddc447d824349b2 Co-authored-by: Eero Konttaniemi Co-authored-by: Milan Smudja Co-authored-by: Developer J Co-authored-by: Haohao Zhang Co-authored-by: lkw123 * feat(lang): add lang config for Bulgarian, Finnish, Ukrainian, Indonesian, Slovak, Turkish and Maori (#3834) * fix: correct deeplinks on iPad (#3883) * feat(studios): add a24 to studios list (#3902) * docs: add demrich as a contributor for code (#3906) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(watchlist): Cache watchlist requests with matching E-Tags (#3901) * perf(watchlist): add E-Tag caching to Plex watchlist requests * refactor(watchlist): increase frequency of watchlist requests * fix: sync watchlist every 3 min instead of 3 sec * docs: add maxnatamo as a contributor for code (#3907) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(plex): refresh token schedule (#3875) * feat: refresh token schedule fix #3861 * fix(i18n): add i18n message * refactor(plextv): use randomUUID crypto instead custom function * docs: add DamsDev1 as a contributor for code (#3924) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix: correct icon showing on certain phones when not pulled (#3939) * feat: add support for requesting "Specials" for TV Shows (#3724) * feat: add support for requesting "Specials" for TV Shows This commit is responsible for adding support in Overseerr for requesting "Special" episodes for TV Shows. This request has become especially pertinent when you consider shows like "Doctor Who". These shows have Specials that are critical to understanding the plot of a TV show. fix #779 * chore(yarn.lock): undo inappropriate changes to yarn.lock I was informed by @sct in a comment on the #3724 PR that it was not appropriate to commit the changes that ended up being made to the yarn.lock file. This commit is responsible, then, for undoing the changes to the yarn.lock file that ended up being submitted. * refactor: change loose equality to strict equality I received a comment from OwsleyJr pointing out that we are using loose equality when we could alternatively just be using strict equality to increase the robustness of our code. This commit does exactly that by squashing out previous usages of loose equality in my commits and replacing them with strict equality * refactor: move 'Specials' string to a global message Owsley pointed out that we are redefining the 'Specials' string multiple times throughout this PR. Instead, we can just move it as a global message. This commit does exactly that. It squashes out and previous declarations of the 'Specials' string inside the src files, and moves it directly to the global messages file. * docs: add AhmedNSidd as a contributor for code (#3964) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(lang): Translations update from Hosted Weblate (#3835) * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Димитър Мазнеков (Topper) Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/bg/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Michael Michael Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/uk/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Hosted Weblate Co-authored-by: dtalens Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ca/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Hungarian) Currently translated at 99.2% (1231 of 1240 strings) Co-authored-by: Dargo Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hu/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Polish) Currently translated at 98.8% (1227 of 1241 strings) Co-authored-by: Hosted Weblate Co-authored-by: senza Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Hosted Weblate Co-authored-by: Robin Van de Vyvere Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Frostar Co-authored-by: Hosted Weblate Co-authored-by: gallegonovato Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (French) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Nackophilz Co-authored-by: TayZ3r Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Per Erik Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Finnish) Currently translated at 2.9% (36 of 1240 strings) Co-authored-by: Oskari Lavinto Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fi/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Albanian) Currently translated at 95.8% (1189 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: W L Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sq/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Hyun Lee Co-authored-by: cutiekeek Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ko/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Portuguese (Portugal)) Currently translated at 98.4% (1221 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: Rafael Souto Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_PT/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional Han script)) Currently translated at 99.9% (1239 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Chinese (Traditional Han script)) Currently translated at 98.2% (1219 of 1241 strings) Co-authored-by: Hosted Weblate Co-authored-by: Marc Lerno Co-authored-by: dtalens Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Norwegian Bokmål) Currently translated at 89.9% (1115 of 1240 strings) Co-authored-by: Hosted Weblate Co-authored-by: exentler Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nb_NO/ Translation: Overseerr/Overseerr Frontend --------- Co-authored-by: Димитър Мазнеков (Topper) Co-authored-by: Michael Michael Co-authored-by: dtalens Co-authored-by: Dargo Co-authored-by: senza Co-authored-by: Robin Van de Vyvere Co-authored-by: Frostar Co-authored-by: gallegonovato Co-authored-by: Nackophilz Co-authored-by: TayZ3r Co-authored-by: Per Erik Co-authored-by: Oskari Lavinto Co-authored-by: W L Co-authored-by: Hyun Lee Co-authored-by: cutiekeek Co-authored-by: Rafael Souto Co-authored-by: Marc Lerno Co-authored-by: exentler * feat(ui): prevent password manager interference & improve service links (#3989) * docs: add s0up4200 as a contributor for code (#4047) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix(ui): update Plex Logo (#3955) * docs: add JackW6809 as a contributor for code (#4048) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: requests/issues menu count (#3470) * feat: request and issue count added to sidebar/mobile menu * fix: added permission check for count visibility * refactor: modified badge design for count * fix: properly update issue and request counts in certain scenarios (#4051) * fix: center count badge on sidebar and mobile menu (#4052) * fix: request english trailers as a fallback when using other languages (#4009) Co-authored-by: Stancu Florin * docs: add StancuFlorin as a contributor for code (#4053) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: added the PWA badge indicator for requests pending (#3411) refactor: removed unnecessary code when sending web push notification fix: moved all notify user logic into webpush refactor: n refactor: remove all unnecessary prettier changes fix: n fix: n fix: n fix: n fix: increment sw version fix: n --------- Co-authored-by: Isaac M Co-authored-by: Joseph Risk Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Loetwiek <79059734+Loetwiek@users.noreply.github.com> Co-authored-by: Loetwiek Co-authored-by: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Co-authored-by: Fuochi Co-authored-by: Weblate (bot) Co-authored-by: Cleiton Carvalho Co-authored-by: Nandor Rusz Co-authored-by: Thomas Schöneberg Co-authored-by: Anders Ecklon Co-authored-by: Kenneth Hansen Co-authored-by: BeardedWatermelon Co-authored-by: SoundwaveUwU Co-authored-by: SoundwaveUwU Co-authored-by: Димитър Мазнеков (Topper) Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Co-authored-by: Don Cezar Co-authored-by: Dragos Co-authored-by: Eduard Oancea Co-authored-by: sct Co-authored-by: Michael Michael Co-authored-by: Сергій Co-authored-by: dtalens Co-authored-by: Karel Krýda Co-authored-by: Smexhy Co-authored-by: Bruno Ševčenko Co-authored-by: Milo Ivir Co-authored-by: Stjepan Co-authored-by: lpispek Co-authored-by: Levente Szajkó Co-authored-by: osh Co-authored-by: Eryk Michalak Co-authored-by: Francesco Co-authored-by: Fhd-pro Co-authored-by: Kobe Co-authored-by: gallegonovato Co-authored-by: Baptiste Co-authored-by: Dimitri Co-authored-by: Maxime Lafarie Co-authored-by: Miguel Co-authored-by: asurare Co-authored-by: Per Erik Co-authored-by: Shjosan Co-authored-by: bittin1ddc447d824349b2 Co-authored-by: Eero Konttaniemi Co-authored-by: Milan Smudja Co-authored-by: Developer J Co-authored-by: Haohao Zhang Co-authored-by: lkw123 Co-authored-by: Jordan Jones Co-authored-by: Brandon Cohen Co-authored-by: David Emrich Co-authored-by: Max T. Kristiansen Co-authored-by: Damien Fajole <60252259+DamsDev1@users.noreply.github.com> Co-authored-by: Ahmed Siddiqui <36286128+AhmedNSidd@users.noreply.github.com> Co-authored-by: Dargo Co-authored-by: senza Co-authored-by: Robin Van de Vyvere Co-authored-by: Frostar Co-authored-by: Nackophilz Co-authored-by: TayZ3r Co-authored-by: Oskari Lavinto Co-authored-by: W L Co-authored-by: Hyun Lee Co-authored-by: cutiekeek Co-authored-by: Rafael Souto Co-authored-by: Marc Lerno Co-authored-by: exentler Co-authored-by: soup Co-authored-by: JackOXI <53652452+JackW6809@users.noreply.github.com> Co-authored-by: Stancu Florin Co-authored-by: Stancu Florin Co-authored-by: Brandon Cohen --- .all-contributorsrc | 84 ++++++++++- README.md | 4 +- public/sw.js | 21 ++- server/api/themoviedb/index.ts | 2 + server/lib/notifications/agents/agent.ts | 2 + server/lib/notifications/agents/webpush.ts | 133 +++++++++++++----- src/assets/services/plex.svg | 124 ++++++---------- .../Common/SensitiveInput/index.tsx | 4 + src/components/IssueDetails/index.tsx | 4 +- .../IssueModal/CreateIssueModal/index.tsx | 4 +- src/components/Layout/MobileMenu/index.tsx | 73 +++++++++- src/components/Layout/Sidebar/index.tsx | 73 ++++++++-- src/components/Layout/index.tsx | 30 +++- src/components/Login/LocalLogin.tsx | 3 + src/components/RequestBlock/index.tsx | 3 + src/components/RequestButton/index.tsx | 3 + src/components/RequestCard/index.tsx | 3 + .../RequestList/RequestItem/index.tsx | 5 +- .../RequestModal/CollectionRequestModal.tsx | 14 +- .../RequestModal/MovieRequestModal.tsx | 14 +- .../RequestModal/TvRequestModal.tsx | 3 + .../Notifications/NotificationsDiscord.tsx | 4 + .../Notifications/NotificationsEmail.tsx | 40 ++++-- .../Notifications/NotificationsTelegram.tsx | 22 ++- src/components/Settings/RadarrModal/index.tsx | 5 +- src/components/Settings/SettingsPlex.tsx | 13 +- src/components/Settings/SettingsServices.tsx | 11 +- src/components/Settings/SonarrModal/index.tsx | 5 +- src/components/UserList/index.tsx | 4 + src/pages/_app.tsx | 30 ++++ 30 files changed, 580 insertions(+), 160 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 610aebc9..8dbd97d6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -94,7 +94,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/345752?v=4", "profile": "https://github.com/jab416171", "contributions": [ - "doc" + "doc", + "code" ] }, { @@ -620,6 +621,87 @@ "contributions": [ "code" ] + }, + { + "login": "j0srisk", + "name": "Joseph Risk", + "avatar_url": "https://avatars.githubusercontent.com/u/18372584?v=4", + "profile": "http://josephrisk.com", + "contributions": [ + "code" + ] + }, + { + "login": "Loetwiek", + "name": "Loetwiek", + "avatar_url": "https://avatars.githubusercontent.com/u/79059734?v=4", + "profile": "https://github.com/Loetwiek", + "contributions": [ + "code" + ] + }, + { + "login": "Fuochi", + "name": "Fuochi", + "avatar_url": "https://avatars.githubusercontent.com/u/4720478?v=4", + "profile": "https://github.com/Fuochi", + "contributions": [ + "doc" + ] + }, + { + "login": "demrich", + "name": "David Emrich", + "avatar_url": "https://avatars.githubusercontent.com/u/30092389?v=4", + "profile": "https://github.com/demrich", + "contributions": [ + "code" + ] + }, + { + "login": "maxnatamo", + "name": "Max T. Kristiansen", + "avatar_url": "https://avatars.githubusercontent.com/u/5898152?v=4", + "profile": "https://maxtrier.dk", + "contributions": [ + "code" + ] + }, + { + "login": "DamsDev1", + "name": "Damien Fajole", + "avatar_url": "https://avatars.githubusercontent.com/u/60252259?v=4", + "profile": "https://damsdev.me", + "contributions": [ + "code" + ] + }, + { + "login": "AhmedNSidd", + "name": "Ahmed Siddiqui", + "avatar_url": "https://avatars.githubusercontent.com/u/36286128?v=4", + "profile": "https://github.com/AhmedNSidd", + "contributions": [ + "code" + ] + }, + { + "login": "JackW6809", + "name": "JackOXI", + "avatar_url": "https://avatars.githubusercontent.com/u/53652452?v=4", + "profile": "https://github.com/JackW6809", + "contributions": [ + "code" + ] + }, + { + "login": "StancuFlorin", + "name": "Stancu Florin", + "avatar_url": "https://avatars.githubusercontent.com/u/1199404?v=4", + "profile": "http://indicus.ro", + "contributions": [ + "code" + ] } ] } diff --git a/README.md b/README.md index 4cc83f49..8bb9c7c9 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon byakurau
    byakurau

    🌍 miknii
    miknii

    🌍 Mackenzie
    Mackenzie

    💻 - soup
    soup

    📖 + soup
    soup

    📖 💻 ceptonit
    ceptonit

    📖 aedelbro
    aedelbro

    💻 @@ -321,6 +321,8 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon Ahmed Siddiqui
    Ahmed Siddiqui

    💻 + JackOXI
    JackOXI

    💻 + Stancu Florin
    Stancu Florin

    💻 diff --git a/public/sw.js b/public/sw.js index 6a89315a..3aec6343 100644 --- a/public/sw.js +++ b/public/sw.js @@ -3,7 +3,7 @@ // previously cached resources to be updated from the network. // This variable is intentionally declared and unused. // eslint-disable-next-line @typescript-eslint/no-unused-vars -const OFFLINE_VERSION = 3; +const OFFLINE_VERSION = 4; const CACHE_NAME = 'offline'; // Customize this with a different URL if needed. const OFFLINE_URL = '/offline.html'; @@ -107,6 +107,25 @@ self.addEventListener('push', (event) => { ); } + // Set the badge with the amount of pending requests + // Only update the badge if the payload confirms they are the admin + if ( + (payload.notificationType === 'MEDIA_APPROVED' || + payload.notificationType === 'MEDIA_DECLINED') && + payload.isAdmin + ) { + if ('setAppBadge' in navigator) { + navigator.setAppBadge(payload.pendingRequestsCount); + } + return; + } + + if (payload.notificationType === 'MEDIA_PENDING') { + if ('setAppBadge' in navigator) { + navigator.setAppBadge(payload.pendingRequestsCount); + } + } + event.waitUntil(self.registration.showNotification(payload.subject, options)); }); diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index 5cf449ea..cb4d4071 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -256,6 +256,7 @@ class TheMovieDb extends ExternalAPI { language, append_to_response: 'credits,external_ids,videos,keywords,release_dates,watch/providers', + include_video_language: language + ', en', }, 43200 ); @@ -280,6 +281,7 @@ class TheMovieDb extends ExternalAPI { language, append_to_response: 'aggregate_credits,credits,external_ids,keywords,videos,content_ratings,watch/providers', + include_video_language: language + ', en', }, 43200 ); diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts index d2b0b165..952e1acf 100644 --- a/server/lib/notifications/agents/agent.ts +++ b/server/lib/notifications/agents/agent.ts @@ -19,6 +19,8 @@ export interface NotificationPayload { request?: MediaRequest; issue?: Issue; comment?: IssueComment; + pendingRequestsCount?: number; + isAdmin?: boolean; } export abstract class BaseAgent { diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index 275a77e8..143961ec 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -1,6 +1,7 @@ import { IssueType, IssueTypeName } from '@server/constants/issue'; -import { MediaType } from '@server/constants/media'; +import { MediaRequestStatus, MediaType } from '@server/constants/media'; import { getRepository } from '@server/datasource'; +import MediaRequest from '@server/entity/MediaRequest'; import { User } from '@server/entity/User'; import { UserPushSubscription } from '@server/entity/UserPushSubscription'; import type { NotificationAgentConfig } from '@server/lib/settings'; @@ -19,6 +20,8 @@ interface PushNotificationPayload { actionUrl?: string; actionUrlTitle?: string; requestId?: number; + pendingRequestsCount?: number; + isAdmin?: boolean; } class WebPushAgent @@ -129,6 +132,8 @@ class WebPushAgent requestId: payload.request?.id, actionUrl, actionUrlTitle, + pendingRequestsCount: payload.pendingRequestsCount, + isAdmin: payload.isAdmin, }; } @@ -152,6 +157,51 @@ class WebPushAgent const mainUser = await userRepository.findOne({ where: { id: 1 } }); + const requestRepository = getRepository(MediaRequest); + + const pendingRequests = await requestRepository.find({ + where: { status: MediaRequestStatus.PENDING }, + }); + + const webPushNotification = async ( + pushSub: UserPushSubscription, + notificationPayload: Buffer + ) => { + logger.debug('Sending web push notification', { + label: 'Notifications', + recipient: pushSub.user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await webpush.sendNotification( + { + endpoint: pushSub.endpoint, + keys: { + auth: pushSub.auth, + p256dh: pushSub.p256dh, + }, + }, + notificationPayload + ); + } catch (e) { + logger.error( + 'Error sending web push notification; removing subscription', + { + label: 'Notifications', + recipient: pushSub.user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + } + ); + + // Failed to send notification so we need to remove the subscription + userPushSubRepository.remove(pushSub); + } + }; + if ( payload.notifyUser && // Check if user has webpush notifications enabled and fallback to true if undefined @@ -169,7 +219,11 @@ class WebPushAgent pushSubs.push(...notifySubs); } - if (payload.notifyAdmin) { + if ( + payload.notifyAdmin || + type === Notification.MEDIA_APPROVED || + type === Notification.MEDIA_DECLINED + ) { const users = await userRepository.find(); const manageUsers = users.filter( @@ -192,7 +246,42 @@ class WebPushAgent }) .getMany(); - pushSubs.push(...allSubs); + // We only want to send the custom notification when type is approved or declined + // Otherwise, default to the normal notification + if ( + type === Notification.MEDIA_APPROVED || + type === Notification.MEDIA_DECLINED + ) { + if (mainUser && allSubs.length > 0) { + webpush.setVapidDetails( + `mailto:${mainUser.email}`, + settings.vapidPublic, + settings.vapidPrivate + ); + + // Custom payload only for updating the app badge + const notificationBadgePayload = Buffer.from( + JSON.stringify( + this.getNotificationPayload(type, { + subject: payload.subject, + notifySystem: false, + notifyAdmin: true, + isAdmin: true, + pendingRequestsCount: pendingRequests.length, + }) + ), + 'utf-8' + ); + + await Promise.all( + allSubs.map(async (sub) => { + webPushNotification(sub, notificationBadgePayload); + }) + ); + } + } else { + pushSubs.push(...allSubs); + } } if (mainUser && pushSubs.length > 0) { @@ -202,6 +291,10 @@ class WebPushAgent settings.vapidPrivate ); + if (type === Notification.MEDIA_PENDING) { + payload = { ...payload, pendingRequestsCount: pendingRequests.length }; + } + const notificationPayload = Buffer.from( JSON.stringify(this.getNotificationPayload(type, payload)), 'utf-8' @@ -209,39 +302,7 @@ class WebPushAgent await Promise.all( pushSubs.map(async (sub) => { - logger.debug('Sending web push notification', { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - }); - - try { - await webpush.sendNotification( - { - endpoint: sub.endpoint, - keys: { - auth: sub.auth, - p256dh: sub.p256dh, - }, - }, - notificationPayload - ); - } catch (e) { - logger.error( - 'Error sending web push notification; removing subscription', - { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - errorMessage: e.message, - } - ); - - // Failed to send notification so we need to remove the subscription - userPushSubRepository.remove(sub); - } + webPushNotification(sub, notificationPayload); }) ); } diff --git a/src/assets/services/plex.svg b/src/assets/services/plex.svg index 14c5abd9..53f28d1c 100644 --- a/src/assets/services/plex.svg +++ b/src/assets/services/plex.svg @@ -1,85 +1,43 @@ - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Common/SensitiveInput/index.tsx b/src/components/Common/SensitiveInput/index.tsx index ae11b951..9a92d254 100644 --- a/src/components/Common/SensitiveInput/index.tsx +++ b/src/components/Common/SensitiveInput/index.tsx @@ -25,6 +25,10 @@ const SensitiveInput = ({ as = 'input', ...props }: SensitiveInputProps) => { return ( <> { autoDismiss: true, }); revalidateIssue(); + mutate('/api/v1/issue/count'); } catch (e) { addToast(intl.formatMessage(messages.toaststatusupdatefailed), { appearance: 'error', @@ -169,6 +170,7 @@ const IssueDetails = () => { method: 'DELETE', }); if (!res.ok) throw new Error(); + mutate('/api/v1/issue/count'); addToast(intl.formatMessage(messages.toastissuedeleted), { appearance: 'success', diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx index 8d880385..58836ef8 100644 --- a/src/components/IssueModal/CreateIssueModal/index.tsx +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -15,7 +15,7 @@ import { Field, Formik } from 'formik'; import Link from 'next/link'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; const messages = defineMessages('components.IssueModal.CreateIssueModal', { @@ -138,6 +138,8 @@ const CreateIssueModal = ({ autoDismiss: true, } ); + + mutate('/api/v1/issue/count'); } if (onCancel) { diff --git a/src/components/Layout/MobileMenu/index.tsx b/src/components/Layout/MobileMenu/index.tsx index fe1e2e40..52e84d3d 100644 --- a/src/components/Layout/MobileMenu/index.tsx +++ b/src/components/Layout/MobileMenu/index.tsx @@ -1,3 +1,4 @@ +import Badge from '@app/components/Common/Badge'; import { menuMessages } from '@app/components/Layout/Sidebar'; import useClickOutside from '@app/hooks/useClickOutside'; import { Permission, useUser } from '@app/hooks/useUser'; @@ -26,9 +27,16 @@ import { } from '@heroicons/react/24/solid'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { cloneElement, useRef, useState } from 'react'; +import { cloneElement, useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; +interface MobileMenuProps { + pendingRequestsCount: number; + openIssuesCount: number; + revalidateIssueCount: () => void; + revalidateRequestsCount: () => void; +} + interface MenuLink { href: string; svgIcon: JSX.Element; @@ -41,7 +49,12 @@ interface MenuLink { dataTestId?: string; } -const MobileMenu = () => { +const MobileMenu = ({ + pendingRequestsCount, + openIssuesCount, + revalidateIssueCount, + revalidateRequestsCount, +}: MobileMenuProps) => { const ref = useRef(null); const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); @@ -139,6 +152,21 @@ const MobileMenu = () => { }) ); + useEffect(() => { + if (openIssuesCount) { + revalidateIssueCount(); + } + + if (pendingRequestsCount) { + revalidateRequestsCount(); + } + }, [ + revalidateIssueCount, + revalidateRequestsCount, + pendingRequestsCount, + openIssuesCount, + ]); + return (
    { { @@ -174,7 +202,25 @@ const MobileMenu = () => { {cloneElement(isActive ? link.svgIconSelected : link.svgIcon, { className: 'h-5 w-5', })} - {link.content} + {link.content} + {link.href === '/requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
    + + {pendingRequestsCount} + +
    + )} + {link.href === '/issues' && + openIssuesCount > 0 && + hasPermission(Permission.MANAGE_ISSUES) && ( +
    + + {openIssuesCount} + +
    + )} ); })} @@ -190,7 +236,7 @@ const MobileMenu = () => { @@ -200,6 +246,23 @@ const MobileMenu = () => { className: 'h-6 w-6', } )} + {link.href === '/requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
    + + {pendingRequestsCount > 99 + ? '99+' + : pendingRequestsCount} + +
    + )} ); })} diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index a947e262..d578bef8 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -1,3 +1,4 @@ +import Badge from '@app/components/Common/Badge'; import UserWarnings from '@app/components/Layout/UserWarnings'; import VersionStatus from '@app/components/Layout/VersionStatus'; import useClickOutside from '@app/hooks/useClickOutside'; @@ -18,7 +19,7 @@ import { import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { Fragment, useRef } from 'react'; +import { Fragment, useEffect, useRef } from 'react'; import { useIntl } from 'react-intl'; export const menuMessages = defineMessages('components.Layout.Sidebar', { @@ -35,6 +36,10 @@ export const menuMessages = defineMessages('components.Layout.Sidebar', { interface SidebarProps { open?: boolean; setClosed: () => void; + pendingRequestsCount: number; + openIssuesCount: number; + revalidateIssueCount: () => void; + revalidateRequestsCount: () => void; } interface SidebarLinkProps { @@ -114,13 +119,35 @@ const SidebarLinks: SidebarLinkProps[] = [ }, ]; -const Sidebar = ({ open, setClosed }: SidebarProps) => { +const Sidebar = ({ + open, + setClosed, + pendingRequestsCount, + openIssuesCount, + revalidateIssueCount, + revalidateRequestsCount, +}: SidebarProps) => { const navRef = useRef(null); const router = useRouter(); const intl = useIntl(); const { hasPermission } = useUser(); useClickOutside(navRef, () => setClosed()); + useEffect(() => { + if (openIssuesCount) { + revalidateIssueCount(); + } + + if (pendingRequestsCount) { + revalidateRequestsCount(); + } + }, [ + revalidateIssueCount, + revalidateRequestsCount, + pendingRequestsCount, + openIssuesCount, + ]); + return ( <>
    @@ -253,18 +280,48 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => { href={sidebarLink.href} as={sidebarLink.as} className={`group flex items-center rounded-md px-2 py-2 text-lg font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none - ${ - router.pathname.match(sidebarLink.activeRegExp) - ? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500' - : 'hover:bg-gray-700 focus:bg-gray-700' - } - `} + ${ + router.pathname.match(sidebarLink.activeRegExp) + ? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500' + : 'hover:bg-gray-700 focus:bg-gray-700' + } + `} data-testid={sidebarLink.dataTestId} > {sidebarLink.svgIcon} {intl.formatMessage( menuMessages[sidebarLink.messagesKey] )} + {sidebarLink.messagesKey === 'requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
    + + {pendingRequestsCount} + +
    + )} + {sidebarLink.messagesKey === 'issues' && + openIssuesCount > 0 && + hasPermission(Permission.MANAGE_ISSUES) && ( +
    + + {openIssuesCount} + +
    + )} ); })} diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index a1964b0b..50d463cf 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -10,6 +10,7 @@ import { useUser } from '@app/hooks/useUser'; import { ArrowLeftIcon, Bars3BottomLeftIcon } from '@heroicons/react/24/solid'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; +import useSWR from 'swr'; type LayoutProps = { children: React.ReactNode; @@ -22,6 +23,18 @@ const Layout = ({ children }: LayoutProps) => { const router = useRouter(); const { currentSettings } = useSettings(); const { setLocale } = useLocale(); + const { data: requestResponse, mutate: revalidateRequestsCount } = useSWR( + '/api/v1/request/count', + { + revalidateOnMount: true, + } + ); + const { data: issueResponse, mutate: revalidateIssueCount } = useSWR( + '/api/v1/issue/count', + { + revalidateOnMount: true, + } + ); useEffect(() => { if (setLocale && user) { @@ -55,10 +68,21 @@ const Layout = ({ children }: LayoutProps) => {
    - - setSidebarOpen(false)} /> + setSidebarOpen(false)} + pendingRequestsCount={requestResponse?.pending ?? 0} + openIssuesCount={issueResponse?.open ?? 0} + revalidateIssueCount={() => revalidateIssueCount()} + revalidateRequestsCount={() => revalidateRequestsCount()} + />
    - + revalidateIssueCount()} + revalidateRequestsCount={() => revalidateRequestsCount()} + />
    diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx index 2372bc7f..74bc1e39 100644 --- a/src/components/Login/LocalLogin.tsx +++ b/src/components/Login/LocalLogin.tsx @@ -114,6 +114,9 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { autoComplete="current-password" data-testid="password" className="!bg-gray-700/80 placeholder:text-gray-400" + data-1pignore="false" + data-lpignore="false" + data-bwignore="false" />
    diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index b63a24dd..7c7494de 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -20,6 +20,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest'; import Link from 'next/link'; import { useState } from 'react'; import { useIntl } from 'react-intl'; +import { mutate } from 'swr'; const messages = defineMessages('components.RequestBlock', { seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -59,6 +60,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); + mutate('/api/v1/request/count'); } setIsUpdating(false); }; @@ -72,6 +74,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); + mutate('/api/v1/request/count'); } setIsUpdating(false); diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index cbe04fe3..957a3390 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -15,6 +15,7 @@ import type Media from '@server/entity/Media'; import type { MediaRequest } from '@server/entity/MediaRequest'; import { useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; +import { mutate } from 'swr'; const messages = defineMessages('components.RequestButton', { viewrequest: 'View Request', @@ -101,6 +102,7 @@ const RequestButton = ({ if (data) { onUpdate(); + mutate('/api/v1/request/count'); } }; @@ -123,6 +125,7 @@ const RequestButton = ({ ); onUpdate(); + mutate('/api/v1/request/count'); }; const buttons: ButtonOption[] = []; diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index f909d617..e936d98e 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -80,6 +80,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => { if (!res.ok) throw new Error(); mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded'); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); }; return ( @@ -271,6 +272,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { if (data) { revalidate(); + mutate('/api/v1/request/count'); } }; @@ -280,6 +282,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { }); if (!res.ok) throw new Error(); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); }; const retryRequest = async () => { diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 3d64941e..5e764ecb 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -27,7 +27,7 @@ import { useState } from 'react'; import { useInView } from 'react-intersection-observer'; import { FormattedRelativeTime, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; const messages = defineMessages('components.RequestList.RequestItem', { seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -69,6 +69,7 @@ const RequestItemError = ({ }); if (!res.ok) throw new Error(); revalidateList(); + mutate('/api/v1/request/count'); }; const { mediaUrl: plexUrl, mediaUrl4k: plexUrl4k } = useDeepLinks({ @@ -334,6 +335,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { if (data) { revalidate(); + mutate('/api/v1/request/count'); } }; @@ -344,6 +346,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { if (!res.ok) throw new Error(); revalidateList(); + mutate('/api/v1/request/count'); }; const deleteMediaFile = async () => { diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 4dd79524..0f83bea7 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -16,7 +16,7 @@ import type { Collection } from '@server/models/Collection'; import { useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; const messages = defineMessages('components.RequestModal', { requestadmin: 'This request will be approved automatically.', @@ -220,6 +220,7 @@ const CollectionRequestModal = ({ ? MediaStatus.UNKNOWN : MediaStatus.PARTIALLY_AVAILABLE ); + mutate('/api/v1/request/count'); } addToast( @@ -239,7 +240,16 @@ const CollectionRequestModal = ({ } finally { setIsUpdating(false); } - }, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]); + }, [ + requestOverrides, + data?.parts, + data?.name, + onComplete, + addToast, + intl, + selectedParts, + is4k, + ]); const hasAutoApprove = hasPermission( [ diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 85af7aef..75638586 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -104,6 +104,7 @@ const MovieRequestModal = ({ if (!res.ok) throw new Error(); const mediaRequest: MediaRequest = await res.json(); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); if (mediaRequest) { if (onComplete) { @@ -138,7 +139,16 @@ const MovieRequestModal = ({ } finally { setIsUpdating(false); } - }, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]); + }, [ + requestOverrides, + data?.id, + data?.title, + is4k, + onComplete, + addToast, + intl, + hasPermission, + ]); const cancelRequest = async () => { setIsUpdating(true); @@ -150,6 +160,7 @@ const MovieRequestModal = ({ if (!res.ok) throw new Error(); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); if (res.status === 204) { if (onComplete) { @@ -197,6 +208,7 @@ const MovieRequestModal = ({ if (!res.ok) throw new Error(); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); addToast( diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 7480578d..0ef1afd1 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -106,6 +106,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + mutate('/api/v1/request/count'); } try { @@ -141,6 +142,7 @@ const TvRequestModal = ({ if (!res.ok) throw new Error(); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); addToast( @@ -189,6 +191,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + mutate('/api/v1/request/count'); } try { diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index 82ac6840..d34edb64 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -238,6 +238,10 @@ const NotificationsDiscord = () => { name="botUsername" type="text" placeholder={settings.currentSettings.applicationTitle} + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.botUsername && diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index fdb292d3..6daff08d 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -104,7 +104,7 @@ const NotificationsEmail = () => { otherwise: Yup.string().nullable(), }) .matches( - /-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/s, + /-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/, intl.formatMessage(messages.validationPgpPrivateKey) ), pgpPassword: Yup.string().when('pgpPrivateKey', { @@ -295,6 +295,10 @@ const NotificationsEmail = () => { name="emailFrom" type="text" inputMode="email" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.emailFrom && @@ -316,6 +320,10 @@ const NotificationsEmail = () => { name="smtpHost" type="text" inputMode="url" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.smtpHost && @@ -337,6 +345,10 @@ const NotificationsEmail = () => { type="text" inputMode="numeric" className="short" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" /> {errors.smtpPort && touched.smtpPort && @@ -390,7 +402,15 @@ const NotificationsEmail = () => {
    - +
    @@ -400,12 +420,7 @@ const NotificationsEmail = () => {
    - +
    @@ -430,6 +445,10 @@ const NotificationsEmail = () => { type="textarea" rows="10" className="font-mono text-xs" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.pgpPrivateKey && @@ -457,7 +476,10 @@ const NotificationsEmail = () => { as="field" id="pgpPassword" name="pgpPassword" - autoComplete="one-time-code" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.pgpPassword && diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index 6636c6b4..bfac39fe 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -245,7 +245,7 @@ const NotificationsTelegram = () => { as="field" id="botAPI" name="botAPI" - autoComplete="one-time-code" + type="text" />
    {errors.botAPI && @@ -264,7 +264,15 @@ const NotificationsTelegram = () => {
    - +
    {errors.botUsername && touched.botUsername && @@ -294,7 +302,15 @@ const NotificationsTelegram = () => {
    - +
    {errors.chatId && touched.chatId && diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index fbeb2dec..b1ae42f7 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -382,6 +382,10 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { id="name" name="name" type="text" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" onChange={(e: React.ChangeEvent) => { setIsValidated(false); setFieldValue('name', e.target.value); @@ -475,7 +479,6 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { as="field" id="apiKey" name="apiKey" - autoComplete="one-time-code" onChange={(e: React.ChangeEvent) => { setIsValidated(false); setFieldValue('apiKey', e.target.value); diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index 780dc0ee..2a2c6167 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -872,6 +872,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { id="tautulliPort" name="tautulliPort" className="short" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" /> {errors.tautulliPort && touched.tautulliPort && @@ -909,6 +913,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { inputMode="url" id="tautulliUrlBase" name="tautulliUrlBase" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.tautulliUrlBase && @@ -929,7 +937,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { as="field" id="tautulliApiKey" name="tautulliApiKey" - autoComplete="one-time-code" />
    {errors.tautulliApiKey && @@ -950,6 +957,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { inputMode="url" id="tautulliExternalUrl" name="tautulliExternalUrl" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.tautulliExternalUrl && diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx index fc058c0b..65180cac 100644 --- a/src/components/Settings/SettingsServices.tsx +++ b/src/components/Settings/SettingsServices.tsx @@ -119,6 +119,8 @@ const ServerInstance = ({

    {name} @@ -147,6 +149,8 @@ const ServerInstance = ({ {internalUrl} @@ -159,7 +163,12 @@ const ServerInstance = ({ {profileName}

    - + {isSonarr ? ( ) : ( diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index fedea2a6..0728c54d 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -415,6 +415,10 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { id="name" name="name" type="text" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" onChange={(e: React.ChangeEvent) => { setIsValidated(false); setFieldValue('name', e.target.value); @@ -508,7 +512,6 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { as="field" id="apiKey" name="apiKey" - autoComplete="one-time-code" onChange={(e: React.ChangeEvent) => { setIsValidated(false); setFieldValue('apiKey', e.target.value); diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index bc7c4441..ae76c6ad 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -399,6 +399,10 @@ const UserList = () => { name="email" type="text" inputMode="email" + autoComplete="off" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" /> {errors.email && diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 3ab8ab13..9e87cbdf 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,6 +11,7 @@ import { LanguageContext } from '@app/context/LanguageContext'; import { SettingsProvider } from '@app/context/SettingsContext'; import { UserContext } from '@app/context/UserContext'; import type { User } from '@app/hooks/useUser'; +import { Permission, useUser } from '@app/hooks/useUser'; import '@app/styles/globals.css'; import '@app/utils/fetchOverride'; import { polyfillIntl } from '@app/utils/polyfillIntl'; @@ -128,6 +129,35 @@ const CoreApp: Omit = ({ loadLocaleData(currentLocale).then(setMessages); }, [currentLocale]); + const { hasPermission } = useUser(); + + useEffect(() => { + const requestsCount = async () => { + const response = await fetch('/api/v1/request/count'); + return await response.json(); + }; + + // Cast navigator to a type that includes setAppBadge and clearAppBadge + // to avoid TypeScript errors while ensuring these methods exist before calling them. + const newNavigator = navigator as unknown as { + setAppBadge?: (count: number) => Promise; + clearAppBadge?: () => Promise; + }; + + if ('setAppBadge' in navigator) { + if ( + !router.pathname.match(/(login|setup|resetpassword)/) && + hasPermission(Permission.ADMIN) + ) { + requestsCount().then((data) => + newNavigator?.setAppBadge?.(data.pending) + ); + } else { + newNavigator?.clearAppBadge?.(); + } + } + }, [hasPermission, router.pathname]); + if (router.pathname.match(/(login|setup|resetpassword)/)) { component = ; } else { From 9cc6930fed31c834201fe4e8a2a2f456b878dec6 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Mon, 3 Mar 2025 05:45:59 +0800 Subject: [PATCH 56/65] fix(api): make item endpoints user-independent (#1413) Fix Jellyfin/Emby item retrieval to work properly with api tokens by using user-independent item endpoints. This will resolve the issue where disabled libraries for jellyseerr owner's would fail to scan even when we migrated to API tokens due to using the user-dependent item view endpoints. --- server/api/jellyfin.ts | 65 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index 27a7cf40..b2323ea5 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import ExternalAPI from '@server/api/externalapi'; import { ApiErrorCode } from '@server/constants/error'; +import { MediaServerType } from '@server/constants/server'; import availabilitySync from '@server/lib/availabilitySync'; +import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import { ApiError } from '@server/types/error'; import { getAppVersion } from '@server/utils/appVersion'; @@ -92,14 +94,22 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem { DateCreated?: string; } +export interface JellyfinItemsReponse { + Items: JellyfinLibraryItemExtended[]; + TotalRecordCount: number; + StartIndex: number; +} + class JellyfinAPI extends ExternalAPI { private userId?: string; + private mediaServerType: MediaServerType; constructor( jellyfinHost: string, authToken?: string | null, deviceId?: string | null ) { + const settings = getSettings(); let authHeaderVal: string; if (authToken) { authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}", Token="${authToken}"`; @@ -116,6 +126,8 @@ class JellyfinAPI extends ExternalAPI { }, } ); + + this.mediaServerType = settings.main.mediaServerType; } public async login( @@ -296,18 +308,15 @@ class JellyfinAPI extends ExternalAPI { public async getLibraryContents(id: string): Promise { try { - const libraryItemsResponse = await this.get( - `/Users/${this.userId}/Items`, - { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series,Movie,Others', - Recursive: 'true', - StartIndex: '0', - ParentId: id, - collapseBoxSetItems: 'false', - } - ); + const libraryItemsResponse = await this.get(`/Items`, { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series,Movie,Others', + Recursive: 'true', + StartIndex: '0', + ParentId: id, + collapseBoxSetItems: 'false', + }); return libraryItemsResponse.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' @@ -324,13 +333,22 @@ class JellyfinAPI extends ExternalAPI { public async getRecentlyAdded(id: string): Promise { try { - const itemResponse = await this.get( - `/Users/${this.userId}/Items/Latest`, - { - Limit: '12', - ParentId: id, - } - ); + const endpoint = + this.mediaServerType === MediaServerType.JELLYFIN + ? `/Items/Latest` + : `/Users/${this.userId}/Items/Latest`; + + const baseParams = { + Limit: '12', + ParentId: id, + }; + + const params = + this.mediaServerType === MediaServerType.JELLYFIN + ? { ...baseParams, userId: this.userId ?? `Me` } + : baseParams; + + const itemResponse = await this.get(endpoint, params); return itemResponse; } catch (e) { @@ -347,11 +365,12 @@ class JellyfinAPI extends ExternalAPI { id: string ): Promise { try { - const itemResponse = await this.get( - `/Users/${this.userId}/Items/${id}` - ); + const itemResponse = await this.get(`/Items`, { + ids: id, + fields: 'ProviderIds,MediaSources,Width,Height,IsHD,DateCreated', + }); - return itemResponse; + return itemResponse.Items?.[0]; } catch (e) { if (availabilitySync.running) { if (e.cause?.status === 500) { From ada467ecf40c7c27d57ae69ad515bd245d7bb639 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sun, 2 Mar 2025 22:53:43 +0100 Subject: [PATCH 57/65] fix(settings): remove dns server option (#1416) * fix(settings): remove dns server option This PR removes the DNS Servers option added in #1266 because it doesn't seem to work reliably. * style: remove whitespace change --- cypress/config/settings.cypress.json | 1 - jellyseerr-api.yml | 3 -- server/index.ts | 6 ---- server/lib/settings/index.ts | 2 -- .../0005_migrate_network_settings.ts | 2 -- server/utils/restartFlag.ts | 3 +- .../Settings/SettingsNetwork/index.tsx | 33 ------------------- src/i18n/locale/en.json | 2 -- 8 files changed, 1 insertion(+), 51 deletions(-) diff --git a/cypress/config/settings.cypress.json b/cypress/config/settings.cypress.json index f376d880..702ee148 100644 --- a/cypress/config/settings.cypress.json +++ b/cypress/config/settings.cypress.json @@ -24,7 +24,6 @@ "partialRequestsEnabled": true, "enableSpecialEpisodes": false, "forceIpv4First": false, - "dnsServers": "", "locale": "en" }, "plex": { diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml index ddd94202..0bbe7ffb 100644 --- a/jellyseerr-api.yml +++ b/jellyseerr-api.yml @@ -194,9 +194,6 @@ components: forceIpv4First: type: boolean example: false - dnsServers: - type: string - example: '1.1.1.1' trustProxy: type: boolean example: true diff --git a/server/index.ts b/server/index.ts index 88baedb8..00876419 100644 --- a/server/index.ts +++ b/server/index.ts @@ -83,12 +83,6 @@ app net.setDefaultAutoSelectFamily(false); } - if (settings.network.dnsServers.trim() !== '') { - dns.setServers( - settings.network.dnsServers.split(',').map((server) => server.trim()) - ); - } - // Register HTTP proxy if (settings.network.proxy.enabled) { await createCustomProxyAgent(settings.network.proxy); diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 7fc09fb3..d85f7137 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -137,7 +137,6 @@ export interface MainSettings { export interface NetworkSettings { csrfProtection: boolean; forceIpv4First: boolean; - dnsServers: string; trustProxy: boolean; proxy: ProxySettings; } @@ -510,7 +509,6 @@ class Settings { csrfProtection: false, trustProxy: false, forceIpv4First: false, - dnsServers: '', proxy: { enabled: false, hostname: '', diff --git a/server/lib/settings/migrations/0005_migrate_network_settings.ts b/server/lib/settings/migrations/0005_migrate_network_settings.ts index a6ad4844..6d4a826f 100644 --- a/server/lib/settings/migrations/0005_migrate_network_settings.ts +++ b/server/lib/settings/migrations/0005_migrate_network_settings.ts @@ -10,7 +10,6 @@ const migrateNetworkSettings = (settings: any): AllSettings => { csrfProtection: settings.main.csrfProtection ?? false, trustProxy: settings.main.trustProxy ?? false, forceIpv4First: settings.main.forceIpv4First ?? false, - dnsServers: settings.main.dnsServers ?? '', proxy: settings.main.proxy ?? { enabled: false, hostname: '', @@ -25,7 +24,6 @@ const migrateNetworkSettings = (settings: any): AllSettings => { delete settings.main.csrfProtection; delete settings.main.trustProxy; delete settings.main.forceIpv4First; - delete settings.main.dnsServers; delete settings.main.proxy; return newSettings; }; diff --git a/server/utils/restartFlag.ts b/server/utils/restartFlag.ts index 24282a09..ffd64df3 100644 --- a/server/utils/restartFlag.ts +++ b/server/utils/restartFlag.ts @@ -18,8 +18,7 @@ class RestartFlag { this.networkSettings.csrfProtection !== networkSettings.csrfProtection || this.networkSettings.trustProxy !== networkSettings.trustProxy || this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled || - this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First || - this.networkSettings.dnsServers !== networkSettings.dnsServers + this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First ); } } diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx index befed1fb..e715267e 100644 --- a/src/components/Settings/SettingsNetwork/index.tsx +++ b/src/components/Settings/SettingsNetwork/index.tsx @@ -45,9 +45,6 @@ const messages = defineMessages('components.Settings.SettingsNetwork', { forceIpv4First: 'Force 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]"', }); const SettingsNetwork = () => { @@ -93,7 +90,6 @@ const SettingsNetwork = () => { initialValues={{ csrfProtection: data?.csrfProtection, forceIpv4First: data?.forceIpv4First, - dnsServers: data?.dnsServers, trustProxy: data?.trustProxy, proxyEnabled: data?.proxy?.enabled, proxyHostname: data?.proxy?.hostname, @@ -116,7 +112,6 @@ const SettingsNetwork = () => { body: JSON.stringify({ csrfProtection: values.csrfProtection, forceIpv4First: values.forceIpv4First, - dnsServers: values.dnsServers, trustProxy: values.trustProxy, proxy: { enabled: values.proxyEnabled, @@ -426,34 +421,6 @@ const SettingsNetwork = () => { /> -
    - -
    -
    - -
    - {errors.dnsServers && - touched.dnsServers && - typeof errors.dnsServers === 'string' && ( -
    {errors.dnsServers}
    - )} -
    -
    diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 73dfc7b8..50df170b 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -940,8 +940,6 @@ "components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection", "components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!", "components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", - "components.Settings.SettingsNetwork.dnsServers": "Custom DNS Servers", - "components.Settings.SettingsNetwork.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"", "components.Settings.SettingsNetwork.docs": "documentation", "components.Settings.SettingsNetwork.forceIpv4First": "Force IPv4 Resolution First", "components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6", From 67bd639a432d724bb34b7d6fed76c0bb66d94147 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sun, 2 Mar 2025 23:46:31 +0100 Subject: [PATCH 58/65] fix(emby): throw the right error message if no library exists (#1415) This PR fixes a bug where the error message when no library exists was not displayed because of a Jellyfin-specific check failing with Emby. --- server/routes/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 018f7b46..d74d328f 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -352,7 +352,7 @@ settingsRoutes.get('/jellyfin/library', async (req, res, next) => { const account = await jellyfinClient.getUser(); // Automatic Library grouping is not supported when user views are used to get library - if (account.Configuration.GroupedFolders.length > 0) { + if (account.Configuration.GroupedFolders?.length > 0) { return next({ status: 501, message: ApiErrorCode.SyncErrorGroupedFolders, From 4e44282387e7b511daecd961cdc9da98cb4b0139 Mon Sep 17 00:00:00 2001 From: Gauthier Date: Mon, 3 Mar 2025 00:13:09 +0100 Subject: [PATCH 59/65] fix(overriderules): enable override rules only when a service exists (#1417) This PR disable the 'New Override Rule' button when no Radarr/Sonarr service exists. --- src/components/Settings/SettingsServices.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx index 65180cac..784d847d 100644 --- a/src/components/Settings/SettingsServices.tsx +++ b/src/components/Settings/SettingsServices.tsx @@ -528,6 +528,7 @@ const SettingsServices = () => {
    diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx index 74bc1e39..169a5a8f 100644 --- a/src/components/Login/LocalLogin.tsx +++ b/src/components/Login/LocalLogin.tsx @@ -75,7 +75,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { {({ errors, touched, isSubmitting, isValid }) => { return ( <> - +

    {intl.formatMessage(messages.loginwithapp, { @@ -94,6 +94,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { type="text" inputMode="email" data-testid="email" + data-form-type="username,email" className="!bg-gray-700/80 placeholder:text-gray-400" />

    @@ -113,6 +114,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { placeholder={intl.formatMessage(messages.password)} autoComplete="current-password" data-testid="password" + data-form-type="password" className="!bg-gray-700/80 placeholder:text-gray-400" data-1pignore="false" data-lpignore="false" diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index d34edb64..92899700 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -239,6 +239,7 @@ const NotificationsDiscord = () => { type="text" placeholder={settings.currentSettings.applicationTitle} autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index 6daff08d..ac3853dd 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -296,6 +296,7 @@ const NotificationsEmail = () => { type="text" inputMode="email" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -321,6 +322,7 @@ const NotificationsEmail = () => { type="text" inputMode="url" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -346,6 +348,7 @@ const NotificationsEmail = () => { inputMode="numeric" className="short" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -407,6 +410,7 @@ const NotificationsEmail = () => { name="authUser" type="text" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -446,6 +450,7 @@ const NotificationsEmail = () => { rows="10" className="font-mono text-xs" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -477,6 +482,7 @@ const NotificationsEmail = () => { id="pgpPassword" name="pgpPassword" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index bfac39fe..ad78a660 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -269,6 +269,7 @@ const NotificationsTelegram = () => { name="botUsername" type="text" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -307,6 +308,7 @@ const NotificationsTelegram = () => { name="chatId" type="text" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index b1ae42f7..e63f4905 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -383,6 +383,7 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { name="name" type="text" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index 2a2c6167..5e2d5d4c 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -873,6 +873,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { name="tautulliPort" className="short" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -914,6 +915,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { id="tautulliUrlBase" name="tautulliUrlBase" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" @@ -958,6 +960,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { id="tautulliExternalUrl" name="tautulliExternalUrl" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index 0728c54d..11954734 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -416,6 +416,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { name="name" type="text" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" diff --git a/src/components/Setup/JellyfinSetup.tsx b/src/components/Setup/JellyfinSetup.tsx index 0a19bffa..70be6eda 100644 --- a/src/components/Setup/JellyfinSetup.tsx +++ b/src/components/Setup/JellyfinSetup.tsx @@ -198,6 +198,11 @@ function JellyfinSetup({ messages.hostname, mediaServerFormatValues )} + autoComplete="off" + data-form-type="other" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.hostname && touched.hostname && ( @@ -282,6 +287,11 @@ function JellyfinSetup({ name="email" type="text" placeholder={intl.formatMessage(messages.email)} + autoComplete="off" + data-form-type="other" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.email && touched.email && ( @@ -298,6 +308,11 @@ function JellyfinSetup({ name="username" type="text" placeholder={intl.formatMessage(messages.username)} + autoComplete="off" + data-form-type="other" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" />
    {errors.username && touched.username && ( @@ -314,6 +329,11 @@ function JellyfinSetup({ name="password" type="password" placeholder={intl.formatMessage(messages.password)} + autoComplete="off" + data-form-type="other" + data-1pignore="true" + data-lpignore="true" + data-bwignore="true" /> {errors.password && touched.password && ( diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index ae76c6ad..9a977881 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -400,6 +400,7 @@ const UserList = () => { type="text" inputMode="email" autoComplete="off" + data-form-type="other" data-1pignore="true" data-lpignore="true" data-bwignore="true" From dcc13080bc0c4bcf38ae77f22eb06eb58ddc9e46 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Sat, 8 Mar 2025 02:45:14 +0800 Subject: [PATCH 62/65] chore: update dependencies (#1393) * chore: update sqlite3 * chore: update nextjs * chore: update semver * chore: update email-templates * chore: update express and express-openapi-validator * chore: override cross-spawn as the packages using it didnt update it * chore: update undici * feat: use csrf-csrf instead of deprecated csurf * chore: override cookie * chore: remove the overrides * chore: update lockfile * chore: revert cypress update * chore: revert revert cypress update * chore: update cypress * ci(cypress): upload video artifacts for debugging * chore(cypress): generate videos * ci(cypress): remove unnecessary matrix.browser in the artifact name * chore: update to es2021 --------- Co-authored-by: Gauthier --- .github/workflows/cypress.yml | 7 + cypress.config.ts | 1 + next-env.d.ts | 2 +- package.json | 34 +- pnpm-lock.yaml | 2753 +++++++++++++++++---------------- server/index.ts | 29 +- server/lib/email/index.ts | 1 + src/utils/fetchOverride.ts | 2 +- tsconfig.json | 2 +- 9 files changed, 1430 insertions(+), 1401 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 4dc75901..ce543097 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -36,3 +36,10 @@ jobs: # Fix test titles in cypress dashboard COMMIT_INFO_MESSAGE: ${{github.event.pull_request.title}} COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha}} + - name: Upload video files + uses: actions/upload-artifact@v4 + with: + name: cypress-videos + path: | + cypress/videos + cypress/screenshots diff --git a/cypress.config.ts b/cypress.config.ts index 457aa326..598bcb73 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ projectId: 'xkm1b4', e2e: { baseUrl: 'http://localhost:5055', + video: true, experimentalSessionAndOrigin: true, }, env: { diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03d..a4a7b3f5 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/package.json b/package.json index ce508dd3..830f9930 100644 --- a/package.json +++ b/package.json @@ -47,16 +47,16 @@ "bcrypt": "5.1.0", "bowser": "2.11.0", "connect-typeorm": "1.1.4", - "cookie-parser": "1.4.6", + "cookie-parser": "1.4.7", "copy-to-clipboard": "3.3.3", "country-flag-icons": "1.5.5", "cronstrue": "2.23.0", - "csurf": "1.11.0", + "csrf-csrf": "^3.1.0", "date-fns": "2.29.3", "dayjs": "1.11.7", - "email-templates": "9.0.0", + "email-templates": "12.0.1", "email-validator": "2.0.4", - "express": "4.18.2", + "express": "4.21.2", "express-openapi-validator": "4.13.8", "express-rate-limit": "6.7.0", "express-session": "1.17.3", @@ -64,15 +64,15 @@ "gravatar-url": "3.1.0", "lodash": "4.17.21", "mime": "3", - "next": "^14.2.4", + "next": "^14.2.24", "node-cache": "5.1.2", "node-gyp": "9.3.1", "node-schedule": "2.1.1", - "nodemailer": "6.9.1", - "openpgp": "5.7.0", + "nodemailer": "6.10.0", + "openpgp": "5.11.2", "pg": "8.11.0", "plex-api": "5.3.2", - "pug": "3.0.2", + "pug": "3.0.3", "react": "^18.3.1", "react-ace": "10.1.0", "react-animate-height": "2.1.2", @@ -91,14 +91,14 @@ "react-use-clipboard": "1.0.9", "reflect-metadata": "0.1.13", "secure-random-password": "0.2.3", - "semver": "7.3.8", + "semver": "7.7.1", "sharp": "^0.33.4", - "sqlite3": "5.1.4", + "sqlite3": "5.1.7", "swagger-ui-express": "4.6.2", "swr": "2.2.5", "tailwind-merge": "^2.6.0", "typeorm": "0.3.11", - "undici": "^6.20.1", + "undici": "^7.3.0", "web-push": "3.5.0", "wink-jaro-distance": "^2.0.0", "winston": "3.8.2", @@ -106,7 +106,7 @@ "xml2js": "0.4.23", "yamljs": "0.3.0", "yup": "0.32.11", - "zod": "3.20.6" + "zod": "3.24.2" }, "devDependencies": { "@commitlint/cli": "17.4.4", @@ -116,8 +116,8 @@ "@semantic-release/exec": "6.0.3", "@semantic-release/git": "10.0.1", "@tailwindcss/aspect-ratio": "0.4.2", - "@tailwindcss/forms": "0.5.3", - "@tailwindcss/typography": "0.5.9", + "@tailwindcss/forms": "0.5.10", + "@tailwindcss/typography": "0.5.16", "@types/bcrypt": "5.0.0", "@types/cookie-parser": "1.4.3", "@types/country-flag-icons": "1.2.0", @@ -146,7 +146,7 @@ "commitizen": "4.3.0", "copyfiles": "2.4.1", "cy-mobile-commands": "0.3.0", - "cypress": "12.7.0", + "cypress": "14.1.0", "cz-conventional-changelog": "3.3.0", "eslint": "8.35.0", "eslint-config-next": "^14.2.4", @@ -159,8 +159,8 @@ "eslint-plugin-react-hooks": "4.6.0", "husky": "8.0.3", "lint-staged": "13.1.2", - "nodemon": "2.0.20", - "postcss": "8.4.21", + "nodemon": "3.1.9", + "postcss": "8.4.31", "prettier": "2.8.4", "prettier-plugin-organize-imports": "3.2.2", "prettier-plugin-tailwindcss": "0.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07a8ac57..a14c0716 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,10 +52,10 @@ importers: version: 2.11.0 connect-typeorm: specifier: 1.1.4 - version: 1.1.4(typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) + version: 1.1.4(typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) cookie-parser: - specifier: 1.4.6 - version: 1.4.6 + specifier: 1.4.7 + version: 1.4.7 copy-to-clipboard: specifier: 3.3.3 version: 3.3.3 @@ -65,9 +65,9 @@ importers: cronstrue: specifier: 2.23.0 version: 2.23.0 - csurf: - specifier: 1.11.0 - version: 1.11.0 + csrf-csrf: + specifier: ^3.1.0 + version: 3.1.0 date-fns: specifier: 2.29.3 version: 2.29.3 @@ -75,20 +75,20 @@ importers: specifier: 1.11.7 version: 1.11.7 email-templates: - specifier: 9.0.0 - version: 9.0.0(encoding@0.1.13)(handlebars@4.7.8)(mustache@4.2.0)(pug@3.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.6) + specifier: 12.0.1 + version: 12.0.1(@babel/core@7.24.7)(encoding@0.1.13)(handlebars@4.7.8)(mustache@4.2.0)(pug@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.7) email-validator: specifier: 2.0.4 version: 2.0.4 express: - specifier: 4.18.2 - version: 4.18.2 + specifier: 4.21.2 + version: 4.21.2 express-openapi-validator: specifier: 4.13.8 version: 4.13.8 express-rate-limit: specifier: 6.7.0 - version: 6.7.0(express@4.18.2) + version: 6.7.0(express@4.21.2) express-session: specifier: 1.17.3 version: 1.17.3 @@ -105,8 +105,8 @@ importers: specifier: '3' version: 3.0.0 next: - specifier: ^14.2.4 - version: 14.2.4(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^14.2.24 + version: 14.2.24(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) node-cache: specifier: 5.1.2 version: 5.1.2 @@ -117,11 +117,11 @@ importers: specifier: 2.1.1 version: 2.1.1 nodemailer: - specifier: 6.9.1 - version: 6.9.1 + specifier: 6.10.0 + version: 6.10.0 openpgp: - specifier: 5.7.0 - version: 5.7.0 + specifier: 5.11.2 + version: 5.11.2 pg: specifier: 8.11.0 version: 8.11.0 @@ -129,8 +129,8 @@ importers: specifier: 5.3.2 version: 5.3.2 pug: - specifier: 3.0.2 - version: 3.0.2 + specifier: 3.0.3 + version: 3.0.3 react: specifier: ^18.3.1 version: 18.3.1 @@ -186,17 +186,17 @@ importers: specifier: 0.2.3 version: 0.2.3 semver: - specifier: 7.3.8 - version: 7.3.8 + specifier: 7.7.1 + version: 7.7.1 sharp: specifier: ^0.33.4 version: 0.33.4 sqlite3: - specifier: 5.1.4 - version: 5.1.4(encoding@0.1.13) + specifier: 5.1.7 + version: 5.1.7 swagger-ui-express: specifier: 4.6.2 - version: 4.6.2(express@4.18.2) + version: 4.6.2(express@4.21.2) swr: specifier: 2.2.5 version: 2.2.5(react@18.3.1) @@ -205,10 +205,10 @@ importers: version: 2.6.0 typeorm: specifier: 0.3.11 - version: 0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + version: 0.3.11(pg@8.11.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) undici: - specifier: ^6.20.1 - version: 6.20.1 + specifier: ^7.3.0 + version: 7.3.0 web-push: specifier: 3.5.0 version: 3.5.0 @@ -231,8 +231,8 @@ importers: specifier: 0.32.11 version: 0.32.11 zod: - specifier: 3.20.6 - version: 3.20.6 + specifier: 3.24.2 + version: 3.24.2 devDependencies: '@commitlint/cli': specifier: 17.4.4 @@ -254,13 +254,13 @@ importers: version: 10.0.1(semantic-release@19.0.5(encoding@0.1.13)) '@tailwindcss/aspect-ratio': specifier: 0.4.2 - version: 0.4.2(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) + version: 0.4.2(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) '@tailwindcss/forms': - specifier: 0.5.3 - version: 0.5.3(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) + specifier: 0.5.10 + version: 0.5.10(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) '@tailwindcss/typography': - specifier: 0.5.9 - version: 0.5.9(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) + specifier: 0.5.16 + version: 0.5.16(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))) '@types/bcrypt': specifier: 5.0.0 version: 5.0.0 @@ -335,7 +335,7 @@ importers: version: 5.54.0(eslint@8.35.0)(typescript@4.9.5) autoprefixer: specifier: 10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.13(postcss@8.4.31) commitizen: specifier: 4.3.0 version: 4.3.0(@types/node@22.10.5)(typescript@4.9.5) @@ -346,8 +346,8 @@ importers: specifier: 0.3.0 version: 0.3.0 cypress: - specifier: 12.7.0 - version: 12.7.0 + specifier: 14.1.0 + version: 14.1.0 cz-conventional-changelog: specifier: 3.3.0 version: 3.3.0(@types/node@22.10.5)(typescript@4.9.5) @@ -385,11 +385,11 @@ importers: specifier: 13.1.2 version: 13.1.2(enquirer@2.4.1) nodemon: - specifier: 2.0.20 - version: 2.0.20 + specifier: 3.1.9 + version: 3.1.9 postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 prettier: specifier: 2.8.4 version: 2.8.4 @@ -407,7 +407,7 @@ importers: version: 1.0.1(semantic-release@19.0.5(encoding@0.1.13)) tailwindcss: specifier: 3.2.7 - version: 3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + version: 3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) ts-node: specifier: 10.9.1 version: 10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5) @@ -442,24 +442,24 @@ packages: resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.3': - resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} '@babel/core@7.24.7': resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} engines: {node: '>=6.9.0'} '@babel/generator@7.24.7': resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.3': - resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.24.7': @@ -478,8 +478,8 @@ packages: resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} engines: {node: '>=6.9.0'} '@babel/helper-create-class-features-plugin@7.24.7': @@ -488,8 +488,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-class-features-plugin@7.25.9': - resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + '@babel/helper-create-class-features-plugin@7.26.9': + resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -568,8 +568,8 @@ packages: resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} '@babel/helper-remap-async-to-generator@7.24.7': @@ -590,8 +590,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.25.9': - resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + '@babel/helper-replace-supers@7.26.5': + resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -648,8 +648,8 @@ packages: resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + '@babel/helpers@7.26.9': + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} engines: {node: '>=6.9.0'} '@babel/highlight@7.24.7': @@ -661,8 +661,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.3': - resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} engines: {node: '>=6.0.0'} hasBin: true @@ -1016,8 +1016,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-flow-strip-types@7.25.9': - resolution: {integrity: sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==} + '@babel/plugin-transform-flow-strip-types@7.26.5': + resolution: {integrity: sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1262,8 +1262,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.25.9': - resolution: {integrity: sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==} + '@babel/plugin-transform-runtime@7.26.9': + resolution: {integrity: sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1322,8 +1322,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.26.3': - resolution: {integrity: sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==} + '@babel/plugin-transform-typescript@7.26.8': + resolution: {integrity: sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1410,28 +1410,32 @@ packages: resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.26.9': + resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.7': resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} '@babel/traverse@7.24.7': resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.4': - resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} engines: {node: '>=6.9.0'} '@babel/types@7.24.7': resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} engines: {node: '>=6.9.0'} '@colors/colors@1.5.0': @@ -1535,8 +1539,8 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@cypress/request@2.88.12': - resolution: {integrity: sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==} + '@cypress/request@3.0.7': + resolution: {integrity: sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==} engines: {node: '>= 6'} '@cypress/xvfb@1.2.4': @@ -1717,8 +1721,11 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@hapi/boom@9.1.4': - resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==} + '@hapi/boom@10.0.1': + resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -1942,88 +1949,242 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@ladjs/consolidate@1.0.4': + resolution: {integrity: sha512-ErvBg5acSqns86V/xW7gjqqnBBs6thnpMB0gGc3oM7WHsV8PWrnBtKI6dumHDT3UT/zEOfGzp7dmSFqWoCXKWQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.22.5 + arc-templates: ^0.5.3 + atpl: '>=0.7.6' + bracket-template: ^1.1.5 + coffee-script: ^1.12.7 + dot: ^1.1.3 + dust: ^0.3.0 + dustjs-helpers: ^1.7.4 + dustjs-linkedin: ^2.7.5 + eco: ^1.1.0-rc-3 + ect: ^0.5.9 + ejs: ^3.1.5 + haml-coffee: ^1.14.1 + hamlet: ^0.3.3 + hamljs: ^0.6.2 + handlebars: ^4.7.6 + hogan.js: ^3.0.2 + htmling: ^0.0.8 + jazz: ^0.0.18 + jqtpl: ~1.1.0 + just: ^0.1.8 + liquid-node: ^3.0.1 + liquor: ^0.0.5 + lodash: ^4.17.20 + mote: ^0.2.0 + mustache: ^4.0.1 + nunjucks: ^3.2.2 + plates: ~0.4.11 + pug: ^3.0.0 + qejs: ^3.0.5 + ractive: ^1.3.12 + react: '>=16.13.1' + react-dom: '>=16.13.1' + slm: ^2.0.0 + swig: ^1.4.2 + swig-templates: ^2.0.3 + teacup: ^2.0.0 + templayed: '>=0.2.3' + then-pug: '*' + tinyliquid: ^0.2.34 + toffee: ^0.3.6 + twig: ^1.15.2 + twing: ^5.0.2 + underscore: ^1.11.0 + vash: ^0.13.0 + velocityjs: ^2.0.1 + walrus: ^0.10.1 + whiskers: ^0.4.0 + peerDependenciesMeta: + '@babel/core': + optional: true + arc-templates: + optional: true + atpl: + optional: true + bracket-template: + optional: true + coffee-script: + optional: true + dot: + optional: true + dust: + optional: true + dustjs-helpers: + optional: true + dustjs-linkedin: + optional: true + eco: + optional: true + ect: + optional: true + ejs: + optional: true + haml-coffee: + optional: true + hamlet: + optional: true + hamljs: + optional: true + handlebars: + optional: true + hogan.js: + optional: true + htmling: + optional: true + jazz: + optional: true + jqtpl: + optional: true + just: + optional: true + liquid-node: + optional: true + liquor: + optional: true + lodash: + optional: true + mote: + optional: true + mustache: + optional: true + nunjucks: + optional: true + plates: + optional: true + pug: + optional: true + qejs: + optional: true + ractive: + optional: true + react: + optional: true + react-dom: + optional: true + slm: + optional: true + swig: + optional: true + swig-templates: + optional: true + teacup: + optional: true + templayed: + optional: true + then-pug: + optional: true + tinyliquid: + optional: true + toffee: + optional: true + twig: + optional: true + twing: + optional: true + underscore: + optional: true + vash: + optional: true + velocityjs: + optional: true + walrus: + optional: true + whiskers: + optional: true + '@ladjs/country-language@0.2.1': resolution: {integrity: sha512-e3AmT7jUnfNE6e2mx2+cPYiWdFW3McySDGRhQEYE6SksjZTMj0PTp+R9x1xG89tHRTsyMNJFl9J4HtZPWZzi1Q==} - '@ladjs/i18n@7.2.6': - resolution: {integrity: sha512-rgCYbDz18ADMjQox09J0G45L8LankQgt7QJqiaPh7dAps/hY/7NB8lotVh8TvFt26jJXPvCErAEsGe2clp/YOg==} - engines: {node: '>=8.3.0'} + '@ladjs/country-language@1.0.3': + resolution: {integrity: sha512-FJROu9/hh4eqVAGDyfL8vpv6Vb0qKHX1ozYLRZ+beUzD5xFf+3r0J+SVIWKviEa7W524Qvqou+ta1WrsRgzxGw==} + engines: {node: '>= 14'} + + '@ladjs/i18n@8.0.3': + resolution: {integrity: sha512-QYeYGz6uJaH41ZVyNoI2Lt2NyfcpKwpDIBMx3psaE1NBJn8P+jk1m0EIjphfYvnRMnl/QyBpn98FfcTUjTkuBw==} + engines: {node: '>=14'} '@mapbox/node-pre-gyp@1.0.11': resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@messageformat/core@3.3.0': - resolution: {integrity: sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==} + '@messageformat/core@3.4.0': + resolution: {integrity: sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==} - '@messageformat/date-skeleton@1.0.1': - resolution: {integrity: sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg==} + '@messageformat/date-skeleton@1.1.0': + resolution: {integrity: sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==} '@messageformat/number-skeleton@1.2.0': resolution: {integrity: sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==} - '@messageformat/parser@5.1.0': - resolution: {integrity: sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==} + '@messageformat/parser@5.1.1': + resolution: {integrity: sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==} '@messageformat/runtime@3.0.1': resolution: {integrity: sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==} - '@next/env@14.2.4': - resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==} + '@next/env@14.2.24': + resolution: {integrity: sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==} '@next/eslint-plugin-next@14.2.4': resolution: {integrity: sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==} - '@next/swc-darwin-arm64@14.2.4': - resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==} + '@next/swc-darwin-arm64@14.2.24': + resolution: {integrity: sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.4': - resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==} + '@next/swc-darwin-x64@14.2.24': + resolution: {integrity: sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.4': - resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==} + '@next/swc-linux-arm64-gnu@14.2.24': + resolution: {integrity: sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.4': - resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==} + '@next/swc-linux-arm64-musl@14.2.24': + resolution: {integrity: sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.4': - resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==} + '@next/swc-linux-x64-gnu@14.2.24': + resolution: {integrity: sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.4': - resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==} + '@next/swc-linux-x64-musl@14.2.24': + resolution: {integrity: sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.4': - resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==} + '@next/swc-win32-arm64-msvc@14.2.24': + resolution: {integrity: sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.4': - resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==} + '@next/swc-win32-ia32-msvc@14.2.24': + resolution: {integrity: sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.4': - resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==} + '@next/swc-win32-x64-msvc@14.2.24': + resolution: {integrity: sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2770,9 +2931,6 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} - '@selderee/plugin-htmlparser2@0.6.0': - resolution: {integrity: sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==} - '@semantic-release/changelog@6.0.2': resolution: {integrity: sha512-jHqfTkoPbDEOAgAP18mGP53IxeMwxTISN+GwTRy9uLu58UjARoZU8ScCgWGeO2WPkEsm57H8AkyY02W2ntIlIw==} engines: {node: '>=14.17'} @@ -3007,15 +3165,15 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' - '@tailwindcss/forms@0.5.3': - resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} + '@tailwindcss/forms@0.5.10': + resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' - '@tailwindcss/typography@0.5.9': - resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} peerDependencies: - tailwindcss: '>=3.0.0 || insiders' + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' '@tanem/react-nprogress@5.0.30': resolution: {integrity: sha512-OBvlGfxWMyAGi6C9V6ZCdu5Kn9drxHZSg977TtF9hBpNtl+GY6lxOsbazkMIQua/xqaDe58Bs1kDTNzdwjIFEA==} @@ -3149,8 +3307,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/multer@1.4.11': - resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==} + '@types/multer@1.4.12': + resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -3158,14 +3316,11 @@ packages: '@types/node-schedule@2.1.0': resolution: {integrity: sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==} - '@types/node@14.18.63': - resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@18.19.70': - resolution: {integrity: sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==} + '@types/node@18.19.76': + resolution: {integrity: sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==} '@types/node@20.5.1': resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} @@ -3241,8 +3396,8 @@ packages: '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - '@types/sizzle@2.3.8': - resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} + '@types/sizzle@2.3.9': + resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -3259,8 +3414,8 @@ packages: '@types/web-push@3.3.2': resolution: {integrity: sha512-JxWGVL/m7mWTIg4mRYO+A6s0jPmBkr4iJr39DqJpRJAc+jrPiEe1/asmkwerzRon8ZZDxaZJpsxpv0Z18Wo9gw==} - '@types/webxr@0.5.20': - resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + '@types/webxr@0.5.21': + resolution: {integrity: sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==} '@types/wink-jaro-distance@2.0.2': resolution: {integrity: sha512-Q79orp7qA/g/uLdFmqd5MtEa0ZfJW5X1WXikAu8IVHt24IrHWrcTNYNdPpLK5mwVg34C6FQnrv/DMtcUhjE/zA==} @@ -3456,6 +3611,10 @@ packages: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3639,8 +3798,8 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - assert-never@1.2.1: - resolution: {integrity: sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} @@ -3670,6 +3829,9 @@ packages: async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3691,8 +3853,8 @@ packages: aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - aws4@1.13.0: - resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} axe-core@4.9.1: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} @@ -3783,6 +3945,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -3795,11 +3960,11 @@ packages: blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + bn.js@4.12.1: + resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} - body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} boolbase@1.0.0: @@ -3807,6 +3972,7 @@ packages: boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -3880,10 +4046,18 @@ packages: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} @@ -3922,8 +4096,8 @@ packages: caniuse-lite@1.0.30001636: resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} - caniuse-lite@1.0.30001690: - resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} + caniuse-lite@1.0.30001700: + resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} @@ -3961,16 +4135,9 @@ packages: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} - cheerio-select@1.6.0: - resolution: {integrity: sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==} - cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - cheerio@1.0.0-rc.10: - resolution: {integrity: sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==} - engines: {node: '>= 6'} - cheerio@1.0.0-rc.12: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} @@ -3979,6 +4146,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -3995,6 +4165,10 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + engines: {node: '>=8'} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -4144,8 +4318,8 @@ packages: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} - compression@1.7.5: - resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==} + compression@1.8.0: + resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} engines: {node: '>= 0.8.0'} computed-style@0.1.4: @@ -4173,172 +4347,6 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - consolidate@0.16.0: - resolution: {integrity: sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==} - engines: {node: '>= 0.10.0'} - deprecated: Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog - peerDependencies: - arc-templates: ^0.5.3 - atpl: '>=0.7.6' - babel-core: ^6.26.3 - bracket-template: ^1.1.5 - coffee-script: ^1.12.7 - dot: ^1.1.3 - dust: ^0.3.0 - dustjs-helpers: ^1.7.4 - dustjs-linkedin: ^2.7.5 - eco: ^1.1.0-rc-3 - ect: ^0.5.9 - ejs: ^3.1.5 - haml-coffee: ^1.14.1 - hamlet: ^0.3.3 - hamljs: ^0.6.2 - handlebars: ^4.7.6 - hogan.js: ^3.0.2 - htmling: ^0.0.8 - jade: ^1.11.0 - jazz: ^0.0.18 - jqtpl: ~1.1.0 - just: ^0.1.8 - liquid-node: ^3.0.1 - liquor: ^0.0.5 - lodash: ^4.17.20 - marko: ^3.14.4 - mote: ^0.2.0 - mustache: ^4.0.1 - nunjucks: ^3.2.2 - plates: ~0.4.11 - pug: ^3.0.0 - qejs: ^3.0.5 - ractive: ^1.3.12 - razor-tmpl: ^1.3.1 - react: ^16.13.1 - react-dom: ^16.13.1 - slm: ^2.0.0 - squirrelly: ^5.1.0 - swig: ^1.4.2 - swig-templates: ^2.0.3 - teacup: ^2.0.0 - templayed: '>=0.2.3' - then-jade: '*' - then-pug: '*' - tinyliquid: ^0.2.34 - toffee: ^0.3.6 - twig: ^1.15.2 - twing: ^5.0.2 - underscore: ^1.11.0 - vash: ^0.13.0 - velocityjs: ^2.0.1 - walrus: ^0.10.1 - whiskers: ^0.4.0 - peerDependenciesMeta: - arc-templates: - optional: true - atpl: - optional: true - babel-core: - optional: true - bracket-template: - optional: true - coffee-script: - optional: true - dot: - optional: true - dust: - optional: true - dustjs-helpers: - optional: true - dustjs-linkedin: - optional: true - eco: - optional: true - ect: - optional: true - ejs: - optional: true - haml-coffee: - optional: true - hamlet: - optional: true - hamljs: - optional: true - handlebars: - optional: true - hogan.js: - optional: true - htmling: - optional: true - jade: - optional: true - jazz: - optional: true - jqtpl: - optional: true - just: - optional: true - liquid-node: - optional: true - liquor: - optional: true - lodash: - optional: true - marko: - optional: true - mote: - optional: true - mustache: - optional: true - nunjucks: - optional: true - plates: - optional: true - pug: - optional: true - qejs: - optional: true - ractive: - optional: true - razor-tmpl: - optional: true - react: - optional: true - react-dom: - optional: true - slm: - optional: true - squirrelly: - optional: true - swig: - optional: true - swig-templates: - optional: true - teacup: - optional: true - templayed: - optional: true - then-jade: - optional: true - then-pug: - optional: true - tinyliquid: - optional: true - toffee: - optional: true - twig: - optional: true - twing: - optional: true - underscore: - optional: true - vash: - optional: true - velocityjs: - optional: true - walrus: - optional: true - whiskers: - optional: true - constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} @@ -4390,34 +4398,23 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-parser@1.4.6: - resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} engines: {node: '>= 0.8.0'} cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - - cookie@0.4.0: - resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==} - engines: {node: '>= 0.6'} - - cookie@0.4.1: - resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} - engines: {node: '>= 0.6'} - cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} copy-to-clipboard@3.3.3: @@ -4430,8 +4427,8 @@ packages: core-js-compat@3.37.1: resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} - core-js-compat@3.39.0: - resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} + core-js-compat@3.40.0: + resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -4500,21 +4497,20 @@ packages: resolution: {integrity: sha512-iPoWUQbCwUmrBf1w9W+9YQs8FowWp/teC2XGz3zAmt0Aja+HWGjyjUkWASWcsdzxSuL0EIIdvlfGEVBljvTbSQ==} hasBin: true - cross-spawn@6.0.5: - resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - csrf@3.1.0: - resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==} - engines: {node: '>= 0.8'} + csrf-csrf@3.1.0: + resolution: {integrity: sha512-kZacFfFbdYFxNnFdigRHCzVAq019vJyUUtgPLjCtzh6jMXcWmf8bGUx/hsqtSEMXaNcPm8iXpjC+hW5aeOsRMg==} css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -4545,17 +4541,12 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - csurf@1.11.0: - resolution: {integrity: sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==} - engines: {node: '>= 0.8.0'} - deprecated: Please use another csrf package - cy-mobile-commands@0.3.0: resolution: {integrity: sha512-Bj5P2ylw88hPqolLu68xWB6euVH5uNt8zyh+Ju8sBukGv39mWZxpjp6LtnUX/LK/YMthwvILYHhvr9SG1TP+4w==} - cypress@12.7.0: - resolution: {integrity: sha512-7rq+nmhzz0u6yabCFyPtADU2OOrYt6pvUau9qV7xyifJ/hnsaw/vkr0tnLlcuuQKUAOC1v1M1e4Z0zG7S0IAvA==} - engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} + cypress@14.1.0: + resolution: {integrity: sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true cz-conventional-changelog@3.3.0: @@ -4592,15 +4583,9 @@ packages: dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - dayjs@1.11.11: - resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} - dayjs@1.11.7: resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} - debounce@1.2.1: - resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -4646,6 +4631,10 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -4700,10 +4689,6 @@ packages: denodeify@1.2.1: resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} - depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -4758,9 +4743,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - discontinuous-range@1.0.0: - resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} - display-notification@2.0.0: resolution: {integrity: sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==} engines: {node: '>=4'} @@ -4817,6 +4799,10 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} @@ -4835,13 +4821,12 @@ packages: electron-to-chromium@1.4.810: resolution: {integrity: sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==} - electron-to-chromium@1.5.77: - resolution: {integrity: sha512-AnJSrt5JpRVgY6dgd5yccguLc5A7oMSF0Kt3fcW+Hp5WTuFbl5upeSFZbMZYy2o7jhmIhU8Ekrd82GhyXUqUUg==} + electron-to-chromium@1.5.103: + resolution: {integrity: sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==} - email-templates@9.0.0: - resolution: {integrity: sha512-ap0p38jAq8FMy86Jp2b3hyCFDUA9utWfOuyKPWhrknmHrrT3n94viGcQIAsaQtUZGaJP/0dJ9w//XqvaZV/yYQ==} - engines: {node: '>=10.0.0'} - deprecated: We just released outbound SMTP support! Try it out at @ https://forwardemail.net/docs/how-to-javascript-contact-forms-node-js 🚀 ✉️ 👽 + email-templates@12.0.1: + resolution: {integrity: sha512-849pjBFVUAWWTa3HqhDjxlXHaSWmxf4CZOlZ9iVkrSAbQ8YCYi+7KiKqt35L6F20WhSViWX7lmMjno6zBv2rNQ==} + engines: {node: '>=14'} email-validator@2.0.4: resolution: {integrity: sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==} @@ -4871,12 +4856,8 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding-japanese@2.0.0: - resolution: {integrity: sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==} - engines: {node: '>=8.10.0'} - - encoding-japanese@2.1.0: - resolution: {integrity: sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==} + encoding-japanese@2.2.0: + resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} engines: {node: '>=8.10.0'} encoding@0.1.13: @@ -4934,6 +4915,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -4949,10 +4934,18 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} @@ -5198,12 +5191,16 @@ packages: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expand-tilde@2.0.2: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} - exponential-backoff@3.1.1: - resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + exponential-backoff@3.1.2: + resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} express-openapi-validator@4.13.8: resolution: {integrity: sha512-89/sdkq+BKBuIyykaMl/vR9grFc3WFUPTjFo0THHbu+5g+q8rA7fKeoMfz+h84yOQIBcztmJ5ZJdk5uhEls31A==} @@ -5218,12 +5215,8 @@ packages: resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} engines: {node: '>= 0.8.0'} - express-session@1.18.0: - resolution: {integrity: sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==} - engines: {node: '>= 0.8.0'} - - express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} extend-object@1.0.0: @@ -5269,12 +5262,12 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-printf@1.6.9: - resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} + fast-printf@1.6.10: + resolution: {integrity: sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==} engines: {node: '>=10.0'} - fast-xml-parser@4.5.1: - resolution: {integrity: sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==} + fast-xml-parser@4.5.3: + resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} hasBin: true fastq@1.17.1: @@ -5304,6 +5297,9 @@ packages: file-stream-rotator@0.6.1: resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5312,8 +5308,8 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} find-cache-dir@2.1.0: @@ -5364,8 +5360,8 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} - flow-parser@0.258.0: - resolution: {integrity: sha512-/f3ui3WaPTRUtqnWaGzf/f352hn4VhqGOiuSVkgaW6SbHNp5EwdDoh6BF3zB9A6kcWhCpg/0x0A3aXU+KXugAA==} + flow-parser@0.261.2: + resolution: {integrity: sha512-RtunoakA3YjtpAxPSOBVW6lmP5NYmETwkpAfNkdr8Ovf86ENkbD3mtPWnswFTIUtRvjwv0i8ZSkHK+AzsUg1JA==} engines: {node: '>=0.4.0'} fn.name@1.1.0: @@ -5385,6 +5381,10 @@ packages: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + formik@2.4.6: resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==} peerDependencies: @@ -5407,6 +5407,9 @@ packages: fromentries@1.3.2: resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.2.0: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} @@ -5463,6 +5466,10 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-paths@0.0.7: resolution: {integrity: sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==} engines: {node: '>=6.4'} @@ -5471,6 +5478,10 @@ packages: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@3.0.0: resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} engines: {node: '>=4'} @@ -5504,6 +5515,9 @@ packages: engines: {node: '>=10'} hasBin: true + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5565,6 +5579,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -5618,6 +5636,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -5677,11 +5699,6 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} - html-to-text@8.2.1: - resolution: {integrity: sha512-aN/3JvAk8qFsWVeE9InWAWueLXrbkoVZy0TkzaGhoRBC2gCFEeRLDDJN3/ijIGHohy6H+SZzUQWN/hcYtaPK8w==} - engines: {node: '>=10.23.2'} - hasBin: true - html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} @@ -5692,19 +5709,12 @@ packages: htmlparser2@5.0.1: resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} - htmlparser2@6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} - htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - http-errors@1.7.3: - resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} - engines: {node: '>= 0.6'} - http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -5725,8 +5735,8 @@ packages: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} - http-signature@1.3.6: - resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} + http-signature@1.4.0: + resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} engines: {node: '>=0.10'} http_ece@1.1.0: @@ -5764,8 +5774,8 @@ packages: i18n-locales@0.0.5: resolution: {integrity: sha512-Kve1AHy6rqyfJHPy8MIvaKBKhHhHPXV+a/TgMkjp3UBhO3gfWR40ZQn8Xy7LI6g3FhmbvkFtv+GCZy6yvuyeHQ==} - i18n@0.14.2: - resolution: {integrity: sha512-f/6Ns2skl6KrpumZsE0A4TaxiEoJRi3Ovko0O+NuD92Ot2sLICpw6Iy+04ph/4tfF7koAWVYElBJ4oftpyhhxw==} + i18n@0.15.1: + resolution: {integrity: sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==} engines: {node: '>=10'} iconv-lite@0.4.24: @@ -5901,10 +5911,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - is-core-module@2.14.0: resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} engines: {node: '>= 0.4'} @@ -6022,6 +6028,10 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -6289,13 +6299,13 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - juice@7.0.0: - resolution: {integrity: sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==} + juice@10.0.1: + resolution: {integrity: sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA==} engines: {node: '>=10.0.0'} hasBin: true - juice@8.1.0: - resolution: {integrity: sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==} + juice@7.0.0: + resolution: {integrity: sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==} engines: {node: '>=10.0.0'} hasBin: true @@ -6351,23 +6361,14 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libbase64@1.2.1: - resolution: {integrity: sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==} - libbase64@1.3.0: resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} - libmime@5.2.0: - resolution: {integrity: sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==} + libmime@5.3.6: + resolution: {integrity: sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==} - libmime@5.3.5: - resolution: {integrity: sha512-nSlR1yRZ43L3cZCiWEw7ali3jY29Hz9CQQ96Oy+sSspYnIP5N54ucOPHqooBsXzwrX1pwn13VUE05q4WmzfaLg==} - - libqp@2.0.1: - resolution: {integrity: sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==} - - libqp@2.1.0: - resolution: {integrity: sha512-O6O6/fsG5jiUVbvdgT7YX3xY3uIadR6wEZ7+vy9u7PKHAlSEB6blvC1o5pHBjgsi95Uo0aiBBdkyFecj6jtb7A==} + libqp@2.1.1: + resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} @@ -6561,11 +6562,11 @@ packages: resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==} engines: {node: '>=12'} - mailparser@3.7.1: - resolution: {integrity: sha512-RCnBhy5q8XtB3mXzxcAfT1huNqN93HTYYyL6XawlIKycfxM/rXPg9tXoZ7D46+SgCS1zxKzw+BayDQSvncSTTw==} + mailparser@3.7.2: + resolution: {integrity: sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==} - mailsplit@5.4.0: - resolution: {integrity: sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==} + mailsplit@5.4.2: + resolution: {integrity: sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==} make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} @@ -6618,6 +6619,10 @@ packages: resolution: {integrity: sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==} engines: {node: '>=0.10.0'} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + md5-hex@3.0.1: resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} engines: {node: '>=8'} @@ -6658,8 +6663,8 @@ packages: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -6796,10 +6801,6 @@ packages: micromark@3.2.0: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} - engines: {node: '>=8.6'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -6839,6 +6840,10 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -6915,6 +6920,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -6972,21 +6980,20 @@ packages: nanoclone@0.2.1: resolution: {integrity: sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - nearley@2.20.1: - resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} - hasBin: true - negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -7001,8 +7008,8 @@ packages: nerf-dart@1.0.0: resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==} - next@14.2.4: - resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==} + next@14.2.24: + resolution: {integrity: sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -7026,15 +7033,19 @@ packages: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} engines: {node: '>=12.0.0'} + node-abi@3.74.0: + resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} + engines: {node: '>=10'} + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - node-addon-api@4.3.0: - resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} - node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-cache@5.1.2: resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} engines: {node: '>= 8.0.0'} @@ -7086,21 +7097,17 @@ packages: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} - nodemailer@6.9.1: - resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==} + nodemailer@6.10.0: + resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==} engines: {node: '>=6.0.0'} - nodemailer@6.9.13: - resolution: {integrity: sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==} + nodemailer@6.9.16: + resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} engines: {node: '>=6.0.0'} - nodemailer@6.9.14: - resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} - engines: {node: '>=6.0.0'} - - nodemon@2.0.20: - resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==} - engines: {node: '>=8.10.0'} + nodemon@3.1.9: + resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==} + engines: {node: '>=10'} hasBin: true noms@0.0.0: @@ -7264,6 +7271,10 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + object-is@1.1.6: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} @@ -7333,8 +7344,8 @@ packages: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} - openpgp@5.7.0: - resolution: {integrity: sha512-wchYJQfFbSaocUvUIYqNrWD+lRSmFSG1d3Ak2CHeXFocDSEsf7Uc1zUzHjSdlZPTvGeeXPQ+MJrwVtalL4QCBg==} + openpgp@5.11.2: + resolution: {integrity: sha512-f8dJFVLwdkvPvW3VPFs6q9Vs2+HNhdvwls7a/MIFcQUB+XiQzRe7alfa3RtwfGJU7oUDDMAWPZ0nYsHa23Az+A==} engines: {node: '>= 8.0.0'} optionator@0.9.4: @@ -7465,9 +7476,6 @@ packages: parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} - parseley@0.7.0: - resolution: {integrity: sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -7503,11 +7511,11 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -7649,17 +7657,13 @@ packages: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -7680,6 +7684,11 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -7770,13 +7779,17 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - preview-email@3.0.20: - resolution: {integrity: sha512-QbAokW2F3p0thQfp2WTZ0rBy+IZuCnf9gIUCLffr+8hq85esq6pzCA7S0eUdD6oTmtKROqoNeH2rXZWrRow7EA==} + preview-email@3.1.0: + resolution: {integrity: sha512-ZtV1YrwscEjlrUzYrTSs6Nwo49JM3pXLM4fFOBSC3wSni+bxaWlw9/Qgk75PZO8M7cX2EybmL2iwvaV3vkAttw==} engines: {node: '>=14'} process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -7818,8 +7831,8 @@ packages: proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} @@ -7857,14 +7870,11 @@ packages: pug-walk@2.0.0: resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} - pug@3.0.2: - resolution: {integrity: sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==} - pug@3.0.3: resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} @@ -7882,17 +7892,16 @@ packages: (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) - qs@6.10.5: - resolution: {integrity: sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==} - engines: {node: '>=0.6'} - deprecated: when using stringify with arrayFormat comma, `[]` is appended on single-item arrays. Upgrade to v6.11.0 or downgrade to v6.10.4 to fix. - - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - qs@6.12.1: - resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} + qs@6.13.1: + resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} + engines: {node: '>=0.6'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} qs@6.5.3: @@ -7904,9 +7913,6 @@ packages: engines: {node: '>=0.4.x'} deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-lit@1.5.2: resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} engines: {node: '>=12'} @@ -7925,13 +7931,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - railroad-diagrams@1.0.0: - resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} - - randexp@0.4.6: - resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} - engines: {node: '>=0.12'} - random-bytes@1.0.0: resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} engines: {node: '>= 0.8'} @@ -7940,8 +7939,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} rc@1.2.8: @@ -8103,11 +8102,14 @@ packages: react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - react-use-measure@2.1.1: - resolution: {integrity: sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==} + react-use-measure@2.1.7: + resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} peerDependencies: react: '>=16.13' react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true react-zdog@1.2.2: resolution: {integrity: sha512-Ix7ALha91aOEwiHuxumCeYbARS5XNpc/w0v145oGkM6poF/CvhKJwzLhM5sEZbtrghMA+psAhOJkCTzJoseicA==} @@ -8251,9 +8253,6 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -8297,10 +8296,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -8322,9 +8317,6 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rndm@1.2.0: - resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} - run-applescript@3.2.0: resolution: {integrity: sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==} engines: {node: '>=4'} @@ -8339,6 +8331,9 @@ packages: rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -8388,9 +8383,6 @@ packages: selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} - selderee@0.6.0: - resolution: {integrity: sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==} - selfsigned@2.4.1: resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} engines: {node: '>=10'} @@ -8421,34 +8413,16 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.0.0: - resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} - hasBin: true - - semver@7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} - engines: {node: '>=10'} - hasBin: true - semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} - send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -8457,10 +8431,6 @@ packages: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} - serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -8476,9 +8446,6 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - setprototypeof@1.1.1: - resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -8514,10 +8481,26 @@ packages: resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} engines: {node: '>= 0.4'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -8529,12 +8512,18 @@ packages: resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} engines: {node: '>=6'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - simple-update-notifier@1.1.0: - resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} - engines: {node: '>=8.10.0'} + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8578,11 +8567,15 @@ packages: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + socks@2.8.4: + resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sorted-array-functions@1.3.0: resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map-support@0.5.21: @@ -8637,8 +8630,8 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sqlite3@5.1.4: - resolution: {integrity: sha512-i0UlWAzPlzX3B5XP2cYuhWQJsTtlMD6obOa1PgeEQ4DHEXUuyJkgv50I3isqZAP5oFc2T8OFvakmDh2W6I+YpA==} + sqlite3@5.1.7: + resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} sshpk@1.18.0: resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} @@ -8667,8 +8660,8 @@ packages: stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - stacktrace-parser@0.1.10: - resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} engines: {node: '>=6'} statuses@1.5.0: @@ -8777,8 +8770,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + strnum@1.1.1: + resolution: {integrity: sha512-O7aCHfYCamLCctjAiaucmE+fHf2DYHkus2OKCn4Wv03sykfFtgeECn505X6K4mPl8CRNd/qurC9guq+ynoN4pw==} style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} @@ -8864,6 +8857,13 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -8880,8 +8880,8 @@ packages: resolution: {integrity: sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==} engines: {node: '>=10'} - terser@5.37.0: - resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} + terser@5.39.0: + resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} engines: {node: '>=10'} hasBin: true @@ -8927,12 +8927,15 @@ packages: resolution: {integrity: sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g==} engines: {node: '>=6'} - tlds@1.252.0: - resolution: {integrity: sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ==} + tlds@1.255.0: + resolution: {integrity: sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==} hasBin: true - tlds@1.253.0: - resolution: {integrity: sha512-lNov5nt5/xw6nK00gtoQSA2I4HcpAnot1TMJccTNw2rtL5jdLN26h3f+mT8VF4JBv5/rBNXyuUPWcogceyKJJw==} + tldts-core@6.1.78: + resolution: {integrity: sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==} + + tldts@6.1.78: + resolution: {integrity: sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==} hasBin: true tmp@0.0.33: @@ -8957,10 +8960,6 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - toidentifier@1.0.0: - resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} - engines: {node: '>=0.6'} - toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -8979,9 +8978,9 @@ packages: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} + tough-cookie@5.1.1: + resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==} + engines: {node: '>=16'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -8990,6 +8989,10 @@ packages: resolution: {integrity: sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==} engines: {node: '>= 0.4'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -9061,10 +9064,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsutils@3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -9232,8 +9231,8 @@ packages: peerDependencies: underscore: 1.x - underscore@1.13.6: - resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -9241,9 +9240,9 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - undici@6.20.1: - resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} - engines: {node: '>=18.17'} + undici@7.3.0: + resolution: {integrity: sha512-Qy96NND4Dou5jKoSJ2gm8ax8AJM/Ey9o9mz7KN1bb9GP+G0l20Zw8afxTnY2f4b7hmhn/z8aC2kfArVQlAhFBw==} + engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} @@ -9314,10 +9313,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -9336,8 +9331,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -9348,9 +9343,6 @@ packages: url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - urlsafe-base64@1.0.0: resolution: {integrity: sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==} @@ -9653,8 +9645,8 @@ packages: zdog@1.1.3: resolution: {integrity: sha512-raRj6r0gPzopFm5XWBJZr/NuV4EEnT4iE+U3dp5FV5pCb588Gmm3zLIp/j9yqqcMiHH8VNQlerLTgOqL7krh6w==} - zod@3.20.6: - resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==} + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} zustand@3.7.2: resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==} @@ -9692,7 +9684,7 @@ snapshots: '@babel/compat-data@7.24.7': {} - '@babel/compat-data@7.26.3': {} + '@babel/compat-data@7.26.8': {} '@babel/core@7.24.7': dependencies: @@ -9707,27 +9699,27 @@ snapshots: '@babel/traverse': 7.24.7 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/core@7.26.0': + '@babel/core@7.26.9': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.3 - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/generator': 7.26.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helpers': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -9741,10 +9733,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - '@babel/generator@7.26.3': + '@babel/generator@7.26.9': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 @@ -9755,7 +9747,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.9 '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': dependencies: @@ -9772,9 +9764,9 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-compilation-targets@7.25.9': + '@babel/helper-compilation-targets@7.26.5': dependencies: - '@babel/compat-data': 7.26.3 + '@babel/compat-data': 7.26.8 '@babel/helper-validator-option': 7.25.9 browserslist: 4.24.3 lru-cache: 5.1.1 @@ -9795,28 +9787,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.24.7)': + '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.24.7) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.24.7) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.9) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -9840,18 +9832,18 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - supports-color '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -9879,8 +9871,8 @@ snapshots: '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color @@ -9893,8 +9885,8 @@ snapshots: '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color @@ -9914,16 +9906,16 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color @@ -9933,11 +9925,11 @@ snapshots: '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.9 '@babel/helper-plugin-utils@7.24.7': {} - '@babel/helper-plugin-utils@7.25.9': {} + '@babel/helper-plugin-utils@7.26.5': {} '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': dependencies: @@ -9953,7 +9945,7 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color @@ -9966,21 +9958,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.9(@babel/core@7.24.7)': + '@babel/helper-replace-supers@7.26.5(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color @@ -10000,8 +9992,8 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color @@ -10032,9 +10024,9 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color @@ -10043,10 +10035,10 @@ snapshots: '@babel/template': 7.24.7 '@babel/types': 7.24.7 - '@babel/helpers@7.26.0': + '@babel/helpers@7.26.9': dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 '@babel/highlight@7.24.7': dependencies: @@ -10059,9 +10051,9 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@babel/parser@7.26.3': + '@babel/parser@7.26.9': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.9 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10093,7 +10085,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.24.7) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) transitivePeerDependencies: @@ -10102,78 +10094,78 @@ snapshots: '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color '@babel/plugin-proposal-export-default-from@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.9) '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.24.7)': dependencies: - '@babel/compat-data': 7.26.3 + '@babel/compat-data': 7.26.8 '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.24.7) '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.26.0)': + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.9) transitivePeerDependencies: - supports-color @@ -10204,7 +10196,7 @@ snapshots: '@babel/plugin-syntax-export-default-from@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)': dependencies: @@ -10214,12 +10206,12 @@ snapshots: '@babel/plugin-syntax-flow@7.26.0(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10249,12 +10241,12 @@ snapshots: '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': dependencies: @@ -10266,9 +10258,9 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': @@ -10291,9 +10283,9 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)': @@ -10314,12 +10306,12 @@ snapshots: '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)': dependencies: @@ -10335,7 +10327,7 @@ snapshots: '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10360,7 +10352,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.24.7) transitivePeerDependencies: - supports-color @@ -10378,7 +10370,7 @@ snapshots: '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10415,10 +10407,10 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.24.7) - '@babel/traverse': 7.26.4 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.24.7) + '@babel/traverse': 7.26.9 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10432,8 +10424,8 @@ snapshots: '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/template': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.26.9 '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10443,7 +10435,7 @@ snapshots: '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10476,17 +10468,17 @@ snapshots: '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-transform-flow-strip-types@7.25.9(@babel/core@7.24.7)': + '@babel/plugin-transform-flow-strip-types@7.26.5(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.24.7) - '@babel/plugin-transform-flow-strip-types@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-flow-strip-types@7.26.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.9) '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10506,9 +10498,9 @@ snapshots: '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color @@ -10526,7 +10518,7 @@ snapshots: '@babel/plugin-transform-literals@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10560,15 +10552,15 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -10600,7 +10592,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10658,7 +10650,7 @@ snapshots: '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10671,8 +10663,8 @@ snapshots: '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -10690,8 +10682,8 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -10713,7 +10705,7 @@ snapshots: '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10725,12 +10717,12 @@ snapshots: '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10748,9 +10740,9 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.24.7) - '@babel/types': 7.26.3 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color @@ -10771,11 +10763,11 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-transform-runtime@7.25.9(@babel/core@7.24.7)': + '@babel/plugin-transform-runtime@7.26.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.24.7) babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.24.7) babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.24.7) @@ -10791,7 +10783,7 @@ snapshots: '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10804,7 +10796,7 @@ snapshots: '@babel/plugin-transform-spread@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color @@ -10817,7 +10809,7 @@ snapshots: '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10839,25 +10831,25 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.26.3(@babel/core@7.24.7)': + '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.24.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color @@ -10882,7 +10874,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)': dependencies: @@ -10977,12 +10969,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-flow@7.25.9(@babel/core@7.26.0)': + '@babel/preset-flow@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-flow-strip-types': 7.26.5(@babel/core@7.26.9) '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)': dependencies: @@ -11014,20 +11006,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': + '@babel/preset-typescript@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) + '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/register@7.25.9(@babel/core@7.26.0)': + '@babel/register@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -11044,17 +11036,21 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.26.9': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - '@babel/template@7.25.9': + '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 '@babel/traverse@7.24.7': dependencies: @@ -11066,19 +11062,19 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/traverse@7.26.4': + '@babel/traverse@7.26.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 - '@babel/template': 7.25.9 - '@babel/types': 7.26.3 - debug: 4.4.0 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.0(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11089,7 +11085,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@babel/types@7.26.3': + '@babel/types@7.26.9': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -11260,24 +11256,24 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@cypress/request@2.88.12': + '@cypress/request@3.0.7': dependencies: aws-sign2: 0.7.0 - aws4: 1.13.0 + aws4: 1.13.2 caseless: 0.12.0 combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 + form-data: 4.0.2 + http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 json-stringify-safe: 5.0.1 mime-types: 2.1.35 performance-now: 2.1.0 - qs: 6.10.5 + qs: 6.13.1 safe-buffer: 5.2.1 - tough-cookie: 4.1.4 + tough-cookie: 5.1.1 tunnel-agent: 0.6.0 uuid: 8.3.2 @@ -11415,7 +11411,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -11554,9 +11550,11 @@ snapshots: '@gar/promisify@1.1.3': {} - '@hapi/boom@9.1.4': + '@hapi/boom@10.0.1': dependencies: - '@hapi/hoek': 9.3.0 + '@hapi/hoek': 11.0.7 + + '@hapi/hoek@11.0.7': {} '@hapi/hoek@9.3.0': {} @@ -11577,7 +11575,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -11767,25 +11765,37 @@ snapshots: '@jsdevtools/ono@7.1.3': {} + '@ladjs/consolidate@1.0.4(@babel/core@7.24.7)(handlebars@4.7.8)(lodash@4.17.21)(mustache@4.2.0)(pug@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.7)': + optionalDependencies: + '@babel/core': 7.24.7 + handlebars: 4.7.8 + lodash: 4.17.21 + mustache: 4.2.0 + pug: 3.0.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + underscore: 1.13.7 + '@ladjs/country-language@0.2.1': dependencies: - underscore: 1.13.6 - underscore.deep: 0.5.3(underscore@1.13.6) + underscore: 1.13.7 + underscore.deep: 0.5.3(underscore@1.13.7) - '@ladjs/i18n@7.2.6': + '@ladjs/country-language@1.0.3': {} + + '@ladjs/i18n@8.0.3': dependencies: - '@hapi/boom': 9.1.4 - '@ladjs/country-language': 0.2.1 + '@hapi/boom': 10.0.1 + '@ladjs/country-language': 1.0.3 boolean: 3.2.0 - debug: 4.3.5(supports-color@8.1.1) - i18n: 0.14.2 + i18n: 0.15.1 i18n-locales: 0.0.5 lodash: 4.17.21 multimatch: 5.0.0 punycode: 2.3.1 - qs: 6.12.1 + qs: 6.14.0 titleize: 2.1.0 - tlds: 1.253.0 + tlds: 1.255.0 transitivePeerDependencies: - supports-color @@ -11798,26 +11808,26 @@ snapshots: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.7.1 tar: 6.2.1 transitivePeerDependencies: - encoding - supports-color - '@messageformat/core@3.3.0': + '@messageformat/core@3.4.0': dependencies: - '@messageformat/date-skeleton': 1.0.1 + '@messageformat/date-skeleton': 1.1.0 '@messageformat/number-skeleton': 1.2.0 - '@messageformat/parser': 5.1.0 + '@messageformat/parser': 5.1.1 '@messageformat/runtime': 3.0.1 make-plural: 7.4.0 safe-identifier: 0.4.2 - '@messageformat/date-skeleton@1.0.1': {} + '@messageformat/date-skeleton@1.1.0': {} '@messageformat/number-skeleton@1.2.0': {} - '@messageformat/parser@5.1.0': + '@messageformat/parser@5.1.1': dependencies: moo: 0.5.2 @@ -11825,37 +11835,37 @@ snapshots: dependencies: make-plural: 7.4.0 - '@next/env@14.2.4': {} + '@next/env@14.2.24': {} '@next/eslint-plugin-next@14.2.4': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.4': + '@next/swc-darwin-arm64@14.2.24': optional: true - '@next/swc-darwin-x64@14.2.4': + '@next/swc-darwin-x64@14.2.24': optional: true - '@next/swc-linux-arm64-gnu@14.2.4': + '@next/swc-linux-arm64-gnu@14.2.24': optional: true - '@next/swc-linux-arm64-musl@14.2.4': + '@next/swc-linux-arm64-musl@14.2.24': optional: true - '@next/swc-linux-x64-gnu@14.2.4': + '@next/swc-linux-x64-gnu@14.2.24': optional: true - '@next/swc-linux-x64-musl@14.2.4': + '@next/swc-linux-x64-musl@14.2.24': optional: true - '@next/swc-win32-arm64-msvc@14.2.4': + '@next/swc-win32-arm64-msvc@14.2.24': optional: true - '@next/swc-win32-ia32-msvc@14.2.4': + '@next/swc-win32-ia32-msvc@14.2.24': optional: true - '@next/swc-win32-x64-msvc@14.2.4': + '@next/swc-win32-x64-msvc@14.2.24': optional: true '@nodelib/fs.scandir@2.1.5': @@ -11873,13 +11883,13 @@ snapshots: '@npmcli/fs@1.1.1': dependencies: '@gar/promisify': 1.1.3 - semver: 7.3.8 + semver: 7.7.1 optional: true '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.3.8 + semver: 7.7.1 '@npmcli/move-file@1.1.2': dependencies: @@ -12503,7 +12513,7 @@ snapshots: hermes-profile-transformer: 0.0.6 node-stream-zip: 1.15.0 ora: 5.4.1 - semver: 7.6.3 + semver: 7.7.1 strip-ansi: 5.2.0 wcwidth: 1.0.1 yaml: 2.7.0 @@ -12525,7 +12535,7 @@ snapshots: chalk: 4.1.2 execa: 5.1.1 fast-glob: 3.3.3 - fast-xml-parser: 4.5.1 + fast-xml-parser: 4.5.3 logkitty: 0.7.1 transitivePeerDependencies: - encoding @@ -12536,7 +12546,7 @@ snapshots: chalk: 4.1.2 execa: 5.1.1 fast-glob: 3.3.3 - fast-xml-parser: 4.5.1 + fast-xml-parser: 4.5.3 ora: 5.4.1 transitivePeerDependencies: - encoding @@ -12551,7 +12561,7 @@ snapshots: dependencies: '@react-native-community/cli-debugger-ui': 13.6.8 '@react-native-community/cli-tools': 13.6.8(encoding@0.1.13) - compression: 1.7.5 + compression: 1.8.0 connect: 3.7.0 errorhandler: 1.5.1 nocache: 3.0.4 @@ -12574,7 +12584,7 @@ snapshots: node-fetch: 2.7.0(encoding@0.1.13) open: 6.4.0 ora: 5.4.1 - semver: 7.6.3 + semver: 7.7.1 shell-quote: 1.8.2 sudo-prompt: 9.2.1 transitivePeerDependencies: @@ -12602,7 +12612,7 @@ snapshots: fs-extra: 8.1.0 graceful-fs: 4.2.11 prompts: 2.4.2 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - bufferutil - encoding @@ -12641,7 +12651,7 @@ snapshots: '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.24.7) - '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.24.7) + '@babel/plugin-transform-flow-strip-types': 7.26.5(@babel/core@7.24.7) '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.24.7) @@ -12653,13 +12663,13 @@ snapshots: '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.24.7) - '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.24.7) + '@babel/plugin-transform-runtime': 7.26.9(@babel/core@7.24.7) '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.24.7) '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.24.7) - '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.24.7) + '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.24.7) '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.24.7) - '@babel/template': 7.25.9 + '@babel/template': 7.26.9 '@react-native/babel-plugin-codegen': 0.74.84(@babel/preset-env@7.24.7(@babel/core@7.24.7)) babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.24.7) react-refresh: 0.14.2 @@ -12669,7 +12679,7 @@ snapshots: '@react-native/codegen@0.74.84(@babel/preset-env@7.24.7(@babel/core@7.24.7))': dependencies: - '@babel/parser': 7.26.3 + '@babel/parser': 7.26.9 '@babel/preset-env': 7.24.7(@babel/core@7.24.7) glob: 7.2.3 hermes-parser: 0.19.1 @@ -13027,15 +13037,15 @@ snapshots: '@react-three/fiber@8.16.8(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)(three@0.165.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 '@types/react-reconciler': 0.26.7 - '@types/webxr': 0.5.20 + '@types/webxr': 0.5.21 base64-js: 1.5.1 buffer: 6.0.3 its-fine: 1.2.5(@types/react@18.3.3)(react@18.3.1) react: 18.3.1 react-reconciler: 0.27.0(react@18.3.1) - react-use-measure: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-use-measure: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) scheduler: 0.21.0 suspend-react: 0.1.3(react@18.3.1) three: 0.165.0 @@ -13182,7 +13192,7 @@ snapshots: '@rnx-kit/chromium-edge-launcher@1.0.0': dependencies: - '@types/node': 18.19.70 + '@types/node': 18.19.76 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -13198,11 +13208,6 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 - '@selderee/plugin-htmlparser2@0.6.0': - dependencies: - domhandler: 4.3.1 - selderee: 0.6.0 - '@semantic-release/changelog@6.0.2(semantic-release@19.0.5(encoding@0.1.13))': dependencies: '@semantic-release/error': 3.0.0 @@ -13216,10 +13221,10 @@ snapshots: conventional-changelog-angular: 5.0.13 conventional-commits-filter: 2.0.7 conventional-commits-parser: 3.2.4 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 import-from: 4.0.0 lodash: 4.17.21 - micromatch: 4.0.7 + micromatch: 4.0.8 semantic-release: 19.0.5(encoding@0.1.13) transitivePeerDependencies: - supports-color @@ -13230,7 +13235,7 @@ snapshots: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 @@ -13242,11 +13247,11 @@ snapshots: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 dir-glob: 3.0.1 execa: 5.1.1 lodash: 4.17.21 - micromatch: 4.0.7 + micromatch: 4.0.8 p-reduce: 2.1.0 semantic-release: 19.0.5(encoding@0.1.13) transitivePeerDependencies: @@ -13260,7 +13265,7 @@ snapshots: '@octokit/plugin-throttling': 5.2.3(@octokit/core@4.2.4(encoding@0.1.13)) '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 dir-glob: 3.0.1 fs-extra: 11.2.0 globby: 11.1.0 @@ -13290,7 +13295,7 @@ snapshots: read-pkg: 5.2.0 registry-auth-token: 5.0.2 semantic-release: 19.0.5(encoding@0.1.13) - semver: 7.3.8 + semver: 7.7.1 tempy: 1.0.1 '@semantic-release/release-notes-generator@10.0.3(semantic-release@19.0.5(encoding@0.1.13))': @@ -13299,7 +13304,7 @@ snapshots: conventional-changelog-writer: 5.0.1 conventional-commits-filter: 2.0.7 conventional-commits-parser: 3.2.4 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 get-stream: 6.0.1 import-from: 4.0.0 into-stream: 6.0.0 @@ -13482,22 +13487,22 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': + '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': dependencies: - tailwindcss: 3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + tailwindcss: 3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) - '@tailwindcss/forms@0.5.3(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': + '@tailwindcss/forms@0.5.10(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + tailwindcss: 3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) - '@tailwindcss/typography@0.5.9(tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': + '@tailwindcss/typography@0.5.16(tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)))': dependencies: lodash.castarray: 4.4.0 lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + tailwindcss: 3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) '@tanem/react-nprogress@5.0.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -13642,9 +13647,9 @@ snapshots: '@types/ms@0.7.34': {} - '@types/multer@1.4.11': + '@types/multer@1.4.12': dependencies: - '@types/express': 4.17.21 + '@types/express': 4.17.17 '@types/node-forge@1.3.11': dependencies: @@ -13654,11 +13659,9 @@ snapshots: dependencies: '@types/node': 22.10.5 - '@types/node@14.18.63': {} - '@types/node@17.0.45': {} - '@types/node@18.19.70': + '@types/node@18.19.76': dependencies: undici-types: 5.26.5 @@ -13739,7 +13742,7 @@ snapshots: '@types/sinonjs__fake-timers@8.1.1': {} - '@types/sizzle@2.3.8': {} + '@types/sizzle@2.3.9': {} '@types/stack-utils@2.0.3': {} @@ -13756,7 +13759,7 @@ snapshots: dependencies: '@types/node': 22.10.5 - '@types/webxr@0.5.20': {} + '@types/webxr@0.5.21': {} '@types/wink-jaro-distance@2.0.2': {} @@ -13789,13 +13792,13 @@ snapshots: '@typescript-eslint/scope-manager': 5.54.0 '@typescript-eslint/type-utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) '@typescript-eslint/utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eslint: 8.35.0 grapheme-splitter: 1.0.4 ignore: 5.3.1 natural-compare-lite: 1.4.0 regexpp: 3.2.0 - semver: 7.6.2 + semver: 7.7.1 tsutils: 3.21.0(typescript@4.9.5) optionalDependencies: typescript: 4.9.5 @@ -13807,7 +13810,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.54.0 '@typescript-eslint/types': 5.54.0 '@typescript-eslint/typescript-estree': 5.54.0(typescript@4.9.5) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eslint: 8.35.0 optionalDependencies: typescript: 4.9.5 @@ -13820,7 +13823,7 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/typescript-estree': 7.2.0(typescript@4.9.5) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eslint: 8.35.0 optionalDependencies: typescript: 4.9.5 @@ -13841,7 +13844,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 5.54.0(typescript@4.9.5) '@typescript-eslint/utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 eslint: 8.35.0 tsutils: 3.21.0(typescript@4.9.5) optionalDependencies: @@ -13859,10 +13862,10 @@ snapshots: dependencies: '@typescript-eslint/types': 5.45.0 '@typescript-eslint/visitor-keys': 5.45.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.8 + semver: 7.7.1 tsutils: 3.21.0(typescript@4.9.5) optionalDependencies: typescript: 4.9.5 @@ -13873,10 +13876,10 @@ snapshots: dependencies: '@typescript-eslint/types': 5.54.0 '@typescript-eslint/visitor-keys': 5.54.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.2 + semver: 7.7.1 tsutils: 3.21.0(typescript@4.9.5) optionalDependencies: typescript: 4.9.5 @@ -13887,11 +13890,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.2 + semver: 7.7.1 ts-api-utils: 1.3.0(typescript@4.9.5) optionalDependencies: typescript: 4.9.5 @@ -13908,7 +13911,7 @@ snapshots: eslint: 8.35.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0(eslint@8.35.0) - semver: 7.6.2 + semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript @@ -13972,13 +13975,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -13986,6 +13989,11 @@ snapshots: dependencies: humanize-ms: 1.2.1 + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + optional: true + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -14009,6 +14017,7 @@ snapshots: dependencies: esprima: 1.2.5 estraverse: 1.9.3 + optional: true anser@1.4.10: {} @@ -14181,7 +14190,7 @@ snapshots: asn1.js@5.4.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 inherits: 2.0.4 minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 @@ -14190,7 +14199,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - assert-never@1.2.1: {} + assert-never@1.4.0: {} assert-plus@1.0.0: {} @@ -14210,18 +14219,20 @@ snapshots: async@3.2.5: {} + async@3.2.6: {} + asynckit@0.4.0: {} at-least-node@1.0.0: {} - autoprefixer@10.4.13(postcss@8.4.21): + autoprefixer@10.4.13(postcss@8.4.31): dependencies: browserslist: 4.23.1 caniuse-lite: 1.0.30001636 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -14230,7 +14241,7 @@ snapshots: aws-sign2@0.7.0: {} - aws4@1.13.0: {} + aws4@1.13.2: {} axe-core@4.9.1: {} @@ -14242,9 +14253,9 @@ snapshots: dependencies: deep-equal-json: 1.0.0 - babel-core@7.0.0-bridge.0(@babel/core@7.26.0): + babel-core@7.0.0-bridge.0(@babel/core@7.26.9): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 babel-plugin-emotion@10.2.2: dependencies: @@ -14265,13 +14276,13 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 cosmiconfig: 6.0.0 - resolve: 1.22.8 + resolve: 1.22.10 babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.26.0 cosmiconfig: 7.1.0 - resolve: 1.22.8 + resolve: 1.22.10 babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.7): dependencies: @@ -14284,7 +14295,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.24.7): dependencies: - '@babel/compat-data': 7.26.3 + '@babel/compat-data': 7.26.8 '@babel/core': 7.24.7 '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.24.7) semver: 6.3.1 @@ -14303,7 +14314,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.24.7) - core-js-compat: 3.39.0 + core-js-compat: 3.40.0 transitivePeerDependencies: - supports-color @@ -14331,7 +14342,7 @@ snapshots: babel-walk@3.0.0-canary-5: dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.26.9 bail@2.0.2: {} @@ -14355,6 +14366,10 @@ snapshots: binary-extensions@2.3.0: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -14367,9 +14382,9 @@ snapshots: blueimp-md5@2.19.0: {} - bn.js@4.12.0: {} + bn.js@4.12.1: {} - body-parser@1.20.1: + body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -14379,8 +14394,8 @@ snapshots: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 + qs: 6.13.0 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: @@ -14416,10 +14431,10 @@ snapshots: browserslist@4.24.3: dependencies: - caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.77 + caniuse-lite: 1.0.30001700 + electron-to-chromium: 1.5.103 node-releases: 2.0.19 - update-browserslist-db: 1.1.1(browserslist@4.24.3) + update-browserslist-db: 1.1.2(browserslist@4.24.3) bser@2.1.1: dependencies: @@ -14500,6 +14515,11 @@ snapshots: cachedir@2.4.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -14508,6 +14528,11 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + call-me-maybe@1.0.2: {} caller-callsite@2.0.0: @@ -14536,7 +14561,7 @@ snapshots: caniuse-lite@1.0.30001636: {} - caniuse-lite@1.0.30001690: {} + caniuse-lite@1.0.30001700: {} cardinal@2.1.1: dependencies: @@ -14555,6 +14580,7 @@ snapshots: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + optional: true chalk@4.1.2: dependencies: @@ -14567,20 +14593,12 @@ snapshots: character-parser@2.2.0: dependencies: - is-regex: 1.1.4 + is-regex: 1.2.1 chardet@0.7.0: {} check-more-types@2.24.0: {} - cheerio-select@1.6.0: - dependencies: - css-select: 4.3.0 - css-what: 6.1.0 - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -14590,16 +14608,6 @@ snapshots: domhandler: 5.0.3 domutils: 3.1.0 - cheerio@1.0.0-rc.10: - dependencies: - cheerio-select: 1.6.0 - dom-serializer: 1.4.1 - domhandler: 4.3.1 - htmlparser2: 6.1.0 - parse5: 6.0.1 - parse5-htmlparser2-tree-adapter: 6.0.1 - tslib: 2.6.3 - cheerio@1.0.0-rc.12: dependencies: cheerio-select: 2.1.0 @@ -14622,6 +14630,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@1.1.4: {} + chownr@2.0.0: {} chrome-launcher@0.15.2: @@ -14637,6 +14647,8 @@ snapshots: ci-info@3.9.0: {} + ci-info@4.1.0: {} + classnames@2.5.1: {} clean-stack@2.2.0: {} @@ -14795,7 +14807,7 @@ snapshots: dependencies: mime-db: 1.53.0 - compression@1.7.5: + compression@1.8.0: dependencies: bytes: 3.1.2 compressible: 2.0.18 @@ -14823,13 +14835,13 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - connect-typeorm@1.1.4(typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))): + connect-typeorm@1.1.4(typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5))): dependencies: '@types/debug': 0.0.31 '@types/express-session': 1.17.6 - debug: 4.3.5(supports-color@8.1.1) - express-session: 1.18.0 - typeorm: 0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + debug: 4.4.0(supports-color@5.5.0) + express-session: 1.17.3 + typeorm: 0.3.11(pg@8.11.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) transitivePeerDependencies: - supports-color @@ -14844,22 +14856,10 @@ snapshots: console-control-strings@1.1.0: {} - consolidate@0.16.0(handlebars@4.7.8)(lodash@4.17.21)(mustache@4.2.0)(pug@3.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.6): - dependencies: - bluebird: 3.7.2 - optionalDependencies: - handlebars: 4.7.8 - lodash: 4.17.21 - mustache: 4.2.0 - pug: 3.0.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - underscore: 1.13.6 - constantinople@4.0.1: dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 content-disposition@0.5.4: dependencies: @@ -14921,24 +14921,18 @@ snapshots: convert-source-map@2.0.0: {} - cookie-parser@1.4.6: + cookie-parser@1.4.7: dependencies: - cookie: 0.4.1 + cookie: 0.7.2 cookie-signature: 1.0.6 cookie-signature@1.0.6: {} - cookie-signature@1.0.7: {} - - cookie@0.4.0: {} - - cookie@0.4.1: {} - cookie@0.4.2: {} - cookie@0.5.0: {} + cookie@0.7.1: {} - cookie@0.6.0: {} + cookie@0.7.2: {} copy-to-clipboard@3.3.3: dependencies: @@ -14958,7 +14952,7 @@ snapshots: dependencies: browserslist: 4.23.1 - core-js-compat@3.39.0: + core-js-compat@3.40.0: dependencies: browserslist: 4.24.3 @@ -15033,15 +15027,16 @@ snapshots: cronstrue@2.23.0: {} - cross-spawn@6.0.5: + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 path-key: 2.0.1 semver: 5.7.2 shebang-command: 1.2.0 which: 1.3.1 + optional: true - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -15049,11 +15044,9 @@ snapshots: crypto-random-string@2.0.0: {} - csrf@3.1.0: + csrf-csrf@3.1.0: dependencies: - rndm: 1.2.0 - tsscmp: 1.0.6 - uid-safe: 2.1.5 + http-errors: 2.0.0 css-select@4.3.0: dependencies: @@ -15088,22 +15081,14 @@ snapshots: csstype@3.1.3: {} - csurf@1.11.0: - dependencies: - cookie: 0.4.0 - cookie-signature: 1.0.6 - csrf: 3.1.0 - http-errors: 1.7.3 - cy-mobile-commands@0.3.0: {} - cypress@12.7.0: + cypress@14.1.0: dependencies: - '@cypress/request': 2.88.12 + '@cypress/request': 3.0.7 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/node': 14.18.63 '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.8 + '@types/sizzle': 2.3.9 arch: 2.2.0 blob-util: 2.0.2 bluebird: 3.7.2 @@ -15111,12 +15096,13 @@ snapshots: cachedir: 2.4.0 chalk: 4.1.2 check-more-types: 2.24.0 + ci-info: 4.1.0 cli-cursor: 3.1.0 cli-table3: 0.6.5 - commander: 5.1.0 + commander: 6.2.1 common-tags: 1.8.2 - dayjs: 1.11.11 - debug: 4.3.5(supports-color@8.1.1) + dayjs: 1.11.7 + debug: 4.4.0(supports-color@8.1.1) enquirer: 2.4.1 eventemitter2: 6.4.7 execa: 4.1.0 @@ -15125,7 +15111,6 @@ snapshots: figures: 3.2.0 fs-extra: 9.1.0 getos: 3.2.1 - is-ci: 3.0.1 is-installed-globally: 0.4.0 lazy-ass: 1.6.0 listr2: 3.14.0(enquirer@2.4.1) @@ -15134,11 +15119,13 @@ snapshots: minimist: 1.2.8 ospath: 1.2.2 pretty-bytes: 5.6.0 + process: 0.11.10 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.6.2 + semver: 7.7.1 supports-color: 8.1.1 tmp: 0.2.3 + tree-kill: 1.2.2 untildify: 4.0.0 yauzl: 2.10.0 @@ -15186,37 +15173,33 @@ snapshots: dateformat@3.0.3: {} - dayjs@1.11.11: {} - dayjs@1.11.7: {} - debounce@1.2.1: {} - debug@2.6.9: dependencies: ms: 2.0.0 - debug@3.2.7(supports-color@5.5.0): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 - debug@3.2.7(supports-color@8.1.1): dependencies: ms: 2.1.3 optionalDependencies: supports-color: 8.1.1 - debug@4.3.5(supports-color@8.1.1): + debug@4.3.5: dependencies: ms: 2.1.2 - optionalDependencies: - supports-color: 8.1.1 - debug@4.4.0: + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + debug@4.4.0(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decamelize-keys@1.1.1: dependencies: @@ -15229,6 +15212,10 @@ snapshots: dependencies: character-entities: 2.0.2 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + dedent@0.7.0: {} deep-equal-json@1.0.0: @@ -15254,7 +15241,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 + side-channel: 1.1.0 which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 @@ -15302,8 +15289,6 @@ snapshots: denodeify@1.2.1: {} - depd@1.1.2: {} - depd@2.0.0: {} deprecation@2.3.1: {} @@ -15318,7 +15303,8 @@ snapshots: detect-libc@2.0.3: {} - detect-newline@3.1.0: {} + detect-newline@3.1.0: + optional: true detective@5.2.1: dependencies: @@ -15338,12 +15324,11 @@ snapshots: dependencies: path-type: 4.0.0 - discontinuous-range@1.0.0: {} - display-notification@2.0.0: dependencies: escape-string-applescript: 1.0.0 run-applescript: 3.2.0 + optional: true dlv@1.1.3: {} @@ -15406,6 +15391,12 @@ snapshots: dotenv@16.4.5: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 @@ -15425,23 +15416,23 @@ snapshots: electron-to-chromium@1.4.810: {} - electron-to-chromium@1.5.77: {} + electron-to-chromium@1.5.103: {} - email-templates@9.0.0(encoding@0.1.13)(handlebars@4.7.8)(mustache@4.2.0)(pug@3.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.6): + email-templates@12.0.1(@babel/core@7.24.7)(encoding@0.1.13)(handlebars@4.7.8)(mustache@4.2.0)(pug@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.7): dependencies: - '@ladjs/i18n': 7.2.6 - consolidate: 0.16.0(handlebars@4.7.8)(lodash@4.17.21)(mustache@4.2.0)(pug@3.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.6) - debug: 4.3.5(supports-color@8.1.1) + '@ladjs/consolidate': 1.0.4(@babel/core@7.24.7)(handlebars@4.7.8)(lodash@4.17.21)(mustache@4.2.0)(pug@3.0.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(underscore@1.13.7) + '@ladjs/i18n': 8.0.3 get-paths: 0.0.7 - html-to-text: 8.2.1 - juice: 8.1.0(encoding@0.1.13) + html-to-text: 9.0.5 + juice: 10.0.1(encoding@0.1.13) lodash: 4.17.21 - nodemailer: 6.9.14 - preview-email: 3.0.20 + nodemailer: 6.10.0 + optionalDependencies: + preview-email: 3.1.0 transitivePeerDependencies: + - '@babel/core' - arc-templates - atpl - - babel-core - bracket-template - coffee-script - dot @@ -15458,13 +15449,11 @@ snapshots: - handlebars - hogan.js - htmling - - jade - jazz - jqtpl - just - liquid-node - liquor - - marko - mote - mustache - nunjucks @@ -15472,17 +15461,14 @@ snapshots: - pug - qejs - ractive - - razor-tmpl - react - react-dom - slm - - squirrelly - supports-color - swig - swig-templates - teacup - templayed - - then-jade - then-pug - tinyliquid - toffee @@ -15510,9 +15496,8 @@ snapshots: encodeurl@2.0.0: {} - encoding-japanese@2.0.0: {} - - encoding-japanese@2.1.0: {} + encoding-japanese@2.2.0: + optional: true encoding@0.1.13: dependencies: @@ -15615,6 +15600,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-get-iterator@1.1.3: @@ -15650,12 +15637,23 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.2 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.2 @@ -15674,7 +15672,8 @@ snapshots: escape-html@1.0.3: {} - escape-string-applescript@1.0.0: {} + escape-string-applescript@1.0.0: + optional: true escape-string-regexp@1.0.5: {} @@ -15706,7 +15705,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.14.0 resolve: 1.22.8 transitivePeerDependencies: @@ -15714,7 +15713,7 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.35.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.35.0): dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.35.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.35.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.35.0) @@ -15731,7 +15730,7 @@ snapshots: eslint-module-utils@2.8.1(@typescript-eslint/parser@5.54.0(eslint@8.35.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint@8.35.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 5.54.0(eslint@8.35.0)(typescript@4.9.5) eslint: 8.35.0 @@ -15741,7 +15740,7 @@ snapshots: eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.35.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.35.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.35.0)(typescript@4.9.5) eslint: 8.35.0 @@ -15774,7 +15773,7 @@ snapshots: array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.35.0 eslint-import-resolver-node: 0.3.9 @@ -15922,8 +15921,8 @@ snapshots: '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.5(supports-color@8.1.1) + cross-spawn: 7.0.6 + debug: 4.3.5 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -15964,7 +15963,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.12.0) eslint-visitor-keys: 3.4.3 - esprima@1.2.5: {} + esprima@1.2.5: + optional: true esprima@4.0.1: {} @@ -15976,7 +15976,8 @@ snapshots: dependencies: estraverse: 5.3.0 - estraverse@1.9.3: {} + estraverse@1.9.3: + optional: true estraverse@4.3.0: {} @@ -15992,17 +15993,18 @@ snapshots: execa@0.10.0: dependencies: - cross-spawn: 6.0.5 + cross-spawn: 6.0.6 get-stream: 3.0.0 is-stream: 1.1.0 npm-run-path: 2.0.2 p-finally: 1.0.0 signal-exit: 3.0.7 strip-eof: 1.0.0 + optional: true execa@4.1.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 5.2.0 human-signals: 1.1.1 is-stream: 2.0.1 @@ -16014,7 +16016,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -16026,7 +16028,7 @@ snapshots: execa@6.1.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 3.0.1 is-stream: 3.0.0 @@ -16040,15 +16042,17 @@ snapshots: dependencies: pify: 2.3.0 + expand-template@2.0.3: {} + expand-tilde@2.0.2: dependencies: homedir-polyfill: 1.0.3 - exponential-backoff@3.1.1: {} + exponential-backoff@3.1.2: {} express-openapi-validator@4.13.8: dependencies: - '@types/multer': 1.4.11 + '@types/multer': 1.4.12 ajv: 6.12.6 content-type: 1.0.5 json-schema-ref-parser: 9.0.9 @@ -16059,11 +16063,11 @@ snapshots: media-typer: 1.1.0 multer: 1.4.5-lts.1 ono: 7.1.3 - path-to-regexp: 6.2.2 + path-to-regexp: 6.3.0 - express-rate-limit@6.7.0(express@4.18.2): + express-rate-limit@6.7.0(express@4.21.2): dependencies: - express: 4.18.2 + express: 4.21.2 express-session@1.17.3: dependencies: @@ -16078,47 +16082,34 @@ snapshots: transitivePeerDependencies: - supports-color - express-session@1.18.0: - dependencies: - cookie: 0.6.0 - cookie-signature: 1.0.7 - debug: 2.6.9 - depd: 2.0.0 - on-headers: 1.0.2 - parseurl: 1.3.3 - safe-buffer: 5.2.1 - uid-safe: 2.1.5 - transitivePeerDependencies: - - supports-color - - express@4.18.2: + express@4.21.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.5.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -16127,7 +16118,8 @@ snapshots: transitivePeerDependencies: - supports-color - extend-object@1.0.0: {} + extend-object@1.0.0: + optional: true extend@3.0.2: {} @@ -16139,7 +16131,7 @@ snapshots: extract-zip@2.0.1(supports-color@8.1.1): dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -16161,7 +16153,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-glob@3.3.3: dependencies: @@ -16175,13 +16167,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-printf@1.6.9: - dependencies: - boolean: 3.2.0 + fast-printf@1.6.10: {} - fast-xml-parser@4.5.1: + fast-xml-parser@4.5.3: dependencies: - strnum: 1.0.5 + strnum: 1.1.1 fastq@1.17.1: dependencies: @@ -16213,6 +16203,8 @@ snapshots: dependencies: moment: 2.30.1 + file-uri-to-path@1.0.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -16229,10 +16221,10 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@1.2.0: + finalhandler@1.3.1: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 @@ -16280,7 +16272,7 @@ snapshots: dependencies: detect-file: 1.0.0 is-glob: 4.0.3 - micromatch: 4.0.7 + micromatch: 4.0.8 resolve-dir: 1.0.1 fixpack@4.0.0: @@ -16291,6 +16283,7 @@ snapshots: detect-newline: 3.1.0 extend-object: 1.0.0 rc: 1.2.8 + optional: true flat-cache@3.2.0: dependencies: @@ -16302,7 +16295,7 @@ snapshots: flow-enums-runtime@0.0.6: {} - flow-parser@0.258.0: {} + flow-parser@0.261.2: {} fn.name@1.1.0: {} @@ -16312,7 +16305,7 @@ snapshots: foreground-child@3.2.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 forever-agent@0.6.1: {} @@ -16323,6 +16316,13 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + formik@2.4.6(react@18.3.1): dependencies: '@types/hoist-non-react-statics': 3.3.5 @@ -16348,6 +16348,8 @@ snapshots: fromentries@1.3.2: {} + fs-constants@1.0.0: {} + fs-extra@11.2.0: dependencies: graceful-fs: 4.2.11 @@ -16422,17 +16424,37 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-paths@0.0.7: dependencies: pify: 4.0.1 - get-port@5.1.1: {} + get-port@5.1.1: + optional: true - get-stream@3.0.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@3.0.0: + optional: true get-stream@5.2.0: dependencies: - pump: 3.0.0 + pump: 3.0.2 get-stream@6.0.1: {} @@ -16448,7 +16470,7 @@ snapshots: getos@3.2.1: dependencies: - async: 3.2.5 + async: 3.2.6 getpass@0.1.7: dependencies: @@ -16471,6 +16493,8 @@ snapshots: split2: 3.2.2 through2: 4.0.2 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -16555,6 +16579,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -16598,6 +16624,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -16612,7 +16640,8 @@ snapshots: hast-util-whitespace@2.0.1: {} - he@1.2.0: {} + he@1.2.0: + optional: true hermes-estree@0.19.1: {} @@ -16648,15 +16677,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - html-to-text@8.2.1: - dependencies: - '@selderee/plugin-htmlparser2': 0.6.0 - deepmerge: 4.3.1 - he: 1.2.0 - htmlparser2: 6.1.0 - minimist: 1.2.8 - selderee: 0.6.0 - html-to-text@9.0.5: dependencies: '@selderee/plugin-htmlparser2': 0.11.0 @@ -16679,13 +16699,6 @@ snapshots: domutils: 2.8.0 entities: 2.2.0 - htmlparser2@6.1.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - entities: 2.2.0 - htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 @@ -16695,14 +16708,6 @@ snapshots: http-cache-semantics@4.1.1: {} - http-errors@1.7.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.1.1 - statuses: 1.5.0 - toidentifier: 1.0.0 - http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -16715,7 +16720,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color optional: true @@ -16724,14 +16729,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -16741,7 +16746,7 @@ snapshots: jsprim: 1.4.2 sshpk: 1.18.0 - http-signature@1.3.6: + http-signature@1.4.0: dependencies: assert-plus: 1.0.0 jsprim: 2.0.2 @@ -16754,14 +16759,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -16781,11 +16786,11 @@ snapshots: dependencies: '@ladjs/country-language': 0.2.1 - i18n@0.14.2: + i18n@0.15.1: dependencies: - '@messageformat/core': 3.3.0 - debug: 4.3.5(supports-color@8.1.1) - fast-printf: 1.6.9 + '@messageformat/core': 3.4.0 + debug: 4.4.0(supports-color@5.5.0) + fast-printf: 1.6.10 make-plural: 7.4.0 math-interval-parser: 2.0.1 mustache: 4.2.0 @@ -16799,6 +16804,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true ieee754@1.2.1: {} @@ -16929,10 +16935,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@3.0.1: - dependencies: - ci-info: 3.9.0 - is-core-module@2.14.0: dependencies: hasown: 2.0.2 @@ -17020,13 +17022,21 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-regex@1.2.1: + dependencies: + call-bound: 1.0.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + is-set@2.0.3: {} is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 - is-stream@1.1.0: {} + is-stream@1.1.0: + optional: true is-stream@2.0.1: {} @@ -17206,19 +17216,19 @@ snapshots: jscodeshift@0.14.0(@babel/preset-env@7.24.7(@babel/core@7.24.7)): dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.26.9) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.26.9) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.26.9) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) '@babel/preset-env': 7.24.7(@babel/core@7.24.7) - '@babel/preset-flow': 7.25.9(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/register': 7.25.9(@babel/core@7.26.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.26.0) + '@babel/preset-flow': 7.25.9(@babel/core@7.26.9) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.9) + '@babel/register': 7.25.9(@babel/core@7.26.9) + babel-core: 7.0.0-bridge.0(@babel/core@7.26.9) chalk: 4.1.2 - flow-parser: 0.258.0 + flow-parser: 0.261.2 graceful-fs: 4.2.11 micromatch: 4.0.8 neo-async: 2.6.2 @@ -17310,6 +17320,16 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + juice@10.0.1(encoding@0.1.13): + dependencies: + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 6.0.1(encoding@0.1.13) + transitivePeerDependencies: + - encoding + juice@7.0.0(encoding@0.1.13): dependencies: cheerio: 1.0.0-rc.12 @@ -17320,16 +17340,6 @@ snapshots: transitivePeerDependencies: - encoding - juice@8.1.0(encoding@0.1.13): - dependencies: - cheerio: 1.0.0-rc.10 - commander: 6.2.1 - mensch: 0.3.4 - slick: 1.12.2 - web-resource-inliner: 6.0.1(encoding@0.1.13) - transitivePeerDependencies: - - encoding - jwa@2.0.0: dependencies: buffer-equal-constant-time: 1.0.1 @@ -17376,27 +17386,19 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libbase64@1.2.1: {} + libbase64@1.3.0: + optional: true - libbase64@1.3.0: {} - - libmime@5.2.0: + libmime@5.3.6: dependencies: - encoding-japanese: 2.0.0 - iconv-lite: 0.6.3 - libbase64: 1.2.1 - libqp: 2.0.1 - - libmime@5.3.5: - dependencies: - encoding-japanese: 2.1.0 + encoding-japanese: 2.2.0 iconv-lite: 0.6.3 libbase64: 1.3.0 - libqp: 2.1.0 + libqp: 2.1.1 + optional: true - libqp@2.0.1: {} - - libqp@2.1.0: {} + libqp@2.1.1: + optional: true lighthouse-logger@1.4.2: dependencies: @@ -17418,17 +17420,18 @@ snapshots: linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 + optional: true lint-staged@13.1.2(enquirer@2.4.1): dependencies: cli-truncate: 3.1.0 colorette: 2.0.20 commander: 9.5.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 execa: 6.1.0 lilconfig: 2.0.6 listr2: 5.0.8(enquirer@2.4.1) - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-inspect: 1.13.2 pidtree: 0.6.0 @@ -17445,7 +17448,7 @@ snapshots: log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 through: 2.3.8 wrap-ansi: 7.0.0 optionalDependencies: @@ -17594,24 +17597,26 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - mailparser@3.7.1: + mailparser@3.7.2: dependencies: - encoding-japanese: 2.1.0 + encoding-japanese: 2.2.0 he: 1.2.0 html-to-text: 9.0.5 iconv-lite: 0.6.3 - libmime: 5.3.5 + libmime: 5.3.6 linkify-it: 5.0.0 - mailsplit: 5.4.0 - nodemailer: 6.9.13 + mailsplit: 5.4.2 + nodemailer: 6.9.16 punycode.js: 2.3.1 - tlds: 1.252.0 + tlds: 1.255.0 + optional: true - mailsplit@5.4.0: + mailsplit@5.4.2: dependencies: - libbase64: 1.2.1 - libmime: 5.2.0 - libqp: 2.0.1 + libbase64: 1.3.0 + libmime: 5.3.6 + libqp: 2.1.1 + optional: true make-dir@2.1.0: dependencies: @@ -17648,7 +17653,7 @@ snapshots: make-fetch-happen@9.1.0: dependencies: - agentkeepalive: 4.5.0 + agentkeepalive: 4.6.0 cacache: 15.3.0 http-cache-semantics: 4.1.1 http-proxy-agent: 4.0.1 @@ -17660,7 +17665,7 @@ snapshots: minipass-fetch: 1.4.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - negotiator: 0.6.3 + negotiator: 0.6.4 promise-retry: 2.0.1 socks-proxy-agent: 6.2.1 ssri: 8.0.1 @@ -17695,6 +17700,8 @@ snapshots: math-interval-parser@2.0.1: {} + math-intrinsics@1.1.0: {} + md5-hex@3.0.1: dependencies: blueimp-md5: 2.19.0 @@ -17763,7 +17770,7 @@ snapshots: type-fest: 0.18.1 yargs-parser: 20.2.9 - merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -17775,7 +17782,7 @@ snapshots: metro-babel-transformer@0.80.12: dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 flow-enums-runtime: 0.0.6 hermes-parser: 0.23.1 nullthrows: 1.1.1 @@ -17788,7 +17795,7 @@ snapshots: metro-cache@0.80.12: dependencies: - exponential-backoff: 3.1.1 + exponential-backoff: 3.1.2 flow-enums-runtime: 0.0.6 metro-core: 0.80.12 @@ -17834,7 +17841,7 @@ snapshots: metro-minify-terser@0.80.12: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.37.0 + terser: 5.39.0 metro-resolver@0.80.12: dependencies: @@ -17842,13 +17849,13 @@ snapshots: metro-runtime@0.80.12: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 flow-enums-runtime: 0.0.6 metro-source-map@0.80.12: dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 flow-enums-runtime: 0.0.6 invariant: 2.2.4 metro-symbolicate: 0.80.12 @@ -17873,10 +17880,10 @@ snapshots: metro-transform-plugins@0.80.12: dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/core': 7.26.9 + '@babel/generator': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -17884,10 +17891,10 @@ snapshots: metro-transform-worker@0.80.12: dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/core': 7.26.9 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 flow-enums-runtime: 0.0.6 metro: 0.80.12 metro-babel-transformer: 0.80.12 @@ -17905,12 +17912,12 @@ snapshots: metro@0.80.12: dependencies: '@babel/code-frame': 7.26.2 - '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/core': 7.26.9 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 accepts: 1.3.8 chalk: 4.1.2 ci-info: 2.0.0 @@ -18065,7 +18072,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -18084,11 +18091,6 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -18112,6 +18114,8 @@ snapshots: mimic-fn@4.0.0: {} + mimic-response@3.1.0: {} + min-indent@1.0.1: {} mini-svg-data-uri@1.4.4: {} @@ -18190,6 +18194,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -18242,19 +18248,14 @@ snapshots: nanoclone@0.2.1: {} - nanoid@3.3.7: {} + nanoid@3.3.8: {} + + napi-build-utils@2.0.0: {} natural-compare-lite@1.4.0: {} natural-compare@1.4.0: {} - nearley@2.20.1: - dependencies: - commander: 2.20.3 - moo: 0.5.2 - railroad-diagrams: 1.0.0 - randexp: 0.4.6 - negotiator@0.6.3: {} negotiator@0.6.4: {} @@ -18263,9 +18264,9 @@ snapshots: nerf-dart@1.0.0: {} - next@14.2.4(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.24(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 14.2.4 + '@next/env': 14.2.24 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001636 @@ -18275,29 +18276,34 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.7)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.4 - '@next/swc-darwin-x64': 14.2.4 - '@next/swc-linux-arm64-gnu': 14.2.4 - '@next/swc-linux-arm64-musl': 14.2.4 - '@next/swc-linux-x64-gnu': 14.2.4 - '@next/swc-linux-x64-musl': 14.2.4 - '@next/swc-win32-arm64-msvc': 14.2.4 - '@next/swc-win32-ia32-msvc': 14.2.4 - '@next/swc-win32-x64-msvc': 14.2.4 + '@next/swc-darwin-arm64': 14.2.24 + '@next/swc-darwin-x64': 14.2.24 + '@next/swc-linux-arm64-gnu': 14.2.24 + '@next/swc-linux-arm64-musl': 14.2.24 + '@next/swc-linux-x64-gnu': 14.2.24 + '@next/swc-linux-x64-musl': 14.2.24 + '@next/swc-win32-arm64-msvc': 14.2.24 + '@next/swc-win32-ia32-msvc': 14.2.24 + '@next/swc-win32-x64-msvc': 14.2.24 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nice-try@1.0.5: {} + nice-try@1.0.5: + optional: true nocache@3.0.4: {} + node-abi@3.74.0: + dependencies: + semver: 7.7.1 + node-abort-controller@3.1.1: {} - node-addon-api@4.3.0: {} - node-addon-api@5.1.0: {} + node-addon-api@7.1.1: {} + node-cache@5.1.2: dependencies: clone: 2.1.2 @@ -18327,7 +18333,7 @@ snapshots: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.7.1 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -18344,7 +18350,7 @@ snapshots: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.6.2 + semver: 7.7.1 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -18365,21 +18371,20 @@ snapshots: node-stream-zip@1.15.0: {} - nodemailer@6.9.1: {} + nodemailer@6.10.0: {} - nodemailer@6.9.13: {} + nodemailer@6.9.16: + optional: true - nodemailer@6.9.14: {} - - nodemon@2.0.20: + nodemon@3.1.9: dependencies: chokidar: 3.6.0 - debug: 3.2.7(supports-color@5.5.0) + debug: 4.4.0(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 - semver: 5.7.2 - simple-update-notifier: 1.1.0 + semver: 7.7.1 + simple-update-notifier: 2.0.0 supports-color: 5.5.0 touch: 3.1.1 undefsafe: 2.0.5 @@ -18408,7 +18413,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.14.0 - semver: 7.3.8 + semver: 7.7.1 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -18420,6 +18425,7 @@ snapshots: npm-run-path@2.0.2: dependencies: path-key: 2.0.1 + optional: true npm-run-path@4.0.1: dependencies: @@ -18465,6 +18471,8 @@ snapshots: object-inspect@1.13.2: {} + object-inspect@1.13.4: {} + object-is@1.1.6: dependencies: call-bind: 1.0.7 @@ -18549,7 +18557,7 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openpgp@5.7.0: + openpgp@5.11.2: dependencies: asn1.js: 5.4.1 @@ -18583,12 +18591,14 @@ snapshots: p-event@4.2.0: dependencies: p-timeout: 3.2.0 + optional: true p-filter@2.1.0: dependencies: p-map: 2.1.0 - p-finally@1.0.0: {} + p-finally@1.0.0: + optional: true p-is-promise@3.0.0: {} @@ -18631,6 +18641,7 @@ snapshots: p-timeout@3.2.0: dependencies: p-finally: 1.0.0 + optional: true p-try@1.0.0: {} @@ -18639,6 +18650,7 @@ snapshots: p-wait-for@3.2.0: dependencies: p-timeout: 3.2.0 + optional: true packet-reader@1.0.0: {} @@ -18682,11 +18694,6 @@ snapshots: leac: 0.6.0 peberminta: 0.9.0 - parseley@0.7.0: - dependencies: - moo: 0.5.2 - nearley: 2.20.1 - parseurl@1.3.3: {} path-exists@3.0.0: {} @@ -18695,7 +18702,8 @@ snapshots: path-is-absolute@1.0.1: {} - path-key@2.0.1: {} + path-key@2.0.1: + optional: true path-key@3.1.1: {} @@ -18708,9 +18716,9 @@ snapshots: lru-cache: 10.2.2 minipass: 7.1.2 - path-to-regexp@0.1.7: {} + path-to-regexp@0.1.12: {} - path-to-regexp@6.2.2: {} + path-to-regexp@6.3.0: {} path-type@4.0.0: {} @@ -18807,54 +18815,48 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-import@14.1.0(postcss@8.4.21): + postcss-import@14.1.0(postcss@8.4.31): dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.4.21): + postcss-js@4.0.1(postcss@8.4.31): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.21 + postcss: 8.4.31 - postcss-load-config@3.1.4(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): + postcss-load-config@3.1.4(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.21 + postcss: 8.4.31 ts-node: 10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5) - postcss-nested@6.0.0(postcss@8.4.21): + postcss-nested@6.0.0(postcss@8.4.31): dependencies: - postcss: 8.4.21 - postcss-selector-parser: 6.1.0 + postcss: 8.4.31 + postcss-selector-parser: 6.1.2 postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@6.1.0: + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} - postcss@8.4.21: - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - postcss@8.4.31: dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -18866,6 +18868,21 @@ snapshots: dependencies: xtend: 4.0.2 + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.74.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.2 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -18900,22 +18917,25 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - preview-email@3.0.20: + preview-email@3.1.0: dependencies: ci-info: 3.9.0 display-notification: 2.0.0 fixpack: 4.0.0 get-port: 5.1.1 - mailparser: 3.7.1 - nodemailer: 6.9.14 + mailparser: 3.7.2 + nodemailer: 6.10.0 open: 7.4.2 p-event: 4.2.0 p-wait-for: 3.2.0 pug: 3.0.3 uuid: 9.0.1 + optional: true process-nextick-args@2.0.1: {} + process@0.11.10: {} + promise-inflight@1.0.1: {} promise-retry@2.0.1: @@ -18955,7 +18975,9 @@ snapshots: proxy-from-env@1.0.0: {} - psl@1.9.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 pstree.remy@1.1.8: {} @@ -18984,7 +19006,7 @@ snapshots: jstransformer: 1.0.0 pug-error: 2.1.0 pug-walk: 2.0.0 - resolve: 1.22.8 + resolve: 1.22.10 pug-lexer@5.0.1: dependencies: @@ -19015,17 +19037,6 @@ snapshots: pug-walk@2.0.0: {} - pug@3.0.2: - dependencies: - pug-code-gen: 3.0.3 - pug-filters: 4.0.0 - pug-lexer: 5.0.1 - pug-linker: 4.0.0 - pug-load: 3.0.0 - pug-parser: 6.0.0 - pug-runtime: 3.0.1 - pug-strip-comments: 2.0.0 - pug@3.0.3: dependencies: pug-code-gen: 3.0.3 @@ -19037,35 +19048,34 @@ snapshots: pug-runtime: 3.0.1 pug-strip-comments: 2.0.0 - pump@3.0.0: + pump@3.0.2: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - punycode.js@2.3.1: {} + punycode.js@2.3.1: + optional: true punycode@2.3.1: {} q@1.5.1: {} - qs@6.10.5: + qs@6.13.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 - qs@6.11.0: + qs@6.13.1: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 - qs@6.12.1: + qs@6.14.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 qs@6.5.3: {} querystring@0.2.1: {} - querystringify@2.2.0: {} - queue-lit@1.5.2: {} queue-microtask@1.2.3: {} @@ -19078,18 +19088,11 @@ snapshots: quick-lru@5.1.1: {} - railroad-diagrams@1.0.0: {} - - randexp@0.4.6: - dependencies: - discontinuous-range: 1.0.0 - ret: 0.1.15 - random-bytes@1.0.0: {} range-parser@1.2.1: {} - raw-body@2.5.1: + raw-body@2.5.2: dependencies: bytes: 3.1.2 http-errors: 2.0.0 @@ -19273,7 +19276,7 @@ snapshots: react-shallow-renderer: 16.15.0(react@18.3.1) regenerator-runtime: 0.13.11 scheduler: 0.24.0-canary-efb381bbf-20230505 - stacktrace-parser: 0.1.10 + stacktrace-parser: 0.1.11 whatwg-fetch: 3.6.20 ws: 6.2.3 yargs: 17.7.2 @@ -19396,10 +19399,10 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-use-measure@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-use-measure@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - debounce: 1.2.1 react: 18.3.1 + optionalDependencies: react-dom: 18.3.1(react@18.3.1) react-zdog@1.2.2: @@ -19580,7 +19583,7 @@ snapshots: request@2.88.2: dependencies: aws-sign2: 0.7.0 - aws4: 1.13.0 + aws4: 1.13.2 caseless: 0.12.0 combined-stream: 1.0.8 extend: 3.0.2 @@ -19606,8 +19609,6 @@ snapshots: require-main-filename@2.0.0: {} - requires-port@1.0.0: {} - resize-observer-polyfill@1.5.1: {} resolve-dir@1.0.1: @@ -19650,8 +19651,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - ret@0.1.15: {} - retry@0.12.0: {} reusify@1.0.4: {} @@ -19666,11 +19665,10 @@ snapshots: dependencies: glob: 7.2.3 - rndm@1.2.0: {} - run-applescript@3.2.0: dependencies: execa: 0.10.0 + optional: true run-async@2.4.1: {} @@ -19682,6 +19680,10 @@ snapshots: dependencies: tslib: 2.6.3 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + sade@1.8.1: dependencies: mri: 1.2.0 @@ -19733,10 +19735,6 @@ snapshots: dependencies: parseley: 0.12.1 - selderee@0.6.0: - dependencies: - parseley: 0.7.0 - selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.11 @@ -19756,7 +19754,7 @@ snapshots: '@semantic-release/release-notes-generator': 10.0.3(semantic-release@19.0.5(encoding@0.1.13)) aggregate-error: 3.1.0 cosmiconfig: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 env-ci: 5.5.0 execa: 5.1.1 figures: 3.2.0 @@ -19768,12 +19766,12 @@ snapshots: lodash: 4.17.21 marked: 4.3.0 marked-terminal: 5.2.0(marked@4.3.0) - micromatch: 4.0.7 + micromatch: 4.0.8 p-each-series: 2.2.0 p-reduce: 2.1.0 read-pkg-up: 7.0.1 resolve-from: 5.0.0 - semver: 7.6.2 + semver: 7.7.1 semver-diff: 3.1.1 signale: 1.4.0 yargs: 16.2.0 @@ -19791,37 +19789,11 @@ snapshots: semver@6.3.1: {} - semver@7.0.0: {} - - semver@7.3.8: - dependencies: - lru-cache: 6.0.0 - semver@7.5.4: dependencies: lru-cache: 6.0.0 - semver@7.6.2: {} - - semver@7.6.3: {} - - send@0.18.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color + semver@7.7.1: {} send@0.19.0: dependencies: @@ -19843,15 +19815,6 @@ snapshots: serialize-error@2.1.0: {} - serve-static@1.15.0: - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - transitivePeerDependencies: - - supports-color - serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -19879,8 +19842,6 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - setprototypeof@1.1.1: {} - setprototypeof@1.2.0: {} sha.js@2.4.11: @@ -19896,7 +19857,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.2 + semver: 7.7.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.4 '@img/sharp-darwin-x64': 0.33.4 @@ -19921,17 +19882,39 @@ snapshots: shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 + optional: true shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} + shebang-regex@1.0.0: + optional: true shebang-regex@3.0.0: {} shell-quote@1.8.2: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -19939,6 +19922,14 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -19949,13 +19940,21 @@ snapshots: figures: 2.0.0 pkg-conf: 2.1.0 + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - simple-update-notifier@1.1.0: + simple-update-notifier@2.0.0: dependencies: - semver: 7.0.0 + semver: 7.7.1 sisteransi@1.0.5: {} @@ -19991,8 +19990,8 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) - socks: 2.8.3 + debug: 4.4.0(supports-color@5.5.0) + socks: 2.8.4 transitivePeerDependencies: - supports-color optional: true @@ -20000,7 +19999,7 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -20010,9 +20009,15 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 + socks@2.8.4: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + optional: true + sorted-array-functions@1.3.0: {} - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: dependencies: @@ -20061,16 +20066,16 @@ snapshots: sprintf-js@1.1.3: {} - sqlite3@5.1.4(encoding@0.1.13): + sqlite3@5.1.7: dependencies: - '@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13) - node-addon-api: 4.3.0 + bindings: 1.5.0 + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 tar: 6.2.1 optionalDependencies: node-gyp: 8.4.1 transitivePeerDependencies: - bluebird - - encoding - supports-color sshpk@1.18.0: @@ -20104,7 +20109,7 @@ snapshots: stackframe@1.3.4: {} - stacktrace-parser@0.1.10: + stacktrace-parser@0.1.11: dependencies: type-fest: 0.7.1 @@ -20204,7 +20209,8 @@ snapshots: strip-bom@4.0.0: {} - strip-eof@1.0.0: {} + strip-eof@1.0.0: + optional: true strip-final-newline@2.0.0: {} @@ -20218,7 +20224,7 @@ snapshots: strip-json-comments@3.1.1: {} - strnum@1.0.5: {} + strnum@1.1.1: {} style-to-object@0.4.4: dependencies: @@ -20267,14 +20273,14 @@ snapshots: css-select: 4.3.0 css-tree: 1.1.3 csso: 4.2.0 - picocolors: 1.0.1 + picocolors: 1.1.1 stable: 0.1.8 swagger-ui-dist@5.17.14: {} - swagger-ui-express@4.6.2(express@4.18.2): + swagger-ui-express@4.6.2(express@4.21.2): dependencies: - express: 4.18.2 + express: 4.21.2 swagger-ui-dist: 5.17.14 swr@2.2.5(react@18.3.1): @@ -20285,7 +20291,7 @@ snapshots: tailwind-merge@2.6.0: {} - tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): + tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): dependencies: arg: 5.0.2 chokidar: 3.6.0 @@ -20293,28 +20299,43 @@ snapshots: detective: 5.2.1 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.3.2 + fast-glob: 3.3.3 glob-parent: 6.0.2 is-glob: 4.0.3 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) - postcss-nested: 6.0.0(postcss@8.4.21) - postcss-selector-parser: 6.1.0 + picocolors: 1.1.1 + postcss: 8.4.31 + postcss-import: 14.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 3.1.4(postcss@8.4.31)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)) + postcss-nested: 6.0.0(postcss@8.4.31) + postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - ts-node tapable@2.2.1: {} + tar-fs@2.1.2: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -20338,7 +20359,7 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser@5.37.0: + terser@5.39.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.14.0 @@ -20380,9 +20401,13 @@ snapshots: titleize@2.1.0: {} - tlds@1.252.0: {} + tlds@1.255.0: {} - tlds@1.253.0: {} + tldts-core@6.1.78: {} + + tldts@6.1.78: + dependencies: + tldts-core: 6.1.78 tmp@0.0.33: dependencies: @@ -20400,8 +20425,6 @@ snapshots: toggle-selection@1.0.6: {} - toidentifier@1.0.0: {} - toidentifier@1.0.1: {} token-stream@1.0.0: {} @@ -20412,15 +20435,12 @@ snapshots: tough-cookie@2.5.0: dependencies: - psl: 1.9.0 + psl: 1.15.0 punycode: 2.3.1 - tough-cookie@4.1.4: + tough-cookie@5.1.1: dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + tldts: 6.1.78 tr46@0.0.3: {} @@ -20430,6 +20450,8 @@ snapshots: typedarray.prototype.slice: 1.0.3 which-typed-array: 1.1.15 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trim-newlines@3.0.1: {} @@ -20512,8 +20534,6 @@ snapshots: tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsutils@3.21.0(typescript@4.9.5): dependencies: tslib: 1.14.1 @@ -20593,7 +20613,7 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): + typeorm@0.3.11(pg@8.11.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -20601,7 +20621,7 @@ snapshots: chalk: 4.1.2 cli-highlight: 2.1.11 date-fns: 2.29.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5 dotenv: 16.4.5 glob: 7.2.3 js-yaml: 4.1.0 @@ -20614,7 +20634,7 @@ snapshots: yargs: 17.7.2 optionalDependencies: pg: 8.11.0 - sqlite3: 5.1.4(encoding@0.1.13) + sqlite3: 5.1.7 ts-node: 10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@22.10.5)(typescript@4.9.5) transitivePeerDependencies: - supports-color @@ -20623,7 +20643,8 @@ snapshots: typescript@5.5.2: {} - uc.micro@2.1.0: {} + uc.micro@2.1.0: + optional: true uglify-js@3.18.0: optional: true @@ -20641,17 +20662,17 @@ snapshots: undefsafe@2.0.5: {} - underscore.deep@0.5.3(underscore@1.13.6): + underscore.deep@0.5.3(underscore@1.13.7): dependencies: - underscore: 1.13.6 + underscore: 1.13.7 - underscore@1.13.6: {} + underscore@1.13.7: {} undici-types@5.26.5: {} undici-types@6.20.0: {} - undici@6.20.1: {} + undici@7.3.0: {} unicode-canonical-property-names-ecmascript@2.0.0: {} @@ -20731,8 +20752,6 @@ snapshots: universalify@0.1.2: {} - universalify@0.2.0: {} - universalify@2.0.1: {} unpipe@1.0.0: {} @@ -20745,7 +20764,7 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 - update-browserslist-db@1.1.1(browserslist@4.24.3): + update-browserslist-db@1.1.2(browserslist@4.24.3): dependencies: browserslist: 4.24.3 escalade: 3.2.0 @@ -20757,11 +20776,6 @@ snapshots: url-join@4.0.1: {} - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - urlsafe-base64@1.0.0: {} use-isomorphic-layout-effect@1.1.2(@types/react@18.3.3)(react@18.3.1): @@ -20782,7 +20796,8 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: {} + uuid@9.0.1: + optional: true uvu@0.5.6: dependencies: @@ -20962,9 +20977,9 @@ snapshots: with@7.0.2: dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - assert-never: 1.2.1 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + assert-never: 1.4.0 babel-walk: 3.0.0-canary-5 word-wrap@1.2.5: {} @@ -21111,7 +21126,7 @@ snapshots: zdog@1.1.3: {} - zod@3.20.6: {} + zod@3.24.2: {} zustand@3.7.2(react@18.3.1): optionalDependencies: diff --git a/server/index.ts b/server/index.ts index 00876419..d672f622 100644 --- a/server/index.ts +++ b/server/index.ts @@ -28,7 +28,7 @@ import restartFlag from '@server/utils/restartFlag'; import { getClientIp } from '@supercharge/request-ip'; import { TypeormStore } from 'connect-typeorm/out'; import cookieParser from 'cookie-parser'; -import csurf from 'csurf'; +import { doubleCsrf } from 'csrf-csrf'; import type { NextFunction, Request, Response } from 'express'; import express from 'express'; import * as OpenApiValidator from 'express-openapi-validator'; @@ -162,18 +162,23 @@ app } }); if (settings.network.csrfProtection) { - server.use( - csurf({ - cookie: { - httpOnly: true, - sameSite: true, - secure: !dev, - }, - }) - ); + const { doubleCsrfProtection, generateToken } = doubleCsrf({ + getSecret: () => settings.clientId, + cookieName: 'XSRF-TOKEN', + cookieOptions: { + httpOnly: true, + sameSite: 'strict', + secure: !dev, + }, + size: 64, + ignoredMethods: ['GET', 'HEAD', 'OPTIONS'], + }); + + server.use(doubleCsrfProtection); + server.use((req, res, next) => { - res.cookie('XSRF-TOKEN', req.csrfToken(), { - sameSite: true, + res.cookie('XSRF-TOKEN', generateToken(req, res), { + sameSite: 'strict', secure: !dev, }); next(); diff --git a/server/lib/email/index.ts b/server/lib/email/index.ts index c38892ae..66b3698f 100644 --- a/server/lib/email/index.ts +++ b/server/lib/email/index.ts @@ -50,6 +50,7 @@ class PreparedEmail extends Email { }, send: true, transport: transport, + preview: false, }); } } diff --git a/src/utils/fetchOverride.ts b/src/utils/fetchOverride.ts index e0a90012..bd4dcd43 100644 --- a/src/utils/fetchOverride.ts +++ b/src/utils/fetchOverride.ts @@ -31,7 +31,7 @@ if (typeof window !== 'undefined') { const headers = { ...(init?.headers || {}), - ...(csrfToken ? { 'XSRF-TOKEN': csrfToken } : {}), + ...(csrfToken ? { 'X-CSRF-TOKEN': csrfToken } : {}), }; const newInit: RequestInit = { diff --git a/tsconfig.json b/tsconfig.json index 6a4686a8..69e6d122 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "ES2021", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, From af8d6b475c0040f7b96f04e3783ac8b4c702b3db Mon Sep 17 00:00:00 2001 From: RankWeis Date: Sun, 9 Mar 2025 07:37:47 -0700 Subject: [PATCH 63/65] fix(overriderules): allows every user to be added to the override rules (#1333) * fix: allows every user to be added to the override rules * chore: code cleanup * fix(overriderules): displaying more than ten users at a time * fix(overriderules): pageSize unaffected by duplicate includeIds Co-authored-by: Gauthier * fix: allows every user to be added to the override rules * chore(overriderules): readding override rules after rebase * chore(overriderules): removing empty file * chore(overriderules): fixing bad merge --------- Co-authored-by: Gauthier --- jellyseerr-api.yml | 5 ++++ server/routes/user/index.ts | 14 ++++++++++- src/components/Selector/index.tsx | 5 +++- .../OverrideRule/OverrideRuleTiles.tsx | 24 +++++++++---------- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml index 0bbe7ffb..00b09596 100644 --- a/jellyseerr-api.yml +++ b/jellyseerr-api.yml @@ -3812,6 +3812,11 @@ paths: required: false schema: type: string + - in: query + name: includeIds + required: false + schema: + type: string responses: '200': description: A JSON array of all users diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index c9bc9834..568124f7 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -32,7 +32,14 @@ const router = Router(); router.get('/', async (req, res, next) => { try { - const pageSize = req.query.take ? Number(req.query.take) : 10; + const includeIds = [ + ...new Set( + req.query.includeIds ? req.query.includeIds.toString().split(',') : [] + ), + ]; + const pageSize = req.query.take + ? Number(req.query.take) + : Math.max(10, includeIds.length); const skip = req.query.skip ? Number(req.query.skip) : 0; const q = req.query.q ? req.query.q.toString().toLowerCase() : ''; let query = getRepository(User).createQueryBuilder('user'); @@ -44,6 +51,10 @@ router.get('/', async (req, res, next) => { ); } + if (includeIds.length > 0) { + query.andWhereInIds(includeIds); + } + switch (req.query.sort) { case 'updated': query = query.orderBy('user.updatedAt', 'DESC'); @@ -84,6 +95,7 @@ router.get('/', async (req, res, next) => { const [users, userCount] = await query .take(pageSize) .skip(skip) + .distinct(true) .getManyAndCount(); return res.status(200).json({ diff --git a/src/components/Selector/index.tsx b/src/components/Selector/index.tsx index 2098c935..5676c794 100644 --- a/src/components/Selector/index.tsx +++ b/src/components/Selector/index.tsx @@ -578,7 +578,10 @@ export const UserSelector = ({ const users = defaultValue.split(','); - const res = await fetch(`/api/v1/user`); + const res = await fetch( + `/api/v1/user?includeIds=${encodeURIComponent(defaultValue)}` + ); + if (!res.ok) { throw new Error('Network response was not ok'); } diff --git a/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx b/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx index a3b9aa37..8b2acaf6 100644 --- a/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx +++ b/src/components/Settings/OverrideRule/OverrideRuleTiles.tsx @@ -127,18 +127,18 @@ const OverrideRuleTiles = ({ }) ); setKeywords(keywords); - const users = await Promise.all( - rules - .map((rule) => rule.users?.split(',')) - .flat() - .filter((userId) => userId) - .map(async (userId) => { - const res = await fetch(`/api/v1/user/${userId}`); - if (!res.ok) throw new Error(); - const user: User = await res.json(); - return user; - }) - ); + const allUsersFromRules = rules + .map((rule) => rule.users) + .filter((users) => users) + .join(','); + if (allUsersFromRules) { + const res = await fetch( + `/api/v1/user?includeIds=${encodeURIComponent(allUsersFromRules)}` + ); + if (!res.ok) throw new Error(); + const users: User[] = (await res.json()).results; + setUsers(users); + } setUsers(users); })(); }, [rules]); From f8c9689745df3b6c04adaf700566dc8efd4b6d8a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 9 Mar 2025 22:40:22 +0800 Subject: [PATCH 64/65] docs: add RankWeis as a contributor for code (#1434) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8dbd97d6..4cbc6e2c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -702,6 +702,15 @@ "contributions": [ "code" ] + }, + { + "login": "RankWeis", + "name": "RankWeis", + "avatar_url": "https://avatars.githubusercontent.com/u/733691?v=4", + "profile": "https://github.com/RankWeis", + "contributions": [ + "code" + ] } ] } diff --git a/README.md b/README.md index 8bb9c7c9..a275de0c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@
    Translation status GitHub -All Contributors +All Contributors **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/)**. @@ -96,7 +96,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon Thegan Govender
    Thegan Govender

    💻 - jab416171
    jab416171

    📖 + jab416171
    jab416171

    📖 💻 Nicolai Van der Storm
    Nicolai Van der Storm

    💻 Smexhy
    Smexhy

    🌍 dd060606
    dd060606

    💻 @@ -171,6 +171,18 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon andrewkolda
    andrewkolda

    🎨 Ishan Jain
    Ishan Jain

    💻 Michael Thomas
    Michael Thomas

    💻 + Joseph Risk
    Joseph Risk

    💻 + Loetwiek
    Loetwiek

    💻 + Fuochi
    Fuochi

    📖 + + + David Emrich
    David Emrich

    💻 + Max T. Kristiansen
    Max T. Kristiansen

    💻 + Damien Fajole
    Damien Fajole

    💻 + Ahmed Siddiqui
    Ahmed Siddiqui

    💻 + JackOXI
    JackOXI

    💻 + Stancu Florin
    Stancu Florin

    💻 + RankWeis
    RankWeis

    💻 From 0113612ced762bf9a0c61c542fc2e66479a55795 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Tue, 11 Mar 2025 01:10:47 +0800 Subject: [PATCH 65/65] chore(i18n): merge weblate translations (#1437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Korean) Currently translated at 91.8% (1225 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ko/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/zh_Hans/ * Translated using Weblate (Ukrainian) Currently translated at 96.1% (1282 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Italian) Currently translated at 84.4% (1127 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/it/ * Translated using Weblate (Japanese) Currently translated at 48.4% (646 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ja/ * Translated using Weblate (Spanish) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/es/ * Translated using Weblate (Croatian) Currently translated at 91.1% (1216 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/hr/ * Translated using Weblate (Arabic) Currently translated at 91.0% (1215 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ar/ * Translated using Weblate (Norwegian Bokmål) Currently translated at 80.0% (1068 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nb_NO/ * Translated using Weblate (Danish) Currently translated at 91.1% (1216 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/da/ * Translated using Weblate (Catalan) Currently translated at 90.7% (1211 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ca/ * Translated using Weblate (Turkish) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Greek) Currently translated at 91.3% (1218 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/el/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 89.5% (1194 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/zh_Hant/ * Translated using Weblate (Swedish) Currently translated at 96.1% (1282 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/sv/ * Translated using Weblate (Polish) Currently translated at 91.0% (1214 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pl/ * Translated using Weblate (Romanian) Currently translated at 32.6% (435 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ro/ * Translated using Weblate (Albanian) Currently translated at 76.3% (1018 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/sq/ * Translated using Weblate (French) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Russian) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ru/ * Translated using Weblate (Lithuanian) Currently translated at 54.1% (722 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/lt/ * Translated using Weblate (Dutch) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Hungarian) Currently translated at 85.2% (1137 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/hu/ * Translated using Weblate (Portuguese (Portugal)) Currently translated at 89.9% (1200 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pt_PT/ * Translated using Weblate (Czech) Currently translated at 91.4% (1220 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/cs/ * Translated using Weblate (Serbian) Currently translated at 46.6% (622 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/sr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 92.5% (1234 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pt_BR/ * Translated using Weblate (Hebrew) Currently translated at 25.7% (343 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/he/ * Translated using Weblate (German) Currently translated at 96.2% (1284 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (Hindi) Currently translated at 10.7% (143 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/hi/ * Translated using Weblate (French) Currently translated at 100.0% (1334 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Italian) Currently translated at 94.3% (1259 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/it/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ * Translated using Weblate (Italian) Currently translated at 94.4% (1260 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/it/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1334 of 1334 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (German) Currently translated at 94.7% (1282 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (Spanish) Currently translated at 94.7% (1282 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/es/ * Translated using Weblate (French) Currently translated at 98.4% (1332 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Dutch) Currently translated at 94.7% (1282 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Polish) Currently translated at 89.5% (1212 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pl/ * Translated using Weblate (Russian) Currently translated at 94.7% (1282 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ru/ * Translated using Weblate (Swedish) Currently translated at 94.6% (1280 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/sv/ * Translated using Weblate (Ukrainian) Currently translated at 94.6% (1280 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 94.7% (1282 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/zh_Hans/ * Translated using Weblate (French) Currently translated at 99.8% (1351 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (French) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Romanian) Currently translated at 46.1% (624 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ro/ * Translated using Weblate (French) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (German) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (Russian) Currently translated at 97.7% (1322 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ru/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (French) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 97.5% (1320 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pt_BR/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Swedish) Currently translated at 95.4% (1291 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/sv/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Polish) Currently translated at 91.2% (1235 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pl/ * Translated using Weblate (German) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (French) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1353 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Ukrainian) Currently translated at 98.7% (1336 of 1353 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ * Translated using Weblate (Arabic) Currently translated at 86.8% (1220 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ar/ * Translated using Weblate (French) Currently translated at 96.2% (1353 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Finnish) Currently translated at 17.2% (243 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fi/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (French) Currently translated at 96.2% (1353 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (French) Currently translated at 96.7% (1360 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Polish) Currently translated at 97.8% (1375 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pl/ * Translated using Weblate (Ukrainian) Currently translated at 96.8% (1361 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Added translation using Weblate (Basque) * Translated using Weblate (Basque) Currently translated at 16.7% (236 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Ukrainian) Currently translated at 96.9% (1362 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Basque) Currently translated at 18.0% (253 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (French) Currently translated at 99.7% (1401 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (French) Currently translated at 99.7% (1401 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Basque) Currently translated at 30.1% (423 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Danish) Currently translated at 86.7% (1219 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/da/ * Translated using Weblate (French) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (German) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (French) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Ukrainian) Currently translated at 97.0% (1363 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Basque) Currently translated at 30.1% (424 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Ukrainian) Currently translated at 97.0% (1364 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/uk/ * Translated using Weblate (Dutch) Currently translated at 99.2% (1394 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Basque) Currently translated at 30.3% (427 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (German) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/de/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/tr/ * Translated using Weblate (Basque) Currently translated at 31.1% (438 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (French) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Basque) Currently translated at 33.0% (464 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Portuguese (Portugal)) Currently translated at 85.6% (1204 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/pt_PT/ * Translated using Weblate (Basque) Currently translated at 35.0% (493 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Basque) Currently translated at 39.7% (558 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Basque) Currently translated at 47.7% (671 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Basque) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/eu/ * Translated using Weblate (Russian) Currently translated at 95.1% (1337 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/ru/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (French) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/fr/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1405 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/nl/ * Translated using Weblate (Czech) Currently translated at 98.7% (1388 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/cs/ * Translated using Weblate (Czech) Currently translated at 99.0% (1392 of 1405 strings) Translate-URL: http://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/cs/ --------- Co-authored-by: Anonymous Co-authored-by: jason54 Co-authored-by: Kristopher Roller Co-authored-by: jellyseerr-weblate <155525085+jellyseerr-weblate@users.noreply.github.com> Co-authored-by: N/A Co-authored-by: pouley Co-authored-by: Nuh uh Co-authored-by: Gauthier Co-authored-by: madax Co-authored-by: SoundwaveUwU Co-authored-by: Michel Heusschen Co-authored-by: Leo THIVILLON Co-authored-by: Uncle Co-authored-by: Gökhan GÜRBÜZ Co-authored-by: Mattias Magnusson Co-authored-by: Radosław Adamczewski Co-authored-by: Alexander Mnich Co-authored-by: HanaO00 Co-authored-by: michael Co-authored-by: Heni FAZZANI Co-authored-by: Matti Koponen Co-authored-by: Gauvain Perchey Co-authored-by: zulimazuli Co-authored-by: Thadah Co-authored-by: Frank Jeager Co-authored-by: Mads K Co-authored-by: Bas <910100490+weblate@proton.me> Co-authored-by: BlackSpirits Co-authored-by: Dennis van J Co-authored-by: Tomáš Holý --- src/i18n/locale/cs.json | 170 +++++- src/i18n/locale/da.json | 12 +- src/i18n/locale/de.json | 93 ++- src/i18n/locale/eu.json | 1171 +++++++++++++++++++++++++++++++++++- src/i18n/locale/fr.json | 97 ++- src/i18n/locale/nl.json | 187 ++++-- src/i18n/locale/pt_PT.json | 11 +- src/i18n/locale/ru.json | 20 +- src/i18n/locale/tr.json | 16 +- src/i18n/locale/uk.json | 5 +- 10 files changed, 1654 insertions(+), 128 deletions(-) diff --git a/src/i18n/locale/cs.json b/src/i18n/locale/cs.json index cf1c5246..ee2f3cec 100644 --- a/src/i18n/locale/cs.json +++ b/src/i18n/locale/cs.json @@ -781,7 +781,7 @@ "components.Settings.externalUrl": "Externí adresa URL", "components.Settings.hostname": "Název hostitele nebo IP adresa", "components.Settings.manualscan": "Manuální skenování knihovny", - "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.plexlibrariesDescription": "Knihovny ve kterých Jellyseerr vyhledává tituly. Nastavte a uložte nastavení připojení k Plex serveru 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.toastTautulliSettingsFailure": "Při ukládání nastavení Tautulli se něco pokazilo.", "components.Settings.webAppUrl": "Webová aplikace Adresa URL", @@ -795,7 +795,7 @@ "components.UserList.deleteconfirm": "Opravdu chcete tohoto uživatele odstranit? Všechny údaje o jeho žádosti budou trvale odstraněny.", "components.UserList.localLoginDisabled": "Nastavení Povolit místní přihlášení je v současné době zakázáno.", "components.UserList.userssaved": "Uživatelská oprávnění byla úspěšně uložena!", - "components.UserList.validationEmail": "Musíte zadat platnou e-mailovou adresu", + "components.UserList.validationEmail": "Emailová adresa je povinná", "components.UserProfile.ProfileHeader.userid": "ID uživatele: {userid}", "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Limit požadavků na filmy", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Limit požadavků na sérii", @@ -831,7 +831,7 @@ "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 neměl 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.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.plexsettingsDescription": "Nastavte připojení k Plex serveru. Jellyseerr prohledá knihovny Plex serveru aby zjistil dostupnost obsahu.", "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.", @@ -1023,7 +1023,7 @@ "components.PermissionEdit.autorequestMovies": "Automatické vyžádání filmů", "components.PermissionEdit.autorequestMoviesDescription": "Udělte oprávnění k automatickému odesílání žádostí o filmy v jiném rozlišení než 4K prostřednictvím Plex Watchlistu.", "components.PermissionEdit.viewrecentDescription": "Udělte oprávnění k zobrazení seznamu nedávno přidaných médií.", - "components.Settings.restartrequiredTooltip": "Aby se změny tohoto nastavení projevily, musí být Overserr restartován", + "components.Settings.restartrequiredTooltip": "Aby se projevily změny tohoto nastavení, musí být Jellyseerr restartován", "components.StatusChecker.reloadApp": "Znovu načíst {applicationTitle}", "components.TitleCard.cleardata": "Vyčistit data", "components.TitleCard.mediaerror": "{mediaType} Nenalezeno", @@ -1231,5 +1231,165 @@ "components.Settings.SonarrModal.animeSeriesType": "Typ anime série", "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Výchozí zařízení", "components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Výchozí zařízení", - "components.Settings.SonarrModal.seriesType": "Typ série" + "components.Settings.SonarrModal.seriesType": "Typ série", + "components.Login.servertype": "Typ serveru", + "components.Login.validationEmailFormat": "Neplatný email", + "components.Login.validationUrlBaseLeadingSlash": "Základ URL adresy musí obsahovat lomítko", + "components.Login.validationEmailRequired": "Musíte poskytnout email", + "components.Login.validationPortRequired": "Musíte poskytnout platné číslo portu", + "components.Login.back": "Vrátit se", + "components.Login.validationUrlBaseTrailingSlash": "Základ URL adresy nesmí končit lomítkem", + "components.Setup.back": "Vrátit se", + "components.Login.validationusernamerequired": "Uživatelské jméno je povinné", + "components.Setup.configemby": "Nastavit Emby", + "components.PermissionEdit.blacklistedItemsDescription": "Udělit oprávnění přidávat média na černou listinu.", + "components.Login.signinwithjellyfin": "Použít Váš {mediaServerName} účet", + "components.Settings.Notifications.userEmailRequired": "Vyžadovat email uživatelů", + "components.TitleCard.addToWatchList": "Přidat na seznam sledování", + "components.TvDetails.removefromwatchlist": "Odstranit ze seznamu sledování", + "components.Login.credentialerror": "Nesprávné uživatelské jméno nebo heslo.", + "components.Setup.servertype": "Zvolte typ serveru", + "components.Settings.manualscanDescriptionJellyfin": "Obvykle se provádí pouze jednou za 24 hodin. Jellyseerr bude kontrolovat nedávno přidané položky vašeho {mediaServerName} serveru agresivněji. Pokud nastavujete Jellyseerr poprvé, doporučujeme provést jednorázovou úplnou ruční kontrolu knihoven!", + "components.ManageSlideOver.manageModalRemoveMediaWarning": "* Toto nenávratně odstraní tento {mediaType} z {arr}, včetně všech souborů.", + "components.Blacklist.blacklistdate": "datum", + "components.Blacklist.mediaName": "Jméno", + "components.MovieDetails.watchlistDeleted": "{title} úspěšně odstraněno ze seznamu sledování!", + "components.Settings.SettingsMain.validationProxyPort": "Musíte poskytnout platný port", + "components.Settings.Notifications.validationWebhookRoleId": "Musíte poskytnout platné ID Discord role", + "components.Blacklist.blacklistedby": "{date} uživatelem {user}", + "components.Layout.UserWarnings.passwordRequired": "Heslo je povinné.", + "components.Login.validationHostnameRequired": "Musíte poskytnout platné hostitelské jméno nebo IP adresu", + "components.Selector.searchStatus": "Vyberte status…", + "components.TvDetails.watchlistSuccess": "{title} úspěšně přidáno na seznam sledování!", + "components.Blacklist.blacklistNotFoundError": "{title} není na černé listině.", + "components.TitleCard.watchlistDeleted": "{title} úspěšně odstraněno ze seznamu sledování!", + "component.BlacklistBlock.blacklistdate": "Datum přidání na černou listinu", + "component.BlacklistBlock.blacklistedby": "Přidáno na černou listinu uživatelem", + "components.Blacklist.blacklistSettingsDescription": "Spravovat média na černé listině.", + "components.Blacklist.mediaTmdbId": "tmdb ID", + "components.Blacklist.mediaType": "Typ", + "components.Blacklist.blacklistsettings": "Nastavení černé listiny", + "components.Discover.FilterSlideover.status": "Status", + "components.Layout.Sidebar.blacklist": "Černá listina", + "components.Layout.UserWarnings.emailInvalid": "Emailová adresa je neplatná.", + "components.Layout.UserWarnings.emailRequired": "Emailová adresa je povinná.", + "components.Login.emailtooltip": "Adresa nemusí být asociována s Vaší {mediaServerName} instancí.", + "components.Login.enablessl": "Používat SSL", + "components.Login.initialsignin": "Připojit", + "components.Login.initialsigningin": "Připojování…", + "components.Login.invalidurlerror": "Nelze se připojit k {mediaServerName} serveru.", + "components.Login.port": "Port", + "components.Login.save": "Přidat", + "components.Login.saving": "Přidávání…", + "components.Login.username": "Uživatelské jméno", + "components.Login.validationemailformat": "Je požadován platný email", + "components.Login.validationhostformat": "Je požadována platná URL", + "components.Login.validationhostrequired": "Je požadována {mediaServerName} URL", + "components.Login.validationservertyperequired": "Prosím zvolte typ serveru", + "components.Login.hostname": "{mediaServerName} URL", + "components.Login.urlBase": "Základ URL adresy", + "components.Login.validationUrlTrailingSlash": "URL adresa nesmí končit lomítkem", + "components.ManageSlideOver.removearr": "Odstranit z {arr}", + "components.ManageSlideOver.removearr4k": "Odstranit z 4K {arr}", + "components.MovieDetails.addtowatchlist": "Přidat na seznam sledování", + "components.MovieDetails.downloadstatus": "Stav stahování", + "components.MovieDetails.openradarr": "Otevřít film v Radarr", + "components.MovieDetails.openradarr4k": "Otevřít film ve 4K Radarr", + "components.MovieDetails.removefromwatchlist": "Odstranit ze seznamu sledování", + "components.MovieDetails.watchlistError": "Něco se pokazilo. Zkuste to znovu.", + "components.MovieDetails.watchlistSuccess": "{title} úspěšně přidáno na seznam sledování!", + "components.PermissionEdit.blacklistedItems": "Přidat média na černou listinu.", + "components.PermissionEdit.manageblacklist": "Spravovat černou listinu", + "components.PermissionEdit.manageblacklistDescription": "Udělit oprávnění spravovat černou listinu.", + "components.PermissionEdit.viewblacklistedItems": "Zobrazit černou listinu.", + "components.PermissionEdit.viewblacklistedItemsDescription": "Udělit oprávnění zobrazit černou listinu.", + "components.RequestList.RequestItem.profileName": "Profil", + "components.RequestList.RequestItem.removearr": "Odstranit z {arr}", + "components.RequestList.sortDirection": "Přepnout směr řazení", + "components.Selector.canceled": "Zrušeno", + "components.Selector.inProduction": "V produkci", + "components.Settings.SettingsAbout.supportjellyseerr": "Podpořit Jellyseerr", + "components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Sken nedávno přidaných na Jellyfin", + "components.Settings.SettingsJobsCache.jellyfin-full-scan": "Kompletní sken knihoven Jellyfin", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Obnovení Plex tokenu", + "components.Settings.SettingsMain.discoverRegionTip": "Filtrovat obsah podle dostupnosti v regionu", + "components.Settings.SettingsMain.proxyEnabled": "HTTP(S) proxy", + "components.Settings.SettingsMain.proxySsl": "Používat SSL pro proxy", + "components.Settings.SettingsMain.proxyPort": "Port proxy", + "components.Settings.SettingsMain.proxyPassword": "Heslo proxy", + "components.Settings.SettingsMain.proxyUser": "Uživatelské jméno proxy", + "components.Settings.SettingsMain.streamingRegion": "Streamovací region", + "components.Settings.SettingsMain.streamingRegionTip": "Zobrazit streamovací služby podle dostupnosti v regionu", + "components.Settings.SettingsMain.discoverRegion": "Region objevování", + "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Obcházet proxy pro lokální adresy", + "components.Settings.SettingsMain.proxyHostname": "Hostitelské jméno proxy", + "components.Settings.apiKey": "API klíč", + "components.Settings.invalidurlerror": "Nelze se připojit k {mediaServerName} serveru.", + "components.Settings.jellyfinForgotPasswordUrl": "URL pro zapomenuté heslo", + "components.Settings.jellyfinSettings": "Nastavení {mediaServerName}", + "components.Settings.jellyfinSettingsSuccess": "{mediaServerName} nastavení úspěšně uloženo!", + "components.Settings.jellyfinSyncFailedGenericError": "Něco se pokazilo při synchronizaci knihoven", + "components.Settings.jellyfinSyncFailedNoLibrariesFound": "Nebyly nalezeny žádné knihovny", + "components.Settings.jellyfinlibraries": "{mediaServerName} knihovny", + "components.Settings.jellyfinsettings": "Nastavení {mediaServerName}", + "components.Settings.manualscanJellyfin": "Manuální skenování knihovny", + "components.Settings.menuJellyfinSettings": "{mediaServerName}", + "components.Settings.jellyfinlibrariesDescription": "Knihovny, ve kterých {mediaServerName} hledá tituly. Pokud nejsou v seznamu žádné knihovny, klikněte na tlačítko níže.", + "components.Settings.jellyfinsettingsDescription": "Konfigurujte nastavení svého {mediaServerName} serveru. {mediaServerName} skenuje Vaše {mediaServerName} knihovny pro zjištění dostupnosti obsahu.", + "components.Settings.save": "Uložit změny", + "components.Settings.saving": "Ukládání…", + "components.Settings.syncJellyfin": "Synchronizovat knihovny", + "components.Settings.syncing": "Synchronizace", + "components.Settings.tip": "Tip", + "components.Setup.configjellyfin": "Nastavit Jellyfin", + "components.Setup.configplex": "Nastavit Plex", + "components.Setup.configuremediaserver": "Nastavit média server", + "components.Setup.signin": "Přihlásit se", + "components.Setup.signinWithEmby": "Zadejte údaje k Emby", + "components.Setup.signinWithJellyfin": "Zadejte údaje k Jellyfin", + "components.Setup.signinWithPlex": "Zadejte údaje k Plex", + "components.Setup.subtitle": "Začněte výběrem média serveru", + "components.StatusBadge.seasonnumber": "S{seasonNumber}", + "components.TitleCard.watchlistError": "Něco se pokazilo. Zkuste to znovu.", + "components.TvDetails.addtowatchlist": "Přidat na seznam sledování", + "components.TitleCard.watchlistCancel": "seznam sledování pro {title} zrušen.", + "components.TitleCard.watchlistSuccess": "{title} úspěšně přidáno na seznam sledování!", + "components.TvDetails.watchlistDeleted": "{title} úspěšně odstraněno ze seznamu sledování!", + "components.TvDetails.watchlistError": "Něco se pokazilo. Zkuste to znovu.", + "components.UserList.importfromJellyfin": "Import uživatelů z {mediaServerName}", + "components.UserList.importfromJellyfinerror": "Něco se pokazilo během importování uživatelů z {mediaServerName}.", + "components.UserList.mediaServerUser": "{mediaServerName} uživatel", + "components.UserList.noJellyfinuserstoimport": "{mediaServerName} nemá žádné uživatele k importování.", + "components.UserList.username": "Uživatelské jméno", + "components.UserList.validationUsername": "Musíte poskytnout uživatelské jméno", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Region pro objevování", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Filtrovat obsah podle dostupnosti v regionu", + "components.UserProfile.UserSettings.UserGeneralSettings.email": "E-mail", + "components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "{mediaServerName} uživatel", + "components.UserProfile.UserSettings.UserGeneralSettings.save": "Uložit změny", + "components.UserProfile.UserSettings.UserGeneralSettings.saving": "Ukládání…", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Streamovací region", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Zobrazit streamovací služby podle dostupnosti v regionu", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "Tento email už je používán!", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Jiný uživatel už používá toto uživatelské jméno. Musíte si nastavit email", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Je požadován platný email", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "Emailová adresa je povinná", + "components.UserProfile.localWatchlist": "Seznam sledování uživatele {username}", + "i18n.addToBlacklist": "Přidat na černou listinu", + "i18n.blacklistError": "Něco se pokazilo. Zkuste to znovu.", + "i18n.blacklist": "Černá listina", + "i18n.blacklistDuplicateError": "{title} už se nachází na černé listině.", + "i18n.blacklistSuccess": "{title} bylo úspěšně přidáno na černou listinu.", + "i18n.blacklisted": "Na černé listině", + "i18n.removeFromBlacklistSuccess": "{title} bylo úspěšně odstraněno z černé listiny.", + "i18n.removefromBlacklist": "Odstranit z černé listiny", + "components.Login.title": "Přidat email", + "components.Login.adminerror": "Musíte se přihlásit administrátorským účtem.", + "components.Login.description": "Protože je toto Vaše první přihlášení do {applicationName}, musíte přidat platnou emailovou adresu.", + "components.Settings.jellyfinSettingsFailure": "Něco se pokazilo při ukládání nastavení {mediaServerName}.", + "components.Settings.timeout": "Časový limit", + "components.Selector.ended": "Ukončeno", + "components.Selector.returningSeries": "Vracející se seriál", + "components.Settings.SettingsJobsCache.usersavatars": "Uživatelské avatary", + "i18n.specials": "Speciály" } diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json index 0f406953..969d1ed8 100644 --- a/src/i18n/locale/da.json +++ b/src/i18n/locale/da.json @@ -326,7 +326,7 @@ "components.RequestModal.requestcancelled": "Forespørgslen for {title} er annulleret.", "components.RequestModal.requestedited": "Forespørgslen for {title} er redigeret!", "components.RequestModal.requesterror": "Noget gik galt under indsendelsen af forespørgslen.", - "components.RequestModal.requestfrom": "{username}s forespørgsel afventer godkendelse.", + "components.RequestModal.requestfrom": "{username}'s forespørgsel afventer godkendelse.", "components.RequestModal.requestseasons": "Forespørg om {seasonCount} {seasonCount, plural, one {Sæson} other {Sæsoner}}", "components.RequestModal.season": "Sæson", "components.ResetPassword.passwordreset": "Nulstil Kodeord", @@ -1227,5 +1227,13 @@ "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Enhedsstandard", "components.Settings.Notifications.NotificationsPushover.sound": "Notifikationslyd", "components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Enhedsstandard", - "components.UserProfile.UserSettings.UserNotificationSettings.sound": "Notifikationslyd" + "components.UserProfile.UserSettings.UserNotificationSettings.sound": "Notifikationslyd", + "components.UserList.validationUsername": "Angiv et brugernavn", + "components.Login.signinwithjellyfin": "Brug din {mediaServerName} konto", + "components.Login.username": "Brugernavn", + "components.Login.validationEmailFormat": "Ugyldig e-mail", + "components.Login.validationEmailRequired": "Angiv en e-mail", + "components.Login.validationusernamerequired": "Brugernavn kræves", + "components.UserList.username": "Brugernavn", + "components.Login.credentialerror": "Brugernavnet eller kodeordet er forkert." } diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json index 3b347c0b..95ad9cf4 100644 --- a/src/i18n/locale/de.json +++ b/src/i18n/locale/de.json @@ -449,8 +449,8 @@ "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet-Benachrichtigungseinstellungen konnten nicht gespeichert werden.", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet-Benachrichtigungseinstellungen erfolgreich gespeichert!", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet Test Benachrichtigung fehlgeschlagen.", - "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Pushbullet test Benachrichtigung wird gesendet…", - "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet test Benachrichtigung gesendet!", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Pushbullet Test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet Test Benachrichtigung gesendet!", "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", @@ -479,8 +479,8 @@ "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.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!", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Web push Test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push Test Benachrichtigung gesendet!", "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push Benachrichtigungseinstellungen konnten nicht gespeichert werden.", "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push Benachrichtigungseinstellungen erfolgreich gespeichert!", "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Dienst aktivieren", @@ -489,8 +489,8 @@ "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Auf Standard zurücksetzen", "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON-Inhalt erfolgreich zurückgesetzt!", "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Hilfe zu Vorlagenvariablen", - "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook Benachrichtigungseinstellungen konnten nicht gespeichert werden.", - "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Webhook test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook Test Benachrichtigung konnte nicht gesendet werden.", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Webhook Test Benachrichtigung wird gesendet…", "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook Test Benachrichtigung gesendet!", "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Du musst einen gültigen JSON-Inhalt angeben", "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Sie müssen mindestens einen Benachrichtigungstypen auswählen", @@ -532,15 +532,15 @@ "components.Settings.Notifications.smtpPort": "SMTP-Port", "components.Settings.Notifications.telegramsettingsfailed": "Telegram-Benachrichtigungseinstellungen konnten nicht gespeichert werden.", "components.Settings.Notifications.telegramsettingssaved": "Telegram-Benachrichtigungseinstellungen erfolgreich gespeichert!", - "components.Settings.Notifications.toastDiscordTestFailed": "Discord test Benachrichtigung fehlgeschlagen.", - "components.Settings.Notifications.toastDiscordTestSending": "Discord test Benachrichtigung wird gesendet…", - "components.Settings.Notifications.toastDiscordTestSuccess": "Discord test Benachrichtigung gesendet!", - "components.Settings.Notifications.toastEmailTestFailed": "E-Mail test Benachrichtigung fehlgeschlagen.", - "components.Settings.Notifications.toastEmailTestSending": "Email test Benachrichtigung wird gesendet…", - "components.Settings.Notifications.toastEmailTestSuccess": "Email test Benachrichtigung gesendet!", - "components.Settings.Notifications.toastTelegramTestFailed": "Telegram test Benachrichtigung fehlgeschlagen.", - "components.Settings.Notifications.toastTelegramTestSending": "Telegram test Benachrichtigung wird gesendet…", - "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram test Benachrichtigung gesendet!", + "components.Settings.Notifications.toastDiscordTestFailed": "Discord Test Benachrichtigung fehlgeschlagen.", + "components.Settings.Notifications.toastDiscordTestSending": "Discord Test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.toastDiscordTestSuccess": "Discord Test Benachrichtigung gesendet!", + "components.Settings.Notifications.toastEmailTestFailed": "E-Mail Test Benachrichtigung fehlgeschlagen.", + "components.Settings.Notifications.toastEmailTestSending": "Email Test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.toastEmailTestSuccess": "Email Test Benachrichtigung gesendet!", + "components.Settings.Notifications.toastTelegramTestFailed": "Telegram Test Benachrichtigung fehlgeschlagen.", + "components.Settings.Notifications.toastTelegramTestSending": "Telegram Test Benachrichtigung wird gesendet…", + "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram Test Benachrichtigung gesendet!", "components.Settings.Notifications.validationBotAPIRequired": "Du musst ein Bot-Autorisierungstoken angeben", "components.Settings.Notifications.validationChatIdRequired": "Du musst eine gültige Chat-ID angeben", "components.Settings.Notifications.validationEmail": "Du musst eine gültige E-Mail-Adresse angeben", @@ -1071,7 +1071,7 @@ "components.Discover.tmdbtvgenre": "TMDB Serien Genre", "components.Discover.tmdbtvkeyword": "TMDB Serien Keyword", "components.Discover.tvgenres": "Serien Genre", - "components.Settings.SettingsMain.apikey": "API Schlüssel", + "components.Settings.SettingsMain.apikey": "API-Schlüssel", "components.Settings.SettingsMain.csrfProtection": "Aktivere CSRF Schutz", "components.Settings.SettingsMain.applicationTitle": "Anwendungstitel", "components.Settings.SettingsMain.csrfProtectionTip": "Limitiere externen API Zugriff auf Lese-Operationen (erfordert HTTPS)", @@ -1272,7 +1272,7 @@ "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", - "components.Settings.Notifications.userEmailRequired": "Benutzer-E-Mai", + "components.Settings.Notifications.userEmailRequired": "Benutzer-E-Mail erforderlich", "components.Settings.Notifications.NotificationsPushover.sound": "Benachrichtigungston", "components.Settings.SonarrModal.seriesType": "TV-Serie Typ", "components.Settings.jellyfinlibrariesDescription": "Die Bibliotheken {mediaServerName} werden nach Titeln durchsucht. Klicke auf die Schaltfläche unten, wenn keine Bibliotheken aufgelistet sind.", @@ -1346,5 +1346,62 @@ "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" + "components.Settings.jellyfinSyncFailedNoLibrariesFound": "Es wurden keine Bibliotheken gefunden", + "components.Settings.scanbackground": "Der Scanvorgang wird im Hintergrund ausgeführt. Sie können in der Zwischenzeit den Einrichtungsprozess fortsetzen.", + "components.Blacklist.blacklistdate": "Datum", + "components.PermissionEdit.viewblacklistedItems": "Medien auf der Sperrliste anzeigen.", + "components.Settings.SettingsMain.discoverRegion": "Region entdecken", + "components.Blacklist.blacklistNotFoundError": "{title} ist nicht auf der Sperrliste.", + "components.PermissionEdit.manageblacklist": "Sperrliste verwalten", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Jellyfin Refresh Token", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Region entdecken", + "i18n.blacklistDuplicateError": "{title} wurde bereits auf die Sperrliste gesetzt.", + "components.Settings.Notifications.validationWebhookRoleId": "Sie müssen eine gültige Discord Rollen-ID angeben", + "components.Settings.Notifications.webhookRoleIdTip": "Die Rollen ID, die in der Webhook Nachricht erwähnt werden soll. Leer lassen, um Erwähnungen zu deaktivieren", + "i18n.addToBlacklist": "Zur Sperrliste hinzufügen", + "components.PermissionEdit.blacklistedItemsDescription": "Autorisierung zum Sperren von Medien.", + "components.Settings.SettingsMain.proxyBypassFilterTip": "Verwenden Sie ',' als Trennzeichen und '*.' als Platzhalter für Subdomains", + "components.Settings.SettingsMain.streamingRegion": "Streaming Region", + "i18n.removeFromBlacklistSuccess": "{title} wurde erfolgreich von der Sperrliste entfernt.", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Streaming Region", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Streaming Seiten nach regionaler Verfügbarkeit anzeigen", + "components.Blacklist.blacklistedby": "{date} durch {user}", + "components.Blacklist.mediaName": "Name", + "components.Blacklist.mediaTmdbId": "tmdb Id", + "components.Blacklist.mediaType": "Typ", + "component.BlacklistBlock.blacklistdate": "Sperrdatum", + "component.BlacklistBlock.blacklistedby": "Gesperrt durch", + "components.Blacklist.blacklistSettingsDescription": "Medien auf der Sperrliste verwalten.", + "components.Blacklist.blacklistsettings": "Sperrlisteneinstellungen", + "component.BlacklistModal.blacklisting": "Sperrliste", + "components.Layout.Sidebar.blacklist": "Sperrliste", + "components.PermissionEdit.manageblacklistDescription": "Erlaubnis zur Verwaltung von Medien auf der Sperrliste erteilen.", + "components.PermissionEdit.blacklistedItems": "Medien sperren.", + "components.PermissionEdit.viewblacklistedItemsDescription": "Erlaubnis zum Anzeigen von Medien auf der Sperrliste erteilen.", + "components.RequestList.RequestItem.removearr": "Von {arr} entfernen", + "components.RequestList.sortDirection": "Sortierrichtung umschalten", + "components.Settings.Notifications.webhookRoleId": "Benachrichtigung Rollen ID", + "components.Settings.SettingsJobsCache.usersavatars": "Avatare der Nutzer", + "components.Settings.SettingsMain.discoverRegionTip": "Inhalte nach regionaler Verfügbarkeit filtern", + "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Proxy für lokale Adressen umgehen", + "components.Settings.SettingsMain.proxyEnabled": "HTTP(S) Proxy", + "components.Settings.SettingsMain.proxyHostname": "Proxy Hostname", + "components.Settings.SettingsMain.proxyPassword": "Proxy Passwort", + "components.Settings.SettingsMain.proxyPort": "Proxy Port", + "components.Settings.SettingsMain.proxySsl": "SSL für Proxy verwenden", + "components.Settings.SettingsMain.proxyUser": "Proxy Benutzername", + "components.Settings.SettingsMain.proxyBypassFilter": "vom Proxy ignorierte Adressen", + "components.Settings.SettingsMain.streamingRegionTip": "Streaming Seiten nach regionaler Verfügbarkeit anzeigen", + "components.Settings.SettingsMain.validationProxyPort": "Sie müssen einen gültigen Port angeben", + "components.Settings.apiKey": "API-Schlüssel", + "components.Settings.tip": "Tipp", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Inhalte nach regionaler Verfügbarkeit filtern", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "Diese E-Mail ist bereits vergeben!", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Ein anderer Benutzer hat bereits diesen Benutzernamen. Sie müssen eine E-Mail festlegen", + "i18n.blacklist": "Sperrliste", + "i18n.blacklistError": "Etwas ist schief gelaufen, versuchen Sie es noch einmal.", + "i18n.blacklistSuccess": "{title} wurde erfolgreich auf die Sperrliste gesetzt.", + "i18n.blacklisted": "Gesperrt", + "i18n.removefromBlacklist": "Von der Sperrliste entfernen", + "i18n.specials": "Besonderheiten" } diff --git a/src/i18n/locale/eu.json b/src/i18n/locale/eu.json index 2f4e910d..2b4bec78 100644 --- a/src/i18n/locale/eu.json +++ b/src/i18n/locale/eu.json @@ -234,5 +234,1174 @@ "components.TvDetails.overview": "Ikuspegi orokorra", "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Administratzailea", "components.UserProfile.UserSettings.UserGeneralSettings.role": "Rola", - "i18n.approved": "Onartuta" + "i18n.approved": "Onartuta", + "components.Discover.CreateSlider.nooptions": "Emaitzarik ez.", + "components.Discover.CreateSlider.searchGenres": "Bilatu generoak…", + "components.Discover.CreateSlider.searchStudios": "Bilatu estudioak…", + "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} filmak", + "components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} filmak", + "components.Discover.FilterSlideover.studio": "Estudioa", + "components.Discover.studios": "Estudioak", + "components.IssueDetails.IssueDescription.description": "Deskribapena", + "components.Layout.Sidebar.browsetv": "Telesailak", + "components.ManageSlideOver.tvshow": "telesailak", + "components.Settings.mediaTypeSeries": "telesaila", + "i18n.tvshow": "Telesailak", + "i18n.tvshows": "Telesailak", + "components.Discover.StudioSlider.studios": "Estudioak", + "components.Discover.networks": "Sareak", + "components.Settings.jellyfinSettingsDescription": "Aukeran, barne eta kanpo-konexioko puntuak konfiguratu {mediaServerName} zerbitzariarentzat. Kasu gehienetan, kanpoko URLa barnekoaren desberdina da. Era berean, pasahitza berrezartzeko URL pertsonalizatu bat ezar daiteke {mediaServerName} saioaren hasierarako, pasahitz desberdin bat berrezartzeko orri bat nahi baduzu. Jellyfinen API gakoa ere aldatu daiteke, lehenago automatikoki sortua.", + "components.Discover.DiscoverWatchlist.discoverwatchlist": "Zure jarraipenak", + "component.BlacklistModal.blacklisting": "Debekua", + "components.Discover.DiscoverWatchlist.watchlist": "Plex jarraipen-zerrenda", + "components.Discover.FilterSlideover.originalLanguage": "Jatorrizko hizkuntza", + "components.Discover.FilterSlideover.releaseDate": "Argitalpen data", + "components.Discover.FilterSlideover.streamingservices": "Streaming zerbitzuak", + "components.Discover.MovieGenreList.moviegenres": "Film generoak", + "components.Discover.MovieGenreSlider.moviegenres": "Film generoak", + "components.Discover.PlexWatchlistSlider.plexwatchlist": "Zure jarraipenak", + "components.Discover.RecentlyAddedSlider.recentlyAdded": "Duela gutxi gehituta", + "components.Discover.TvGenreList.seriesgenres": "Telesailen generoak", + "components.Discover.tmdbstudio": "TMDB Estudioa", + "components.Discover.tvgenres": "Telesailen generoak", + "components.Discover.upcoming": "Hurrengo filmak", + "components.IssueDetails.IssueDescription.deleteissue": "Ezabatu intzidentzia", + "components.IssueDetails.IssueDescription.edit": "Editatu deskribapena", + "components.IssueDetails.allepisodes": "Atal guztiak", + "components.IssueDetails.allseasons": "Denboraldi guztiak", + "components.IssueDetails.closeissue": "Itxi intzidentzia", + "components.IssueDetails.deleteissue": "Ezabatu intzidentzia", + "components.IssueDetails.episode": "{episodeNumber} atala", + "components.IssueDetails.lastupdated": "Azken eguneraketa", + "components.IssueDetails.nocomments": "Iruzkinik ez.", + "components.IssueDetails.reopenissue": "Ireki intzidentzia berriro", + "components.IssueList.IssueItem.problemepisode": "Kaltetutako atala", + "components.IssueList.IssueItem.viewissue": "Ikusi intzidentzia", + "components.IssueList.sortAdded": "Azkenak", + "components.IssueList.sortModified": "Azkenik aldatuak", + "components.IssueModal.CreateIssueModal.allepisodes": "Atal guztiak", + "components.IssueModal.CreateIssueModal.allseasons": "Denboraldi guztiak", + "components.IssueModal.CreateIssueModal.episode": "{episodeNumber} atala", + "components.IssueModal.CreateIssueModal.problemepisode": "Kaltetutako atala", + "components.IssueModal.CreateIssueModal.problemseason": "Kaltetutako denboraldia", + "components.IssueModal.CreateIssueModal.season": "{seasonNumber} denboraldia", + "components.IssueModal.CreateIssueModal.submitissue": "Bidali intzidentzia", + "components.IssueModal.CreateIssueModal.toastviewissue": "Ikusi intzidentzia", + "components.IssueModal.CreateIssueModal.whatswrong": "Zer gertatzen da?", + "components.LanguageSelector.languageServerDefault": "({language}) lehenetsia", + "components.LanguageSelector.originalLanguageDefault": "Hizkuntza guztiak", + "components.Layout.LanguagePicker.displaylanguage": "Bistaratzeko hizkuntza", + "components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Film eskaerak", + "components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Telesail eskaerak", + "components.Layout.UserDropdown.signout": "Itxi saioa", + "components.Layout.VersionStatus.streamdevelop": "Jellyseerr Develop", + "components.Layout.VersionStatus.streamstable": "Jellyseerr Stable", + "components.Login.back": "Itzuli", + "components.Login.email": "Helbide elektronikoa", + "components.Login.enablessl": "Erabili SSL", + "components.Login.forgotpassword": "Pasahitza ahaztu duzu?", + "components.Login.hostname": "{mediaServerName} URL", + "components.Login.servertype": "Zerbitzari mota", + "components.Login.signin": "Hasi saioa", + "components.Login.signingin": "Saioa hasten…", + "components.Login.title": "Gehitu e-posta", + "components.Login.urlBase": "Oinarrizko URLa", + "components.Login.validationEmailFormat": "Helbide elektroniko hau baliogabea da", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Honek {mediaType}-ko datu guztiak betirako ezabatuko ditu, eskaera guztiak barne. Elementu hau {mediaServerName} liburutegian badago, edukiaren informazioa hurrengo eskanerrean birsortuko da.", + "components.Login.validationusernamerequired": "Erabiltzaile-izen bat behar da", + "components.ManageSlideOver.alltime": "Denbora guztia", + "components.ManageSlideOver.manageModalClearMedia": "Garbitu datuak", + "components.ManageSlideOver.manageModalIssues": "Irekitako intzidentziak", + "components.ManageSlideOver.manageModalMedia4k": "4K edukia", + "components.ManageSlideOver.manageModalNoRequests": "Eskaerarik ez.", + "components.ManageSlideOver.manageModalTitle": "Kudeatu {mediaType}", + "components.ManageSlideOver.playedby": "Honek erreproduzituta", + "components.MediaSlider.ShowMoreCard.seemore": "Ikusi gehiago", + "components.MovieDetails.MovieCast.fullcast": "Aktore guztiak", + "components.MovieDetails.MovieCrew.fullcrew": "Talde guztiak", + "components.MovieDetails.digitalrelease": "Argitalpen digitala", + "components.MovieDetails.downloadstatus": "Deskargaren egoera", + "components.MovieDetails.managemovie": "Kudeatu filma", + "components.MovieDetails.originallanguage": "Jatorrizko hizkuntza", + "components.MovieDetails.originaltitle": "Jatorrizko izenburua", + "components.MovieDetails.overviewunavailable": "Ikuspegi orokorra ez dago eskuragarri.", + "components.MovieDetails.physicalrelease": "Argitalpen fisikoa", + "components.MovieDetails.runtime": "{minutes} minutu", + "components.MovieDetails.showless": "Erakutsi gutxiago", + "components.MovieDetails.showmore": "Erakutsi gehiago", + "components.MovieDetails.watchtrailer": "Ikusi trailerra", + "components.NotificationTypeSelector.issuecomment": "Intzidentziaren iruzkina", + "components.NotificationTypeSelector.issuecreated": "Intzidentzia bidalita", + "components.NotificationTypeSelector.issuereopened": "Intzidentzia berriro irekita", + "components.NotificationTypeSelector.issueresolved": "Intzidentzia konponduta", + "components.NotificationTypeSelector.mediaapproved": "Eskaera onartuta", + "components.NotificationTypeSelector.mediaavailable": "Eskaera eskuragarri", + "components.NotificationTypeSelector.notificationTypes": "Jakinarazpen motak", + "components.PermissionEdit.advancedrequest": "Eskaera aurreratuak", + "components.PermissionEdit.autoapprove4k": "Auto-onartu 4K", + "components.PermissionEdit.autoapproveMovies": "Auto-onartu filmak", + "components.PermissionEdit.autoapproveSeries": "Auto-onartu telesailak", + "components.PermissionEdit.autorequestMovies": "Auto-eskatu filmak", + "components.PermissionEdit.createissues": "Bidali intzidentziak", + "components.PermissionEdit.manageblacklist": "Kudeatu zerrenda beltza", + "components.PermissionEdit.manageissues": "Kudeatu intzidentziak", + "components.PermissionEdit.managerequests": "Kudeatu eskaerak", + "components.PermissionEdit.request4k": "Eskatu 4K", + "components.PermissionEdit.requestMovies": "Eskatu filmak", + "components.PermissionEdit.requestTv": "Eskatu telesailak", + "components.PermissionEdit.users": "Kudeatu erabiltzaileak", + "components.PermissionEdit.viewissues": "Ikusi intzidentziak", + "components.PermissionEdit.viewrequests": "Ikusi eskaerak", + "components.PersonDetails.ascharacter": "{character} gisa", + "components.PersonDetails.birthdate": "{birthdate}(e)an jaiota", + "components.PlexLoginButton.signingin": "Saioa hasten…", + "components.PlexLoginButton.signinwithplex": "Hasi saioa", + "components.RequestBlock.approve": "Onartu eskaera", + "components.RequestBlock.decline": "Ukatu eskaera", + "components.RequestBlock.delete": "Ezabatu eskaera", + "components.RequestBlock.edit": "Editatu eskaera", + "components.RequestBlock.languageprofile": "Hizkuntza profila", + "components.RequestBlock.profilechanged": "Kalitate profila", + "components.RequestBlock.requestdate": "Eskaera data", + "components.RequestBlock.requestedby": "Honek eskatuta", + "components.RequestBlock.requestoverrides": "Eskaera anulazioak", + "components.RequestButton.approverequest": "Onartu eskaera", + "components.RequestButton.declinerequest": "Ukatu eskaera", + "components.RequestButton.requestmore": "Eskatu gehiago", + "components.RequestButton.viewrequest": "Ikusi eskaera", + "components.RequestCard.approverequest": "Onartu eskaera", + "components.RequestCard.cancelrequest": "Utzi eskaera", + "components.RequestCard.declinerequest": "Ukatu eskaera", + "components.RequestCard.deleterequest": "Ezabatu eskaera", + "components.RequestCard.editrequest": "Editatu eskaera", + "components.RequestCard.tmdbid": "TMDB ID", + "components.RequestCard.tvdbid": "TheTVDB ID", + "components.RequestCard.unknowntitle": "Izenburu ezezaguna", + "components.RequestList.RequestItem.cancelRequest": "Utzi eskaera", + "components.RequestList.RequestItem.deleterequest": "Ezabatu eskaera", + "components.RequestList.RequestItem.editrequest": "Editatu eskaera", + "components.RequestList.RequestItem.tmdbid": "TMDB ID", + "components.RequestList.RequestItem.tvdbid": "TheTVDB ID", + "components.RequestList.RequestItem.unknowntitle": "Izenburu ezezaguna", + "components.RequestList.sortAdded": "Azkenak", + "components.RequestModal.AdvancedRequester.default": "{name} (Lehenetsia)", + "components.RequestModal.AdvancedRequester.destinationserver": "Helburuko zerbitzaria", + "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", + "components.RequestModal.AdvancedRequester.languageprofile": "Hizkuntza profila", + "components.RequestModal.AdvancedRequester.qualityprofile": "Kalitate profila", + "components.RequestModal.AdvancedRequester.selecttags": "Hautatu etiketak", + "components.RequestModal.QuotaDisplay.requiredquota": "Gutxienez {seasons} {seasons, plural, one {season request} other {season requests}} erabilgarri izan behar duzu/dituzu telesail honetarako eskaera bat bidaltzeko.", + "components.RequestModal.alreadyrequested": "Eskatuta dagoeneko", + "components.RequestModal.approve": "Onartu eskaera", + "components.RequestModal.autoapproval": "Onarpen automatikoa", + "components.RequestModal.cancel": "Utzi eskaera", + "components.RequestModal.edit": "Editatu eskaera", + "components.Settings.SettingsAbout.runningDevelop": "Jellyseerr-en garapen adarra exekutatzen ari zara, garapenean laguntzen duten edo \"bleeding-edge\" probetan laguntzen dutenentzat bakarrik gomendatzen dena.", + "components.Settings.SettingsJobsCache.imagecacheDescription": "Ezarpenetan gaituta dagoenean, Jellyseerr-ek aurrez konfiguratutako kanpoko iturrietatik proxya egin eta irudiak cacheatuko ditu. Cachean gordetako irudiak zure konfigurazio karpetan gordetzen dira. Fitxategiak {appDataPath}/cache/images bide-izenean aurki ditzakezu.", + "components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr-ek mantentze-lan batzuk egiten ditu aldizka programatutako lan gisa, baina behean eskuz ere abiarazi daitezke. Lan bat eskuz exekutatzeak ez du bere programazioa aldatuko.", + "components.Settings.manualscanDescription": "Normalean, hau 24 orduz behin bakarrik egingo da. Jellyseerr-ek zure Plex zerbitzariaren berriki gehitutakoak gehiagotan egiaztatuko dira. Plex konfiguratzen duzun lehen aldia bada, liburutegi osoa eskuz eskaneatzea gomendatzen da!", + "components.Settings.manualscanDescriptionJellyfin": "Normalean, hau 24 orduz behin bakarrik egingo da. Jellyseerr-ek zure {mediaServerName} zerbitzariaren berriki gehitutakoak gehiagotan egiaztatuko dira. Jellyseerr konfiguratzen duzun lehen aldia bada, liburutegi osoa eskuz eskaneatzea gomendatzen da!", + "components.Settings.noDefaultNon4kServer": "{serverType} zerbitzari bakarra baduzu 4K eta ez-4K edukirako (edo 4k edukia soilik deskargatzen baduzu), zure {serverType} zerbitzaria EZ da 4k zerbitzari gisa markatu behar.", + "components.Settings.settingUpPlexDescription": "Plex konfiguratzeko, xehetasunak eskuz sartu edo plex.tv-tik lortutako zerbitzari bat hauta dezakezu. Sakatu goitibeherako menuaren eskuinean dagoen botoia erabilgarri dauden zerbitzariak lortzeko.", + "components.Settings.plexlibrariesDescription": "Jellyseerr-ek eskaneatuko dituen liburutegiak. Konfiguratu eta gorde zure Plex konexioaren ezarpenak, eta egin klik beheko botoian liburutegirik zerrendatzen ez bada.", + "components.UserList.newJellyfinsigninenabled": "Gaitu {mediaServerName} saio-hasiera berria ezarpena gaituta dago une honetan. Liburutegirako sarbidea duten {mediaServerName} erabiltzaileak ez dira inportatu behar saioa hasteko.", + "components.UserList.newplexsigninenabled": "Gaitu Plex-en saioa hasteko modu berria ezarpena gaituta dago une honetan. Liburutegirako sarbidea duten Plex erabiltzaileak ez dira inportatu behar saioa hasteko.", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Zure kontuak ez du pasahitzik ezarrita. Konfiguratu pasahitz bat behean kontu hau \"erabiltzaile lokal\" gisa saioa hasteko zure helbide elektronikoa erabiliz.", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "Erabiltzaile honek gutxienez {seasons} {seasons, plural, one {season request} other {season requests}} erabilgarri izan behar du/ditu telesail honetarako eskaera bat bidaltzeko.", + "components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} Telesailak", + "components.IssueDetails.problemepisode": "Kaltetutako atala", + "components.IssueDetails.season": "{seasonNumber} denboraldia", + "components.RequestModal.AdvancedRequester.requestas": "Eskatu erabiltzaile honen izenean", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Erabiltzaile-kontu honek ez du pasahitzik ezarrita. Konfiguratu pasahitz bat behean kontu hau \"erabiltzaile lokal\" gisa saioa hasi dezan.", + "components.IssueDetails.IssueComment.edit": "Editatu iruzkina", + "components.IssueDetails.problemseason": "Kaltetutako denboraldia", + "components.RequestList.sortModified": "Azkenekoz aldatutakoak", + "components.Discover.TvGenreSlider.tvgenres": "Telesailen generoak", + "components.NotificationTypeSelector.mediadeclined": "Eskaera ukatuta", + "components.PermissionEdit.autorequestSeries": "Auto-eskatu telesailak", + "components.MovieDetails.theatricalrelease": "Zineman argitaratuta", + "components.RequestBlock.server": "Helburuko zerbitzaria", + "components.RequestModal.AdvancedRequester.notagoptions": "Etiketarik gabe.", + "components.Settings.serviceSettingsDescription": "Konfiguratu zure {serverType} zerbitzaria(k) jarraian. Hainbat {serverType} zerbitzari konekta ditzakezu, baina horietako bi bakarrik markatu daitezke lehetsi gisa (bata 4K eta bestea 4K ez dena). Administratzaileek eskaera berriak prozesatzeko erabilitako zerbitzaria baliogabetu dezakete onartu aurretik.", + "components.PermissionEdit.managerequestsDescription": "Eduki-eskaerak kudeatzeko baimenak ematen ditu. Baimen hau duen erabiltzaile baten eskaera guztiak automatikoki onartuko dira.", + "components.Settings.noDefault4kServer": "{serverType}-(e)ko 4K zerbitzari bat lehenetsi behar da erabiltzaileen {mediaType}-(e)ko 4K eskaerak gaitu ahal izateko.", + "components.ResetPassword.requestresetlinksuccessmessage": "Pasahitza berrezartzeko esteka bat adierazitako helbide elektronikora bidaliko da, erabiltzaile baliozko bati lotuta badago.", + "components.AppDataWarning.dockerVolumeMissingDescription": "{appDataPath} bolumenaren muntaia ez da behar bezala konfiguratu. Datu guztiak ezabatu egingo dira edukiontzia gelditu edo berriro hasten denean.", + "components.Login.description": "{applicationName} aplikazioan saioa hasten duzun lehen aldia denez, baliozko helbide elektroniko bat gehitu behar duzu.", + "components.PermissionEdit.usersDescription": "Eman baimena erabiltzaileak kudeatzeko. Baimen hau duten erabiltzaileek ezin dituzte administratzaile-pribilegioa duten erabiltzaileak aldatu edo pribilegio hau eman.", + "components.Settings.SettingsAbout.betawarning": "Hau BETA softwarea da. Ezaugarriak apurtura edota desegonkorrak izan daitezke. Mesedez, eman edozein arazoren berri GitHuben!", + "components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerrrek kanpoko APIaren eskaerak cachean gordetzen ditu, errendimendua optimizatzeko eta beharrezkoak ez diren API deirik ez egiteko.", + "components.Settings.jellyfinsettingsDescription": "Konfiguratu zerbitzarirako doikuntzak {mediaServerName}-(e)rako. {mediaServerName} liburutegiak eskaneatzen ditu, zer eduki dagoen ikusteko.", + "components.Settings.noDefaultServer": "Gutxienez {serverType} zerbitzari bat lehenetsi gisa markatu behar da {mediaType} eskaerak prozesatu ahal izateko.", + "components.Settings.tautulliSettingsDescription": "Aukeran, konfiguratu zure Tautulli zerbitzariaren ezarpenak. Jellyseerr-ek Tautulliren Plex multimedia erreprodukzioen historialaren datuak lortu ditu.", + "components.Settings.plexsettingsDescription": "Konfiguratu zure Plex zerbitzariaren ezarpenak. Jellyseerr-ek zure Plexeko liburutegiak eskaneatzen ditu edukiaren erabilgarritasuna zehazteko.", + "components.Settings.webAppUrlTip": "Aukeran erabiltzaileak zure zerbitzariko web aplikaziora zuzendu, web-aplikazio \"ostatuaren\" ordez", + "components.UserList.deleteconfirm": "Ziur al zaude erabiltzaile hau ezabatu nahi duzula? Bere eskaera guztiak betirako ezabatuko dira.", + "components.Login.emailtooltip": "Helbidea ez da zertan zure {mediaServerName} instantziari lotu behar.", + "components.ManageSlideOver.manageModalRemoveMediaWarning": "* Honek {mediaType} hau betirako ezabatuko du {arr}(e)tik, fitxategi guztiak barne.", + "components.NotificationTypeSelector.mediafailedDescription": "Bidali jakinarazpenak multimedia eskaerak Radarr eta Sonarr-en gehitzeak huts egiten badu.", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Jakinarazi beste erabiltzaileek automatikoki onartzen diren multimedia eskaera berriak bidaltzen dituztenean.", + "components.NotificationTypeSelector.usermediafailedDescription": "Jakinarazi multimedia eskaerak Radarr edo Sonarr-en gehitzen ez direnean.", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Bidali jakinarazpenak erabiltzaileek automatikoki onartzen diren multimedia eskaera berriak bidaltzen dituztenean.", + "components.NotificationTypeSelector.usermediarequestedDescription": "Jakinarazi beste erabiltzaile batzuek onartu beharreko multimedia eskaerak bidaltzen dituztenean.", + "components.NotificationTypeSelector.mediarequestedDescription": "Bidali jakinarazpenak erabiltzaileek onarpena behar duten multimedia eskaera berriak bidaltzen dituztenean.", + "components.PermissionEdit.autorequestDescription": "Baimendu Plexen jarraipen-zerrendaren bidez 4K ez diren eskaerak automatikoki bidaltzeko.", + "components.PermissionEdit.autorequestMoviesDescription": "Baimendu Plexen jarraipen-zerrendaren bidez 4K ez diren film eskaerak automatikoki bidaltzeko.", + "components.PermissionEdit.autorequestSeriesDescription": "Baimendu Plexen jarraipen-zerrendaren bidez 4K ez diren telesail eskaerak automatikoki bidaltzeko.", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "Ezin izan dugu telesail hau automatikoki lotu. Mesedez, hautatu bat datorrena azpian.", + "components.Settings.Notifications.chatIdTip": "Hasi txat bat zure bot-arekin, gehitu @get_id_bot eta egin /my_id komandoa", + "components.Settings.Notifications.encryptionTip": "Kasu gehienetan, TLS inplizituak 465 portua eta STARTTLSk 587 portua erabiltzen dute", + "components.Settings.RadarrModal.tagRequestsInfo": "Gehitu etiketa gehigarri bat automatikoki eskaeraren erabiltzaile ID eta erabiltzaile-izenarekin", + "components.Settings.SettingsLogs.logsDescription": "Erregistro horiek zuzenean ikus ditzakezu stdout edo {appDataPath}/logs/overseerr.log bidez.", + "components.Settings.SettingsMain.proxyBypassFilterTip": "Erabili ',' banatzaile eta '*.' komodin gisa azpidominioetarako", + "components.Settings.SonarrModal.tagRequestsInfo": "Gehitu etiketa gehigarri bat automatikoki eskaeraren erabiltzaile ID eta erabiltzaile-izenarekin", + "components.Settings.restartrequiredTooltip": "Jellyseerr berrabiarazi egin behar da aldaketa hauek indarrean jartzeko", + "components.UserList.passwordinfodescription": "Konfiguratu aplikazioaren URL bat eta gaitu posta elektronikoko jakinarazpenak pasahitz sortze automatikoa ahalbidetzeko.", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Arazoren bat izan da pasahitza gordetzean. Zure uneko pasahitza behar bezala sartu duzu?", + "components.Settings.Notifications.webhookRoleIdTip": "Webhook mezuan aipatu beharreko rolaren IDa. Utzi hutsik aipamenak desgaitzeko", + "components.Discover.resetwarning": "Berrezarri kontrol irristari guztiak modu lehenetsira. Honek kontrol irristari pertsonalizatuak ere ezabatuko ditu!", + "components.RequestModal.QuotaDisplay.quotaLink": "Zure eskaera-mugen laburpena ikus dezakezu zure profil-orrian.", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Web push jakinarazpenak jasotzeko, Jellyseerr HTTPS bidez zerbitzatu behar da.", + "components.Settings.SettingsMain.csrfProtectionHoverTip": "Ez ezazu konfigurazio hau gaitu ez badakizun zer egiten ari zaren!", + "components.Discover.DiscoverMovies.activefilters": "{count, plural, one {Gaitutako iragazki #} other {Gaitutako # iragazki}}", + "components.Discover.DiscoverTv.activefilters": "{count, plural, one {Gaitutako iragazki #} other {Gaitutako # iragazki}}", + "components.Discover.FilterSlideover.activefilters": "{count, plural, one {Gaitutako iragazki #} other {Gaitutako # iragazki}}", + "components.IssueModal.CreateIssueModal.providedetail": "Mesedez, eman aurkitu duzun arazoaren azalpen zehatza.", + "components.NotificationTypeSelector.mediaautorequestedDescription": "Jakinarazi multimedia eskaera berriak automatikoki bidaltzen direnean zure jarraipen-zerrendan.", + "components.PermissionEdit.viewissuesDescription": "Baimendu beste erabiltzaileek sortutako multimedia arazoak ikustea.", + "components.PermissionEdit.viewrecentDescription": "Baimendu orain dela gutxi gehitutako multimedia-zerrenda ikustea.", + "components.PermissionEdit.viewrequestsDescription": "Baimendu beste erabiltzaile batzuek bidalitako bitarteko-eskaerak ikustea.", + "components.RequestButton.approve4krequests": "Onartu {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}", + "components.RequestButton.decline4krequests": "Ukatu {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}", + "components.RequestModal.QuotaDisplay.allowedRequests": " {limit} {type} eskaera egin ditzakezu {days} egunero.", + "components.RequestModal.SearchByNameModal.nomatches": "Ezin izan da telesail honetarako bat-etortze bat aurkitu.", + "components.ResetPassword.validationpasswordminchars": "Pasahitza laburregia da; gutxienez 8 karaktere izan beharko lituzke", + "components.Settings.Notifications.botUsernameTip": "Baimendu erabiltzaileei zure bot-ekin txat bat hasi eta jakinarazpenak konfiguratzen", + "components.Settings.SettingsMain.cacheImagesTip": "Kanpotik ateratako irudiak cacheatu (biltegi kantitate handia behar du)", + "components.Settings.SettingsMain.trustProxyTip": "Onartu Jellyseerr proxy baten atzean dauden bezeroen IP helbideak ondo erregistratzea", + "components.Settings.jellyfinlibrariesDescription": "{mediaServerName}-(e)k eskaneatzen dituen liburutegiak. Egin klik beheko botoian liburutegirik zerrendatzen ez bada.", + "components.Settings.scanbackground": "Eskaneatzea atzeko planoan egongo da. Bitartean konfigurazio prozesua jarraitu dezakezu.", + "components.UserList.usercreatedfailedexisting": "Emandako helbide elektronikoa dagoeneko beste erabiltzaile batek darabil.", + "components.UserList.validationpasswordminchars": "Pasahitza laburregia da; gutxienez 8 karaktere izan beharko lituzke", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Beste erabiltzaile batek dagoeneko erabiltzaile-izen hori du. Helbide elektronikoa ezarri behar duzu", + "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "Ez duzu baimenik erabiltzaile honen pasahitza aldatzeko.", + "components.UserProfile.UserSettings.unauthorizedDescription": "Ez duzu baimenik erabiltzaile honen ezarpenak aldatzeko.", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPasswordLength": "Pasahitza laburregia da; gutxienez 8 karaktere izan beharko lituzke", + "components.Settings.SettingsUsers.localLoginTip": "Baimendu erabiltzaileei haien helbide elektronikoa eta pasahitza erabiliz saioa hasten, {mediaServerName}-(r)en OAuth ordez", + "components.RequestModal.QuotaDisplay.allowedRequestsUser": "Erabiltzaile honek {limit} {type} eskaera egin ditzake {days} egunero.", + "components.RequestModal.QuotaDisplay.quotaLinkUser": "Erabiltzailearen eskaera-mugen laburpena ikus dezakezu profil orrian.", + "components.RequestModal.requestmovies4k": "Eskatu {count} {count, plural, one {film} other {film}} 4K-n", + "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {0} other {#}} {type} {remaining, plural, one {eskaera} other {eskaera}} geratzen dira", + "components.Discover.CreateSlider.needresults": "Gutxienez emaitza 1 behar duzu.", + "components.Discover.CreateSlider.editsuccess": "Irristaria editatuta eta pertsonalizazio ezarpenak gordeta.", + "components.Discover.PlexWatchlistSlider.emptywatchlist": "Zure Plex jarraipen-zerrendara gehitutako multimedia hemen agertuko da.", + "components.Discover.emptywatchlist": "Zure Plex jarraipen-zerrendara gehitutako multimedia hemen agertuko da.", + "components.Discover.resetfailed": "Zerbait gaizki atera da pertsonalizazioaren konfigurazioa berrezartzean.", + "components.IssueDetails.IssueComment.areyousuredelete": "Ziur zaude iruzkin hau ezabatu nahi duzula?", + "components.IssueDetails.deleteissueconfirm": "Ziur zaude intzidentzia hau ezabatu nahi duzula?", + "components.Login.adminerror": "Administratzaile kontu bat erabili behar duzu saioa hasteko.", + "components.Login.validationHostnameRequired": "Baliozko ostalari-izen edo IP helbide bat eman behar duzu", + "components.Login.validationUrlBaseTrailingSlash": "URLa ez du amaierako barra batean amaitu behar", + "components.NotificationTypeSelector.adminissuereopenedDescription": "Jakinarazi beste erabiltzaile batzuek intzidentziak berriro irekitzen dituztenean.", + "components.NotificationTypeSelector.adminissueresolvedDescription": "Jakinarazi beste erabiltzaile batzuek intzidentziak konpontzen dituztenean.", + "components.NotificationTypeSelector.userissuecommentDescription": "Jakinarazki intzidentziak iruzkin berriak jasotzen dituztenean.", + "components.Settings.Notifications.NotificationsPushover.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Erregistratu aplikazio bat Jellyseerr-en erabiltzeko", + "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Baliozko aplikazio token bat eman behar duzu", + "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Baliozko JSON edukia eman behar duzu", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push jakinarazpenen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.botApiTip": "Sortu bot bat Jellyseerr-en erabiltzeko", + "components.Settings.Notifications.validationSmtpHostRequired": "Baliozko ostalari-izen edo IP helbide bat eman behar duzu", + "components.Settings.Notifications.validationPgpPrivateKey": "Baliozko PGP gako pribatu bat eman behar duzu", + "components.Settings.Notifications.validationWebhookRoleId": "Baliozko Discord rol baten IDa eman behar duzu", + "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "URLa ez du amaierako barra batean amaitu behar", + "components.Settings.RadarrModal.validationHostnameRequired": "Baliozko ostalari-izen edo IP helbide bat eman behar duzu", + "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "{jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "{jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "{jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}", + "components.Settings.SettingsMain.toastApiKeyFailure": "Zerbait gaizki joan da API gako berria sortzean.", + "components.Settings.SettingsMain.csrfProtectionTip": "Ezarri kanpoko API sarbidea irakurtzeko soilik (HTTPS behar du)", + "components.Settings.SettingsUsers.newPlexLoginTip": "Onartu {mediaServerName} erabiltzaileak saioa hastea inportatu gabe", + "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "URLa ez du amaierako barra batean amaitu behar", + "components.Settings.SonarrModal.validationHostnameRequired": "Baliozko ostalari-izen edo IP helbide bat eman behar duzu", + "components.Settings.deleteserverconfirm": "Ziur zaude zerbitzari hau ezabatu nahi duzula?", + "components.Settings.experimentalTooltip": "Ezarpen hau gaitzeak aplikazioaren ustekabeko portaera sortu dezake", + "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.Settings.jellyfinSyncFailedAutomaticGroupedFolders": "Autentifikazio pertsonalizatua liburutegiko taldekatze automatikoarekin ez da onartzen", + "components.Settings.validationHostnameRequired": "Baliozko ostalari-izen edo IP helbide bat eman behar duzu", + "components.Settings.validationUrlBaseTrailingSlash": "URLa ez da barra batean amaitu behar", + "components.Settings.validationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.StatusChecker.appUpdatedDescription": "Mesedez, egin klik azpiko botoian aplikazioa freskatzeko.", + "components.StatusChecker.restartRequiredDescription": "Mesedez, berrabiarazi zerbitzaria eguneratutako ezarpenak aplikatzeko.", + "components.TvDetails.episodeCount": "{episodeCount, plural, one {Atal #} other {# atal}}", + "components.UserList.importedfromJellyfin": "{userCount} {mediaServerName} {userCount, plural, one {erabiltzaile} other {erabiltzaile}} ondo inportatu dira!", + "components.UserList.importedfromplex": "{userCount} Plex {userCount, plural, one {erabiltzaile} other {erabiltzaile}} ondo inportatu dira!", + "components.TvDetails.seasons": "{seasonCount, plural, one {Denboraldi #} other {# denboraldi}}", + "components.UserList.localLoginDisabled": "Gaitu tokiko saioa ezarpena desgaituta dago.", + "components.UserProfile.UserSettings.UserGeneralSettings.discordIdTip": "Zure Discord kontuarekin bat datorren multi-digitu ID zenbakia", + "components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "Baliozko Discord erabiltzaile ID bat eman behar duzu", + "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "Zure kontuarekin bat datorren multi-digitu ID zenbakia", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Baliozko erabiltzaile edo talde-gakoa eman behar duzu", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Baliozko PGP gako publiko bat eman behar duzu", + "components.UserProfile.emptywatchlist": "Zure Plex jarraipen-zerrendara gehitutako multimedia hemen agertuko da.", + "components.RequestModal.requestseasons4k": "Eskatu {seasonCount} {seasonCount, plural, one {denboraldi} other {denboraldi}} 4K-n", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Hasi txat bat, gehitu @get_id_bot, eta egin /my_id komandoa", + "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Settings.Notifications.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Sortu token bat zure kontuaren ezarpenetatik", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Baliozko erabiltzaile edo talde-gakoa eman behar duzu", + "components.Settings.advancedTooltip": "Ezarpen hau gaizki konfiguratzeak funtzionalitatea apurtu dezake", + "components.Settings.Notifications.NotificationsSlack.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Discover.CreateSlider.addsuccess": "Irristari berria sortu eta pertsonalizazio ezarpenak gorde dira.", + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Discover.CreateSlider.validationDatarequired": "Balio bat eman behar duzu.", + "components.Discover.FilterSlideover.voteCount": "Bozka zenbakia {minValue} eta {maxValue} artean", + "components.Discover.updatefailed": "Zerbait gaizki atera da ezarpen pertsonalizatuak eguneratzean.", + "components.IssueDetails.toaststatusupdatefailed": "Zerbait gaizki atera da intzidentziaren egoera eguneratzean.", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Atal} other {Atal}}", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Denboraldi} other {Denboraldi}}", + "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commit}} atzetik", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Zerbait gaizki joan da intzidentzia bidaltzean.", + "components.Login.loginerror": "Zerbait gaizki atera da saioa hastean.", + "components.Login.validationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.Login.validationPortRequired": "Baliozko ataka zenbakia eman behar da", + "components.Login.validationUrlBaseLeadingSlash": "URLak ez du hasierako barra bat izan behar", + "components.Login.validationemailrequired": "Baliozko helbide elektroniko bat eman behar duzu", + "components.ManageSlideOver.plays": "{playCount, number} {playCount, plural, one {erreprodukzio} other {erreprodukzio}}", + "components.ManageSlideOver.markallseasons4kavailable": "Markatu 4K-n eskuragarri dauden denboraldi guztiak", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Kaleratze data} other {Kaleratze datak}}", + "components.NotificationTypeSelector.adminissuecommentDescription": "Jakinarazi beste erabiltzaileek intzidentziatan iruzkinak bidaltzen dituztenean.", + "components.MovieDetails.productioncountries": "Produkzioa {countryCount, plural, one {herrialde} other {herrialde}}", + "components.NotificationTypeSelector.issuecommentDescription": "Bidali jakinarazpenak intzidentziak iruzkin berriak jasotzen dituztenean.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Bidali jakinarazpenak multimedia eskaerak eskuz onartzen direnean.", + "components.NotificationTypeSelector.userissuereopenedDescription": "Jakinarazki zuk bidalitako intzidentziak berriro irekitzen badira.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Jakinarazki zuk bidalitako intzidentziak konpontzen badira.", + "components.NotificationTypeSelector.usermediaapprovedDescription": "Jakinarazki zure multimedia eskaerak onartzen badira.", + "components.NotificationTypeSelector.usermediaavailableDescription": "Jakinarazi zure multimedia eskaerak eskuragarri egiten badira.", + "components.NotificationTypeSelector.usermediadeclinedDescription": "Jakinarazi zure multimedia eskaerak ukatzen badira.", + "components.PermissionEdit.adminDescription": "Administratzaile sarbide osoa. Beste baimen egiaztapen guztiak saihesten ditu.", + "components.PermissionEdit.advancedrequestDescription": "Baimendu multimedia eskaera aukera aurreratuak editatzea.", + "components.PermissionEdit.autoapprove4kDescription": "Baimendu onarpen automatikoa 4K multimedia eskaeretarako.", + "components.NotificationTypeSelector.mediaavailableDescription": "Bidali jakinarazpenak multimedia eskaerak eskuragarri daudenean.", + "components.NotificationTypeSelector.mediadeclinedDescription": "Bidali jakinarazpenak multimedia eskaerak ukatzen direnean.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Jakinarazki beste erabiltzaileek intzidentziak irekitzen dituztenean.", + "components.PermissionEdit.autoapprove4kMoviesDescription": "Onartu 4K filma eskaerak automatikoki.", + "components.PermissionEdit.autoapproveDescription": "Baimendu onarpen automatikoa ez-4K multimedia eskaeretarako.", + "components.PermissionEdit.request4kDescription": "Baimendu 4K multimedia eskatzea.", + "components.PermissionEdit.request4kMoviesDescription": "Baimendu 4K film eskaerak bidaltzea.", + "components.PermissionEdit.request4kTvDescription": "Baimendu 4K telesail eskaerak bidaltzea.", + "components.PermissionEdit.requestDescription": "Baimendu ez-4K multimedia eskaerak bidaltzea.", + "components.PermissionEdit.requestMoviesDescription": "Baimendu ez-4K film eskaerak bidaltzea.", + "components.PermissionEdit.requestTvDescription": "Baimendu ez-4K telesail eskaerak bidaltzea.", + "components.PermissionEdit.autoapprove4kSeriesDescription": "Onartu 4K telesailak automatikoki.", + "components.PermissionEdit.autoapproveMoviesDescription": "Onartu ez-4K filma eskaerak automatikoki.", + "components.PermissionEdit.autoapproveSeriesDescription": "Onartu ez-4K telesailen eskaerak automatikoki.", + "components.PermissionEdit.viewwatchlistsDescription": "Baimendu beste erabiltzaileen {mediaServerName} jarraipen-zerrendak ikustea.", + "components.QuotaSelector.seasons": "{count, plural, one {denboraldi} other {denboraldi}}", + "components.RequestBlock.seasons": "{seasonCount, plural, one {Denboraldi} other {Denboraldi}}", + "components.RequestButton.approverequests": "Onartu {requestCount, plural, one {Request} other {{requestCount} Requests}}", + "components.RequestButton.declinerequests": "Ukatu {requestCount, plural, one {Request} other {{requestCount} Requests}}", + "components.RequestCard.failedretry": "Zerbait gaizki joan da eskaera berriro saiatzean.", + "components.RequestList.RequestItem.failedretry": "Zerbait gaizki joan da eskaera berriro saiatzean.", + "components.RequestCard.seasons": "{seasonCount, plural, one {Denboraldi} other {Denboraldi}}", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Denboraldi} other {Denboraldi}}", + "components.RequestModal.AdvancedRequester.animenote": "* Telesail hau anime bat da.", + "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {denboraldi} other {denboraldi}}", + "components.RequestModal.errorediting": "Zerbait gaizki joan da eskaera editatzean.", + "components.RequestModal.requesterror": "Zerbait gaizki joan da eskaera bidaltzean.", + "components.RequestModal.requestseasons": "Eskatu {seasonCount} {seasonCount, plural, one {denboraldia} other {denboraldiak}}", + "components.ResetPassword.validationemailrequired": "Baliozko helbide elektroniko bat eman behar duzu", + "components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "Gotify jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URLak ez du barra batean amaitu behar", + "components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "Baliozko URL bat eman behar duzu", + "components.Settings.Notifications.validationBotAPIRequired": "Bot baimen token bat eman behar duzu", + "components.Settings.Notifications.validationChatIdRequired": "Baliozko txat ID bat eman behar duzu", + "components.Settings.Notifications.validationEmail": "Baliozko helbide elektroniko bat eman behar duzu", + "components.Settings.Notifications.validationSmtpPortRequired": "Baliozko ataka zenbaki bat eman behar duzu", + "components.Settings.Notifications.webhookUrlTip": "Sortu webhook integrazio bat zure zerbitzarian", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "URLak ez du hasierako barra bat izan behar", + "components.Settings.RadarrModal.validationPortRequired": "Baliozko ataka zenbaki bat eman behar duzu", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Zerbait gaizki joan da zeregina gordetzean.", + "components.Settings.SettingsMain.generalsettingsDescription": "Konfiguratu Jellyseerr-en ezarpen global eta lehenetsiak.", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Oinarrizko URLak hasierako barra bat izan behar du", + "components.Settings.SonarrModal.validationPortRequired": "Baliozko ataka zenbakia eman behar da", + "components.Settings.jellyfinSettingsFailure": "Zerbait gaizki joan da {mediaServerName} ezarpenak gordetzean.", + "components.Settings.serverpresetLoad": "Sakatu botoia zerbitzari eskuragarriak kargatzeko", + "components.Settings.toastTautulliSettingsFailure": "Zerbait gaizki joan da Tautulli ezarpenak gordetzean.", + "components.Settings.validationPortRequired": "Baliozko ataka zenbaki bat eman behar duzu", + "components.Settings.validationUrlBaseLeadingSlash": "URLak ez du hasierako barra bat izan behar", + "components.Setup.subtitle": "Hasi zure multimedia zerbitzaria aukeratzearekin", + "components.TvDetails.Season.somethingwentwrong": "Zerbait gaizki joan da denboraldiaren datuak lortzerakoan.", + "components.TvDetails.productioncountries": "Produkzioa {countryCount, plural, one {herrialde} other {herrialde}}", + "components.UserList.autogeneratepasswordTip": "Bidali zerbitzariak sortutako pasahitza erabiltzaileari e-posta bidez", + "components.UserList.importfromJellyfinerror": "Zerbait gaizki joan da {mediaServerName} erabiltzaileak inportatzerakoan.", + "components.UserList.importfromplexerror": "Zerbait gaizki joan da Plex erabiltzaileak inportatzerakoan.", + "components.UserList.noJellyfinuserstoimport": "Ez dago {mediaServerName} erabiltzailerik inportatzeko.", + "components.UserList.nouserstoimport": "Ez dago Plex erabiltzailerik inportatzeko.", + "components.UserList.usercreatedfailed": "Zerbait gaizki joan da erabiltzailea sortzerakoan.", + "components.UserList.userdeleteerror": "Zerbait gaizki joan da erabiltzailea ezabatzerakoan.", + "components.UserList.userfail": "Zerbait gaizki joan da erabiltzailearen baimenak gordetzerakoan.", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatikoki eskatu zure Plex jarraipen zerrendan dauden filmak", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Sortu token bat zure kontuaren ezarpenetatik", + "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Baliozko erabiltzaile ID bat eman behar duzu", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Baliozko aplikazio token bat eman behar duzu", + "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Baliozko txat ID bat eman behar duzu", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push jakinarazpenen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Zerbait gaizki joan da pasahitza gordetzean.", + "i18n.removeFromBlacklistSuccess": "{title} zerrenda beltzetik ondo kendu da.", + "i18n.showingresults": "{total}(e)tik {from}(e)tik {to}(e)ra emaitza erakusten", + "components.IssueDetails.toasteditdescriptionfailed": "Zerbait gaizki atera da intzidentziaren deskribapena editatzean.", + "components.RequestModal.requestmovies": "Eskatu {count} {count, plural, one {film} other {film}}", + "components.IssueDetails.toastissuedeletefailed": "Zerbait gaizki joan da intzidentzia ezabatzean.", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatikoki eskatu zure Plex jarraipen zerrendan dauden telesailak", + "components.Settings.Notifications.NotificationsGotify.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Ez da beharrezkoa profil lehenetsia erabiltzen ari bazara", + "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "Gutxienez jakinarazpen mota bat aukeratu behar duzu", + "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {film} other {film}}", + "components.RequestModal.requestadmin": "Eskaera hau automatikoki onartuko da.", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Gotify proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "LunaSea proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Zure erabiltzaile edo gailuan oinarritutako jakinarazpen WebHook URLa", + "components.Settings.Notifications.NotificationsGotify.validationTokenRequired": "Aplikazio token bat eman behar duzu", + "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Erregistratu aplikazio bat {applicationTitle}-(r)ekin erabiltzeko", + "components.Blacklist.blacklistNotFoundError": "{title} ez dago zerrenda beltzean.", + "components.Blacklist.blacklistSettingsDescription": "Kudeatu zerrenda beltzean dagoen multimedia.", + "components.Discover.CreateSlider.editfail": "Irristaria editatzeak huts egin du.", + "components.Discover.CreateSlider.providetmdbkeywordid": "Eman TMDB gako ID bat", + "components.Discover.CreateSlider.providetmdbnetwork": "Eman TMDB sarearen IDa", + "components.Discover.CreateSlider.providetmdbsearch": "Eman bilaketa-kontsulta bat", + "components.Discover.CreateSlider.validationTitlerequired": "Izenburu bat eman behar duzu.", + "components.Discover.CreateSlider.starttyping": "Hasi idazten bilatzeko.", + "components.Discover.CreateSlider.searchKeywords": "Bilatu gako hitzak…", + "components.Discover.CreateSlider.slidernameplaceholder": "Irristariaren izena", + "components.Discover.DiscoverMovieLanguage.languageMovies": "Film {language}-(e)ak", + "components.Discover.DiscoverMovies.sortPopularityAsc": "Ospea gorantz", + "components.Discover.DiscoverMovies.sortPopularityDesc": "Ospea beherantz", + "components.Discover.DiscoverSliderEdit.deletefail": "Irristaria ezabatzeak huts egin du.", + "components.Discover.DiscoverTv.sortFirstAirDateAsc": "Lehen emisioaren data gorantz", + "components.Discover.DiscoverTv.sortFirstAirDateDesc": "Lehen emisioaren data beherantz", + "components.Discover.DiscoverMovies.sortReleaseDateAsc": "Kaleratze data gorantz", + "components.Discover.DiscoverMovies.sortReleaseDateDesc": "Kaleratze data beherantz", + "components.Discover.DiscoverMovies.sortTitleAsc": "Izenburua (A-Z) gorantz", + "components.Discover.DiscoverMovies.sortTitleDesc": "Izenburua (Z-A) beherantz", + "components.Discover.DiscoverMovies.sortTmdbRatingAsc": "TMDB puntuazioa gorantz", + "components.Discover.DiscoverMovies.sortTmdbRatingDesc": "TMDB puntuazioa beherantz", + "components.Discover.DiscoverNetwork.networkSeries": "{network}-(e)ko telesailak", + "components.Discover.DiscoverSliderEdit.enable": "Txandakatu ikusgarritasuna", + "components.Discover.DiscoverStudio.studioMovies": "{studio}-(e)ko filmak", + "components.Discover.DiscoverSliderEdit.deletesuccess": "Irristaria ondo ezabatu da.", + "components.Discover.DiscoverTv.sortTitleAsc": "Izenburua (A-Z) gorantz", + "components.Discover.DiscoverTv.sortTitleDesc": "Izenburua (Z-A) beherantz", + "components.Discover.DiscoverTv.sortPopularityAsc": "Ospea gorantz", + "components.Discover.DiscoverTv.sortPopularityDesc": "Ospea beherantz", + "components.Discover.DiscoverTvGenre.genreSeries": "{genre}-(e)ko telesailak", + "components.Discover.FilterSlideover.ratingText": "{minValue} eta {maxValue} arteko balorazioak", + "components.Discover.FilterSlideover.clearfilters": "Garbitu gaitutako iragazkiak", + "components.Discover.FilterSlideover.firstAirDate": "Lehen emisio data", + "components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minutuko iraupena", + "components.Discover.FilterSlideover.from": "Hemendik", + "components.Discover.FilterSlideover.tmdbuservotecount": "TMDB erabiltzaileen bozka zenbakia", + "components.Discover.FilterSlideover.tmdbuserscore": "TMDB erabiltzaileen puntuazioa", + "components.Discover.FilterSlideover.to": "Hona", + "components.Discover.NetworkSlider.networks": "Sareak", + "components.Discover.createnewslider": "Sortu irristari berria", + "components.Discover.customizediscover": "Pertsonalizatu aurkitu", + "components.Discover.resettodefault": "Berrezarri lehenetsira", + "components.Discover.tmdbmoviegenre": "TMDB film generoa", + "components.Discover.tmdbmoviekeyword": "TMDB film gakoa", + "components.Discover.plexwatchlist": "Zure jarraipenak", + "components.Discover.popularmovies": "Film ospetsuak", + "components.Discover.populartv": "Telesail ospetsuak", + "components.Discover.recentlyAdded": "Duela gutxi gehituta", + "components.Discover.recentrequests": "Eskaera berriak", + "components.Discover.stopediting": "Editatzen utzi", + "components.Discover.discover": "Aurkitu", + "components.Discover.moviegenres": "Film generoak", + "components.Discover.tmdbmoviestreamingservices": "TMDB film streaming zerbitzuak", + "components.Discover.resetsuccess": "Aurkitu pertsonalizazio ezarpenak ondo berrezarri dira.", + "components.DownloadBlock.formattedTitle": "{title}: {seasonNumber}. denboraldia {episodeNumber}. atala", + "components.Discover.tmdbtvstreamingservices": "TMDB TV streaming zerbitzuak", + "components.IssueDetails.IssueComment.postedby": "{username}-(e)k {relativeTime} datan bidalita", + "components.Discover.updatesuccess": "Aurkitu pertsonalizazio ezarpenak eguneratu dira.", + "components.Discover.tmdbtvgenre": "TMDB telesail generoa", + "components.Discover.tmdbtvkeyword": "TMDB telesail gakoa", + "components.Discover.tmdbnetwork": "TMDB sarea", + "components.Discover.tmdbsearch": "TMDB bilaketa", + "components.Discover.upcomingmovies": "Hurrengo filmak", + "components.Discover.upcomingtv": "Hurrengo telesailak", + "components.DownloadBlock.estimatedtime": "{time} estimatuta", + "components.IssueDetails.IssueComment.delete": "Ezabatu iruzkina", + "components.Discover.trending": "Joerak", + "components.IssueDetails.IssueComment.postedbyedited": "{username}-(e)k bidalita {relativeTime} datan (Editatuta)", + "components.IssueDetails.IssueComment.validationComment": "Mezu bat idatzi behar duzu", + "components.IssueDetails.openedby": "#{issueId} irekita {relativeTime} datan {username}-(r)engatik", + "components.IssueDetails.openin4karr": "Ireki 4K-n {arr}", + "components.IssueDetails.closeissueandcomment": "Itxi iruzkinarekin", + "components.IssueDetails.commentplaceholder": "Gehitu iruzkin bat…", + "components.IssueDetails.openinarr": "Ireki {arr}-en", + "components.IssueDetails.play4konplex": "Ikusi 4K-n {mediaServerName}-(e)n", + "components.IssueDetails.playonplex": "Ikusi {mediaServerName}-(e)n", + "components.IssueDetails.reopenissueandcomment": "Ireki berriro iruzkinarekin", + "components.IssueDetails.toastissuedeleted": "Intzidentzia ondo ezabatu da!", + "components.IssueList.IssueItem.openeduserdate": "{date} {user} erabiltzailearengatik", + "components.IssueDetails.toaststatusupdated": "Intzidentziaren egoera ondo editatu da!", + "components.IssueList.showallissues": "Erakutsi intzidentzia guztiak", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Deskribapen bat eman behar duzu", + "components.Layout.UserWarnings.emailRequired": "Helbide elektronikoa beharrezkoa da.", + "components.Layout.SearchInput.searchPlaceholder": "Bilatu filmak eta telesailak", + "components.Layout.UserWarnings.emailInvalid": "Helbide elektronikoa baliogabea da.", + "components.Layout.UserWarnings.passwordRequired": "Pasahitza behar da.", + "components.IssueModal.CreateIssueModal.reportissue": "Bidali intzidentzia bat", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "{title} intzidentzia txostena ondo bidali da!", + "components.Login.credentialerror": "Erabiltzailea edo pasahitza baliogabeak dira.", + "components.Login.invalidurlerror": "Ezin da {mediaServerName} zerbitzarira konektatu.", + "components.Login.validationEmailRequired": "Helbide elektroniko bat eman behar duzu", + "components.Login.validationpasswordrequired": "Pasahitz bat eman behar duzu", + "components.Login.validationservertyperequired": "Mesedez, hautatu zerbitzari mota bat", + "components.Login.signinheader": "Hasi saioa jarraitzeko", + "components.Login.signinwithjellyfin": "Erabili zure {mediaServerName} kontua", + "components.Login.signinwithoverseerr": "Erabili zure {applicationTitle} kontua", + "components.Login.signinwithplex": "Erabili zure Plex kontua", + "components.Layout.VersionStatus.outofdate": "Zaharkituta", + "components.Login.validationemailformat": "Baliozko helbide elektronikoa behar da", + "components.Login.validationhostformat": "Baliozko URLa behar da", + "components.Login.validationhostrequired": "{mediaServerName} URLa behar da", + "components.ManageSlideOver.mark4kavailable": "Markatu 4K-n eskuragarri", + "components.ManageSlideOver.markallseasonsavailable": "Markatu denboraldi guztiak eskuragarri", + "components.MovieDetails.mark4kavailable": "Markatu 4K-n eskuragarri", + "components.ManageSlideOver.openarr4k": "Ireki 4K-n {arr}", + "components.ManageSlideOver.pastdays": "Azken {days, number} egunetan", + "components.ManageSlideOver.removearr4k": "Kendu 4K {arr}", + "components.MovieDetails.openradarr": "Ireki filma Radarr-en", + "components.ManageSlideOver.markavailable": "Markatu eskuragarri gisa", + "components.ManageSlideOver.openarr": "Ireki {arr}-en", + "components.ManageSlideOver.opentautulli": "Ireki Tautullin", + "components.ManageSlideOver.removearr": "Kendu {arr}-etik", + "components.MovieDetails.addtowatchlist": "Gehitu jarraipen zerrendara", + "components.MovieDetails.imdbuserscore": "IMDB erabiltzaileen puntuazioa", + "components.MovieDetails.markavailable": "Markatu eskuragarri gisa", + "components.MovieDetails.openradarr4k": "Ireki filma 4K Radarr-en", + "components.MovieDetails.studio": "{studioCount, plural, one {Estudio} other {Estudio}}", + "components.NotificationTypeSelector.issuecreatedDescription": "Bidali jakinarazpenak intzidentziak sortzen direnean.", + "components.NotificationTypeSelector.issuereopenedDescription": "Bidali jakinarazpenak intzidentziak berriro irekitzen direnean.", + "components.NotificationTypeSelector.issueresolvedDescription": "Bidali jakinarazpenak intzidentziak konpontzen direnean.", + "components.MovieDetails.watchlistError": "Zerbait gaizki joan da, saiatu berriro.", + "components.MovieDetails.play4k": "Ikusi 4K-n {mediaServerName}-(e)n", + "components.MovieDetails.rtaudiencescore": "Rotten Tomatoes audientzia puntuazioa", + "components.MovieDetails.play": "Ikusi {mediaServerName}-(e)n", + "components.MovieDetails.removefromwatchlist": "Kendu jarraipen zerrendatik", + "components.MovieDetails.reportissue": "Bidali intzidentzia bat", + "components.MovieDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer", + "components.MovieDetails.streamingproviders": "Orain emititzen hemen", + "components.MovieDetails.tmdbuserscore": "TMDB erabiltzaileen puntuazioa", + "components.MovieDetails.viewfullcrew": "Ikusi talde osoa", + "components.NotificationTypeSelector.mediaAutoApproved": "Eskaera automatikoki onartuta", + "components.MovieDetails.similar": "Antzeko filmak", + "components.MovieDetails.watchlistDeleted": "{title} jarraipen-zerrendatik ondo kendu da!", + "components.MovieDetails.watchlistSuccess": "{title} ondo gehitu da jarraipen zerrendara!", + "components.NotificationTypeSelector.mediaautorequested": "Eskaera automatikoki bidalita", + "components.NotificationTypeSelector.mediafailed": "Eskaera prozesatzeak huts egin du", + "components.NotificationTypeSelector.mediarequested": "Eskaera onartzeko zain dago", + "components.PermissionEdit.autoapprove4kMovies": "Auto-onartu 4K filmak", + "components.PermissionEdit.autoapprove4kSeries": "Auto-onartu 4K telesailak", + "components.PermissionEdit.manageissuesDescription": "Baimendu multimedia intzidentziak kudeatzea.", + "components.PermissionEdit.manageblacklistDescription": "Baimendu zerrenda beltzean dagoen multimedia kudeatzea.", + "components.PermissionEdit.createissuesDescription": "Baimendu multimedia intzidentziak sortzea.", + "components.PermissionEdit.blacklistedItemsDescription": "Baimendu multimedia zerrenda beltzean jartzea.", + "components.PermissionEdit.request4kMovies": "Eskatu 4K filmak", + "components.PermissionEdit.request4kTv": "Eskatu 4K telesailak", + "components.PermissionEdit.viewblacklistedItems": "Ikusi zerrenda beltzean dagoen multimedia.", + "components.PermissionEdit.blacklistedItems": "Multimedia zerrenda beltzean sartu.", + "components.PermissionEdit.viewblacklistedItemsDescription": "Baimendu zerrenda beltzean dagoen multimedia ikustea.", + "components.QuotaSelector.days": "{count, plural, one {egun} other {egun}}", + "components.QuotaSelector.movies": "{count, plural, one {film} other {film}}", + "components.QuotaSelector.movieRequests": "{quotaLimit} {movies} {quotaDays} {days}", + "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} {quotaDays} {days}", + "components.PersonDetails.alsoknownas": "Beste izen batzuk: {names}", + "components.PermissionEdit.viewrecent": "Ikusi berriki gehituta", + "components.PermissionEdit.viewwatchlists": "Ikusi {mediaServerName} jarraipen zerrendak", + "components.PersonDetails.lifespan": "{birthdate} – {deathdate}", + "components.RequestBlock.lastmodifiedby": "Azkenengoz editatuta honengatik", + "components.RegionSelector.regionDefault": "Eskualde guztiak", + "components.RegionSelector.regionServerDefault": "Lehenetsia ({region})", + "components.RequestBlock.rootfolder": "Erro karpeta", + "components.RequestButton.requestmore4k": "Eskatu gehiago 4K-n", + "components.RequestButton.approverequest4k": "Onartu 4K eskaera", + "components.RequestButton.declinerequest4k": "Ukatu 4K eskaera", + "components.RequestButton.viewrequest4k": "Ikusi 4K eskaera", + "components.RequestCard.mediaerror": "{mediaType} ez da aurkitu", + "components.RequestList.RequestItem.mediaerror": "{mediaType} ez da aurkitu", + "components.RequestList.RequestItem.modifieduserdate": "{date} {user} erabiltzailearengatik", + "components.RequestList.RequestItem.removearr": "Kendu {arr}-etik", + "components.RequestList.showallrequests": "Erakutsi eskaera guztiak", + "components.RequestList.sortDirection": "Txandakatu ordenaren norabidea", + "components.RequestModal.AdvancedRequester.rootfolder": "Erro karpeta", + "components.RequestModal.numberofepisodes": "Atal #", + "components.RequestModal.pending4krequest": "Onartzeke 4K eskaera", + "components.RequestModal.pendingrequest": "Eskaera zain", + "components.RequestModal.requestCancel": "{title} eskaera utzita.", + "components.RequestModal.requestSuccess": "{title} ondo eskatu da!", + "components.RequestModal.requestcancelled": "{title} eskaera utzita.", + "components.RequestModal.requestfrom": "{username}-(r)en eskaera onartzeko zain dago.", + "components.RequestModal.requestmovie4ktitle": "Eskatu filma 4K-n", + "components.RequestModal.requestcollectiontitle": "Eskatu bilduma", + "components.RequestModal.requestmovietitle": "Eskatu filma", + "components.RequestModal.requestseriestitle": "Eskatu telesailak", + "components.RequestModal.seasonnumber": "{number}. denboraldia", + "components.ResetPassword.confirmpassword": "Berretsi pasahitza", + "components.ResetPassword.email": "Helbide elektronikoa", + "components.ResetPassword.gobacklogin": "Itzuli saioa hasteko orrira", + "components.ResetPassword.passwordreset": "Pasahitza berrezarri", + "components.ResetPassword.resetpassword": "Berrezarri zure pasahitza", + "components.ResetPassword.validationpasswordmatch": "Pasahitzak bat egin behar dute", + "components.ResetPassword.resetpasswordsuccessmessage": "Pasahitza ondo berrezarri da!", + "components.ResetPassword.validationpasswordrequired": "Pasahitz bat eman behar duzu", + "components.Search.searchresults": "Bilaketa emaitzak", + "components.Selector.inProduction": "Ekoizpenean", + "components.Selector.nooptions": "Emaitzarik ez.", + "components.Selector.returningSeries": "Itzulitako telesaila", + "components.Selector.searchKeywords": "Bilatu gako hitzak…", + "components.Selector.searchStudios": "Bilatu estudioak…", + "components.Selector.showmore": "Erakutsi gehiago", + "components.Selector.starttyping": "Hasi idazten bilatzeko.", + "components.Settings.Notifications.NotificationsGotify.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Gotify proba jakinarazpena bidalita!", + "components.Settings.Notifications.NotificationsGotify.token": "Aplikazioaren tokena", + "components.Settings.Notifications.NotificationsGotify.url": "Zerbitzariaren URLa", + "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URLa", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Sarbide tokena", + "components.Settings.Notifications.NotificationsPushbullet.channelTag": "Kanalaren etiketa", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Pushbullet proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet proba jakinarazpena bidalita!", + "components.Settings.Notifications.NotificationsPushover.accessToken": "Aplikazioaren API tokena", + "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Pushover proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "Zure 30 karaktereko erabiltzaile edo talde identifikatzailea", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Pushover proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover proba jakinarazpena bidalita!", + "components.Settings.Notifications.NotificationsPushover.userToken": "Erabiltzaile edo talde gakoa", + "components.Settings.Notifications.NotificationsPushover.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Gailu lehenetsia", + "components.Settings.Notifications.NotificationsPushover.sound": "Jakinarazpen soinua", + "components.Settings.Notifications.NotificationsSlack.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "Slack proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack proba jakinarazpena bidalita!", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Berrezarri lehenetsira", + "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Txantiloien aldagai laguntza", + "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URLa", + "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.NotificationsWebhook.authheader": "Baimen goiburua", + "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON edukia", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON edukia ondo berrezarri da!", + "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Webhook proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook proba jakinarazpena bidalita!", + "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URLa", + "components.Settings.Notifications.discordsettingsfailed": "Discord jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.emailsettingsfailed": "E-posta jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.pgpPasswordTip": "Sinatu zifratutako mezuak OpenPGP erabiliz", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Web push proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push proba jakinarazpena bidalita!", + "components.Settings.Notifications.encryptionDefault": "Erabili STARTTLS eskuragarri badago", + "components.Settings.Notifications.allowselfsigned": "Baimendu auto-sinatutako ziurtagiriak", + "components.Settings.Notifications.botAPI": "Bot baimen tokena", + "components.Settings.Notifications.botAvatarUrl": "Bot avatar URLa", + "components.Settings.Notifications.encryptionImplicitTls": "Erabili TLS implizitua", + "components.Settings.Notifications.encryptionOpportunisticTls": "Beti erabili STARTTLS", + "components.Settings.Notifications.pgpPrivateKey": "PGP gako pribatua", + "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.agentenabled": "Gaitu agentea", + "components.Settings.Notifications.authPass": "SMTP pasahitza", + "components.Settings.Notifications.authUser": "SMTP erabiltzaile-izena", + "components.Settings.Notifications.botUsername": "Bot erabiltzaile-izena", + "components.Settings.Notifications.chatId": "Txat ID", + "components.Settings.Notifications.emailsender": "Igorlearen helbidea", + "components.Settings.Notifications.enableMentions": "Gaitu aipamenak", + "components.Settings.Notifications.pgpPassword": "PGP pasahitza", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push jakinarazpen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.discordsettingssaved": "Discord jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.emailsettingssaved": "E-posta jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.pgpPrivateKeyTip": "Sinatu zifratutako mezuak OpenPGP erabiliz", + "components.Settings.Notifications.telegramsettingsfailed": "Telegram jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.toastDiscordTestFailed": "Discord proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.toastEmailTestFailed": "E-posta proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.toastTelegramTestFailed": "Telegram proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.validationPgpPassword": "PGP pasahitz bat eman behar duzu", + "components.Settings.Notifications.validationUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.Notifications.sendSilentlyTip": "Bidali jakinarazpenak soinurik gabe", + "components.Settings.Notifications.toastDiscordTestSending": "Discord proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.toastDiscordTestSuccess": "Discord proba jakinarazpena bidalita!", + "components.Settings.Notifications.toastEmailTestSending": "E-posta proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.toastEmailTestSuccess": "E-posta proba jakinarazpena bidalita!", + "components.Settings.Notifications.toastTelegramTestSending": "Telegram proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram proba jakinarazpena bidalita!", + "components.Settings.Notifications.userEmailRequired": "Behartu erabiltzaileen helbide elektronikoak", + "components.Settings.Notifications.webhookRoleId": "Jakinarazpen Rol IDa", + "components.Settings.Notifications.sendSilently": "Bidali isilean", + "components.Settings.Notifications.senderName": "Igorlearen izena", + "components.Settings.Notifications.smtpHost": "SMTP ostalaria", + "components.Settings.Notifications.smtpPort": "SMTP ataka", + "components.Settings.Notifications.webhookUrl": "Webhook URLa", + "components.Settings.RadarrModal.add": "Gehitu zerbitzaria", + "components.Settings.Notifications.telegramsettingssaved": "Telegram jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.RadarrModal.testFirstQualityProfiles": "Probatu konexioa kalitate profilak kargatzeko", + "components.Settings.RadarrModal.testFirstRootFolders": "Probatu konexioa erro karpetak kargatzeko", + "components.Settings.RadarrModal.create4kradarr": "Gehitu 4K Radarr zerbitzari berria", + "components.Settings.RadarrModal.testFirstTags": "Probatu konexioa etiketak kargatzeko", + "components.Settings.RadarrModal.toastRadarrTestFailure": "Radarr-era konektatzeak huts egin du.", + "components.Settings.RadarrModal.createradarr": "Gehitu Radarr zerbitzari berria", + "components.Settings.RadarrModal.edit4kradarr": "Editatu 4K Radarr zerbitzaria", + "components.Settings.RadarrModal.default4kserver": "4K zerbitzari lehenetsia", + "components.Settings.RadarrModal.editradarr": "Editatu Radarr zerbitzaria", + "components.Settings.RadarrModal.enableSearch": "Gaitu bilaketa automatikoa", + "components.Settings.RadarrModal.loadingprofiles": "Kalitate profilak kargatzen…", + "components.Settings.RadarrModal.loadingrootfolders": "Erro karpetak kargatzen…", + "components.Settings.RadarrModal.selectQualityProfile": "Hautatu kalitate profila", + "components.Settings.RadarrModal.selectRootFolder": "Hautatu erro karpeta", + "components.Settings.RadarrModal.apiKey": "API gakoa", + "components.Settings.RadarrModal.baseUrl": "Oinarrizko URLa", + "components.Settings.RadarrModal.defaultserver": "Zerbitzari lehenetsia", + "components.Settings.RadarrModal.externalUrl": "Kanpoko URLa", + "components.Settings.RadarrModal.inCinemas": "Zinematan", + "components.Settings.RadarrModal.loadingTags": "Kargatu etiketak…", + "components.Settings.RadarrModal.minimumAvailability": "Gutxieneko eskuragarritasuna", + "components.Settings.RadarrModal.notagoptions": "Etiketarik gabe.", + "components.Settings.RadarrModal.qualityprofile": "Kalitate profila", + "components.Settings.RadarrModal.selecttags": "Hautatu etiketak", + "components.Settings.RadarrModal.server4k": "4K zerbitzaria", + "components.Settings.RadarrModal.servername": "Zerbitzariaren izena", + "components.Settings.RadarrModal.ssl": "Erabili SSL", + "components.Settings.RadarrModal.syncEnabled": "Gaitu eskaneoa", + "components.Settings.RadarrModal.tagRequests": "Etiketa eskaerak", + "components.Settings.RadarrModal.validationApiKeyRequired": "API gakoa eman behar duzu", + "components.Settings.RadarrModal.validationApplicationUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Gutxieneko eskuragarritasuna hautatu behar duzu", + "components.Settings.RadarrModal.validationNameRequired": "Zerbitzari-izen bat eman behar duzu", + "components.Settings.RadarrModal.validationProfileRequired": "Kalitate profil bat hautatu behar duzu", + "components.Settings.RadarrModal.validationRootFolderRequired": "Erro karpeta bat hautatu behar duzu", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Kalertaze-data ez dago eskuragarri unean.", + "components.Settings.SettingsAbout.helppaycoffee": "Lagundu kafe batera gonbidatuz", + "components.Settings.SettingsAbout.Releases.viewongithub": "Ikusi GitHuben", + "components.Settings.SettingsAbout.outofdate": "Zaharkituta", + "components.Settings.SettingsAbout.uptodate": "Eguneratuta", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} aldaketa egunkaria", + "components.Settings.SettingsAbout.Releases.viewchangelog": "Ikusi aldaketa egunkaria", + "components.Settings.SettingsAbout.appDataPath": "Datuen direktorioa", + "components.Settings.SettingsAbout.gettingsupport": "Lortu laguntza", + "components.Settings.SettingsAbout.githubdiscussions": "GitHub eztabaidak", + "components.Settings.SettingsAbout.overseerrinformation": "Jellyseerr-i buruz", + "components.Settings.SettingsAbout.supportjellyseerr": "Lagundu Jellyseerr", + "components.Settings.SettingsAbout.supportoverseerr": "Lagundu Overseerr", + "components.Settings.SettingsAbout.timezone": "Ordu-eremua", + "components.Settings.SettingsAbout.totalmedia": "Multimedia guztia", + "components.Settings.SettingsAbout.totalrequests": "Eskaera guztiak", + "components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin liburutegi osoko eskaneoa", + "components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Jellyfin berriki gehitutakoen eskaneoa", + "components.Settings.SettingsJobsCache.availability-sync": "Multimedia eskuragarritasun sinkronizazioa", + "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cachea garbituta.", + "components.Settings.SettingsJobsCache.download-sync-reset": "Deskarga sinkronizatua berrezarri", + "components.Settings.SettingsJobsCache.image-cache-cleanup": "Irudien cache garbiketa", + "components.Settings.SettingsJobsCache.imagecachesize": "Cache tamaina guztira", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Ekintza ondo editatu da!", + "components.Settings.SettingsJobsCache.jobsandcache": "Ekintzak eta cachea", + "components.Settings.SettingsJobsCache.cachekeys": "Gako guztiak", + "components.Settings.SettingsJobsCache.cacheksize": "Gakoaren tamaina", + "components.Settings.SettingsJobsCache.cachename": "Cachearen izena", + "components.Settings.SettingsJobsCache.cachevsize": "Balioaren tamaina", + "components.Settings.SettingsJobsCache.canceljob": "Utzi ekintza", + "components.Settings.SettingsJobsCache.download-sync": "Deskarga sinkronizatua", + "components.Settings.SettingsJobsCache.editJobSchedule": "Aldatu ekintza", + "components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Uneko maiztasuna", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Maiztasun berria", + "components.Settings.SettingsJobsCache.flushcache": "Garbitu cachea", + "components.Settings.SettingsJobsCache.imagecache": "Irudien cachea", + "components.Settings.SettingsJobsCache.imagecachecount": "Cacheatutako irudiak", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} utzita.", + "components.Settings.SettingsJobsCache.jobname": "Ekintzaren izena", + "components.Settings.SettingsJobsCache.jobstarted": "{jobname} hasita.", + "components.Settings.SettingsJobsCache.nextexecution": "Hurrengo abiarazpena", + "components.Settings.SettingsLogs.copiedLogMessage": "Erregistro mezua arbelera kopiatuta.", + "components.Settings.SettingsJobsCache.plex-full-scan": "Plex liburutegi osoko eskaneoa", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex berriki gehitutako eskaneoa", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Plex freskatze tokena", + "components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex jarraipen-zerrenda sinkronizazioa", + "components.Settings.SettingsLogs.copyToClipboard": "Kopiatu arbelera", + "components.Settings.SettingsLogs.showall": "Erakutsi erregistro guztiak", + "components.Settings.SettingsJobsCache.radarr-scan": "Radarr eskaneoa", + "components.Settings.SettingsJobsCache.runnow": "Abiarazi orain", + "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr eskaneoa", + "components.Settings.SettingsJobsCache.unknownJob": "Ekintza ezezaguna", + "components.Settings.SettingsJobsCache.usersavatars": "Erabiltzaileen avatarrak", + "components.Settings.SettingsLogs.extraData": "Datu gehigarriak", + "components.Settings.SettingsLogs.logDetails": "Erregistroaren xehetasunak", + "components.Settings.SettingsLogs.viewdetails": "Ikusi xehetasunak", + "components.Settings.SettingsMain.apikey": "API gakoa", + "components.Settings.SettingsMain.applicationTitle": "Aplikazioaren izenburua", + "components.Settings.SettingsMain.applicationurl": "Aplikazioaren URLa", + "components.Settings.SettingsMain.streamingRegionTip": "Erakutsi streaming guneak eskualdeko eskuragarritasunaren arabera", + "components.Settings.SettingsMain.toastSettingsFailure": "Zerbait gaizki joan da ezarpenak gordetzean.", + "components.Settings.SettingsMain.discoverRegionTip": "Iragazi edukia eskualdeko eskuragarritasunarengatik", + "components.Settings.SettingsMain.originallanguageTip": "Iragazi edukia iturrizko hizkuntzarengatik", + "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Saihestu proxya helbide lokaletarako", + "components.Settings.SettingsMain.partialRequestsEnabled": "Baimendu telesailen eskaera partzialak", + "components.Settings.SettingsMain.proxySsl": "Erabili SSL proxyrako", + "components.Settings.SettingsMain.cacheImages": "Gaitu irudien cachea", + "components.Settings.SettingsMain.csrfProtection": "Gaitu CSRF babesa", + "components.Settings.SettingsMain.hideAvailable": "Ezkutatu eskuragarri dagoen multimedia", + "components.Settings.SettingsMain.proxyBypassFilter": "Proxyak ezikusitako helbideak", + "components.Settings.SettingsMain.trustProxy": "Gaitu proxy onarpena", + "components.Settings.SettingsMain.discoverRegion": "Aurkitu eskualdea", + "components.Settings.SettingsMain.generalsettings": "Ezarpen orokorrak", + "components.Settings.SettingsMain.locale": "Bistaratzeko hizkuntza", + "components.Settings.SettingsMain.originallanguage": "Aurkitu hizkuntza", + "components.Settings.SettingsMain.proxyEnabled": "HTTP(S) Proxya", + "components.Settings.SettingsMain.proxyHostname": "Proxy ostalari-izena", + "components.Settings.SettingsMain.proxyPassword": "Proxy pasahitza", + "components.Settings.SettingsMain.proxyPort": "Proxy ataka", + "components.Settings.SettingsMain.proxyUser": "Proxy erabiltzaile-izena", + "components.Settings.SettingsMain.streamingRegion": "Streaming eskualdea", + "components.Settings.SettingsMain.validationApplicationTitle": "Aplikazioaren izenburu bat eman behar duzu", + "components.Settings.SettingsMain.validationApplicationUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.SettingsMain.validationProxyPort": "Baliozko ataka bat eman behar duzu", + "components.Settings.SettingsUsers.defaultPermissionsTip": "Erabiltzaile berrie ematen zaizkien hasierako baimenak", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "Film eskaera muga globala", + "components.Settings.SettingsUsers.newPlexLogin": "Gaitu {mediaServerName} saio hasiera berria", + "components.Settings.SettingsUsers.localLogin": "Gaitu saio hasiera lokala", + "components.Settings.SettingsUsers.defaultPermissions": "Baimen lehenetsiak", + "components.Settings.SonarrModal.create4ksonarr": "Gehitu 4K Sonarr zerbitzari berria", + "components.Settings.SonarrModal.loadingprofiles": "Kalitate profilak kargatzen…", + "components.Settings.SonarrModal.loadingrootfolders": "Erro karpetak kargatzen…", + "components.Settings.SonarrModal.selectLanguageProfile": "Hautatu hizkuntza profila", + "components.Settings.SonarrModal.selectQualityProfile": "Hautatu kalitate profila", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "Telesail eskaera muga globala", + "components.Settings.SonarrModal.edit4ksonarr": "Editatu 4K Sonarr zerbitzaria", + "components.Settings.SonarrModal.hostname": "Ostalari-izena edo IP helbidea", + "components.Settings.SonarrModal.animeSeriesType": "Anime telesail mota", + "components.Settings.SonarrModal.animelanguageprofile": "Anime hizkuntza profila", + "components.Settings.SonarrModal.animequalityprofile": "Anime kalitate profila", + "components.Settings.SonarrModal.animerootfolder": "Anime erro karpeta", + "components.Settings.SonarrModal.default4kserver": "4K zerbitzari lehenetsia", + "components.Settings.SonarrModal.enableSearch": "Gaitu bilaketa automatikoa", + "components.Settings.SonarrModal.loadinglanguageprofiles": "Hizkuntza profilak kargatzen…", + "components.Settings.SonarrModal.selectRootFolder": "Hautatu erro karpeta", + "components.Settings.SettingsUsers.userSettings": "Erabiltzailearen ezarpenak", + "components.Settings.SonarrModal.add": "Gehitu zerbitzaria", + "components.Settings.SonarrModal.animeTags": "Anime etiketak", + "components.Settings.SonarrModal.apiKey": "API gakoa", + "components.Settings.SonarrModal.baseUrl": "Oinarrizko URLa", + "components.Settings.SonarrModal.externalUrl": "Kanpoko URLa", + "components.Settings.SonarrModal.languageprofile": "Hizkuntza profila", + "components.Settings.SonarrModal.loadingTags": "Kargatu etiketak…", + "components.Settings.SonarrModal.notagoptions": "Etiketarik gabe.", + "components.Settings.SonarrModal.qualityprofile": "Kalitate profila", + "components.Settings.SonarrModal.rootfolder": "Erro karpeta", + "components.Settings.SonarrModal.seasonfolders": "Denboraldien karpetak", + "components.Settings.SonarrModal.selecttags": "Hautatu etiketak", + "components.Settings.SonarrModal.seriesType": "Telesail mota", + "components.Settings.SonarrModal.servername": "Zerbitzariaren izena", + "components.Settings.SettingsUsers.toastSettingsSuccess": "Erabiltzailearen ezarpenak ondo gorde dira!", + "components.Settings.SonarrModal.testFirstLanguageProfiles": "Probatu konexioa hizkuntza profilak kargatzeko", + "components.Settings.SonarrModal.testFirstQualityProfiles": "Probatu konexioa kalitate profilak kargatzeko", + "components.Settings.SonarrModal.testFirstRootFolders": "Probatu konexioa erro karpetak kargatzeko", + "components.Settings.SonarrModal.validationApiKeyRequired": "API gakoa eman behar duzu", + "components.Settings.SonarrModal.validationApplicationUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.SonarrModal.validationLanguageProfileRequired": "Hizkuntza profil bat hautatu behar duzu", + "components.Settings.SonarrModal.validationNameRequired": "Zerbitzari-izen bat eman behar duzu", + "components.Settings.SonarrModal.validationProfileRequired": "Kalitate profil bat hautatu behar duzu", + "components.Settings.SonarrModal.validationRootFolderRequired": "Erro karpeta bat hautatu behar duzu", + "components.Settings.SonarrModal.testFirstTags": "Probatu konexioa etiketak kargatzeko", + "components.Settings.SonarrModal.toastSonarrTestFailure": "Sonarr-era konektatzeak huts egin du.", + "components.Settings.copied": "API gakoa arbelera kopiatuta.", + "components.Settings.hostname": "Ostalari-izena edo IP helbidea", + "components.Settings.addradarr": "Gehitu Radarr zerbitzaria", + "components.Settings.addsonarr": "Gehitu Sonarr zerbitzaria", + "components.Settings.currentlibrary": "Uneko liburutegia: {name}", + "components.Settings.deleteServer": "Ezabatu {serverType} zerbitzaria", + "components.Settings.SonarrModal.ssl": "Erabili SSL", + "components.Settings.SonarrModal.syncEnabled": "Gaitu eskaneoa", + "components.Settings.SonarrModal.tagRequests": "Etiketa eskaerak", + "components.Settings.activeProfile": "Gaitutako profila", + "components.Settings.apiKey": "API gakoa", + "components.Settings.cancelscan": "Utzi eskaneoa", + "components.Settings.default4k": "4K lehenetsia", + "components.Settings.enablessl": "Erabili SSL", + "components.Settings.externalUrl": "Kanpoko URLa", + "components.Settings.SonarrModal.toastSonarrTestSuccess": "Sonarr konexioa ondo ezarri da!", + "components.Settings.invalidurlerror": "Ezin da {mediaServerName} zerbitzarira konektatu.", + "components.Settings.jellyfinSyncFailedGenericError": "Zerbait gaizki joan da liburutegiak sinkronizatzean", + "components.Settings.jellyfinSyncFailedNoLibrariesFound": "Ez da liburutegirik aurkitu", + "components.Settings.jellyfinForgotPasswordUrl": "Ahaztutako pasahitza URLa", + "components.Settings.librariesRemaining": "Geratzen diren liburutegiak: {count}", + "components.Settings.manualscan": "Eskuzko liburutegi eskaneoa", + "components.Settings.jellyfinSettings": "{mediaServerName} ezarpenak", + "components.Settings.jellyfinlibraries": "{mediaServerName} liburutegiak", + "components.Settings.jellyfinsettings": "{mediaServerName} ezarpenak", + "components.Settings.jellyfinSettingsSuccess": "{mediaServerName} ezarpenak ondo gorde dira!", + "components.Settings.notificationAgentSettingsDescription": "Konfiguratu eta gaitu jakinarazpen agenteak.", + "components.Settings.manualscanJellyfin": "Eskuzko liburutegi eskaneoa", + "components.Settings.menuJobs": "Ekintzak eta cachea", + "components.Settings.notificationsettings": "Jakinarazpen ezarpenak", + "components.Settings.notrunning": "Ez dago exekutatzen", + "components.Settings.plexlibraries": "Plex liburutegiak", + "components.Settings.plexsettings": "Plex ezarpenak", + "components.Settings.radarrsettings": "Radarr ezarpenak", + "components.Settings.save": "Gorde aldaketak", + "components.Settings.scan": "Sinkronizatu liburutegiak", + "components.Settings.serverpresetManualMessage": "Eskuzko konfigurazioa", + "components.Settings.serverpresetRefreshing": "Zerbitzariak lortzen…", + "components.Settings.sonarrsettings": "Sonarr ezarpenak", + "components.Settings.startscan": "Hasi eskaneoa", + "components.Settings.syncJellyfin": "Sinkronizatu liburutegiak", + "components.Settings.tautulliApiKey": "API gakoa", + "components.Settings.tautulliSettings": "Tautulli ezarpenak", + "components.Settings.toastPlexRefreshFailure": "Plex zerbitzariaren zerrenda eskuratzeak huts egin du.", + "components.Settings.validationApiKey": "API gakoa eman behar duzu", + "components.Settings.validationUrl": "Baliozko URL bat eman behar duzu", + "components.Settings.toastPlexConnecting": "Plex zerbitzarira konektatzen saiatzen…", + "components.Settings.toastPlexConnectingFailure": "Plex-era konektatzeak huts egin du.", + "components.Settings.toastPlexRefresh": "Zerbitzarien zerrenda Plex-etik lortzen…", + "components.Setup.signinMessage": "Hasteko, hasi saioa", + "components.Setup.signinWithEmby": "Sartu zure Emby informazioa", + "components.Setup.signinWithJellyfin": "Sartu zure Jellyfin informazioa", + "components.Setup.signinWithPlex": "Sartu zure Plex informazioa", + "components.Settings.webAppUrl": "Web App URLa", + "components.Setup.configuremediaserver": "Konfiguratu multimedia zerbitzaria", + "components.Setup.servertype": "Aukeratu zerbitzariaren mota", + "components.Settings.urlBase": "Oinarrizko URLa", + "components.Settings.webpush": "Web Push", + "components.Setup.back": "Itzuli", + "components.Setup.configemby": "Konfiguratu Emby", + "components.Setup.configjellyfin": "Konfiguratu Jellyfin", + "components.Setup.configplex": "Konfiguratu Plex", + "components.Setup.configureservices": "Konfiguratu zerbitzuak", + "components.Setup.finish": "Bukatu konfigurazioa", + "components.Setup.signin": "Hasi saioa", + "components.Settings.toastPlexConnectingSuccess": "Plex konexioa ondo ezarri da!", + "components.Settings.toastPlexRefreshSuccess": "Plex zerbitzarien zerrenda ondo lortu da!", + "components.Settings.toastTautulliSettingsSuccess": "Tautulli ezarpenak ondo gorde dira!", + "components.TvDetails.network": "{networkCount, plural, one {Sare} other {Sare}}", + "components.TitleCard.watchlistError": "Zerbait gaizki joan da, saiatu berriro.", + "components.TitleCard.watchlistCancel": "{title}-(r)en jarraipen-zerrenda utzita.", + "components.Setup.welcome": "Ongi etorri Jellyseerr-era", + "components.StatusBadge.openinarr": "Ireki {arr}-en", + "components.StatusBadge.playonplex": "Ikusi {mediaServerName}-(e)n", + "components.StatusChecker.restartRequired": "Zerbitzaria berrabiarazi behar da", + "components.TitleCard.addToWatchList": "Gehitu jarraipen zerrendara", + "components.TitleCard.mediaerror": "{mediaType} ez da aurkitu", + "components.TvDetails.Season.noepisodes": "Atal zerrenda ez dago eskuragarri.", + "components.TvDetails.TvCast.fullseriescast": "Telesailaren aktore guztiak", + "components.TvDetails.TvCrew.fullseriescrew": "Telesailaren talde osoa", + "components.TvDetails.firstAirDate": "Lehen emisio data", + "components.TvDetails.nextAirDate": "Hurrengo emisioa", + "components.StatusBadge.managemedia": "Kudeatu {mediaType}", + "components.StatusBadge.status4k": "4K {status}", + "components.StatusChecker.appUpdated": "{applicationTitle} eguneratuta", + "components.StatusChecker.reloadApp": "Kargatu {applicationTitle} berriro", + "components.TitleCard.cleardata": "Garbitu datuak", + "components.TitleCard.tmdbid": "TMDB ID", + "components.TitleCard.tvdbid": "TheTVDB ID", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} minutu", + "components.TvDetails.manageseries": "Kudeatu telesailak", + "components.TitleCard.watchlistDeleted": "{title} jarraipen zerrendatik ondo kendu da!", + "components.TitleCard.watchlistSuccess": "{title} jarraipen zerrendara ondo gehitu da!", + "components.TvDetails.watchlistError": "Zerbait gaizki joan da, saiatu berriro.", + "components.TvDetails.play4k": "Ikusi 4K-n {mediaServerName}-(e)n", + "components.TvDetails.rtaudiencescore": "Rotten Tomatoes audientzia puntuazioa", + "components.TvDetails.play": "Ikusi {mediaServerName}-(e)n", + "components.TvDetails.removefromwatchlist": "Kendu jarraipen zerrendatik", + "components.TvDetails.reportissue": "Bidali intzidentzia bat", + "components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer", + "components.TvDetails.streamingproviders": "Orain emititzen hemen", + "components.TvDetails.tmdbuserscore": "TMDB erabiltzaileen puntuazioa", + "components.TvDetails.viewfullcrew": "Ikusi talde osoa", + "components.UserList.autogeneratepassword": "Sortu pasahitza automatikoki", + "components.UserList.createlocaluser": "Sortu erabiltzaile lokala", + "components.UserList.edituser": "Editatu erabiltzaileen baimenak", + "components.UserList.importfromJellyfin": "Inportatu {mediaServerName} erabiltzaileak", + "components.TvDetails.originallanguage": "Jatorrizko hizkuntza", + "components.TvDetails.originaltitle": "Jatorrizko izenburua", + "components.TvDetails.overviewunavailable": "Ikuspegi orokorra ez dago eskuragarri.", + "components.TvDetails.seasonnumber": "{seasonNumber} denboraldia", + "components.TvDetails.showtype": "Telesail mota", + "components.TvDetails.similar": "Antzerako telesailak", + "components.TvDetails.status4k": "4K {status}", + "components.TvDetails.watchtrailer": "Ikusi trailerra", + "components.UserList.bulkedit": "Multzoko edizioa", + "components.UserList.deleteuser": "Ezabatu erabiltzailea", + "components.UserList.email": "Helbide elektronikoa", + "components.TvDetails.watchlistDeleted": "{title} jarraipen-zerrendatik ondo kendu da!", + "components.TvDetails.watchlistSuccess": "{title} ondo gehitu da jarraipen zerrendara!", + "components.UserList.validationUsername": "Erabiltzaile-izen bat eman behar duzu", + "components.UserList.importfrommediaserver": "Inportatu {mediaServerName} erabiltzaileak", + "components.UserList.importfromplex": "Inportatu Plex erabiltzaileak", + "components.UserList.localuser": "Erabiltzaile lokala", + "components.UserList.mediaServerUser": "{mediaServerName} erabiltzailea", + "components.UserList.plexuser": "Plex erabiltzailea", + "components.UserList.sortCreated": "Batze data", + "components.UserList.sortDisplayName": "Bistaratze izena", + "components.UserList.sortRequests": "Eskaera zenbakia", + "components.UserList.userlist": "Erabiltzaileen zerrenda", + "components.UserList.validationEmail": "Helbide elektronikoa beharrezkoa da", + "components.UserList.usercreatedsuccess": "Erabiltzailea ondo sortu da!", + "components.UserList.userdeleted": "Erabiltzailea ondo ezabatu da!", + "components.UserList.userssaved": "Erabiltzailearen baimenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Iragazi edukia eskualdeko eskuragarritasunarengatik", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Iragazi edukia iturrizko hizkuntzarengatik", + "components.UserProfile.ProfileHeader.userid": "Erabiltzailearen ID: {userid}", + "components.UserProfile.UserSettings.UserGeneralSettings.discordId": "Discord erabiltzailearen ID", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Baliogabetu muga globala", + "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Film eskaera muga", + "components.UserProfile.ProfileHeader.joindate": "{joindate} batu zen", + "components.UserProfile.ProfileHeader.profile": "Ikusi profila", + "components.UserProfile.ProfileHeader.settings": "Editatu ezarpenak", + "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Kontuaren mota", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Bistaratzeko hizkuntza", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Aurkitu eskualdea", + "components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Bistaratze izena", + "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Ezarpen orokorrak", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "({language}) lehenetsia", + "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Erabiltzaile lokala", + "components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "{mediaServerName} erabiltzailea", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Aurkitu hizkuntza", + "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex erabiltzailea", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Auto-eskatu filmak", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Auto-eskatu telesailak", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Erakutsi streaming guneak eskualdeko eskuragarritasunaren arabera", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Zerbait gaizki joan da ezarpenak gordetzean.", + "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Iragazi edukia eskualdeko eskuragarritasunarengatik", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "Helbide elektroni hau dagoeneko hartuta dago!", + "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Telesail eskaera muga", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Baliozko helbide elektronikoa behar da", + "components.UserProfile.UserSettings.UserGeneralSettings.region": "Aurkitu eskualdea", + "components.UserProfile.UserSettings.UserGeneralSettings.save": "Gorde aldaketak", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Streaming eskualdea", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "Helbide elektronikoa beharrezkoa da", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "E-posta jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Zure 30 karaktereko erabiltzaile edo talde identifikatzailea", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Zifratu e-posta mezuak OpenPGP erabiliz", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Bidali jakinarazpenak soinurik gabe", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Erabiltzaile edo talde gakoa", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP gako publikoa", + "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "Erabiltzailearen IDa", + "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Jakinarazpen ezarpenak", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Sarbide tokena", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Bidali isilean", + "components.UserProfile.UserSettings.UserNotificationSettings.sound": "Jakinarazpen soinua", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Txat ID", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "E-posta jakinarazpenen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet jakinarazpenen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover jakinarazpenen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Sarbide tokena eman behar duzu", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Pasahitz berria berretsi behar duzu", + "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Zure uneko pasahitza eman behar duzu", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Pasahitz berri bat eman behar duzu", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "Pasahitzak bat egin behar dute", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", + "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Berretsi pasahitza", + "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Uneko pasahitza", + "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Pasahitz berria", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram jakinarazpenen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Web push jakinarazpen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Pasahitza ondo gorde da!", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Zerbait gaizki joan da ezarpenak gordetzean.", + "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Ezin dituzu zure baimenak aldatu.", + "components.UserProfile.pastdays": "{type} (azken {days} egunak)", + "components.UserProfile.limit": "{limit}-(e)tik {remaining}", + "i18n.addToBlacklist": "Gehitu zerrenda beltzera", + "components.UserProfile.localWatchlist": "{username}-(r)en jarraipen zerrenda", + "components.UserProfile.movierequests": "Film eskaerak", + "components.UserProfile.plexwatchlist": "Plex jarraipen-zerrenda", + "components.UserProfile.recentlywatched": "Berriki ikusita", + "components.UserProfile.recentrequests": "Eskaera berriak", + "components.UserProfile.requestsperdays": "{limit} geratzen dira", + "components.UserProfile.seriesrequest": "Telesail eskaerak", + "components.UserProfile.totalrequests": "Eskaera guztiak", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Baimenak ondo gorde dira!", + "i18n.blacklistDuplicateError": "{title} zerrenda beltzean dago jada.", + "i18n.blacklistError": "Zerbait gaizki joan da, saiatu berriro.", + "i18n.resultsperpage": "Erakutsi {pageSize} emaitza orri bakoitzean", + "i18n.blacklistSuccess": "{title} zerrenda beltzera gehitu da.", + "i18n.areyousure": "Ziur zaude?", + "i18n.removefromBlacklist": "Kendu zerrenda beltzetik", + "i18n.request4k": "Eskatu 4K-n", + "i18n.delimitedlist": "{a}, {b}", + "i18n.noresults": "Emaitzarik ez.", + "i18n.notrequested": "Eskatu gabe", + "i18n.partiallyavailable": "Partzialki eskuragarri", + "i18n.restartRequired": "Berrabiarazi behar da", + "i18n.save": "Gorde aldaketak", + "pages.errormessagewithcode": "{statusCode} - {error}", + "pages.internalservererror": "Zerbitzariaren barneko errorea", + "pages.pagenotfound": "Orria ez da aurkitu", + "pages.somethingwentwrong": "Zerbait gaizki joan da", + "i18n.usersettings": "Erabiltzailearen ezarpenak", + "pages.returnHome": "Itzuli hasierara", + "pages.serviceunavailable": "Zerbitzua ez dago eskuragarri", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet proba jakinarazpena bidaltzeak huts egin du.", + "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook jakinarazpen ezarpenak gordetzeak huts egin du.", + "components.ResetPassword.emailresetlink": "E-posta berreskuratze esteka", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Sarbide tokena eman behar duzu", + "components.Settings.SettingsUsers.toastSettingsFailure": "Zerbait gaizki joan da ezarpenak gordetzean.", + "components.Settings.SettingsUsers.userSettingsDescription": "Konfiguratu erabiltzaileen ezarpen global eta lehenetsiak.", + "components.Discover.CreateSlider.providetmdbgenreid": "Eman TMDB genero ID bat", + "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Ez dituzu denboraldi nahiko eskaerarik", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Sortu Sarrera Webhook integrazio bat", + "components.Discover.CreateSlider.providetmdbstudio": "Eman TMDB estudioaren IDa", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea proba jakinarazpena bidalita!", + "components.Discover.DiscoverTv.sortTmdbRatingAsc": "TMDB puntuazioa gorantz", + "components.Settings.RadarrModal.selectMinimumAvailability": "Hautatu gutxieneko erabilgarritasuna", + "components.Settings.SonarrModal.editsonarr": "Editatu Sonarr zerbitzaria", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Gaitu agentea", + "components.Settings.SettingsMain.toastSettingsSuccess": "Ezarpenak ondo gorde dira!", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook proba jakinarazpena bidaltzeak huts egin du.", + "components.RequestModal.pendingapproval": "Zure eskaera onartzeko zain dago.", + "components.RequestModal.requestcollection4ktitle": "Eskatu bilduma 4K-n", + "components.Settings.SonarrModal.createsonarr": "Gehitu Sonarr zerbitzari berria", + "components.Selector.searchGenres": "Hautatu generoak…", + "components.Selector.searchStatus": "Hautatu egoera...", + "components.Selector.showless": "Erakutsi gutxiago", + "components.Settings.Notifications.NotificationsLunaSea.profileName": "Profilaren izena", + "components.Settings.RadarrModal.rootfolder": "Erro karpeta", + "components.IssueDetails.toasteditdescriptionsuccess": "Intzidentziaren deskribapena ondo editatu da!", + "components.RequestModal.requestedited": "{title}-(e)rako eskaera ondo editatu da!", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet jakinarazpenen ezarpenak ondo gorde dira!", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord jakinarazpenen ezarpenak ondo gorde dira!", + "components.RequestModal.requestApproved": "{title} eskaera onartuta!", + "components.RequestModal.requestseries4ktitle": "Eskatu telesaila 4K-n", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Gotify proba jakinarazpena bidaltzen…", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "LunaSea proba jakinarazpena bidaltzen…", + "components.Settings.RadarrModal.hostname": "Ostalari-izena edo IP helbidea", + "components.Discover.DiscoverTv.sortTmdbRatingDesc": "TMDB puntuazioa beherantz", + "components.TvDetails.addtowatchlist": "Gehitu jarraipen zerrendara", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Aplikazioaren API tokena", + "components.Discover.DiscoverTvLanguage.languageSeries": "{language}-(e)ko telesailak", + "components.RequestModal.selectmovies": "Hautatu filma(k)", + "components.RequestModal.selectseason": "Hautatu denboraldia(k)", + "components.Settings.Notifications.encryption": "Zifratze metodoa", + "components.Settings.SonarrModal.defaultserver": "Zerbitzari lehenetsia", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarr konexioa ondo ezarri da!", + "components.Settings.SonarrModal.server4k": "4K zerbitzaria", + "components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Gailu lehenetsia", + "components.Settings.SettingsMain.toastApiKeySuccess": "API gako berria ondo sortu da!", + "components.TvDetails.episodeRuntime": "Atalaren iraupena", + "components.Settings.Notifications.NotificationsGotify.gotifysettingssaved": "Gotify jakinarazpenen ezarpenak ondo gorde dira!", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea jakinarazpenen ezarpenak ondo gorde dira!" } diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json index 8322705a..514486cc 100644 --- a/src/i18n/locale/fr.json +++ b/src/i18n/locale/fr.json @@ -79,20 +79,20 @@ "components.Discover.FilterSlideover.tmdbuservotecount": "Nombre de votes utilisateur TMDB", "components.Discover.FilterSlideover.to": "À", "components.Discover.FilterSlideover.voteCount": "Nombre de votes entre {minValue} et {maxValue}", - "components.Discover.MovieGenreList.moviegenres": "Genres de films", - "components.Discover.MovieGenreSlider.moviegenres": "Genres de films", + "components.Discover.MovieGenreList.moviegenres": "Films par genres", + "components.Discover.MovieGenreSlider.moviegenres": "Films par genres", "components.Discover.NetworkSlider.networks": "Diffuseurs", "components.Discover.PlexWatchlistSlider.emptywatchlist": "Les médias ajoutés à votre liste de lecture Plex apparaîtront ici.", "components.Discover.PlexWatchlistSlider.plexwatchlist": "Votre liste de lecture", "components.Discover.RecentlyAddedSlider.recentlyAdded": "Récemment ajoutés", "components.Discover.StudioSlider.studios": "Studios", - "components.Discover.TvGenreList.seriesgenres": "Genres de séries", - "components.Discover.TvGenreSlider.tvgenres": "Genres de séries", + "components.Discover.TvGenreList.seriesgenres": "Séries par genres", + "components.Discover.TvGenreSlider.tvgenres": "Séries par genres", "components.Discover.createnewslider": "Créer un nouveau slider", "components.Discover.customizediscover": "Personnaliser Découvrir", "components.Discover.discover": "Découvrir", "components.Discover.emptywatchlist": "Les médias ajoutés à votre liste de lecture Plex apparaîtront ici.", - "components.Discover.moviegenres": "Genres de films", + "components.Discover.moviegenres": "Films par genres", "components.Discover.networks": "Diffuseurs", "components.Discover.plexwatchlist": "Votre liste de lecture", "components.Discover.popularmovies": "Films populaires", @@ -115,7 +115,7 @@ "components.Discover.tmdbtvkeyword": "Mot-clé de la série TMDB", "components.Discover.tmdbtvstreamingservices": "Services de streaming TMDB TV", "components.Discover.trending": "Tendances", - "components.Discover.tvgenres": "Genres de séries", + "components.Discover.tvgenres": "Séries par genres", "components.Discover.upcoming": "Films à venir", "components.Discover.upcomingmovies": "Films à venir", "components.Layout.SearchInput.searchPlaceholder": "Rechercher films et séries", @@ -219,7 +219,7 @@ "components.Settings.hostname": "Nom d'hôte ou adresse IP", "components.Settings.librariesRemaining": "Bibliothèques restantes : {count}", "components.Settings.manualscan": "Scan manuel des bibliothèques", - "components.Settings.manualscanDescription": "Normalement, le scan sera effectué une fois toutes les 24 heures seulement. Jellyseerr vérifiera les ajouts récents de votre serveur Plex de façon proactive. Si c'est la première fois que vous configurez Plex, un scan complet de la bibliothèque est recommandé!", + "components.Settings.manualscanDescription": "Normalement, le scan sera effectué une fois toutes les 24 heures seulement. Jellyseerr vérifiera les ajouts récents de votre serveur Plex de façon proactive. Si c'est la première fois que vous configurez Plex, un scan complet de la bibliothèque est recommandé !", "components.Settings.menuAbout": "À propos", "components.Settings.menuGeneralSettings": "Général", "components.Settings.menuJobs": "Tâches et cache", @@ -425,7 +425,7 @@ "components.RequestBlock.profilechanged": "Profil de qualité", "components.NotificationTypeSelector.mediadeclined": "Demande refusée", "components.NotificationTypeSelector.mediadeclinedDescription": "Envoyer des notifications lorsqu'une demande de média est refusée.", - "i18n.experimental": "Expérimentale", + "i18n.experimental": "Expérimental", "components.RequestModal.requesterror": "Une erreur s'est produite lors de la demande.", "components.RequestModal.SearchByNameModal.notvdbiddescription": "Nous n'avons pas pu associer cette série automatiquement. Veuillez sélectionner l'association correcte dans la liste ci-dessous.", "components.Login.signinwithplex": "Utilisez votre compte Plex", @@ -499,7 +499,7 @@ "components.Settings.SettingsJobsCache.cacheflushed": "Cache de {cachename} vidé.", "components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr met en cache les demandes aux points de terminaison d'API externes pour optimiser les performances et éviter de faire des appels d'API inutiles.", "components.Settings.SettingsJobsCache.cache": "Cache", - "i18n.advanced": "Avancés", + "i18n.advanced": "Avancé", "components.UserList.users": "Utilisateurs", "components.Setup.setup": "Configuration", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "L'URL ne doit pas se terminer par une barre oblique finale", @@ -628,7 +628,7 @@ "components.Settings.scan": "Synchroniser les bibliothèques", "components.Settings.SettingsJobsCache.sonarr-scan": "Scan de Sonarr", "components.Settings.SettingsJobsCache.radarr-scan": "Scan de Radarr", - "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Scan des ajouts récents Plex.", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Scan des ajouts récents Plex", "components.Settings.SettingsJobsCache.plex-full-scan": "Scan complet des bibliothèques Plex.", "components.Settings.Notifications.validationUrl": "Vous devez fournir une URL valide", "components.Settings.Notifications.botAvatarUrl": "L'URL de l'avatar de votre Bot", @@ -778,7 +778,7 @@ "components.Settings.Notifications.botUsernameTip": "Permet aux utilisateurs de démarrer également une conversation avec votre bot et de configurer leurs propres notifications personnelles", "components.RequestModal.pendingapproval": "Votre demande est en attente de validation.", "components.RequestList.RequestItem.mediaerror": "{mediaType} non trouvé", - "components.RequestList.RequestItem.deleterequest": "Supprimer la Demande", + "components.RequestList.RequestItem.deleterequest": "Supprimer la demande", "components.RequestList.RequestItem.cancelRequest": "Annuler la demande", "components.RequestCard.mediaerror": "{mediaType} non trouvé", "components.RequestCard.deleterequest": "Supprimer la demande", @@ -1141,7 +1141,7 @@ "components.Login.validationhostformat": "URL valide requise", "components.Login.validationhostrequired": "{mediaServerName} URL requise", "components.Login.validationusernamerequired": "Nom d'utilisateur requis", - "components.MovieDetails.imdbuserscore": "Note Utilisateurs", + "components.MovieDetails.imdbuserscore": "Note des utilisateurs IMDB", "components.ManageSlideOver.manageModalRemoveMediaWarning": "* Cela supprimera irréversiblement ce(tte) {mediaType} de {arr}, y compris tous les fichiers.", "components.ManageSlideOver.removearr": "Supprimer de {arr}", "components.ManageSlideOver.removearr4k": "Supprimer de {arr} 4K", @@ -1152,8 +1152,8 @@ "components.MovieDetails.play": "Lire sur {mediaServerName}", "components.MovieDetails.play4k": "Lire en 4k sur {mediaServerName}", "components.MovieDetails.reportissue": "Signaler un problème", - "components.MovieDetails.rtaudiencescore": "Score d’audience de Rotten Tomatoes", - "components.MovieDetails.rtcriticsscore": "Rotten Tomatoes Tomatomètre", + "components.MovieDetails.rtaudiencescore": "Score d’audience Rotten Tomatoes", + "components.MovieDetails.rtcriticsscore": "Tomatomètre Rotten Tomatoes", "components.MovieDetails.tmdbuserscore": "Note des utilisateurs TMDB", "components.PermissionEdit.viewwatchlistsDescription": "Autorise à voir les listes de lecture {mediaServerName} des autres utilisateurs.", "components.RequestBlock.approve": "Approuver la demande", @@ -1234,30 +1234,30 @@ "components.Settings.RadarrModal.tagRequestsInfo": "Ajouter automatiquement un tag supplémentaire avec l'ID utilisateur et le nom d'affichage du demandeur", "components.IssueModal.issueVideo": "Vidéo", "components.Settings.Notifications.NotificationsPushover.sound": "Son de notification", - "components.Settings.jellyfinSettings": "Paramètres pour {mediaServerName}", + "components.Settings.jellyfinSettings": "Paramètres {mediaServerName}", "components.Settings.jellyfinSettingsFailure": "Une erreur est survenue lors de l'enregistrement des paramètres pour {mediaServerName}.", "components.Settings.jellyfinSettingsSuccess": "Les paramètres pour {mediaServerName} ont été enregistrés avec succès !", "components.Settings.jellyfinlibraries": "Bibliothèques {mediaServerName}", "components.Settings.jellyfinlibrariesDescription": "Les bibliothèques de {mediaServerName} sont en cours d'analyse. Cliquez sur le bouton ci-dessous si aucune bibliothèque n'est répertoriée.", - "components.Settings.jellyfinsettings": "Paramètres pour {mediaServerName}", + "components.Settings.jellyfinsettings": "Paramètres {mediaServerName}", "components.Settings.manualscanJellyfin": "Analyse manuelle de la bibliothèque", "components.Settings.menuJellyfinSettings": "{mediaServerName}", "components.Settings.save": "Enregistrer les modifications", "components.Settings.saving": "Sauvegarde en cours…", "components.Settings.syncing": "Synchronisation en cours", "components.Setup.signin": "Se connecter", - "components.Setup.signinWithPlex": "Renseigner vos informations d'identification de Plex", + "components.Setup.signinWithPlex": "Entrez vos identifiants Plex", "components.StatusBadge.managemedia": "Gérer {mediaType}", "components.StatusBadge.openinarr": "Ouvrir dans {arr}", "components.StatusBadge.playonplex": "Lire sur {mediaServerName}", - "components.TitleCard.addToWatchList": "Ajouter à votre watchlist", + "components.TitleCard.addToWatchList": "Ajouter à la liste de surveillance", "components.TitleCard.watchlistCancel": "Watchlist pour {title} annulée.", "components.TitleCard.watchlistError": "Une erreur est survenue. Veuillez réessayer.", "components.TitleCard.watchlistSuccess": "{title} a été ajouté à votre watchlist avec succès !", "components.TvDetails.Season.somethingwentwrong": "Une erreur est survenue lors de la récupération des données de la saison.", "components.TvDetails.manageseries": "Gérer les séries", - "components.TvDetails.play": "Jouer sur {mediaServerName}", - "components.TvDetails.play4k": "Jouer en 4K sur {mediaServerName}", + "components.TvDetails.play": "Lire sur {mediaServerName}", + "components.TvDetails.play4k": "Lire en 4K sur {mediaServerName}", "components.TvDetails.rtcriticsscore": "Tomatometer sur Rotten Tomatoes", "components.TvDetails.seasonnumber": "Saison {seasonNumber}", "components.TvDetails.seasonstitle": "Saisons", @@ -1287,8 +1287,8 @@ "components.Setup.configuremediaserver": "Configurer le serveur multimédia", "components.TvDetails.rtaudiencescore": "Score de l'audience sur Rotten Tomatoes", "components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "Utilisateur {mediaServerName}", - "components.Setup.signinWithJellyfin": "Renseigner vos informations d'identification de Jellyfin", - "components.UserList.mediaServerUser": "Utilisateur de {mediaServerName}", + "components.Setup.signinWithJellyfin": "Entrez vos identifiants Jellyfin", + "components.UserList.mediaServerUser": "Utilisateur {mediaServerName}", "components.TvDetails.episodeCount": "{episodeCount, plural, one {# Épisode} other {# Épisodes}}", "components.UserList.newJellyfinsigninenabled": "Le paramètre Activer la nouvelle connexion à {mediaServerName} est actuellement activé. Les utilisateurs de {mediaServerName} avec accès à la bibliothèque n'ont pas besoin d'être importés pour se connecter.", "components.UserProfile.UserSettings.UserGeneralSettings.email": "E-mail", @@ -1343,7 +1343,7 @@ "components.Setup.configemby": "Configurer Emby", "components.Setup.configjellyfin": "Configurer Jellyfin", "components.Setup.configplex": "Configurer Plex", - "components.Setup.signinWithEmby": "Renseigner vos informations d'identification d'Emby", + "components.Setup.signinWithEmby": "Entrez vos identifiants Emby", "components.Setup.subtitle": "Commencez par choisir votre serveur multimédia", "components.StatusBadge.seasonnumber": "S{seasonNumber}", "components.Discover.FilterSlideover.status": "Statut", @@ -1355,8 +1355,53 @@ "component.BlacklistBlock.blacklistdate": "Date de mise en liste noire", "component.BlacklistBlock.blacklistedby": "Mis en liste noire par", "component.BlacklistModal.blacklisting": "Ajout en liste noire", - "components.Blacklist.blacklistNotFoundError": "{title} n'est pas en liste noire.", - "components.Blacklist.blacklistSettingsDescription": "Gérer le contenu en liste noire", + "components.Blacklist.blacklistNotFoundError": "{title} n'est pas dans la liste noire.", + "components.Blacklist.blacklistSettingsDescription": "Gérer le contenu en liste noire.", "components.Blacklist.blacklistsettings": "Paramètres de la liste noire", - "components.Layout.Sidebar.blacklist": "Liste noir" + "components.Layout.Sidebar.blacklist": "Liste noire", + "components.PermissionEdit.viewblacklistedItems": "Voir les médias dans la liste noire.", + "i18n.blacklistDuplicateError": "{title} est déjà dans la liste noire.", + "i18n.blacklistError": "Une erreur s'est produite, réessayez.", + "components.PermissionEdit.blacklistedItems": "Ajouter le média à la liste noire.", + "components.Settings.Notifications.validationWebhookRoleId": "Vous devez fournir un identifiant Discord valide", + "components.Settings.SettingsMain.discoverRegion": "Pays à découvrir", + "components.PermissionEdit.manageblacklistDescription": "Accorder la permission de gérer la liste noire.", + "components.Settings.SettingsMain.proxyPassword": "Mot de passe du proxy", + "components.RequestList.RequestItem.removearr": "Supprimer de {arr}", + "components.Settings.SettingsMain.streamingRegionTip": "Afficher les sites de streaming par disponibilité dans les pays", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Afficher les sites de streaming par disponibilité régionale", + "components.PermissionEdit.blacklistedItemsDescription": "Accorder la permission de mettre les médias sur liste noire.", + "components.PermissionEdit.manageblacklist": "Gérer la liste noire", + "components.PermissionEdit.viewblacklistedItemsDescription": "Accorder la permission de voir la liste noire.", + "components.RequestList.sortDirection": "Inverser la direction du tri", + "components.Settings.SettingsJobsCache.usersavatars": "Avatars des utilisateurs", + "components.Settings.SettingsMain.discoverRegionTip": "Filtrer le contenu par disponibilité dans les pays", + "components.Settings.SettingsMain.proxyBypassFilterTip": "Utilisez ',' comme séparateur et '*.' comme caractère générique pour les sous-domaines", + "components.Settings.SettingsMain.proxyEnabled": "Proxy HTTP(S)", + "components.Settings.SettingsMain.proxyHostname": "Nom d'hôte du proxy", + "components.Settings.SettingsMain.proxyPort": "Port du proxy", + "components.Settings.SettingsMain.proxySsl": "Utiliser SSL pour le proxy", + "components.Settings.SettingsMain.proxyUser": "Nom d'utilisateur du proxy", + "components.Settings.SettingsMain.streamingRegion": "Pays de diffusion", + "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Contourner le proxy pour les adresses locales", + "components.Settings.SettingsMain.proxyBypassFilter": "Adresses proxy ignorées", + "components.Settings.SettingsMain.validationProxyPort": "Vous devez fournir un port valide", + "components.Settings.apiKey": "Clé API", + "components.Settings.scanbackground": "L'analyse s'exécutera en arrière-plan. Vous pouvez poursuivre la configuration en attendant.", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Pays à découvrir", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Filtrer le contenu par disponibilité régionale", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Pays de diffusion", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "Cet email est déjà pris !", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Quelqu'un d'autre possède déjà ce nom d'utilisateur. Vous devez utiliser une adresse e-mail", + "i18n.blacklistSuccess": "{title} a été ajouté dans la liste noire avec succès.", + "i18n.blacklisted": "Sur liste noire", + "i18n.addToBlacklist": "Ajouter à la liste noire", + "i18n.blacklist": "Liste noire", + "i18n.removeFromBlacklistSuccess": "{title} a été retiré de la liste noire avec succès.", + "i18n.removefromBlacklist": "Retirer de la liste noire", + "i18n.specials": "Hors-série", + "components.Settings.Notifications.webhookRoleIdTip": "L'ID à mentionner dans le message du webhook. Laissez ce champ vide pour désactiver les mentions", + "components.Settings.Notifications.webhookRoleId": "ID de rôle de notification", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Rafraîchir le token Plex", + "components.Settings.tip": "Conseil" } diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json index bec5ca4a..66488038 100644 --- a/src/i18n/locale/nl.json +++ b/src/i18n/locale/nl.json @@ -11,22 +11,22 @@ "components.Layout.Sidebar.requests": "Verzoeken", "components.Layout.Sidebar.settings": "Instellingen", "components.Layout.Sidebar.users": "Gebruikers", - "components.Layout.UserDropdown.signout": "Uitloggen", + "components.Layout.UserDropdown.signout": "Afmelden", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Cast", "components.MovieDetails.originallanguage": "Oorspronkelijke taal", "components.MovieDetails.overview": "Overzicht", "components.MovieDetails.overviewunavailable": "Overzicht niet beschikbaar.", "components.MovieDetails.recommendations": "Aanbevelingen", - "components.MovieDetails.releasedate": "{releaseCount, plural, one {Releasedatum} other {Releasedata}}", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Verschijningsdatum} other {Verschijningsdata}}", "components.MovieDetails.revenue": "Omzet", "components.MovieDetails.runtime": "{minutes} minuten", "components.MovieDetails.similar": "Vergelijkbare titels", "components.PersonDetails.appearsin": "Verschijningen", "components.PersonDetails.ascharacter": "als {character}", - "components.RequestBlock.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}", - "components.RequestCard.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}", - "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}", + "components.RequestBlock.seasons": "{seasonCount, plural, one {Seizoen} other {Seizoenen}}", + "components.RequestCard.seasons": "{seasonCount, plural, one {Seizoen} other {Seizoenen}}", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Seizoen} other {Seizoenen}}", "components.RequestList.requests": "Verzoeken", "components.RequestModal.cancel": "Verzoek annuleren", "components.RequestModal.numberofepisodes": "Aantal afleveringen", @@ -34,11 +34,11 @@ "components.RequestModal.requestCancel": "Verzoek voor {title} is geannuleerd.", "components.RequestModal.requestSuccess": "{title} is succesvol aangevraagd!", "components.RequestModal.requestadmin": "Dit verzoek zal automatisch goedgekeurd worden.", - "components.RequestModal.requestfrom": "Het verzoek van {user} is in behandeling.", + "components.RequestModal.requestfrom": "Het verzoek van {user} is in afwachting van goedkeuring.", "components.RequestModal.requestseasons": "{seasonCount} {seasonCount, plural, one {seizoen} other {seizoenen}} aanvragen", "components.RequestModal.season": "Seizoen", "components.RequestModal.seasonnumber": "Seizoen {number}", - "components.RequestModal.selectseason": "Seizoen(en) selecteren", + "components.RequestModal.selectseason": "Seizoenen selecteren", "components.Search.searchresults": "Zoekresultaten", "components.Settings.Notifications.agentenabled": "Agent inschakelen", "components.Settings.Notifications.authPass": "SMTP-wachtwoord", @@ -240,7 +240,7 @@ "components.MovieDetails.MovieCrew.fullcrew": "Volledige crew", "components.CollectionDetails.requestcollection": "Collectie aanvragen", "components.UserList.importedfromplex": "{userCount} Plex-{userCount, plural, one {gebruiker} other {gebruikers}} succesvol geïmporteerd!", - "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} changelog", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Changelog voor {version}", "components.Settings.SettingsAbout.Releases.releases": "Versies", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Instellingen voor Slack-meldingen succesvol opgeslagen!", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Instellingen voor Slack-meldingen konden niet opgeslagen worden.", @@ -261,7 +261,7 @@ "components.RequestButton.requestmore4k": "Meer in 4K aanvragen", "components.RequestButton.approverequests": "{requestCount, plural, one {Verzoek} other {{requestCount} Verzoeken}} goedkeuren", "components.RequestButton.approve4krequests": "{requestCount, plural, one {4K-verzoek} other {{requestCount} 4K-verzoeken}} goedkeuren", - "components.RequestButton.declinerequests": "{requestCount, plural, one {verzoek} other {{requestCount} verzoeken}} weigeren", + "components.RequestButton.declinerequests": "{requestCount, plural, one {Verzoek} other {{requestCount} Verzoeken}} weigeren", "components.RequestButton.decline4krequests": "{requestCount, plural, one {4K-verzoek} other {{requestCount} 4K-verzoeken}} weigeren", "components.StatusBadge.status4k": "4K {status}", "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Instellingen voor webhook-meldingen succesvol opgeslagen!", @@ -297,7 +297,7 @@ "components.Login.validationemailrequired": "Je moet een geldig e-mailadres opgeven", "components.Login.signinwithoverseerr": "{applicationTitle}-account gebruiken", "components.Login.password": "Wachtwoord", - "components.Login.loginerror": "Er ging iets mis bij het inloggen.", + "components.Login.loginerror": "Er ging iets mis bij het aanmelden.", "components.Login.email": "E-mailadres", "components.MediaSlider.ShowMoreCard.seemore": "Meer", "i18n.edit": "Bewerken", @@ -323,9 +323,9 @@ "components.Login.signinwithplex": "Plex-account gebruiken", "components.Login.signinheader": "Log in om verder te gaan", "components.Login.signingin": "Aanmelden…", - "components.Login.signin": "Inloggen", + "components.Login.signin": "Aanmelden", "components.Settings.notificationAgentSettingsDescription": "Meldingsagenten configureren en inschakelen.", - "components.PlexLoginButton.signinwithplex": "Inloggen", + "components.PlexLoginButton.signinwithplex": "Aanmelden", "components.PlexLoginButton.signingin": "Aanmelden…", "components.PermissionEdit.advancedrequest": "Geavanceerde aanvragen", "components.PermissionEdit.admin": "Beheerder", @@ -407,7 +407,7 @@ "components.Settings.RadarrModal.validationApplicationUrl": "Je moet een geldige URL opgeven", "components.PermissionEdit.viewrequestsDescription": "Toestemming geven om mediaverzoeken van andere gebruikers te bekijken.", "components.PermissionEdit.viewrequests": "Verzoeken bekijken", - "components.UserList.validationEmail": "E-mailadres verplicht", + "components.UserList.validationEmail": "E-mailadres vereist", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Basis-URL mag niet eindigen op een schuine streep", "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Basis-URL moet met een schuine streep beginnen", "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "URL-basis mag niet eindigen op een schuine streep", @@ -425,7 +425,7 @@ "components.ResetPassword.resetpasswordsuccessmessage": "Wachtwoord is succesvol opnieuw ingesteld!", "components.ResetPassword.requestresetlinksuccessmessage": "Er wordt een link om het wachtwoord te resetten naar het opgegeven e-mailadres gestuurd als dat gekoppeld is aan een geldige gebruiker.", "components.ResetPassword.password": "Wachtwoord", - "components.ResetPassword.gobacklogin": "Terug naar inlogpagina", + "components.ResetPassword.gobacklogin": "Terug naar aanmeldpagina", "components.ResetPassword.emailresetlink": "Herstellink e-mailen", "components.ResetPassword.confirmpassword": "Wachtwoord bevestigen", "components.Login.forgotpassword": "Wachtwoord vergeten?", @@ -569,7 +569,7 @@ "components.Settings.SettingsLogs.resumeLogs": "Hervatten", "components.Settings.SettingsLogs.pauseLogs": "Pauze", "components.Settings.SettingsLogs.message": "Bericht", - "components.Settings.SettingsLogs.logsDescription": "Je kunt deze logs ook rechtstreeks bekijken via stdout, of in {appDataPath}/logs/overseerr.log.", + "components.Settings.SettingsLogs.logsDescription": "Je kunt deze logboeken ook rechtstreeks bekijken via stdout, of in {appDataPath}/logs/overseerr.log.", "components.Settings.SettingsLogs.logs": "Logboeken", "components.Settings.SettingsLogs.level": "Ernst", "components.Settings.SettingsLogs.label": "Label", @@ -616,7 +616,7 @@ "components.Settings.SettingsUsers.tvRequestLimitLabel": "Globale aanvraaglimiet voor series", "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {seizoen} other {seizoenen}}", "components.RequestModal.QuotaDisplay.season": "seizoen", - "components.RequestModal.QuotaDisplay.requiredquota": "Je moet nog minstens {seasons} {seasons, plural, one {seizoensverzoek} other {seizoensverzoek}} over hebben om deze serie aan te vragen.", + "components.RequestModal.QuotaDisplay.requiredquota": "Je moet nog minstens {seasons} {seasons, plural, one {seizoensverzoek} other {seizoensverzoeken}} over hebben om deze serie aan te vragen.", "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {Geen} other {#}} {type}{remaining, plural, one {verzoek} other {verzoeken}} resterend", "components.RequestModal.QuotaDisplay.quotaLinkUser": "Je kan een overzicht van de aanvraaglimieten van deze gebruiker bekijken op hun profielpagina.", "components.RequestModal.QuotaDisplay.quotaLink": "Je kan een overzicht van je aanvraaglimieten bekijken op jouw profielpagina.", @@ -691,7 +691,7 @@ "components.RequestModal.pendingapproval": "Je verzoek is in afwachting van goedkeuring.", "components.RequestList.RequestItem.cancelRequest": "Verzoek annuleren", "components.NotificationTypeSelector.notificationTypes": "Meldingtypes", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Er is voor jouw account momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord om in te kunnen loggen als een \"lokale gebruiker\" met je e-mailadres.", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Er is voor jouw account momenteel geen wachtwoord ingesteld. Stel hieronder een wachtwoord in om aanmelden als \"lokale gebruiker\" (met je e-mailadres) in te schakelen.", "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Deze gebruikersaccount heeft momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord zodat deze account in staat is om zich aan te melden als een \"lokale gebruiker\".", "components.Settings.serviceSettingsDescription": "Stel je {serverType}-server(s) hieronder in. Je kunt meerdere {serverType}-servers verbinden, maar slechts twee ervan kunnen als standaard worden gemarkeerd (één niet-4K en één 4K). Beheerders kunnen vóór goedkeuring de server aanpassen die voor nieuwe aanvragen gebruikt wordt.", "components.Settings.noDefaultServer": "Ten minste één {serverType} server moet als standaard worden gemarkeerd om {mediaType}verzoeken te kunnen verwerken.", @@ -784,7 +784,7 @@ "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Om web-pushmeldingen te ontvangen, moet Jellyseerr via HTTPS worden weergegeven.", "components.RequestList.RequestItem.requesteddate": "Aangevraagd", "components.RequestCard.failedretry": "Er ging opnieuw iets mis tijdens het aanvragen.", - "components.Settings.SettingsUsers.localLoginTip": "Sta gebruikers toe om in te loggen met hun e-mailadres en wachtwoord, in plaats van met Plex OAuth", + "components.Settings.SettingsUsers.localLoginTip": "Sta gebruikers toe om zich aan te melden met hun e-mailadres en wachtwoord, in plaats van met {mediaServerName} OAuth", "components.Settings.SettingsUsers.defaultPermissionsTip": "Initiële machtigingen toegekend aan nieuwe gebruikers", "components.QuotaSelector.tvRequests": "{quotaLimit} {seasons} per {quotaDays} {days}", "components.QuotaSelector.seasons": "{count, plural, one {seizoen} other {seizoenen}}", @@ -814,7 +814,7 @@ "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Nieuwe frequentie", "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Er ging iets mis bij het opslaan van de taak.", "components.Settings.SettingsJobsCache.editJobSchedule": "Taak wijzigen", - "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Elk(e) {jobScheduleHours, plural, one {uur} other {{jobScheduleHours} uren}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Elk(e) {jobScheduleHours, plural, one {uur} other {{jobScheduleHours} uur}}", "components.Settings.SettingsAbout.runningDevelop": "Je voert de developversie van Jellyseerr uit, die alleen wordt aanbevolen als je bijdraagt aan de ontwikkeling of de allereerste versies helpt testen.", "components.StatusBadge.status": "{status}", "components.IssueDetails.IssueComment.areyousuredelete": "Weet je zeker dat je deze opmerking wilt verwijderen?", @@ -857,7 +857,7 @@ "components.IssueDetails.toaststatusupdated": "Probleemstatus succesvol bijgewerkt!", "components.IssueDetails.closeissueandcomment": "Afsluiten met opmerking", "components.IssueModal.CreateIssueModal.problemseason": "Getroffen seizoen", - "components.IssueDetails.openedby": "#{issueId} {relativeTime} ingediend door {username}", + "components.IssueDetails.openedby": "#{issueId} - {relativeTime} ingediend door {username}", "components.IssueDetails.IssueDescription.description": "Beschrijving", "components.NotificationTypeSelector.issuecommentDescription": "Melding sturen wanneer problemen nieuwe opmerkingen krijgen.", "components.IssueModal.CreateIssueModal.toastviewissue": "Probleem bekijken", @@ -868,13 +868,13 @@ "components.IssueDetails.episode": "Aflevering {episodeNumber}", "components.IssueDetails.issuepagetitle": "Probleem", "components.IssueDetails.issuetype": "Type", - "components.IssueDetails.leavecomment": "Opmerking geven", + "components.IssueDetails.leavecomment": "Opmerking plaatsen", "components.IssueDetails.deleteissueconfirm": "Weet je zeker dat je dit probleem wilt verwijderen?", "components.IssueDetails.unknownissuetype": "Onbekend", "components.IssueDetails.openinarr": "Openen in {arr}", "components.IssueDetails.toasteditdescriptionfailed": "Er ging iets mis bij het bewerken van de beschrijving van het probleem.", "components.IssueList.IssueItem.issuetype": "Type", - "components.IssueList.IssueItem.opened": "Onopgelost", + "components.IssueList.IssueItem.opened": "Ingediend", "components.IssueDetails.reopenissue": "Probleem opnieuw indienen", "components.IssueDetails.reopenissueandcomment": "Opnieuw indienen met opmerking", "components.IssueDetails.season": "Seizoen {seasonNumber}", @@ -892,7 +892,7 @@ "components.IssueModal.CreateIssueModal.problemepisode": "Getroffen aflevering", "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Probleemmelding voor {title} succesvol ingediend!", "components.PermissionEdit.viewissues": "Problemen weergeven", - "components.IssueModal.issueOther": "Andere", + "components.IssueModal.issueOther": "Anders", "components.Layout.Sidebar.issues": "Problemen", "components.ManageSlideOver.manageModalClearMedia": "Gegevens wissen", "components.ManageSlideOver.manageModalClearMediaWarning": "* Hiermee worden alle gegevens voor deze {mediaType} onomkeerbaar verwijderd, inclusief eventuele verzoeken. Als dit item in je {mediaServerName}-bibliotheek staat, worden de mediagegevens opnieuw aangemaakt tijdens de volgende scan.", @@ -900,9 +900,9 @@ "components.ManageSlideOver.manageModalTitle": "{mediaType} beheren", "components.ManageSlideOver.tvshow": "serie", "components.NotificationTypeSelector.userissueresolvedDescription": "Ontvang een melding wanneer problemen die jij hebt gemeld, opgelost zijn.", - "components.NotificationTypeSelector.issuecomment": "Opmerking op probleem", + "components.NotificationTypeSelector.issuecomment": "Reactie op probleem", "components.NotificationTypeSelector.issueresolvedDescription": "Stuur meldingen wanneer problemen opgelost zijn.", - "components.ManageSlideOver.openarr4k": "Openen in 4K {arr}", + "components.ManageSlideOver.openarr4k": "Openen in 4K-{arr}", "components.NotificationTypeSelector.adminissuecommentDescription": "Ontvang een melding wanneer andere gebruikers reageren op problemen.", "components.NotificationTypeSelector.userissuecommentDescription": "Ontvang een melding wanneer er nieuwe reacties komen op problemen die jij hebt gemeld.", "components.NotificationTypeSelector.userissuecreatedDescription": "Ontvang een melding wanneer andere gebruikers problemen melden.", @@ -929,8 +929,8 @@ "components.IssueDetails.playonplex": "Afspelen op {mediaServerName}", "components.IssueDetails.play4konplex": "Afspelen op {mediaServerName} in 4K", "components.IssueDetails.openin4karr": "Openen in 4K {arr}", - "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {aflevering} other {afleveringen}}", - "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}", + "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Aflevering} other {Afleveringen}}", + "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Seizoen} other {Seizoenen}}", "components.ManageSlideOver.manageModalIssues": "Onopgeloste problemen", "components.IssueModal.CreateIssueModal.extras": "Extra's", "components.NotificationTypeSelector.adminissueresolvedDescription": "Ontvang een melding wanneer problemen worden opgelost door andere gebruikers.", @@ -940,15 +940,15 @@ "components.NotificationTypeSelector.userissuereopenedDescription": "Ontvang een bericht wanneer problemen die jij hebt gemeld, opnieuw worden ingediend.", "components.RequestModal.requestseasons4k": "{seasonCount} {seasonCount, plural, one {seizoen} other {seizoenen}} aanvragen in 4K", "components.RequestModal.requestmovies": "{count} {count, plural, one {film} other {films}} aanvragen", - "components.RequestModal.selectmovies": "Film(s) selecteren", + "components.RequestModal.selectmovies": "Films selecteren", "components.MovieDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}", "components.RequestModal.requestmovies4k": "{count} {count, plural, one {film} other {films}} in 4K aanvragen", "components.TvDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}", - "components.IssueDetails.commentplaceholder": "Voeg een opmerking toe…", + "components.IssueDetails.commentplaceholder": "Opmerking toevoegen…", "components.RequestModal.requestApproved": "Verzoek voor {title} goedgekeurd!", "components.RequestModal.approve": "Verzoek goedkeuren", "components.Settings.RadarrModal.inCinemas": "In de bioscoop", - "components.Settings.RadarrModal.released": "Uitgekomen", + "components.Settings.RadarrModal.released": "Uitgebracht", "components.Settings.RadarrModal.announced": "Aangekondigd", "components.Settings.Notifications.enableMentions": "Vermeldingen inschakelen", "components.Settings.Notifications.NotificationsGotify.agentenabled": "Agent inschakelen", @@ -969,7 +969,7 @@ "components.ManageSlideOver.manageModalAdvanced": "Geavanceerd", "components.ManageSlideOver.alltime": "Altijd", "components.ManageSlideOver.markallseasons4kavailable": "Alle seizoenen markeren als beschikbaar in 4K", - "components.ManageSlideOver.opentautulli": "In Tautulli openen", + "components.ManageSlideOver.opentautulli": "Openen in Tautulli", "components.ManageSlideOver.pastdays": "Afgelopen {days, number} dagen", "components.ManageSlideOver.playedby": "Afgespeeld door", "components.ManageSlideOver.plays": "{playCount, number} {playCount, plural, one {keer afgespeeld} other {keer afgespeeld}}", @@ -999,8 +999,8 @@ "components.StatusBadge.managemedia": "{mediaType} beheren", "components.StatusBadge.openinarr": "Openen in {arr}", "components.StatusBadge.playonplex": "Afspelen op {mediaServerName}", - "components.UserProfile.emptywatchlist": "Media die zijn toegevoegd aan je Plex Kijklijst verschijnen hier.", - "components.MovieDetails.digitalrelease": "Digitale release", + "components.UserProfile.emptywatchlist": "Media die zijn toegevoegd aan je Plex-kijklijst verschijnen hier.", + "components.MovieDetails.digitalrelease": "Digitale uitgave", "i18n.restartRequired": "Opnieuw opstarten vereist", "components.PermissionEdit.viewrecentDescription": "Toestemming geven om de lijst met onlangs toegevoegde media weer te geven.", "components.PermissionEdit.viewrecent": "Onlangs toegevoegd weergeven", @@ -1030,27 +1030,27 @@ "components.TvDetails.seasonstitle": "Seizoenen", "components.Discover.DiscoverWatchlist.discoverwatchlist": "Jouw kijklijst", "components.Discover.plexwatchlist": "Jouw kijklijst", - "components.MovieDetails.physicalrelease": "Fysieke release", + "components.MovieDetails.physicalrelease": "Fysieke uitgave", "components.PermissionEdit.autorequest": "Automatisch aanvragen", - "components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Kijklijst synchroniseren", + "components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex-kijklijst synchroniseren", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Series automatisch aanvragen", - "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatisch series op je Plex Kijklijst aanvragen", - "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatisch films op je Plex Kijklijst aanvragen", - "components.PermissionEdit.autorequestDescription": "Toestemming geven om niet-4K media in je Plex Kijklijst automatisch aan te vragen.", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatisch series op je Plex-kijklijst aanvragen", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatisch films op je Plex-kijklijst aanvragen", + "components.PermissionEdit.autorequestDescription": "Toestemming geven om niet-4K media in je Plex-kijklijst automatisch aan te vragen.", "components.RequestCard.tvdbid": "TheTVDB ID", "components.Discover.DiscoverWatchlist.watchlist": "Plex-kijklijst", - "components.MovieDetails.theatricalrelease": "Bioscooprelease", + "components.MovieDetails.theatricalrelease": "Bioscoopuitgave", "components.NotificationTypeSelector.mediaautorequested": "Aanvraag automatisch ingediend", - "components.NotificationTypeSelector.mediaautorequestedDescription": "Ontvang een melding wanneer er automatisch nieuwe mediaverzoeken worden ingediend voor items op je Plex Kijklijst.", - "components.PermissionEdit.autorequestSeriesDescription": "Toestemming geven om niet-4K series in je Plex Kijklijst automatisch aan te vragen.", - "components.PermissionEdit.viewwatchlists": "Plex Kijklijsten bekijken", - "components.PermissionEdit.viewwatchlistsDescription": "Toestemming verlenen om de Plex Kijklijsten van andere gebruikers te bekijken.", + "components.NotificationTypeSelector.mediaautorequestedDescription": "Ontvang een melding wanneer er automatisch nieuwe mediaverzoeken worden ingediend voor items op je Plex-kijklijst.", + "components.PermissionEdit.autorequestSeriesDescription": "Toestemming geven om niet-4K series in je Plex-kijklijst automatisch aan te vragen.", + "components.PermissionEdit.viewwatchlists": "Plex-kijklijsten bekijken", + "components.PermissionEdit.viewwatchlistsDescription": "Toestemming verlenen om de Plex-kijklijsten van andere gebruikers te bekijken.", "components.Settings.SettingsLogs.viewdetails": "Details bekijken", "components.Settings.advancedTooltip": "Deze instelling onjuist configureren, kan resulteren in gebroken functionaliteit", "components.StatusChecker.reloadApp": "{applicationTitle} opnieuw laden", "components.TitleCard.tmdbid": "TMDB ID", "components.StatusChecker.appUpdatedDescription": "Klik op de onderstaande knop om de toepassing opnieuw te laden.", - "components.UserProfile.plexwatchlist": "Plex Kijklijst", + "components.UserProfile.plexwatchlist": "Plex-kijklijst", "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Films automatisch aanvragen", "components.TvDetails.manageseries": "Serie beheren", "components.MovieDetails.managemovie": "Film beheren", @@ -1075,7 +1075,7 @@ "components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Filmverzoeken", "components.Layout.UserDropdown.requests": "Verzoeken", "components.RequestBlock.decline": "Verzoek weigeren", - "components.Discover.emptywatchlist": "Media die zijn toegevoegd aan je Plex Kijklijst verschijnen hier.", + "components.Discover.emptywatchlist": "Media die zijn toegevoegd aan je Plex-kijklijst verschijnen hier.", "components.RequestBlock.delete": "Verzoek verwijderen", "components.RequestBlock.edit": "Verzoek bewerken", "components.RequestBlock.lastmodifiedby": "Laatst gewijzigd door", @@ -1107,7 +1107,7 @@ "components.Discover.customizediscover": "Ontdekken aanpassen", "components.Discover.DiscoverSliderEdit.remove": "Verwijderen", "components.Discover.resetfailed": "Er is iets fout gegaan bij het resetten van de instellingen van Ontdekken.", - "components.Discover.PlexWatchlistSlider.emptywatchlist": "Media die zijn toegevoegd aan je Plex Kijklijst verschijnen hier.", + "components.Discover.PlexWatchlistSlider.emptywatchlist": "Media die zijn toegevoegd aan je Plex-kijklijst verschijnen hier.", "components.Discover.PlexWatchlistSlider.plexwatchlist": "Jouw kijklijst", "components.Discover.RecentlyAddedSlider.recentlyAdded": "Onlangs toegevoegd", "components.Discover.networks": "Netwerken", @@ -1138,7 +1138,7 @@ "components.Discover.updatesuccess": "Instellingen Ontdekken bijgewerkt.", "components.Discover.DiscoverMovies.sortPopularityAsc": "Populariteit oplopend", "components.Discover.DiscoverMovies.sortPopularityDesc": "Populariteit aflopend", - "components.Discover.DiscoverMovies.sortReleaseDateAsc": "Releasedatum oplopend", + "components.Discover.DiscoverMovies.sortReleaseDateAsc": "Verschijningsdatum oplopend", "components.Discover.DiscoverMovies.sortTitleAsc": "Titel oplopend (A-Z)", "components.Discover.DiscoverMovies.sortTitleDesc": "Titel aflopend (Z-A)", "components.Discover.DiscoverMovies.sortTmdbRatingAsc": "TMDB-beoordeling oplopend", @@ -1148,8 +1148,8 @@ "components.Discover.DiscoverTv.discovertv": "Series", "components.Discover.DiscoverTv.sortFirstAirDateAsc": "Eerste uitzenddatum oplopend", "components.Discover.DiscoverTv.sortPopularityDesc": "Populariteit aflopend", - "components.Discover.DiscoverTv.sortTitleAsc": "Titel (A-Z) oplopend", - "components.Discover.DiscoverTv.sortTitleDesc": "Titel (Z-A) aflopend", + "components.Discover.DiscoverTv.sortTitleAsc": "Titel oplopend (A-Z)", + "components.Discover.DiscoverTv.sortTitleDesc": "Titel aflopend (Z-A)", "components.Discover.FilterSlideover.activefilters": "{count, plural, one {# filter actief} other {# filters actief}}", "components.Discover.FilterSlideover.clearfilters": "Actieve filters wissen", "components.Discover.FilterSlideover.filters": "Filters", @@ -1159,8 +1159,8 @@ "components.Discover.FilterSlideover.keywords": "Trefwoorden", "components.Discover.FilterSlideover.originalLanguage": "Oorspronkelijke taal", "components.Discover.FilterSlideover.ratingText": "Beoordelingen tussen {minValue} en {maxValue}", - "components.Discover.FilterSlideover.releaseDate": "Releasedatum", - "components.Discover.FilterSlideover.runtime": "Duur", + "components.Discover.FilterSlideover.releaseDate": "Verschijningsdatum", + "components.Discover.FilterSlideover.runtime": "Speelduur", "components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minuten looptijd", "components.Discover.FilterSlideover.studio": "Studio", "components.Discover.FilterSlideover.tmdbuserscore": "TMDB-gebruikersscore", @@ -1189,7 +1189,7 @@ "components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# filter actief} other {# filters actief}}", "components.Discover.DiscoverMovies.discovermovies": "Films", "components.Discover.DiscoverTv.sortTmdbRatingDesc": "TMDB-beoordeling aflopend", - "components.Discover.DiscoverMovies.sortReleaseDateDesc": "Releasedatum aflopend", + "components.Discover.DiscoverMovies.sortReleaseDateDesc": "Verschijningsdatum aflopend", "components.Discover.DiscoverTv.sortPopularityAsc": "Populariteit oplopend", "components.Discover.DiscoverTv.sortTmdbRatingAsc": "TMDB-beoordeling oplopend", "components.Discover.DiscoverTv.sortFirstAirDateDesc": "Eerste uitzenddatum aflopend", @@ -1290,24 +1290,24 @@ "i18n.collection": "Collectie", "components.UserProfile.localWatchlist": "Kijklijst van {username}", "components.Setup.signinWithPlex": "Vul de Plex gegevens in", - "components.Settings.jellyfinSettingsDescription": "Configureer optioneel de interne en externe eindpunten voor de {mediaServerName} server. In de meeste gevallen verschilt de externe URL met de interne URL. Een aangepaste wachtwoord reset URL kan ook gebruikt worden voor de {mediaServerName} login, voor het geval dat je doorverwezen wilt worden naar een andere wachtwoord reset pagina. Je kunt ook de Jellyfin API-sleutel wijzigen, die eerder automatisch is gegenereerd.", + "components.Settings.jellyfinSettingsDescription": "Configureer optioneel de interne en externe eindpunten voor je {mediaServerName}-server. Meestal verschilt de externe URL van de interne URL. Als je wilt doorverwijzen naar een andere pagina voor wachtwoordherstel, kun je een aangepaste URL voor wachtwoordherstel instellen voor het aanmelden met {mediaServerName}. Je kunt ook de API-sleutel voor Jellyfin wijzigen, die eerder automatisch is gegenereerd.", "components.Settings.jellyfinsettingsDescription": "Configureer de instellingen voor uw {mediaServerName} server. {mediaServerName} scanned uw {mediaServerName} bibliotheken om te zien welke content beschikbaar is.", "components.TitleCard.watchlistDeleted": "{title} Is succesvol verwijderd van de kijklijst!", "components.TitleCard.watchlistSuccess": "{title} succesvol toegevoegd aan de kijklijst!", "components.TitleCard.watchlistCancel": "kijklijst voor {title} is geannuleerd.", - "components.UserList.importedfromJellyfin": "{userCount} {mediaServerName} {userCount, plural, one {user} other {users}} succesvol geimporteerd!", - "components.UserList.newJellyfinsigninenabled": "De Gebruik Nieuwe {mediaServerName} Login instelling staat momenteel aan. {mediaServerName} gebruikers met toegang tot de bibliotheek, hoeven niet geïmporteerd te worden om in te kunnen loggen.", + "components.UserList.importedfromJellyfin": "{userCount} {mediaServerName}-{userCount, plural, one {gebruiker} other {gebruikers}} succesvol geïmporteerd!", + "components.UserList.newJellyfinsigninenabled": "De instelling Nieuwe {mediaServerName}-aanmelding gebruiken is momenteel ingeschakeld. {mediaServerName}-gebruikers met toegang tot de bibliotheek hoeven niet geïmporteerd te worden om zich te kunnen aanmelden.", "components.Login.back": "Ga terug", "components.Login.invalidurlerror": "Kan geen verbinding maken met de {mediaServerName} server.", "components.TvDetails.addtowatchlist": "Toevoegen aan kijklijst", "components.Login.validationUrlBaseTrailingSlash": "URL basis mag niet eindigen met een schuine streep", "components.Selector.returningSeries": "Terugkerende serie", "components.MovieDetails.addtowatchlist": "Toevoegen aan kijklijst", - "components.MovieDetails.watchlistDeleted": "{title} is succesvol verwijderd uit de kijklijst!", - "components.MovieDetails.watchlistSuccess": "{title} is succesvol toegevoegd aan de kijklijst!", - "components.TvDetails.watchlistDeleted": "{title} is succesvol verwijderd uit de kijklijst!", - "components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Geldig e-mailadres verplicht", - "components.Login.adminerror": "Je moet een beheerdersaccount gebruiken om in te loggen.", + "components.MovieDetails.watchlistDeleted": "{title} is succesvol verwijderd van je kijklijst!", + "components.MovieDetails.watchlistSuccess": "{title} is succesvol toegevoegd aan je kijklijst!", + "components.TvDetails.watchlistDeleted": "{title} is succesvol verwijderd van de kijklijst!", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailformat": "Geldig e-mailadres vereist", + "components.Login.adminerror": "Je moet een beheerdersaccount gebruiken om je aan te melden.", "components.Selector.inProduction": "In productie", "components.Discover.FilterSlideover.status": "Status", "components.Login.hostname": "{mediaServerName} URL", @@ -1315,10 +1315,10 @@ "components.Login.servertype": "Servertype", "components.Login.validationHostnameRequired": "Je moet een geldige hostnaam of IP-adres opgeven", "components.Login.validationPortRequired": "Je moet een geldig poortnummer opgeven", - "components.Login.validationUrlBaseLeadingSlash": "URL basis moet beginnen met een schuine streep", + "components.Login.validationUrlBaseLeadingSlash": "URL-basis moet beginnen met een schuine streep", "components.Login.validationUrlTrailingSlash": "URL mag niet eindigen met een schuine streep", "components.Login.validationservertyperequired": "Kies een servertype", - "components.MovieDetails.removefromwatchlist": "Verwijderen uit kijklijst", + "components.MovieDetails.removefromwatchlist": "Verwijderen van kijklijst", "components.MovieDetails.watchlistError": "Er is iets misgegaan, probeer het opnieuw.", "components.RequestList.RequestItem.profileName": "Profiel", "components.Selector.canceled": "Geannuleerd", @@ -1337,14 +1337,71 @@ "components.Setup.configplex": "Configureer Plex", "components.Setup.servertype": "Kies servertype", "components.Setup.signinWithEmby": "Vul de Emby gegevens in", - "components.Setup.subtitle": "egin met het kiezen van je mediaserver", + "components.Setup.subtitle": "Begin met het kiezen van je mediaserver", "components.StatusBadge.seasonnumber": "S{seasonNumber}", "components.TvDetails.removefromwatchlist": "Verwijderen uit kijklijst", "components.TvDetails.watchlistError": "Er is iets misgegaan, probeer het opnieuw.", "components.TvDetails.watchlistSuccess": "{title} is succesvol toegevoegd aan de kijklijst!", "components.UserList.username": "Gebruikersnaam", "components.UserList.validationUsername": "Je moet een gebruikersnaam opgeven", - "components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "E-mailadres verplicht", + "components.UserProfile.UserSettings.UserGeneralSettings.validationemailrequired": "E-mailadres vereist", "components.Login.enablessl": "Gebruik SSL", - "components.Login.urlBase": "URL basis" + "components.Login.urlBase": "URL basis", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Streamingdiensten tonen op regionale beschikbaarheid", + "component.BlacklistBlock.blacklistdate": "Datum geblokkeerd", + "components.Settings.scanbackground": "Het scannen gebeurt op de achtergrond. In de tussentijd kun je verdergaan met het instelproces.", + "components.Blacklist.blacklistNotFoundError": "{title} staat niet op de blokkeerlijst.", + "components.Settings.SettingsMain.validationProxyPort": "Je moet een geldige poort opgeven", + "components.Layout.Sidebar.blacklist": "Blokkeerlijst", + "components.Settings.SettingsMain.streamingRegionTip": "Streamingdiensten tonen op regionale beschikbaarheid", + "components.PermissionEdit.manageblacklistDescription": "Toestemming geven om geblokkeerde media te beheren.", + "components.RequestList.RequestItem.removearr": "Verwijderen van {arr}", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Regio voor ontdekken", + "components.Settings.apiKey": "API-sleutel", + "i18n.blacklistSuccess": "{title} is succesvol geblokkeerd.", + "i18n.addToBlacklist": "Toevoegen aan blokkeerlijst", + "component.BlacklistBlock.blacklistedby": "Geblokkeerd door", + "component.BlacklistModal.blacklisting": "Blokkeren", + "components.Blacklist.blacklistSettingsDescription": "Media op de blokkeerlijst beheren.", + "components.Blacklist.blacklistdate": "datum", + "components.Blacklist.blacklistedby": "{date} door {user}", + "components.Blacklist.blacklistsettings": "Instellingen blokkeerlijst", + "components.Blacklist.mediaName": "Naam", + "components.Blacklist.mediaTmdbId": "tmdb-id", + "components.Blacklist.mediaType": "Type", + "components.PermissionEdit.blacklistedItems": "Media blokkeren.", + "components.PermissionEdit.blacklistedItemsDescription": "Toestemming geven om media te blokkeren.", + "components.PermissionEdit.manageblacklist": "Blokkeerlijst beheren", + "components.PermissionEdit.viewblacklistedItems": "Geblokkeerde media inzien.", + "components.PermissionEdit.viewblacklistedItemsDescription": "Toestemming geven om geblokkeerde media in te zien.", + "components.RequestList.sortDirection": "Sorteerrichting wisselen", + "components.Settings.Notifications.validationWebhookRoleId": "Je moet een geldige Discord Role ID opgeven", + "components.Settings.Notifications.webhookRoleId": "Role-id melding", + "components.Settings.Notifications.webhookRoleIdTip": "De role-id die in het webhook-bericht vermeld moet worden. Laat leeg om vermeldingen uit te zetten", + "components.Settings.SettingsJobsCache.usersavatars": "Gebruikersavatars", + "components.Settings.SettingsMain.discoverRegion": "Regio voor Ontdekken", + "components.Settings.SettingsMain.discoverRegionTip": "Inhoud filteren op regionale beschikbaarheid", + "components.Settings.SettingsMain.streamingRegion": "Regio voor streamen", + "components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Inhoud filteren op regionale beschikbaarheid", + "components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Regio voor streamen", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "Deze e-mail is bezet!", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Een andere gebruiker heeft deze gebruikersnaam al. Je moet een e-mail instellen", + "i18n.blacklist": "Blokkeerlijst", + "i18n.blacklistDuplicateError": "{title} staat al op de blokkeerlijst.", + "i18n.blacklistError": "Er ging iets mis; probeer opnieuw.", + "i18n.blacklisted": "Geblokkeerd", + "i18n.removeFromBlacklistSuccess": "{title} is succesvol verwijderd van de blokkeerlijst.", + "i18n.removefromBlacklist": "Verwijderen van blokkeerlijst", + "i18n.specials": "Specials", + "components.Settings.SettingsJobsCache.plex-refresh-token": "Plex-verversingstoken", + "components.Settings.SettingsMain.proxyBypassFilter": "Genegeerde proxy-adressen", + "components.Settings.SettingsMain.proxyBypassFilterTip": "Gebruik ',' als scheidingsteken en '*' als joker voor subdomeinen", + "components.Settings.SettingsMain.proxyBypassLocalAddresses": "Proxy omzeilen voor lokale adressen", + "components.Settings.SettingsMain.proxyEnabled": "HTTP(S)-proxy", + "components.Settings.SettingsMain.proxyHostname": "Hostnaam proxy", + "components.Settings.SettingsMain.proxyPassword": "Wachtwoord proxy", + "components.Settings.SettingsMain.proxyPort": "Proxypoort", + "components.Settings.SettingsMain.proxySsl": "SSL/TLS gebruiken voor proxy", + "components.Settings.SettingsMain.proxyUser": "Gebruikersnaam proxy", + "components.Settings.tip": "Tip" } diff --git a/src/i18n/locale/pt_PT.json b/src/i18n/locale/pt_PT.json index d791599e..37db3cb4 100644 --- a/src/i18n/locale/pt_PT.json +++ b/src/i18n/locale/pt_PT.json @@ -1211,5 +1211,14 @@ "components.Discover.emptywatchlist": "Mídia adicionadas à sua Lista Para Assistir do Plex aparecerão aqui.", "components.RequestList.RequestItem.unknowntitle": "Título Desconhecido", "components.Selector.searchGenres": "Selecione os gêneros…", - "components.Selector.searchKeywords": "Pesquisar palavras-chave…" + "components.Selector.searchKeywords": "Pesquisar palavras-chave…", + "component.BlacklistBlock.blacklistdate": "Data de inclusão na lista negra", + "components.Blacklist.blacklistsettings": "Definições da lista negra", + "component.BlacklistBlock.blacklistedby": "Colocado na lista negra por", + "component.BlacklistModal.blacklisting": "Lista negra", + "components.Blacklist.blacklistNotFoundError": "{title}2 não está na lista negra.", + "components.Blacklist.blacklistSettingsDescription": "Faça a gestão dos conteúdos multimédia colocados na lista negra.", + "components.Blacklist.blacklistdate": "data", + "components.Blacklist.blacklistedby": "{date} por {user}", + "components.Blacklist.mediaName": "Nome" } diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index 8ba2257a..f3f08817 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -1336,5 +1336,23 @@ "components.Settings.invalidurlerror": "Не удалось подключиться к {mediaServerName} серверу.", "components.Settings.jellyfinForgotPasswordUrl": "Забыли пароль URL", "components.Settings.jellyfinSyncFailedGenericError": "Что-то пошло не так при синхронизации библиотек", - "components.Settings.jellyfinSyncFailedNoLibrariesFound": "Библиотеки не найдены" + "components.Settings.jellyfinSyncFailedNoLibrariesFound": "Библиотеки не найдены", + "component.BlacklistBlock.blacklistdate": "Дата внесения в чёрный список", + "components.Blacklist.blacklistSettingsDescription": "Управлять медиа в чёрном списке.", + "components.PermissionEdit.manageblacklistDescription": "Предоставить разрешение на управление чёрным списком.", + "components.PermissionEdit.viewblacklistedItems": "Открыть чёрный список.", + "components.Blacklist.mediaType": "Тип", + "components.Layout.Sidebar.blacklist": "Чёрный список", + "components.PermissionEdit.blacklistedItems": "Внести в чёрный список.", + "components.Login.hostname": "Адрес {mediaServerName}", + "component.BlacklistBlock.blacklistedby": "Внесено в чёрный список", + "component.BlacklistModal.blacklisting": "Внесение в чёрный список", + "components.Blacklist.blacklistNotFoundError": "{title} не в чёрном списке.", + "components.Blacklist.blacklistdate": "дата", + "components.Blacklist.blacklistedby": "{date}, {user}", + "components.Blacklist.blacklistsettings": "Настройки чёрного списка", + "components.Blacklist.mediaName": "Название", + "components.Blacklist.mediaTmdbId": "tmdb Id", + "components.PermissionEdit.blacklistedItemsDescription": "Дать права на внесение в чёрный список.", + "components.PermissionEdit.manageblacklist": "Управлять чёрным списком" } diff --git a/src/i18n/locale/tr.json b/src/i18n/locale/tr.json index 54af53d3..ae978969 100644 --- a/src/i18n/locale/tr.json +++ b/src/i18n/locale/tr.json @@ -272,10 +272,10 @@ "components.ManageSlideOver.removearr": "{arr}'dan Kaldır", "components.ManageSlideOver.tvshow": "dizi", "components.MediaSlider.ShowMoreCard.seemore": "Daha Fazla", - "components.MovieDetails.MovieCast.fullcast": "Tüm Kadro", + "components.MovieDetails.MovieCast.fullcast": "Tüm Oyuncular", "components.MovieDetails.MovieCrew.fullcrew": "Tüm Ekip", "components.MovieDetails.budget": "Bütçe", - "components.MovieDetails.cast": "Kadro", + "components.MovieDetails.cast": "Oyuncular", "components.MovieDetails.digitalrelease": "Dijital Sürüm", "components.MovieDetails.downloadstatus": "İndirme Durumu", "components.MovieDetails.managemovie": "Filmi Yönet", @@ -375,7 +375,7 @@ "components.PersonDetails.appearsin": "Yer Aldığı İçerikler", "components.PersonDetails.ascharacter": "{character} rolüyle", "components.PersonDetails.birthdate": "Doğumu {birthdate}", - "components.PersonDetails.crewmember": "Ekibi", + "components.PersonDetails.crewmember": "Bilinen İşleri", "components.PersonDetails.lifespan": "{birthdate} – {deathdate}", "components.PlexLoginButton.signingin": "Giriş Yapılıyor…", "components.PlexLoginButton.signinwithplex": "Giriş Yap", @@ -929,7 +929,7 @@ "components.Settings.Notifications.toastDiscordTestFailed": "Discord test bildirimi gönderilemedi.", "components.Settings.RadarrModal.selectMinimumAvailability": "Asgari erişilebilirlik ayarını seç", "components.Settings.RadarrModal.validationHostnameRequired": "Geçerli bir sunucu adresi girmelisin", - "components.Settings.SettingsAbout.betawarning": "Bu bir BETA yazılımdır. Özellikler hatalı ya da dengesiz olabilir. Bir hatala karşılaşırsanız lütfen GitHub'dan bildirin!", + "components.Settings.SettingsAbout.betawarning": "Bu BETA yazılımdır. Özellikler bozuk ve/veya dengesiz olabilir. Karşılaştığınız herhangi bir sorunu lütfen GitHub'da bildirin!", "components.Settings.SettingsMain.cacheImagesTip": "Dış kaynaklardan alınan resimleri önbelleğe al (depolama kullanımını arttıracaktır)", "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Bir Webhook entegrasyonu oluştur", "components.Settings.Notifications.discordsettingssaved": "Discord bildirim ayarları kaydedildi!", @@ -938,7 +938,7 @@ "components.Settings.Notifications.webhookUrlTip": "Sunucunuz için bir Webhook entegrasyonu oluştur", "components.Settings.SettingsAbout.outofdate": "Eski Sürüm", "components.Settings.SettingsAbout.runningDevelop": "Jellyseerr'in yalnızca geliştirmeye katkıda bulunan veya en son testlere yardımcı olan kişiler için önerilen develop dalını çalıştırıyorsunuz.", - "components.Settings.SettingsJobsCache.cachevsize": "Önbelleğin Büyüklüğü", + "components.Settings.SettingsJobsCache.cachevsize": "Değer Boyutu", "components.Settings.SettingsJobsCache.imagecacheDescription": "Ayarlarda etkinleştirildiğinde, Jellyseerr önceden yapılandırılmış harici kaynaklardan gelen görüntüleri proxy'leyecek ve önbelleğe alacaktır. Önbelleğe alınan görüntüler yapılandırma klasörünüze kaydedilir. Dosyaları {appDataPath}/cache/images konumunda bulabilirsiniz.", "components.Settings.SettingsUsers.userSettingsDescription": "Genel ve varsayılan kullanıcı ayarlarını yapılandırın.", "components.Settings.SonarrModal.editsonarr": "Sonarr Sunucusunu Düzenle", @@ -1036,10 +1036,10 @@ "components.TitleCard.watchlistSuccess": "{title} izleme listesine başarıyla eklendi!", "components.TvDetails.Season.noepisodes": "Bölüm listesi mevcut değil.", "components.TvDetails.Season.somethingwentwrong": "Sezon verisi alınırken bir şeyler ters gitti.", - "components.TvDetails.TvCast.fullseriescast": "Dizinin Tam Kadrosu", + "components.TvDetails.TvCast.fullseriescast": "Dizinin Tüm Oyuncuları", "components.TvDetails.TvCrew.fullseriescrew": "Dizinin Tam Ekibi", "components.TvDetails.anime": "Anime", - "components.TvDetails.cast": "Kadro", + "components.TvDetails.cast": "Oyuncular", "components.TvDetails.episodeRuntime": "Bölümün Süresi", "components.TvDetails.episodeRuntimeMinutes": "{runtime} dakika", "components.TvDetails.firstAirDate": "İlk Yayın Tarihi", @@ -1291,7 +1291,7 @@ "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "30 karakterlik kullanıcı ya da grup kimliği", "components.UserProfile.emptywatchlist": "Plex İzleme Listenize eklenen içerikler burada gözükeceklerdir.", "i18n.restartRequired": "Yeniden Başlatma Gereklidir", - "components.UserProfile.UserSettings.UserGeneralSettings.discordIdTip": "Discord hesabınız ile bağıntılı numaralardan oluşan kullanıcı ID'niz", + "components.UserProfile.UserSettings.UserGeneralSettings.discordIdTip": "Discord kullanıcı hesabınızla ilişkili çok haneli kimlik numarası", "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet bildirim ayarları kaydedildi!", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Bir sohbet başlatın ve @get_id_bot ID'li botunuzu ekleyin. Son olarak /my_id komutunu kullanın", "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Geçerli bir uygulama token'i sağlamalısınız", diff --git a/src/i18n/locale/uk.json b/src/i18n/locale/uk.json index b9567d0d..41891b67 100644 --- a/src/i18n/locale/uk.json +++ b/src/i18n/locale/uk.json @@ -1375,5 +1375,8 @@ "i18n.addToBlacklist": "Додати в чорний список", "i18n.blacklist": "Чорний список", "i18n.removeFromBlacklistSuccess": "{title}було успішно видалено з чорного списку.", - "i18n.blacklisted": "У чорному списку" + "i18n.blacklisted": "У чорному списку", + "components.Settings.SettingsJobsCache.usersavatars": "Аватар користувача", + "components.PermissionEdit.manageblacklist": "Керувати чорним списком", + "components.Settings.Notifications.validationWebhookRoleId": "Ви повинні надати дійсний ідентифікатор ролі Discord" }