Added check for active DHCP before enable

This commit is contained in:
Ildar Kamalov 2018-12-29 18:43:17 +03:00
parent d8802a9709
commit 568784b992
8 changed files with 282 additions and 150 deletions

View File

@ -1,5 +1,5 @@
{ {
"refresh_status": "Refresh status", "check_dhcp_servers": "Check for DHCP servers",
"save_config": "Save config", "save_config": "Save config",
"enabled_dhcp": "DHCP server enabled", "enabled_dhcp": "DHCP server enabled",
"disabled_dhcp": "DHCP server disabled", "disabled_dhcp": "DHCP server disabled",

View File

@ -553,31 +553,14 @@ export const getDhcpInterfaces = () => async (dispatch) => {
} }
}; };
export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
export const setDhcpConfig = config => async (dispatch) => {
dispatch(setDhcpConfigRequest());
try {
await apiClient.setDhcpConfig(config);
dispatch(getDhcpStatus());
dispatch(addSuccessToast('dhcp_config_saved'));
dispatch(setDhcpConfigSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setDhcpConfigFailure());
}
};
export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST'); export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS'); export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = () => async (dispatch) => { export const findActiveDhcp = name => async (dispatch) => {
dispatch(findActiveDhcpRequest()); dispatch(findActiveDhcpRequest());
try { try {
const activeDhcp = await apiClient.findActiveDhcp(); const activeDhcp = await apiClient.findActiveDhcp(name);
dispatch(findActiveDhcpSuccess(activeDhcp)); dispatch(findActiveDhcpSuccess(activeDhcp));
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
@ -585,27 +568,84 @@ export const findActiveDhcp = () => async (dispatch) => {
} }
}; };
export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
// TODO rewrite findActiveDhcp part
export const setDhcpConfig = config => async (dispatch) => {
dispatch(setDhcpConfigRequest());
try {
if (config.interface_name) {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp));
if (!activeDhcp.found) {
await apiClient.setDhcpConfig(config);
dispatch(addSuccessToast('dhcp_config_saved'));
dispatch(setDhcpConfigSuccess());
dispatch(getDhcpStatus());
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
} else {
await apiClient.setDhcpConfig(config);
dispatch(addSuccessToast('dhcp_config_saved'));
dispatch(setDhcpConfigSuccess());
dispatch(getDhcpStatus());
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setDhcpConfigFailure());
}
};
export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST'); export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE'); export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS'); export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
// TODO rewrite findActiveDhcp part
export const toggleDhcp = config => async (dispatch) => { export const toggleDhcp = config => async (dispatch) => {
dispatch(toggleDhcpRequest()); dispatch(toggleDhcpRequest());
let successMessage = '';
try { if (config.enabled) {
if (config.enabled) { dispatch(addSuccessToast('disabled_dhcp'));
successMessage = 'disabled_dhcp'; try {
await apiClient.setDhcpConfig({ ...config, enabled: false }); await apiClient.setDhcpConfig({ ...config, enabled: false });
} else { dispatch(toggleDhcpSuccess());
successMessage = 'enabled_dhcp'; dispatch(getDhcpStatus());
await apiClient.setDhcpConfig({ ...config, enabled: true }); } catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
} }
} else {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp));
dispatch(addSuccessToast(successMessage)); if (!activeDhcp.found) {
dispatch(toggleDhcpSuccess()); try {
} catch (error) { await apiClient.setDhcpConfig({ ...config, enabled: true });
dispatch(addErrorToast({ error })); dispatch(toggleDhcpSuccess());
dispatch(toggleDhcpFailure()); dispatch(getDhcpStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
}
dispatch(addSuccessToast('enabled_dhcp'));
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
} }
}; };

View File

@ -328,8 +328,12 @@ export default class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
findActiveDhcp() { findActiveDhcp(name) {
const { path, method } = this.DHCP_FIND_ACTIVE; const { path, method } = this.DHCP_FIND_ACTIVE;
return this.makeRequest(path, method); const parameters = {
data: name,
headers: { 'Content-Type': 'text/plain' },
};
return this.makeRequest(path, method, parameters);
} }
} }

View File

