diff --git a/frontend/.eslintrc b/frontend/.eslintrc
index 8b1a87670..b7a5b2137 100644
--- a/frontend/.eslintrc
+++ b/frontend/.eslintrc
@@ -268,7 +268,7 @@
"react/jsx-no-bind": [2, { "allowArrowFunctions": true }],
"react/jsx-no-duplicate-props": [2, { "ignoreCase": true }],
"react/jsx-max-props-per-line": [2, { "maximum": 2 }],
- "react/jsx-handler-names": [2, { "eventHandlerPrefix": "on", "eventHandlerPropPrefix": "on" }],
+ "react/jsx-handler-names": [2, { "eventHandlerPrefix": "(on|dispatch)", "eventHandlerPropPrefix": "on" }],
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-uses-react": 2,
diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js
index 9e15ece36..249f86661 100644
--- a/frontend/src/App/AppRoutes.js
+++ b/frontend/src/App/AppRoutes.js
@@ -21,9 +21,9 @@ import Settings from 'Settings/Settings';
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
import Profiles from 'Settings/Profiles/Profiles';
import Quality from 'Settings/Quality/Quality';
-import IndexerSettings from 'Settings/Indexers/IndexerSettings';
-import ImportListSettings from 'Settings/ImportLists/ImportListSettings';
-import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSettings';
+import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
+import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
+import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
import TagSettings from 'Settings/Tags/TagSettings';
@@ -169,17 +169,17 @@ function AppRoutes(props) {
+
+
+
+
+ }
onSavePress={this.onSavePress}
/>
@@ -71,4 +92,9 @@ class DownloadClientSettings extends Component {
}
}
+DownloadClientSettings.propTypes = {
+ isTestingAll: PropTypes.bool.isRequired,
+ dispatchTestAllDownloadClients: PropTypes.func.isRequired
+};
+
export default DownloadClientSettings;
diff --git a/frontend/src/Settings/DownloadClients/DownloadClientSettingsConnector.js b/frontend/src/Settings/DownloadClients/DownloadClientSettingsConnector.js
new file mode 100644
index 000000000..5e1a8a1ca
--- /dev/null
+++ b/frontend/src/Settings/DownloadClients/DownloadClientSettingsConnector.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { testAllDownloadClients } from 'Store/Actions/settingsActions';
+import DownloadClientSettings from './DownloadClientSettings';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.downloadClients.isTestingAll,
+ (isTestingAll) => {
+ return {
+ isTestingAll
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ dispatchTestAllDownloadClients: testAllDownloadClients
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSettings);
diff --git a/frontend/src/Settings/ImportLists/ImportListSettings.js b/frontend/src/Settings/ImportLists/ImportListSettings.js
index 6ba6e1894..561bcfd5a 100644
--- a/frontend/src/Settings/ImportLists/ImportListSettings.js
+++ b/frontend/src/Settings/ImportLists/ImportListSettings.js
@@ -1,6 +1,10 @@
-import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import React, { Component, Fragment } from 'react';
+import { icons } from 'Helpers/Props';
import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
+import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
+import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector';
@@ -38,10 +42,33 @@ class ImportListSettings extends Component {
// Render
render() {
+ const {
+ isTestingAll,
+ dispatchTestAllImportLists
+ } = this.props;
+
+ const {
+ isSaving,
+ hasPendingChanges
+ } = this.state;
+
return (
+
+
+
+
+ }
onSavePress={this.onSavePress}
/>
@@ -53,4 +80,9 @@ class ImportListSettings extends Component {
}
}
+ImportListSettings.propTypes = {
+ isTestingAll: PropTypes.bool.isRequired,
+ dispatchTestAllImportLists: PropTypes.func.isRequired
+};
+
export default ImportListSettings;
diff --git a/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js b/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js
new file mode 100644
index 000000000..7607faef7
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListSettingsConnector.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { testAllImportLists } from 'Store/Actions/settingsActions';
+import ImportListSettings from './ImportListSettings';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.importLists.isTestingAll,
+ (isTestingAll) => {
+ return {
+ isTestingAll
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ dispatchTestAllImportLists: testAllImportLists
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(ImportListSettings);
diff --git a/frontend/src/Settings/Indexers/IndexerSettings.js b/frontend/src/Settings/Indexers/IndexerSettings.js
index 3b42dc2e5..6538e8152 100644
--- a/frontend/src/Settings/Indexers/IndexerSettings.js
+++ b/frontend/src/Settings/Indexers/IndexerSettings.js
@@ -1,6 +1,10 @@
-import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import React, { Component, Fragment } from 'react';
+import { icons } from 'Helpers/Props';
import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
+import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
+import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import IndexersConnector from './Indexers/IndexersConnector';
import IndexerOptionsConnector from './Options/IndexerOptionsConnector';
@@ -43,6 +47,11 @@ class IndexerSettings extends Component {
// Render
render() {
+ const {
+ isTestingAll,
+ dispatchTestAllIndexers
+ } = this.props;
+
const {
isSaving,
hasPendingChanges
@@ -53,6 +62,18 @@ class IndexerSettings extends Component {
+
+
+
+
+ }
onSavePress={this.onSavePress}
/>
@@ -71,4 +92,9 @@ class IndexerSettings extends Component {
}
}
+IndexerSettings.propTypes = {
+ isTestingAll: PropTypes.bool.isRequired,
+ dispatchTestAllIndexers: PropTypes.func.isRequired
+};
+
export default IndexerSettings;
diff --git a/frontend/src/Settings/Indexers/IndexerSettingsConnector.js b/frontend/src/Settings/Indexers/IndexerSettingsConnector.js
new file mode 100644
index 000000000..1eaf098d7
--- /dev/null
+++ b/frontend/src/Settings/Indexers/IndexerSettingsConnector.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { testAllIndexers } from 'Store/Actions/settingsActions';
+import IndexerSettings from './IndexerSettings';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.indexers.isTestingAll,
+ (isTestingAll) => {
+ return {
+ isTestingAll
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ dispatchTestAllIndexers: testAllIndexers
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(IndexerSettings);
diff --git a/frontend/src/Settings/SettingsToolbar.js b/frontend/src/Settings/SettingsToolbar.js
index fafca0171..a47923c75 100644
--- a/frontend/src/Settings/SettingsToolbar.js
+++ b/frontend/src/Settings/SettingsToolbar.js
@@ -42,6 +42,7 @@ class SettingsToolbar extends Component {
showSave,
isSaving,
hasPendingChanges,
+ additionalButtons,
hasPendingLocation,
onSavePress,
onConfirmNavigation,
@@ -67,7 +68,12 @@ class SettingsToolbar extends Component {
onPress={onSavePress}
/>
}
+
+ {
+ additionalButtons
+ }
+
{
+ dispatch(set({
+ section,
+ isTestingAll: false,
+ saveError: null
+ }));
+ });
+
+ request.fail((xhr) => {
+ dispatch(set({
+ section,
+ isTestingAll: false
+ }));
+ });
+ };
+}
+
+export default createTestAllProvidersHandler;
diff --git a/frontend/src/Store/Actions/Settings/downloadClients.js b/frontend/src/Store/Actions/Settings/downloadClients.js
index e10414b9a..651f062ed 100644
--- a/frontend/src/Store/Actions/Settings/downloadClients.js
+++ b/frontend/src/Store/Actions/Settings/downloadClients.js
@@ -7,6 +7,7 @@ import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
+import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
@@ -27,6 +28,7 @@ export const CANCEL_SAVE_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelSaveD
export const DELETE_DOWNLOAD_CLIENT = 'settings/downloadClients/deleteDownloadClient';
export const TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/testDownloadClient';
export const CANCEL_TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelTestDownloadClient';
+export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients';
//
// Action Creators
@@ -40,6 +42,7 @@ export const cancelSaveDownloadClient = createThunk(CANCEL_SAVE_DOWNLOAD_CLIENT)
export const deleteDownloadClient = createThunk(DELETE_DOWNLOAD_CLIENT);
export const testDownloadClient = createThunk(TEST_DOWNLOAD_CLIENT);
export const cancelTestDownloadClient = createThunk(CANCEL_TEST_DOWNLOAD_CLIENT);
+export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS);
export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return {
@@ -75,6 +78,7 @@ export default {
isSaving: false,
saveError: null,
isTesting: false,
+ isTestingAll: false,
items: [],
pendingChanges: {}
},
@@ -90,7 +94,8 @@ export default {
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
- [CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section)
+ [CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
+ [TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
},
//
diff --git a/frontend/src/Store/Actions/Settings/importLists.js b/frontend/src/Store/Actions/Settings/importLists.js
index 00b3f3950..7b67c3a77 100644
--- a/frontend/src/Store/Actions/Settings/importLists.js
+++ b/frontend/src/Store/Actions/Settings/importLists.js
@@ -7,6 +7,7 @@ import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
+import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
@@ -27,6 +28,7 @@ export const CANCEL_SAVE_IMPORT_LIST = 'settings/importlists/cancelSaveImportLis
export const DELETE_IMPORT_LIST = 'settings/importlists/deleteImportList';
export const TEST_IMPORT_LIST = 'settings/importlists/testImportList';
export const CANCEL_TEST_IMPORT_LIST = 'settings/importlists/cancelTestImportList';
+export const TEST_ALL_IMPORT_LISTS = 'settings/importlists/testAllImportLists';
//
// Action Creators
@@ -40,6 +42,7 @@ export const cancelSaveImportList = createThunk(CANCEL_SAVE_IMPORT_LIST);
export const deleteImportList = createThunk(DELETE_IMPORT_LIST);
export const testImportList = createThunk(TEST_IMPORT_LIST);
export const cancelTestImportList = createThunk(CANCEL_TEST_IMPORT_LIST);
+export const testAllImportLists = createThunk(TEST_ALL_IMPORT_LISTS);
export const setImportListValue = createAction(SET_IMPORT_LIST_VALUE, (payload) => {
return {
@@ -75,6 +78,7 @@ export default {
isSaving: false,
saveError: null,
isTesting: false,
+ isTestingAll: false,
items: [],
pendingChanges: {}
},
@@ -90,7 +94,8 @@ export default {
[CANCEL_SAVE_IMPORT_LIST]: createCancelSaveProviderHandler(section),
[DELETE_IMPORT_LIST]: createRemoveItemHandler(section, '/importlist'),
[TEST_IMPORT_LIST]: createTestProviderHandler(section, '/importlist'),
- [CANCEL_TEST_IMPORT_LIST]: createCancelTestProviderHandler(section)
+ [CANCEL_TEST_IMPORT_LIST]: createCancelTestProviderHandler(section),
+ [TEST_ALL_IMPORT_LISTS]: createTestAllProvidersHandler(section, '/importlist')
},
//
diff --git a/frontend/src/Store/Actions/Settings/indexers.js b/frontend/src/Store/Actions/Settings/indexers.js
index cbc177dfa..622ae685f 100644
--- a/frontend/src/Store/Actions/Settings/indexers.js
+++ b/frontend/src/Store/Actions/Settings/indexers.js
@@ -7,6 +7,7 @@ import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
+import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
@@ -27,6 +28,7 @@ export const CANCEL_SAVE_INDEXER = 'settings/indexers/cancelSaveIndexer';
export const DELETE_INDEXER = 'settings/indexers/deleteIndexer';
export const TEST_INDEXER = 'settings/indexers/testIndexer';
export const CANCEL_TEST_INDEXER = 'settings/indexers/cancelTestIndexer';
+export const TEST_ALL_INDEXERS = 'settings/indexers/testAllIndexers';
//
// Action Creators
@@ -40,6 +42,7 @@ export const cancelSaveIndexer = createThunk(CANCEL_SAVE_INDEXER);
export const deleteIndexer = createThunk(DELETE_INDEXER);
export const testIndexer = createThunk(TEST_INDEXER);
export const cancelTestIndexer = createThunk(CANCEL_TEST_INDEXER);
+export const testAllIndexers = createThunk(TEST_ALL_INDEXERS);
export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => {
return {
@@ -75,6 +78,7 @@ export default {
isSaving: false,
saveError: null,
isTesting: false,
+ isTestingAll: false,
items: [],
pendingChanges: {}
},
@@ -90,7 +94,8 @@ export default {
[CANCEL_SAVE_INDEXER]: createCancelSaveProviderHandler(section),
[DELETE_INDEXER]: createRemoveItemHandler(section, '/indexer'),
[TEST_INDEXER]: createTestProviderHandler(section, '/indexer'),
- [CANCEL_TEST_INDEXER]: createCancelTestProviderHandler(section)
+ [CANCEL_TEST_INDEXER]: createCancelTestProviderHandler(section),
+ [TEST_ALL_INDEXERS]: createTestAllProvidersHandler(section, '/indexer')
},
//
diff --git a/frontend/src/System/Status/Health/Health.js b/frontend/src/System/Status/Health/Health.js
index ff2165048..a90e53f93 100644
--- a/frontend/src/System/Status/Health/Health.js
+++ b/frontend/src/System/Status/Health/Health.js
@@ -3,7 +3,8 @@ import React, { Component } from 'react';
import titleCase from 'Utilities/String/titleCase';
import { icons, kinds } from 'Helpers/Props';
import Icon from 'Components/Icon';
-import Link from 'Components/Link/Link';
+import IconButton from 'Components/Link/IconButton';
+import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FieldSet from 'Components/FieldSet';
import Table from 'Components/Table/Table';
@@ -18,36 +19,68 @@ function getInternalLink(source) {
case 'IndexerSearchCheck':
case 'IndexerStatusCheck':
return (
-
- Settings
-
+
);
case 'DownloadClientCheck':
case 'ImportMechanismCheck':
return (
-
- Settings
-
+
);
case 'RootFolderCheck':
return (
-
-
- Artist Editor
-
-
+
);
case 'UpdateCheck':
return (
-
- Updates
-
+
);
default:
return;
}
}
+function getTestLink(source, props) {
+ switch (source) {
+ case 'IndexerStatusCheck':
+ return (
+
+ );
+ case 'DownloadClientCheck':
+ return (
+
+ );
+
+ default:
+ break;
+ }
+}
+
const columns = [
{
className: styles.status,
@@ -60,12 +93,8 @@ const columns = [
isVisible: true
},
{
- name: 'wikiLink',
- label: 'Wiki',
- isVisible: true
- },
- {
- name: 'internalLink',
+ name: 'actions',
+ label: 'Actions',
isVisible: true
}
];
@@ -121,6 +150,7 @@ class Health extends Component {
{
items.map((item) => {
const internalLink = getInternalLink(item.source);
+ const testLink = getTestLink(item.source, this.props);
return (
@@ -135,18 +165,20 @@ class Health extends Component {
{item.message}
-
- Wiki
-
-
+ />
-
{
internalLink
}
+
+ {
+ !!testLink &&
+ testLink
+ }
);
@@ -164,7 +196,11 @@ class Health extends Component {
Health.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
- items: PropTypes.array.isRequired
+ items: PropTypes.array.isRequired,
+ isTestingAllDownloadClients: PropTypes.bool.isRequired,
+ isTestingAllIndexers: PropTypes.bool.isRequired,
+ dispatchTestAllDownloadClients: PropTypes.func.isRequired,
+ dispatchTestAllIndexers: PropTypes.func.isRequired
};
export default Health;
diff --git a/frontend/src/System/Status/Health/HealthConnector.js b/frontend/src/System/Status/Health/HealthConnector.js
index 67f6a39dc..d2adc41cc 100644
--- a/frontend/src/System/Status/Health/HealthConnector.js
+++ b/frontend/src/System/Status/Health/HealthConnector.js
@@ -3,12 +3,15 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchHealth } from 'Store/Actions/systemActions';
+import { testAllDownloadClients, testAllIndexers } from 'Store/Actions/settingsActions';
import Health from './Health';
function createMapStateToProps() {
return createSelector(
(state) => state.system.health,
- (health) => {
+ (state) => state.settings.downloadClients.isTestingAll,
+ (state) => state.settings.indexers.isTestingAll,
+ (health, isTestingAllDownloadClients, isTestingAllIndexers) => {
const {
isFetching,
isPopulated,
@@ -18,14 +21,18 @@ function createMapStateToProps() {
return {
isFetching,
isPopulated,
- items
+ items,
+ isTestingAllDownloadClients,
+ isTestingAllIndexers
};
}
);
}
const mapDispatchToProps = {
- fetchHealth
+ dispatchFetchHealth: fetchHealth,
+ dispatchTestAllDownloadClients: testAllDownloadClients,
+ dispatchTestAllIndexers: testAllIndexers
};
class HealthConnector extends Component {
@@ -34,23 +41,28 @@ class HealthConnector extends Component {
// Lifecycle
componentDidMount() {
- this.props.fetchHealth();
+ this.props.dispatchFetchHealth();
}
//
// Render
render() {
+ const {
+ dispatchFetchHealth,
+ ...otherProps
+ } = this.props;
+
return (
);
}
}
HealthConnector.propTypes = {
- fetchHealth: PropTypes.func.isRequired
+ dispatchFetchHealth: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(HealthConnector);