render filterable communities

This commit is contained in:
Matthias Hannig 2018-10-18 15:02:34 +02:00
parent d7f3b6884c
commit 46eb39f9b5
8 changed files with 288 additions and 28 deletions

View File

@ -308,7 +308,8 @@ type PaginatedResponse struct {
}
type FilterableResponse struct {
Filters *SearchFilters `json:"filters"`
FiltersAvailable *SearchFilters `json:"filters_available"`
FiltersApplied *SearchFilters `json:"filters_applied"`
}
type PaginatedRoutesResponse struct {

View File

@ -123,7 +123,8 @@ func apiLookupPrefixGlobal(
},
},
FilterableResponse: api.FilterableResponse{
Filters: filtersAvailable,
FiltersAvailable: filtersAvailable,
FiltersApplied: filters,
},
}

View File

@ -0,0 +1,13 @@
export const FILTER_KEY_SOURCES = "sources"
export const FILTER_KEY_ASNS = "asns"
export const FILTER_KEY_COMMUNITIES = "communities"
export const FILTER_KEY_EXT_COMMUNITIES = "ext_communities"
export const FILTER_KEY_LARGE_COMMUNITIES = "large_communities"
export const FILTER_GROUP_SOURCES = 0
export const FILTER_GROUP_ASNS = 1
export const FILTER_GROUP_COMMUNITIES = 2
export const FILTER_GROUP_EXT_COMMUNITIES = 3
export const FILTER_GROUP_LARGE_COMMUNITIES = 4

View File

@ -0,0 +1,220 @@
import React from 'react'
import {connect} from 'react-redux'
import {makeReadableCommunity}
from 'components/routeservers/communities/utils'
import {FILTER_GROUP_SOURCES,
FILTER_GROUP_ASNS,
FILTER_GROUP_COMMUNITIES,
FILTER_GROUP_EXT_COMMUNITIES,
FILTER_GROUP_LARGE_COMMUNITIES}
from './filter-groups'
class RouteserversSelect extends React.Component {
render() {
// Sort filters available
const sortedFiltersAvailable = this.props.available.sort((a, b) => {
return a.value - b.value;
});
// Build options
const optionsAvailable = sortedFiltersAvailable.map((filter) => {
return <option key={filter.value} value={filter.value}>
{filter.name} ({filter.cardinality})
</option>;
});
return (
<table className="select-ctrl">
<tbody>
<tr>
<td className="select-container">
<select className="form-control">
{optionsAvailable}
</select>
</td>
</tr>
</tbody>
</table>
);
}
}
class PeersFilterSelect extends React.Component {
render() {
// Sort filters available
const sortedFiltersAvailable = this.props.available.sort((a, b) => {
return a.name.localeCompare(b.name);
});
// Build options
const optionsAvailable = sortedFiltersAvailable.map((filter) => {
return <option key={filter.value} value={filter.value}>
{filter.name} ({filter.cardinality})
</option>;
});
return (
<table className="select-ctrl">
<tbody>
<tr>
<td className="select-container">
<select className="form-control">
{optionsAvailable}
</select>
</td>
</tr>
</tbody>
</table>
);
}
}
class _CommunitiesSelect extends React.Component {
render() {
const communitiesAvailable = this.props.available.communities.sort((a, b) => {
return (a.value[0] - b.value[0]) * 100000 + (a.value[1] - b.value[1]);
});
const extCommunitiesAvailable = this.props.available.ext.sort((a, b) => {
return (a.value[1] - b.value[1]) * 100000 + (a.value[2] - b.value[2]);
});
const largeCommunitiesAvailable = this.props.available.large.sort((a, b) => {
return (a.value[0] - b.value[0]) * 10000000000 +
(a.value[1] - b.value[1]) * 100000 +
(a.value[2] - b.value[2]);
});
const communitiesOptions = communitiesAvailable.map((filter) => {
const name = makeReadableCommunity(this.props.communities, filter.value);
const cls = `select-bgp-community-0-${filter.value[0]} ` +
`select-bgp-community-1-${filter.value[1]}`;
return (
<option key={filter.value} value={filter.value} className={cls}>
{filter.name} {name} ({filter.cardinality})
</option>
);
});
const extCommunitiesOptions = extCommunitiesAvailable.map((filter) => {
const name = makeReadableCommunity(this.props.communities, filter.value);
const cls = `select-bgp-community-0-${filter.value[0]} ` +
`select-bgp-community-1-${filter.value[1]}` +
`select-bgp_community-2-${filter.value[2]}`;
return (
<option key={filter.value} value={filter.value} className={cls}>
{filter.name} {name} ({filter.cardinality})
</option>
);
});
const largeCommunitiesOptions = largeCommunitiesAvailable.map((filter) => {
const name = makeReadableCommunity(this.props.communities, filter.value);
const cls = `select-bgp-community-0-${filter.value[0]} ` +
`select-bgp-community-1-${filter.value[1]}` +
`select-bgp_community-2-${filter.value[2]}`;
return (
<option key={filter.value} value={filter.value} className={cls}>
{filter.name} {name} ({filter.cardinality})
</option>
);
});
return (
<table className="select-ctrl">
<tbody>
<tr>
<td className="select-container">
<select className="form-control">
{communitiesOptions.length > 0 &&
<optgroup label="Communities">
{communitiesOptions}
</optgroup>}
{extCommunitiesOptions.length > 0 &&
<optgroup label="Ext. Communities">
{extCommunitiesOptions}
</optgroup>}
{largeCommunitiesOptions.length > 0 &&
<optgroup label="Large Communities">
{largeCommunitiesOptions}
</optgroup>}
</select>
</td>
<td>
<button className="btn">
<i className="fa fa-plus"></i>
</button>
</td>
</tr>
</tbody>
</table>
);
}
}
const CommunitiesSelect = connect(
(state) => ({
communities: state.config.bgp_communities,
})
)(_CommunitiesSelect);
class FiltersEditor extends React.Component {
render() {
return (
<div className="card lookup-filters-editor">
<h2>Route server</h2>
<RouteserversSelect available={this.props.availableSources}
applied={this.props.appliedSources} />
<h2>Neighbor</h2>
<PeersFilterSelect available={this.props.availableAsns}
applied={this.props.appliedAsns} />
<h2>Communities</h2>
<CommunitiesSelect available={this.props.availableCommunities}
applied={this.props.appliedCommunities} />
</div>
);
}
}
export default connect(
(state) => ({
available: state.lookup.filtersAvailable,
applied: state.lookup.filtersApplied,
availableSources: state.lookup.filtersAvailable[FILTER_GROUP_SOURCES].filters,
appliedSources: state.lookup.filtersApplied[FILTER_GROUP_SOURCES].filters,
availableAsns: state.lookup.filtersAvailable[FILTER_GROUP_ASNS].filters,
appliedAsns: state.lookup.filtersApplied[FILTER_GROUP_ASNS].filters,
availableCommunities: {
communities: state.lookup.filtersAvailable[FILTER_GROUP_COMMUNITIES].filters,
ext: state.lookup.filtersAvailable[FILTER_GROUP_EXT_COMMUNITIES].filters,
large: state.lookup.filtersAvailable[FILTER_GROUP_LARGE_COMMUNITIES].filters,
},
appliedCommunities: {
communities: state.lookup.filtersApplied[FILTER_GROUP_COMMUNITIES].filters,
ext: state.lookup.filtersApplied[FILTER_GROUP_EXT_COMMUNITIES].filters,
large: state.lookup.filtersApplied[FILTER_GROUP_LARGE_COMMUNITIES].filters,
},
})
)(FiltersEditor);

