diff --git a/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css new file mode 100644 index 000000000..b3102d230 --- /dev/null +++ b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css @@ -0,0 +1,11 @@ +.title { + composes: title from '~Components/DescriptionList/DescriptionListItemTitle.css'; + + width: 90px; +} + +.description { + composes: title from '~Components/DescriptionList/DescriptionListItemDescription.css'; + + margin-left: 110px; +} diff --git a/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css.d.ts b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css.d.ts new file mode 100644 index 000000000..e0d8c2f6b --- /dev/null +++ b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.css.d.ts @@ -0,0 +1,8 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'description': string; + 'title': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.tsx b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.tsx new file mode 100644 index 000000000..badbee351 --- /dev/null +++ b/frontend/src/Indexer/Index/Table/DisabledIndexerInfo.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import DescriptionList from 'Components/DescriptionList/DescriptionList'; +import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; +import formatDateTime from 'Utilities/Date/formatDateTime'; +import translate from 'Utilities/String/translate'; +import styles from './DisabledIndexerInfo.css'; + +interface DisabledIndexerInfoProps { + mostRecentFailure: Date; + disabledTill: Date; + initialFailure: Date; + longDateFormat: string; + timeFormat: string; +} + +function DisabledIndexerInfo(props: DisabledIndexerInfoProps) { + const { + mostRecentFailure, + disabledTill, + initialFailure, + longDateFormat, + timeFormat, + } = props; + + return ( + + + + + + + + ); +} + +export default DisabledIndexerInfo; diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx b/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx index 365e14ced..89ecdd501 100644 --- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx +++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx @@ -30,9 +30,8 @@ interface IndexerIndexRowProps { function IndexerIndexRow(props: IndexerIndexRowProps) { const { indexerId, columns, isSelectMode } = props; - const { indexer, appProfile } = useSelector( - createIndexerIndexItemSelector(props.indexerId) - ); + const { indexer, appProfile, status, longDateFormat, timeFormat } = + useSelector(createIndexerIndexItemSelector(props.indexerId)); const { id, @@ -44,7 +43,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) { protocol, privacy, priority, - status, fields, added, capabilities, @@ -123,6 +121,8 @@ function IndexerIndexRow(props: IndexerIndexRowProps) { enabled={enable} redirect={redirect} status={status} + longDateFormat={longDateFormat} + timeFormat={timeFormat} component={VirtualTableRowCell} /> ); diff --git a/frontend/src/Indexer/Index/Table/IndexerStatusCell.css b/frontend/src/Indexer/Index/Table/IndexerStatusCell.css index fbcd5eee9..6f5022ea8 100644 --- a/frontend/src/Indexer/Index/Table/IndexerStatusCell.css +++ b/frontend/src/Indexer/Index/Table/IndexerStatusCell.css @@ -7,3 +7,7 @@ .statusIcon { width: 20px !important; } + +.indexerStatusTooltip { + display: flex; +} diff --git a/frontend/src/Indexer/Index/Table/IndexerStatusCell.css.d.ts b/frontend/src/Indexer/Index/Table/IndexerStatusCell.css.d.ts index f7788f04f..c9b930d6d 100644 --- a/frontend/src/Indexer/Index/Table/IndexerStatusCell.css.d.ts +++ b/frontend/src/Indexer/Index/Table/IndexerStatusCell.css.d.ts @@ -1,6 +1,7 @@ // This file is automatically generated. // Please do not change this file! interface CssExports { + 'indexerStatusTooltip': string; 'status': string; 'statusIcon': string; } diff --git a/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx b/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx index 85cf08d38..4e8d1bd80 100644 --- a/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx +++ b/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx @@ -1,9 +1,11 @@ import React from 'react'; import Icon from 'Components/Icon'; import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell'; -import { icons, kinds } from 'Helpers/Props'; +import Popover from 'Components/Tooltip/Popover'; +import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { IndexerStatus } from 'Indexer/Indexer'; -import formatDateTime from 'Utilities/Date/formatDateTime'; +import translate from 'Utilities/String/translate'; +import DisabledIndexerInfo from './DisabledIndexerInfo'; import styles from './IndexerStatusCell.css'; interface IndexerStatusCellProps { @@ -11,6 +13,8 @@ interface IndexerStatusCellProps { enabled: boolean; redirect: boolean; status: IndexerStatus; + longDateFormat: string; + timeFormat: string; component?: React.ElementType; } @@ -20,6 +24,8 @@ function IndexerStatusCell(props: IndexerStatusCellProps) { enabled, redirect, status, + longDateFormat, + timeFormat, component: Component = VirtualTableRowCell, ...otherProps } = props; @@ -41,13 +47,29 @@ function IndexerStatusCell(props: IndexerStatusCellProps) { /> } {status ? ( - + } + title={translate('IndexerDisabled')} + body={ +
+ +
+ } + position={tooltipPositions.BOTTOM} /> ) : null} diff --git a/frontend/src/Indexer/Index/createIndexerIndexItemSelector.ts b/frontend/src/Indexer/Index/createIndexerIndexItemSelector.ts index 8dc3adb7c..12d042f7a 100644 --- a/frontend/src/Indexer/Index/createIndexerIndexItemSelector.ts +++ b/frontend/src/Indexer/Index/createIndexerIndexItemSelector.ts @@ -2,12 +2,16 @@ import { createSelector } from 'reselect'; import Indexer from 'Indexer/Indexer'; import createIndexerAppProfileSelector from 'Store/Selectors/createIndexerAppProfileSelector'; import createIndexerSelector from 'Store/Selectors/createIndexerSelector'; +import createIndexerStatusSelector from 'Store/Selectors/createIndexerStatusSelector'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; function createIndexerIndexItemSelector(indexerId: number) { return createSelector( createIndexerSelector(indexerId), createIndexerAppProfileSelector(indexerId), - (indexer: Indexer, appProfile) => { + createIndexerStatusSelector(indexerId), + createUISettingsSelector(), + (indexer: Indexer, appProfile, status, uiSettings) => { // If a series is deleted this selector may fire before the parent // selectors, which will result in an undefined series, if that happens // we want to return early here and again in the render function to avoid @@ -20,6 +24,9 @@ function createIndexerIndexItemSelector(indexerId: number) { return { indexer, appProfile, + status, + longDateFormat: uiSettings.longDateFormat, + timeFormat: uiSettings.timeFormat, }; } ); diff --git a/frontend/src/Indexer/Indexer.ts b/frontend/src/Indexer/Indexer.ts index 3ed4682ed..5ce83264b 100644 --- a/frontend/src/Indexer/Indexer.ts +++ b/frontend/src/Indexer/Indexer.ts @@ -2,6 +2,8 @@ import ModelBase from 'App/ModelBase'; export interface IndexerStatus extends ModelBase { disabledTill: Date; + initialFailure: Date; + mostRecentFailure: Date; } export interface IndexerCategory extends ModelBase { diff --git a/frontend/src/Store/Selectors/createIndexerStatusSelector.js b/frontend/src/Store/Selectors/createIndexerStatusSelector.js index e25d618fe..1912ea1a0 100644 --- a/frontend/src/Store/Selectors/createIndexerStatusSelector.js +++ b/frontend/src/Store/Selectors/createIndexerStatusSelector.js @@ -1,11 +1,10 @@ import _ from 'lodash'; import { createSelector } from 'reselect'; -function createIndexerStatusSelector() { +function createIndexerStatusSelector(indexerId) { return createSelector( - (state, { indexerId }) => indexerId, (state) => state.indexerStatus.items, - (indexerId, indexerStatus) => { + (indexerStatus) => { return _.find(indexerStatus, { indexerId }); } ); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 336bd7883..2b468a2d1 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -116,6 +116,7 @@ "Details": "Details", "DevelopmentSettings": "Development Settings", "Disabled": "Disabled", + "DisabledUntil": "Disabled Until", "Discord": "Discord", "Docker": "Docker", "Donations": "Donations", @@ -190,6 +191,7 @@ "IndexerAlreadySetup": "At least one instance of indexer is already setup", "IndexerAuth": "Indexer Auth", "IndexerDetails": "Indexer Details", + "IndexerDisabled": "Indexer Disabled", "IndexerFlags": "Indexer Flags", "IndexerHealthCheckNoIndexers": "No indexers enabled, Prowlarr will not return search results", "IndexerInfo": "Indexer Info", @@ -216,6 +218,7 @@ "IndexerVipCheckExpiredClientMessage": "Indexer VIP benefits have expired: {0}", "IndexerVipCheckExpiringClientMessage": "Indexer VIP benefits expiring soon: {0}", "Info": "Info", + "InitialFailure": "Initial Failure", "InstanceName": "Instance Name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InteractiveSearch": "Interactive Search", @@ -224,6 +227,7 @@ "Language": "Language", "LastDuration": "Last Duration", "LastExecution": "Last Execution", + "LastFailure": "Last Failure", "LastWriteTime": "Last Write Time", "LaunchBrowserHelpText": " Open a web browser and navigate to the Prowlarr homepage on app start.", "Level": "Level", diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs index e624440a1..46379f783 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerStatusResource.cs @@ -10,6 +10,8 @@ namespace Prowlarr.Api.V1.Indexers { public int IndexerId { get; set; } public DateTime? DisabledTill { get; set; } + public DateTime? MostRecentFailure { get; set; } + public DateTime? InitialFailure { get; set; } } public static class IndexerStatusResourceMapper @@ -24,7 +26,9 @@ namespace Prowlarr.Api.V1.Indexers return new IndexerStatusResource { IndexerId = model.ProviderId, - DisabledTill = model.DisabledTill + DisabledTill = model.DisabledTill, + MostRecentFailure = model.MostRecentFailure, + InitialFailure = model.InitialFailure, }; }