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:
commit
1a3d98d3bf
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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'
|
||||||
>
|
>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
&& <>
|
&& <>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
.form__button {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.form__button {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>}
|
||||||
</>;
|
</>;
|
||||||
|
|
Loading…
Reference in New Issue