From 1bc3f7be4b07211563a1e254c28ce51e1bc337a2 Mon Sep 17 00:00:00 2001 From: sct Date: Fri, 30 Oct 2020 14:38:19 +0000 Subject: [PATCH] feat(frontend): plex library scan --- overseerr-api.yml | 5 + server/job/plexsync.ts | 10 +- server/routes/settings.ts | 2 +- src/components/Settings/SettingsLayout.tsx | 2 +- src/components/Settings/SettingsPlex.tsx | 254 +++++++++++++++++---- src/i18n/locale/en.json | 23 ++ src/i18n/locale/ja.json | 23 ++ 7 files changed, 269 insertions(+), 50 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index 511920bc..c9b1cc72 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -816,6 +816,11 @@ paths: schema: type: boolean example: false + - in: query + name: start + schema: + type: boolean + example: false responses: '200': description: Status of Plex Sync diff --git a/server/job/plexsync.ts b/server/job/plexsync.ts index bb1c058f..b35f7160 100644 --- a/server/job/plexsync.ts +++ b/server/job/plexsync.ts @@ -13,6 +13,14 @@ const imdbRegex = new RegExp(/imdb:\/\/(tt[0-9]+)/); const tmdbRegex = new RegExp(/tmdb:\/\/([0-9]+)/); const plexRegex = new RegExp(/plex:\/\//); +export interface SyncStatus { + running: boolean; + progress: number; + total: number; + currentLibrary: Library; + libraries: Library[]; +} + class JobPlexSync { private tmdb: TheMovieDb; private plexClient: PlexAPI; @@ -161,7 +169,7 @@ class JobPlexSync { } } - public status() { + public status(): SyncStatus { return { running: this.running, progress: this.progress, diff --git a/server/routes/settings.ts b/server/routes/settings.ts index 0bcd4450..395e3838 100644 --- a/server/routes/settings.ts +++ b/server/routes/settings.ts @@ -106,7 +106,7 @@ settingsRoutes.get('/plex/library', async (req, res) => { settingsRoutes.get('/plex/sync', (req, res) => { if (req.query.cancel) { jobPlexSync.cancel(); - } else { + } else if (req.query.start) { jobPlexSync.run(); } diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx index 64b09523..f4b8471c 100644 --- a/src/components/Settings/SettingsLayout.tsx +++ b/src/components/Settings/SettingsLayout.tsx @@ -45,7 +45,7 @@ const SettingsLayout: React.FC = ({ children }) => { regex: /^\/settings(\/main)?$/, })} {settingsLink({ - text: 'Plex Settings', + text: 'Plex', route: '/settings/plex', regex: /^\/settings\/plex/, })} diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index 44d9b857..abe02878 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -1,16 +1,51 @@ import React, { useState } from 'react'; import LoadingSpinner from '../Common/LoadingSpinner'; import type { PlexSettings } from '../../../server/lib/settings'; +import type { SyncStatus } from '../../../server/job/plexsync'; import useSWR from 'swr'; import { useFormik } from 'formik'; import Button from '../Common/Button'; import axios from 'axios'; import LibraryItem from './LibraryItem'; +import Badge from '../Common/Badge'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +const messages = defineMessages({ + plexsettings: 'Plex Settings', + plexsettingsDescription: + 'Configure the settings for your Plex server. Overseerr uses your Plex server to scan your library at an interval and see what content is available.', + servername: 'Server Name (Automatically Set)', + servernamePlaceholder: 'Plex Server Name', + hostname: 'Hostname/IP', + port: 'Port', + save: 'Save Changes', + saving: 'Saving...', + plexlibraries: 'Plex Libraries', + plexlibrariesDescription: + 'These are the libraries Overseerr will scan for titles. If you see no libraries listed, you will need to run at least one sync by clicking the button below. You must first configure and save your plex connection settings before you will be able to retrieve your libraries.', + syncing: 'Syncing', + sync: 'Sync Plex Libraries', + manualscan: 'Manual Library Scan', + manualscanDescription: + "Normally, this will only be run once every 6 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one time full manual library scan is recommended!", + notrunning: 'Not Running', + currentlibrary: 'Current Library: {name}', + librariesRemaining: 'Libraries Remaining: {count}', + startscan: 'Start Scan', + cancelscan: 'Cancel Scan', +}); const SettingsPlex: React.FC = () => { + const intl = useIntl(); const { data, error, revalidate } = useSWR( '/api/v1/settings/plex' ); + const { data: dataSync, revalidate: revalidateSync } = useSWR( + '/api/v1/settings/plex/sync', + { + refreshInterval: 1000, + } + ); const [isSyncing, setIsSyncing] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [submitError, setSubmitError] = useState(null); @@ -21,6 +56,7 @@ const SettingsPlex: React.FC = () => { }, enableReinitialize: true, onSubmit: async (values) => { + setSubmitError(null); setIsUpdating(true); try { await axios.post('/api/v1/settings/plex', { @@ -30,7 +66,7 @@ const SettingsPlex: React.FC = () => { revalidate(); } catch (e) { - setSubmitError(e.message); + setSubmitError(e.response.data.message); } finally { setIsUpdating(false); } @@ -55,6 +91,24 @@ const SettingsPlex: React.FC = () => { revalidate(); }; + const startScan = async () => { + await axios.get('/api/v1/settings/plex/sync', { + params: { + start: true, + }, + }); + revalidateSync(); + }; + + const cancelScan = async () => { + await axios.get('/api/v1/settings/plex/sync', { + params: { + cancel: true, + }, + }); + revalidateSync(); + }; + const toggleLibrary = async (libraryId: string) => { setIsSyncing(true); if (activeLibraries.includes(libraryId)) { @@ -84,29 +138,34 @@ const SettingsPlex: React.FC = () => { <>

- Plex Settings +

- Configure the settings for your Plex server. Overseerr uses your Plex - server to scan your library at an interval and see what content is - available. +

+ {submitError && ( +
+ {submitError} +
+ )}
{ htmlFor="hostname" className="block text-sm font-medium leading-5 text-cool-gray-400 sm:mt-px sm:pt-2" > - Hostname/IP +
@@ -139,7 +198,7 @@ const SettingsPlex: React.FC = () => { htmlFor="port" className="block text-sm font-medium leading-5 text-cool-gray-400 sm:mt-px sm:pt-2" > - Port +
@@ -159,51 +218,152 @@ const SettingsPlex: React.FC = () => {
-
-

- Plex Libraries -

-

- These are the libraries Overseerr will scan for titles. If you see - no libraries listed, you will need to run at least one sync by - clicking the button below. You must first configure and save your - plex connection settings before you will be able to retrieve your - libraries. -

-
- +
+
    + {data?.libraries.map((library) => ( + toggleLibrary(library.id)} + /> + ))} +
+
+
+

+ +

+

+ +

+
+
+
+ {dataSync?.running && ( +
- - {isSyncing ? 'Syncing...' : 'Sync Plex Libraries'} - + )} +
+ + {dataSync?.running + ? `${dataSync.progress} of ${dataSync.total}` + : 'Not running'} + +
+
+
+ {dataSync?.running && ( + <> +
+ + + +
+
+ + + library.id === dataSync.currentLibrary.id + ) + 1 + ).length, + }} + /> + +
+ + )} +
+ {!dataSync?.running && ( + + )} + + {dataSync?.running && ( + + )} +
+
-
    - {data?.libraries.map((library) => ( - toggleLibrary(library.id)} - /> - ))} -
