You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
657 lines
22 KiB
657 lines
22 KiB
import _ from 'lodash';
|
|
import PropTypes from 'prop-types';
|
|
import React, { Component } from 'react';
|
|
import { icons, inputTypes, kinds, sizes } from 'Helpers/Props';
|
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
import FieldSet from 'Components/FieldSet';
|
|
import Icon from 'Components/Icon';
|
|
import ClipboardButton from 'Components/Link/ClipboardButton';
|
|
import PageContent from 'Components/Page/PageContent';
|
|
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
|
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
|
import Form from 'Components/Form/Form';
|
|
import FormGroup from 'Components/Form/FormGroup';
|
|
import FormLabel from 'Components/Form/FormLabel';
|
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
import FormInputButton from 'Components/Form/FormInputButton';
|
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
|
|
class GeneralSettings extends Component {
|
|
|
|
//
|
|
// Lifecycle
|
|
|
|
constructor(props, context) {
|
|
super(props, context);
|
|
|
|
this.state = {
|
|
isConfirmApiKeyResetModalOpen: false,
|
|
isRestartRequiredModalOpen: false
|
|
};
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
const {
|
|
settings,
|
|
isSaving,
|
|
saveError
|
|
} = this.props;
|
|
|
|
if (isSaving || saveError || !prevProps.isSaving) {
|
|
return;
|
|
}
|
|
|
|
const prevSettings = prevProps.settings;
|
|
|
|
const keys = [
|
|
'bindAddress',
|
|
'port',
|
|
'urlBase',
|
|
'enableSsl',
|
|
'sslPort',
|
|
'sslCertHash',
|
|
'authenticationMethod',
|
|
'username',
|
|
'password',
|
|
'apiKey'
|
|
];
|
|
|
|
const pendingRestart = _.some(keys, (key) => {
|
|
const setting = settings[key];
|
|
const prevSetting = prevSettings[key];
|
|
|
|
if (!setting || !prevSetting) {
|
|
return false;
|
|
}
|
|
|
|
const previousValue = prevSetting.previousValue;
|
|
const value = setting.value;
|
|
|
|
return previousValue != null && previousValue !== value;
|
|
});
|
|
|
|
this.setState({ isRestartRequiredModalOpen: pendingRestart });
|
|
}
|
|
|
|
//
|
|
// Listeners
|
|
|
|
onApikeyFocus = (event) => {
|
|
event.target.select();
|
|
}
|
|
|
|
onResetApiKeyPress = () => {
|
|
this.setState({ isConfirmApiKeyResetModalOpen: true });
|
|
}
|
|
|
|
onConfirmResetApiKey = () => {
|
|
this.setState({ isConfirmApiKeyResetModalOpen: false });
|
|
this.props.onConfirmResetApiKey();
|
|
}
|
|
|
|
onCloseResetApiKeyModal = () => {
|
|
this.setState({ isConfirmApiKeyResetModalOpen: false });
|
|
}
|
|
|
|
onConfirmRestart = () => {
|
|
this.setState({ isRestartRequiredModalOpen: false });
|
|
this.props.onConfirmRestart();
|
|
}
|
|
|
|
onCloseRestartRequiredModalOpen = () => {
|
|
this.setState({ isRestartRequiredModalOpen: false });
|
|
}
|
|
|
|
//
|
|
// Render
|
|
|
|
render() {
|
|
const {
|
|
advancedSettings,
|
|
isFetching,
|
|
isPopulated,
|
|
error,
|
|
settings,
|
|
hasSettings,
|
|
isResettingApiKey,
|
|
isMono,
|
|
isWindows,
|
|
mode,
|
|
onInputChange,
|
|
...otherProps
|
|
} = this.props;
|
|
|
|
const {
|
|
isConfirmApiKeyResetModalOpen,
|
|
isRestartRequiredModalOpen
|
|
} = this.state;
|
|
|
|
const {
|
|
bindAddress,
|
|
port,
|
|
urlBase,
|
|
enableSsl,
|
|
sslPort,
|
|
sslCertHash,
|
|
launchBrowser,
|
|
authenticationMethod,
|
|
username,
|
|
password,
|
|
apiKey,
|
|
proxyEnabled,
|
|
proxyType,
|
|
proxyHostname,
|
|
proxyPort,
|
|
proxyUsername,
|
|
proxyPassword,
|
|
proxyBypassFilter,
|
|
proxyBypassLocalAddresses,
|
|
logLevel,
|
|
analyticsEnabled,
|
|
branch,
|
|
updateAutomatically,
|
|
updateMechanism,
|
|
updateScriptPath
|
|
} = settings;
|
|
|
|
const authenticationMethodOptions = [
|
|
{ key: 'none', value: 'None' },
|
|
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
|
{ key: 'forms', value: 'Forms (Login Page)' }
|
|
];
|
|
|
|
const proxyTypeOptions = [
|
|
{ key: 'http', value: 'HTTP(S)' },
|
|
{ key: 'socks4', value: 'Socks4' },
|
|
{ key: 'socks5', value: 'Socks5 (Support TOR)' }
|
|
];
|
|
|
|
const logLevelOptions = [
|
|
{ key: 'info', value: 'Info' },
|
|
{ key: 'debug', value: 'Debug' },
|
|
{ key: 'trace', value: 'Trace' }
|
|
];
|
|
|
|
const updateOptions = [
|
|
{ key: 'builtIn', value: 'Built-In' },
|
|
{ key: 'script', value: 'Script' }
|
|
];
|
|
|
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
|
|
|
return (
|
|
<PageContent title="General Settings">
|
|
<SettingsToolbarConnector
|
|
{...otherProps}
|
|
/>
|
|
|
|
<PageContentBodyConnector>
|
|
{
|
|
isFetching && !isPopulated &&
|
|
<LoadingIndicator />
|
|
}
|
|
|
|
{
|
|
!isFetching && error &&
|
|
<div>Unable to load General settings</div>
|
|
}
|
|
|
|
{
|
|
hasSettings && isPopulated && !error &&
|
|
<Form
|
|
id="generalSettings"
|
|
{...otherProps}
|
|
>
|
|
<FieldSet
|
|
legend="Start-Up"
|
|
>
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
>
|
|
<FormLabel>Bind Address</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="bindAddress"
|
|
helpText="Valid IP4 address or '*' for all interfaces"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...bindAddress}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Port Number</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.NUMBER}
|
|
name="port"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...port}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>URL Base</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="urlBase"
|
|
helpText="For reverse proxy support, default is empty"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...urlBase}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
size={sizes.MEDIUM}
|
|
>
|
|
<FormLabel>Enable SSL</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.CHECK}
|
|
name="enableSsl"
|
|
helpText=" Requires restart running as administrator to take effect"
|
|
onChange={onInputChange}
|
|
{...enableSsl}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{
|
|
enableSsl.value &&
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
>
|
|
<FormLabel>SSL Port</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.NUMBER}
|
|
name="sslPort"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...sslPort}
|
|
/>
|
|
</FormGroup>
|
|
}
|
|
|
|
{
|
|
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>
|
|
}
|
|
|
|
{
|
|
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 Lidarr homepage on app start."
|
|
onChange={onInputChange}
|
|
{...launchBrowser}
|
|
/>
|
|
</FormGroup>
|
|
}
|
|
|
|
</FieldSet>
|
|
|
|
<FieldSet
|
|
legend="Security"
|
|
>
|
|
<FormGroup>
|
|
<FormLabel>Authentication</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.SELECT}
|
|
name="authenticationMethod"
|
|
values={authenticationMethodOptions}
|
|
helpText="Require Username and Password to access Lidarr"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...authenticationMethod}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{
|
|
authenticationEnabled &&
|
|
<FormGroup>
|
|
<FormLabel>Username</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="username"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...username}
|
|
/>
|
|
</FormGroup>
|
|
}
|
|
|
|
{
|
|
authenticationEnabled &&
|
|
<FormGroup>
|
|
<FormLabel>Password</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.PASSWORD}
|
|
name="password"
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...password}
|
|
/>
|
|
</FormGroup>
|
|
}
|
|
|
|
<FormGroup>
|
|
<FormLabel>API Key</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="apiKey"
|
|
readOnly={true}
|
|
helpTextWarning="Requires restart to take effect"
|
|
buttons={[
|
|
<ClipboardButton
|
|
key="copy"
|
|
value={apiKey.value}
|
|
kind={kinds.DEFAULT}
|
|
/>,
|
|
|
|
<FormInputButton
|
|
key="reset"
|
|
kind={kinds.DANGER}
|
|
onPress={this.onResetApiKeyPress}
|
|
>
|
|
<Icon name={isResettingApiKey ? `${icons.REFRESH} fa-spin` : icons.REFRESH} />
|
|
</FormInputButton>
|
|
]}
|
|
onChange={onInputChange}
|
|
onFocus={this.onApikeyFocus}
|
|
{...apiKey}
|
|
/>
|
|
</FormGroup>
|
|
</FieldSet>
|
|
|
|
<FieldSet
|
|
legend="Proxy Settings"
|
|
>
|
|
<FormGroup size={sizes.MEDIUM}>
|
|
<FormLabel>Use Proxy</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.CHECK}
|
|
name="proxyEnabled"
|
|
onChange={onInputChange}
|
|
{...proxyEnabled}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{
|
|
proxyEnabled.value &&
|
|
<div>
|
|
<FormGroup>
|
|
<FormLabel>Proxy Type</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.SELECT}
|
|
name="proxyType"
|
|
values={proxyTypeOptions}
|
|
onChange={onInputChange}
|
|
{...proxyType}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Hostname</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="proxyHostname"
|
|
onChange={onInputChange}
|
|
{...proxyHostname}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Port</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.NUMBER}
|
|
name="proxyPort"
|
|
onChange={onInputChange}
|
|
{...proxyPort}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Username</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="proxyUsername"
|
|
helpText="You only need to enter a username and password if one is required. Leave them blank otherwise."
|
|
onChange={onInputChange}
|
|
{...proxyUsername}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Password</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.PASSWORD}
|
|
name="proxyPassword"
|
|
helpText="You only need to enter a username and password if one is required. Leave them blank otherwise."
|
|
onChange={onInputChange}
|
|
{...proxyPassword}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
<FormLabel>Ignored Addresses</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="proxyBypassFilter"
|
|
helpText="Use ',' as a separator, and '*.' as a wildcard for subdomains"
|
|
onChange={onInputChange}
|
|
{...proxyBypassFilter}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup size={sizes.MEDIUM}>
|
|
<FormLabel>Bypass Proxy for Local Addresses</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.CHECK}
|
|
name="proxyBypassLocalAddresses"
|
|
onChange={onInputChange}
|
|
{...proxyBypassLocalAddresses}
|
|
/>
|
|
</FormGroup>
|
|
</div>
|
|
}
|
|
</FieldSet>
|
|
|
|
<FieldSet
|
|
legend="Logging"
|
|
>
|
|
<FormGroup>
|
|
<FormLabel>Log Level</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.SELECT}
|
|
name="logLevel"
|
|
values={logLevelOptions}
|
|
helpTextWarning={logLevel.value === 'trace' ? 'Trace logging should only be enabled temporarily' : undefined}
|
|
onChange={onInputChange}
|
|
{...logLevel}
|
|
/>
|
|
</FormGroup>
|
|
</FieldSet>
|
|
|
|
<FieldSet
|
|
legend="Analytics"
|
|
>
|
|
<FormGroup size={sizes.MEDIUM}>
|
|
<FormLabel>Send Anonymous Usage Data</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.CHECK}
|
|
name="analyticsEnabled"
|
|
helpText="Send anonymous usage and error information to Lidarr's servers. This includes information on your browser, which Lidarr WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes."
|
|
helpTextWarning="Requires restart to take effect"
|
|
onChange={onInputChange}
|
|
{...analyticsEnabled}
|
|
/>
|
|
</FormGroup>
|
|
</FieldSet>
|
|
|
|
{
|
|
advancedSettings &&
|
|
<FieldSet
|
|
legend="Updates"
|
|
>
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
>
|
|
<FormLabel>Branch</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="branch"
|
|
helpText="Branch to use to update Lidarr"
|
|
helpLink="https://github.com/lidarr/Lidarr/wiki/Release-Branches"
|
|
onChange={onInputChange}
|
|
{...branch}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{
|
|
isMono &&
|
|
<div>
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
size={sizes.MEDIUM}
|
|
>
|
|
<FormLabel>Automatic</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.CHECK}
|
|
name="updateAutomatically"
|
|
helpText="Automatically download and install updates. You will still be able to install from System: Updates"
|
|
onChange={onInputChange}
|
|
{...updateAutomatically}
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
>
|
|
<FormLabel>Mechanism</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.SELECT}
|
|
name="updateMechanism"
|
|
values={updateOptions}
|
|
helpText="Use Lidarr's built-in updater or a script"
|
|
helpLink="https://github.com/lidarr/Lidarr/wiki/Updating"
|
|
onChange={onInputChange}
|
|
{...updateMechanism}
|
|
/>
|
|
</FormGroup>
|
|
|
|
{
|
|
updateMechanism.value === 'script' &&
|
|
<FormGroup
|
|
advancedSettings={advancedSettings}
|
|
isAdvanced={true}
|
|
>
|
|
<FormLabel>Script Path</FormLabel>
|
|
|
|
<FormInputGroup
|
|
type={inputTypes.TEXT}
|
|
name="updateScriptPath"
|
|
helpText="Path to a custom script that takes an extracted update package and handle the remainder of the update process"
|
|
onChange={onInputChange}
|
|
{...updateScriptPath}
|
|
/>
|
|
</FormGroup>
|
|
}
|
|
</div>
|
|
}
|
|
</FieldSet>
|
|
}
|
|
</Form>
|
|
}
|
|
</PageContentBodyConnector>
|
|
|
|
<ConfirmModal
|
|
isOpen={isConfirmApiKeyResetModalOpen}
|
|
kind={kinds.DANGER}
|
|
title="Reset API Key"
|
|
message="Are you sure you want to reset your API Key?"
|
|
confirmLabel="Reset"
|
|
onConfirm={this.onConfirmResetApiKey}
|
|
onCancel={this.onCloseResetApiKeyModal}
|
|
/>
|
|
|
|
<ConfirmModal
|
|
isOpen={isRestartRequiredModalOpen}
|
|
kind={kinds.DANGER}
|
|
title="Restart Lidarr"
|
|
message="Lidarr requires a restart to apply changes, do you want to restart now?"
|
|
cancelLabel="I'll restart later"
|
|
confirmLabel="Restart Now"
|
|
onConfirm={this.onConfirmRestart}
|
|
onCancel={this.onCloseRestartRequiredModalOpen}
|
|
/>
|
|
</PageContent>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
GeneralSettings.propTypes = {
|
|
advancedSettings: PropTypes.bool.isRequired,
|
|
isFetching: PropTypes.bool.isRequired,
|
|
isPopulated: PropTypes.bool.isRequired,
|
|
error: PropTypes.object,
|
|
isSaving: PropTypes.bool.isRequired,
|
|
saveError: PropTypes.object,
|
|
settings: PropTypes.object.isRequired,
|
|
isResettingApiKey: PropTypes.bool.isRequired,
|
|
hasSettings: PropTypes.bool.isRequired,
|
|
isMono: PropTypes.bool.isRequired,
|
|
isWindows: PropTypes.bool.isRequired,
|
|
mode: PropTypes.string.isRequired,
|
|
onInputChange: PropTypes.func.isRequired,
|
|
onConfirmResetApiKey: PropTypes.func.isRequired,
|
|
onConfirmRestart: PropTypes.func.isRequired
|
|
};
|
|
|
|
export default GeneralSettings;
|