diff --git a/backend/config.go b/backend/config.go index ab39856..6bbaf35 100644 --- a/backend/config.go +++ b/backend/config.go @@ -59,7 +59,7 @@ type ThemeConfig struct { type PaginationConfig struct { RoutesFilteredPageSize int `ini:"routes_filtered_page_size"` - RoutesAcceptedPageSize int `ini:"routes_accpted_page_size"` + RoutesAcceptedPageSize int `ini:"routes_accepted_page_size"` RoutesNotExportedPageSize int `ini:"routes_not_exported_page_size"` } diff --git a/backend/sources/birdwatcher/parsers.go b/backend/sources/birdwatcher/parsers.go index 759419b..8a04c6a 100644 --- a/backend/sources/birdwatcher/parsers.go +++ b/backend/sources/birdwatcher/parsers.go @@ -53,6 +53,7 @@ func parseApiStatus(bird ClientResponse, config Config) (api.ApiStatus, error) { return status, fmt.Errorf(birdErr) } + // Parse TTL ttl, err := parseServerTime( bird["ttl"], config.ServerTime, @@ -62,10 +63,39 @@ func parseApiStatus(bird ClientResponse, config Config) (api.ApiStatus, error) { return api.ApiStatus{}, err } + // Parse Cache Status + cacheStatus, _ := parseCacheStatus(birdApi, config) + status := api.ApiStatus{ Version: birdApi["Version"].(string), ResultFromCache: birdApi["result_from_cache"].(bool), Ttl: ttl, + CacheStatus: cacheStatus, + } + + return status, nil +} + +// Parse cache status from api response +func parseCacheStatus(cacheStatus map[string]interface{}, config Config) (api.CacheStatus, error) { + cache, ok := cacheStatus["cache_status"].(map[string]interface{}) + if !ok { + return api.CacheStatus{}, fmt.Errorf("Invalid Cache Status") + } + + cachedAt, ok := cache["cached_at"].(map[string]interface{}) + if !ok { + return api.CacheStatus{}, fmt.Errorf("Invalid Cache Status") + } + + cachedAtTime, err := parseServerTime(cachedAt["date"], config.ServerTime, config.Timezone) + if err != nil { + return api.CacheStatus{}, err + } + + status := api.CacheStatus{ + CachedAt: cachedAtTime, + // We ommit OrigTTL for now... } return status, nil diff --git a/backend/sources/birdwatcher/source.go b/backend/sources/birdwatcher/source.go index 8f6c3fa..0f9f8ba 100644 --- a/backend/sources/birdwatcher/source.go +++ b/backend/sources/birdwatcher/source.go @@ -210,10 +210,14 @@ func (self *Birdwatcher) Routes(neighbourId string) (*api.RoutesResponse, error) gateway := "" learnt_from := "" - if len(imported) > 0 { // infer next_hop ip address from imported[0] - gateway = imported[0].Gateway //TODO: change mechanism to infer gateway when state becomes available elsewhere. - learnt_from = mustString(imported[0].Details["learnt_from"], gateway) // also take learnt_from address into account if present. - // ^ learnt_from is regularly present on routes for remote-triggered blackholing or on filtered routes (e.g. next_hop not in AS-Set) + if len(imported) > 0 { + // infer next_hop ip address from imported[0] + gateway = imported[0].Gateway + //TODO: change mechanism to infer gateway when state becomes available elsewhere. + learnt_from = mustString(imported[0].Details["learnt_from"], gateway) + // also take learnt_from address into account if present. + // ^ learnt_from is regularly present on routes for remote-triggered + // blackholing or on filtered routes (e.g. next_hop not in AS-Set) } // Optional: Filtered @@ -233,7 +237,9 @@ func (self *Birdwatcher) Routes(neighbourId string) (*api.RoutesResponse, error) } // choose routes with next_hop == gateway of this neighbour for _, route := range filtered { - if (route.Gateway == gateway) || (route.Gateway == learnt_from) || (route.Details["learnt_from"] == gateway) { + if (route.Gateway == gateway) || + (route.Gateway == learnt_from) || + (route.Details["learnt_from"] == gateway) { result_filtered = append(result_filtered, route) delete(importedMap, route.Id) // remove routes that are filtered on pipe } else if len(imported) == 0 { // in case there are just filtered routes diff --git a/client/assets/scss/components/status.scss b/client/assets/scss/components/status.scss index 5f65b70..e617992 100644 --- a/client/assets/scss/components/status.scss +++ b/client/assets/scss/components/status.scss @@ -19,18 +19,15 @@ } .routeserver-status { - ul { - margin: 0px; - padding: 0px; - list-style: none; - li { - padding: 10px; + margin: 0px; + padding: 0px; + td { + vertical-align: top; + padding: 4px; - i { - width: 25px; - } + i { + width: 15px; } - } } diff --git a/client/components/api-status/cache.jsx b/client/components/api-status/cache.jsx new file mode 100644 index 0000000..f016c62 --- /dev/null +++ b/client/components/api-status/cache.jsx @@ -0,0 +1,30 @@ + +import React from 'react' +import {connect} from 'react-redux' + +import {parseServerTime} from 'components/datetime/parse' + +import moment from 'moment' + +/* + * Calculate age (generated_at), and set from_cache_status + */ +export const apiCacheStatus = function(apiStatus) { + if (apiStatus == {}) { + return null; + } + + const cacheStatus = apiStatus["cache_status"] || {}; + const cachedAt = cacheStatus.cached_at; + if (!cachedAt) { + return null; + } + + const fromCache = apiStatus.result_from_cache; + const ttl = parseServerTime(apiStatus.ttl); + const generatedAt = parseServerTime(cachedAt); + const age = ttl.diff(generatedAt); // ms + + return {fromCache, age, generatedAt, ttl}; +}; + diff --git a/client/components/datetime/index.jsx b/client/components/datetime/index.jsx index 96de70b..c50605a 100644 --- a/client/components/datetime/index.jsx +++ b/client/components/datetime/index.jsx @@ -10,6 +10,8 @@ import React from 'react' import moment from 'moment' +import {parseServerTime} from './parse' + export default class Datetime extends React.Component { render() { @@ -18,7 +20,7 @@ export default class Datetime extends React.Component { timefmt = 'LLLL'; } - let time = moment(this.props.value); + let time = parseServerTime(this.props.value); return ( {time.format(timefmt)} ); diff --git a/client/components/datetime/parse.jsx b/client/components/datetime/parse.jsx new file mode 100644 index 0000000..0fc3591 --- /dev/null +++ b/client/components/datetime/parse.jsx @@ -0,0 +1,16 @@ + +/* + * Some datetime parsing helper functions + */ + +import moment from 'moment' + + +window.moment = moment; + +export function parseServerTime(serverTime) { + const fmt = "YYYY-MM-DDTHH:mm:ss.SSSSSSSSZ"; // S was 4 byte short + return moment(serverTime, fmt); +} + + diff --git a/client/components/relativetime/timestamp.jsx b/client/components/relativetime/timestamp.jsx index b944084..d41373a 100644 --- a/client/components/relativetime/timestamp.jsx +++ b/client/components/relativetime/timestamp.jsx @@ -7,9 +7,9 @@ window.momnet = moment; export default class RelativeTimestamp extends React.Component { render() { - - let now = moment.utc() - let rel = moment(now._d.getTime() - (this.props.value / 1000.0 / 1000.0)) + const tsMs = this.props.value / 1000.0 / 1000.0; // nano -> micro -> milli + const now = moment.utc() + const rel = now.subtract(tsMs, 'ms'); return ( {rel.fromNow(this.props.suffix)} diff --git a/client/components/routeservers/routes/page.jsx b/client/components/routeservers/routes/page.jsx index 1d0b01b..63750e9 100644 --- a/client/components/routeservers/routes/page.jsx +++ b/client/components/routeservers/routes/page.jsx @@ -11,6 +11,8 @@ import Details from '../details' import Status from '../status' import PageHeader from 'components/page-header' +import {apiCacheStatus} from 'components/api-status/cache' + import ProtocolName from 'components/routeservers/protocols/name' @@ -106,6 +108,11 @@ class RoutesPage extends React.Component { } render() { + let cacheStatus = apiCacheStatus(this.props.routes.received.apiStatus); + if (this.props.anyLoading) { + cacheStatus = null; + } + return(
+ | Last Reboot: |
+
+ | Last Reconfig: |
+
+ | {rsStatus.message} | +