Fixed: Backend Updates from Sonarr

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Co-Authored-By: taloth <taloth@users.noreply.github.com>
This commit is contained in:
Qstick
2019-06-30 21:50:01 -04:00
parent d178dce0d3
commit 91ab518dfb
131 changed files with 2422 additions and 988 deletions

View File

@@ -54,7 +54,8 @@ class DownloadClient extends Component {
const {
id,
name,
enable
enable,
priority
} = this.props;
return (
@@ -80,6 +81,16 @@ class DownloadClient extends Component {
Disabled
</Label>
}
{
priority > 1 &&
<Label
kind={kinds.DISABLED}
outline={true}
>
Priority: {priority}
</Label>
}
</div>
<EditDownloadClientModalConnector
@@ -107,6 +118,7 @@ DownloadClient.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
priority: PropTypes.number.isRequired,
onConfirmDeleteDownloadClient: PropTypes.func.isRequired
};

View File

@@ -44,6 +44,7 @@ class EditDownloadClientModalContent extends Component {
implementationName,
name,
enable,
priority,
fields,
message
} = item;
@@ -67,9 +68,7 @@ class EditDownloadClientModalContent extends Component {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
{
!!message &&
<Alert
@@ -117,6 +116,23 @@ class EditDownloadClientModalContent extends Component {
})
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Client Priority</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="priority"
helpText="Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority."
min={1}
max={50}
{...priority}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>

View File

@@ -23,6 +23,7 @@ function EditRemotePathMappingModalContent(props) {
isSaving,
saveError,
item,
downloadClientHosts,
onInputChange,
onSavePress,
onModalClose,
@@ -55,17 +56,16 @@ function EditRemotePathMappingModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Host</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
type={inputTypes.SELECT}
name="host"
helpText="The same host you specified for the remote Download Client"
{...host}
values={downloadClientHosts}
onChange={onInputChange}
/>
</FormGroup>
@@ -140,6 +140,7 @@ EditRemotePathMappingModalContent.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.shape(remotePathMappingShape).isRequired,
downloadClientHosts: PropTypes.arrayOf(PropTypes.string).isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,

View File

@@ -13,11 +13,39 @@ const newRemotePathMapping = {
localPath: ''
};
const selectDownloadClientHosts = createSelector(
(state) => state.settings.downloadClients.items,
(downloadClients) => {
const hosts = downloadClients.reduce((acc, downloadClient) => {
const name = downloadClient.name;
const host = downloadClient.fields.find((field) => {
return field.name === 'host';
});
if (host) {
const group = acc[host.value] = acc[host.value] || [];
group.push(name);
}
return acc;
}, {});
return Object.keys(hosts).map((host) => {
return {
key: host,
value: host,
hint: `${hosts[host].join(', ')}`
};
});
}
);
function createRemotePathMappingSelector() {
return createSelector(
(state, { id }) => id,
(state) => state.settings.remotePathMappings,
(id, remotePathMappings) => {
selectDownloadClientHosts,
(id, remotePathMappings, downloadClientHosts) => {
const {
isFetching,
error,
@@ -37,7 +65,8 @@ function createRemotePathMappingSelector() {
isSaving,
saveError,
item: settings.settings,
...settings
...settings,
downloadClientHosts
};
}
);
@@ -55,8 +84,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setRemotePathMappingValue,
saveRemotePathMapping
dispatchSetRemotePathMappingValue: setRemotePathMappingValue,
dispatchSaveRemotePathMapping: saveRemotePathMapping
};
class EditRemotePathMappingModalContentConnector extends Component {
@@ -67,7 +96,7 @@ class EditRemotePathMappingModalContentConnector extends Component {
componentDidMount() {
if (!this.props.id) {
Object.keys(newRemotePathMapping).forEach((name) => {
this.props.setRemotePathMappingValue({
this.props.dispatchSetRemotePathMappingValue({
name,
value: newRemotePathMapping[name]
});
@@ -85,11 +114,11 @@ class EditRemotePathMappingModalContentConnector extends Component {
// Listeners
onInputChange = ({ name, value }) => {
this.props.setRemotePathMappingValue({ name, value });
this.props.dispatchSetRemotePathMappingValue({ name, value });
}
onSavePress = () => {
this.props.saveRemotePathMapping({ id: this.props.id });
this.props.dispatchSaveRemotePathMapping({ id: this.props.id });
}
//
@@ -111,8 +140,8 @@ EditRemotePathMappingModalContentConnector.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
setRemotePathMappingValue: PropTypes.func.isRequired,
saveRemotePathMapping: PropTypes.func.isRequired,
dispatchSetRemotePathMappingValue: PropTypes.func.isRequired,
dispatchSaveRemotePathMapping: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -49,7 +49,8 @@ function BackupSettings(props) {
<FormInputGroup
type={inputTypes.NUMBER}
name="backupInterval"
helpText="Interval in days"
unit="days"
helpText="Interval between automatic backups"
onChange={onInputChange}
{...backupInterval}
/>
@@ -64,7 +65,8 @@ function BackupSettings(props) {
<FormInputGroup
type={inputTypes.NUMBER}
name="backupRetention"
helpText="Retention in days. Automatic backups older the retention will be cleaned up automatically"
unit="days"
helpText="Automatic backups older the retention will be cleaned up automatically"
onChange={onInputChange}
{...backupRetention}
/>

View File

@@ -16,6 +16,19 @@ import ProxySettings from './ProxySettings';
import SecuritySettings from './SecuritySettings';
import UpdateSettings from './UpdateSettings';
const requiresRestartKeys = [
'bindAddress',
'port',
'urlBase',
'enableSsl',
'sslPort',
'sslCertHash',
'authenticationMethod',
'username',
'password',
'apiKey'
];
class GeneralSettings extends Component {
//
@@ -42,20 +55,7 @@ class GeneralSettings extends Component {
const prevSettings = prevProps.settings;
const keys = [
'bindAddress',
'port',
'urlBase',
'enableSsl',
'sslPort',
'sslCertHash',
'authenticationMethod',
'username',
'password',
'apiKey'
];
const pendingRestart = _.some(keys, (key) => {
const pendingRestart = _.some(requiresRestartKeys, (key) => {
const setting = settings[key];
const prevSetting = prevSettings[key];
@@ -98,6 +98,7 @@ class GeneralSettings extends Component {
isResettingApiKey,
isMono,
isWindows,
isWindowsService,
mode,
onInputChange,
onConfirmResetApiKey,
@@ -177,7 +178,9 @@ class GeneralSettings extends Component {
isOpen={this.state.isRestartRequiredModalOpen}
kind={kinds.DANGER}
title="Restart Radarr"
message="Radarr requires a restart to apply changes, do you want to restart now?"
message={
`Radarr requires a restart to apply changes, do you want to restart now? ${isWindowsService ? 'Depending which user is running the Radarr service you may need to restart Radarr as admin once before the service will start automatically.' : ''}`
}
cancelLabel="I'll restart later"
confirmLabel="Restart Now"
onConfirm={this.onConfirmRestart}
@@ -201,6 +204,7 @@ GeneralSettings.propTypes = {
hasSettings: PropTypes.bool.isRequired,
isMono: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired,
isWindowsService: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired,

View File

@@ -26,6 +26,7 @@ function createMapStateToProps() {
isResettingApiKey,
isMono: systemStatus.isMono,
isWindows: systemStatus.isWindows,
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
mode: systemStatus.mode,
...sectionSettings
};
@@ -58,7 +59,7 @@ class GeneralSettingsConnector extends Component {
}
componentWillUnmount() {
this.props.clearPendingChanges({ section: SECTION });
this.props.clearPendingChanges({ section: `settings.${SECTION}` });
}
//

View File

@@ -87,56 +87,59 @@ function HostSettings(props) {
</FormGroup>
{
enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Port</FormLabel>
enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Port</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslPort}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslPort}
/>
</FormGroup> :
null
}
{
isWindows && enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Hash</FormLabel>
isWindows && enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Hash</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertHash"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertHash}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertHash"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertHash}
/>
</FormGroup> :
null
}
{
mode !== 'service' &&
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
isWindows && mode !== 'service' ?
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText=" Open a web browser and navigate to Radarr homepage on app start."
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText=" Open a web browser and navigate to Radarr homepage on app start."
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup> :
null
}
</FieldSet>

View File

@@ -3,10 +3,12 @@ import React, { Component } from 'react';
import ReactSlider from 'react-slider';
import formatBytes from 'Utilities/Number/formatBytes';
import roundNumber from 'Utilities/Number/roundNumber';
import { kinds } from 'Helpers/Props';
import { kinds, tooltipPositions } from 'Helpers/Props';
import Label from 'Components/Label';
import NumberInput from 'Components/Form/NumberInput';
import TextInput from 'Components/Form/TextInput';
import Popover from 'Components/Tooltip/Popover';
import QualityDefinitionLimits from './QualityDefinitionLimits';
import styles from './QualityDefinition.css';
const MIN = 0;
@@ -139,12 +141,10 @@ class QualityDefinition extends Component {
} = this.state;
const minBytes = minSize * 1024 * 1024;
const minThirty = formatBytes(minBytes * 90, 2);
const minSixty = formatBytes(minBytes * 140, 2);
const minSixty = `${formatBytes(minBytes * 60)}/h`;
const maxBytes = maxSize && maxSize * 1024 * 1024;
const maxThirty = maxBytes ? formatBytes(maxBytes * 90, 2) : 'Unlimited';
const maxSixty = maxBytes ? formatBytes(maxBytes * 140, 2) : 'Unlimited';
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/h` : 'Unlimited';
return (
<div className={styles.qualityDefinition}>
@@ -178,13 +178,35 @@ class QualityDefinition extends Component {
<div className={styles.sizes}>
<div>
<Label kind={kinds.WARNING}>{minThirty}</Label>
<Label kind={kinds.INFO}>{minSixty}</Label>
<Popover
anchor={
<Label kind={kinds.INFO}>{minSixty}</Label>
}
title="Minimum Limits"
body={
<QualityDefinitionLimits
bytes={minBytes}
message="No minimum for any runtime"
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
<div>
<Label kind={kinds.WARNING}>{maxThirty}</Label>
<Label kind={kinds.INFO}>{maxSixty}</Label>
<Popover
anchor={
<Label kind={kinds.WARNING}>{maxSixty}</Label>
}
title="Maximum Limits"
body={
<QualityDefinitionLimits
bytes={maxBytes}
message="No limit for any runtime"
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
</div>
</div>

View File

@@ -12,15 +12,15 @@ function QualityDefinitionLimits(props) {
return <div>{message}</div>;
}
const thirty = formatBytes(bytes * 30);
const fourtyFive = formatBytes(bytes * 45);
const sixty = formatBytes(bytes * 60);
const ninety = formatBytes(bytes * 90);
const hundredTwenty = formatBytes(bytes * 120);
return (
<div>
<div>30 Minutes: {thirty}</div>
<div>45 Minutes: {fourtyFive}</div>
<div>60 Minutes: {sixty}</div>
<div>90 Minutes: {ninety}</div>
<div>120 Minutes: {hundredTwenty}</div>
</div>
);
}