Only allow single click on buttons

Closes #544
This commit is contained in:
Ildar Kamalov 2019-01-16 14:51:17 +03:00
parent f1b6da93cf
commit e1bb428a6a
8 changed files with 80 additions and 24 deletions

View File

@ -352,11 +352,11 @@ export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS'); export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
export const refreshFilters = () => async (dispatch) => { export const refreshFilters = () => async (dispatch) => {
dispatch(refreshFiltersRequest); dispatch(refreshFiltersRequest());
dispatch(showLoading()); dispatch(showLoading());
try { try {
const refreshText = await apiClient.refreshFilters(); const refreshText = await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess); dispatch(refreshFiltersSuccess());
if (refreshText.includes('OK')) { if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) { if (refreshText.includes('OK 0')) {

View File

@ -19,7 +19,9 @@ body {
} }
.loading-bar { .loading-bar {
position: absolute; position: fixed;
top: 0;
left: 0;
z-index: 103; z-index: 103;
height: 3px; height: 3px;
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%); background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);

View File

@ -25,12 +25,17 @@ class Dashboard extends Component {
} }
getToggleFilteringButton = () => { getToggleFilteringButton = () => {
const { protectionEnabled } = this.props.dashboard; const { protectionEnabled, processingProtection } = this.props.dashboard;
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection'; const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success'; const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
return ( return (
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}> <button
type="button"
className={`btn btn-sm mr-2 ${buttonClass}`}
onClick={() => this.props.toggleProtection(protectionEnabled)}
disabled={processingProtection}
>
<Trans>{buttonText}</Trans> <Trans>{buttonText}</Trans>
</button> </button>
); );
@ -125,6 +130,7 @@ Dashboard.propTypes = {
isCoreRunning: PropTypes.bool, isCoreRunning: PropTypes.bool,
getFiltering: PropTypes.func, getFiltering: PropTypes.func,
toggleProtection: PropTypes.func, toggleProtection: PropTypes.func,
processingProtection: PropTypes.bool,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -68,7 +68,7 @@ class Filters extends Component {
render() { render() {
const { t } = this.props; const { t } = this.props;
const { filters, userRules } = this.props.filtering; const { filters, userRules, processingRefreshFilters } = this.props.filtering;
return ( return (
<div> <div>
<PageTitle title={ t('filters') } /> <PageTitle title={ t('filters') } />
@ -95,8 +95,21 @@ class Filters extends Component {
noDataText={ t('no_filters_added') } noDataText={ t('no_filters_added') }
/> />
<div className="card-actions"> <div className="card-actions">
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}><Trans>add_filter_btn</Trans></button> <button
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}><Trans>check_updates_btn</Trans></button> className="btn btn-success btn-standart mr-2"
type="submit"
onClick={this.props.toggleFilteringModal}
>
<Trans>add_filter_btn</Trans>
</button>
<button
className="btn btn-primary btn-standart"
type="submit"
onClick={this.props.refreshFilters}
disabled={processingRefreshFilters}
>
<Trans>check_updates_btn</Trans>
</button>
</div> </div>
</Card> </Card>
</div> </div>
@ -114,6 +127,7 @@ class Filters extends Component {
toggleModal={this.props.toggleFilteringModal} toggleModal={this.props.toggleFilteringModal}
addFilter={this.props.addFilter} addFilter={this.props.addFilter}
isFilterAdded={this.props.filtering.isFilterAdded} isFilterAdded={this.props.filtering.isFilterAdded}
processingAddFilter={this.props.filtering.processingAddFilter}
title={ t('new_filter_btn') } title={ t('new_filter_btn') }
inputDescription={ t('enter_valid_filter_url') } inputDescription={ t('enter_valid_filter_url') }
/> />
@ -130,6 +144,8 @@ Filters.propTypes = {
filters: PropTypes.array, filters: PropTypes.array,
isFilteringModalOpen: PropTypes.bool.isRequired, isFilteringModalOpen: PropTypes.bool.isRequired,
isFilterAdded: PropTypes.bool, isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
processingRefreshFilters: PropTypes.bool,
}), }),
removeFilter: PropTypes.func.isRequired, removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired, toggleFilterStatus: PropTypes.func.isRequired,

View File

@ -77,6 +77,7 @@ class Logs extends Component {
type="button" type="button"
className={`btn btn-sm ${buttonClass}`} className={`btn btn-sm ${buttonClass}`}
onClick={() => this.toggleBlocking(buttonType, domain)} onClick={() => this.toggleBlocking(buttonType, domain)}
disabled={this.props.filtering.processingRules}
> >
<Trans>{buttonText}</Trans> <Trans>{buttonText}</Trans>
</button> </button>
@ -269,7 +270,7 @@ class Logs extends Component {
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME); saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
}; };
renderButtons(queryLogEnabled) { renderButtons(queryLogEnabled, logStatusProcessing) {
if (queryLogEnabled) { if (queryLogEnabled) {
return ( return (
<Fragment> <Fragment>
@ -277,6 +278,7 @@ class Logs extends Component {
className="btn btn-gray btn-sm mr-2" className="btn btn-gray btn-sm mr-2"
type="submit" type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)} onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
><Trans>disabled_log_btn</Trans></button> ><Trans>disabled_log_btn</Trans></button>
<button <button
className="btn btn-primary btn-sm mr-2" className="btn btn-primary btn-sm mr-2"
@ -297,6 +299,7 @@ class Logs extends Component {
className="btn btn-success btn-sm mr-2" className="btn btn-success btn-sm mr-2"
type="submit" type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)} onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
><Trans>enabled_log_btn</Trans></button> ><Trans>enabled_log_btn</Trans></button>
); );
} }
@ -308,7 +311,7 @@ class Logs extends Component {
<Fragment> <Fragment>
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }> <PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
<div className="page-title__actions"> <div className="page-title__actions">
{this.renderButtons(queryLogEnabled)} {this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
</div> </div>
</PageTitle> </PageTitle>
<Card> <Card>
@ -332,6 +335,8 @@ Logs.propTypes = {
userRules: PropTypes.string, userRules: PropTypes.string,
setRules: PropTypes.func, setRules: PropTypes.func,
addSuccessToast: PropTypes.func, addSuccessToast: PropTypes.func,
processingRules: PropTypes.bool,
logStatusProcessing: PropTypes.bool,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -23,7 +23,7 @@ class Dhcp extends Component {
} }
getToggleDhcpButton = () => { getToggleDhcpButton = () => {
const { config, active } = this.props.dhcp; const { config, active, processingDhcp } = this.props.dhcp;
const activeDhcpFound = active && active.found; const activeDhcpFound = active && active.found;
const filledConfig = Object.keys(config).every((key) => { const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled') { if (key === 'enabled') {
@ -39,6 +39,7 @@ class Dhcp extends Component {
type="button" type="button"
className="btn btn-standart mr-2 btn-gray" className="btn btn-standart mr-2 btn-gray"
onClick={() => this.props.toggleDhcp(config)} onClick={() => this.props.toggleDhcp(config)}
disabled={processingDhcp}
> >
<Trans>dhcp_disable</Trans> <Trans>dhcp_disable</Trans>
</button> </button>
@ -50,7 +51,7 @@ class Dhcp extends Component {
type="button" type="button"
className="btn btn-standart mr-2 btn-success" className="btn btn-standart mr-2 btn-success"
onClick={() => this.handleToggle(config)} onClick={() => this.handleToggle(config)}
disabled={!filledConfig || activeDhcpFound} disabled={!filledConfig || activeDhcpFound || processingDhcp}
> >
<Trans>dhcp_enable</Trans> <Trans>dhcp_enable</Trans>
</button> </button>

View File

@ -55,6 +55,7 @@ class Modal extends Component {
isOpen, isOpen,
title, title,
inputDescription, inputDescription,
processingAddFilter,
} = this.props; } = this.props;
const { isUrlValid, url, name } = this.state; const { isUrlValid, url, name } = this.state;
const inputUrlClass = classnames({ const inputUrlClass = classnames({
@ -71,8 +72,8 @@ class Modal extends Component {
if (!this.props.isFilterAdded) { if (!this.props.isFilterAdded) {
return ( return (
<React.Fragment> <React.Fragment>
<input type="text" className={inputNameClass} placeholder={ this.props.t('enter_name_hint') } onChange={this.handleNameChange} /> <input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
<input type="text" className={inputUrlClass} placeholder={ this.props.t('enter_url_hint') } onChange={this.handleUrlChange} /> <input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
{inputDescription && {inputDescription &&
<div className="description"> <div className="description">
{inputDescription} {inputDescription}
@ -93,7 +94,7 @@ class Modal extends Component {
<ReactModal <ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered" className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0} closeTimeoutMS={0}
isOpen={ isOpen } isOpen={isOpen}
onRequestClose={this.closeModal} onRequestClose={this.closeModal}
> >
<div className="modal-content"> <div className="modal-content">
@ -106,14 +107,26 @@ class Modal extends Component {
</button> </button>
</div> </div>
<div className="modal-body"> <div className="modal-body">
{ renderBody()} {renderBody()}
</div> </div>
{ {!this.props.isFilterAdded &&
!this.props.isFilterAdded && <div className="modal-footer">
<div className="modal-footer"> <button
<button type="button" className="btn btn-secondary" onClick={this.closeModal}><Trans>cancel_btn</Trans></button> type="button"
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}><Trans>add_filter_btn</Trans></button> className="btn btn-secondary"
</div> onClick={this.closeModal}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="button"
className="btn btn-success"
onClick={this.handleNext}
disabled={isValidForSubmit || processingAddFilter}
>
<Trans>add_filter_btn</Trans>
</button>
</div>
} }
</div> </div>
</ReactModal> </ReactModal>
@ -128,6 +141,7 @@ Modal.propTypes = {
inputDescription: PropTypes.string, inputDescription: PropTypes.string,
addFilter: PropTypes.func.isRequired, addFilter: PropTypes.func.isRequired,
isFilterAdded: PropTypes.bool, isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
t: PropTypes.func, t: PropTypes.func,
}; };

View File

@ -140,8 +140,14 @@ const dashboard = handleActions({
return newState; return newState;
}, },
[actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
[actions.toggleProtectionSuccess]: (state) => { [actions.toggleProtectionSuccess]: (state) => {
const newState = { ...state, protectionEnabled: !state.protectionEnabled }; const newState = {
...state,
protectionEnabled: !state.protectionEnabled,
processingProtection: false,
};
return newState; return newState;
}, },
@ -164,6 +170,7 @@ const dashboard = handleActions({
processingFiltering: true, processingFiltering: true,
upstreamDns: [], upstreamDns: [],
protectionEnabled: false, protectionEnabled: false,
processingProtection: false,
}); });
const queryLogs = handleActions({ const queryLogs = handleActions({
@ -228,6 +235,8 @@ const filtering = handleActions({
isFilteringModalOpen: false, isFilteringModalOpen: false,
processingFilters: false, processingFilters: false,
processingRules: false, processingRules: false,
processingAddFilter: false,
processingRefreshFilters: false,
filters: [], filters: [],
userRules: '', userRules: '',
}); });
@ -291,16 +300,19 @@ const dhcp = handleActions({
processingStatus: false, processingStatus: false,
}), }),
[actions.toggleDhcpRequest]: state => ({ ...state, processingDhcp: true }),
[actions.toggleDhcpFailure]: state => ({ ...state, processingDhcp: false }),
[actions.toggleDhcpSuccess]: (state) => { [actions.toggleDhcpSuccess]: (state) => {
const { config } = state; const { config } = state;
const newConfig = { ...config, enabled: !config.enabled }; const newConfig = { ...config, enabled: !config.enabled };
const newState = { ...state, config: newConfig }; const newState = { ...state, config: newConfig, processingDhcp: false };
return newState; return newState;
}, },
}, { }, {
processing: true, processing: true,
processingStatus: false, processingStatus: false,
processingInterfaces: false, processingInterfaces: false,
processingDhcp: false,
config: { config: {
enabled: false, enabled: false,
}, },