@ -1,7 +1,6 @@
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, formValueSelector } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { withNamespaces, Trans } from 'react-i18next'; import { withNamespaces, Trans } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
@ -45,88 +44,17 @@ const renderField = ({
</Fragment> </Fragment>
); );
const renderInterfaces = (interfaces => ( const Form = (props) => {
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return (
<option value={name} key={name} disabled={onlyIPv6}>
{name} - {interfaceIP}
</option>
);
})
));
const renderInterfaceValues = (interfaceValues => (
<ul className="list-unstyled mt-1 mb-0">
<li>
<span className="interface__title">MTU: </span>
{interfaceValues.mtu}
</li>
<li>
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
{interfaceValues.hardware_address}
</li>
<li>
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
{
interfaceValues.ip_addresses
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
}
</li>
</ul>
));
let Form = (props) => {
const { const {
t, t,
handleSubmit, handleSubmit,
pristine, pristine,
submitting, submitting,
interfaces,
processing,
interfaceValue,
} = props; } = props;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="row"> <div className="row">
<div className="col-12">
{!processing && interfaces &&
<div className="row">
<div className="col-sm-12 col-md-6">
<div className="form__group form__group--dhcp">
<label>{t('dhcp_interface_select')}</label>
<Field
name="interface_name"
component="select"
className="form-control custom-select"
>
<option value="">{t('dhcp_interface_select')}</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
</div>
<div className="col-lg-6"> <div className="col-lg-6">
<div className="form__group form__group--dhcp"> <div className="form__group form__group--dhcp">
<label>{t('dhcp_form_gateway_input')}</label> <label>{t('dhcp_form_gateway_input')}</label>
@ -211,20 +139,10 @@ Form.propTypes = {
submitting: PropTypes.bool, submitting: PropTypes.bool,
interfaces: PropTypes.object, interfaces: PropTypes.object,
processing: PropTypes.bool, processing: PropTypes.bool,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object, initialValues: PropTypes.object,
t: PropTypes.func, t: PropTypes.func,
}; };
const selector = formValueSelector('dhcpForm');
Form = connect((state) => {
const interfaceValue = selector(state, 'interface_name');
return {
interfaceValue,
};
})(Form);
export default flow([ export default flow([
withNamespaces(), withNamespaces(),
reduxForm({ form: 'dhcpForm' }), reduxForm({ form: 'dhcpForm' }),

View File

@ -0,0 +1,113 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { withNamespaces, Trans } from 'react-i18next';
import flow from 'lodash/flow';
const renderInterfaces = (interfaces => (
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return (
<option value={name} key={name} disabled={onlyIPv6}>
{name} - {interfaceIP}
</option>
);
})
));
const renderInterfaceValues = (interfaceValues => (
<ul className="list-unstyled mt-1 mb-0">
<li>
<span className="interface__title">MTU: </span>
{interfaceValues.mtu}
</li>
<li>
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
{interfaceValues.hardware_address}
</li>
<li>
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
{
interfaceValues.ip_addresses
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
}
</li>
</ul>
));
let Interface = (props) => {
const {
t,
handleChange,
interfaces,
processing,
interfaceValue,
enabled,
} = props;
return (
<form>
{!processing && interfaces &&
<div className="row">
<div className="col-sm-12 col-md-6">
<div className="form__group form__group--dhcp">
<label>{t('dhcp_interface_select')}</label>
<Field
name="interface_name"
component="select"
className="form-control custom-select"
onChange={handleChange}
>
<option value="" disabled={enabled}>{t('dhcp_interface_select')}</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
</form>
);
};
Interface.propTypes = {
handleChange: PropTypes.func,
interfaces: PropTypes.object,
processing: PropTypes.bool,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object,
enabled: PropTypes.bool,
t: PropTypes.func,
};
const selector = formValueSelector('dhcpInterface');
Interface = connect((state) => {
const interfaceValue = selector(state, 'interface_name');
return {
interfaceValue,
};
})(Interface);
export default flow([
withNamespaces(),
reduxForm({ form: 'dhcpInterface' }),
])(Interface);

View File

@ -5,6 +5,7 @@ import { Trans, withNamespaces } from 'react-i18next';
import Form from './Form'; import Form from './Form';
import Leases from './Leases'; import Leases from './Leases';
import Interface from './Interface';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
class Dhcp extends Component { class Dhcp extends Component {
@ -12,27 +13,80 @@ class Dhcp extends Component {
this.props.setDhcpConfig(values); this.props.setDhcpConfig(values);
}; };
handleRefresh = () => { handleFormChange = (value) => {
this.props.findActiveDhcp(); this.props.setDhcpConfig(value);
}
handleToggle = (config) => {
this.props.toggleDhcp(config);
this.props.findActiveDhcp(config.interface_name);
} }
getToggleDhcpButton = () => { getToggleDhcpButton = () => {
const { config } = this.props.dhcp; const { config, active } = this.props.dhcp;
const buttonText = config.enabled ? 'dhcp_disable' : 'dhcp_enable'; const activeDhcpFound = active && active.found;
const buttonClass = config.enabled ? 'btn-gray' : 'btn-success'; const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled') {
return true;
}
return config[key];
});
if (config.enabled) {
return (
<button
type="button"
className="btn btn-standart mr-2 btn-gray"
onClick={() => this.props.toggleDhcp(config)}
>
<Trans>dhcp_disable</Trans>
</button>
);
}
return ( return (
<button <button
type="button" type="button"
className={`btn btn-standart mr-2 ${buttonClass}`} className="btn btn-standart mr-2 btn-success"
onClick={() => this.props.toggleDhcp(config)} onClick={() => this.handleToggle(config)}
disabled={!config.interface_name} disabled={!filledConfig || activeDhcpFound}
> >
<Trans>{buttonText}</Trans> <Trans>dhcp_enable</Trans>
</button> </button>
); );
} }
getActiveDhcpMessage = () => {
const { active } = this.props.dhcp;
if (active) {
if (active.error) {
return (
<div className="text-danger">
{active.error}
</div>
);
}
return (
<Fragment>
{active.found ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
</Fragment>
);
}
return '';
}
render() { render() {
const { t, dhcp } = this.props; const { t, dhcp } = this.props;
const statusButtonClass = classnames({ const statusButtonClass = classnames({
@ -42,14 +96,20 @@ class Dhcp extends Component {
return ( return (
<Fragment> <Fragment>
{!dhcp.processing && <Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings">
<Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings"> <div className="dhcp">
<div className="row"> {!dhcp.processing &&
<div className="col"> <Fragment>
<Interface
onChange={this.handleFormChange}
initialValues={dhcp.config}
interfaces={dhcp.interfaces}
processing={dhcp.processingInterfaces}
enabled={dhcp.config.enabled}
/>
<Form <Form
onSubmit={this.handleFormSubmit} onSubmit={this.handleFormSubmit}
initialValues={dhcp.config} initialValues={dhcp.config}
enabled={dhcp.config.enabled}
interfaces={dhcp.interfaces} interfaces={dhcp.interfaces}
processing={dhcp.processingInterfaces} processing={dhcp.processingInterfaces}
/> />
@ -57,29 +117,21 @@ class Dhcp extends Component {
<div className="card-actions mb-3"> <div className="card-actions mb-3">
{this.getToggleDhcpButton()} {this.getToggleDhcpButton()}
<button <button
className={statusButtonClass}
type="button" type="button"
onClick={this.handleRefresh} className={statusButtonClass}
onClick={() =>
this.props.findActiveDhcp(dhcp.config.interface_name)
}
disabled={!dhcp.config.interface_name} disabled={!dhcp.config.interface_name}
> >
<Trans>refresh_status</Trans> <Trans>check_dhcp_servers</Trans>
</button> </button>
</div> </div>
{dhcp.active && {this.getActiveDhcpMessage()}
<div className="text-secondary"> </Fragment>
{dhcp.active.found ? ( }
<span className="text-danger"> </div>
<Trans>dhcp_found</Trans> </Card>
</span>
) : (
<Trans>dhcp_not_found</Trans>
)}
</div>
}
</div>
</div>
</Card>
}
{!dhcp.processing && dhcp.config.enabled && {!dhcp.processing && dhcp.config.enabled &&
<Card title={ t('dhcp_leases') } bodyType="card-body box-body--settings"> <Card title={ t('dhcp_leases') } bodyType="card-body box-body--settings">
<div className="row"> <div className="row">

View File

@ -44,3 +44,7 @@
.interface__ip:last-child:after { .interface__ip:last-child:after {
content: ""; content: "";
} }
.dhcp {
min-height: 450px;
}

View File

@ -304,6 +304,7 @@ const dhcp = handleActions({
config: { config: {
enabled: false, enabled: false,
}, },
active: null,
leases: [], leases: [],
}); });