Compare commits
2 Commits
0xsysr3ll/
...
fallenbage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
959a40ff20 | ||
|
|
137df4c9c8 |
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,33 +1,14 @@
|
||||
<!--
|
||||
Please read contributing guide before submitting
|
||||
your pull request. Please fill in each section below to help us better prioritize your pull request. Thanks!
|
||||
-->
|
||||
#### Description
|
||||
|
||||
## Description
|
||||
#### Screenshot (if UI-related)
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
#### To-Dos
|
||||
|
||||
- Fixes #XXXX
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Screenshots / Logs (if applicable)
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] I have read and followed the contribution [guidelines](https://github.com/seerr-team/seerr/blob/develop/CONTRIBUTING.md).
|
||||
- [ ] Disclosed any use of AI (see our [policy](https://github.com/seerr-team/seerr/blob/develop/CONTRIBUTING.md#ai-assistance-notice))
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] All new and existing tests passed.
|
||||
- [ ] Disclosed any use of AI (see our [policy](../CONTRIBUTING.md#ai-assistance-notice))
|
||||
- [ ] Successful build `pnpm build`
|
||||
- [ ] Translation keys `pnpm i18n:extract`
|
||||
- [ ] Database migration (if required)
|
||||
|
||||
#### Issues Fixed or Closed
|
||||
|
||||
- Fixes #XXXX
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
---
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
name: 'Seerr Labeller'
|
||||
name: 'AI-generated pull requests'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
permissions: {}
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
ai-generated-support:
|
||||
if: >
|
||||
github.event_name == 'pull_request_target' &&
|
||||
(github.event.label.name == 'ai-generated' || (github.event.action == 'reopened' && contains(github.event.pull_request.labels.*.name, 'ai-generated')))
|
||||
if: github.event.label.name == 'ai-generated' || github.event.action == 'reopened'
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: ai-generated-${{ github.event.pull_request.number }}
|
||||
group: support-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -46,7 +43,7 @@ jobs:
|
||||
retry gh pr close "$NUMBER" -R "$GH_REPO" || true
|
||||
gh pr lock "$NUMBER" -R "$GH_REPO" -r "spam" || true
|
||||
|
||||
- name: Label removed, reopen and unlock pull request
|
||||
- name: Reopened or label removed, unlock pull request
|
||||
if: github.event.action == 'unlabeled' && github.event.label.name == 'ai-generated'
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -60,52 +57,3 @@ jobs:
|
||||
run: |
|
||||
gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "ai-generated" || true
|
||||
gh pr unlock "$NUMBER" -R "$GH_REPO" || true
|
||||
|
||||
support:
|
||||
if: >
|
||||
github.event_name == 'issues' &&
|
||||
(github.event.label.name == 'support' ||
|
||||
(github.event.action == 'reopened' && contains(github.event.issue.labels.*.name, 'support')))
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: support-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
issues: write
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
steps:
|
||||
- name: Label added, comment and close issue
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'support'
|
||||
shell: bash
|
||||
env:
|
||||
BODY: >
|
||||
:wave: @${{ env.ISSUE_AUTHOR }}, we use the issue tracker exclusively
|
||||
for bug reports and feature requests. However, this issue appears
|
||||
to be a support request. Please use our support channels
|
||||
to get help with Seerr.
|
||||
|
||||
- [Discord](https://discord.gg/seerr)
|
||||
run: |
|
||||
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
|
||||
retry gh issue comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
|
||||
retry gh issue close "$NUMBER" -R "$GH_REPO" || true
|
||||
gh issue lock "$NUMBER" -R "$GH_REPO" -r "off_topic" || true
|
||||
|
||||
- name: Label removed, reopen and unlock issue
|
||||
if: github.event.action == 'unlabeled' && github.event.label.name == 'support'
|
||||
shell: bash
|
||||
run: |
|
||||
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
|
||||
retry gh issue reopen "$NUMBER" -R "$GH_REPO" || true
|
||||
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
|
||||
|
||||
- name: Remove support label on manual reopen
|
||||
if: github.event.action == 'reopened'
|
||||
shell: bash
|
||||
run: |
|
||||
gh issue edit "$NUMBER" -R "$GH_REPO" --remove-label "support" || true
|
||||
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
|
||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -14,9 +14,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DOCKER_HUB: seerr/seerr
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -143,7 +140,7 @@ jobs:
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKER_HUB }}
|
||||
${{ github.repository }}
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=develop
|
||||
|
||||
4
.github/workflows/helm.yml
vendored
4
.github/workflows/helm.yml
vendored
@@ -105,7 +105,7 @@ jobs:
|
||||
uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
|
||||
- name: Downloads artifacts
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
|
||||
- name: Downloads artifacts
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
|
||||
5
.github/workflows/preview.yml
vendored
5
.github/workflows/preview.yml
vendored
@@ -11,9 +11,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DOCKER_HUB: seerr/seerr
|
||||
|
||||
concurrency:
|
||||
group: preview-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -118,7 +115,7 @@ jobs:
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKER_HUB }}
|
||||
${{ github.repository }}
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=preview-${{ steps.ver.outputs.version }}
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -144,7 +144,7 @@ jobs:
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKER_HUB }}
|
||||
${{ github.repository }}
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=${{ env.VERSION }}
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
- name: Resolve manifest digest
|
||||
id: digests
|
||||
run: |
|
||||
DIGEST=$(docker buildx imagetools inspect "${{ env.DOCKER_HUB }}:${{ env.VERSION }}" --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
DIGEST=$(docker buildx imagetools inspect "${{ github.repository }}:${{ env.VERSION }}" --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
echo "IMAGE_DIGEST=$DIGEST" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Also tag :latest (non-pre-release only)
|
||||
@@ -206,7 +206,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
|
||||
- name: Install Trivy
|
||||
uses: aquasecurity/setup-trivy@e6c2c5e321ed9123bda567646e2f96565e34abe1 # v0.2.4
|
||||
@@ -267,7 +267,7 @@ jobs:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
|
||||
- name: Verify signatures
|
||||
run: |
|
||||
|
||||
57
.github/workflows/support.yml
vendored
Normal file
57
.github/workflows/support.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
name: 'Support requests'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
permissions:
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
support:
|
||||
if: github.event.label.name == 'support' || github.event.action == 'reopened'
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: support-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
issues: write
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
steps:
|
||||
- name: Label added, comment and close issue
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'support'
|
||||
shell: bash
|
||||
env:
|
||||
BODY: >
|
||||
:wave: @${{ env.ISSUE_AUTHOR }}, we use the issue tracker exclusively
|
||||
for bug reports and feature requests. However, this issue appears
|
||||
to be a support request. Please use our support channels
|
||||
to get help with Seerr.
|
||||
|
||||
- [Discord](https://discord.gg/seerr)
|
||||
run: |
|
||||
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
|
||||
retry gh issue comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
|
||||
retry gh issue close "$NUMBER" -R "$GH_REPO" || true
|
||||
gh issue lock "$NUMBER" -R "$GH_REPO" -r "off_topic" || true
|
||||
|
||||
- name: Reopened or label removed, unlock issue
|
||||
if: github.event.action == 'unlabeled' && github.event.label.name == 'support'
|
||||
shell: bash
|
||||
run: |
|
||||
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
|
||||
retry gh issue reopen "$NUMBER" -R "$GH_REPO" || true
|
||||
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
|
||||
|
||||
- name: Remove support label on manual reopen
|
||||
if: github.event.action == 'reopened'
|
||||
shell: bash
|
||||
run: |
|
||||
gh issue edit "$NUMBER" -R "$GH_REPO" --remove-label "support" || true
|
||||
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -71,6 +71,3 @@ tsconfig.tsbuildinfo
|
||||
|
||||
# Config Cache Directory
|
||||
config/cache
|
||||
|
||||
# Docker compose
|
||||
compose.override.yaml
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
.next/
|
||||
dist/
|
||||
config/
|
||||
CHANGELOG.md
|
||||
pnpm-lock.yaml
|
||||
cypress/config/settings.cypress.json
|
||||
|
||||
|
||||
1216
CHANGELOG.md
Normal file
1216
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -151,9 +151,9 @@ When adding new UI text, please try to adhere to the following guidelines:
|
||||
|
||||
## Translation
|
||||
|
||||
We use [Weblate](https://translate.seerr.dev/projects/seerr/seerr-frontend/) for our translations, and your help with localizing Seerr would be greatly appreciated! If your language is not listed below, please [open a feature request](/../../issues/new/choose).
|
||||
We use [Weblate](https://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/) for our translations, and your help with localizing Seerr would be greatly appreciated! If your language is not listed below, please [open a feature request](/../../issues/new/choose).
|
||||
|
||||
<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/multi-auto.svg" alt="Translation status" /></a>
|
||||
<a href="https://jellyseerr.borgcube.de/engage/jellysseerr/"><img src="https://jellyseerr.borgcube.de/widget/jellyseerr/multi-auto.svg" alt="Translation status" /></a>
|
||||
|
||||
## Migrations
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ WORKDIR /app
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store CI=true pnpm install --prod --frozen-lockfile
|
||||
|
||||
FROM base AS build
|
||||
|
||||
ARG COMMIT_TAG
|
||||
ENV COMMIT_TAG=${COMMIT_TAG}
|
||||
FROM base as build
|
||||
|
||||
RUN \
|
||||
case "${TARGETPLATFORM}" in \
|
||||
|
||||
10
README.md
10
README.md
@@ -7,8 +7,8 @@
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/seerr"><img src="https://img.shields.io/discord/783137440809746482" alt="Discord"></a>
|
||||
<a href="https://hub.docker.com/r/seerr/seerr"><img src="https://img.shields.io/docker/pulls/seerr/seerr" alt="Docker pulls"></a>
|
||||
<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="https://hub.docker.com/r/fallenbagel/jellyseerr"><img src="https://img.shields.io/docker/pulls/fallenbagel/jellyseerr" alt="Docker pulls"></a>
|
||||
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="https://github.com/seerr-team/seerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/seerr-team/seerr"></a>
|
||||
|
||||
**Seerr** 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/)**.
|
||||
@@ -36,6 +36,12 @@ Check out our documentation for instructions on how to install and run Seerr:
|
||||
|
||||
https://docs.seerr.dev/getting-started/
|
||||
|
||||
### Packages:
|
||||
|
||||
Archlinux: [AUR](https://aur.archlinux.org/packages/jellyseerr)
|
||||
|
||||
Nix: [Nixpkg](https://search.nixos.org/packages?channel=unstable&show=jellyseerr)
|
||||
|
||||
## Preview
|
||||
|
||||
<img src="./public/preview.jpg">
|
||||
|
||||
@@ -4,8 +4,8 @@ name: seerr-chart
|
||||
description: Seerr helm chart for Kubernetes
|
||||
type: application
|
||||
version: 3.0.0
|
||||
# renovate: image=ghcr.io/seerr-team/seerr
|
||||
appVersion: '3.0.0'
|
||||
# renovate: image=ghcr.io/fallenbagel/jellyseerr
|
||||
appVersion: '2.7.3'
|
||||
maintainers:
|
||||
- name: Seerr Team
|
||||
url: https://github.com/orgs/seerr-team/people
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# seerr-chart
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Seerr helm chart for Kubernetes
|
||||
|
||||
@@ -22,13 +22,13 @@ Kubernetes: `>=1.23.0-0`
|
||||
|
||||
## Installation
|
||||
|
||||
Refer to [Seerr kubernetes documentation](https://docs.seerr.dev/getting-started/kubernetes)
|
||||
Refer to [https://docs.seerr.dev/getting-started/kubernetes](Seerr kubernetes documentation)
|
||||
|
||||
## Update Notes
|
||||
|
||||
### Updating to 3.0.0
|
||||
|
||||
Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳 refer to our [Migration guide](https://docs.seerr.dev/migration-guide).
|
||||
Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳.
|
||||
|
||||
### Updating to 2.7.0
|
||||
|
||||
@@ -70,20 +70,12 @@ If `replicaCount` value was used - remove it. Helm update should work fine after
|
||||
| nodeSelector | object | `{}` | |
|
||||
| podAnnotations | object | `{}` | |
|
||||
| podLabels | object | `{}` | |
|
||||
| podSecurityContext.fsGroup | int | `1000` | |
|
||||
| podSecurityContext.fsGroupChangePolicy | string | `"OnRootMismatch"` | |
|
||||
| podSecurityContext | object | `{}` | |
|
||||
| probes.livenessProbe | object | `{}` | Configure liveness probe |
|
||||
| probes.readinessProbe | object | `{}` | Configure readiness probe |
|
||||
| probes.startupProbe | string | `nil` | Configure startup probe |
|
||||
| resources | object | `{}` | |
|
||||
| securityContext.allowPrivilegeEscalation | bool | `false` | |
|
||||
| securityContext.capabilities.drop[0] | string | `"ALL"` | |
|
||||
| securityContext.privileged | bool | `false` | |
|
||||
| securityContext.readOnlyRootFilesystem | bool | `false` | |
|
||||
| securityContext.runAsGroup | int | `1000` | |
|
||||
| securityContext.runAsNonRoot | bool | `true` | |
|
||||
| securityContext.runAsUser | int | `1000` | |
|
||||
| securityContext.seccompProfile.type | string | `"RuntimeDefault"` | |
|
||||
| securityContext | object | `{}` | |
|
||||
| service.port | int | `80` | |
|
||||
| service.type | string | `"ClusterIP"` | |
|
||||
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Refer to [Seerr kubernetes documentation](https://docs.seerr.dev/getting-started/kubernetes)
|
||||
Refer to [https://docs.seerr.dev/getting-started/kubernetes](Seerr kubernetes documentation)
|
||||
|
||||
## Update Notes
|
||||
|
||||
### Updating to 3.0.0
|
||||
|
||||
Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳 refer to our [Migration guide](https://docs.seerr.dev/migration-guide).
|
||||
Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳.
|
||||
|
||||
### Updating to 2.7.0
|
||||
|
||||
|
||||
@@ -50,22 +50,16 @@ serviceAccount:
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
@@ -24,9 +24,10 @@ import TabItem from '@theme/TabItem';
|
||||
```bash
|
||||
sudo mkdir -p /opt/seerr && cd /opt/seerr
|
||||
```
|
||||
2. Clone the Seerr repository and checkout the main branch:
|
||||
2. Clone the Seerr repository and checkout the develop branch:
|
||||
```bash
|
||||
git clone https://github.com/seerr-team/seerr.git .
|
||||
git clone https://github.com/fallenbagel/jellyseerr.git
|
||||
cd jellyseerr
|
||||
git checkout main
|
||||
```
|
||||
3. Install the dependencies:
|
||||
@@ -198,9 +199,9 @@ pm2 status seerr
|
||||
mkdir C:\seerr
|
||||
cd C:\seerr
|
||||
```
|
||||
2. Clone the Seerr repository and checkout the main branch:
|
||||
2. Clone the Seerr repository and checkout the develop branch:
|
||||
```powershell
|
||||
git clone https://github.com/seerr-team/seerr.git .
|
||||
git clone https://github.com/fallenbagel/jellyseerr.git .
|
||||
git checkout main
|
||||
```
|
||||
3. Install the dependencies:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kubernetes (Advanced)
|
||||
description: Install Seerr in Kubernetes
|
||||
description: Install Jellyseerr in Kubernetes
|
||||
sidebar_position: 3
|
||||
---
|
||||
# Kubernetes
|
||||
@@ -11,16 +11,17 @@ This method is not recommended for most users. It is intended for advanced users
|
||||
:::info
|
||||
All official Seerr charts are cryptographically signed and include a verified [Software Bill of Materials (SBOM)](https://cyclonedx.org/).
|
||||
|
||||
To confirm that the chart you are using is authentic and unmodified, please refer to the [Verifying Signed Artifacts](/using-seerr/advanced/verifying-signed-artifacts#verifying-signed-helm-charts) guide.
|
||||
To confirm that the chart you are using is authentic and unmodified, please refer to the [Verifying Signed Artifacts](/using-jellyseerr/advanced/verifying-signed-artifacts#verifying-signed-helm-charts) guide.
|
||||
:::
|
||||
|
||||
## Installation
|
||||
```console
|
||||
helm install seerr oci://ghcr.io/seerr-team/seerr/seerr-chart
|
||||
```
|
||||
Helm values can be found in the Seerr repository under [charts/seerr-chart/README.md](https://github.com/seerr-team/seerr/tree/develop/charts/seerr-chart).
|
||||
Helm values can be found in the Jellyseerr repository under [charts/jellyseerr-chart/README.md](https://github.com/fallenbagel/jellyseerr/tree/develop/charts/jellyseerr-chart).
|
||||
|
||||
Verify the signature with [cosign](https://docs.sigstore.dev/cosign/system_config/installation/) (replace [tag], with the TAG you want to verify) :
|
||||
```console
|
||||
cosign verify ghcr.io/seerr-team/seerr/seerr-chart:[tag] --certificate-identity=https://github.com/seerr-team/seerr/.github/workflows/helm.yml@refs/heads/main --certificate-oidc-issuer=https://token.actions.githubusercontent.com
|
||||
cosign verify ghcr.io/seerr-team/seerr/seerr-chart:[tag] --certificate-identity=https://github.com/fallenbagel/jellyseerr/.github/workflows/helm.yml@refs/heads/main --certificate-oidc-issuer=https://token.ac
|
||||
tions.githubusercontent.com
|
||||
```
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
title: Migration guide
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Whether you come from Overseerr or Jellyseerr, you don't need to perform any manual migration steps, your instance will automatically be migrated to Seerr.
|
||||
This migration will run automatically the first time you start your instance using the Seerr codebase (Docker image or source build or Kubernetes, etc.).
|
||||
An additional migration will happen for Overseerr users, to migrate their configuration to the new codebase.
|
||||
|
||||
:::warning
|
||||
Before doing anything you should backup your existing instance so that you can rollback in case something goes wrong.
|
||||
See [Backups](/using-seerr/backups) for details on how to properly backup your instance.
|
||||
:::
|
||||
|
||||
## Docker
|
||||
Refer to [Seerr Docker Documentation](/getting-started/docker), all of our examples have been updated to reflect the below change.
|
||||
|
||||
Changes :
|
||||
- Renamed all references from `overseerr` or `jellyseerr` to `seerr`.
|
||||
- The container image reference has been updated.
|
||||
- The container can now be run as a non-root user (`node` user); remove the `user` directive if you have configured it.
|
||||
- The container no longer provides an init process, so you must configure it by adding `init: true` for Docker Compose or `--init` for the Docker CLI.
|
||||
|
||||
:::info
|
||||
**Config folder permissions**: Since the container now runs as the `node` user (UID 1000), you must ensure your config folder has the correct permissions. The `node` user must have read and write access to the `/app/config` directory.
|
||||
|
||||
If you're migrating from a previous installation, you may need to update the ownership of your config folder:
|
||||
```bash
|
||||
docker run --rm -v /path/to/appdata/config:/data alpine chown -R 1000:1000 /data
|
||||
```
|
||||
|
||||
This ensures the `node` user (UID 1000) owns the config directory and can read and write to it.
|
||||
:::
|
||||
|
||||
### Unix
|
||||
|
||||
Summary of changes :
|
||||
<Tabs groupId="docker-methods" queryString>
|
||||
<TabItem value="docker-compose" label="Docker compose">
|
||||
```yaml {3-6}
|
||||
---
|
||||
services:
|
||||
seerr:
|
||||
image: ghcr.io/seerr-team/seerr:latest
|
||||
init: true
|
||||
container_name: seerr
|
||||
environment:
|
||||
- LOG_LEVEL=debug
|
||||
- TZ=Asia/Tashkent
|
||||
- PORT=5055 #optional
|
||||
ports:
|
||||
- 5055:5055
|
||||
volumes:
|
||||
- /path/to/appdata/config:/app/config
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/status || exit 1
|
||||
start_period: 20s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="docker-cli" label="Docker CLI">
|
||||
```bash {2-3,10}
|
||||
docker run -d \
|
||||
--name seerr \
|
||||
--init \
|
||||
-e LOG_LEVEL=debug \
|
||||
-e TZ=Asia/Tashkent \
|
||||
-e PORT=5055 \
|
||||
-p 5055:5055 \
|
||||
-v /path/to/appdata/config:/app/config \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/seerr-team/seerr:latest
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Windows
|
||||
Summary of changes :
|
||||
<Tabs groupId="docker-methods" queryString>
|
||||
<TabItem value="docker-compose" label="Docker compose">
|
||||
```yaml {3-6,13,23}
|
||||
---
|
||||
services:
|
||||
seerr:
|
||||
image: ghcr.io/seerr-team/seerr:latest
|
||||
init: true
|
||||
container_name: seerr
|
||||
environment:
|
||||
- LOG_LEVEL=debug
|
||||
- TZ=Asia/Tashkent
|
||||
ports:
|
||||
- 5055:5055
|
||||
volumes:
|
||||
- seerr-data:/app/config
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/status || exit 1
|
||||
start_period: 20s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
seerr-data:
|
||||
external: true
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="docker-cli" label="Docker CLI">
|
||||
```bash {2-3,8,10}
|
||||
docker run -d \
|
||||
--name seerr \
|
||||
--init \
|
||||
-e LOG_LEVEL=debug \
|
||||
-e TZ=Asia/Tashkent \
|
||||
-e PORT=5055 \
|
||||
-p 5055:5055 \
|
||||
-v seerr-data:/app/config \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/seerr-team/seerr:latest
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Kubernetes
|
||||
Refer to [Seerr Kubernetes Documentation](/getting-started/kubernetes), all of our examples have been updated to reflect the below change.
|
||||
|
||||
Changes :
|
||||
- All references to `jellyseerr` have been renamed to `seerr` in the manifests.
|
||||
- The container image reference has been updated.
|
||||
- The default `securityContext` and `podSecurityContext` have been updated to support running the container without root permissions.
|
||||
|
||||
Summary of changes :
|
||||
<Tabs groupId="kubernetes-values" queryString>
|
||||
<TabItem value="old" label="Old values">
|
||||
```yaml
|
||||
image:
|
||||
repository: fallenbagel/jellyseerr
|
||||
podSecurityContext: {}
|
||||
securityContext: {}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="new" label="New values">
|
||||
```yaml
|
||||
image:
|
||||
repository: seerr-team/seerr
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -174,36 +174,4 @@ This can happen if you have a new installation of Jellyfin/Emby or if you have c
|
||||
|
||||
This process should restore your admin privileges while preserving your settings.
|
||||
|
||||
## Failed to enable web push notifications
|
||||
|
||||
### Option 1: You are using Pi-hole
|
||||
|
||||
When using Pi-hole, you need to whitelist the proper domains in order for the queries to not be intercepted and blocked by Pi-hole.
|
||||
If you are using a chromium based browser (eg: Chrome, Brave, Edge...), the domain you need to whitelist is `fcm.googleapis.com`
|
||||
If you are using Firefox, the domain you need to whitelist is `push.services.mozilla.com`
|
||||
|
||||
1. Log into your Pi-hole through the admin interface, then click on Domains situated under GROUP MANAGEMENT.
|
||||
2. Add the domain corresponding to your browser in the `Domain to be added` field and then click on Add to allowed domains.
|
||||
3. Now in order for those changes to be used you need to flush your current dns cache.
|
||||
4. You can do so by using this command line in your Pi-hole terminal:
|
||||
```bash
|
||||
pihole restartdns
|
||||
```
|
||||
If this command fails (which is unlikely), use this equivalent:
|
||||
```bash
|
||||
pihole -f && pihole restartdns
|
||||
```
|
||||
5. Then restart your Seerr instance and try to enable the web push notifications again.
|
||||
|
||||
|
||||
### Option 2: You are using Brave browser
|
||||
|
||||
Brave is a "De-Googled" browser. So by default or if you refused a prompt in the past, it cuts the access to the FCM (Firebase Cloud Messaging) service, which is mandatory for the web push notifications on Chromium based browsers.
|
||||
|
||||
1. Open Brave and paste this address in the url bar: `brave://settings/privacy`
|
||||
2. Look for the option: "Use Google services for push messaging"
|
||||
3. Activate this option
|
||||
4. Relaunch Brave completely
|
||||
5. You should now see the notifications prompt appearing instead of an error message.
|
||||
|
||||
If you still encounter issues, please reach out on our support channels.
|
||||
|
||||
@@ -22,4 +22,4 @@ Users can customize their notification preferences in their own user notificatio
|
||||
|
||||
## Requesting New Notification Agents
|
||||
|
||||
If we do not currently support your preferred notification agent, feel free to [submit a feature request on GitHub](https://github.com/seerr-team/seerr/issues). However, please be sure to search first and confirm that there is not already an existing request for the agent!
|
||||
If we do not currently support your preferred notification agent, feel free to [submit a feature request on GitHub](https://github.com/fallenbagel/jellyseerr/issues). However, please be sure to search first and confirm that there is not already an existing request for the agent!
|
||||
|
||||
@@ -16,7 +16,7 @@ User notifications are separate from system notifications, and the available not
|
||||
|
||||
### Application/API Token
|
||||
|
||||
[Register an application](https://pushover.net/apps/build) and enter the API token in this field. (You can use one of the [official icons in our GitHub repository](https://github.com/seerr-team/seerr/tree/develop/public) when configuring the application.)
|
||||
[Register an application](https://pushover.net/apps/build) and enter the API token in this field. (You can use one of the [official icons in our GitHub repository](https://github.com/fallenbagel/jellyseerr/tree/develop/public) when configuring the application.)
|
||||
|
||||
For more details on registering applications or the API token, please see the [Pushover API documentation](https://pushover.net/api#registration).
|
||||
|
||||
|
||||
@@ -22,17 +22,6 @@ This is typically not needed. Please refer to your webhook provider's documentat
|
||||
|
||||
This value will be sent as an `Authorization` HTTP header.
|
||||
|
||||
### Custom Headers (optional)
|
||||
|
||||
You can add additional custom HTTP headers to be sent with each webhook request. This is useful for API keys, custom authentication schemes, or any other headers your webhook endpoint requires.
|
||||
|
||||
- Click "Add Header" to add a new header
|
||||
- Enter the header name and value
|
||||
|
||||
:::warning
|
||||
You cannot configure both the **Authorization Header** field and a custom `Authorization` header in Custom Headers at the same time. You must choose one method.
|
||||
:::
|
||||
|
||||
### JSON Payload
|
||||
|
||||
Customize the JSON payload to suit your needs. Seerr provides several [template variables](#template-variables) for use in the payload, which will be replaced with the relevant data when the notifications are triggered.
|
||||
|
||||
24
gen-docs/blog/2025-09-29-introducing-jellyseerr-blog.md
Normal file
24
gen-docs/blog/2025-09-29-introducing-jellyseerr-blog.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Welcome to the Jellyseerr Blog
|
||||
description: The official Jellyseerr blog for release notes, technical updates, and community news.
|
||||
slug: welcome
|
||||
authors: [fallenbagel, gauthier-th]
|
||||
tags: [announcement, jellyseerr, blog]
|
||||
image: https://raw.githubusercontent.com/fallenbagel/jellyseerr/refs/heads/develop/gen-docs/static/img/logo.svg
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
We are pleased to introduce the official Jellyseerr blog.
|
||||
|
||||
This space will serve as the central place for:
|
||||
|
||||
- Release announcements
|
||||
- Updates on new features and improvements
|
||||
- Technical articles, such as details on our [**DNS caching package**](https://github.com/jellyseerr/dns-caching) and other enhancements
|
||||
- Community-related news
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
Our goal is to keep the community informed and provide deeper insights into the ongoing development of Jellyseerr.
|
||||
|
||||
Thank you for being part of the Jellyseerr project. More updates will follow soon.
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: Welcome to the Seerr Blog
|
||||
description: The official Seerr blog for release notes, technical updates, and community news.
|
||||
slug: welcome
|
||||
authors: [fallenbagel, gauthier-th]
|
||||
tags: [announcement, seerr, blog]
|
||||
image: https://raw.githubusercontent.com/seerr-team/seerr/refs/heads/develop/gen-docs/static/img/logo.svg
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
We are pleased to introduce the official Seerr blog.
|
||||
|
||||
This space will serve as the central place for:
|
||||
|
||||
- Release announcements
|
||||
- Updates on new features and improvements
|
||||
- Technical articles, such as details on our [**DNS caching package**](https://github.com/seerr/dns-caching) and other enhancements
|
||||
- Community-related news
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
Our goal is to keep the community informed and provide deeper insights into the ongoing development of Seerr.
|
||||
|
||||
Thank you for being part of the Seerr project. More updates will follow soon.
|
||||
100
package.json
100
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "seerr",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"postinstall": "node postinstall-win.js",
|
||||
@@ -33,38 +33,38 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dr.pogodin/csurf": "^1.16.6",
|
||||
"@formatjs/intl-displaynames": "6.8.13",
|
||||
"@dr.pogodin/csurf": "^1.14.1",
|
||||
"@formatjs/intl-displaynames": "6.2.6",
|
||||
"@formatjs/intl-locale": "3.1.1",
|
||||
"@formatjs/intl-pluralrules": "5.4.6",
|
||||
"@formatjs/intl-pluralrules": "5.1.10",
|
||||
"@formatjs/intl-utils": "3.8.4",
|
||||
"@formatjs/swc-plugin-experimental": "^0.4.0",
|
||||
"@headlessui/react": "1.7.12",
|
||||
"@heroicons/react": "2.2.0",
|
||||
"@heroicons/react": "2.0.16",
|
||||
"@supercharge/request-ip": "1.2.0",
|
||||
"@svgr/webpack": "6.5.1",
|
||||
"@tanem/react-nprogress": "5.0.56",
|
||||
"@tanem/react-nprogress": "5.0.30",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/wink-jaro-distance": "^2.0.2",
|
||||
"ace-builds": "1.43.4",
|
||||
"axios": "1.13.2",
|
||||
"axios-rate-limit": "1.4.0",
|
||||
"ace-builds": "1.15.2",
|
||||
"axios": "1.10.0",
|
||||
"axios-rate-limit": "1.3.0",
|
||||
"bcrypt": "5.1.0",
|
||||
"bowser": "2.13.1",
|
||||
"bowser": "2.11.0",
|
||||
"connect-typeorm": "1.1.4",
|
||||
"cookie-parser": "1.4.7",
|
||||
"copy-to-clipboard": "3.3.3",
|
||||
"country-flag-icons": "1.6.4",
|
||||
"country-flag-icons": "1.5.5",
|
||||
"cronstrue": "2.23.0",
|
||||
"date-fns": "2.29.3",
|
||||
"dayjs": "1.11.19",
|
||||
"dayjs": "1.11.7",
|
||||
"dns-caching": "^0.2.7",
|
||||
"email-templates": "12.0.3",
|
||||
"email-templates": "12.0.1",
|
||||
"express": "4.21.2",
|
||||
"express-openapi-validator": "4.13.8",
|
||||
"express-rate-limit": "6.7.0",
|
||||
"express-session": "1.18.2",
|
||||
"formik": "^2.4.9",
|
||||
"express-session": "1.17.3",
|
||||
"formik": "^2.4.6",
|
||||
"gravatar-url": "3.1.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
@@ -76,19 +76,19 @@
|
||||
"node-schedule": "2.1.1",
|
||||
"nodemailer": "6.10.0",
|
||||
"openpgp": "5.11.2",
|
||||
"pg": "8.16.3",
|
||||
"pg": "8.11.0",
|
||||
"plex-api": "5.3.2",
|
||||
"pug": "3.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-ace": "10.1.0",
|
||||
"react-animate-height": "2.1.2",
|
||||
"react-aria": "3.44.0",
|
||||
"react-aria": "3.23.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-intersection-observer": "9.4.3",
|
||||
"react-intl": "^6.6.8",
|
||||
"react-markdown": "8.0.5",
|
||||
"react-popper-tooltip": "4.4.2",
|
||||
"react-select": "5.10.2",
|
||||
"react-select": "5.7.0",
|
||||
"react-spring": "9.7.1",
|
||||
"react-tailwindcss-datepicker-sct": "1.3.4",
|
||||
"react-toast-notifications": "2.5.1",
|
||||
@@ -97,19 +97,19 @@
|
||||
"react-use-clipboard": "1.0.9",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"secure-random-password": "0.2.3",
|
||||
"semver": "7.7.3",
|
||||
"semver": "7.7.1",
|
||||
"sharp": "^0.33.4",
|
||||
"sqlite3": "5.1.7",
|
||||
"swagger-ui-express": "4.6.2",
|
||||
"swr": "2.3.7",
|
||||
"swr": "2.2.5",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"typeorm": "0.3.12",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"undici": "^7.16.0",
|
||||
"validator": "^13.15.23",
|
||||
"web-push": "3.6.7",
|
||||
"undici": "^7.3.0",
|
||||
"validator": "^13.15.15",
|
||||
"web-push": "3.5.0",
|
||||
"wink-jaro-distance": "^2.0.0",
|
||||
"winston": "3.18.3",
|
||||
"winston": "3.8.2",
|
||||
"winston-daily-rotate-file": "4.7.1",
|
||||
"xml2js": "0.4.23",
|
||||
"yamljs": "0.3.0",
|
||||
@@ -123,33 +123,32 @@
|
||||
"@tailwindcss/forms": "0.5.10",
|
||||
"@tailwindcss/typography": "0.5.16",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/cookie-parser": "1.4.10",
|
||||
"@types/country-flag-icons": "1.2.2",
|
||||
"@types/csurf": "1.11.5",
|
||||
"@types/cookie-parser": "1.4.3",
|
||||
"@types/country-flag-icons": "1.2.0",
|
||||
"@types/csurf": "1.11.2",
|
||||
"@types/email-templates": "8.0.4",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/express-session": "1.18.2",
|
||||
"@types/lodash": "4.17.21",
|
||||
"@types/express-session": "1.17.6",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/mime": "3",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node-schedule": "2.1.8",
|
||||
"@types/node-schedule": "2.1.0",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-transition-group": "4.4.12",
|
||||
"@types/react-transition-group": "4.4.5",
|
||||
"@types/secure-random-password": "0.2.1",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/validator": "^13.15.10",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/swagger-ui-express": "4.1.3",
|
||||
"@types/validator": "^13.15.3",
|
||||
"@types/web-push": "3.3.2",
|
||||
"@types/xml2js": "0.4.11",
|
||||
"@types/yamljs": "0.2.31",
|
||||
"@types/yup": "0.29.14",
|
||||
"@typescript-eslint/eslint-plugin": "5.54.0",
|
||||
"@typescript-eslint/parser": "5.54.0",
|
||||
"autoprefixer": "10.4.22",
|
||||
"baseline-browser-mapping": "^2.8.32",
|
||||
"commitizen": "4.3.1",
|
||||
"autoprefixer": "10.4.13",
|
||||
"commitizen": "4.3.0",
|
||||
"copyfiles": "2.4.1",
|
||||
"cy-mobile-commands": "0.3.0",
|
||||
"cypress": "14.1.0",
|
||||
@@ -158,22 +157,22 @@
|
||||
"eslint-config-next": "^14.2.4",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-formatjs": "4.9.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-no-relative-import-paths": "1.6.1",
|
||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||
"eslint-plugin-no-relative-import-paths": "1.5.2",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"husky": "8.0.3",
|
||||
"lint-staged": "13.1.2",
|
||||
"nodemon": "3.1.11",
|
||||
"postcss": "8.5.6",
|
||||
"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",
|
||||
"tailwindcss": "3.2.7",
|
||||
"ts-node": "10.9.2",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"ts-node": "10.9.1",
|
||||
"tsc-alias": "1.8.2",
|
||||
"tsconfig-paths": "4.1.2",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"engines": {
|
||||
@@ -182,7 +181,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"sqlite3/node-gyp": "8.4.1",
|
||||
"@types/express-session": "1.18.2"
|
||||
"@types/express-session": "1.17.6"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
@@ -205,11 +204,8 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@swc/core",
|
||||
"bcrypt",
|
||||
"cypress",
|
||||
"sharp",
|
||||
"sqlite3"
|
||||
"sqlite3",
|
||||
"bcrypt"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
4118
pnpm-lock.yaml
generated
4118
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@@ -112,10 +112,6 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
|
||||
DateCreated?: string;
|
||||
}
|
||||
|
||||
type EpisodeReturn<T> = T extends { includeMediaInfo: true }
|
||||
? JellyfinLibraryItemExtended[]
|
||||
: JellyfinLibraryItem[];
|
||||
|
||||
export interface JellyfinItemsReponse {
|
||||
Items: JellyfinLibraryItemExtended[];
|
||||
TotalRecordCount: number;
|
||||
@@ -149,7 +145,7 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeaderVal,
|
||||
'X-Emby-Authorization': authHeaderVal,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
@@ -419,22 +415,13 @@ class JellyfinAPI extends ExternalAPI {
|
||||
}
|
||||
}
|
||||
|
||||
public async getEpisodes<
|
||||
T extends { includeMediaInfo?: boolean } | undefined = undefined
|
||||
>(
|
||||
public async getEpisodes(
|
||||
seriesID: string,
|
||||
seasonID: string,
|
||||
options?: T
|
||||
): Promise<EpisodeReturn<T>> {
|
||||
seasonID: string
|
||||
): Promise<JellyfinLibraryItem[]> {
|
||||
try {
|
||||
const episodeResponse = await this.get<any>(
|
||||
`/Shows/${seriesID}/Episodes`,
|
||||
{
|
||||
params: {
|
||||
seasonId: seasonID,
|
||||
...(options?.includeMediaInfo && { fields: 'MediaSources' }),
|
||||
},
|
||||
}
|
||||
`/Shows/${seriesID}/Episodes?seasonId=${seasonID}`
|
||||
);
|
||||
|
||||
return episodeResponse.Items.filter(
|
||||
|
||||
@@ -196,33 +196,16 @@ class WebhookAgent
|
||||
}
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (settings.options.authHeader) {
|
||||
headers.Authorization = settings.options.authHeader;
|
||||
}
|
||||
|
||||
if (
|
||||
settings.options.customHeaders &&
|
||||
settings.options.customHeaders.length > 0
|
||||
) {
|
||||
settings.options.customHeaders.forEach((header) => {
|
||||
if (header.key && header.value) {
|
||||
// Don't override Authorization header if it's already set via authHeader
|
||||
if (
|
||||
header.key.toLowerCase() !== 'authorization' ||
|
||||
!settings.options.authHeader
|
||||
) {
|
||||
headers[header.key] = header.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await axios.post(
|
||||
webhookUrl,
|
||||
this.buildPayload(type, payload),
|
||||
Object.keys(headers).length > 0 ? { headers } : undefined
|
||||
settings.options.authHeader
|
||||
? {
|
||||
headers: {
|
||||
Authorization: settings.options.authHeader,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -374,10 +374,9 @@ class JellyfinScanner {
|
||||
) ?? []
|
||||
).length;
|
||||
|
||||
const jellyfinSeasons = await this.jfClient.getSeasons(Id);
|
||||
|
||||
for (const season of seasons) {
|
||||
const matchedJellyfinSeason = jellyfinSeasons.find((md) => {
|
||||
const JellyfinSeasons = await this.jfClient.getSeasons(Id);
|
||||
const matchedJellyfinSeason = JellyfinSeasons.find((md) => {
|
||||
if (tvdbSeasonFromAnidb) {
|
||||
// In AniDB we don't have the concept of seasons,
|
||||
// we have multiple shows with only Season 1 (and sometimes a season with index 0 for specials).
|
||||
@@ -398,52 +397,38 @@ class JellyfinScanner {
|
||||
|
||||
// Check if we found the matching season and it has all the available episodes
|
||||
if (matchedJellyfinSeason) {
|
||||
// If we have a matched Jellyfin season, get its children metadata so we can check details
|
||||
const episodes = await this.jfClient.getEpisodes(
|
||||
Id,
|
||||
matchedJellyfinSeason.Id
|
||||
);
|
||||
|
||||
//Get count of episodes that are HD and 4K
|
||||
let totalStandard = 0;
|
||||
let total4k = 0;
|
||||
|
||||
if (!this.enable4kShow) {
|
||||
const episodes = await this.jfClient.getEpisodes(
|
||||
Id,
|
||||
matchedJellyfinSeason.Id
|
||||
);
|
||||
//use for loop to make sure this loop _completes_ in full
|
||||
//before the next section
|
||||
for (const episode of episodes) {
|
||||
let episodeCount = 1;
|
||||
|
||||
for (const episode of episodes) {
|
||||
let episodeCount = 1;
|
||||
|
||||
// count number of combined episodes
|
||||
if (
|
||||
episode.IndexNumber !== undefined &&
|
||||
episode.IndexNumberEnd !== undefined
|
||||
) {
|
||||
episodeCount =
|
||||
episode.IndexNumberEnd - episode.IndexNumber + 1;
|
||||
}
|
||||
|
||||
totalStandard += episodeCount;
|
||||
// count number of combined episodes
|
||||
if (
|
||||
episode.IndexNumber !== undefined &&
|
||||
episode.IndexNumberEnd !== undefined
|
||||
) {
|
||||
episodeCount =
|
||||
episode.IndexNumberEnd - episode.IndexNumber + 1;
|
||||
}
|
||||
} else {
|
||||
// 4K detection enabled - request media info to check resolution
|
||||
const episodes = await this.jfClient.getEpisodes(
|
||||
Id,
|
||||
matchedJellyfinSeason.Id,
|
||||
{ includeMediaInfo: true }
|
||||
);
|
||||
|
||||
for (const episode of episodes) {
|
||||
let episodeCount = 1;
|
||||
if (!this.enable4kShow) {
|
||||
totalStandard += episodeCount;
|
||||
} else {
|
||||
const ExtendedEpisodeData = await this.jfClient.getItemData(
|
||||
episode.Id
|
||||
);
|
||||
|
||||
// count number of combined episodes
|
||||
if (
|
||||
episode.IndexNumber !== undefined &&
|
||||
episode.IndexNumberEnd !== undefined
|
||||
) {
|
||||
episodeCount =
|
||||
episode.IndexNumberEnd - episode.IndexNumber + 1;
|
||||
}
|
||||
|
||||
// MediaSources field is included in response when includeMediaInfo is true
|
||||
// We iterate all MediaSources to detect if episode has both standard AND 4K versions
|
||||
episode.MediaSources?.some((MediaSource) => {
|
||||
ExtendedEpisodeData?.MediaSources?.some((MediaSource) => {
|
||||
return MediaSource.MediaStreams.some((MediaStream) => {
|
||||
if (MediaStream.Type === 'Video') {
|
||||
if ((MediaStream.Width ?? 0) >= 2000) {
|
||||
|
||||
@@ -275,7 +275,6 @@ export interface NotificationAgentWebhook extends NotificationAgentConfig {
|
||||
webhookUrl: string;
|
||||
jsonPayload: string;
|
||||
authHeader?: string;
|
||||
customHeaders?: { key: string; value: string }[];
|
||||
supportVariables?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -626,6 +626,76 @@ authRoutes.post('/local', async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
const mainUser = await userRepository.findOneOrFail({
|
||||
select: { id: true, plexToken: true, plexId: true },
|
||||
where: { id: 1 },
|
||||
});
|
||||
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
|
||||
|
||||
if (!user.plexId) {
|
||||
try {
|
||||
const plexUsersResponse = await mainPlexTv.getUsers();
|
||||
const account = plexUsersResponse.MediaContainer.User.find(
|
||||
(account) =>
|
||||
account.$.email &&
|
||||
account.$.email.toLowerCase() === user.email.toLowerCase()
|
||||
)?.$;
|
||||
|
||||
if (
|
||||
account &&
|
||||
(await mainPlexTv.checkUserAccess(parseInt(account.id)))
|
||||
) {
|
||||
logger.info(
|
||||
'Found matching Plex user; updating user with Plex data',
|
||||
{
|
||||
label: 'API',
|
||||
ip: req.ip,
|
||||
email: body.email,
|
||||
userId: user.id,
|
||||
plexId: account.id,
|
||||
plexUsername: account.username,
|
||||
}
|
||||
);
|
||||
|
||||
user.plexId = parseInt(account.id);
|
||||
user.avatar = account.thumb;
|
||||
user.email = account.email;
|
||||
user.plexUsername = account.username;
|
||||
user.userType = UserType.PLEX;
|
||||
|
||||
await userRepository.save(user);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong fetching Plex users', {
|
||||
label: 'API',
|
||||
errorMessage: e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
user.plexId &&
|
||||
user.plexId !== mainUser.plexId &&
|
||||
!(await mainPlexTv.checkUserAccess(user.plexId))
|
||||
) {
|
||||
logger.warn(
|
||||
'Failed sign-in attempt from Plex user without access to the media server',
|
||||
{
|
||||
label: 'API',
|
||||
account: {
|
||||
ip: req.ip,
|
||||
email: body.email,
|
||||
userId: user.id,
|
||||
plexId: user.plexId,
|
||||
},
|
||||
}
|
||||
);
|
||||
return next({
|
||||
status: 403,
|
||||
message: 'Access denied.',
|
||||
});
|
||||
}
|
||||
|
||||
// Set logged in session
|
||||
if (user && req.session) {
|
||||
req.session.userId = user.id;
|
||||
@@ -705,7 +775,7 @@ authRoutes.post('/logout', async (req, res, next) => {
|
||||
});
|
||||
return next({ status: 500, message: 'Failed to destroy session.' });
|
||||
}
|
||||
logger.debug('Successfully logged out user', {
|
||||
logger.info('Successfully logged out user', {
|
||||
label: 'Auth',
|
||||
userId,
|
||||
});
|
||||
|
||||
@@ -279,7 +279,6 @@ notificationRoutes.get('/webhook', (_req, res) => {
|
||||
'utf8'
|
||||
)
|
||||
),
|
||||
customHeaders: webhookSettings.options.customHeaders ?? [],
|
||||
supportVariables: webhookSettings.options.supportVariables ?? false,
|
||||
},
|
||||
};
|
||||
@@ -302,7 +301,6 @@ notificationRoutes.post('/webhook', async (req, res, next) => {
|
||||
),
|
||||
webhookUrl: req.body.options.webhookUrl,
|
||||
authHeader: req.body.options.authHeader,
|
||||
customHeaders: req.body.options.customHeaders ?? [],
|
||||
supportVariables: req.body.options.supportVariables ?? false,
|
||||
},
|
||||
};
|
||||
@@ -335,7 +333,6 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => {
|
||||
),
|
||||
webhookUrl: req.body.options.webhookUrl,
|
||||
authHeader: req.body.options.authHeader,
|
||||
customHeaders: req.body.options.customHeaders ?? [],
|
||||
supportVariables: req.body.options.supportVariables ?? false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -354,7 +354,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
src={
|
||||
title?.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -233,7 +233,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -25,7 +25,7 @@ const LabeledCheckbox: React.FC<LabeledCheckboxProps> = ({
|
||||
<Field type="checkbox" id={id} name={id} onChange={onChange} />
|
||||
</div>
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<label htmlFor="localLogin" className="block" aria-label={label}>
|
||||
<label htmlFor="localLogin" className="block">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-white">{label}</span>
|
||||
<span className="font-normal text-gray-400">{description}</span>
|
||||
|
||||
@@ -232,7 +232,7 @@ const IssueDetails = () => {
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -151,7 +151,7 @@ const IssueItem = ({ issue }: IssueItemProps) => {
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -490,7 +490,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -46,7 +46,7 @@ const NotificationType = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<label htmlFor={option.id} className="block" aria-label={option.name}>
|
||||
<label htmlFor={option.id} className="block">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-white">{option.name}</span>
|
||||
<span className="font-normal text-gray-400">
|
||||
|
||||
@@ -123,7 +123,7 @@ const PermissionOption = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<label htmlFor={option.id} className="block" aria-label={option.name}>
|
||||
<label htmlFor={option.id} className="block">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-white">{option.name}</span>
|
||||
<span className="font-normal text-gray-400">
|
||||
|
||||
@@ -292,7 +292,6 @@ const PersonDetails = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="lg:hidden">{mediaTypePicker}</div>
|
||||
{data.biography && (
|
||||
<div className="relative text-left">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
||||
@@ -315,6 +314,7 @@ const PersonDetails = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:hidden">{mediaTypePicker}</div>
|
||||
{data.knownForDepartment === 'Acting' ? [cast, crew] : [crew, cast]}
|
||||
{isLoading && <LoadingSpinner />}
|
||||
</>
|
||||
|
||||
@@ -617,7 +617,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -440,7 +440,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -450,7 +450,7 @@ const CollectionRequestModal = ({
|
||||
src={
|
||||
part.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -92,7 +92,8 @@ const SearchByNameModal = ({
|
||||
<CachedImage
|
||||
type="tvdb"
|
||||
src={
|
||||
item.remotePoster ?? '/images/seerr_poster_not_found.png'
|
||||
item.remotePoster ??
|
||||
'/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt={item.title}
|
||||
className="w-100 h-auto rounded-md"
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
TmdbGenre,
|
||||
TmdbKeywordSearchResponse,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
import type { GenreSliderItem } from '@server/interfaces/api/discoverInterfaces';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import type {
|
||||
Keyword,
|
||||
@@ -184,7 +185,9 @@ export const GenreSelector = ({
|
||||
}, [defaultValue, type]);
|
||||
|
||||
const loadGenreOptions = async (inputValue: string) => {
|
||||
const results = await axios.get<TmdbGenre[]>(`/api/v1/genres/${type}`);
|
||||
const results = await axios.get<GenreSliderItem[]>(
|
||||
`/api/v1/discover/genreslider/${type}`
|
||||
);
|
||||
|
||||
return results.data
|
||||
.map((result) => ({
|
||||
@@ -198,7 +201,7 @@ export const GenreSelector = ({
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
key={`genre-select-${type}-${defaultDataValue}`}
|
||||
key={`genre-select-${defaultDataValue}`}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
defaultValue={isMulti ? defaultDataValue : defaultDataValue?.[0]}
|
||||
|
||||
@@ -5,12 +5,7 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { isValidURL } from '@app/utils/urlValidationHelper';
|
||||
import {
|
||||
ArrowDownOnSquareIcon,
|
||||
BeakerIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
@@ -85,16 +80,6 @@ const messages = defineMessages(
|
||||
supportVariablesTip:
|
||||
'Available variables are documented in the webhook template variables section',
|
||||
authheader: 'Authorization Header',
|
||||
customHeaders: 'Custom Headers',
|
||||
customHeadersTip:
|
||||
'Add custom HTTP headers to include with webhook requests',
|
||||
customHeadersAdd: 'Add Header',
|
||||
customHeadersRemove: 'Remove',
|
||||
customHeadersKey: 'Header Name',
|
||||
customHeadersValue: 'Header Value',
|
||||
customHeadersIncomplete: 'All headers must have both name and value',
|
||||
customHeadersAuthConflict:
|
||||
'Cannot use both Authorization Header and custom Authorization header. Please remove one.',
|
||||
validationJsonPayloadRequired: 'You must provide a valid JSON payload',
|
||||
webhooksettingssaved: 'Webhook notification settings saved successfully!',
|
||||
webhooksettingsfailed: 'Webhook notification settings failed to save.',
|
||||
@@ -140,43 +125,6 @@ const NotificationsWebhook = () => {
|
||||
|
||||
supportVariables: Yup.boolean(),
|
||||
|
||||
customHeaders: Yup.array()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
key: Yup.string(),
|
||||
value: Yup.string(),
|
||||
})
|
||||
)
|
||||
.test(
|
||||
'complete-headers',
|
||||
intl.formatMessage(messages.customHeadersIncomplete),
|
||||
function (headers) {
|
||||
if (!headers || headers.length === 0) return true;
|
||||
return headers.every(
|
||||
(header) =>
|
||||
(!header.key || !header.key.trim()) ===
|
||||
(!header.value || !header.value.trim())
|
||||
);
|
||||
}
|
||||
)
|
||||
.test(
|
||||
'auth-conflict',
|
||||
intl.formatMessage(messages.customHeadersAuthConflict),
|
||||
function (headers) {
|
||||
const { authHeader } = this.parent;
|
||||
if (!authHeader || !headers || headers.length === 0) return true;
|
||||
|
||||
const hasCustomAuthHeader = headers.some(
|
||||
(header) =>
|
||||
header.key &&
|
||||
header.value &&
|
||||
header.key.toLowerCase() === 'authorization'
|
||||
);
|
||||
|
||||
return !hasCustomAuthHeader;
|
||||
}
|
||||
),
|
||||
|
||||
jsonPayload: Yup.string()
|
||||
.when('enabled', {
|
||||
is: true,
|
||||
@@ -211,7 +159,6 @@ const NotificationsWebhook = () => {
|
||||
webhookUrl: data.options.webhookUrl,
|
||||
jsonPayload: data.options.jsonPayload,
|
||||
authHeader: data.options.authHeader,
|
||||
customHeaders: data.options.customHeaders ?? [],
|
||||
supportVariables: data.options.supportVariables ?? false,
|
||||
}}
|
||||
validationSchema={NotificationsWebhookSchema}
|
||||
@@ -224,9 +171,6 @@ const NotificationsWebhook = () => {
|
||||
webhookUrl: values.webhookUrl,
|
||||
jsonPayload: JSON.stringify(values.jsonPayload),
|
||||
authHeader: values.authHeader,
|
||||
customHeaders: values.customHeaders.filter(
|
||||
(h: { key: string; value: string }) => h.key && h.value
|
||||
),
|
||||
supportVariables: values.supportVariables,
|
||||
},
|
||||
});
|
||||
@@ -285,9 +229,6 @@ const NotificationsWebhook = () => {
|
||||
webhookUrl: values.webhookUrl,
|
||||
jsonPayload: JSON.stringify(values.jsonPayload),
|
||||
authHeader: values.authHeader,
|
||||
customHeaders: values.customHeaders.filter(
|
||||
(h: { key: string; value: string }) => h.key && h.value
|
||||
),
|
||||
supportVariables: values.supportVariables ?? false,
|
||||
},
|
||||
});
|
||||
@@ -403,86 +344,6 @@ const NotificationsWebhook = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="customHeaders" className="text-label">
|
||||
{intl.formatMessage(messages.customHeaders)}
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.customHeadersTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="space-y-2">
|
||||
{values.customHeaders.map(
|
||||
(header: { key: string; value: string }, index: number) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
name={`customHeaders.${index}.key`}
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(
|
||||
messages.customHeadersKey
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
name={`customHeaders.${index}.value`}
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(
|
||||
messages.customHeadersValue
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
buttonType="danger"
|
||||
buttonSize="sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const newHeaders = values.customHeaders.filter(
|
||||
(
|
||||
_: { key: string; value: string },
|
||||
i: number
|
||||
) => i !== index
|
||||
);
|
||||
setFieldValue('customHeaders', newHeaders);
|
||||
}}
|
||||
title={intl.formatMessage(
|
||||
messages.customHeadersRemove
|
||||
)}
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
buttonType="default"
|
||||
buttonSize="sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setFieldValue('customHeaders', [
|
||||
...values.customHeaders,
|
||||
{ key: '', value: '' },
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{intl.formatMessage(messages.customHeadersAdd)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{errors.customHeaders &&
|
||||
touched.customHeaders &&
|
||||
typeof errors.customHeaders === 'string' && (
|
||||
<div className="error">{errors.customHeaders}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="webhook-json-payload" className="text-label">
|
||||
{intl.formatMessage(messages.customJson)}
|
||||
|
||||
@@ -337,13 +337,7 @@ const OverrideRuleModal = ({
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<GenreSelector
|
||||
type={
|
||||
values.radarrServiceId != null
|
||||
? 'movie'
|
||||
: values.sonarrServiceId != null
|
||||
? 'tv'
|
||||
: 'tv'
|
||||
}
|
||||
type={values.radarrServiceId ? 'movie' : 'tv'}
|
||||
defaultValue={values.genre}
|
||||
isMulti
|
||||
isDisabled={!isValidated || isTesting}
|
||||
|
||||
@@ -335,7 +335,7 @@ const TitleCard = ({
|
||||
src={
|
||||
image
|
||||
? `https://image.tmdb.org/t/p/w300_and_h450_face${image}`
|
||||
: `/images/seerr_poster_not_found_logo_top.png`
|
||||
: `/images/jellyseerr_poster_not_found_logo_top.png`
|
||||
}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
fill
|
||||
|
||||
@@ -532,7 +532,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
: '/images/seerr_poster_not_found.png'
|
||||
: '/images/jellyseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
|
||||
@@ -2,7 +2,6 @@ import { UserType } from '@server/constants/user';
|
||||
import type { PermissionCheckOptions } from '@server/lib/permissions';
|
||||
import { hasPermission, Permission } from '@server/lib/permissions';
|
||||
import type { NotificationAgentKey } from '@server/lib/settings';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { MutatorCallback } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
||||
@@ -57,21 +56,13 @@ export const useUser = ({
|
||||
id,
|
||||
initialData,
|
||||
}: { id?: number; initialData?: User } = {}): UserHookResponse => {
|
||||
const router = useRouter();
|
||||
const isAuthPage = /^\/(login|setup|resetpassword(?:\/|$))/.test(
|
||||
router.pathname
|
||||
);
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
mutate: revalidate,
|
||||
} = useSWR<User>(id ? `/api/v1/user/${id}` : `/api/v1/auth/me`, {
|
||||
fallbackData: initialData,
|
||||
refreshInterval: !isAuthPage ? 30000 : 0,
|
||||
revalidateOnFocus: !isAuthPage,
|
||||
revalidateOnMount: !isAuthPage,
|
||||
revalidateOnReconnect: !isAuthPage,
|
||||
refreshInterval: 30000,
|
||||
errorRetryInterval: 30000,
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
|
||||
@@ -681,14 +681,6 @@
|
||||
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration",
|
||||
"components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent",
|
||||
"components.Settings.Notifications.NotificationsWebhook.authheader": "Authorization Header",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeaders": "Custom Headers",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersAdd": "Add Header",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersAuthConflict": "Cannot use both Authorization Header and custom Authorization header. Please remove one.",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersIncomplete": "All headers must have both name and value",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersKey": "Header Name",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersRemove": "Remove",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersTip": "Add custom HTTP headers to include with webhook requests",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customHeadersValue": "Header Value",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload",
|
||||
"components.Settings.Notifications.NotificationsWebhook.resetPayload": "Reset to Default",
|
||||
"components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload reset successfully!",
|
||||
|
||||
@@ -63,7 +63,7 @@ class PlexOAuth {
|
||||
'X-Plex-Client-Identifier': clientId,
|
||||
'X-Plex-Model': 'Plex OAuth',
|
||||
'X-Plex-Platform': browser.getBrowserName(),
|
||||
'X-Plex-Platform-Version': browser.getBrowserVersion() || 'Unknown',
|
||||
'X-Plex-Platform-Version': browser.getBrowserVersion(),
|
||||
'X-Plex-Device': browser.getOSName(),
|
||||
'X-Plex-Device-Name': `${browser.getBrowserName()} (Seerr)`,
|
||||
'X-Plex-Device-Screen-Resolution':
|
||||
|
||||
Reference in New Issue
Block a user