From 56d5356f1eb810b6159e2999818756cb112899ef Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 11 Feb 2021 21:54:00 -0500 Subject: [PATCH] Show Indexer Status on Indexer Table --- frontend/src/Components/Page/PageConnector.js | 14 ++++++ .../Indexer/Index/MovieIndexItemConnector.js | 15 +++++- .../Indexer/Index/Table/IndexerStatusCell.js | 16 +++++++ .../src/Indexer/Index/Table/MovieIndexRow.js | 10 +++- frontend/src/Store/Actions/index.js | 2 + .../src/Store/Actions/indexerStatusActions.js | 46 +++++++++++++++++++ .../Selectors/createIndexerStatusSelector.js | 14 ++++++ .../Indexers/IndexerResource.cs | 1 + .../Indexers/IndexerStatusModule.cs | 23 ++++++++++ .../Indexers/IndexerStatusResource.cs | 36 +++++++++++++++ 10 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 frontend/src/Store/Actions/indexerStatusActions.js create mode 100644 frontend/src/Store/Selectors/createIndexerStatusSelector.js create mode 100644 src/Prowlarr.Api.V1/Indexers/IndexerStatusModule.cs create mode 100644 src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index 889f559af..5f66ad81e 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -6,6 +6,7 @@ import { createSelector } from 'reselect'; import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchIndexers } from 'Store/Actions/indexerActions'; +import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions'; import { fetchIndexerCategories, fetchIndexerFlags, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions'; import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchTags } from 'Store/Actions/tagActions'; @@ -48,6 +49,7 @@ const selectIsPopulated = createSelector( (state) => state.settings.ui.isPopulated, (state) => state.settings.languages.isPopulated, (state) => state.indexers.isPopulated, + (state) => state.indexerStatus.isPopulated, (state) => state.settings.indexerCategories.isPopulated, (state) => state.settings.indexerFlags.isPopulated, (state) => state.system.status.isPopulated, @@ -57,6 +59,7 @@ const selectIsPopulated = createSelector( uiSettingsIsPopulated, languagesIsPopulated, indexersIsPopulated, + indexerStatusIsPopulated, indexerCategoriesIsPopulated, indexerFlagsIsPopulated, systemStatusIsPopulated @@ -67,6 +70,7 @@ const selectIsPopulated = createSelector( uiSettingsIsPopulated && languagesIsPopulated && indexersIsPopulated && + indexerStatusIsPopulated && indexerCategoriesIsPopulated && indexerFlagsIsPopulated && systemStatusIsPopulated @@ -80,6 +84,7 @@ const selectErrors = createSelector( (state) => state.settings.ui.error, (state) => state.settings.languages.error, (state) => state.indexers.error, + (state) => state.indexerStatus.error, (state) => state.settings.indexerCategories.error, (state) => state.settings.indexerFlags.error, (state) => state.system.status.error, @@ -89,6 +94,7 @@ const selectErrors = createSelector( uiSettingsError, languagesError, indexersError, + indexerStatusError, indexerCategoriesError, indexerFlagsError, systemStatusError @@ -99,6 +105,7 @@ const selectErrors = createSelector( uiSettingsError || languagesError || indexersError || + indexerStatusError || indexerCategoriesError || indexerFlagsError || systemStatusError @@ -111,6 +118,7 @@ const selectErrors = createSelector( uiSettingsError, languagesError, indexersError, + indexerStatusError, indexerCategoriesError, indexerFlagsError, systemStatusError @@ -157,6 +165,9 @@ function createMapDispatchToProps(dispatch, props) { dispatchFetchIndexers() { dispatch(fetchIndexers()); }, + dispatchFetchIndexerStatus() { + dispatch(fetchIndexerStatus()); + }, dispatchFetchIndexerCategories() { dispatch(fetchIndexerCategories()); }, @@ -197,6 +208,7 @@ class PageConnector extends Component { this.props.dispatchFetchTags(); this.props.dispatchFetchLanguages(); this.props.dispatchFetchIndexers(); + this.props.dispatchFetchIndexerStatus(); this.props.dispatchFetchIndexerCategories(); this.props.dispatchFetchIndexerFlags(); this.props.dispatchFetchUISettings(); @@ -221,6 +233,7 @@ class PageConnector extends Component { dispatchFetchTags, dispatchFetchLanguages, dispatchFetchIndexers, + dispatchFetchIndexerStatus, dispatchFetchIndexerCategories, dispatchFetchIndexerFlags, dispatchFetchUISettings, @@ -260,6 +273,7 @@ PageConnector.propTypes = { dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchLanguages: PropTypes.func.isRequired, dispatchFetchIndexers: PropTypes.func.isRequired, + dispatchFetchIndexerStatus: PropTypes.func.isRequired, dispatchFetchIndexerCategories: PropTypes.func.isRequired, dispatchFetchIndexerFlags: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired, diff --git a/frontend/src/Indexer/Index/MovieIndexItemConnector.js b/frontend/src/Indexer/Index/MovieIndexItemConnector.js index 6a573d3a6..ef1f6c002 100644 --- a/frontend/src/Indexer/Index/MovieIndexItemConnector.js +++ b/frontend/src/Indexer/Index/MovieIndexItemConnector.js @@ -6,6 +6,8 @@ import * as commandNames from 'Commands/commandNames'; import { executeCommand } from 'Store/Actions/commandActions'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; import createIndexerSelector from 'Store/Selectors/createIndexerSelector'; +import createIndexerStatusSelector from 'Store/Selectors/createIndexerStatusSelector'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; function selectShowSearchAction() { return createSelector( @@ -19,12 +21,16 @@ function selectShowSearchAction() { function createMapStateToProps() { return createSelector( createIndexerSelector(), + createIndexerStatusSelector(), selectShowSearchAction(), createExecutingCommandsSelector(), + createUISettingsSelector(), ( movie, + status, showSearchAction, - executingCommands + executingCommands, + uiSettings ) => { // If a movie is deleted this selector may fire before the parent @@ -32,6 +38,8 @@ function createMapStateToProps() { // we want to return early here and again in the render function to avoid // trying to show a movie that has no information available. + console.log(status); + if (!movie) { return {}; } @@ -52,9 +60,12 @@ function createMapStateToProps() { return { ...movie, + status, showSearchAction, isRefreshingMovie, - isSearchingMovie + isSearchingMovie, + longDateFormat: uiSettings.longDateFormat, + timeFormat: uiSettings.timeFormat }; } ); diff --git a/frontend/src/Indexer/Index/Table/IndexerStatusCell.js b/frontend/src/Indexer/Index/Table/IndexerStatusCell.js index 4fd53220c..2c3191aa2 100644 --- a/frontend/src/Indexer/Index/Table/IndexerStatusCell.js +++ b/frontend/src/Indexer/Index/Table/IndexerStatusCell.js @@ -3,12 +3,16 @@ import React from 'react'; import Icon from 'Components/Icon'; import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell'; import { icons, kinds } from 'Helpers/Props'; +import formatDateTime from 'Utilities/Date/formatDateTime'; import styles from './IndexerStatusCell.css'; function IndexerStatusCell(props) { const { className, enabled, + status, + longDateFormat, + timeFormat, component: Component, ...otherProps } = props; @@ -26,6 +30,15 @@ function IndexerStatusCell(props) { title={enabled ? 'Indexer is Enabled' : 'Indexer is Disabled'} /> } + { + status && + + } ); } @@ -33,6 +46,9 @@ function IndexerStatusCell(props) { IndexerStatusCell.propTypes = { className: PropTypes.string.isRequired, enabled: PropTypes.bool.isRequired, + status: PropTypes.object, + longDateFormat: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired, component: PropTypes.elementType }; diff --git a/frontend/src/Indexer/Index/Table/MovieIndexRow.js b/frontend/src/Indexer/Index/Table/MovieIndexRow.js index c220b3944..9cf216bf2 100644 --- a/frontend/src/Indexer/Index/Table/MovieIndexRow.js +++ b/frontend/src/Indexer/Index/Table/MovieIndexRow.js @@ -68,9 +68,12 @@ class MovieIndexRow extends Component { protocol, privacy, priority, + status, added, capabilities, columns, + longDateFormat, + timeFormat, isMovieEditorActive, isSelected, onSelectedChange @@ -113,6 +116,8 @@ class MovieIndexRow extends Component { className={styles[column.name]} enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch} status={status} + longDateFormat={longDateFormat} + timeFormat={timeFormat} component={VirtualTableRowCell} /> ); @@ -253,6 +258,7 @@ MovieIndexRow.propTypes = { enableRss: PropTypes.bool.isRequired, enableAutomaticSearch: PropTypes.bool.isRequired, enableInteractiveSearch: PropTypes.bool.isRequired, + status: PropTypes.object, capabilities: PropTypes.object.isRequired, added: PropTypes.string.isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, @@ -260,7 +266,9 @@ MovieIndexRow.propTypes = { isSearchingMovie: PropTypes.bool.isRequired, isMovieEditorActive: PropTypes.bool.isRequired, isSelected: PropTypes.bool, - onSelectedChange: PropTypes.func.isRequired + onSelectedChange: PropTypes.func.isRequired, + longDateFormat: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired }; MovieIndexRow.defaultProps = { diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index ff8981a58..cdb94861e 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -6,6 +6,7 @@ import * as history from './historyActions'; import * as indexers from './indexerActions'; import * as indexerIndex from './indexerIndexActions'; import * as indexerStats from './indexerStatsActions'; +import * as indexerStatus from './indexerStatusActions'; import * as movies from './movieActions'; import * as oAuth from './oAuthActions'; import * as paths from './pathActions'; @@ -29,6 +30,7 @@ export default [ indexers, indexerIndex, indexerStats, + indexerStatus, settings, system, tags diff --git a/frontend/src/Store/Actions/indexerStatusActions.js b/frontend/src/Store/Actions/indexerStatusActions.js new file mode 100644 index 000000000..07ba81ff6 --- /dev/null +++ b/frontend/src/Store/Actions/indexerStatusActions.js @@ -0,0 +1,46 @@ +import { createThunk, handleThunks } from 'Store/thunks'; +import createFetchHandler from './Creators/createFetchHandler'; +import createHandleActions from './Creators/createHandleActions'; + +// +// Variables + +export const section = 'indexerStatus'; + +// +// State + +export const defaultState = { + isFetching: false, + isPopulated: false, + error: null, + items: [], + + details: { + isFetching: false, + isPopulated: false, + error: null, + items: [] + } +}; + +// +// Actions Types + +export const FETCH_INDEXER_STATUS = 'indexerStatus/fetchIndexerStatus'; + +// +// Action Creators + +export const fetchIndexerStatus = createThunk(FETCH_INDEXER_STATUS); + +// +// Action Handlers + +export const actionHandlers = handleThunks({ + [FETCH_INDEXER_STATUS]: createFetchHandler(section, '/indexerStatus') +}); + +// +// Reducers +export const reducers = createHandleActions({}, defaultState, section); diff --git a/frontend/src/Store/Selectors/createIndexerStatusSelector.js b/frontend/src/Store/Selectors/createIndexerStatusSelector.js new file mode 100644 index 000000000..e25d618fe --- /dev/null +++ b/frontend/src/Store/Selectors/createIndexerStatusSelector.js @@ -0,0 +1,14 @@ +import _ from 'lodash'; +import { createSelector } from 'reselect'; + +function createIndexerStatusSelector() { + return createSelector( + (state, { indexerId }) => indexerId, + (state) => state.indexerStatus.items, + (indexerId, indexerStatus) => { + return _.find(indexerStatus, { indexerId }); + } + ); +} + +export default createIndexerStatusSelector; diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs index beae8724b..c990a2813 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs @@ -20,6 +20,7 @@ namespace Prowlarr.Api.V1.Indexers public IndexerCapabilityResource Capabilities { get; set; } public int Priority { get; set; } public DateTime Added { get; set; } + public IndexerStatusResource Status { get; set; } } public class IndexerResourceMapper : ProviderResourceMapper diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerStatusModule.cs b/src/Prowlarr.Api.V1/Indexers/IndexerStatusModule.cs new file mode 100644 index 000000000..017a107c1 --- /dev/null +++ b/src/Prowlarr.Api.V1/Indexers/IndexerStatusModule.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using NzbDrone.Core.Indexers; +using Prowlarr.Http; + +namespace Prowlarr.Api.V1.Indexers +{ + public class IndexerStatusModule : ProwlarrRestModule + { + private readonly IIndexerStatusService _indexerStatusService; + + public IndexerStatusModule(IIndexerStatusService indexerStatusService) + { + _indexerStatusService = indexerStatusService; + + GetResourceAll = GetAll; + } + + private List GetAll() + { + return _indexerStatusService.GetBlockedProviders().ToResource(); + } + } +} diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs new file mode 100644 index 000000000..e624440a1 --- /dev/null +++ b/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Indexers; +using Prowlarr.Http.REST; + +namespace Prowlarr.Api.V1.Indexers +{ + public class IndexerStatusResource : RestResource + { + public int IndexerId { get; set; } + public DateTime? DisabledTill { get; set; } + } + + public static class IndexerStatusResourceMapper + { + public static IndexerStatusResource ToResource(this IndexerStatus model) + { + if (model == null) + { + return null; + } + + return new IndexerStatusResource + { + IndexerId = model.ProviderId, + DisabledTill = model.DisabledTill + }; + } + + public static List ToResource(this IEnumerable models) + { + return models.Select(ToResource).ToList(); + } + } +}