Pull request #796: + client: 2152 Smartphone compatible design for user interface

#2152

* commit 'be82502ba78d52068184b03db5fd4bb044a26583':
  Fix margins
  Fix markup
  Fix dhcp interfaces markup
  + client: 2152 Smartphone compatible design for user interface
This commit is contained in:
Artem Baskal 2020-10-08 12:10:40 +03:00
commit 1a3d98d3bf
12 changed files with 149 additions and 90 deletions

View File

@ -63,10 +63,6 @@ body {
cursor: not-allowed; cursor: not-allowed;
} }
.select--no-warning {
margin-bottom: 1.375rem;
}
.button-action { .button-action {
visibility: hidden; visibility: hidden;
} }
@ -75,3 +71,9 @@ body {
.button-action--active { .button-action--active {
visibility: visible; visibility: visible;
} }
@media (max-width: 500px) {
.dashboard .button-action {
visibility: visible;
}
}

View File

@ -33,3 +33,24 @@
right: 0; right: 0;
} }
} }
.page-title--dashboard {
display: flex;
align-items: center;
}
.dashboard-title__button{
margin: 0 0.5rem;
}
@media (max-width: 767.98px) {
.page-title--dashboard {
flex-direction: column;
align-items: flex-start;
}
.dashboard-title__button{
margin: 0.5rem 0;
display: block;
}
}

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import classNames from 'classnames';
import Statistics from './Statistics'; import Statistics from './Statistics';
import Counters from './Counters'; import Counters from './Counters';
import Clients from './Clients'; import Clients from './Clients';
@ -17,6 +17,7 @@ const Dashboard = ({
getStats, getStats,
getStatsConfig, getStatsConfig,
dashboard, dashboard,
dashboard: { protectionEnabled, processingProtection },
toggleProtection, toggleProtection,
stats, stats,
access, access,
@ -33,20 +34,12 @@ const Dashboard = ({
getAllStats(); getAllStats();
}, []); }, []);
const getToggleFilteringButton = () => {
const { protectionEnabled, processingProtection } = dashboard;
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection'; const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
return <button const buttonClass = classNames('btn btn-sm dashboard-title__button', {
type="button" 'btn-gray': protectionEnabled,
className={`btn btn-sm mr-2 ${buttonClass}`} 'btn-success': !protectionEnabled,
onClick={() => toggleProtection(protectionEnabled)} });
disabled={processingProtection}
>
<Trans>{buttonText}</Trans>
</button>;
};
const refreshButton = <button const refreshButton = <button
type="button" type="button"
@ -62,24 +55,27 @@ const Dashboard = ({
? t('for_last_24_hours') ? t('for_last_24_hours')
: t('for_last_days', { count: stats.interval }); : t('for_last_days', { count: stats.interval });
const refreshFullButton = <button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={() => getAllStats()}
>
<Trans>refresh_statics</Trans>
</button>;
const statsProcessing = stats.processingStats const statsProcessing = stats.processingStats
|| stats.processingGetConfig || stats.processingGetConfig
|| access.processing; || access.processing;
return <> return <>
<PageTitle title={t('dashboard')}> <PageTitle title={t('dashboard')} containerClass="page-title--dashboard">
<div className="page-title__actions"> <button
{getToggleFilteringButton()} type="button"
{refreshFullButton} className={buttonClass}
</div> onClick={() => toggleProtection(protectionEnabled)}
disabled={processingProtection}
>
<Trans>{buttonText}</Trans>
</button>
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={getAllStats}
>
<Trans>refresh_statics</Trans>
</button>
</PageTitle> </PageTitle>
{statsProcessing && <Loading />} {statsProcessing && <Loading />}
{!statsProcessing && <div className="row row-cards dashboard"> {!statsProcessing && <div className="row row-cards dashboard">

View File

@ -46,7 +46,7 @@ const renderInterfaceValues = ({
gateway_ip, gateway_ip,
hardware_address, hardware_address,
ip_addresses, ip_addresses,
}) => <div className='d-flex align-items-end col-6'> }) => <div className='d-flex align-items-end dhcp__interfaces-info'>
<ul className="list-unstyled m-0"> <ul className="list-unstyled m-0">
{getInterfaceValues({ {getInterfaceValues({
gateway_ip, gateway_ip,
@ -77,12 +77,12 @@ const Interfaces = () => {
return !processingInterfaces return !processingInterfaces
&& interfaces && interfaces
&& <> && <>
<div className="row align-items-center pb-2"> <div className="row dhcp__interfaces">
<div className="col-6"> <div className="col col__dhcp">
<Field <Field
name="interface_name" name="interface_name"
component={renderSelectField} component={renderSelectField}
className="form-control custom-select" className="form-control custom-select pl-4 col-md"
validate={[validateRequiredValue]} validate={[validateRequiredValue]}
label='dhcp_interface_select' label='dhcp_interface_select'
> >

View File

@ -0,0 +1,47 @@
.dhcp-form__button {
margin: 0 1rem;
}
.page-title--dhcp {
display: flex;
align-items: center;
}
.col__dhcp {
flex: 0 0 50%;
max-width: 50%;
padding-right: 0;
}
.dhcp__interfaces {
padding-bottom: 1rem;
}
.dhcp__interfaces-info {
padding: 0.5rem 0.75rem 0;
line-break: anywhere;
}
@media (max-width: 991.98px) {
.dhcp-form__button {
margin: 0.5rem 0;
display: block;
}
.page-title--dhcp {
flex-direction: column;
align-items: flex-start;
padding-bottom: 0.5rem;
}
.col__dhcp {
flex: 0 0 100%;
max-width: 100%;
padding-right: 0.75rem;
}
.dhcp__interfaces {
flex-direction: column;
padding-bottom: 0.5rem;
}
}

View File

@ -31,6 +31,7 @@ import {
calculateDhcpPlaceholdersIpv4, calculateDhcpPlaceholdersIpv4,
calculateDhcpPlaceholdersIpv6, calculateDhcpPlaceholdersIpv6,
} from '../../../helpers/helpers'; } from '../../../helpers/helpers';
import './index.css';
const Dhcp = () => { const Dhcp = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -114,7 +115,7 @@ const Dhcp = () => {
.every(Boolean) || Object.values(v6) .every(Boolean) || Object.values(v6)
.every(Boolean)); .every(Boolean));
const className = classNames('btn btn-sm mr-2', { const className = classNames('btn btn-sm', {
'btn-gray': enabled, 'btn-gray': enabled,
'btn-outline-success': !enabled, 'btn-outline-success': !enabled,
}); });
@ -141,7 +142,7 @@ const Dhcp = () => {
</button>; </button>;
}; };
const statusButtonClass = classNames('btn btn-sm mx-2', { const statusButtonClass = classNames('btn btn-sm dhcp-form__button', {
'btn-loading btn-primary': processingStatus, 'btn-loading btn-primary': processingStatus,
'btn-outline-primary': !processingStatus, 'btn-outline-primary': !processingStatus,
}); });
@ -171,9 +172,7 @@ const Dhcp = () => {
const toggleDhcpButton = getToggleDhcpButton(); const toggleDhcpButton = getToggleDhcpButton();
return <> return <>
<PageTitle title={t('dhcp_settings')} subtitle={t('dhcp_description')}> <PageTitle title={t('dhcp_settings')} subtitle={t('dhcp_description')} containerClass="page-title--dhcp">
<div className="page-title__actions">
<div className="mb-3">
{toggleDhcpButton} {toggleDhcpButton}
<button <button
type="button" type="button"
@ -185,14 +184,12 @@ const Dhcp = () => {
</button> </button>
<button <button
type="button" type="button"
className='btn btn-sm mx-2 btn-outline-secondary' className='btn btn-sm btn-outline-secondary'
disabled={!enteredSomeValue || processingConfig} disabled={!enteredSomeValue || processingConfig}
onClick={clear} onClick={clear}
> >
<Trans>reset_settings</Trans> <Trans>reset_settings</Trans>
</button> </button>
</div>
</div>
</PageTitle> </PageTitle>
{!processing && !processingInterfaces {!processing && !processingInterfaces
&& <> && <>

View File

@ -0,0 +1,11 @@
.form__button {
margin-left: 1.5rem;
}
@media (max-width: 500px) {
.form__button {
margin-left: 0;
margin-top: 1rem;
display: block;
}
}

View File

@ -6,6 +6,7 @@ import flow from 'lodash/flow';
import { CheckboxField, renderRadioField, toNumber } from '../../../helpers/form'; import { CheckboxField, renderRadioField, toNumber } from '../../../helpers/form';
import { FORM_NAME, QUERY_LOG_INTERVALS_DAYS } from '../../../helpers/constants'; import { FORM_NAME, QUERY_LOG_INTERVALS_DAYS } from '../../../helpers/constants';
import '../FormButton.css';
const getIntervalFields = (processing, t, toNumber) => QUERY_LOG_INTERVALS_DAYS.map((interval) => { const getIntervalFields = (processing, t, toNumber) => QUERY_LOG_INTERVALS_DAYS.map((interval) => {
const title = interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval }); const title = interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval });
@ -68,7 +69,7 @@ const Form = (props) => {
</button> </button>
<button <button
type="button" type="button"
className="btn btn-outline-secondary btn-standard ml-5" className="btn btn-outline-secondary btn-standard form__button"
onClick={() => handleClear()} onClick={() => handleClear()}
disabled={processingClear} disabled={processingClear}
> >

View File

@ -6,6 +6,7 @@ import flow from 'lodash/flow';
import { renderRadioField, toNumber } from '../../../helpers/form'; import { renderRadioField, toNumber } from '../../../helpers/form';
import { FORM_NAME, STATS_INTERVALS_DAYS } from '../../../helpers/constants'; import { FORM_NAME, STATS_INTERVALS_DAYS } from '../../../helpers/constants';
import '../FormButton.css';
const getIntervalFields = (processing, t, toNumber) => STATS_INTERVALS_DAYS.map((interval) => { const getIntervalFields = (processing, t, toNumber) => STATS_INTERVALS_DAYS.map((interval) => {
const title = interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval }); const title = interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval });
@ -52,7 +53,7 @@ const Form = (props) => {
</button> </button>
<button <button
type="button" type="button"
className="btn btn-outline-secondary btn-standard ml-5" className="btn btn-outline-secondary btn-standard form__button"
onClick={() => handleReset()} onClick={() => handleReset()}
disabled={processingReset} disabled={processingReset}
> >

View File

@ -36,15 +36,3 @@
font-size: 36px; font-size: 36px;
line-height: 46px; line-height: 46px;
} }
.page-title__actions {
display: block;
}
@media screen and (min-width: 768px) {
.page-title__actions {
display: inline-block;
vertical-align: baseline;
margin-left: 20px;
}
}

View File

@ -3,24 +3,23 @@ import PropTypes from 'prop-types';
import './PageTitle.css'; import './PageTitle.css';
const PageTitle = ({ title, subtitle, children }) => ( const PageTitle = ({
<div className="page-header"> title, subtitle, children, containerClass,
<h1 className="page-title"> }) => <div className="page-header">
{title} <div className={containerClass}>
<h1 className="page-title pr-2">{title}</h1>
{children} {children}
</h1> </div>
{subtitle && ( {subtitle && <div className="page-subtitle">
<div className="page-subtitle">
{subtitle} {subtitle}
</div> </div>}
)} </div>;
</div>
);
PageTitle.propTypes = { PageTitle.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
subtitle: PropTypes.string, subtitle: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
containerClass: PropTypes.string,
}; };
export default PageTitle; export default PageTitle;

View File

@ -1,7 +1,6 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Trans } from 'react-i18next'; import { Trans } from 'react-i18next';
import classNames from 'classnames';
import { createOnBlurHandler } from './helpers'; import { createOnBlurHandler } from './helpers';
import { R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants'; import { R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
@ -203,13 +202,10 @@ export const renderSelectField = ({
label, label,
}) => { }) => {
const showWarning = touched && error; const showWarning = touched && error;
const selectClass = classNames('form-control custom-select', {
'select--no-warning': !showWarning,
});
return <> return <>
{label && <label><Trans>{label}</Trans></label>} {label && <label><Trans>{label}</Trans></label>}
<select {...input} className={selectClass}>{children}</select> <select {...input} className='form-control custom-select'>{children}</select>
{showWarning {showWarning
&& <span className="form__message form__message--error form__message--left-pad"><Trans>{error}</Trans></span>} && <span className="form__message form__message--error form__message--left-pad"><Trans>{error}</Trans></span>}
</>; </>;