diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 6ba40384..bbfe2336 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -286,7 +286,9 @@ "client_deleted": "Client \"{{key}}\" successfully deleted", "client_added": "Client \"{{key}}\" successfully added", "client_updated": "Client \"{{key}}\" successfully updated", - "table_statistics": "Statistics (last 24 hours)", + "table_statistics": "Requests count (last 24 hours)", "clients_not_found": "No clients found", - "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?" + "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", + "auto_clients_title": "Clients (runtime)", + "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration" } \ No newline at end of file diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 493a4140..39224388 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -213,9 +213,14 @@ export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS'); export const getClients = () => async (dispatch) => { dispatch(getClientsRequest()); try { - const clients = await apiClient.getClients(); - const sortedClients = sortClients(clients); - dispatch(getClientsSuccess(sortedClients)); + const data = await apiClient.getClients(); + const sortedClients = data.clients && sortClients(data.clients); + const sortedAutoClients = data.auto_clients && sortClients(data.auto_clients); + + dispatch(getClientsSuccess({ + clients: sortedClients || [], + autoClients: sortedAutoClients || [], + })); } catch (error) { dispatch(addErrorToast({ error })); dispatch(getClientsFailure()); diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index d7081e38..fbef279e 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -24,7 +24,8 @@ class Clients extends Component { Header: 'IP', accessor: 'ip', Cell: ({ value }) => { - const clientName = getClientName(this.props.clients, value); + const clientName = getClientName(this.props.clients, value) + || getClientName(this.props.autoClients, value); let client; if (clientName) { @@ -79,6 +80,7 @@ Clients.propTypes = { dnsQueries: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, clients: PropTypes.array.isRequired, + autoClients: PropTypes.array.isRequired, t: PropTypes.func, }; diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index 84215d56..51ede02c 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -96,6 +96,7 @@ class Dashboard extends Component { refreshButton={refreshButton} topClients={dashboard.topStats.top_clients} clients={dashboard.clients} + autoClients={dashboard.autoClients} />
diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js index 7791025a..beae9e53 100644 --- a/client/src/components/Logs/index.js +++ b/client/src/components/Logs/index.js @@ -196,7 +196,8 @@ class Logs extends Component { Cell: (row) => { const { reason } = row.original; const isFiltered = row ? reason.indexOf('Filtered') === 0 : false; - const clientName = getClientName(dashboard.clients, row.value); + const clientName = getClientName(dashboard.clients, row.value) + || getClientName(dashboard.autoClients, row.value); let client; if (clientName) { diff --git a/client/src/components/Settings/Clients/AutoClients.js b/client/src/components/Settings/Clients/AutoClients.js new file mode 100644 index 00000000..1f750909 --- /dev/null +++ b/client/src/components/Settings/Clients/AutoClients.js @@ -0,0 +1,131 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; +import ReactTable from 'react-table'; + +import { CLIENT_ID } from '../../../helpers/constants'; +import Card from '../../ui/Card'; + +class AutoClients extends Component { + getClient = (name, clients) => { + const client = clients.find(item => name === item.name); + + if (client) { + const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP; + + return { + identifier, + use_global_settings: true, + ...client, + }; + } + + return { + identifier: 'ip', + use_global_settings: true, + }; + }; + + getStats = (ip, stats) => { + if (stats && stats.top_clients) { + return stats.top_clients[ip]; + } + + return ''; + }; + + columns = [ + { + Header: this.props.t('table_client'), + accessor: 'ip', + Cell: (row) => { + if (row.value) { + return ( +
+ + {row.value} (IP) + +
+ ); + } else if (row.original && row.original.mac) { + return ( +
+ + {row.original.mac} (MAC) + +
+ ); + } + + return ''; + }, + }, + { + Header: this.props.t('table_name'), + accessor: 'name', + Cell: ({ value }) => ( +
+ + {value} + +
+ ), + }, + { + Header: this.props.t('table_statistics'), + accessor: 'statistics', + Cell: (row) => { + const clientIP = row.original.ip; + const clientStats = clientIP && this.getStats(clientIP, this.props.topStats); + + if (clientStats) { + return ( +
+
+ {clientStats} +
+
+ ); + } + + return '–'; + }, + }, + ]; + + render() { + const { t, autoClients } = this.props; + + return ( + + + + ); + } +} + +AutoClients.propTypes = { + t: PropTypes.func.isRequired, + autoClients: PropTypes.array.isRequired, + topStats: PropTypes.object.isRequired, +}; + +export default withNamespaces()(AutoClients); diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 9292efb3..3435e610 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -1,14 +1,17 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { withNamespaces, Trans } from 'react-i18next'; + import Upstream from './Upstream'; import Dhcp from './Dhcp'; import Encryption from './Encryption'; import Clients from './Clients'; +import AutoClients from './Clients/AutoClients'; import Checkbox from '../ui/Checkbox'; import Loading from '../ui/Loading'; import PageTitle from '../ui/PageTitle'; import Card from '../ui/Card'; + import './Settings.css'; class Settings extends Component { @@ -93,20 +96,26 @@ class Settings extends Component { processingSetUpstream={settings.processingSetUpstream} /> {!dashboard.processingTopStats && !dashboard.processingClients && ( - + + + + )} { const newState = { ...state, - clients: payload, + clients: payload.clients, + autoClients: payload.autoClients, processingClients: false, }; return newState; @@ -210,6 +211,7 @@ const dashboard = handleActions({ dnsAddresses: [], dnsVersion: '', clients: [], + autoClients: [], topStats: [], });