git ssb

16+

Dominic / patchbay



Tree: 700625f33ea2e217265009f6ac9ade4897ab173f

Files: 700625f33ea2e217265009f6ac9ade4897ab173f / app / page / network.js

6848 bytesRaw
1const nest = require('depnest')
2const { h, Value, Dict, onceTrue, computed, watch, watchAll, throttle } = require('mutant')
3const Chart = require('chart.js')
4const pull = require('pull-stream')
5
6const MINUTE = 60 * 1000
7const DAY = 24 * 60 * MINUTE
8
9const GRAPH_Y_STEP = 50
10const GRAPH_Y_MIN = 100
11
12exports.gives = nest({
13 'app.html.menuItem': true,
14 'app.page.network': true
15})
16
17exports.needs = nest({
18 'about.html.avatar': 'first',
19 'app.html.scroller': 'first',
20 'app.sync.goTo': 'first',
21 'sbot.obs.connection': 'first',
22 'sbot.obs.localPeers': 'first',
23 'sbot.obs.connectedPeers': 'first'
24})
25
26exports.create = function (api) {
27 return nest({
28 'app.html.menuItem': menuItem,
29 'app.page.network': networkPage
30 })
31
32 function menuItem () {
33 return h('a', {
34 'ev-click': () => api.app.sync.goTo({ page: 'network' })
35 }, '/network')
36 }
37
38 function networkPage (location) {
39 const minsPerStep = 10
40 const scale = 1 * DAY
41
42 const state = buildState({ api, minsPerStep, scale })
43 const canvas = h('canvas', { height: 500, width: 1200, style: { height: '500px', width: '1200px' } })
44
45 const page = h('NetworkPage', [
46 h('div.container', [
47 h('h1', 'Network'),
48 h('section', [
49 h('h2', [
50 'Local Peers',
51 h('i.fa.fa-question-circle-o', { title: 'these are people on the same WiFi/ LAN as you right now. You might not know some of them yet, but you can click through to find out more about them and follow them if you like.' })
52 ]),
53 computed(state.localPeers, peers => {
54 if (!peers.length) return h('p', 'No local peers (on same wifi/ LAN)')
55
56 return peers.map(peer => api.about.html.avatar(peer))
57 })
58 ]),
59 h('section', [
60 h('h2', [
61 'Remote Peers',
62 h('i.fa.fa-question-circle-o', { title: 'these are peers which have fixed addresses, and are likely friends of friends (a.k.a. pubs)' })
63 ]),
64 computed(state.remotePeers, peers => {
65 if (!peers.length) return h('p', 'No remote peers connected')
66
67 return peers.map(peer => api.about.html.avatar(peer))
68 })
69 ]),
70 h('section', [
71 h('h2', [
72 'Received Messages',
73 h('i.fa.fa-question-circle-o', {
74 title: `Messages received per ${minsPerStep}-minute block over the last ${scale / DAY} days`
75 })
76 ]),
77 canvas
78 ])
79 ])
80 ])
81
82 initialiseChart({ canvas, state })
83
84 var { container } = api.app.html.scroller({ prepend: page })
85 container.title = '/network'
86 return container
87 }
88}
89
90function initialiseChart ({ canvas, state: { data, range } }) {
91 var chart = new Chart(canvas.getContext('2d'), chartConfig(range))
92
93 watch(range, ({ lower, upper }) => {
94 // set horizontal scale
95 chart.options.scales.xAxes[0].time.min = lower
96 chart.options.scales.xAxes[0].time.max = upper
97 chart.update()
98 })
99
100 watchAll([throttle(data, 300), range], (data, { lower, upper }) => {
101 const _data = Object.keys(data)
102 .sort((a, b) => a < b ? -1 : +1)
103 .map(ts => {
104 return {
105 t: Number(ts), // NOTE - might need to offset by a half-step ?
106 y: data[ts]
107 }
108 })
109
110 // update chard data
111 chart.data.datasets[0].data = _data
112
113 // scales the height of the graph (to the visible data)!
114 const slice = _data
115 .filter(d => d.t >= lower && d.t < upper)
116 .map(d => d.y)
117 .sort((a, b) => a > b ? -1 : +1)
118
119 var h = slice[0]
120 if (!h || h < GRAPH_Y_MIN) h = GRAPH_Y_MIN // min-height
121 else h = h + (GRAPH_Y_STEP - h % GRAPH_Y_STEP) // round height to multiples of GRAPH_Y_STEP
122 chart.options.scales.yAxes[0].ticks.max = h
123
124 chart.update()
125 })
126}
127
128// ///// HELPERS /////
129
130function buildState ({ api, minsPerStep, scale }) {
131 const data = Dict({
132 [toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE]: 0,
133 [toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE - scale]: 0
134 })
135 onceTrue(api.sbot.obs.connection, server => {
136 getData({ data, server, minsPerStep, scale })
137 })
138
139 const latest = Value(toTimeBlock(Date.now(), minsPerStep))
140 // start of the most recent bar
141 setInterval(() => {
142 latest.set(toTimeBlock(Date.now(), minsPerStep))
143 }, minsPerStep / 4 * MINUTE)
144
145 const range = computed([latest], (latest) => {
146 return {
147 upper: latest + minsPerStep * MINUTE,
148 lower: latest + minsPerStep * MINUTE - scale
149 }
150 })
151
152 const localPeers = throttle(api.sbot.obs.localPeers(), 1000)
153 const remotePeers = computed([localPeers, throttle(api.sbot.obs.connectedPeers(), 1000)], (local, connected) => {
154 return connected.filter(peer => !local.includes(peer))
155 })
156
157 return {
158 data,
159 range,
160 localPeers,
161 remotePeers
162 }
163}
164
165function getData ({ data, server, minsPerStep, scale }) {
166 const upperEnd = toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE
167 const lowerBound = upperEnd - scale
168
169 const query = [
170 {
171 $filter: {
172 timestamp: { $gte: lowerBound }
173 }
174 }, {
175 $filter: {
176 value: {
177 author: { $ne: server.id }
178 }
179 }
180 }, {
181 $map: {
182 ts: ['timestamp']
183 }
184 }
185 ]
186
187 pull(
188 server.query.read({ query, live: true }),
189 pull.filter(m => !m.sync),
190 pull.map(m => toTimeBlock(m.ts, minsPerStep)),
191 pull.drain(ts => {
192 if (data.has(ts)) data.put(ts, data.get(ts) + 1)
193 else data.put(ts, 1)
194 })
195 )
196}
197
198
199function toTimeBlock (ts, minsPerStep) {
200 return Math.floor(ts / (minsPerStep * MINUTE)) * (minsPerStep * MINUTE)
201}
202
203function chartConfig ({ lower, upper }) {
204 const barColor = 'hsla(215, 57%, 60%, 1)'
205
206 return {
207 type: 'bar',
208 data: {
209 datasets: [{
210 backgroundColor: barColor,
211 borderColor: barColor,
212 data: []
213 }]
214 },
215 options: {
216 legend: {
217 display: false
218 },
219 scales: {
220 xAxes: [{
221 type: 'time',
222 distribution: 'linear',
223 time: {
224 // unit: 'day',
225 // min: lower,
226 // max: upper,
227 // tooltipFormat: 'MMMM D',
228 // stepSize: 7
229 unit: 'minute',
230 min: lower,
231 max: upper,
232 tooltipFormat: 'HH:mm',
233 stepSize: 4 * 60
234 // stepSize: 240
235 },
236 bounds: 'ticks',
237 ticks: {
238 // maxTicksLimit: 4 // already disabled
239 },
240 gridLines: {
241 display: false
242 }
243 // maxBarThickness: 2
244 }],
245
246 yAxes: [{
247 ticks: {
248 min: 0,
249 stepSize: GRAPH_Y_STEP,
250 suggestedMax: GRAPH_Y_MIN
251 }
252 }]
253 },
254 animation: {
255 // duration: 300
256 }
257 }
258 }
259}
260

Built with git-ssb-web