+ client: handle static leases form

This commit is contained in:
Ildar Kamalov 2019-05-28 15:07:46 +03:00 committed by Simon Zolin
parent a8384c004e
commit 6f2503a09f
13 changed files with 510 additions and 245 deletions

View File

@ -38,6 +38,13 @@
"dhcp_error": "We could not determine whether there is another DHCP server in the network.", "dhcp_error": "We could not determine whether there is another DHCP server in the network.",
"dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.", "dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.",
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. We will automatically set this IP address as static if you press Enable DHCP button.", "dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. We will automatically set this IP address as static if you press Enable DHCP button.",
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_new_static_lease": "New static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
"form_enter_hostname": "Enter hostname",
"error_details": "Error details", "error_details": "Error details",
"back": "Back", "back": "Back",
"dashboard": "Dashboard", "dashboard": "Dashboard",

View File

@ -662,17 +662,11 @@ export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS'); export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE'); export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
// TODO rewrite findActiveDhcp part
export const setDhcpConfig = values => async (dispatch, getState) => { export const setDhcpConfig = values => async (dispatch, getState) => {
const { config } = getState().dhcp; const { config } = getState().dhcp;
const updatedConfig = { ...config, ...values }; const updatedConfig = { ...config, ...values };
dispatch(setDhcpConfigRequest()); dispatch(setDhcpConfigRequest());
if (values.interface_name) { dispatch(findActiveDhcp(values.interface_name));
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(values.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp));
if (!activeDhcp.found) {
try { try {
await apiClient.setDhcpConfig(updatedConfig); await apiClient.setDhcpConfig(updatedConfig);
dispatch(setDhcpConfigSuccess(updatedConfig)); dispatch(setDhcpConfigSuccess(updatedConfig));
@ -681,63 +675,66 @@ export const setDhcpConfig = values => async (dispatch, getState) => {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(setDhcpConfigFailure()); dispatch(setDhcpConfigFailure());
} }
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
} else {
try {
await apiClient.setDhcpConfig(updatedConfig);
dispatch(setDhcpConfigSuccess(updatedConfig));
dispatch(addSuccessToast('dhcp_config_saved'));
} 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 = values => async (dispatch) => {
export const toggleDhcp = config => async (dispatch) => {
dispatch(toggleDhcpRequest()); dispatch(toggleDhcpRequest());
let config = { ...values, enabled: false };
let successMessage = 'disabled_dhcp';
if (!values.enabled) {
config = { ...values, enabled: true };
successMessage = 'enabled_dhcp';
dispatch(findActiveDhcp(values.interface_name));
}
if (config.enabled) {
try { try {
await apiClient.setDhcpConfig({ ...config, enabled: false }); await apiClient.setDhcpConfig(config);
dispatch(toggleDhcpSuccess()); dispatch(toggleDhcpSuccess());
dispatch(addSuccessToast('disabled_dhcp')); dispatch(addSuccessToast(successMessage));
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure()); dispatch(toggleDhcpFailure());
} }
} else { };
dispatch(findActiveDhcpRequest());
try { export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL');
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp)); export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
export const addStaticLeaseFailure = createAction('ADD_STATIC_LEASE_FAILURE');
if (!activeDhcp.found) { export const addStaticLeaseSuccess = createAction('ADD_STATIC_LEASE_SUCCESS');
try {
await apiClient.setDhcpConfig({ ...config, enabled: true }); export const addStaticLease = config => async (dispatch) => {
dispatch(toggleDhcpSuccess()); dispatch(addStaticLeaseRequest());
dispatch(addSuccessToast('enabled_dhcp')); try {
} catch (error) { await apiClient.addStaticLease(config);
dispatch(addErrorToast({ error })); dispatch(addStaticLeaseSuccess());
dispatch(toggleDhcpFailure()); dispatch(addSuccessToast(t('dhcp_lease_added', { key: config.hostname })));
} dispatch(toggleLeaseModal());
} else { dispatch(getDhcpStatus());
dispatch(addErrorToast({ error: 'dhcp_found' })); } catch (error) {
} dispatch(addErrorToast({ error }));
} catch (error) { dispatch(addStaticLeaseFailure());
dispatch(addErrorToast({ error })); }
dispatch(findActiveDhcpFailure()); };
}
} export const removeStaticLeaseRequest = createAction('REMOVE_STATIC_LEASE_REQUEST');
export const removeStaticLeaseFailure = createAction('REMOVE_STATIC_LEASE_FAILURE');
export const removeStaticLeaseSuccess = createAction('REMOVE_STATIC_LEASE_SUCCESS');
export const removeStaticLease = config => async (dispatch) => {
dispatch(removeStaticLeaseRequest());
try {
await apiClient.removeStaticLease(config);
dispatch(removeStaticLeaseSuccess());
dispatch(addSuccessToast(t('dhcp_lease_deleted', { key: config.hostname })));
dispatch(getDhcpStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(removeStaticLeaseFailure());
}
}; };

View File

@ -318,6 +318,8 @@ export default class Api {
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' }; DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' }; DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' }; DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
getDhcpStatus() { getDhcpStatus() {
const { path, method } = this.DHCP_STATUS; const { path, method } = this.DHCP_STATUS;
@ -347,6 +349,24 @@ export default class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
addStaticLease(config) {
const { path, method } = this.DHCP_ADD_STATIC_LEASE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
removeStaticLease(config) {
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
// Installation // Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' }; INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' }; INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };

View File

@ -1,22 +1,97 @@
import React from 'react'; import React 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 { withNamespaces } from 'react-i18next'; import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form'; import { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
const Form = (props) => { 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 Form = (props) => {
const { const {
t, t,
handleSubmit, handleSubmit,
submitting, submitting,
invalid, invalid,
enabled,
interfaces,
interfaceValue,
processingConfig, processingConfig,
processingInterfaces,
} = props; } = props;
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{!processingInterfaces && interfaces &&
<div className="row">
<div className="col-sm-12 col-md-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_interface_select')}</label>
<Field
name="interface_name"
component="select"
className="form-control custom-select"
validate={[required]}
>
<option value="" disabled={enabled}>
{t('dhcp_interface_select')}
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{interfaces[interfaceValue] &&
renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
<div className="row"> <div className="row">
<div className="col-lg-6"> <div className="col-lg-6">
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
@ -101,11 +176,24 @@ Form.propTypes = {
submitting: PropTypes.bool, submitting: PropTypes.bool,
invalid: PropTypes.bool, invalid: PropTypes.bool,
interfaces: PropTypes.object, interfaces: PropTypes.object,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object, initialValues: PropTypes.object,
processingConfig: PropTypes.bool, processingConfig: PropTypes.bool,
processingInterfaces: PropTypes.bool,
enabled: PropTypes.bool,
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

@ -1,114 +0,0 @@
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--settings">
<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">
{interfaces[interfaceValue] &&
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

@ -1,49 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
class StaticLeases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
render() {
const { staticLeases, t } = this.props;
return (
<ReactTable
data={staticLeases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
}, {
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
},
]}
showPagination={false}
noDataText={t('dhcp_leases_not_found')}
minRows={6}
className="-striped -highlight card-table-overflow"
/>
);
}
}
StaticLeases.propTypes = {
staticLeases: PropTypes.array,
t: PropTypes.func,
};
export default withNamespaces()(StaticLeases);

View File

@ -0,0 +1,97 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, ipv4, mac, required } from '../../../../helpers/form';
const Form = (props) => {
const {
t,
handleSubmit,
reset,
pristine,
submitting,
toggleLeaseModal,
processingAdding,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<Field
id="mac"
name="mac"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
validate={[required, mac]}
/>
</div>
<div className="form__group">
<Field
id="ip"
name="ip"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_ip')}
validate={[required, ipv4]}
/>
</div>
<div className="form__group">
<Field
id="hostname"
name="hostname"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_hostname')}
validate={[required]}
/>
</div>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
toggleLeaseModal();
}}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || pristine || processingAdding}
>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({ form: 'leaseForm' }),
])(Form);

View File

@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import ReactModal from 'react-modal';
import Form from './Form';
const Modal = (props) => {
const {
isModalOpen,
handleSubmit,
toggleLeaseModal,
processingAdding,
} = props;
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleLeaseModal()}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
<Trans>dhcp_new_static_lease</Trans>
</h4>
<button type="button" className="close" onClick={() => toggleLeaseModal()}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
onSubmit={handleSubmit}
toggleLeaseModal={toggleLeaseModal}
processingAdding={processingAdding}
/>
</div>
</ReactModal>
);
};
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
};
export default withNamespaces()(Modal);

View File

@ -0,0 +1,111 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
import Modal from './Modal';
class StaticLeases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
handleSubmit = (data) => {
this.props.addStaticLease(data);
}
handleDelete = (ip, mac, hostname) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('delete_confirm', { key: hostname }))) {
this.props.removeStaticLease({ ip, mac, hostname });
}
}
render() {
const {
isModalOpen,
toggleLeaseModal,
processingAdding,
processingDeleting,
staticLeases,
t,
} = this.props;
return (
<Fragment>
<ReactTable
data={staticLeases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
},
{
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
},
{
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
},
{
Header: <Trans>actions_table_header</Trans>,
accessor: 'actions',
maxWidth: 150,
Cell: (row) => {
const { ip, mac, hostname } = row.original;
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
title={t('delete_table_action')}
disabled={processingDeleting}
onClick={() =>
this.handleDelete(ip, mac, hostname)
}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
},
]}
showPagination={false}
noDataText={t('dhcp_static_leases_not_found')}
className="-striped -highlight card-table-overflow"
minRows={6}
/>
<Modal
isModalOpen={isModalOpen}
toggleLeaseModal={toggleLeaseModal}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
/>
</Fragment>
);
}
}
StaticLeases.propTypes = {
staticLeases: PropTypes.array.isRequired,
isModalOpen: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
removeStaticLease: PropTypes.func.isRequired,
addStaticLease: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingDeleting: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(StaticLeases);

View File

@ -6,14 +6,15 @@ import { Trans, withNamespaces } from 'react-i18next';
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants'; import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
import Form from './Form'; import Form from './Form';
import Leases from './Leases'; import Leases from './Leases';
import StaticLeases from './StaticLeases'; import StaticLeases from './StaticLeases/index';
import Interface from './Interface';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
import Accordion from '../../ui/Accordion'; import Accordion from '../../ui/Accordion';
class Dhcp extends Component { class Dhcp extends Component {
handleFormSubmit = (values) => { handleFormSubmit = (values) => {
if (values.interface_name) {
this.props.setDhcpConfig(values); this.props.setDhcpConfig(values);
}
}; };
handleToggle = (config) => { handleToggle = (config) => {
@ -169,18 +170,16 @@ class Dhcp extends Component {
<div className="dhcp"> <div className="dhcp">
{!dhcp.processing && {!dhcp.processing &&
<Fragment> <Fragment>
<Interface
onChange={this.handleFormSubmit}
initialValues={{ interface_name }}
interfaces={dhcp.interfaces}
processing={dhcp.processingInterfaces}
enabled={dhcp.config.enabled}
/>
<Form <Form
onSubmit={this.handleFormSubmit} onSubmit={this.handleFormSubmit}
initialValues={{ ...values }} initialValues={{
interface_name,
...values,
}}
interfaces={dhcp.interfaces} interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig} processingConfig={dhcp.processingConfig}
processingInterfaces={dhcp.processingInterfaces}
enabled={enabled}
/> />
<hr/> <hr/>
<div className="card-actions mb-3"> <div className="card-actions mb-3">
@ -189,11 +188,11 @@ class Dhcp extends Component {
type="button" type="button"
className={statusButtonClass} className={statusButtonClass}
onClick={() => onClick={() =>
this.props.findActiveDhcp(dhcp.config.interface_name) this.props.findActiveDhcp(interface_name)
} }
disabled={ disabled={
dhcp.config.enabled enabled
|| !dhcp.config.interface_name || !interface_name
|| dhcp.processingConfig || dhcp.processingConfig
} }
> >
@ -222,8 +221,25 @@ class Dhcp extends Component {
</Card> </Card>
<Card title={ t('dhcp_static_leases') } bodyType="card-body box-body--settings"> <Card title={ t('dhcp_static_leases') } bodyType="card-body box-body--settings">
<div className="row"> <div className="row">
<div className="col"> <div className="col-12">
<StaticLeases staticLeases={dhcp.staticLeases} /> <StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={this.props.addStaticLease}
removeStaticLease={this.props.removeStaticLease}
toggleLeaseModal={this.props.toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => this.props.toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div> </div>
</div> </div>
</Card> </Card>
@ -241,6 +257,9 @@ Dhcp.propTypes = {
setDhcpConfig: PropTypes.func, setDhcpConfig: PropTypes.func,
findActiveDhcp: PropTypes.func, findActiveDhcp: PropTypes.func,
handleSubmit: PropTypes.func, handleSubmit: PropTypes.func,
addStaticLease: PropTypes.func,
removeStaticLease: PropTypes.func,
toggleLeaseModal: PropTypes.func,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -128,6 +128,9 @@ class Settings extends Component {
getDhcpStatus={this.props.getDhcpStatus} getDhcpStatus={this.props.getDhcpStatus}
findActiveDhcp={this.props.findActiveDhcp} findActiveDhcp={this.props.findActiveDhcp}
setDhcpConfig={this.props.setDhcpConfig} setDhcpConfig={this.props.setDhcpConfig}
addStaticLease={this.props.addStaticLease}
removeStaticLease={this.props.removeStaticLease}
toggleLeaseModal={this.props.toggleLeaseModal}
/> />
</div> </div>
</div> </div>

View File

@ -11,6 +11,9 @@ import {
getDhcpInterfaces, getDhcpInterfaces,
setDhcpConfig, setDhcpConfig,
findActiveDhcp, findActiveDhcp,
addStaticLease,
removeStaticLease,
toggleLeaseModal,
} from '../actions'; } from '../actions';
import { import {
getTlsStatus, getTlsStatus,
@ -62,6 +65,9 @@ const mapDispatchToProps = {
updateClient, updateClient,
deleteClient, deleteClient,
toggleClientModal, toggleClientModal,
addStaticLease,
removeStaticLease,
toggleLeaseModal,
}; };
export default connect( export default connect(

View File

@ -351,18 +351,49 @@ const dhcp = handleActions({
const newState = { ...state, config: newConfig, processingConfig: false }; const newState = { ...state, config: newConfig, processingConfig: false };
return newState; return newState;
}, },
[actions.toggleLeaseModal]: (state) => {
const newState = {
...state,
isModalOpen: !state.isModalOpen,
};
return newState;
},
[actions.addStaticLeaseRequest]: state => ({ ...state, processingAdding: true }),
[actions.addStaticLeaseFailure]: state => ({ ...state, processingAdding: false }),
[actions.addStaticLeaseSuccess]: (state) => {
const newState = {
...state,
processingAdding: false,
};
return newState;
},
[actions.removeStaticLeaseRequest]: state => ({ ...state, processingDeleting: true }),
[actions.removeStaticLeaseFailure]: state => ({ ...state, processingDeleting: false }),
[actions.removeStaticLeaseSuccess]: (state) => {
const newState = {
...state,
processingDeleting: false,
};
return newState;
},
}, { }, {
processing: true, processing: true,
processingStatus: false, processingStatus: false,
processingInterfaces: false, processingInterfaces: false,
processingDhcp: false, processingDhcp: false,
processingConfig: false, processingConfig: false,
processingAdding: false,
processingDeleting: false,
config: { config: {
enabled: false, enabled: false,
}, },
check: null, check: null,
leases: [], leases: [],
staticLeases: [], staticLeases: [],
isModalOpen: false,
}); });
export default combineReducers({ export default combineReducers({