diff --git a/Requestrr.WebApi/ClientApp/package-lock.json b/Requestrr.WebApi/ClientApp/package-lock.json
index 34e95c0b..0f854e96 100644
--- a/Requestrr.WebApi/ClientApp/package-lock.json
+++ b/Requestrr.WebApi/ClientApp/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "Requestrr",
- "version": "2.0.5",
+ "version": "2.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/Requestrr.WebApi/ClientApp/package.json b/Requestrr.WebApi/ClientApp/package.json
index 50aef77e..e52f2243 100644
--- a/Requestrr.WebApi/ClientApp/package.json
+++ b/Requestrr.WebApi/ClientApp/package.json
@@ -1,6 +1,6 @@
{
"name": "Requestrr",
- "version": "2.0.5",
+ "version": "2.1.0",
"description": "Requestrr is a server designed to faciliate the request of media through chat applications.",
"main": "index.js",
"author": "Darkalfx",
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovie.jsx
similarity index 78%
rename from Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr.jsx
rename to Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovie.jsx
index 0eaca1ce..0104a9f8 100644
--- a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr.jsx
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovie.jsx
@@ -2,10 +2,11 @@ import React from "react";
import Loader from 'react-loader-spinner'
import { connect } from 'react-redux';
import { Alert } from "reactstrap";
-import { testOverseerrSettings } from "../../store/actions/MovieClientsActions"
-import ValidatedTextbox from "../Inputs/ValidatedTextbox"
-import Textbox from "../Inputs/Textbox"
-import Dropdown from "../Inputs/Dropdown"
+import { testOverseerrMovieSettings } from "../../../../store/actions/OverseerrClientRadarrActions"
+import { setOverseerrMovieConnectionSettings } from "../../../../store/actions/OverseerrClientRadarrActions"
+import ValidatedTextbox from "../../../Inputs/ValidatedTextbox"
+import Dropdown from "../../../Inputs/Dropdown"
+import OverseerrMovieCategoryList from "./OverseerrMovieCategoryList"
import {
FormGroup,
@@ -14,7 +15,7 @@ import {
Col
} from "reactstrap";
-class Overseerr extends React.Component {
+class OverseerrMovie extends React.Component {
constructor(props) {
super(props);
@@ -49,6 +50,10 @@ class Overseerr extends React.Component {
this.updateStateFromProps(this.props);
}
+ componentDidUpdate(prevProps) {
+ this.onValidate();
+ }
+
updateStateFromProps = props => {
this.setState({
isTestingSettings: false,
@@ -66,7 +71,8 @@ class Overseerr extends React.Component {
isDefaultApiUserIDValid: true,
useSSL: props.settings.useSSL,
apiVersion: props.settings.version,
- });
+ isValid: false,
+ }, this.onValueChange);
}
onUseSSLChanged = event => {
@@ -102,7 +108,7 @@ class Overseerr extends React.Component {
port: this.state.port,
apiKey: this.state.apiKey,
useSSL: this.state.useSSL,
- defaultApiUserID: this.state.defaultApiUserID,
+ DefaultApiUserID: this.state.DefaultApiUserID,
version: this.state.apiVersion,
})
.then(data => {
@@ -132,6 +138,14 @@ class Overseerr extends React.Component {
}
onValueChange() {
+ this.props.setConnectionSettings({
+ hostname: this.state.hostname,
+ port: this.state.port,
+ apiKey: this.state.apiKey,
+ useSSL: this.state.useSSL,
+ version: this.state.apiVersion,
+ });
+
this.props.onChange({
client: this.state.client,
hostname: this.state.hostname,
@@ -139,9 +153,6 @@ class Overseerr extends React.Component {
apiKey: this.state.apiKey,
defaultApiUserID: this.state.defaultApiUserID,
useSSL: this.state.useSSL,
- qualityProfile: this.state.qualityProfile,
- path: this.state.path,
- profile: this.state.profile,
version: this.state.apiVersion,
});
@@ -149,7 +160,43 @@ class Overseerr extends React.Component {
}
onValidate() {
- this.props.onValidate(this.state.isApiKeyValid && this.state.isHostnameValid && this.state.isPortValid && this.state.isDefaultApiUserIDValid);
+ let isValid = this.state.isApiKeyValid
+ && this.state.isHostnameValid
+ && this.state.isPortValid
+ && this.state.isDefaultApiUserIDValid
+ && (this.props.settings.categories.length == 0 || (this.props.settings.categories.every(x => this.validateCategory(x)) && this.props.settings.isRadarrServiceSettingsValid));
+
+ if (isValid !== this.state.isValid) {
+ this.setState({ isValid: isValid },
+ () => this.props.onValidate(isValid));
+ }
+ }
+
+ validateCategory(category) {
+ if (!/\S/.test(category.name)) {
+ return false;
+ }
+ else if (/^[\w-]{1,32}$/.test(category.name)) {
+ var names = this.props.settings.categories.map(x => x.name);
+
+ if (new Set(names).size !== names.length) {
+ return false;
+ }
+ else if (this.props.settings.radarrServiceSettings.radarrServices.every(x => x.id !== category.serviceId)) {
+ return false;
+ }
+ else {
+ var radarrService = this.props.settings.radarrServiceSettings.radarrServices.filter(x => x.id === category.serviceId)[0];
+
+ if (radarrService.profiles.length == 0 || radarrService.rootPaths.length == 0) {
+ return false;
+ }
+ }
+ }
+ else {
+ return false;
+ }
+ return true;
}
render() {
@@ -283,14 +330,21 @@ class Overseerr extends React.Component {
-
+
>
);
}
}
+const mapPropsToState = state => {
+ return {
+ settings: state.movies.overseerr
+ }
+};
+
const mapPropsToAction = {
- testSettings: testOverseerrSettings,
+ testSettings: testOverseerrMovieSettings,
+ setConnectionSettings: setOverseerrMovieConnectionSettings,
};
-export default connect(null, mapPropsToAction)(Overseerr);
\ No newline at end of file
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrMovie);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategory.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategory.jsx
new file mode 100644
index 00000000..c723c23b
--- /dev/null
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategory.jsx
@@ -0,0 +1,320 @@
+import React from "react";
+import Loader from 'react-loader-spinner'
+import { connect } from 'react-redux';
+import { Alert } from "reactstrap";
+import { loadRadarrServiceSettings } from "../../../../store/actions/OverseerrClientRadarrActions"
+import { setOverseerrMovieCategory } from "../../../../store/actions/OverseerrClientRadarrActions"
+import { removeOverseerrMovieCategory } from "../../../../store/actions/OverseerrClientRadarrActions"
+import ValidatedTextbox from "../../../Inputs/ValidatedTextbox"
+import Dropdown from "../../../Inputs/Dropdown"
+import MultiDropdown from "../../../Inputs/MultiDropdown"
+
+import {
+ FormGroup,
+ Input,
+ Row,
+ Col,
+ Collapse
+} from "reactstrap";
+
+class OverseerrMovieCategory extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ nameErrorMessage: "",
+ isNameValid: true,
+ isOpen: false,
+ };
+
+ this.validateName = this.validateName.bind(this);
+ this.setCategory = this.setCategory.bind(this);
+ this.deleteCategory = this.deleteCategory.bind(this);
+ }
+
+ componentDidMount() {
+ if (this.props.category.wasCreated) {
+ this.setState({ isOpen: true });
+ }
+
+ if (this.props.canConnect) {
+ this.props.loadServiceSettings(false);
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ var previousNames = prevProps.overseerr.categories.map(x => x.name);
+ var currentNames = this.props.overseerr.categories.map(x => x.name);
+
+ if (!(previousNames.length == currentNames.length && currentNames.every((value, index) => previousNames[index] == value))) {
+ this.validateName(this.props.category.name)
+ }
+
+ if (this.props.canConnect) {
+ this.props.loadServiceSettings(false);
+ }
+
+ if (prevProps.isSaving != this.props.isSaving) {
+ this.setState({
+ isOpen: false,
+ });
+ }
+ }
+
+ validateNonEmptyString = value => {
+ return /\S/.test(value);
+ }
+
+ validateName(value) {
+ var state = { isNameValid: true };
+
+ if (!/\S/.test(value)) {
+ state = {
+ ...state,
+ nameErrorMessage: "A category name is required.",
+ isNameValid: false,
+ };
+ }
+ else if (/^[\w-]{1,32}$/.test(value)) {
+ if (this.props.overseerr.categories.map(x => x.id).includes(this.props.category.id) && this.props.overseerr.categories.filter(c => typeof c.id !== 'undefined' && c.id != this.props.category.id && c.name.toLowerCase().trim() == value.toLowerCase().trim()).length > 0) {
+ state = {
+ nameErrorMessage: "All categories must have different names.",
+ isNameValid: false,
+ };
+ }
+ }
+ else {
+ state = {
+ nameErrorMessage: "Invalid categorie names, make sure they only contain alphanumeric characters, dashes and underscores. (No spaces, etc)",
+ isNameValid: false,
+ };
+ }
+
+ this.setState(state);
+
+ return state.isNameValid;
+ }
+
+ setCategory(fieldChanged, data) {
+ this.props.setOverseerrCategory(this.props.category.id, fieldChanged, data);
+ }
+
+ deleteCategory() {
+ this.setState({
+ isOpen: false,
+ }, () => setTimeout(() => this.props.removeOverseerrCategory(this.props.category.id), 150));
+ }
+
+ render() {
+ return (
+ <>
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ this.setCategory("name", newName)}
+ onValidate={isValid => this.setState({ isNameValid: isValid })} />
+
+
+
+ { return { name: x.name, value: x.id } })}
+ onChange={newServiceId => this.setCategory("serviceId", newServiceId)} />
+
+
+ {
+ this.props.overseerr.radarrServiceSettings.radarrServices.length === 0 ? (
+
+ Could not find any radarr instances.
+ )
+ : null
+ }
+
+
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].rootPaths.map(x => { return { name: x.name, value: x.name } }) : []}
+ onChange={newRootFolder => this.setCategory("rootFolder", newRootFolder)} />
+
+
+ {
+ (this.props.overseerr.radarrServiceSettings.radarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].rootPaths.map(x => { return { name: x.name, value: x.name } }) : []).length === 0 ? (
+
+ Could not find any paths.
+ )
+ : null
+ }
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].profiles.map(x => { return { name: x.name, value: x.id } }) : []}
+ onChange={newProfileId => this.setCategory("profileId", newProfileId)} />
+
+
+ {
+ (this.props.overseerr.radarrServiceSettings.radarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].profiles.map(x => { return { name: x.name, value: x.id } }) : []).length === 0 ? (
+
+ Could not find any profiles.
+ )
+ : null
+ }
+
+
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].tags : []).filter(x => this.props.category.tags.includes(x.id))}
+ items={this.props.overseerr.radarrServiceSettings.radarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.radarrServiceSettings.radarrServices.filter(x => x.id == this.props.category.serviceId)[0].tags : []}
+ onChange={newTags => this.setCategory("tags", newTags.map(x => x.id))} />
+
+
+ {
+ !this.props.overseerr.isRadarrServiceSettingsValid ? (
+
+ Could not load tags, cannot reach Overseerr.
+ )
+ : null
+ }
+
+
+
+
+
+ this.setCategory("is4K", !this.props.category.is4K)}
+ checked={this.props.category.is4K}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ >
+ );
+ }
+}
+
+const mapPropsToState = state => {
+ return {
+ overseerr: state.movies.overseerr
+ }
+};
+
+const mapPropsToAction = {
+ loadServiceSettings: loadRadarrServiceSettings,
+ setOverseerrCategory: setOverseerrMovieCategory,
+ removeOverseerrCategory: removeOverseerrMovieCategory,
+};
+
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrMovieCategory);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategoryList.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategoryList.jsx
new file mode 100644
index 00000000..3e4e9c88
--- /dev/null
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/Movies/OverseerrMovieCategoryList.jsx
@@ -0,0 +1,100 @@
+import React from "react";
+import { connect } from 'react-redux';
+import { addOverseerrMovieCategory } from "../../../../store/actions/OverseerrClientRadarrActions"
+import OverseerrMovieCategory from "./OverseerrMovieCategory";
+
+// reactstrap components
+import {
+ Button,
+ Card,
+ CardHeader,
+ CardBody,
+ FormGroup,
+ Form,
+ Input,
+ Container,
+ Row,
+ Col,
+ UncontrolledTooltip,
+} from "reactstrap";
+
+class OverseerrMovieCategoryList extends React.Component {
+ constructor(props) {
+ super(props);
+ this.createOverseerrCategory = this.createOverseerrCategory.bind(this);
+ }
+
+ createOverseerrCategory() {
+ var newId = Math.floor((Math.random() * 900) + 1);
+
+ while (this.props.overseerr.categories.map(x => x.id).includes(newId)) {
+ newId = Math.floor((Math.random() * 900) + 1);
+ }
+
+ var newCategory = {
+ id: newId,
+ name: "new-category",
+ serviceId: this.props.overseerr.radarrServiceSettings.radarrServices.length > 0 ? this.props.overseerr.radarrServiceSettings.radarrServices[0].id : -1,
+ profileId: -1,
+ rootFolder: "",
+ tags: [],
+ wasCreated: true
+ };
+
+ this.props.addOverseerrCategory(newCategory);
+ }
+
+ render() {
+ return (
+ <>
+
+
+ Overseerr Category Settings
+
+
+
+
+
+
+ Category |
+ Actions |
+
+
+
+ {this.props.overseerr.categories.map((category, key) => {
+ return (
+
+
+ )
+ })}
+
+
+
+
+
+ |
+
+
+
+
+
+
+ >
+ );
+ }
+}
+
+const mapPropsToState = state => {
+ return {
+ overseerr: state.movies.overseerr
+ }
+};
+
+const mapPropsToAction = {
+ addOverseerrCategory: addOverseerrMovieCategory,
+};
+
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrMovieCategoryList);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShow.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShow.jsx
new file mode 100644
index 00000000..9ea9967b
--- /dev/null
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShow.jsx
@@ -0,0 +1,350 @@
+import React from "react";
+import Loader from 'react-loader-spinner'
+import { connect } from 'react-redux';
+import { Alert } from "reactstrap";
+import { testOverseerrTvShowSettings } from "../../../../store/actions/OverseerrClientSonarrActions"
+import { setOverseerrTvShowConnectionSettings } from "../../../../store/actions/OverseerrClientSonarrActions"
+import ValidatedTextbox from "../../../Inputs/ValidatedTextbox"
+import Dropdown from "../../../Inputs/Dropdown"
+import OverseerrTvShowCategoryList from "./OverseerrTvShowCategoryList"
+
+import {
+ FormGroup,
+ Input,
+ Row,
+ Col
+} from "reactstrap";
+
+class OverseerrTvShow extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isTestingSettings: false,
+ testSettingsRequested: false,
+ testSettingsSuccess: false,
+ testSettingsError: "",
+ hostname: "",
+ isHostnameValid: false,
+ port: "7878",
+ isPortValid: false,
+ apiKey: "",
+ isApiKeyValid: false,
+ defaultApiUserID: "",
+ isDefaultApiUserIDValid: true,
+ useSSL: "",
+ apiVersion: "",
+ };
+
+ this.onTestSettings = this.onTestSettings.bind(this);
+ this.onUseSSLChanged = this.onUseSSLChanged.bind(this);
+ this.onValueChange = this.onValueChange.bind(this);
+ this.onValidate = this.onValidate.bind(this);
+ this.updateStateFromProps = this.updateStateFromProps.bind(this);
+ this.validateNonEmptyString = this.validateNonEmptyString.bind(this);
+ this.validatePort = this.validatePort.bind(this);
+ this.validateDefaultUserId = this.validateDefaultUserId.bind(this);
+ }
+
+ componentDidMount() {
+ this.updateStateFromProps(this.props);
+ }
+
+ componentDidUpdate(prevProps) {
+ this.onValidate();
+ }
+
+ updateStateFromProps = props => {
+ this.setState({
+ isTestingSettings: false,
+ TestingSettings: false,
+ testSettingsRequested: false,
+ testSettingsSuccess: false,
+ testSettingsError: "",
+ hostname: props.settings.hostname,
+ isHostnameValid: false,
+ port: props.settings.port,
+ isPortValid: false,
+ apiKey: props.settings.apiKey,
+ isApiKeyValid: false,
+ defaultApiUserID: props.settings.defaultApiUserID,
+ isDefaultApiUserIDValid: true,
+ useSSL: props.settings.useSSL,
+ apiVersion: props.settings.version,
+ isValid: false,
+ }, this.onValueChange);
+ }
+
+ onUseSSLChanged = event => {
+ this.setState({
+ useSSL: !this.state.useSSL
+ }, this.onValueChange);
+ }
+
+ validateNonEmptyString = value => {
+ return /\S/.test(value);
+ }
+
+ validatePort = value => {
+ return /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/.test(value);
+ }
+
+ validateDefaultUserId = value => {
+ return (!value || value.length === 0 || /^\s*$/.test(value)) || (/^[1-9]\d*$/).test(value);
+ }
+
+ onTestSettings = e => {
+ e.preventDefault();
+
+ if (!this.state.isTestingSettings
+ && this.state.isHostnameValid
+ && this.state.isPortValid
+ && this.state.isDefaultApiUserIDValid
+ && this.state.isApiKeyValid) {
+ this.setState({ isTestingSettings: true });
+
+ this.props.testSettings({
+ hostname: this.state.hostname,
+ port: this.state.port,
+ apiKey: this.state.apiKey,
+ useSSL: this.state.useSSL,
+ DefaultApiUserID: this.state.DefaultApiUserID,
+ version: this.state.apiVersion,
+ })
+ .then(data => {
+ this.setState({ isTestingSettings: false });
+
+ if (data.ok) {
+ this.setState({
+ testSettingsRequested: true,
+ testSettingsError: "",
+ testSettingsSuccess: true
+ });
+ }
+ else {
+ var error = "An unknown error occurred while testing the settings";
+
+ if (typeof (data.error) === "string")
+ error = data.error;
+
+ this.setState({
+ testSettingsRequested: true,
+ testSettingsError: error,
+ testSettingsSuccess: false
+ });
+ }
+ });
+ }
+ }
+
+ onValueChange() {
+ this.props.setConnectionSettings({
+ hostname: this.state.hostname,
+ port: this.state.port,
+ apiKey: this.state.apiKey,
+ useSSL: this.state.useSSL,
+ version: this.state.apiVersion,
+ });
+
+ this.props.onChange({
+ client: this.state.client,
+ hostname: this.state.hostname,
+ port: this.state.port,
+ apiKey: this.state.apiKey,
+ defaultApiUserID: this.state.defaultApiUserID,
+ useSSL: this.state.useSSL,
+ version: this.state.apiVersion,
+ });
+
+ this.onValidate();
+ }
+
+ onValidate() {
+ let isValid = this.state.isApiKeyValid
+ && this.state.isHostnameValid
+ && this.state.isPortValid
+ && this.state.isDefaultApiUserIDValid
+ && (this.props.settings.categories.length == 0 || (this.props.settings.categories.every(x => this.validateCategory(x)) && this.props.settings.isSonarrServiceSettingsValid));
+
+ if (isValid !== this.state.isValid) {
+ this.setState({ isValid: isValid },
+ () => this.props.onValidate(isValid));
+ }
+ }
+
+ validateCategory(category) {
+ if (!/\S/.test(category.name)) {
+ return false;
+ }
+ else if (/^[\w-]{1,32}$/.test(category.name)) {
+ var names = this.props.settings.categories.map(x => x.name);
+
+ if (new Set(names).size !== names.length) {
+ return false;
+ }
+ else if (this.props.settings.sonarrServiceSettings.sonarrServices.every(x => x.id !== category.serviceId)) {
+ return false;
+ }
+ else {
+ var sonarrService = this.props.settings.sonarrServiceSettings.sonarrServices.filter(x => x.id === category.serviceId)[0];
+
+ if (sonarrService.profiles.length == 0 || sonarrService.rootPaths.length == 0) {
+ return false;
+ }
+ }
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ render() {
+ return (
+ <>
+
+
+ Overseerr Connection Settings
+
+
+
+
+
+ this.setState({ apiVersion: newApiVersion }, this.onValueChange)} />
+
+
+ this.setState({ apiKey: newApiKey }, this.onValueChange)}
+ onValidate={isValid => this.setState({ isApiKeyValid: isValid }, this.onValidate)} />
+
+
+
+
+ this.setState({ hostname: newHostname }, this.onValueChange)}
+ onValidate={isValid => this.setState({ isHostnameValid: isValid }, this.onValidate)} />
+
+
+ this.setState({ port: newPort }, this.onValueChange)}
+ onValidate={isValid => this.setState({ isPortValid: isValid }, this.onValidate)} />
+
+
+
+
+ this.setState({ defaultApiUserID: newDefaultApiUserID }, this.onValueChange)}
+ onValidate={isValid => this.setState({ isDefaultApiUserIDValid: isValid }, this.onValidate)} />
+
+
+
+
+
+
+
+
+
+
+
+ Click here to view how configure Overseerr permissions with the bot
+
+
+
+
+
+ {
+ this.state.testSettingsRequested && !this.state.isTestingSettings ?
+ !this.state.testSettingsSuccess ? (
+
+ {this.state.testSettingsError}
+ )
+ :
+ The specified settings are valid.
+
+ : null
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+}
+
+const mapPropsToState = state => {
+ return {
+ settings: state.tvShows.overseerr
+ }
+};
+
+const mapPropsToAction = {
+ testSettings: testOverseerrTvShowSettings,
+ setConnectionSettings: setOverseerrTvShowConnectionSettings,
+};
+
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrTvShow);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategory.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategory.jsx
new file mode 100644
index 00000000..39685e71
--- /dev/null
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategory.jsx
@@ -0,0 +1,351 @@
+import React from "react";
+import Loader from 'react-loader-spinner'
+import { connect } from 'react-redux';
+import { Alert } from "reactstrap";
+import { loadSonarrServiceSettings } from "../../../../store/actions/OverseerrClientSonarrActions"
+import { setOverseerrTvShowCategory } from "../../../../store/actions/OverseerrClientSonarrActions"
+import { removeOverseerrTvShowCategory } from "../../../../store/actions/OverseerrClientSonarrActions"
+import ValidatedTextbox from "../../../Inputs/ValidatedTextbox"
+import Dropdown from "../../../Inputs/Dropdown"
+import MultiDropdown from "../../../Inputs/MultiDropdown"
+
+import {
+ FormGroup,
+ Input,
+ Row,
+ Col,
+ Collapse
+} from "reactstrap";
+
+class OverseerrTvShowCategory extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ nameErrorMessage: "",
+ isNameValid: true,
+ isOpen: false,
+ };
+
+ this.validateName = this.validateName.bind(this);
+ this.setCategory = this.setCategory.bind(this);
+ this.deleteCategory = this.deleteCategory.bind(this);
+ }
+
+ componentDidMount() {
+ if (this.props.category.wasCreated) {
+ this.setState({ isOpen: true });
+ }
+
+ if (this.props.canConnect) {
+ this.props.loadServiceSettings(false);
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ var previousNames = prevProps.overseerr.categories.map(x => x.name);
+ var currentNames = this.props.overseerr.categories.map(x => x.name);
+
+ if (!(previousNames.length == currentNames.length && currentNames.every((value, index) => previousNames[index] == value))) {
+ this.validateName(this.props.category.name)
+ }
+
+ if (this.props.canConnect) {
+ this.props.loadServiceSettings(false);
+ }
+
+ if (prevProps.isSaving != this.props.isSaving) {
+ this.setState({
+ isOpen: false,
+ });
+ }
+ }
+
+ validateNonEmptyString = value => {
+ return /\S/.test(value);
+ }
+
+ validateName(value) {
+ var state = { isNameValid: true };
+
+ if (!/\S/.test(value)) {
+ state = {
+ ...state,
+ nameErrorMessage: "A category name is required.",
+ isNameValid: false,
+ };
+ }
+ else if (/^[\w-]{1,32}$/.test(value)) {
+ if (this.props.overseerr.categories.map(x => x.id).includes(this.props.category.id) && this.props.overseerr.categories.filter(c => typeof c.id !== 'undefined' && c.id != this.props.category.id && c.name.toLowerCase().trim() == value.toLowerCase().trim()).length > 0) {
+ state = {
+ nameErrorMessage: "All categories must have different names.",
+ isNameValid: false,
+ };
+ }
+ }
+ else {
+ state = {
+ nameErrorMessage: "Invalid categorie names, make sure they only contain alphanumeric characters, dashes and underscores. (No spaces, etc)",
+ isNameValid: false,
+ };
+ }
+
+ this.setState(state);
+
+ return state.isNameValid;
+ }
+
+ setCategory(fieldChanged, data) {
+ this.props.setOverseerrCategory(this.props.category.id, fieldChanged, data);
+ }
+
+ deleteCategory() {
+ this.setState({
+ isOpen: false,
+ }, () => setTimeout(() => this.props.removeOverseerrCategory(this.props.category.id), 150));
+ }
+
+ render() {
+ return (
+ <>
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ this.setCategory("name", newName)}
+ onValidate={isValid => this.setState({ isNameValid: isValid })} />
+
+
+
+ { return { name: x.name, value: x.id } })}
+ onChange={newServiceId => this.setCategory("serviceId", newServiceId)} />
+
+
+ {
+ this.props.overseerr.sonarrServiceSettings.sonarrServices.length === 0 ? (
+
+ Could not find any sonarr instances.
+ )
+ : null
+ }
+
+
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].rootPaths.map(x => { return { name: x.name, value: x.name } }) : []}
+ onChange={newRootFolder => this.setCategory("rootFolder", newRootFolder)} />
+
+
+ {
+ (this.props.overseerr.sonarrServiceSettings.sonarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].rootPaths.map(x => { return { name: x.name, value: x.name } }) : []).length === 0 ? (
+
+ Could not find any paths.
+ )
+ : null
+ }
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].profiles.map(x => { return { name: x.name, value: x.id } }) : []}
+ onChange={newProfileId => this.setCategory("profileId", newProfileId)} />
+
+
+ {
+ (this.props.overseerr.sonarrServiceSettings.sonarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].profiles.map(x => { return { name: x.name, value: x.id } }) : []).length === 0 ? (
+
+ Could not find any profiles.
+ )
+ : null
+ }
+
+
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].languageProfiles.map(x => { return { name: x.name, value: x.id } }) : []}
+ onChange={newLanguageId => this.setCategory("languageProfileId", newLanguageId)} />
+
+
+ {
+ (this.props.overseerr.sonarrServiceSettings.sonarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].languageProfiles.map(x => { return { name: x.name, value: x.id } }) : []).length === 0 ? (
+
+ Could not find any languages.
+ )
+ : null
+ }
+
+
+
+ x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].tags : []).filter(x => this.props.category.tags.includes(x.id))}
+ items={this.props.overseerr.sonarrServiceSettings.sonarrServices.some(x => x.id == this.props.category.serviceId) ? this.props.overseerr.sonarrServiceSettings.sonarrServices.filter(x => x.id == this.props.category.serviceId)[0].tags : []}
+ onChange={newTags => this.setCategory("tags", newTags.map(x => x.id))} />
+
+
+ {
+ !this.props.overseerr.isSonarrServiceSettingsValid ? (
+
+ Could not load tags, cannot reach Overseerr.
+ )
+ : null
+ }
+
+
+
+
+
+ this.setCategory("is4K", !this.props.category.is4K)}
+ checked={this.props.category.is4K}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ >
+ );
+ }
+}
+
+const mapPropsToState = state => {
+ return {
+ overseerr: state.tvShows.overseerr
+ }
+};
+
+const mapPropsToAction = {
+ loadServiceSettings: loadSonarrServiceSettings,
+ setOverseerrCategory: setOverseerrTvShowCategory,
+ removeOverseerrCategory: removeOverseerrTvShowCategory,
+};
+
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrTvShowCategory);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategoryList.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategoryList.jsx
new file mode 100644
index 00000000..1c8faf56
--- /dev/null
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Overseerr/TvShows/OverseerrTvShowCategoryList.jsx
@@ -0,0 +1,100 @@
+import React from "react";
+import { connect } from 'react-redux';
+import { addOverseerrTvShowCategory } from "../../../../store/actions/OverseerrClientSonarrActions"
+import OverseerrTvShowCategory from "./OverseerrTvShowCategory";
+
+// reactstrap components
+import {
+ Button,
+ Card,
+ CardHeader,
+ CardBody,
+ FormGroup,
+ Form,
+ Input,
+ Container,
+ Row,
+ Col,
+ UncontrolledTooltip,
+} from "reactstrap";
+
+class OverseerrTvShowCategoryList extends React.Component {
+ constructor(props) {
+ super(props);
+ this.createOverseerrCategory = this.createOverseerrCategory.bind(this);
+ }
+
+ createOverseerrCategory() {
+ var newId = Math.floor((Math.random() * 900) + 1);
+
+ while (this.props.overseerr.categories.map(x => x.id).includes(newId)) {
+ newId = Math.floor((Math.random() * 900) + 1);
+ }
+
+ var newCategory = {
+ id: newId,
+ name: "new-category",
+ serviceId: this.props.overseerr.sonarrServiceSettings.sonarrServices.length > 0 ? this.props.overseerr.sonarrServiceSettings.sonarrServices[0].id : -1,
+ profileId: -1,
+ rootFolder: "",
+ tags: [],
+ wasCreated: true
+ };
+
+ this.props.addOverseerrCategory(newCategory);
+ }
+
+ render() {
+ return (
+ <>
+
+
+ Overseerr Category Settings
+
+
+
+
+
+
+ Category |
+ Actions |
+
+
+
+ {this.props.overseerr.categories.map((category, key) => {
+ return (
+
+
+ )
+ })}
+
+
+
+
+
+ |
+
+
+
+
+
+
+ >
+ );
+ }
+}
+
+const mapPropsToState = state => {
+ return {
+ overseerr: state.tvShows.overseerr
+ }
+};
+
+const mapPropsToAction = {
+ addOverseerrCategory: addOverseerrTvShowCategory,
+};
+
+export default connect(mapPropsToState, mapPropsToAction)(OverseerrTvShowCategoryList);
\ No newline at end of file
diff --git a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Radarr/RadarrCategory.jsx b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Radarr/RadarrCategory.jsx
index 9fbbdb64..b2d3f98a 100644
--- a/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Radarr/RadarrCategory.jsx
+++ b/Requestrr.WebApi/ClientApp/src/components/DownloadClients/Radarr/RadarrCategory.jsx
@@ -24,26 +24,18 @@ class RadarrCategory extends React.Component {
super(props);
this.state = {
- arePathsValid: true,
- areProfilesValid: true,
- areTagsValid: true,
nameErrorMessage: "",
isNameValid: true,
isOpen: false,
};
this.validateName = this.validateName.bind(this);
- this.setPaths = this.setPaths.bind(this);
- this.setProfiles = this.setProfiles.bind(this);
- this.setTags = this.setTags.bind(this);
this.setCategory = this.setCategory.bind(this);
this.deleteCategory = this.deleteCategory.bind(this);
}
componentDidMount() {
- var category = { ...this.props.category };
-
- if (category.wasCreated) {
+ if (this.props.category.wasCreated) {
this.setState({ isOpen: true });
}
@@ -52,22 +44,14 @@ class RadarrCategory extends React.Component {
this.props.loadRootPaths(false);
this.props.loadTags(false);
}
-
- category = this.setPaths(category);
- category = this.setProfiles(category);
- category = this.setTags(category);
-
- this.setCategory(category)
}
componentDidUpdate(prevProps, prevState) {
- var category = { ...this.props.category };
-
var previousNames = prevProps.radarr.categories.map(x => x.name);
var currentNames = this.props.radarr.categories.map(x => x.name);
if (!(previousNames.length == currentNames.length && currentNames.every((value, index) => previousNames[index] == value))) {
- this.validateName(category.name)
+ this.validateName(this.props.category.name)
}
if (this.props.canConnect) {
@@ -76,22 +60,6 @@ class RadarrCategory extends React.Component {
this.props.loadTags(false);
}
- if (!(prevProps.radarr.tags.length == this.props.radarr.tags.length && prevProps.radarr.tags.reduce((a, b, i) => a && this.props.radarr.tags[i], true))) {
- category = this.setTags(category);
- }
-
- if (!(prevProps.radarr.profiles.length == this.props.radarr.profiles.length && prevProps.radarr.profiles.reduce((a, b, i) => a && this.props.radarr.profiles[i], true))) {
- category = this.setProfiles(category);
- }
-
- if (!(prevProps.radarr.paths.length == this.props.radarr.paths.length && prevProps.radarr.paths.reduce((a, b, i) => a && this.props.radarr.paths[i], true))) {
- category = this.setPaths(category);
- }
-
- if (JSON.stringify(category) !== JSON.stringify(this.props.category)) {
- this.setCategory(category)
- }
-
if (prevProps.isSaving != this.props.isSaving) {
this.setState({
isOpen: false,
@@ -99,38 +67,6 @@ class RadarrCategory extends React.Component {
}
}
- setPaths(category) {
- if (this.props.radarr.paths.length > 0) {
- var defaultPathId = this.props.radarr.paths[0].path;
- var pathValue = this.props.radarr.paths.map(x => x.path).includes(category.rootFolder) ? category.rootFolder : defaultPathId;
-
- category = { ...category, rootFolder: pathValue };
- }
-
- return category;
- }
-
- setProfiles(category) {
- if (this.props.radarr.profiles.length > 0) {
- var defaultProfileId = this.props.radarr.profiles[0].id;
- var profileValue = this.props.radarr.profiles.map(x => x.id).includes(category.profileId) ? category.profileId : defaultProfileId;
-
- category = { ...category, profileId: profileValue };
- }
-
- return category;
- }
-
- setTags(category) {
- if (this.props.radarr.tags.length > 0) {
- var tagsValue = category.tags.filter(x => this.props.radarr.tags.map(x => x.id).includes(x));
-
- category = { ...category, tags: tagsValue };
- }
-
- return category;
- }
-
validateNonEmptyString = value => {
return /\S/.test(value);
}
@@ -164,9 +100,9 @@ class RadarrCategory extends React.Component {
return state.isNameValid;
}
-
- setCategory(category) {
- this.props.setRadarrCategory(category);
+
+ setCategory(fieldChanged, data) {
+ this.props.setRadarrCategory(this.props.category.id, fieldChanged, data);
}
deleteCategory() {
@@ -207,7 +143,7 @@ class RadarrCategory extends React.Component {
isSubmitted={this.props.isSubmitted || this.state.isNameValid}
value={this.props.category.name}
validation={this.validateName}
- onChange={newName => this.setCategory({ ...this.props.category, name: newName })}
+ onChange={newName => this.setCategory("name", newName)}
onValidate={isValid => this.setState({ isNameValid: isValid })} />
@@ -218,7 +154,7 @@ class RadarrCategory extends React.Component {
name="Path"
value={this.props.category.rootFolder}
items={this.props.radarr.paths.map(x => { return { name: x.path, value: x.path } })}
- onChange={newPath => this.setCategory({ ...this.props.category, rootFolder: newPath })} />
+ onChange={newPath => this.setCategory("rootFolder", newPath)} />