Merge: * WHOIS: improvements
Close #861 * commit '563f8031dce889b076c91a4b3b6489241cd2b322': - client: fix naming - client: fix typo in translations + client: add icons to the whois info * clients: remove an auto-client if a client with the same IP was added manually + whois: use "descr" or "netname" in case there's no "orgname"
This commit is contained in:
commit
407917241a
|
@ -393,8 +393,9 @@
|
|||
"sign_out": "Sign out",
|
||||
"forgot_password": "Forgot password?",
|
||||
"forgot_password_desc": "Please follow <0>these steps</0> to create a new password for your user account.",
|
||||
"city": "<0>City:</0> {{value}}",
|
||||
"country": "<0>Country:</0> {{value}}",
|
||||
"orgname": "<0>OrgName:</0> {{value}}",
|
||||
"location": "Location",
|
||||
"orgname": "Organization name",
|
||||
"netname": "Network name",
|
||||
"descr": "Description",
|
||||
"whois": "Whois"
|
||||
}
|
||||
|
|
|
@ -28,13 +28,13 @@ const countCell = dnsQueries =>
|
|||
return <Cell value={value} percent={percent} color={percentColor} />;
|
||||
};
|
||||
|
||||
const clientCell = (clients, autoClients) =>
|
||||
const clientCell = (clients, autoClients, t) =>
|
||||
function cell(row) {
|
||||
const { value } = row;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow logs__row--column">
|
||||
{formatClientCell(value, clients, autoClients)}
|
||||
{formatClientCell(value, clients, autoClients, t)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -59,11 +59,13 @@ const Clients = ({
|
|||
accessor: 'ip',
|
||||
sortMethod: (a, b) =>
|
||||
parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
Cell: clientCell(clients, autoClients),
|
||||
Cell: clientCell(clients, autoClients, t),
|
||||
},
|
||||
{
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
accessor: 'count',
|
||||
minWidth: 180,
|
||||
maxWidth: 200,
|
||||
Cell: countCell(dnsQueries),
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -129,3 +129,25 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__whois {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.logs__whois::after {
|
||||
content: "|";
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.logs__whois:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.logs__whois-icon.icons {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 2px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ class Logs extends Component {
|
|||
};
|
||||
|
||||
getClientCell = ({ original, value }) => {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, t } = this.props;
|
||||
const { clients, autoClients } = dashboard;
|
||||
const { reason, domain } = original;
|
||||
const isFiltered = this.checkFiltered(reason);
|
||||
|
@ -200,7 +200,7 @@ class Logs extends Component {
|
|||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row logs__row--overflow logs__row--column">
|
||||
{formatClientCell(value, clients, autoClients)}
|
||||
{formatClientCell(value, clients, autoClients, t)}
|
||||
</div>
|
||||
{isRewrite ? (
|
||||
<div className="logs__action">
|
||||
|
|
|
@ -4,9 +4,12 @@ import { withNamespaces } from 'react-i18next';
|
|||
import ReactTable from 'react-table';
|
||||
|
||||
import Card from '../../ui/Card';
|
||||
import WhoisCell from './WhoisCell';
|
||||
import WrapCell from './WrapCell';
|
||||
|
||||
import whoisCell from './whoisCell';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 200;
|
||||
|
||||
class AutoClients extends Component {
|
||||
getStats = (ip, stats) => {
|
||||
if (stats) {
|
||||
|
@ -21,26 +24,31 @@ class AutoClients extends Component {
|
|||
{
|
||||
Header: this.props.t('table_client'),
|
||||
accessor: 'ip',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: WrapCell,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_name'),
|
||||
accessor: 'name',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: WrapCell,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('source_label'),
|
||||
accessor: 'source',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: WrapCell,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('whois'),
|
||||
accessor: 'whois_info',
|
||||
Cell: WhoisCell,
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: whoisCell(this.props.t),
|
||||
},
|
||||
{
|
||||
Header: this.props.t('requests_count'),
|
||||
accessor: 'statistics',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: (row) => {
|
||||
const clientIP = row.original.ip;
|
||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topClients);
|
||||
|
|
|
@ -7,7 +7,8 @@ import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
|||
import Card from '../../ui/Card';
|
||||
import Modal from './Modal';
|
||||
import WrapCell from './WrapCell';
|
||||
import WhoisCell from './WhoisCell';
|
||||
|
||||
import whoisCell from './whoisCell';
|
||||
|
||||
class ClientsTable extends Component {
|
||||
handleFormAdd = (values) => {
|
||||
|
@ -138,11 +139,17 @@ class ClientsTable extends Component {
|
|||
|
||||
return (
|
||||
<div className="logs__row logs__row--icons">
|
||||
{value && value.length > 0 ? value.map(service => (
|
||||
<svg className="service__icon service__icon--table" title={service} key={service}>
|
||||
<use xlinkHref={`#service_${service}`} />
|
||||
</svg>
|
||||
)) : '–'}
|
||||
{value && value.length > 0
|
||||
? value.map(service => (
|
||||
<svg
|
||||
className="service__icon service__icon--table"
|
||||
title={service}
|
||||
key={service}
|
||||
>
|
||||
<use xlinkHref={`#service_${service}`} />
|
||||
</svg>
|
||||
))
|
||||
: '–'}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -151,7 +158,7 @@ class ClientsTable extends Component {
|
|||
Header: this.props.t('whois'),
|
||||
accessor: 'whois_info',
|
||||
minWidth: 200,
|
||||
Cell: WhoisCell,
|
||||
Cell: whoisCell(this.props.t),
|
||||
},
|
||||
{
|
||||
Header: this.props.t('requests_count'),
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const getFormattedWhois = (value) => {
|
||||
const keys = Object.keys(value);
|
||||
|
||||
if (keys.length > 0) {
|
||||
return (
|
||||
keys.map(key => (
|
||||
<div key={key} title={value[key]}>
|
||||
<Trans
|
||||
values={{ value: value[key] }}
|
||||
components={[<small key="0">text</small>]}
|
||||
>
|
||||
{key}
|
||||
</Trans>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return '–';
|
||||
};
|
||||
|
||||
const WhoisCell = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text logs__text--wrap">
|
||||
{getFormattedWhois(value)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
WhoisCell.propTypes = {
|
||||
value: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default WhoisCell;
|
|
@ -10,10 +10,7 @@ const WrapCell = ({ value }) => (
|
|||
);
|
||||
|
||||
WrapCell.propTypes = {
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
export default WrapCell;
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import React, { Fragment } from 'react';
|
||||
|
||||
import { normalizeWhois } from '../../../helpers/helpers';
|
||||
import { WHOIS_ICONS } from '../../../helpers/constants';
|
||||
|
||||
const getFormattedWhois = (value, t) => {
|
||||
const whoisInfo = normalizeWhois(value);
|
||||
const whoisKeys = Object.keys(whoisInfo);
|
||||
|
||||
if (whoisKeys.length > 0) {
|
||||
return whoisKeys.map((key) => {
|
||||
const icon = WHOIS_ICONS[key];
|
||||
return (
|
||||
<div key={key} title={t(key)}>
|
||||
{icon && (
|
||||
<Fragment>
|
||||
<svg className="logs__whois-icon text-muted-dark icons">
|
||||
<use xlinkHref={`#${icon}`} />
|
||||
</svg>
|
||||
|
||||
</Fragment>
|
||||
)}
|
||||
{whoisInfo[key]}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return '–';
|
||||
};
|
||||
|
||||
const whoisCell = t =>
|
||||
function cell(row) {
|
||||
const { value } = row;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text logs__text--wrap">{getFormattedWhois(value, t)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default whoisCell;
|
|
@ -131,6 +131,18 @@ const Icons = () => (
|
|||
<symbol id="question" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><line x1="12" y1="17" x2="12" y2="17" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="question" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><line x1="12" y1="17" x2="12" y2="17" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="network" viewBox="0 0 50 50" fill="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M 25 7 C 15.941406 7 7.339844 10.472656 0.78125 16.773438 L 0.0625 17.464844 L 5.59375 23.230469 L 6.320313 22.539063 C 11.378906 17.679688 18.015625 15 25 15 C 31.984375 15 38.621094 17.679688 43.683594 22.539063 L 44.40625 23.230469 L 49.941406 17.464844 L 49.21875 16.769531 C 42.660156 10.46875 34.058594 7 25 7 Z M 25 19 C 19.046875 19 13.394531 21.28125 9.085938 25.421875 L 8.363281 26.113281 L 13.921875 31.90625 L 14.644531 31.210938 C 17.464844 28.496094 21.144531 27 25 27 C 28.855469 27 32.535156 28.496094 35.355469 31.210938 L 36.078125 31.90625 L 41.636719 26.113281 L 40.917969 25.421875 C 36.605469 21.28125 30.953125 19 25 19 Z M 25 31 C 22.15625 31 19.453125 32.089844 17.390625 34.074219 L 16.671875 34.765625 L 25 43.441406 L 33.328125 34.765625 L 32.609375 34.074219 C 30.546875 32.089844 27.84375 31 25 31 Z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="location" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12,2C8.134,2,5,5.134,5,9c0,5,7,13,7,13s7-8,7-13C19,5.134,15.866,2,12,2z M12,11.5c-1.381,0-2.5-1.119-2.5-2.5 c0-1.381,1.119-2.5,2.5-2.5s2.5,1.119,2.5,2.5C14.5,10.381,13.381,11.5,12,11.5z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
|
|
@ -267,3 +267,10 @@ export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
|||
export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||
|
||||
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
|
||||
|
||||
export const WHOIS_ICONS = {
|
||||
location: 'location',
|
||||
orgname: 'network',
|
||||
netname: 'network',
|
||||
descr: '',
|
||||
};
|
||||
|
|
|
@ -1,32 +1,55 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { getClientInfo } from './helpers';
|
||||
import { getClientInfo, normalizeWhois } from './helpers';
|
||||
import { WHOIS_ICONS } from './constants';
|
||||
|
||||
export const formatClientCell = (value, clients, autoClients) => {
|
||||
const getFormattedWhois = (whois, t) => {
|
||||
const whoisInfo = normalizeWhois(whois);
|
||||
return (
|
||||
Object.keys(whoisInfo).map((key) => {
|
||||
const icon = WHOIS_ICONS[key];
|
||||
return (
|
||||
<span className="logs__whois text-muted" key={key} title={t(key)}>
|
||||
{icon && (
|
||||
<Fragment>
|
||||
<svg className="logs__whois-icon icons">
|
||||
<use xlinkHref={`#${icon}`} />
|
||||
</svg>
|
||||
</Fragment>
|
||||
)}{whoisInfo[key]}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const formatClientCell = (value, clients, autoClients, t) => {
|
||||
const clientInfo = getClientInfo(clients, value) || getClientInfo(autoClients, value);
|
||||
const { name, whois } = clientInfo;
|
||||
let whoisContainer = '';
|
||||
let nameContainer = value;
|
||||
|
||||
if (whois && name) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||
{name} <small className="text-muted-dark">({value})</small>
|
||||
</div>
|
||||
<div className="logs__text logs__text--wrap" title={whois}>
|
||||
<small className="text-muted">{whois}</small>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (name) {
|
||||
return (
|
||||
if (name) {
|
||||
nameContainer = (
|
||||
<span className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||
{name} <small>({value})</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (whois) {
|
||||
whoisContainer = (
|
||||
<div className="logs__text logs__text--wrap mt-1">
|
||||
{getFormattedWhois(whois, t)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
<span className="logs__text">
|
||||
<Fragment>
|
||||
{nameContainer}
|
||||
{whoisContainer}
|
||||
</Fragment>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -245,21 +245,6 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
|
|||
|
||||
export const normalizeTextarea = text => text && text.replace(/[;, ]/g, '\n').split('\n').filter(n => n);
|
||||
|
||||
const formatWhois = (whois) => {
|
||||
if (!whois) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const keys = Object.keys(whois);
|
||||
if (keys.length > 0) {
|
||||
return (
|
||||
keys.map(key => whois[key])
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getClientInfo = (clients, ip) => {
|
||||
const client = clients.find(item => ip === item.ip);
|
||||
|
||||
|
@ -268,8 +253,7 @@ export const getClientInfo = (clients, ip) => {
|
|||
}
|
||||
|
||||
const { name, whois_info } = client;
|
||||
const formattedWhois = formatWhois(whois_info);
|
||||
const whois = formattedWhois && formattedWhois.length > 0 && formattedWhois.join(' | ');
|
||||
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
|
||||
|
||||
return { name, whois };
|
||||
};
|
||||
|
@ -308,3 +292,29 @@ export const normalizeRulesTextarea = text => text && text.replace(/^\n/g, '').r
|
|||
export const isVersionGreater = (currentVersion, previousVersion) => (
|
||||
versionCompare(currentVersion, previousVersion) === -1
|
||||
);
|
||||
|
||||
export const normalizeWhois = (whois) => {
|
||||
if (Object.keys(whois).length > 0) {
|
||||
const {
|
||||
city, country, ...values
|
||||
} = whois;
|
||||
let location = (country && country) || '';
|
||||
|
||||
if (city && location) {
|
||||
location = `${location}, ${city}`;
|
||||
} else if (city) {
|
||||
location = city;
|
||||
}
|
||||
|
||||
if (location) {
|
||||
return {
|
||||
location,
|
||||
...values,
|
||||
};
|
||||
}
|
||||
|
||||
return { ...values };
|
||||
}
|
||||
|
||||
return whois;
|
||||
};
|
||||
|
|
|
@ -206,6 +206,12 @@ func (clients *clientsContainer) Add(c Client) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
ch, ok := clients.ipHost[c.IP]
|
||||
if ok {
|
||||
c.WhoisInfo = ch.WhoisInfo
|
||||
delete(clients.ipHost, c.IP)
|
||||
}
|
||||
|
||||
clients.list[c.Name] = &c
|
||||
if len(c.IP) != 0 {
|
||||
clients.ipIndex[c.IP] = &c
|
||||
|
@ -287,12 +293,14 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) {
|
|||
if ok {
|
||||
c.WhoisInfo = info
|
||||
log.Debug("Clients: set WHOIS info for client %s: %v", c.Name, c.WhoisInfo)
|
||||
return
|
||||
}
|
||||
|
||||
ch, ok := clients.ipHost[ip]
|
||||
if ok {
|
||||
ch.WhoisInfo = info
|
||||
log.Debug("Clients: set WHOIS info for auto-client %s: %v", ch.Host, ch.WhoisInfo)
|
||||
return
|
||||
}
|
||||
|
||||
ch = ClientHost{
|
||||
|
@ -310,6 +318,12 @@ func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (
|
|||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// check index
|
||||
_, ok := clients.ipIndex[ip]
|
||||
if ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check index
|
||||
c, ok := clients.ipHost[ip]
|
||||
if ok && c.Source > source {
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
whois "github.com/likexian/whois-go"
|
||||
)
|
||||
|
||||
const maxValueLength = 250
|
||||
|
||||
// Whois - module context
|
||||
type Whois struct {
|
||||
clients *clientsContainer
|
||||
|
@ -26,9 +28,19 @@ func initWhois(clients *clientsContainer) *Whois {
|
|||
return &w
|
||||
}
|
||||
|
||||
// If the value is too large - cut it and append "..."
|
||||
func trimValue(s string) string {
|
||||
if len(s) <= maxValueLength {
|
||||
return s
|
||||
}
|
||||
return s[:maxValueLength-3] + "..."
|
||||
}
|
||||
|
||||
// Parse plain-text data from the response
|
||||
func whoisParse(data string) map[string]string {
|
||||
m := map[string]string{}
|
||||
descr := ""
|
||||
netname := ""
|
||||
lines := strings.Split(data, "\n")
|
||||
for _, ln := range lines {
|
||||
ln = strings.TrimSpace(ln)
|
||||
|
@ -45,14 +57,31 @@ func whoisParse(data string) map[string]string {
|
|||
k = strings.ToLower(k)
|
||||
v := strings.TrimSpace(kv[1])
|
||||
|
||||
if k == "orgname" || k == "org-name" {
|
||||
m["orgname"] = v
|
||||
} else if k == "city" {
|
||||
m["city"] = v
|
||||
} else if k == "country" {
|
||||
m["country"] = v
|
||||
switch k {
|
||||
case "org-name":
|
||||
m["orgname"] = trimValue(v)
|
||||
case "orgname":
|
||||
fallthrough
|
||||
case "city":
|
||||
fallthrough
|
||||
case "country":
|
||||
m[k] = trimValue(v)
|
||||
|
||||
case "descr":
|
||||
descr = v
|
||||
case "netname":
|
||||
netname = v
|
||||
}
|
||||
}
|
||||
|
||||
// descr or netname -> orgname
|
||||
_, ok := m["orgname"]
|
||||
if !ok && len(descr) != 0 {
|
||||
m["orgname"] = trimValue(descr)
|
||||
} else if !ok && len(netname) != 0 {
|
||||
m["orgname"] = trimValue(netname)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue