Show Indexer Status on Indexer Table

This commit is contained in:
Qstick
2021-02-11 21:54:00 -05:00
parent a41ae141cd
commit 56d5356f1e
10 changed files with 174 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import { createSelector } from 'reselect';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchIndexers } from 'Store/Actions/indexerActions'; import { fetchIndexers } from 'Store/Actions/indexerActions';
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
import { fetchIndexerCategories, fetchIndexerFlags, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions'; import { fetchIndexerCategories, fetchIndexerFlags, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions'; import { fetchTags } from 'Store/Actions/tagActions';
@@ -48,6 +49,7 @@ const selectIsPopulated = createSelector(
(state) => state.settings.ui.isPopulated, (state) => state.settings.ui.isPopulated,
(state) => state.settings.languages.isPopulated, (state) => state.settings.languages.isPopulated,
(state) => state.indexers.isPopulated, (state) => state.indexers.isPopulated,
(state) => state.indexerStatus.isPopulated,
(state) => state.settings.indexerCategories.isPopulated, (state) => state.settings.indexerCategories.isPopulated,
(state) => state.settings.indexerFlags.isPopulated, (state) => state.settings.indexerFlags.isPopulated,
(state) => state.system.status.isPopulated, (state) => state.system.status.isPopulated,
@@ -57,6 +59,7 @@ const selectIsPopulated = createSelector(
uiSettingsIsPopulated, uiSettingsIsPopulated,
languagesIsPopulated, languagesIsPopulated,
indexersIsPopulated, indexersIsPopulated,
indexerStatusIsPopulated,
indexerCategoriesIsPopulated, indexerCategoriesIsPopulated,
indexerFlagsIsPopulated, indexerFlagsIsPopulated,
systemStatusIsPopulated systemStatusIsPopulated
@@ -67,6 +70,7 @@ const selectIsPopulated = createSelector(
uiSettingsIsPopulated && uiSettingsIsPopulated &&
languagesIsPopulated && languagesIsPopulated &&
indexersIsPopulated && indexersIsPopulated &&
indexerStatusIsPopulated &&
indexerCategoriesIsPopulated && indexerCategoriesIsPopulated &&
indexerFlagsIsPopulated && indexerFlagsIsPopulated &&
systemStatusIsPopulated systemStatusIsPopulated
@@ -80,6 +84,7 @@ const selectErrors = createSelector(
(state) => state.settings.ui.error, (state) => state.settings.ui.error,
(state) => state.settings.languages.error, (state) => state.settings.languages.error,
(state) => state.indexers.error, (state) => state.indexers.error,
(state) => state.indexerStatus.error,
(state) => state.settings.indexerCategories.error, (state) => state.settings.indexerCategories.error,
(state) => state.settings.indexerFlags.error, (state) => state.settings.indexerFlags.error,
(state) => state.system.status.error, (state) => state.system.status.error,
@@ -89,6 +94,7 @@ const selectErrors = createSelector(
uiSettingsError, uiSettingsError,
languagesError, languagesError,
indexersError, indexersError,
indexerStatusError,
indexerCategoriesError, indexerCategoriesError,
indexerFlagsError, indexerFlagsError,
systemStatusError systemStatusError
@@ -99,6 +105,7 @@ const selectErrors = createSelector(
uiSettingsError || uiSettingsError ||
languagesError || languagesError ||
indexersError || indexersError ||
indexerStatusError ||
indexerCategoriesError || indexerCategoriesError ||
indexerFlagsError || indexerFlagsError ||
systemStatusError systemStatusError
@@ -111,6 +118,7 @@ const selectErrors = createSelector(
uiSettingsError, uiSettingsError,
languagesError, languagesError,
indexersError, indexersError,
indexerStatusError,
indexerCategoriesError, indexerCategoriesError,
indexerFlagsError, indexerFlagsError,
systemStatusError systemStatusError
@@ -157,6 +165,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchIndexers() { dispatchFetchIndexers() {
dispatch(fetchIndexers()); dispatch(fetchIndexers());
}, },
dispatchFetchIndexerStatus() {
dispatch(fetchIndexerStatus());
},
dispatchFetchIndexerCategories() { dispatchFetchIndexerCategories() {
dispatch(fetchIndexerCategories()); dispatch(fetchIndexerCategories());
}, },
@@ -197,6 +208,7 @@ class PageConnector extends Component {
this.props.dispatchFetchTags(); this.props.dispatchFetchTags();
this.props.dispatchFetchLanguages(); this.props.dispatchFetchLanguages();
this.props.dispatchFetchIndexers(); this.props.dispatchFetchIndexers();
this.props.dispatchFetchIndexerStatus();
this.props.dispatchFetchIndexerCategories(); this.props.dispatchFetchIndexerCategories();
this.props.dispatchFetchIndexerFlags(); this.props.dispatchFetchIndexerFlags();
this.props.dispatchFetchUISettings(); this.props.dispatchFetchUISettings();
@@ -221,6 +233,7 @@ class PageConnector extends Component {
dispatchFetchTags, dispatchFetchTags,
dispatchFetchLanguages, dispatchFetchLanguages,
dispatchFetchIndexers, dispatchFetchIndexers,
dispatchFetchIndexerStatus,
dispatchFetchIndexerCategories, dispatchFetchIndexerCategories,
dispatchFetchIndexerFlags, dispatchFetchIndexerFlags,
dispatchFetchUISettings, dispatchFetchUISettings,
@@ -260,6 +273,7 @@ PageConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchLanguages: PropTypes.func.isRequired, dispatchFetchLanguages: PropTypes.func.isRequired,
dispatchFetchIndexers: PropTypes.func.isRequired, dispatchFetchIndexers: PropTypes.func.isRequired,
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
dispatchFetchIndexerCategories: PropTypes.func.isRequired, dispatchFetchIndexerCategories: PropTypes.func.isRequired,
dispatchFetchIndexerFlags: PropTypes.func.isRequired, dispatchFetchIndexerFlags: PropTypes.func.isRequired,
dispatchFetchUISettings: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired,

View File

@@ -6,6 +6,8 @@ import * as commandNames from 'Commands/commandNames';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import createIndexerSelector from 'Store/Selectors/createIndexerSelector'; import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
import createIndexerStatusSelector from 'Store/Selectors/createIndexerStatusSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
function selectShowSearchAction() { function selectShowSearchAction() {
return createSelector( return createSelector(
@@ -19,12 +21,16 @@ function selectShowSearchAction() {
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createIndexerSelector(), createIndexerSelector(),
createIndexerStatusSelector(),
selectShowSearchAction(), selectShowSearchAction(),
createExecutingCommandsSelector(), createExecutingCommandsSelector(),
createUISettingsSelector(),
( (
movie, movie,
status,
showSearchAction, showSearchAction,
executingCommands executingCommands,
uiSettings
) => { ) => {
// If a movie is deleted this selector may fire before the parent // 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 // we want to return early here and again in the render function to avoid
// trying to show a movie that has no information available. // trying to show a movie that has no information available.
console.log(status);
if (!movie) { if (!movie) {
return {}; return {};
} }
@@ -52,9 +60,12 @@ function createMapStateToProps() {
return { return {
...movie, ...movie,
status,
showSearchAction, showSearchAction,
isRefreshingMovie, isRefreshingMovie,
isSearchingMovie isSearchingMovie,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
}; };
} }
); );

View File

@@ -3,12 +3,16 @@ import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import formatDateTime from 'Utilities/Date/formatDateTime';
import styles from './IndexerStatusCell.css'; import styles from './IndexerStatusCell.css';
function IndexerStatusCell(props) { function IndexerStatusCell(props) {
const { const {
className, className,
enabled, enabled,
status,
longDateFormat,
timeFormat,
component: Component, component: Component,
...otherProps ...otherProps
} = props; } = props;
@@ -26,6 +30,15 @@ function IndexerStatusCell(props) {
title={enabled ? 'Indexer is Enabled' : 'Indexer is Disabled'} title={enabled ? 'Indexer is Enabled' : 'Indexer is Disabled'}
/> />
} }
{
status &&
<Icon
className={styles.statusIcon}
kind={kinds.DANGER}
name={icons.WARNING}
title={`Indexer is Disabled due to failures until ${formatDateTime(status.disabledTill, longDateFormat, timeFormat)}`}
/>
}
</Component> </Component>
); );
} }
@@ -33,6 +46,9 @@ function IndexerStatusCell(props) {
IndexerStatusCell.propTypes = { IndexerStatusCell.propTypes = {
className: PropTypes.string.isRequired, className: PropTypes.string.isRequired,
enabled: PropTypes.bool.isRequired, enabled: PropTypes.bool.isRequired,
status: PropTypes.object,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
component: PropTypes.elementType component: PropTypes.elementType
}; };

View File

@@ -68,9 +68,12 @@ class MovieIndexRow extends Component {
protocol, protocol,
privacy, privacy,
priority, priority,
status,
added, added,
capabilities, capabilities,
columns, columns,
longDateFormat,
timeFormat,
isMovieEditorActive, isMovieEditorActive,
isSelected, isSelected,
onSelectedChange onSelectedChange
@@ -113,6 +116,8 @@ class MovieIndexRow extends Component {
className={styles[column.name]} className={styles[column.name]}
enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch} enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch}
status={status} status={status}
longDateFormat={longDateFormat}
timeFormat={timeFormat}
component={VirtualTableRowCell} component={VirtualTableRowCell}
/> />
); );
@@ -253,6 +258,7 @@ MovieIndexRow.propTypes = {
enableRss: PropTypes.bool.isRequired, enableRss: PropTypes.bool.isRequired,
enableAutomaticSearch: PropTypes.bool.isRequired, enableAutomaticSearch: PropTypes.bool.isRequired,
enableInteractiveSearch: PropTypes.bool.isRequired, enableInteractiveSearch: PropTypes.bool.isRequired,
status: PropTypes.object,
capabilities: PropTypes.object.isRequired, capabilities: PropTypes.object.isRequired,
added: PropTypes.string.isRequired, added: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired,
@@ -260,7 +266,9 @@ MovieIndexRow.propTypes = {
isSearchingMovie: PropTypes.bool.isRequired, isSearchingMovie: PropTypes.bool.isRequired,
isMovieEditorActive: PropTypes.bool.isRequired, isMovieEditorActive: PropTypes.bool.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired onSelectedChange: PropTypes.func.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired
}; };
MovieIndexRow.defaultProps = { MovieIndexRow.defaultProps = {

View File

@@ -6,6 +6,7 @@ import * as history from './historyActions';
import * as indexers from './indexerActions'; import * as indexers from './indexerActions';
import * as indexerIndex from './indexerIndexActions'; import * as indexerIndex from './indexerIndexActions';
import * as indexerStats from './indexerStatsActions'; import * as indexerStats from './indexerStatsActions';
import * as indexerStatus from './indexerStatusActions';
import * as movies from './movieActions'; import * as movies from './movieActions';
import * as oAuth from './oAuthActions'; import * as oAuth from './oAuthActions';
import * as paths from './pathActions'; import * as paths from './pathActions';
@@ -29,6 +30,7 @@ export default [
indexers, indexers,
indexerIndex, indexerIndex,
indexerStats, indexerStats,
indexerStatus,
settings, settings,
system, system,
tags tags

View File

@@ -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);

View File

@@ -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;

View File

@@ -20,6 +20,7 @@ namespace Prowlarr.Api.V1.Indexers
public IndexerCapabilityResource Capabilities { get; set; } public IndexerCapabilityResource Capabilities { get; set; }
public int Priority { get; set; } public int Priority { get; set; }
public DateTime Added { get; set; } public DateTime Added { get; set; }
public IndexerStatusResource Status { get; set; }
} }
public class IndexerResourceMapper : ProviderResourceMapper<IndexerResource, IndexerDefinition> public class IndexerResourceMapper : ProviderResourceMapper<IndexerResource, IndexerDefinition>

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Indexers
{
public class IndexerStatusModule : ProwlarrRestModule<IndexerStatusResource>
{
private readonly IIndexerStatusService _indexerStatusService;
public IndexerStatusModule(IIndexerStatusService indexerStatusService)
{
_indexerStatusService = indexerStatusService;
GetResourceAll = GetAll;
}
private List<IndexerStatusResource> GetAll()
{
return _indexerStatusService.GetBlockedProviders().ToResource();
}
}
}

View File

@@ -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<IndexerStatusResource> ToResource(this IEnumerable<IndexerStatus> models)
{
return models.Select(ToResource).ToList();
}
}
}