initial port of neighbors table
This commit is contained in:
parent
96454440ac
commit
70fab38365
@ -1,15 +1,9 @@
|
||||
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
import { useRef }
|
||||
import { useRef
|
||||
, useMemo
|
||||
}
|
||||
from 'react';
|
||||
|
||||
import { useLocation }
|
||||
from 'react-router-dom';
|
||||
|
||||
import { ipToNumeric }
|
||||
from 'app/utils/ip'
|
||||
|
||||
import { useNeighbors }
|
||||
from 'app/components/neighbors/Provider';
|
||||
import NeighborsTable
|
||||
@ -34,73 +28,6 @@ const getFilterAsn = (filter) => {
|
||||
return asn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort alphanumeric
|
||||
*/
|
||||
const sortAnum = (sort) => {
|
||||
return (a, b) => {
|
||||
const va = a[sort];
|
||||
const vb = b[sort];
|
||||
if (va < vb ) { return -1; }
|
||||
if (va > vb ) { return 1; }
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by IPAddress
|
||||
*/
|
||||
const sortIpAddr = (sort) => {
|
||||
return (a, b) => {
|
||||
const va = ipToNumeric(a[sort]);
|
||||
const vb = ipToNumeric(b[sort]);
|
||||
|
||||
// Handle ipv6 case
|
||||
if (va instanceof bigInt) {
|
||||
return va.compareTo(vb);
|
||||
}
|
||||
|
||||
if (va < vb ) { return -1; }
|
||||
if (va > vb ) { return 1; }
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort with order
|
||||
*/
|
||||
const sortOrder = (cmp, order) => {
|
||||
return (a, b) => {
|
||||
const res = cmp(a, b);
|
||||
if (order === 'desc') {
|
||||
return res * -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort neighbors
|
||||
*/
|
||||
const sortNeighbors = (neighbors, sort, order) => {
|
||||
// Make compare function
|
||||
let cmp = sortAnum(sort);
|
||||
if (sort === "address") {
|
||||
cmp = sortIpAddr(sort);
|
||||
}
|
||||
return neighbors.sort(sortOrder(cmp, order));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if state is up or established
|
||||
*/
|
||||
const isUpState = (s) => {
|
||||
if (!s) { return false; }
|
||||
s = s.toLowerCase();
|
||||
return (s.includes("up") || s.includes("established"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter neighbors
|
||||
@ -137,11 +64,14 @@ const Neighbors = ({filter}) => {
|
||||
const refDown = useRef();
|
||||
const {isLoading, neighbors} = useNeighbors();
|
||||
|
||||
const filtered = useMemo(
|
||||
() => filterNeighbors(neighbors, filter),
|
||||
[neighbors, filter]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIndicator show={true} />;
|
||||
}
|
||||
|
||||
const filtered = filterNeighbors(neighbors, filter);
|
||||
if (!filtered || filtered.length === 0) {
|
||||
// Empty Neighbors List
|
||||
return (
|
||||
|
@ -1,41 +1,311 @@
|
||||
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
import { FontAwesomeIcon }
|
||||
from '@fortawesome/react-fontawesome';
|
||||
import { faCircleArrowUp
|
||||
, faCircleArrowDown
|
||||
}
|
||||
from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
|
||||
import { useMemo
|
||||
}
|
||||
from 'react';
|
||||
|
||||
import { useParams }
|
||||
from 'react-router-dom';
|
||||
|
||||
import { Link
|
||||
}
|
||||
from 'react-router-dom';
|
||||
|
||||
import { ipToNumeric }
|
||||
from 'app/utils/ip'
|
||||
|
||||
import { useConfig }
|
||||
from 'app/components/config/Provider';
|
||||
import { useSelectedRouteServer }
|
||||
from 'app/components/routeservers/Provider';
|
||||
import { useQuery
|
||||
, useQueryLink
|
||||
}
|
||||
from 'app/components/query';
|
||||
|
||||
import RelativeTimestamp
|
||||
from 'app/components/datetime/RelativeTimestamp';
|
||||
|
||||
/**
|
||||
* Default: Sort by ASN, ascending order.
|
||||
*/
|
||||
const querySortDefault = {
|
||||
s: 'asn',
|
||||
o: 'asc',
|
||||
};
|
||||
|
||||
const lookupProperty = (obj, path) => {
|
||||
let property = path.split(".").reduce((acc, part) => acc[part], obj);
|
||||
if (typeof(property) == "undefined") {
|
||||
property = `Property "${path}" not found in object.`;
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if state is up or established
|
||||
*/
|
||||
const isUpState = (s) => {
|
||||
if (!s) { return false; }
|
||||
s = s.toLowerCase();
|
||||
return (s.includes("up") || s.includes("established"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort alphanumeric
|
||||
*/
|
||||
const sortAnum = (sort) => {
|
||||
return (a, b) => {
|
||||
const va = a[sort];
|
||||
const vb = b[sort];
|
||||
if (va < vb ) { return -1; }
|
||||
if (va > vb ) { return 1; }
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by IPAddress
|
||||
*/
|
||||
const sortIpAddr = (sort) => {
|
||||
return (a, b) => {
|
||||
const va = ipToNumeric(a[sort]);
|
||||
const vb = ipToNumeric(b[sort]);
|
||||
|
||||
// Handle ipv6 case
|
||||
if (va instanceof bigInt) {
|
||||
return va.compareTo(vb);
|
||||
}
|
||||
|
||||
if (va < vb ) { return -1; }
|
||||
if (va > vb ) { return 1; }
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort with order
|
||||
*/
|
||||
const sortOrder = (cmp, order) => {
|
||||
return (a, b) => {
|
||||
const res = cmp(a, b);
|
||||
if (order === 'desc') {
|
||||
return res * -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort neighbors
|
||||
*/
|
||||
const sortNeighbors = (neighbors, sort, order) => {
|
||||
// Make compare function
|
||||
let cmp = sortAnum(sort);
|
||||
if (sort === "address") {
|
||||
cmp = sortIpAddr(sort);
|
||||
}
|
||||
return neighbors.sort(sortOrder(cmp, order));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Section renders the sections title
|
||||
*/
|
||||
const Section = ({state}) => {
|
||||
let sectionTitle = '';
|
||||
let sectionCls = 'card-header card-header-neighbors ';
|
||||
switch(state) {
|
||||
case 'up':
|
||||
sectionTitle = 'BGP Sessions Established';
|
||||
sectionCls += 'established ';
|
||||
break;
|
||||
case 'down':
|
||||
sectionTitle = 'BGP Sessions Down';
|
||||
sectionCls += 'down ';
|
||||
break;
|
||||
case 'start':
|
||||
sectionTitle = 'BGP Sessions Start';
|
||||
sectionCls += '';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return (<p className={sectionCls}>{sectionTitle}</p>);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RoutesLink is a link to the routes of the neighbor
|
||||
*/
|
||||
const RoutesLink = ({neighbor, children}) => {
|
||||
const { routeServerId } = useParams();
|
||||
if (!isUpState(neighbor.state)) {
|
||||
return <>{children}</>;
|
||||
};
|
||||
const url = `/routeservers/${routeServerId}/protocols/${neighbor.id}/routes`;
|
||||
return (
|
||||
<Link to={url}>{children}</Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort indicator indicates the sorting order of the column
|
||||
*/
|
||||
const SortIndicator = ({order, active}) => {
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
let icon = faCircleArrowUp;
|
||||
if (order === 'desc') {
|
||||
icon = faCircleArrowDown;
|
||||
}
|
||||
return <FontAwesomeIcon icon={icon} />;
|
||||
}
|
||||
|
||||
|
||||
const ColumnHeader = ({title, id}) => {
|
||||
const [query, makeLink] = useQueryLink(querySortDefault);
|
||||
const sort = id.toLowerCase();
|
||||
const active = query.s === sort;
|
||||
|
||||
let cls = `col-neighbor-attr col-neighbor-${id} `;
|
||||
let link = makeLink({s: sort}); // s: Sort column
|
||||
|
||||
if (active) {
|
||||
cls += 'col-neighbor-active ';
|
||||
link = makeLink({o: query.o === 'asc' ? 'desc' : 'asc'}); // o: Toggle order
|
||||
}
|
||||
return (
|
||||
<th className={cls}>
|
||||
<Link to={link}>{title} <SortIndicator active={active} order={query.o} /></Link>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
// Column Widgets:
|
||||
const ColDescription = ({neighbor}) => {
|
||||
return (
|
||||
<td>
|
||||
<RoutesLink neighbor={neighbor}>
|
||||
{neighbor.description}
|
||||
{!isUpState(neighbor.state) &&
|
||||
neighbor.last_error &&
|
||||
<span className="protocol-state-error">
|
||||
{neighbor.last_error}
|
||||
</span>}
|
||||
</RoutesLink>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const ColUptime = ({neighbor}) => {
|
||||
return (
|
||||
<td className="date-since">
|
||||
<RelativeTimestamp value={neighbor.uptime} suffix={true} />
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const ColLinked = ({neighbor, column}) => {
|
||||
// Access neighbor property by path
|
||||
const property = lookupProperty(neighbor, column);
|
||||
return (
|
||||
<td>
|
||||
<RoutesLink neighbor={neighbor}>
|
||||
{property}
|
||||
</RoutesLink>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const ColPlain = ({neighbor, column}) => {
|
||||
// Access neighbor property by path
|
||||
const property = lookupProperty(neighbor, column);
|
||||
return (
|
||||
<td>{property}</td>
|
||||
);
|
||||
}
|
||||
|
||||
const ColNotAvailable = () => {
|
||||
return <td>-</td>;
|
||||
}
|
||||
|
||||
|
||||
const NeighborColumn = ({neighbor, column}) => {
|
||||
const rs = useSelectedRouteServer();
|
||||
const widgets = {
|
||||
// Special cases
|
||||
"asn": ColPlain,
|
||||
"state": ColPlain,
|
||||
|
||||
"Uptime": ColUptime,
|
||||
"Description": ColDescription,
|
||||
};
|
||||
|
||||
// For openbgpd the value is ommitted
|
||||
if (rs.type === "openbgpd") {
|
||||
widgets["routes_not_exported"] = ColNotAvailable;
|
||||
}
|
||||
|
||||
// Get render function
|
||||
let Widget = widgets[column] || ColLinked;
|
||||
return (
|
||||
<Widget neighbor={neighbor} column={column} />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* NeighborsTable renders the table of neighbors
|
||||
*/
|
||||
const NeighborsTable = ({neighbors, state, ref}) => {
|
||||
const config = useConfig();
|
||||
const [query] = useQuery();
|
||||
|
||||
const columns = config.neighbors_columns;
|
||||
const columnsOrder = config.neighbors_columns_order;
|
||||
|
||||
const sortColumn = query.s;
|
||||
const sortOrder = query.o;
|
||||
|
||||
const sortedNeighbors = useMemo(
|
||||
() => sortNeighbors(neighbors, sortColumn, sortOrder),
|
||||
[neighbors, sortColumn, sortOrder]);
|
||||
|
||||
if (!neighbors || neighbors.length === 0) {
|
||||
return null; // nothing to do here
|
||||
}
|
||||
|
||||
let sectionTitle = '';
|
||||
let sectionAnchor = 'sessions-unknown';
|
||||
let sectionCls = 'card-header card-header-neighbors ';
|
||||
const header = columnsOrder.map((col) =>
|
||||
<ColumnHeader
|
||||
key={col}
|
||||
id={col}
|
||||
title={columns[col]} />);
|
||||
|
||||
switch(state) {
|
||||
case 'up':
|
||||
sectionAnchor = 'sessions-up';
|
||||
sectionTitle = 'BGP Sessions Established';
|
||||
sectionCls += 'established ';
|
||||
break;
|
||||
case 'down':
|
||||
sectionAnchor = 'sessions-down';
|
||||
break;
|
||||
case 'start':
|
||||
sectionAnchor = 'sessions-down';
|
||||
sectionTitle = 'BGP Sessions Down';
|
||||
sectionCls += 'down ';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
let header = <td>Header</td>;
|
||||
let rows = <tr><td>Row</td></tr>;
|
||||
const rows = sortedNeighbors.map((neighbor) => {
|
||||
const columns = columnsOrder.map(
|
||||
(col) =>
|
||||
<NeighborColumn
|
||||
key={col}
|
||||
column={col}
|
||||
neighbor={neighbor} />);
|
||||
return <tr key={neighbor.id}>{columns}</tr>;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card" ref={ref}>
|
||||
<p className={sectionCls}>{sectionTitle}</p>
|
||||
|
||||
<Section state={state} />
|
||||
<table className="table table-striped table-protocols">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -1,39 +0,0 @@
|
||||
|
||||
import { useMemo
|
||||
, useCallback
|
||||
}
|
||||
from 'react';
|
||||
|
||||
import { useSearchParams
|
||||
}
|
||||
from 'react-router-dom';
|
||||
|
||||
|
||||
/**
|
||||
* useQuery is an extension to useLocation to handle
|
||||
* query parameters. Internally this uses URLSearchParams
|
||||
* for decoding but returns an object merged with the defaults.
|
||||
* To prevent loops, the search parameters are only updated
|
||||
* if they differ.
|
||||
*/
|
||||
export const useQuery = (defaults={}) => {
|
||||
const [query, setQuery] = useSearchParams(defaults);
|
||||
const params = useMemo(() => {
|
||||
// For convenient access convert params to object
|
||||
let q = {};
|
||||
for (const [k, v] of query) {
|
||||
q[k] = v;
|
||||
}
|
||||
return q;
|
||||
}, [query]);
|
||||
|
||||
const update = useCallback((q) => {
|
||||
// Only update if query differs
|
||||
const next = new URLSearchParams({...params, ...q});
|
||||
if (next.toString() !== query.toString()) {
|
||||
setQuery(next);
|
||||
}
|
||||
}, [params, query, setQuery]);
|
||||
return [params, update];
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { useSearchParams }
|
||||
from 'react-router-dom';
|
||||
|
||||
import { useQuery }
|
||||
from 'app/components/search/query';
|
||||
from 'app/components/query';
|
||||
|
||||
import { useSelectedRouteServer }
|
||||
from 'app/components/routeservers/Provider';
|
||||
@ -61,7 +61,7 @@ const NeighborsPage = () => {
|
||||
/>
|
||||
</div>
|
||||
<QuickLinks />
|
||||
<Neighbors filter={""} />
|
||||
<Neighbors filter={query.q} />
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-12 col-aside-details">
|
||||
<div className="card">
|
||||
|
Loading…
x
Reference in New Issue
Block a user