Convert General Settings to TypeScript

pull/7605/head
Mark McDowall 2 months ago
parent e62c687d93
commit ebb0aab2f5
No known key found for this signature in database

@ -12,7 +12,7 @@ import SeriesDetailsPageConnector from 'Series/Details/SeriesDetailsPageConnecto
import SeriesIndex from 'Series/Index/SeriesIndex';
import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
import GeneralSettings from 'Settings/General/GeneralSettings';
import ImportListSettings from 'Settings/ImportLists/ImportListSettings';
import IndexerSettings from 'Settings/Indexers/IndexerSettings';
import MediaManagement from 'Settings/MediaManagement/MediaManagement';
@ -129,7 +129,7 @@ function AppRoutes() {
<Route path="/settings/tags" component={TagSettings} />
<Route path="/settings/general" component={GeneralSettingsConnector} />
<Route path="/settings/general" component={GeneralSettings} />
<Route path="/settings/ui" component={UISettings} />

@ -1,4 +1,4 @@
import React, { FocusEvent, ReactNode } from 'react';
import React, { ComponentType, FocusEvent, ReactNode } from 'react';
import Link from 'Components/Link/Link';
import DownloadProtocol from 'DownloadClient/DownloadProtocol';
import { inputTypes } from 'Helpers/Props';
@ -37,88 +37,39 @@ import TextArea from './TextArea';
import TextInput from './TextInput';
import styles from './FormInputGroup.css';
function getComponent(type: InputType) {
switch (type) {
case inputTypes.AUTO_COMPLETE:
return AutoCompleteInput;
case inputTypes.CAPTCHA:
return CaptchaInput;
case inputTypes.CHECK:
return CheckInput;
case inputTypes.DEVICE:
return DeviceInput;
case inputTypes.KEY_VALUE_LIST:
return KeyValueListInput;
case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInput;
case inputTypes.MONITOR_EPISODES_SELECT:
return MonitorEpisodesSelectInput;
case inputTypes.MONITOR_NEW_ITEMS_SELECT:
return MonitorNewItemsSelectInput;
case inputTypes.NUMBER:
return NumberInput;
case inputTypes.OAUTH:
return OAuthInput;
case inputTypes.PASSWORD:
return PasswordInput;
case inputTypes.PATH:
return PathInput;
case inputTypes.QUALITY_PROFILE_SELECT:
return QualityProfileSelectInput;
case inputTypes.INDEXER_SELECT:
return IndexerSelectInput;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInput;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInput;
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInput;
case inputTypes.SELECT:
return EnhancedSelectInput;
case inputTypes.DYNAMIC_SELECT:
return ProviderDataSelectInput;
case inputTypes.TAG:
case inputTypes.SERIES_TAG:
return SeriesTagInput;
case inputTypes.SERIES_TYPE_SELECT:
return SeriesTypeSelectInput;
case inputTypes.TEXT_AREA:
return TextArea;
case inputTypes.TEXT_TAG:
return TextTagInput;
case inputTypes.TAG_SELECT:
return TagSelectInput;
case inputTypes.UMASK:
return UMaskInput;
default:
return TextInput;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const componentMap: Record<InputType, ComponentType<any>> = {
autoComplete: AutoCompleteInput,
captcha: CaptchaInput,
check: CheckInput,
date: TextInput,
device: DeviceInput,
file: TextInput,
float: NumberInput,
keyValueList: KeyValueListInput,
languageSelect: LanguageSelectInput,
monitorEpisodesSelect: MonitorEpisodesSelectInput,
monitorNewItemsSelect: MonitorNewItemsSelectInput,
number: NumberInput,
oauth: OAuthInput,
password: PasswordInput,
path: PathInput,
qualityProfileSelect: QualityProfileSelectInput,
indexerSelect: IndexerSelectInput,
indexerFlagsSelect: IndexerFlagsSelectInput,
downloadClientSelect: DownloadClientSelectInput,
rootFolderSelect: RootFolderSelectInput,
select: EnhancedSelectInput,
dynamicSelect: ProviderDataSelectInput,
tag: SeriesTagInput,
seriesTag: SeriesTagInput,
seriesTypeSelect: SeriesTypeSelectInput,
text: TextInput,
textArea: TextArea,
textTag: TextTagInput,
tagSelect: TagSelectInput,
umask: UMaskInput,
};
export interface FormInputGroupValues<T> {
key: T;
@ -135,6 +86,7 @@ interface FormInputGroupProps<T> {
className?: string;
containerClassName?: string;
inputClassName?: string;
autocomplete?: string;
name: string;
value?: unknown;
values?: FormInputGroupValues<unknown>[];
@ -189,7 +141,7 @@ function FormInputGroup<T>(props: FormInputGroupProps<T>) {
...otherProps
} = props;
const InputComponent = getComponent(type);
const InputComponent = componentMap[type];
const checkInput = type === inputTypes.CHECK;
const hasError = !!errors.length;
const hasWarning = !hasError && !!warnings.length;
@ -201,7 +153,6 @@ function FormInputGroup<T>(props: FormInputGroupProps<T>) {
<div className={containerClassName}>
<div className={className}>
<div className={styles.inputContainer}>
{/* @ts-expect-error - need to pass through all the expected options */}
<InputComponent
className={inputClassName}
helpText={helpText}

@ -1,8 +0,0 @@
import { useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
function useIsWindows() {
return useSelector((state: AppState) => state.system.status.item.isWindows);
}
export default useIsWindows;

@ -1,22 +1,23 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
function AnalyticSettings(props) {
const {
settings,
onInputChange
} = props;
const {
analyticsEnabled
} = settings;
interface AnalyticSettingsProps {
analyticsEnabled: PendingSection<General>['analyticsEnabled'];
onInputChange: (change: InputChanged) => void;
}
function AnalyticSettings({
analyticsEnabled,
onInputChange,
}: AnalyticSettingsProps) {
return (
<FieldSet legend={translate('Analytics')}>
<FormGroup size={sizes.MEDIUM}>
@ -35,9 +36,4 @@ function AnalyticSettings(props) {
);
}
AnalyticSettings.propTypes = {
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default AnalyticSettings;

@ -1,35 +1,37 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings';
import { inputTypes } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
function BackupSettings(props) {
const {
advancedSettings,
settings,
onInputChange
} = props;
interface BackupSettingsProps {
backupFolder: PendingSection<General>['backupFolder'];
backupInterval: PendingSection<General>['backupInterval'];
backupRetention: PendingSection<General>['backupRetention'];
onInputChange: (change: InputChanged) => void;
}
const {
backupFolder,
backupInterval,
backupRetention
} = settings;
function BackupSettings({
backupFolder,
backupInterval,
backupRetention,
onInputChange,
}: BackupSettingsProps) {
const showAdvancedSettings = useShowAdvancedSettings();
if (!advancedSettings) {
if (!showAdvancedSettings) {
return null;
}
return (
<FieldSet legend={translate('Backups')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('Folder')}</FormLabel>
<FormInputGroup
@ -41,10 +43,7 @@ function BackupSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('Interval')}</FormLabel>
<FormInputGroup
@ -57,10 +56,7 @@ function BackupSettings(props) {
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('Retention')}</FormLabel>
<FormInputGroup
@ -76,10 +72,4 @@ function BackupSettings(props) {
);
}
BackupSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default BackupSettings;

@ -1,222 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { kinds } from 'Helpers/Props';
import SettingsToolbar from 'Settings/SettingsToolbar';
import translate from 'Utilities/String/translate';
import AnalyticSettings from './AnalyticSettings';
import BackupSettings from './BackupSettings';
import HostSettings from './HostSettings';
import LoggingSettings from './LoggingSettings';
import ProxySettings from './ProxySettings';
import SecuritySettings from './SecuritySettings';
import UpdateSettings from './UpdateSettings';
const requiresRestartKeys = [
'bindAddress',
'port',
'urlBase',
'instanceName',
'enableSsl',
'sslPort',
'sslCertHash',
'sslCertPassword'
];
class GeneralSettings extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isRestartRequiredModalOpen: false
};
}
componentDidUpdate(prevProps) {
const {
settings,
isSaving,
saveError,
isResettingApiKey
} = this.props;
if (!isResettingApiKey && prevProps.isResettingApiKey) {
this.setState({ isRestartRequiredModalOpen: true });
return;
}
if (isSaving || saveError || !prevProps.isSaving) {
return;
}
const prevSettings = prevProps.settings;
const pendingRestart = _.some(requiresRestartKeys, (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
onConfirmRestart = () => {
this.setState({ isRestartRequiredModalOpen: false });
this.props.onConfirmRestart();
};
onCloseRestartRequiredModalOpen = () => {
this.setState({ isRestartRequiredModalOpen: false });
};
//
// Render
render() {
const {
advancedSettings,
isFetching,
isPopulated,
error,
settings,
hasSettings,
isResettingApiKey,
isWindows,
isWindowsService,
mode,
packageUpdateMechanism,
onInputChange,
onConfirmResetApiKey,
...otherProps
} = this.props;
return (
<PageContent title={translate('GeneralSettings')}>
<SettingsToolbar
{...otherProps}
/>
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && error &&
<Alert kind={kinds.DANGER}>
{translate('GeneralSettingsLoadError')}
</Alert>
}
{
hasSettings && isPopulated && !error &&
<Form
id="generalSettings"
{...otherProps}
>
<HostSettings
advancedSettings={advancedSettings}
settings={settings}
isWindows={isWindows}
mode={mode}
onInputChange={onInputChange}
/>
<SecuritySettings
settings={settings}
isResettingApiKey={isResettingApiKey}
onInputChange={onInputChange}
onConfirmResetApiKey={onConfirmResetApiKey}
/>
<ProxySettings
settings={settings}
onInputChange={onInputChange}
/>
<LoggingSettings
advancedSettings={advancedSettings}
settings={settings}
onInputChange={onInputChange}
/>
<AnalyticSettings
settings={settings}
onInputChange={onInputChange}
/>
<UpdateSettings
advancedSettings={advancedSettings}
settings={settings}
isWindows={isWindows}
packageUpdateMechanism={packageUpdateMechanism}
onInputChange={onInputChange}
/>
<BackupSettings
advancedSettings={advancedSettings}
settings={settings}
onInputChange={onInputChange}
/>
</Form>
}
</PageContentBody>
<ConfirmModal
isOpen={this.state.isRestartRequiredModalOpen}
kind={kinds.DANGER}
title={translate('RestartSonarr')}
message={`${translate('RestartRequiredToApplyChanges')} ${isWindowsService ? translate('RestartRequiredWindowsService') : ''}`}
cancelLabel={translate('RestartLater')}
confirmLabel={translate('RestartNow')}
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,
isWindows: PropTypes.bool.isRequired,
isWindowsService: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired,
onConfirmRestart: PropTypes.func.isRequired
};
export default GeneralSettings;

@ -0,0 +1,233 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import * as commandNames from 'Commands/commandNames';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { kinds } from 'Helpers/Props';
import SettingsToolbar from 'Settings/SettingsToolbar';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import {
fetchGeneralSettings,
saveGeneralSettings,
setGeneralSettingsValue,
} from 'Store/Actions/settingsActions';
import { restart } from 'Store/Actions/systemActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import useIsWindowsService from 'System/useIsWindowsService';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import AnalyticSettings from './AnalyticSettings';
import BackupSettings from './BackupSettings';
import HostSettings from './HostSettings';
import LoggingSettings from './LoggingSettings';
import ProxySettings from './ProxySettings';
import SecuritySettings from './SecuritySettings';
import UpdateSettings from './UpdateSettings';
const SECTION = 'general';
const requiresRestartKeys = [
'bindAddress',
'port',
'urlBase',
'instanceName',
'enableSsl',
'sslPort',
'sslCertHash',
'sslCertPassword',
];
function GeneralSettings() {
const dispatch = useDispatch();
const isWindowsService = useIsWindowsService();
const isResettingApiKey = useSelector(
createCommandExecutingSelector(commandNames.RESET_API_KEY)
);
const {
isFetching,
isPopulated,
isSaving,
error,
saveError,
settings,
hasSettings,
hasPendingChanges,
pendingChanges,
validationErrors,
validationWarnings,
} = useSelector(createSettingsSectionSelector(SECTION));
const wasResettingApiKey = usePrevious(isResettingApiKey);
const wasSaving = usePrevious(isSaving);
const previousPendingChanges = usePrevious(pendingChanges);
const [isRestartRequiredModalOpen, setIsRestartRequiredModalOpen] =
useState(false);
const handleInputChange = useCallback(
(change: InputChanged) => {
// @ts-expect-error - actions aren't typed
dispatch(setGeneralSettingsValue(change));
},
[dispatch]
);
const handleSavePress = useCallback(() => {
dispatch(saveGeneralSettings());
}, [dispatch]);
const handleConfirmRestart = useCallback(() => {
setIsRestartRequiredModalOpen(false);
dispatch(restart());
}, [dispatch]);
const handleCloseRestartRequiredModalOpen = useCallback(() => {
setIsRestartRequiredModalOpen(false);
}, []);
useEffect(() => {
dispatch(fetchGeneralSettings());
return () => {
dispatch(clearPendingChanges({ section: `settings.${SECTION}` }));
};
}, [dispatch]);
useEffect(() => {
if (!isResettingApiKey && wasResettingApiKey) {
dispatch(fetchGeneralSettings());
}
}, [isResettingApiKey, wasResettingApiKey, dispatch]);
useEffect(() => {
const isRestartedRequired =
previousPendingChanges &&
Object.keys(previousPendingChanges).some((key) => {
return requiresRestartKeys.includes(key);
});
if (!isSaving && wasSaving && !saveError && isRestartedRequired) {
setIsRestartRequiredModalOpen(true);
}
}, [isSaving, wasSaving, saveError, previousPendingChanges]);
useEffect(() => {
if (!isResettingApiKey && wasResettingApiKey) {
setIsRestartRequiredModalOpen(true);
}
}, [isResettingApiKey, wasResettingApiKey]);
return (
<PageContent title={translate('GeneralSettings')}>
<SettingsToolbar
hasPendingChanges={hasPendingChanges}
isSaving={isSaving}
onSavePress={handleSavePress}
/>
<PageContentBody>
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{!isFetching && error ? (
<Alert kind={kinds.DANGER}>
{translate('GeneralSettingsLoadError')}
</Alert>
) : null}
{hasSettings && isPopulated && !error ? (
<Form
id="generalSettings"
validationErrors={validationErrors}
validationWarnings={validationWarnings}
>
<HostSettings
bindAddress={settings.bindAddress}
port={settings.port}
urlBase={settings.urlBase}
instanceName={settings.instanceName}
applicationUrl={settings.applicationUrl}
enableSsl={settings.enableSsl}
sslPort={settings.sslPort}
sslCertPath={settings.sslCertPath}
sslCertPassword={settings.sslCertPassword}
launchBrowser={settings.launchBrowser}
onInputChange={handleInputChange}
/>
<SecuritySettings
authenticationMethod={settings.authenticationMethod}
authenticationRequired={settings.authenticationRequired}
username={settings.username}
password={settings.password}
passwordConfirmation={settings.passwordConfirmation}
apiKey={settings.apiKey}
certificateValidation={settings.certificateValidation}
isResettingApiKey={isResettingApiKey}
onInputChange={handleInputChange}
/>
<ProxySettings
proxyEnabled={settings.proxyEnabled}
proxyType={settings.proxyType}
proxyHostname={settings.proxyHostname}
proxyPort={settings.proxyPort}
proxyUsername={settings.proxyUsername}
proxyPassword={settings.proxyPassword}
proxyBypassFilter={settings.proxyBypassFilter}
proxyBypassLocalAddresses={settings.proxyBypassLocalAddresses}
onInputChange={handleInputChange}
/>
<LoggingSettings
logLevel={settings.logLevel}
logSizeLimit={settings.logSizeLimit}
onInputChange={handleInputChange}
/>
<AnalyticSettings
analyticsEnabled={settings.analyticsEnabled}
onInputChange={handleInputChange}
/>
<UpdateSettings
branch={settings.branch}
updateAutomatically={settings.updateAutomatically}
updateMechanism={settings.updateMechanism}
updateScriptPath={settings.updateScriptPath}
onInputChange={handleInputChange}
/>
<BackupSettings
backupFolder={settings.backupFolder}
backupInterval={settings.backupInterval}
backupRetention={settings.backupRetention}
onInputChange={handleInputChange}
/>
</Form>
) : null}
</PageContentBody>
<ConfirmModal
isOpen={isRestartRequiredModalOpen}
kind={kinds.DANGER}
title={translate('RestartSonarr')}
message={`${translate('RestartRequiredToApplyChanges')} ${
isWindowsService ? translate('RestartRequiredWindowsService') : ''
}`}
cancelLabel={translate('RestartLater')}
confirmLabel={translate('RestartNow')}
onConfirm={handleConfirmRestart}
onCancel={handleCloseRestartRequiredModalOpen}
/>
</PageContent>
);
}
export default GeneralSettings;

@ -1,110 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
import { restart } from 'Store/Actions/systemActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import GeneralSettings from './GeneralSettings';
const SECTION = 'general';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createSettingsSectionSelector(SECTION),
createCommandExecutingSelector(commandNames.RESET_API_KEY),
createSystemStatusSelector(),
(advancedSettings, sectionSettings, isResettingApiKey, systemStatus) => {
return {
advancedSettings,
isResettingApiKey,
isWindows: systemStatus.isWindows,
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
mode: systemStatus.mode,
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
...sectionSettings
};
}
);
}
const mapDispatchToProps = {
setGeneralSettingsValue,
saveGeneralSettings,
fetchGeneralSettings,
executeCommand,
restart,
clearPendingChanges
};
class GeneralSettingsConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchGeneralSettings();
}
componentDidUpdate(prevProps) {
if (!this.props.isResettingApiKey && prevProps.isResettingApiKey) {
this.props.fetchGeneralSettings();
}
}
componentWillUnmount() {
this.props.clearPendingChanges({ section: `settings.${SECTION}` });
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.setGeneralSettingsValue({ name, value });
};
onSavePress = () => {
this.props.saveGeneralSettings();
};
onConfirmResetApiKey = () => {
this.props.executeCommand({ name: commandNames.RESET_API_KEY });
};
onConfirmRestart = () => {
this.props.restart();
};
//
// Render
render() {
return (
<GeneralSettings
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
onConfirmResetApiKey={this.onConfirmResetApiKey}
onConfirmRestart={this.onConfirmRestart}
{...this.props}
/>
);
}
}
GeneralSettingsConnector.propTypes = {
isResettingApiKey: PropTypes.bool.isRequired,
setGeneralSettingsValue: PropTypes.func.isRequired,
saveGeneralSettings: PropTypes.func.isRequired,
fetchGeneralSettings: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired,
restart: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(GeneralSettingsConnector);

@ -1,214 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function HostSettings(props) {
const {
advancedSettings,
settings,
isWindows,
mode,
onInputChange
} = props;
const {
bindAddress,
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
sslCertPassword,
launchBrowser
} = settings;
return (
<FieldSet legend={translate('Host')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('BindAddress')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="bindAddress"
helpText={translate('BindAddressHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...bindAddress}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PortNumber')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="port"
min={1}
max={65535}
autocomplete="off"
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...port}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('UrlBase')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="urlBase"
helpText={translate('UrlBaseHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...urlBase}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('InstanceName')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="instanceName"
helpText={translate('InstanceNameHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...instanceName}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('EnableSsl')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableSsl"
helpText={translate('EnableSslHelpText')}
onChange={onInputChange}
{...enableSsl}
/>
</FormGroup>
{
enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('SslPort')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslPort}
/>
</FormGroup> :
null
}
{
enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('SslCertPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertPath"
helpText={translate('SslCertPathHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPath}
/>
</FormGroup> :
null
}
{
enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('SslCertPassword')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="sslCertPassword"
helpText={translate('SslCertPasswordHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPassword}
/>
</FormGroup> :
null
}
{
isWindows && mode !== 'service' ?
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('OpenBrowserOnStart')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText={translate('OpenBrowserOnStartHelpText')}
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup> :
null
}
</FieldSet>
);
}
HostSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired,
isWindows: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default HostSettings;

@ -0,0 +1,191 @@
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings';
import { inputTypes, sizes } from 'Helpers/Props';
import useIsWindowsService from 'System/useIsWindowsService';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
interface HostSettingsProps {
bindAddress: PendingSection<General>['bindAddress'];
port: PendingSection<General>['port'];
urlBase: PendingSection<General>['urlBase'];
instanceName: PendingSection<General>['instanceName'];
applicationUrl: PendingSection<General>['applicationUrl'];
enableSsl: PendingSection<General>['enableSsl'];
sslPort: PendingSection<General>['sslPort'];
sslCertPath: PendingSection<General>['sslCertPath'];
sslCertPassword: PendingSection<General>['sslCertPassword'];
launchBrowser: PendingSection<General>['launchBrowser'];
onInputChange: (change: InputChanged) => void;
}
function HostSettings({
bindAddress,
port,
urlBase,
instanceName,
applicationUrl,
enableSsl,
sslPort,
sslCertPath,
sslCertPassword,
launchBrowser,
onInputChange,
}: HostSettingsProps) {
const showAdvancedSettings = useShowAdvancedSettings();
const isWindowsService = useIsWindowsService();
return (
<FieldSet legend={translate('Host')}>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('BindAddress')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="bindAddress"
helpText={translate('BindAddressHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...bindAddress}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PortNumber')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="port"
min={1}
max={65535}
autocomplete="off"
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...port}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('UrlBase')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="urlBase"
helpText={translate('UrlBaseHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...urlBase}
/>
</FormGroup>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('InstanceName')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="instanceName"
helpText={translate('InstanceNameHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...instanceName}
/>
</FormGroup>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('ApplicationURL')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="applicationUrl"
helpText={translate('ApplicationUrlHelpText')}
onChange={onInputChange}
{...applicationUrl}
/>
</FormGroup>
<FormGroup
advancedSettings={showAdvancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('EnableSsl')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableSsl"
helpText={translate('EnableSslHelpText')}
onChange={onInputChange}
{...enableSsl}
/>
</FormGroup>
{enableSsl.value ? (
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('SslPort')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslPort}
/>
</FormGroup>
) : null}
{enableSsl.value ? (
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('SslCertPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertPath"
helpText={translate('SslCertPathHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPath}
/>
</FormGroup>
) : null}
{enableSsl.value ? (
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('SslCertPassword')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="sslCertPassword"
helpText={translate('SslCertPasswordHelpText')}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
onChange={onInputChange}
{...sslCertPassword}
/>
</FormGroup>
) : null}
{isWindowsService ? (
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('OpenBrowserOnStart')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText={translate('OpenBrowserOnStartHelpText')}
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup>
) : null}
</FieldSet>
);
}
export default HostSettings;

@ -1,10 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings';
import { inputTypes } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
const logLevelOptions = [
@ -12,33 +15,34 @@ const logLevelOptions = [
key: 'info',
get value() {
return translate('Info');
}
},
},
{
key: 'debug',
get value() {
return translate('Debug');
}
},
},
{
key: 'trace',
get value() {
return translate('Trace');
}
}
},
},
];
function LoggingSettings(props) {
const {
advancedSettings,
settings,
onInputChange
} = props;
interface LoggingSettingsProps {
logLevel: PendingSection<General>['logLevel'];
logSizeLimit: PendingSection<General>['logSizeLimit'];
onInputChange: (change: InputChanged) => void;
}
const {
logLevel,
logSizeLimit
} = settings;
function LoggingSettings({
logLevel,
logSizeLimit,
onInputChange,
}: LoggingSettingsProps) {
const showAdvancedSettings = useShowAdvancedSettings();
return (
<FieldSet legend={translate('Logging')}>
@ -49,16 +53,17 @@ function LoggingSettings(props) {
type={inputTypes.SELECT}
name="logLevel"
values={logLevelOptions}
helpTextWarning={logLevel.value === 'trace' ? translate('LogLevelTraceHelpTextWarning') : undefined}
helpTextWarning={
logLevel.value === 'trace'
? translate('LogLevelTraceHelpTextWarning')
: undefined
}
onChange={onInputChange}
{...logLevel}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('LogSizeLimit')}</FormLabel>
<FormInputGroup
@ -76,10 +81,4 @@ function LoggingSettings(props) {
);
}
LoggingSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default LoggingSettings;

@ -1,152 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function ProxySettings(props) {
const {
settings,
onInputChange
} = props;
const {
proxyEnabled,
proxyType,
proxyHostname,
proxyPort,
proxyUsername,
proxyPassword,
proxyBypassFilter,
proxyBypassLocalAddresses
} = settings;
const proxyTypeOptions = [
{
key: 'http',
value: translate('HttpHttps')
},
{
key: 'socks4',
value: translate('Socks4')
},
{
key: 'socks5',
value: translate('Socks5')
}
];
return (
<FieldSet legend={translate('Proxy')}>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('UseProxy')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="proxyEnabled"
onChange={onInputChange}
{...proxyEnabled}
/>
</FormGroup>
{
proxyEnabled.value &&
<div>
<FormGroup>
<FormLabel>{translate('ProxyType')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="proxyType"
values={proxyTypeOptions}
onChange={onInputChange}
{...proxyType}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Hostname')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyHostname"
onChange={onInputChange}
{...proxyHostname}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Port')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="proxyPort"
min={1}
max={65535}
onChange={onInputChange}
{...proxyPort}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyUsername"
helpText={translate('ProxyUsernameHelpText')}
onChange={onInputChange}
{...proxyUsername}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="proxyPassword"
helpText={translate('ProxyPasswordHelpText')}
onChange={onInputChange}
{...proxyPassword}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IgnoredAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyBypassFilter"
helpText={translate('ProxyBypassFilterHelpText')}
onChange={onInputChange}
{...proxyBypassFilter}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('BypassProxyForLocalAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="proxyBypassLocalAddresses"
onChange={onInputChange}
{...proxyBypassLocalAddresses}
/>
</FormGroup>
</div>
}
</FieldSet>
);
}
ProxySettings.propTypes = {
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default ProxySettings;

@ -0,0 +1,153 @@
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes, sizes } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
interface ProxySettingsProps {
proxyEnabled: PendingSection<General>['proxyEnabled'];
proxyType: PendingSection<General>['proxyType'];
proxyHostname: PendingSection<General>['proxyHostname'];
proxyPort: PendingSection<General>['proxyPort'];
proxyUsername: PendingSection<General>['proxyUsername'];
proxyPassword: PendingSection<General>['proxyPassword'];
proxyBypassFilter: PendingSection<General>['proxyBypassFilter'];
proxyBypassLocalAddresses: PendingSection<General>['proxyBypassLocalAddresses'];
onInputChange: (change: InputChanged) => void;
}
function ProxySettings({
proxyEnabled,
proxyType,
proxyHostname,
proxyPort,
proxyUsername,
proxyPassword,
proxyBypassFilter,
proxyBypassLocalAddresses,
onInputChange,
}: ProxySettingsProps) {
const proxyTypeOptions = [
{
key: 'http',
value: translate('HttpHttps'),
},
{
key: 'socks4',
value: translate('Socks4'),
},
{
key: 'socks5',
value: translate('Socks5'),
},
];
return (
<FieldSet legend={translate('Proxy')}>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('UseProxy')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="proxyEnabled"
onChange={onInputChange}
{...proxyEnabled}
/>
</FormGroup>
{proxyEnabled.value && (
<div>
<FormGroup>
<FormLabel>{translate('ProxyType')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="proxyType"
values={proxyTypeOptions}
onChange={onInputChange}
{...proxyType}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Hostname')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyHostname"
onChange={onInputChange}
{...proxyHostname}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Port')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="proxyPort"
min={1}
max={65535}
onChange={onInputChange}
{...proxyPort}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyUsername"
helpText={translate('ProxyUsernameHelpText')}
onChange={onInputChange}
{...proxyUsername}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="proxyPassword"
helpText={translate('ProxyPasswordHelpText')}
onChange={onInputChange}
{...proxyPassword}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IgnoredAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="proxyBypassFilter"
helpText={translate('ProxyBypassFilterHelpText')}
onChange={onInputChange}
{...proxyBypassFilter}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('BypassProxyForLocalAddresses')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="proxyBypassLocalAddresses"
onChange={onInputChange}
{...proxyBypassLocalAddresses}
/>
</FormGroup>
</div>
)}
</FieldSet>
);
}
export default ProxySettings;

@ -1,278 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputButton from 'Components/Form/FormInputButton';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import ClipboardButton from 'Components/Link/ClipboardButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
export const authenticationMethodOptions = [
{
key: 'none',
get value() {
return translate('None');
},
isDisabled: true
},
{
key: 'external',
get value() {
return translate('External');
},
isHidden: true
},
{
key: 'basic',
get value() {
return translate('AuthBasic');
}
},
{
key: 'forms',
get value() {
return translate('AuthForm');
}
}
];
export const authenticationRequiredOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
}
];
const certificateValidationOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
}
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
}
},
{
key: 'disabled',
get value() {
return translate('Disabled');
}
}
];
class SecuritySettings extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isConfirmApiKeyResetModalOpen: false
};
}
//
// Listeners
onApikeyFocus = (event) => {
event.target.select();
};
onResetApiKeyPress = () => {
this.setState({ isConfirmApiKeyResetModalOpen: true });
};
onConfirmResetApiKey = () => {
this.setState({ isConfirmApiKeyResetModalOpen: false });
this.props.onConfirmResetApiKey();
};
onCloseResetApiKeyModal = () => {
this.setState({ isConfirmApiKeyResetModalOpen: false });
};
//
// Render
render() {
const {
settings,
isResettingApiKey,
onInputChange
} = this.props;
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation,
apiKey,
certificateValidation
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
return (
<FieldSet legend={translate('Security')}>
<FormGroup>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
{...username}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
{...password}
/>
</FormGroup> :
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup> :
null
}
<FormGroup>
<FormLabel>{translate('ApiKey')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="apiKey"
readOnly={true}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[
<ClipboardButton
key="copy"
value={apiKey.value}
kind={kinds.DEFAULT}
/>,
<FormInputButton
key="reset"
kind={kinds.DANGER}
onPress={this.onResetApiKeyPress}
>
<Icon
name={icons.REFRESH}
isSpinning={isResettingApiKey}
/>
</FormInputButton>
]}
onChange={onInputChange}
onFocus={this.onApikeyFocus}
{...apiKey}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('CertificateValidation')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="certificateValidation"
values={certificateValidationOptions}
helpText={translate('CertificateValidationHelpText')}
onChange={onInputChange}
{...certificateValidation}
/>
</FormGroup>
<ConfirmModal
isOpen={this.state.isConfirmApiKeyResetModalOpen}
kind={kinds.DANGER}
title={translate('ResetAPIKey')}
message={translate('ResetAPIKeyMessageText')}
confirmLabel={translate('Reset')}
onConfirm={this.onConfirmResetApiKey}
onCancel={this.onCloseResetApiKeyModal}
/>
</FieldSet>
);
}
}
SecuritySettings.propTypes = {
settings: PropTypes.object.isRequired,
isResettingApiKey: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired
};
export default SecuritySettings;

@ -0,0 +1,263 @@
import React, { FocusEvent, useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
import * as commandNames from 'Commands/commandNames';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputButton from 'Components/Form/FormInputButton';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import ClipboardButton from 'Components/Link/ClipboardButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import translate from 'Utilities/String/translate';
export const authenticationMethodOptions = [
{
key: 'none',
get value() {
return translate('None');
},
isDisabled: true,
},
{
key: 'external',
get value() {
return translate('External');
},
isHidden: true,
},
{
key: 'basic',
get value() {
return translate('AuthBasic');
},
},
{
key: 'forms',
get value() {
return translate('AuthForm');
},
},
];
export const authenticationRequiredOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
},
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
},
},
];
const certificateValidationOptions = [
{
key: 'enabled',
get value() {
return translate('Enabled');
},
},
{
key: 'disabledForLocalAddresses',
get value() {
return translate('DisabledForLocalAddresses');
},
},
{
key: 'disabled',
get value() {
return translate('Disabled');
},
},
];
interface SecuritySettingsProps {
authenticationMethod: PendingSection<General>['authenticationMethod'];
authenticationRequired: PendingSection<General>['authenticationRequired'];
username: PendingSection<General>['username'];
password: PendingSection<General>['password'];
passwordConfirmation: PendingSection<General>['passwordConfirmation'];
apiKey: PendingSection<General>['apiKey'];
certificateValidation: PendingSection<General>['certificateValidation'];
isResettingApiKey: boolean;
onInputChange: (change: InputChanged) => void;
}
function SecuritySettings({
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation,
apiKey,
certificateValidation,
isResettingApiKey,
onInputChange,
}: SecuritySettingsProps) {
const dispatch = useDispatch();
const [isConfirmApiKeyResetModalOpen, setIsConfirmApiKeyResetModalOpen] =
useState(false);
const handleApikeyFocus = useCallback(
(event: FocusEvent<HTMLInputElement, Element>) => {
event.target.select();
},
[]
);
const handleResetApiKeyPress = useCallback(() => {
setIsConfirmApiKeyResetModalOpen(true);
}, []);
const handleConfirmResetApiKey = useCallback(() => {
setIsConfirmApiKeyResetModalOpen(false);
dispatch(executeCommand({ name: commandNames.RESET_API_KEY }));
}, [dispatch]);
const handleCloseResetApiKeyModal = useCallback(() => {
setIsConfirmApiKeyResetModalOpen(false);
}, []);
// createCommandExecutingSelector(commandNames.RESET_API_KEY),
const authenticationEnabled =
authenticationMethod && authenticationMethod.value !== 'none';
return (
<FieldSet legend={translate('Security')}>
<FormGroup>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
{authenticationEnabled ? (
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
) : null}
{authenticationEnabled ? (
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
{...username}
/>
</FormGroup>
) : null}
{authenticationEnabled ? (
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
{...password}
/>
</FormGroup>
) : null}
{authenticationEnabled ? (
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup>
) : null}
<FormGroup>
<FormLabel>{translate('ApiKey')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="apiKey"
readOnly={true}
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
buttons={[
<ClipboardButton
key="copy"
value={apiKey.value}
kind={kinds.DEFAULT}
/>,
<FormInputButton
key="reset"
kind={kinds.DANGER}
onPress={handleResetApiKeyPress}
>
<Icon name={icons.REFRESH} isSpinning={isResettingApiKey} />
</FormInputButton>,
]}
onChange={onInputChange}
onFocus={handleApikeyFocus}
{...apiKey}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('CertificateValidation')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="certificateValidation"
values={certificateValidationOptions}
helpText={translate('CertificateValidationHelpText')}
onChange={onInputChange}
{...certificateValidation}
/>
</FormGroup>
<ConfirmModal
isOpen={isConfirmApiKeyResetModalOpen}
kind={kinds.DANGER}
title={translate('ResetAPIKey')}
message={translate('ResetAPIKeyMessageText')}
confirmLabel={translate('Reset')}
onConfirm={handleConfirmResetApiKey}
onCancel={handleCloseResetApiKeyModal}
/>
</FieldSet>
);
}
export default SecuritySettings;

@ -1,34 +1,38 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings';
import { inputTypes, sizes } from 'Helpers/Props';
import useSystemStatus from 'System/useSystemStatus';
import { InputChanged } from 'typings/inputs';
import { PendingSection } from 'typings/pending';
import General from 'typings/Settings/General';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
const branchValues = [
'main',
'develop'
];
function UpdateSettings(props) {
const {
advancedSettings,
settings,
packageUpdateMechanism,
onInputChange
} = props;
const {
branch,
updateAutomatically,
updateMechanism,
updateScriptPath
} = settings;
if (!advancedSettings) {
const branchValues = ['main', 'develop'];
interface UpdateSettingsProps {
branch: PendingSection<General>['branch'];
updateAutomatically: PendingSection<General>['updateAutomatically'];
updateMechanism: PendingSection<General>['updateMechanism'];
updateScriptPath: PendingSection<General>['updateScriptPath'];
onInputChange: (change: InputChanged) => void;
}
function UpdateSettings({
branch,
updateAutomatically,
updateMechanism,
updateScriptPath,
onInputChange,
}: UpdateSettingsProps) {
const showAdvancedSettings = useShowAdvancedSettings();
const { packageUpdateMechanism } = useSystemStatus();
if (!showAdvancedSettings) {
return null;
}
@ -39,7 +43,7 @@ function UpdateSettings(props) {
if (usingExternalUpdateMechanism) {
updateOptions.push({
key: packageUpdateMechanism,
value: titleCase(packageUpdateMechanism)
value: titleCase(packageUpdateMechanism),
});
} else {
updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') });
@ -49,27 +53,30 @@ function UpdateSettings(props) {
return (
<FieldSet legend={translate('Updates')}>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('Branch')}</FormLabel>
<FormInputGroup
type={inputTypes.AUTO_COMPLETE}
name="branch"
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
helpText={
usingExternalUpdateMechanism
? translate('BranchUpdateMechanism')
: translate('BranchUpdate')
}
helpLink="https://wiki.servarr.com/sonarr/settings#updates"
{...branch}
// @ts-expect-error - FormInputGroup doesn't accept a values prop
// of string[] which is needed for AutoCompleteInput
values={branchValues}
onChange={onInputChange}
readOnly={usingExternalUpdateMechanism}
onChange={onInputChange}
/>
</FormGroup>
<div>
<FormGroup
advancedSettings={advancedSettings}
advancedSettings={showAdvancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
@ -79,16 +86,17 @@ function UpdateSettings(props) {
type={inputTypes.CHECK}
name="updateAutomatically"
helpText={translate('UpdateAutomaticallyHelpText')}
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
helpTextWarning={
updateMechanism.value === 'docker'
? translate('AutomaticUpdatesDisabledDocker')
: undefined
}
onChange={onInputChange}
{...updateAutomatically}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('Mechanism')}</FormLabel>
<FormInputGroup
@ -102,34 +110,22 @@ function UpdateSettings(props) {
/>
</FormGroup>
{
updateMechanism.value === 'script' &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateScriptPath}
/>
</FormGroup>
}
{updateMechanism.value === 'script' ? (
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
<FormLabel>{translate('ScriptPath')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText={translate('UpdateScriptPathHelpText')}
onChange={onInputChange}
{...updateScriptPath}
/>
</FormGroup>
) : null}
</div>
</FieldSet>
);
}
UpdateSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired,
isWindows: PropTypes.bool.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired
};
export default UpdateSettings;

@ -1,4 +1,4 @@
import { cloneDeep, isEmpty } from 'lodash';
import { cloneDeep } from 'lodash';
import { Error } from 'App/State/AppSectionState';
import Field from 'typings/Field';
import {
@ -10,6 +10,7 @@ import {
ValidationFailure,
ValidationWarning,
} from 'typings/pending';
import isEmpty from 'Utilities/Object/isEmpty';
interface ValidationFailures {
errors: ValidationError[];

@ -0,0 +1,9 @@
import useSystemStatus from './useSystemStatus';
function useIsWindowsService() {
const { isWindows, mode } = useSystemStatus();
return isWindows && mode === 'service';
}
export default useIsWindowsService;

@ -0,0 +1,8 @@
import { useSelector } from 'react-redux';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
function useSystemStatus() {
return useSelector(createSystemStatusSelector());
}
export default useSystemStatus;

@ -1,4 +1,8 @@
function isEmpty<T extends object>(obj: T) {
function isEmpty<T extends object>(obj: T | undefined) {
if (!obj) {
return false;
}
for (const prop in obj) {
if (Object.hasOwn(obj, prop)) {
return false;

@ -18,6 +18,7 @@ export default interface General {
password: string;
passwordConfirmation: string;
logLevel: string;
logSizeLimit: number;
consoleLogLevel: string;
branch: string;
apiKey: string;

Loading…
Cancel
Save