Simon Marsh
8cde2ae854
All checks were successful
continuous-integration/drone/push Build is passing
414 lines
11 KiB
JavaScript
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
|