View File

@ -6,6 +6,7 @@ import PageHeader from 'components/page-header'
import Lookup from 'components/lookup'
import LookupSummary from 'components/lookup/results-summary'
import LookupFilters from 'components/lookup/filters'
import Content from 'components/content'
@ -22,6 +23,7 @@ class _LookupView extends React.Component {
</div>
<div className="col-aside-details col-lg-3 col-md-12">
<LookupSummary />
<LookupFilters />
</div>
</div>
);

View File

@ -13,12 +13,23 @@ import {LOAD_RESULTS_REQUEST,
const LOCATION_CHANGE = '@@router/LOCATION_CHANGE'
const initialFilterState = [
{"key": "sources", "filters": []},
{"key": "asns", "filters": []},
{"key": "communities", "filters": []},
{"key": "ext_communities", "filters": []},
{"key": "large_communities", "filters": []},
];
const initialState = {
query: "",
queryValue: "",
anchor: "",
filtersAvailable: initialFilterState,
filtersApplied: initialFilterState,
routesImported: [],
routesFiltered: [],
@ -91,6 +102,10 @@ const _loadQueryResult = function(state, payload) {
routesImported: imported.routes,
routesFiltered: filtered.routes,
// Filters available
filtersAvailable: results.filters_available,
filtersApplied: results.filters_applied,
// Pagination
pageImported: imported.pagination.page,
pageFiltered: filtered.pagination.page,
@ -101,7 +116,6 @@ const _loadQueryResult = function(state, payload) {
totalRoutesImported: imported.pagination.total_results,
totalRoutesFiltered: filtered.pagination.total_results,
// Statistics
queryDurationMs: results.request_duration_ms,
totalRoutes: imported.pagination.total_results + filtered.pagination.total_results

View File

@ -2,28 +2,7 @@
import React from 'react'
import {connect} from 'react-redux'
import {resolveCommunity} from './utils'
/*
* Expand variables in string:
* "Test AS$0 rejects $2"
* will expand with [23, 42, 123] to
* "Test AS23 rejects 123"
*/
function _expandVars(str, vars) {
if (!str) {
return str; // We don't have to do anything.
}
var res = str;
vars.map((v, i) => {
res = res.replace(`$${i}`, v);
});
return res;
}
import {makeReadableCommunity} from './utils'
/*
* Make style tags
@ -36,12 +15,15 @@ function _makeStyleTags(community) {
}
/*
* Render community label
*/
class Label extends React.Component {
render() {
// Lookup communities
const readableCommunityLabel = resolveCommunity(this.props.communities, this.props.community);
const readableCommunity = _expandVars(readableCommunityLabel, this.props.community);
const readableCommunity = makeReadableCommunity(
this.props.communities,
this.props.community);
const key = this.props.community.join(":");
let cls = 'label label-bgp-community ';

View File

@ -72,4 +72,31 @@ export function isRejectCandidate(rejectCommunities, route) {
return (resolved.length > 0);
}
/*
* Expand variables in string:
* "Test AS$0 rejects $2"
* will expand with [23, 42, 123] to
* "Test AS23 rejects 123"
*/
export function expandVars(str, vars) {
if (!str) {
return str; // We don't have to do anything.
}
var res = str;
vars.map((v, i) => {
res = res.replace(`$${i}`, v);
});
return res;
}
export function makeReadableCommunity(communities, community) {
const label = resolveCommunity(communities, community);
return expandVars(label, community);
}
export function communityRepr(community) {
return community.join(":");
}