- +
); }; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 7b1fd4f5..352a8b97 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -26,21 +26,44 @@ "components.MovieDetails.status": "Status", "components.MovieDetails.unavailable": "Unavailable", "components.MovieDetails.userrating": "User Rating", + "components.MovieDetails.viewrequest": "View Request", "components.PendingRequest.approve": "Approve", "components.PendingRequest.decline": "Decline", "components.PendingRequest.pendingdescription": "This title was requested by {username} ({email}) on {date}", "components.PendingRequest.pendingtitle": "Pending Request", "components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?", "components.RequestModal.requestadmin": "Your request will be immediately approved.", + "components.Settings.cancelscan": "Cancel Scan", + "components.Settings.currentlibrary": "Current Library: {name}", + "components.Settings.hostname": "Hostname/IP", + "components.Settings.librariesRemaining": "Libraries Remaining: {count}", + "components.Settings.manualscan": "Manual Library Scan", + "components.Settings.manualscanDescription": "Normally, this will only be run once every 6 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one time full manual library scan is recommended!", + "components.Settings.notrunning": "Not Running", + "components.Settings.plexlibraries": "Plex Libraries", + "components.Settings.plexlibrariesDescription": "These are the libraries Overseerr will scan for titles. If you see no libraries listed, you will need to run at least one sync by clicking the button below. You must first configure and save your plex connection settings before you will be able to retrieve your libraries.", + "components.Settings.plexsettings": "Plex Settings", + "components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr uses your Plex server to scan your library at an interval and see what content is available.", + "components.Settings.port": "Port", + "components.Settings.save": "Save Changes", + "components.Settings.saving": "Saving...", + "components.Settings.servername": "Server Name (Automatically Set)", + "components.Settings.servernamePlaceholder": "Plex Server Name", + "components.Settings.startscan": "Start Scan", + "components.Settings.sync": "Sync Plex Libraries", + "components.Settings.syncing": "Syncing", + "components.TvDetails.approverequests": "Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}", "components.TvDetails.available": "Available", "components.TvDetails.cancelrequest": "Cancel Request", "components.TvDetails.cast": "Cast", + "components.TvDetails.declinerequests": "Decline {requestCount} {requestCount, plural, one {Request} other {Requests}}", "components.TvDetails.originallanguage": "Original Language", "components.TvDetails.overview": "Overview", "components.TvDetails.overviewunavailable": "Overview unavailable", "components.TvDetails.pending": "Pending", "components.TvDetails.recommendations": "Recommendations", "components.TvDetails.request": "Request", + "components.TvDetails.requestmore": "Request More", "components.TvDetails.similar": "Similar Series", "components.TvDetails.status": "Status", "components.TvDetails.unavailable": "Unavailable", diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index 45f5fa13..7fefa71d 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -26,21 +26,44 @@ "components.MovieDetails.status": "状態", "components.MovieDetails.unavailable": "", "components.MovieDetails.userrating": "ユーザー評価", + "components.MovieDetails.viewrequest": "", "components.PendingRequest.approve": "", "components.PendingRequest.decline": "", "components.PendingRequest.pendingdescription": "", "components.PendingRequest.pendingtitle": "", "components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?", "components.RequestModal.requestadmin": "このリクエストが今すぐ承認致します。よろしいですか?", + "components.Settings.cancelscan": "", + "components.Settings.currentlibrary": "", + "components.Settings.hostname": "", + "components.Settings.librariesRemaining": "", + "components.Settings.manualscan": "", + "components.Settings.manualscanDescription": "", + "components.Settings.notrunning": "", + "components.Settings.plexlibraries": "", + "components.Settings.plexlibrariesDescription": "", + "components.Settings.plexsettings": "", + "components.Settings.plexsettingsDescription": "", + "components.Settings.port": "", + "components.Settings.save": "", + "components.Settings.saving": "", + "components.Settings.servername": "", + "components.Settings.servernamePlaceholder": "", + "components.Settings.startscan": "", + "components.Settings.sync": "", + "components.Settings.syncing": "", + "components.TvDetails.approverequests": "", "components.TvDetails.available": "", "components.TvDetails.cancelrequest": "", "components.TvDetails.cast": "", + "components.TvDetails.declinerequests": "", "components.TvDetails.originallanguage": "", "components.TvDetails.overview": "", "components.TvDetails.overviewunavailable": "", "components.TvDetails.pending": "", "components.TvDetails.recommendations": "", "components.TvDetails.request": "", + "components.TvDetails.requestmore": "", "components.TvDetails.similar": "", "components.TvDetails.status": "", "components.TvDetails.unavailable": "",