Added validation on change and enable encryption checkbox
This commit is contained in:
parent
d44f68e844
commit
05cce8b107
@ -219,18 +219,27 @@
|
|||||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||||
"encryption_https": "HTTPS port",
|
"encryption_https": "HTTPS port",
|
||||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\\dns-query' location.",
|
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||||
"encryption_dot": "DNS-over-TLS port",
|
"encryption_dot": "DNS-over-TLS port",
|
||||||
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
||||||
"encryption_certificates": "Certificates",
|
"encryption_certificates": "Certificates",
|
||||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}</0> or you can buy it from one of the trusted Certificate Authorities.",
|
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}</0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||||
"encryption_certificates_input": "Copy/paste your PEM-encoded cerificates here.",
|
"encryption_certificates_input": "Copy/paste your PEM-encoded cerificates here.",
|
||||||
"encryption_status": "Status",
|
"encryption_status": "Status",
|
||||||
"encryption_certificates_for": "Certificates for {{domains}}",
|
"encryption_expire": "Expires",
|
||||||
"encryption_expire": "Expire on {{date}}",
|
|
||||||
"encryption_key": "Private key",
|
"encryption_key": "Private key",
|
||||||
"encryption_key_input": "Copy/paste your PEM-encoded private key for your cerficate here.",
|
"encryption_key_input": "Copy/paste your PEM-encoded private key for your cerficate here.",
|
||||||
|
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||||
|
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||||
|
"encryption_chain_valid": "Certificate chain is valid",
|
||||||
|
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||||
|
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||||
|
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||||
|
"encryption_subject": "Subject",
|
||||||
|
"encryption_issuer": "Issuer",
|
||||||
|
"encryption_hostnames": "Hostnames",
|
||||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
|
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
|
||||||
|
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
|
||||||
"form_error_port_range": "Enter port value in the range of 80-65535",
|
"form_error_port_range": "Enter port value in the range of 80-65535",
|
||||||
"form_error_equal": "Shouldn't be equal",
|
"form_error_equal": "Shouldn't be equal",
|
||||||
"form_error_password": "Password mismatched",
|
"form_error_password": "Password mismatched",
|
||||||
|
70
client/src/actions/encryption.js
Normal file
70
client/src/actions/encryption.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import Api from '../api/Api';
|
||||||
|
import { addErrorToast, addSuccessToast } from './index';
|
||||||
|
|
||||||
|
const apiClient = new Api();
|
||||||
|
|
||||||
|
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
|
||||||
|
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
|
||||||
|
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
|
||||||
|
|
||||||
|
export const getTlsStatus = () => async (dispatch) => {
|
||||||
|
dispatch(getTlsStatusRequest());
|
||||||
|
try {
|
||||||
|
const status = await apiClient.getTlsStatus();
|
||||||
|
status.certificate_chain = atob(status.certificate_chain);
|
||||||
|
status.private_key = atob(status.private_key);
|
||||||
|
|
||||||
|
dispatch(getTlsStatusSuccess(status));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(getTlsStatusFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
|
||||||
|
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
|
||||||
|
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
|
||||||
|
|
||||||
|
export const setTlsConfig = config => async (dispatch) => {
|
||||||
|
dispatch(setTlsConfigRequest());
|
||||||
|
try {
|
||||||
|
const values = { ...config };
|
||||||
|
values.certificate_chain = btoa(values.certificate_chain);
|
||||||
|
values.private_key = btoa(values.private_key);
|
||||||
|
values.port_https = values.port_https || 0;
|
||||||
|
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||||
|
|
||||||
|
const response = await apiClient.setTlsConfig(values);
|
||||||
|
response.certificate_chain = atob(response.certificate_chain);
|
||||||
|
response.private_key = atob(response.private_key);
|
||||||
|
dispatch(setTlsConfigSuccess(response));
|
||||||
|
dispatch(addSuccessToast('encryption_config_saved'));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(setTlsConfigFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUEST');
|
||||||
|
export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE');
|
||||||
|
export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS');
|
||||||
|
|
||||||
|
export const validateTlsConfig = config => async (dispatch) => {
|
||||||
|
dispatch(validateTlsConfigRequest());
|
||||||
|
try {
|
||||||
|
const values = { ...config };
|
||||||
|
values.certificate_chain = btoa(values.certificate_chain);
|
||||||
|
values.private_key = btoa(values.private_key);
|
||||||
|
values.port_https = values.port_https || 0;
|
||||||
|
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||||
|
|
||||||
|
const response = await apiClient.validateTlsConfig(values);
|
||||||
|
response.certificate_chain = atob(response.certificate_chain);
|
||||||
|
response.private_key = atob(response.private_key);
|
||||||
|
dispatch(validateTlsConfigSuccess(response));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(validateTlsConfigFailure());
|
||||||
|
}
|
||||||
|
};
|
@ -650,44 +650,3 @@ export const toggleDhcp = config => async (dispatch) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
|
|
||||||
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
|
|
||||||
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
|
|
||||||
|
|
||||||
export const getTlsStatus = () => async (dispatch) => {
|
|
||||||
dispatch(getTlsStatusRequest());
|
|
||||||
try {
|
|
||||||
const status = await apiClient.getTlsStatus();
|
|
||||||
status.certificate_chain = atob(status.certificate_chain);
|
|
||||||
status.private_key = atob(status.private_key);
|
|
||||||
|
|
||||||
dispatch(getTlsStatusSuccess(status));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(getTlsStatusFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
|
|
||||||
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
|
|
||||||
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
|
|
||||||
|
|
||||||
export const setTlsConfig = config => async (dispatch) => {
|
|
||||||
dispatch(setTlsConfigRequest());
|
|
||||||
try {
|
|
||||||
const values = { ...config };
|
|
||||||
values.certificate_chain = btoa(values.certificate_chain);
|
|
||||||
values.private_key = btoa(values.private_key);
|
|
||||||
values.port_https = values.port_https || 0;
|
|
||||||
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
|
||||||
|
|
||||||
await apiClient.setTlsConfig(values);
|
|
||||||
dispatch(setTlsConfigSuccess(config));
|
|
||||||
dispatch(addSuccessToast('encryption_config_saved'));
|
|
||||||
dispatch(getTlsStatus());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(setTlsConfigFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -358,6 +358,7 @@ export default class Api {
|
|||||||
// DNS-over-HTTPS and DNS-over-TLS
|
// DNS-over-HTTPS and DNS-over-TLS
|
||||||
TLS_STATUS = { path: 'tls/status', method: 'GET' };
|
TLS_STATUS = { path: 'tls/status', method: 'GET' };
|
||||||
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
|
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
|
||||||
|
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
|
||||||
|
|
||||||
getTlsStatus() {
|
getTlsStatus() {
|
||||||
const { path, method } = this.TLS_STATUS;
|
const { path, method } = this.TLS_STATUS;
|
||||||
@ -372,4 +373,13 @@ export default class Api {
|
|||||||
};
|
};
|
||||||
return this.makeRequest(path, method, parameters);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateTlsConfig(config) {
|
||||||
|
const { path, method } = this.TLS_VALIDATE;
|
||||||
|
const parameters = {
|
||||||
|
data: config,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
};
|
||||||
|
return this.makeRequest(path, method, parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import { HashRouter, Route } from 'react-router-dom';
|
import { HashRouter, Route } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { withNamespaces } from 'react-i18next';
|
||||||
import LoadingBar from 'react-redux-loading-bar';
|
import LoadingBar from 'react-redux-loading-bar';
|
||||||
|
|
||||||
import 'react-table/react-table.css';
|
import 'react-table/react-table.css';
|
||||||
@ -18,6 +18,7 @@ import Footer from '../ui/Footer';
|
|||||||
import Toasts from '../Toasts';
|
import Toasts from '../Toasts';
|
||||||
import Status from '../ui/Status';
|
import Status from '../ui/Status';
|
||||||
import Topline from '../ui/Topline';
|
import Topline from '../ui/Topline';
|
||||||
|
import EncryptionTopline from '../ui/EncryptionTopline';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
@ -56,7 +57,6 @@ class App extends Component {
|
|||||||
!dashboard.processingVersions &&
|
!dashboard.processingVersions &&
|
||||||
dashboard.isCoreRunning &&
|
dashboard.isCoreRunning &&
|
||||||
dashboard.isUpdateAvailable;
|
dashboard.isUpdateAvailable;
|
||||||
const isExpiringCertificate = !encryption.processing && encryption.warning;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HashRouter hashType='noslash'>
|
<HashRouter hashType='noslash'>
|
||||||
@ -66,12 +66,8 @@ class App extends Component {
|
|||||||
{dashboard.announcement} <a href={dashboard.announcementUrl} target="_blank" rel="noopener noreferrer">Click here</a> for more info.
|
{dashboard.announcement} <a href={dashboard.announcementUrl} target="_blank" rel="noopener noreferrer">Click here</a> for more info.
|
||||||
</Topline>
|
</Topline>
|
||||||
}
|
}
|
||||||
{isExpiringCertificate &&
|
{!encryption.processing &&
|
||||||
<Topline type="warning">
|
<EncryptionTopline notAfter={encryption.not_after} />
|
||||||
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
|
||||||
topline_expiring_certificate
|
|
||||||
</Trans>
|
|
||||||
</Topline>
|
|
||||||
}
|
}
|
||||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||||
<Route component={Header} />
|
<Route component={Header} />
|
||||||
|
@ -12,7 +12,6 @@ import './Header.css';
|
|||||||
class Header extends Component {
|
class Header extends Component {
|
||||||
state = {
|
state = {
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isDropdownOpen: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleMenuOpen = () => {
|
toggleMenuOpen = () => {
|
||||||
@ -25,6 +24,7 @@ class Header extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard } = this.props;
|
const { dashboard } = this.props;
|
||||||
|
const { isMenuOpen } = this.state;
|
||||||
const badgeClass = classnames({
|
const badgeClass = classnames({
|
||||||
'badge dns-status': true,
|
'badge dns-status': true,
|
||||||
'badge-success': dashboard.protectionEnabled,
|
'badge-success': dashboard.protectionEnabled,
|
||||||
@ -52,7 +52,7 @@ class Header extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
location={this.props.location}
|
location={this.props.location}
|
||||||
isMenuOpen={this.state.isMenuOpen}
|
isMenuOpen={isMenuOpen}
|
||||||
toggleMenuOpen={this.toggleMenuOpen}
|
toggleMenuOpen={this.toggleMenuOpen}
|
||||||
closeMenu={this.closeMenu}
|
closeMenu={this.closeMenu}
|
||||||
/>
|
/>
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Field, reduxForm } from 'redux-form';
|
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
import { renderField, renderSelectField, toNumber, port } from '../../../helpers/form';
|
import { renderField, renderSelectField, toNumber, port } from '../../../helpers/form';
|
||||||
|
import { EMPTY_DATE } from '../../../helpers/constants';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
const validate = (values) => {
|
const validate = (values) => {
|
||||||
@ -20,21 +23,46 @@ const validate = (values) => {
|
|||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Form = (props) => {
|
let Form = (props) => {
|
||||||
const {
|
const {
|
||||||
t,
|
t,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
handleChange,
|
||||||
|
isEnabled,
|
||||||
|
certificateChain,
|
||||||
|
privateKey,
|
||||||
reset,
|
reset,
|
||||||
invalid,
|
invalid,
|
||||||
submitting,
|
submitting,
|
||||||
processing,
|
processing,
|
||||||
statusCert,
|
not_after,
|
||||||
statusKey,
|
valid_chain,
|
||||||
|
valid_key,
|
||||||
|
dns_names,
|
||||||
|
key_type,
|
||||||
|
issuer,
|
||||||
|
subject,
|
||||||
|
warning_validation,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name="enabled"
|
||||||
|
type="checkbox"
|
||||||
|
component={renderSelectField}
|
||||||
|
placeholder={t('encryption_enable')}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form__desc">
|
||||||
|
<Trans>encryption_enable_desc</Trans>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<label className="form__label" htmlFor="server_name">
|
<label className="form__label" htmlFor="server_name">
|
||||||
<Trans>encryption_server</Trans>
|
<Trans>encryption_server</Trans>
|
||||||
@ -49,6 +77,8 @@ const Form = (props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={t('encryption_server_enter')}
|
placeholder={t('encryption_server_enter')}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__desc">
|
<div className="form__desc">
|
||||||
<Trans>encryption_server_desc</Trans>
|
<Trans>encryption_server_desc</Trans>
|
||||||
@ -62,6 +92,8 @@ const Form = (props) => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
component={renderSelectField}
|
component={renderSelectField}
|
||||||
placeholder={t('encryption_redirect')}
|
placeholder={t('encryption_redirect')}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__desc">
|
<div className="form__desc">
|
||||||
<Trans>encryption_redirect_desc</Trans>
|
<Trans>encryption_redirect_desc</Trans>
|
||||||
@ -84,6 +116,8 @@ const Form = (props) => {
|
|||||||
placeholder={t('encryption_https')}
|
placeholder={t('encryption_https')}
|
||||||
validate={[port]}
|
validate={[port]}
|
||||||
normalize={toNumber}
|
normalize={toNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__desc">
|
<div className="form__desc">
|
||||||
<Trans>encryption_https_desc</Trans>
|
<Trans>encryption_https_desc</Trans>
|
||||||
@ -104,6 +138,8 @@ const Form = (props) => {
|
|||||||
placeholder={t('encryption_dot')}
|
placeholder={t('encryption_dot')}
|
||||||
validate={[port]}
|
validate={[port]}
|
||||||
normalize={toNumber}
|
normalize={toNumber}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__desc">
|
<div className="form__desc">
|
||||||
<Trans>encryption_dot_desc</Trans>
|
<Trans>encryption_dot_desc</Trans>
|
||||||
@ -132,28 +168,42 @@ const Form = (props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control--textarea"
|
className="form-control form-control--textarea"
|
||||||
placeholder={t('encryption_certificates_input')}
|
placeholder={t('encryption_certificates_input')}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__status">
|
<div className="form__status">
|
||||||
{statusCert &&
|
{certificateChain &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="form__label form__label--bold">
|
<div className="form__label form__label--bold">
|
||||||
<Trans>encryption_status</Trans>:
|
<Trans>encryption_status</Trans>:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<ul>
|
||||||
{statusCert}
|
<li className={valid_chain ? 'text-success' : 'text-danger'}>
|
||||||
</div>
|
{valid_chain ?
|
||||||
|
<Trans>encryption_chain_valid</Trans>
|
||||||
|
: <Trans>encryption_chain_invalid</Trans>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
{subject &&
|
||||||
|
<li><Trans>encryption_subject</Trans>: {subject}</li>
|
||||||
|
}
|
||||||
|
{issuer &&
|
||||||
|
<li><Trans>encryption_issuer</Trans>: {issuer}</li>
|
||||||
|
}
|
||||||
|
{not_after && not_after !== EMPTY_DATE &&
|
||||||
|
<li>
|
||||||
|
<Trans>encryption_expire</Trans>:
|
||||||
|
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
{dns_names &&
|
||||||
|
<li>
|
||||||
|
<Trans>encryption_hostnames</Trans>: {dns_names}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
{/* <div>
|
|
||||||
<Trans values={{ domains: '*.example.org, example.org' }}>
|
|
||||||
encryption_certificates_for
|
|
||||||
</Trans>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Trans values={{ date: '2022-01-01' }}>
|
|
||||||
encryption_expire
|
|
||||||
</Trans>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,33 +221,54 @@ const Form = (props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control--textarea"
|
className="form-control form-control--textarea"
|
||||||
placeholder="Copy/paste your PEM-encoded private key for your cerficate here."
|
placeholder="Copy/paste your PEM-encoded private key for your cerficate here."
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
<div className="form__status">
|
<div className="form__status">
|
||||||
{statusKey &&
|
{privateKey &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="form__label form__label--bold">
|
<div className="form__label form__label--bold">
|
||||||
<Trans>encryption_status</Trans>:
|
<Trans>encryption_status</Trans>:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<p className={valid_key ? 'text-success' : 'text-danger'}>
|
||||||
{statusKey}
|
{valid_key ?
|
||||||
</div>
|
<Trans values={{ type: key_type }}>
|
||||||
|
encryption_key_valid
|
||||||
|
</Trans>
|
||||||
|
: <Trans values={{ type: key_type }}>
|
||||||
|
encryption_key_invalid
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<p className="text-danger">
|
||||||
|
{warning_validation && warning_validation}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="btn-list">
|
<div className="btn-list mt-2">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standart"
|
className="btn btn-success btn-standart"
|
||||||
disabled={invalid || submitting || processing}
|
disabled={
|
||||||
|
invalid
|
||||||
|
|| submitting
|
||||||
|
|| processing
|
||||||
|
|| !valid_chain
|
||||||
|
|| !valid_key
|
||||||
|
|| warning_validation
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Trans>save_config</Trans>
|
<Trans>save_config</Trans>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="button"
|
||||||
className="btn btn-secondary btn-standart"
|
className="btn btn-secondary btn-standart"
|
||||||
disabled={submitting || processing}
|
disabled={submitting || processing}
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
@ -211,16 +282,40 @@ const Form = (props) => {
|
|||||||
|
|
||||||
Form.propTypes = {
|
Form.propTypes = {
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
handleChange: PropTypes.func,
|
||||||
|
isEnabled: PropTypes.bool.isRequired,
|
||||||
|
certificateChain: PropTypes.string.isRequired,
|
||||||
|
privateKey: PropTypes.string.isRequired,
|
||||||
reset: PropTypes.func.isRequired,
|
reset: PropTypes.func.isRequired,
|
||||||
submitting: PropTypes.bool.isRequired,
|
submitting: PropTypes.bool.isRequired,
|
||||||
invalid: PropTypes.bool.isRequired,
|
invalid: PropTypes.bool.isRequired,
|
||||||
initialValues: PropTypes.object.isRequired,
|
initialValues: PropTypes.object.isRequired,
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
statusCert: PropTypes.string,
|
status_key: PropTypes.string,
|
||||||
statusKey: PropTypes.string,
|
not_after: PropTypes.string,
|
||||||
|
warning_validation: PropTypes.string,
|
||||||
|
valid_chain: PropTypes.bool,
|
||||||
|
valid_key: PropTypes.bool,
|
||||||
|
dns_names: PropTypes.string,
|
||||||
|
key_type: PropTypes.string,
|
||||||
|
issuer: PropTypes.string,
|
||||||
|
subject: PropTypes.string,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selector = formValueSelector('encryptionForm');
|
||||||
|
|
||||||
|
Form = connect((state) => {
|
||||||
|
const isEnabled = selector(state, 'enabled');
|
||||||
|
const certificateChain = selector(state, 'certificate_chain');
|
||||||
|
const privateKey = selector(state, 'private_key');
|
||||||
|
return {
|
||||||
|
isEnabled,
|
||||||
|
certificateChain,
|
||||||
|
privateKey,
|
||||||
|
};
|
||||||
|
})(Form);
|
||||||
|
|
||||||
export default flow([
|
export default flow([
|
||||||
withNamespaces(),
|
withNamespaces(),
|
||||||
reduxForm({
|
reduxForm({
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withNamespaces } from 'react-i18next';
|
import { withNamespaces } from 'react-i18next';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
@ -10,14 +11,20 @@ class Encryption extends Component {
|
|||||||
this.props.setTlsConfig(values);
|
this.props.setTlsConfig(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleFormChange = debounce((values) => {
|
||||||
|
this.props.validateTlsConfig(values);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { encryption, t } = this.props;
|
const { encryption, t } = this.props;
|
||||||
const {
|
const {
|
||||||
processing,
|
enabled,
|
||||||
processingConfig,
|
server_name,
|
||||||
status_cert: statusCert,
|
force_https,
|
||||||
status_key: statusKey,
|
port_https,
|
||||||
...values
|
port_dns_over_tls,
|
||||||
|
certificate_chain,
|
||||||
|
private_key,
|
||||||
} = encryption;
|
} = encryption;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -29,11 +36,19 @@ class Encryption extends Component {
|
|||||||
bodyType="card-body box-body--settings"
|
bodyType="card-body box-body--settings"
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
initialValues={{ ...values }}
|
initialValues={{
|
||||||
|
enabled,
|
||||||
|
server_name,
|
||||||
|
force_https,
|
||||||
|
port_https,
|
||||||
|
port_dns_over_tls,
|
||||||
|
certificate_chain,
|
||||||
|
private_key,
|
||||||
|
}}
|
||||||
processing={encryption.processingConfig}
|
processing={encryption.processingConfig}
|
||||||
statusCert={statusCert}
|
|
||||||
statusKey={statusKey}
|
|
||||||
onSubmit={this.handleFormSubmit}
|
onSubmit={this.handleFormSubmit}
|
||||||
|
onChange={this.handleFormChange}
|
||||||
|
{...this.props.encryption}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
}
|
}
|
||||||
@ -44,6 +59,7 @@ class Encryption extends Component {
|
|||||||
|
|
||||||
Encryption.propTypes = {
|
Encryption.propTypes = {
|
||||||
setTlsConfig: PropTypes.func.isRequired,
|
setTlsConfig: PropTypes.func.isRequired,
|
||||||
|
validateTlsConfig: PropTypes.func.isRequired,
|
||||||
encryption: PropTypes.object.isRequired,
|
encryption: PropTypes.object.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -100,6 +100,7 @@ class Settings extends Component {
|
|||||||
<Encryption
|
<Encryption
|
||||||
encryption={this.props.encryption}
|
encryption={this.props.encryption}
|
||||||
setTlsConfig={this.props.setTlsConfig}
|
setTlsConfig={this.props.setTlsConfig}
|
||||||
|
validateTlsConfig={this.props.validateTlsConfig}
|
||||||
/>
|
/>
|
||||||
<Dhcp
|
<Dhcp
|
||||||
dhcp={this.props.dhcp}
|
dhcp={this.props.dhcp}
|
||||||
|
41
client/src/components/ui/EncryptionTopline.js
Normal file
41
client/src/components/ui/EncryptionTopline.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
|
import isAfter from 'date-fns/is_after';
|
||||||
|
import addDays from 'date-fns/add_days';
|
||||||
|
|
||||||
|
import Topline from './Topline';
|
||||||
|
import { EMPTY_DATE } from '../../helpers/constants';
|
||||||
|
|
||||||
|
const EncryptionTopline = (props) => {
|
||||||
|
if (props.notAfter !== EMPTY_DATE) {
|
||||||
|
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
|
||||||
|
const isExpired = isAfter(Date.now(), props.notAfter);
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
return (
|
||||||
|
<Topline type="danger">
|
||||||
|
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||||
|
topline_expired_certificate
|
||||||
|
</Trans>
|
||||||
|
</Topline>
|
||||||
|
);
|
||||||
|
} else if (isAboutExpire) {
|
||||||
|
return (
|
||||||
|
<Topline type="warning">
|
||||||
|
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||||
|
topline_expiring_certificate
|
||||||
|
</Trans>
|
||||||
|
</Topline>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
EncryptionTopline.propTypes = {
|
||||||
|
notAfter: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNamespaces()(EncryptionTopline);
|
@ -11,9 +11,12 @@ import {
|
|||||||
getDhcpInterfaces,
|
getDhcpInterfaces,
|
||||||
setDhcpConfig,
|
setDhcpConfig,
|
||||||
findActiveDhcp,
|
findActiveDhcp,
|
||||||
|
} from '../actions';
|
||||||
|
import {
|
||||||
getTlsStatus,
|
getTlsStatus,
|
||||||
setTlsConfig,
|
setTlsConfig,
|
||||||
} from '../actions';
|
validateTlsConfig,
|
||||||
|
} from '../actions/encryption';
|
||||||
import Settings from '../components/Settings';
|
import Settings from '../components/Settings';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
@ -46,6 +49,7 @@ const mapDispatchToProps = {
|
|||||||
findActiveDhcp,
|
findActiveDhcp,
|
||||||
getTlsStatus,
|
getTlsStatus,
|
||||||
setTlsConfig,
|
setTlsConfig,
|
||||||
|
validateTlsConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -73,3 +73,4 @@ export const SETTINGS_NAMES = {
|
|||||||
|
|
||||||
export const STANDARD_DNS_PORT = 53;
|
export const STANDARD_DNS_PORT = 53;
|
||||||
export const STANDARD_WEB_PORT = 80;
|
export const STANDARD_WEB_PORT = 80;
|
||||||
|
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
|
||||||
|
62
client/src/reducers/encryption.js
Normal file
62
client/src/reducers/encryption.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
|
import * as actions from '../actions/encryption';
|
||||||
|
|
||||||
|
const encryption = handleActions({
|
||||||
|
[actions.getTlsStatusRequest]: state => ({ ...state, processing: true }),
|
||||||
|
[actions.getTlsStatusFailure]: state => ({ ...state, processing: false }),
|
||||||
|
[actions.getTlsStatusSuccess]: (state, { payload }) => {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
...payload,
|
||||||
|
processing: false,
|
||||||
|
};
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.setTlsConfigRequest]: state => ({ ...state, processingConfig: true }),
|
||||||
|
[actions.setTlsConfigFailure]: state => ({ ...state, processingConfig: false }),
|
||||||
|
[actions.setTlsConfigSuccess]: (state, { payload }) => {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
...payload,
|
||||||
|
processingConfig: false,
|
||||||
|
};
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.validateTlsConfigRequest]: state => ({ ...state, processingValidate: true }),
|
||||||
|
[actions.validateTlsConfigFailure]: state => ({ ...state, processingValidate: false }),
|
||||||
|
[actions.validateTlsConfigSuccess]: (state, { payload }) => {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
...payload,
|
||||||
|
processingValidate: false,
|
||||||
|
};
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
processing: true,
|
||||||
|
processingConfig: false,
|
||||||
|
processingValidate: false,
|
||||||
|
enabled: false,
|
||||||
|
dns_names: null,
|
||||||
|
force_https: false,
|
||||||
|
issuer: '',
|
||||||
|
key_type: '',
|
||||||
|
not_after: '',
|
||||||
|
not_before: '',
|
||||||
|
port_dns_over_tls: 853,
|
||||||
|
port_https: 443,
|
||||||
|
subject: '',
|
||||||
|
valid_chain: false,
|
||||||
|
valid_key: false,
|
||||||
|
status_cert: '',
|
||||||
|
status_key: '',
|
||||||
|
certificate_chain: '',
|
||||||
|
private_key: '',
|
||||||
|
server_name: '',
|
||||||
|
warning_validation: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default encryption;
|
@ -6,6 +6,7 @@ import versionCompare from '../helpers/versionCompare';
|
|||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import toasts from './toasts';
|
import toasts from './toasts';
|
||||||
|
import encryption from './encryption';
|
||||||
|
|
||||||
const settings = handleActions({
|
const settings = handleActions({
|
||||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||||
@ -302,38 +303,6 @@ const dhcp = handleActions({
|
|||||||
leases: [],
|
leases: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const encryption = handleActions({
|
|
||||||
[actions.getTlsStatusRequest]: state => ({ ...state, processing: true }),
|
|
||||||
[actions.getTlsStatusFailure]: state => ({ ...state, processing: false }),
|
|
||||||
[actions.getTlsStatusSuccess]: (state, { payload }) => {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
...payload,
|
|
||||||
processing: false,
|
|
||||||
};
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.setTlsConfigRequest]: state => ({ ...state, processingConfig: true }),
|
|
||||||
[actions.setTlsConfigFailure]: state => ({ ...state, processingConfig: false }),
|
|
||||||
[actions.setTlsConfigSuccess]: (state, { payload }) => {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
...payload,
|
|
||||||
processingConfig: false,
|
|
||||||
};
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
processing: true,
|
|
||||||
processingConfig: false,
|
|
||||||
status_cert: '',
|
|
||||||
status_key: '',
|
|
||||||
certificate_chain: '',
|
|
||||||
private_key: '',
|
|
||||||
server_name: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
dashboard,
|
dashboard,
|
||||||
|
@ -63,7 +63,7 @@ type dnsConfig struct {
|
|||||||
var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"}
|
var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"}
|
||||||
|
|
||||||
type tlsConfigSettings struct {
|
type tlsConfigSettings struct {
|
||||||
Enabled bool `yaml:"enaled" json:"enabled"`
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||||
ServerName string `yaml:"server_name" json:"server_name,omitempty"`
|
ServerName string `yaml:"server_name" json:"server_name,omitempty"`
|
||||||
ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"`
|
ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"`
|
||||||
PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"`
|
PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"`
|
||||||
|
Loading…
Reference in New Issue
Block a user