From f618901048c88c13aa4dcc6e2e5f33bffb16cabc Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 25 Jul 2023 01:27:20 +0300 Subject: [PATCH] Convert Indexer Stats to Typescript --- frontend/.eslintrc.js | 3 +- frontend/src/App/AppRoutes.js | 4 +- frontend/src/App/State/AppState.ts | 2 + .../src/App/State/IndexerStatsAppState.ts | 11 + .../src/Components/Form/FormInputGroup.js | 1 + .../src/Components/Form/PathInputConnector.js | 1 + .../Table/Cells/TableRowCellButton.css.d.ts | 7 + .../Table/Cells/TableRowCellButton.js | 25 -- .../Table/Cells/TableRowCellButton.tsx | 19 ++ .../Indexer/{NoIndexer.js => NoIndexer.tsx} | 23 +- .../Stats/{Stats.css => IndexerStats.css} | 0 .../{Stats.css.d.ts => IndexerStats.css.d.ts} | 0 frontend/src/Indexer/Stats/IndexerStats.tsx | 275 ++++++++++++++++++ .../Indexer/Stats/IndexerStatsFilterMenu.tsx | 27 ++ frontend/src/Indexer/Stats/Stats.js | 261 ----------------- frontend/src/Indexer/Stats/StatsConnector.js | 51 ---- frontend/src/Indexer/Stats/StatsFilterMenu.js | 37 --- .../Stats/StatsFilterModalConnector.js | 24 -- frontend/src/typings/IndexerStats.ts | 31 ++ src/NzbDrone.Core/Localization/Core/en.json | 3 + 20 files changed, 389 insertions(+), 416 deletions(-) create mode 100644 frontend/src/App/State/IndexerStatsAppState.ts create mode 100644 frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts delete mode 100644 frontend/src/Components/Table/Cells/TableRowCellButton.js create mode 100644 frontend/src/Components/Table/Cells/TableRowCellButton.tsx rename frontend/src/Indexer/{NoIndexer.js => NoIndexer.tsx} (67%) rename frontend/src/Indexer/Stats/{Stats.css => IndexerStats.css} (100%) rename frontend/src/Indexer/Stats/{Stats.css.d.ts => IndexerStats.css.d.ts} (100%) create mode 100644 frontend/src/Indexer/Stats/IndexerStats.tsx create mode 100644 frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx delete mode 100644 frontend/src/Indexer/Stats/Stats.js delete mode 100644 frontend/src/Indexer/Stats/StatsConnector.js delete mode 100644 frontend/src/Indexer/Stats/StatsFilterMenu.js delete mode 100644 frontend/src/Indexer/Stats/StatsFilterModalConnector.js create mode 100644 frontend/src/typings/IndexerStats.ts diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index c312414a2..8d844cb8b 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -26,7 +26,8 @@ module.exports = { globals: { expect: false, chai: false, - sinon: false + sinon: false, + JSX: true }, parserOptions: { diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index 0df7d2a49..8bf97249c 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -5,7 +5,7 @@ import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import HistoryConnector from 'History/HistoryConnector'; import IndexerIndex from 'Indexer/Index/IndexerIndex'; -import StatsConnector from 'Indexer/Stats/StatsConnector'; +import IndexerStats from 'Indexer/Stats/IndexerStats'; import SearchIndexConnector from 'Search/SearchIndexConnector'; import ApplicationSettingsConnector from 'Settings/Applications/ApplicationSettingsConnector'; import DevelopmentSettingsConnector from 'Settings/Development/DevelopmentSettingsConnector'; @@ -60,7 +60,7 @@ function AppRoutes(props) { {/* diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index 5c678abea..e07f42f13 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -1,4 +1,5 @@ import IndexerAppState, { IndexerIndexAppState } from './IndexerAppState'; +import IndexerStatsAppState from './IndexerStatsAppState'; import SettingsAppState from './SettingsAppState'; import TagsAppState from './TagsAppState'; @@ -36,6 +37,7 @@ export interface CustomFilter { interface AppState { indexerIndex: IndexerIndexAppState; + indexerStats: IndexerStatsAppState; indexers: IndexerAppState; settings: SettingsAppState; tags: TagsAppState; diff --git a/frontend/src/App/State/IndexerStatsAppState.ts b/frontend/src/App/State/IndexerStatsAppState.ts new file mode 100644 index 000000000..a696860b3 --- /dev/null +++ b/frontend/src/App/State/IndexerStatsAppState.ts @@ -0,0 +1,11 @@ +import { AppSectionItemState } from 'App/State/AppSectionState'; +import { Filter } from 'App/State/AppState'; +import { IndexerStats } from 'typings/IndexerStats'; + +export interface IndexerStatsAppState + extends AppSectionItemState { + selectedFilterKey: string; + filters: Filter[]; +} + +export default IndexerStatsAppState; diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index bd0e9184d..a00f6f8de 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -270,6 +270,7 @@ FormInputGroup.propTypes = { helpTexts: PropTypes.arrayOf(PropTypes.string), helpTextWarning: PropTypes.string, helpLink: PropTypes.string, + autoFocus: PropTypes.bool, includeNoChange: PropTypes.bool, includeNoChangeDisabled: PropTypes.bool, selectedValueOptions: PropTypes.object, diff --git a/frontend/src/Components/Form/PathInputConnector.js b/frontend/src/Components/Form/PathInputConnector.js index 3917a8d3f..563437f9a 100644 --- a/frontend/src/Components/Form/PathInputConnector.js +++ b/frontend/src/Components/Form/PathInputConnector.js @@ -68,6 +68,7 @@ class PathInputConnector extends Component { } PathInputConnector.propTypes = { + ...PathInput.props, includeFiles: PropTypes.bool.isRequired, dispatchFetchPaths: PropTypes.func.isRequired, dispatchClearPaths: PropTypes.func.isRequired diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts b/frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts new file mode 100644 index 000000000..c748f6f97 --- /dev/null +++ b/frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts @@ -0,0 +1,7 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + 'cell': string; +} +export const cssExports: CssExports; +export default cssExports; diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.js b/frontend/src/Components/Table/Cells/TableRowCellButton.js deleted file mode 100644 index ff50d3bc9..000000000 --- a/frontend/src/Components/Table/Cells/TableRowCellButton.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Link from 'Components/Link/Link'; -import TableRowCell from './TableRowCell'; -import styles from './TableRowCellButton.css'; - -function TableRowCellButton({ className, ...otherProps }) { - return ( - - ); -} - -TableRowCellButton.propTypes = { - className: PropTypes.string.isRequired -}; - -TableRowCellButton.defaultProps = { - className: styles.cell -}; - -export default TableRowCellButton; diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.tsx b/frontend/src/Components/Table/Cells/TableRowCellButton.tsx new file mode 100644 index 000000000..c80a3d626 --- /dev/null +++ b/frontend/src/Components/Table/Cells/TableRowCellButton.tsx @@ -0,0 +1,19 @@ +import React, { ReactNode } from 'react'; +import Link, { LinkProps } from 'Components/Link/Link'; +import TableRowCell from './TableRowCell'; +import styles from './TableRowCellButton.css'; + +interface TableRowCellButtonProps extends LinkProps { + className?: string; + children: ReactNode; +} + +function TableRowCellButton(props: TableRowCellButtonProps) { + const { className = styles.cell, ...otherProps } = props; + + return ( + + ); +} + +export default TableRowCellButton; diff --git a/frontend/src/Indexer/NoIndexer.js b/frontend/src/Indexer/NoIndexer.tsx similarity index 67% rename from frontend/src/Indexer/NoIndexer.js rename to frontend/src/Indexer/NoIndexer.tsx index f94df7902..75650cad6 100644 --- a/frontend/src/Indexer/NoIndexer.js +++ b/frontend/src/Indexer/NoIndexer.tsx @@ -1,15 +1,16 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Button from 'Components/Link/Button'; import { kinds } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import styles from './NoIndexer.css'; -function NoIndexer(props) { - const { - totalItems, - onAddIndexerPress - } = props; +interface NoIndexerProps { + totalItems: number; + onAddIndexerPress(): void; +} + +function NoIndexer(props: NoIndexerProps) { + const { totalItems, onAddIndexerPress } = props; if (totalItems > 0) { return ( @@ -28,10 +29,7 @@ function NoIndexer(props) {
-
@@ -39,9 +37,4 @@ function NoIndexer(props) { ); } -NoIndexer.propTypes = { - totalItems: PropTypes.number.isRequired, - onAddIndexerPress: PropTypes.func.isRequired -}; - export default NoIndexer; diff --git a/frontend/src/Indexer/Stats/Stats.css b/frontend/src/Indexer/Stats/IndexerStats.css similarity index 100% rename from frontend/src/Indexer/Stats/Stats.css rename to frontend/src/Indexer/Stats/IndexerStats.css diff --git a/frontend/src/Indexer/Stats/Stats.css.d.ts b/frontend/src/Indexer/Stats/IndexerStats.css.d.ts similarity index 100% rename from frontend/src/Indexer/Stats/Stats.css.d.ts rename to frontend/src/Indexer/Stats/IndexerStats.css.d.ts diff --git a/frontend/src/Indexer/Stats/IndexerStats.tsx b/frontend/src/Indexer/Stats/IndexerStats.tsx new file mode 100644 index 000000000..0a61fc8fc --- /dev/null +++ b/frontend/src/Indexer/Stats/IndexerStats.tsx @@ -0,0 +1,275 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import IndexerStatsAppState from 'App/State/IndexerStatsAppState'; +import Alert from 'Components/Alert'; +import BarChart from 'Components/Chart/BarChart'; +import DoughnutChart from 'Components/Chart/DoughnutChart'; +import StackedBarChart from 'Components/Chart/StackedBarChart'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; +import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; +import { align, kinds } from 'Helpers/Props'; +import { + fetchIndexerStats, + setIndexerStatsFilter, +} from 'Store/Actions/indexerStatsActions'; +import { + IndexerStatsHost, + IndexerStatsIndexer, + IndexerStatsUserAgent, +} from 'typings/IndexerStats'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; +import translate from 'Utilities/String/translate'; +import IndexerStatsFilterMenu from './IndexerStatsFilterMenu'; +import styles from './IndexerStats.css'; + +function getAverageResponseTimeData(indexerStats: IndexerStatsIndexer[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.indexerName, + value: indexer.averageResponseTime, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getFailureRateData(indexerStats: IndexerStatsIndexer[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.indexerName, + value: + (indexer.numberOfFailedQueries + + indexer.numberOfFailedRssQueries + + indexer.numberOfFailedAuthQueries + + indexer.numberOfFailedGrabs) / + (indexer.numberOfQueries + + indexer.numberOfRssQueries + + indexer.numberOfAuthQueries + + indexer.numberOfGrabs), + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getTotalRequestsData(indexerStats: IndexerStatsIndexer[]) { + const data = { + labels: indexerStats.map((indexer) => indexer.indexerName), + datasets: [ + { + label: translate('SearchQueries'), + data: indexerStats.map((indexer) => indexer.numberOfQueries), + }, + { + label: translate('RssQueries'), + data: indexerStats.map((indexer) => indexer.numberOfRssQueries), + }, + { + label: translate('AuthQueries'), + data: indexerStats.map((indexer) => indexer.numberOfAuthQueries), + }, + ], + }; + + return data; +} + +function getNumberGrabsData(indexerStats: IndexerStatsIndexer[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.indexerName, + value: indexer.numberOfGrabs - indexer.numberOfFailedGrabs, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getUserAgentGrabsData(indexerStats: IndexerStatsUserAgent[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.userAgent ? indexer.userAgent : 'Other', + value: indexer.numberOfGrabs, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getUserAgentQueryData(indexerStats: IndexerStatsUserAgent[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.userAgent ? indexer.userAgent : 'Other', + value: indexer.numberOfQueries, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getHostGrabsData(indexerStats: IndexerStatsHost[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.host ? indexer.host : 'Other', + value: indexer.numberOfGrabs, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +function getHostQueryData(indexerStats: IndexerStatsHost[]) { + const data = indexerStats.map((indexer) => { + return { + label: indexer.host ? indexer.host : 'Other', + value: indexer.numberOfQueries, + }; + }); + + data.sort((a, b) => { + return b.value - a.value; + }); + + return data; +} + +const indexerStatsSelector = () => { + return createSelector( + (state: AppState) => state.indexerStats, + (indexerStats: IndexerStatsAppState) => { + return indexerStats; + } + ); +}; + +function IndexerStats() { + const { isFetching, isPopulated, item, error, filters, selectedFilterKey } = + useSelector(indexerStatsSelector()); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchIndexerStats()); + }, [dispatch]); + + const onFilterSelect = useCallback( + (value: string) => { + dispatch(setIndexerStatsFilter({ selectedFilterKey: value })); + }, + [dispatch] + ); + + const isLoaded = !error && isPopulated; + + return ( + + + + + + + + {isFetching && !isPopulated && } + + {!isFetching && !!error && ( + + {getErrorMessage(error, 'Failed to load indexer stats from API')} + + )} + + {isLoaded && ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ )} +
+
+ ); +} + +export default IndexerStats; diff --git a/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx b/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx new file mode 100644 index 000000000..7b30be4c3 --- /dev/null +++ b/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import FilterMenu from 'Components/Menu/FilterMenu'; +import { align } from 'Helpers/Props'; + +interface IndexerStatsFilterMenuProps { + selectedFilterKey: string | number; + filters: object[]; + isDisabled: boolean; + onFilterSelect(filterName: string): unknown; +} + +function IndexerStatsFilterMenu(props: IndexerStatsFilterMenuProps) { + const { selectedFilterKey, filters, isDisabled, onFilterSelect } = props; + + return ( + + ); +} + +export default IndexerStatsFilterMenu; diff --git a/frontend/src/Indexer/Stats/Stats.js b/frontend/src/Indexer/Stats/Stats.js deleted file mode 100644 index 30bff5f84..000000000 --- a/frontend/src/Indexer/Stats/Stats.js +++ /dev/null @@ -1,261 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Alert from 'Components/Alert'; -import BarChart from 'Components/Chart/BarChart'; -import DoughnutChart from 'Components/Chart/DoughnutChart'; -import StackedBarChart from 'Components/Chart/StackedBarChart'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; -import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; -import { align, kinds } from 'Helpers/Props'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; -import translate from 'Utilities/String/translate'; -import StatsFilterMenu from './StatsFilterMenu'; -import styles from './Stats.css'; - -function getAverageResponseTimeData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.indexerName, - value: indexer.averageResponseTime - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getFailureRateData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.indexerName, - value: (indexer.numberOfFailedQueries + indexer.numberOfFailedRssQueries + indexer.numberOfFailedAuthQueries + indexer.numberOfFailedGrabs) / - (indexer.numberOfQueries + indexer.numberOfRssQueries + indexer.numberOfAuthQueries + indexer.numberOfGrabs) - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getTotalRequestsData(indexerStats) { - const data = { - labels: indexerStats.map((indexer) => indexer.indexerName), - datasets: [ - { - label: 'Search Queries', - data: indexerStats.map((indexer) => indexer.numberOfQueries) - }, - { - label: 'Rss Queries', - data: indexerStats.map((indexer) => indexer.numberOfRssQueries) - }, - { - label: 'Auth Queries', - data: indexerStats.map((indexer) => indexer.numberOfAuthQueries) - } - ] - }; - - return data; -} - -function getNumberGrabsData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.indexerName, - value: indexer.numberOfGrabs - indexer.numberOfFailedGrabs - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getUserAgentGrabsData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.userAgent ? indexer.userAgent : 'Other', - value: indexer.numberOfGrabs - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getUserAgentQueryData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.userAgent ? indexer.userAgent : 'Other', - value: indexer.numberOfQueries - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getHostGrabsData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.host ? indexer.host : 'Other', - value: indexer.numberOfGrabs - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function getHostQueryData(indexerStats) { - const data = indexerStats.map((indexer) => { - return { - label: indexer.host ? indexer.host : 'Other', - value: indexer.numberOfQueries - }; - }); - - data.sort((a, b) => { - return b.value - a.value; - }); - - return data; -} - -function Stats(props) { - const { - item, - isFetching, - isPopulated, - error, - filters, - selectedFilterKey, - onFilterSelect - } = props; - - const isLoaded = !!(!error && isPopulated); - - return ( - - - - - - - - { - isFetching && !isPopulated && - - } - - { - !isFetching && !!error && - - {getErrorMessage(error, 'Failed to load indexer stats from API')} - - } - - { - isLoaded && -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- } -
-
- ); -} - -Stats.propTypes = { - item: PropTypes.object.isRequired, - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, - selectedFilterKey: PropTypes.string.isRequired, - onFilterSelect: PropTypes.func.isRequired, - error: PropTypes.object, - data: PropTypes.object -}; - -export default Stats; diff --git a/frontend/src/Indexer/Stats/StatsConnector.js b/frontend/src/Indexer/Stats/StatsConnector.js deleted file mode 100644 index 006716953..000000000 --- a/frontend/src/Indexer/Stats/StatsConnector.js +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchIndexerStats, setIndexerStatsFilter } from 'Store/Actions/indexerStatsActions'; -import Stats from './Stats'; - -function createMapStateToProps() { - return createSelector( - (state) => state.indexerStats, - (indexerStats) => indexerStats - ); -} - -function createMapDispatchToProps(dispatch, props) { - return { - onFilterSelect(selectedFilterKey) { - dispatch(setIndexerStatsFilter({ selectedFilterKey })); - }, - dispatchFetchIndexerStats() { - dispatch(fetchIndexerStats()); - } - }; -} - -class StatsConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.dispatchFetchIndexerStats(); - } - - // - // Render - - render() { - return ( - - ); - } -} - -StatsConnector.propTypes = { - dispatchFetchIndexerStats: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, createMapDispatchToProps)(StatsConnector); diff --git a/frontend/src/Indexer/Stats/StatsFilterMenu.js b/frontend/src/Indexer/Stats/StatsFilterMenu.js deleted file mode 100644 index 283159b7e..000000000 --- a/frontend/src/Indexer/Stats/StatsFilterMenu.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import FilterMenu from 'Components/Menu/FilterMenu'; -import { align } from 'Helpers/Props'; - -function StatsFilterMenu(props) { - const { - selectedFilterKey, - filters, - isDisabled, - onFilterSelect - } = props; - - return ( - - ); -} - -StatsFilterMenu.propTypes = { - selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, - isDisabled: PropTypes.bool.isRequired, - onFilterSelect: PropTypes.func.isRequired -}; - -StatsFilterMenu.defaultProps = { - showCustomFilters: false -}; - -export default StatsFilterMenu; diff --git a/frontend/src/Indexer/Stats/StatsFilterModalConnector.js b/frontend/src/Indexer/Stats/StatsFilterModalConnector.js deleted file mode 100644 index 53bf2ed3c..000000000 --- a/frontend/src/Indexer/Stats/StatsFilterModalConnector.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import FilterModal from 'Components/Filter/FilterModal'; -import { setIndexerStatsFilter } from 'Store/Actions/indexerStatsActions'; - -function createMapStateToProps() { - return createSelector( - (state) => state.indexerStats.items, - (state) => state.indexerStats.filterBuilderProps, - (sectionItems, filterBuilderProps) => { - return { - sectionItems, - filterBuilderProps, - customFilterType: 'indexerStats' - }; - } - ); -} - -const mapDispatchToProps = { - dispatchSetFilter: setIndexerStatsFilter -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); diff --git a/frontend/src/typings/IndexerStats.ts b/frontend/src/typings/IndexerStats.ts new file mode 100644 index 000000000..74fa1862e --- /dev/null +++ b/frontend/src/typings/IndexerStats.ts @@ -0,0 +1,31 @@ +export interface IndexerStatsIndexer { + indexerId: number; + indexerName: string; + averageResponseTime: number; + numberOfQueries: number; + numberOfGrabs: number; + numberOfRssQueries: number; + numberOfAuthQueries: number; + numberOfFailedQueries: number; + numberOfFailedGrabs: number; + numberOfFailedRssQueries: number; + numberOfFailedAuthQueries: number; +} + +export interface IndexerStatsUserAgent { + userAgent: string; + numberOfQueries: number; + numberOfGrabs: number; +} + +export interface IndexerStatsHost { + host: string; + numberOfQueries: number; + numberOfGrabs: number; +} + +export interface IndexerStats { + indexers: IndexerStatsIndexer[]; + userAgents: IndexerStatsUserAgent[]; + hosts: IndexerStatsHost[]; +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 9b89992d2..f10617902 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -56,6 +56,7 @@ "Artist": "Artist", "AudioSearch": "Audio Search", "Auth": "Auth", + "AuthQueries": "Auth Queries", "Authentication": "Authentication", "AuthenticationMethodHelpText": "Require Username and Password to access Prowlarr", "AuthenticationRequired": "Authentication Required", @@ -398,6 +399,7 @@ "Result": "Result", "Retention": "Retention", "RssFeed": "RSS Feed", + "RssQueries": "RSS Queries", "SSLCertPassword": "SSL Cert Password", "SSLCertPasswordHelpText": "Password for pfx file", "SSLCertPath": "SSL Cert Path", @@ -413,6 +415,7 @@ "SearchCapabilities": "Search Capabilities", "SearchCountIndexers": "Search {0} indexers", "SearchIndexers": "Search Indexers", + "SearchQueries": "Search Queries", "SearchType": "Search Type", "SearchTypes": "Search Types", "Season": "Season",