dn42grcd/StaticRoot/grc.js
Simon Marsh 8cde2ae854
All checks were successful
continuous-integration/drone/push Build is passing
re-work loadsa stuff
2020-10-18 13:55:13 +01:00

414 lines
11 KiB
JavaScript

//////////////////////////////////////////////////////////////////////////
// DN42 Realtime GRC
//////////////////////////////////////////////////////////////////////////
const ExplorerURL='https://explorer.burble.com/#'
//////////////////////////////////////////////////////////////////////////
// smaller display components
// display an ASN
Vue.component('reg-asn', {
template: '#reg-asn',
props: [ 'asn' ],
data() {
return { }
},
computed: {
normal: function() {
if (this.asn.startsWith("AS")) {
return this.asn;
} else {
return "AS" + this.asn;
}
},
url: function() {
return ExplorerURL + "/aut-num/" + this.normal;
}
}
})
// display a path of ASNs
Vue.component('reg-path', {
template: '#reg-path',
props: [ 'path' ],
data() {
return { }
},
computed: {
list: function() {
return this.path.split(" ");
}
}
})
// display a inet{6,}num
Vue.component('reg-prefix', {
template: '#reg-prefix',
props: [ 'prefix' ],
data() {
return { }
},
computed: {
isIPv6: function() {
return this.prefix.includes(":")
},
forced: function() { return this.prefix },
url: function() {
var prefix = this.prefix.replace("/", "_")
if (this.isIPv6) {
return ExplorerURL + "/inet6num/" + prefix;
}
else {
return ExplorerURL + "/inetnum/" + prefix;
}
}
}
})
// display mntner(s)
Vue.component('reg-mnts', {
template: '#reg-mnts',
props: [ 'mnts' ],
data() {
return { }
}
})
// display mntner
Vue.component('reg-mnt', {
template: '#reg-mnt',
props: [ 'mnt' ],
data() {
return { }
},
computed: {
url: function() {
return ExplorerURL + "/mntner/" + this.mnt
}
}
})
// display a set of AS Paths
Vue.component('rt-paths', {
template: '#rt-paths',
props: [ 'paths' ],
data() {
return { }
},
methods: {
showPaths: function() {
const e = this.$createElement
// create a new array of reversed paths
var paths = [ ]
this.paths.forEach(path => {
paths.push(path.split(" ").reverse())
})
// and sort for easier comparing
paths.sort(
(a,b) => a.join(" ").localeCompare(b.join(" "))
)
// now build the modal dialog
// for each path in the list
var pathList = [ e('p', { }, 'Paths are reverse sorted') ]
paths.forEach(path => {
// for each ASN in the path
var asnList = [ ]
path.forEach(asn => {
asnList.push(e('b-link', {
class: 'px-1 my-0 py-0',
props: {
href: ExplorerURL + "/aut-num/" + asn
}
}, asn))
})
pathList.push(e('li', {
class: 'my-0 py-0'
}, asnList))
})
var vnode = e('ul', { }, pathList)
this.$bvModal.msgBoxOk([vnode], {
title: 'AS Paths',
size: 'xl',
centered: true
})
}
},
computed: {
count: function() {
return this.paths.length
}
}
})
//////////////////////////////////////////////////////////////////////////
// flap list component
Vue.component('app-flaps', {
template: '#app-flaps-template',
data() {
return {
filter: '',
}
},
computed: {
total: function() {
return this.$root.flapsTotal
},
// converts the map to an array for iterating,
// applying any filtering that is required
list: function() {
var list = [ ]
var flaps = this.$root.flaps
var total = this.total
Object.keys(flaps).forEach((prefix) => {
if (this.filter == '' || prefix.includes(this.filter)) {
list.push({
prefix: prefix,
count: flaps[prefix].count,
percent: Math.round(flaps[prefix].count*100 / total),
paths: flaps[prefix].paths,
mntner: flaps[prefix].mntner
})
}
})
list.sort(
(a,b) => b.count - a.count
)
return list.slice(0, 10)
}
},
mounted() {
this.$root.$on('flaps-update', data => {
this.update(data)
})
}
})
//////////////////////////////////////////////////////////////////////////
// roa list component
Vue.component('app-roa', {
template: '#app-roa-template',
data() {
return {
filter4: '',
filter6: ''
}
},
computed: {
list4: function() {
var list = [ ]
this.$root.roas.forEach((roa) => {
if (!roa.prefix.includes(':') &&
(this.filter4 == '' ||
roa.prefix.includes(this.filter4))) {
list.push(roa)
}
})
// sort by origin
list.sort((a,b) => {
var asna = a.origin.substr(2)
var asnb = b.origin.substr(2)
return asna - asnb
})
return list
},
list6: function() {
var list = [ ]
this.$root.roas.forEach((roa) => {
if (roa.prefix.includes(':') &&
(this.filter6 == '' ||
roa.prefix.includes(this.filter6))) {
list.push(roa)
}
})
// sort by origin
list.sort((a,b) => {
var asna = a.origin.substr(2)
var asnb = b.origin.substr(2)
return asna - asnb
})
return list
}
},
mounted() {
this.$root.$on('roa-update', data => {
this.update(data)
});
}
})
//////////////////////////////////////////////////////////////////////////
// timer to update every minute
Vue.component('app-timer', {
template: '#app-timer',
data() {
return {
seconds: 0,
timer: 0,
roaCounter: 0
}
},
methods: {
trigger: function() {
if (this.seconds <= 0) {
this.seconds = 61;
// refresh flaps data
axios.get('/api/flaps').then(response => {
// build a new map of the prefixes and total updates
var flaps = { }
var total = 0
response.data.list.forEach((update) => {
// initialise if no existing entry for this prefix
if (!(update.prefix in flaps)) {
flaps[update.prefix] = {
count: 0,
paths: [ ],
mntner: this.$root.mntmap[update.prefix] || [ ]
}
}
// canonicalise the ASN path
var tmp = update.path.split(" ")
var path = tmp.map((asn) => {
return "AS" + asn
}).join(" ")
// finally update the prefix entry
flaps[update.prefix].count += update.count
flaps[update.prefix].paths.push(path)
total += update.count
})
this.$root.flaps = flaps
this.$root.flapsTotal = total
});
if (this.roaCounter == 0) {
this.roaCounter = 59
// refresh roa data
axios.get('/api/roa').then(response => {
var roas = [ ]
response.data.list.forEach((roa) => {
var asn = "AS" + roa.origin
roas.push({
origin: asn,
prefix: roa.prefix,
mntner: this.$root.mntmap[asn] || [ ]
})
})
this.$root.roas = roas
});
}
else {
this.roaCounter -= 1
}
}
// countdown every second
this.seconds -= 1;
this.timer = setTimeout(() => this.trigger(), 1000);
}
},
mounted() {
this.trigger()
}
})
//////////////////////////////////////////////////////////////////////////
// main vue application starts here
// initialise the Vue Router
const router = new VueRouter({
routes: [
{ path: '/', component: Vue.component('app-flaps') },
{ path: '/flaps', component: Vue.component('app-flaps') },
{ path: '/roa', component: Vue.component('app-roa') }
]
})
// and the main app instance
const vm = new Vue({
el: '#grc_realtime',
data: {
flaps: { },
flapsTotal: 0,
roas: [ ],
mntmap: { }
},
mounted() {
axios.get('https://explorer.burble.com/api/registry/*inet/*/mnt-by?raw').then(response => {
// put the response in the mntmap hash
Object.keys(response.data).forEach(index => {
var slash = index.indexOf('/')
var prefix = index.substr(slash + 1)
var prefix = prefix.replace('_', '/')
this.mntmap[prefix] = response.data[index]['mnt-by']
})
// update the flaps list if required
Object.keys(this.flaps).forEach(prefix => {
this.flaps[prefix].mntner = this.mntmap[prefix]
})
})
axios.get('https://explorer.burble.com/api/registry/aut-num/*/mnt-by?raw').then(response => {
// put the response in the mntmap hash
Object.keys(response.data).forEach(index => {
var slash = index.indexOf('/')
var autnum = index.substr(slash + 1)
this.mntmap[autnum] = response.data[index]['mnt-by']
})
// and roas
this.roas.forEach(roa => {
roa.mntner = this.mntmap[roa.origin]
})
})
},
router
})
//////////////////////////////////////////////////////////////////////////
// end of code