git ssb

16+

Dominic / patchbay



Tree: 63fa12d1ee21d1db1852e1753ede96bce6564cbd

Files: 63fa12d1ee21d1db1852e1753ede96bce6564cbd / app / page / network.js

8861 bytesRaw
1const nest = require('depnest')
2const { h, Value, Dict, dictToCollection, 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', 'My state'),
72 // mix: hello friend, this area is a total Work In Progress. It's a mess but useful diagnostics.
73 // Let's redesign and revisit it aye!
74 h('div', ['My sequence: ', state.seq]),
75 h('div', [
76 'Replicated:',
77 h('div', computed([state.seq, dictToCollection(state.replication)], (seq, replication) => {
78 return replication.map(r => {
79 if (!r.value.replicating) {
80 return h('div', [
81 h('code', r.key),
82 ' no ebt data'
83 ])
84 }
85
86 const { requested, sent } = r.value.replicating
87 // TODO report that r.value.seq is NOT the current local value of the seq (well it's ok, just just gets out of sync)
88 // const reqDiff = requested - r.value.seq
89 const reqDiff = requested - seq
90 const sentDiff = sent - seq
91
92 return h('div', [
93 h('code', r.key),
94 ` - requested: ${requested} `,
95 reqDiff === 0 ? h('i.fa.fa-check-circle-o') : `(${reqDiff})`,
96 `, sent: ${sent} `,
97 sentDiff === 0 ? h('i.fa.fa-check-circle-o') : `(${sentDiff})`
98 ])
99 })
100 }))
101 ])
102 ]),
103 h('section', [
104 h('h2', [
105 'Received Messages',
106 h('i.fa.fa-question-circle-o', {
107 title: `Messages received per ${minsPerStep}-minute block over the last ${scale / DAY} days`
108 })
109 ]),
110 canvas
111 ])
112 ])
113 ])
114
115 initialiseChart({ canvas, state })
116
117 var { container } = api.app.html.scroller({ prepend: page })
118 container.title = '/network'
119 return container
120 }
121}
122
123function initialiseChart ({ canvas, state: { data, range } }) {
124 var chart = new Chart(canvas.getContext('2d'), chartConfig(range))
125
126 watch(range, ({ lower, upper }) => {
127 // set horizontal scale
128 chart.options.scales.xAxes[0].time.min = lower
129 chart.options.scales.xAxes[0].time.max = upper
130 chart.update()
131 })
132
133 watchAll([throttle(data, 300), range], (data, { lower, upper }) => {
134 const _data = Object.keys(data)
135 .sort((a, b) => a < b ? -1 : +1)
136 .map(ts => {
137 return {
138 t: Number(ts), // NOTE - might need to offset by a half-step ?
139 y: data[ts]
140 }
141 })
142
143 // update chard data
144 chart.data.datasets[0].data = _data
145
146 // scales the height of the graph (to the visible data)!
147 const slice = _data
148 .filter(d => d.t >= lower && d.t < upper)
149 .map(d => d.y)
150 .sort((a, b) => a > b ? -1 : +1)
151
152 var h = slice[0]
153 if (!h || h < GRAPH_Y_MIN) h = GRAPH_Y_MIN // min-height
154 else h = h + (GRAPH_Y_STEP - h % GRAPH_Y_STEP) // round height to multiples of GRAPH_Y_STEP
155 chart.options.scales.yAxes[0].ticks.max = h
156
157 chart.update()
158 })
159}
160
161// ///// HELPERS /////
162
163function buildState ({ api, minsPerStep, scale }) {
164 // build localPeers, remotePeers
165 const localPeers = throttle(api.sbot.obs.localPeers(), 1000)
166 const remotePeers = computed([localPeers, throttle(api.sbot.obs.connectedPeers(), 1000)], (local, connected) => {
167 return connected.filter(peer => !local.includes(peer))
168 })
169
170 // build seq, replication (my current state, and replicated state)
171 const seq = Value()
172 const replication = Dict({})
173 onceTrue(api.sbot.obs.connection, server => {
174 setInterval(() => {
175 // TODO check ebt docs if this is best method
176 server.ebt.peerStatus(server.id, (err, data) => {
177 if (err) return console.error(err)
178
179 seq.set(data.seq)
180 for (var peer in data.peers) {
181 replication.put(peer, data.peers[peer])
182 }
183 })
184 }, 5e3)
185 })
186
187 // build data, range
188 const data = Dict({
189 [toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE]: 0,
190 [toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE - scale]: 0
191 })
192 onceTrue(api.sbot.obs.connection, server => {
193 getData({ data, server, minsPerStep, scale })
194 })
195
196 const latest = Value(toTimeBlock(Date.now(), minsPerStep))
197 // start of the most recent bar
198 setInterval(() => {
199 latest.set(toTimeBlock(Date.now(), minsPerStep))
200 }, minsPerStep / 4 * MINUTE)
201
202 const range = computed([latest], (latest) => {
203 return {
204 upper: latest + minsPerStep * MINUTE,
205 lower: latest + minsPerStep * MINUTE - scale
206 }
207 })
208 return {
209 localPeers,
210 remotePeers,
211 seq,
212 replication,
213 data, // TODO rename this !!
214 range
215 }
216}
217
218function getData ({ data, server, minsPerStep, scale }) {
219 const upperEnd = toTimeBlock(Date.now(), minsPerStep) + minsPerStep * MINUTE
220 const lowerBound = upperEnd - scale
221
222 const query = [
223 {
224 $filter: {
225 timestamp: { $gte: lowerBound }
226 }
227 }, {
228 $filter: {
229 value: {
230 author: { $ne: server.id }
231 }
232 }
233 }, {
234 $map: {
235 ts: ['timestamp']
236 }
237 }
238 ]
239
240 pull(
241 server.query.read({ query, live: true }),
242 pull.filter(m => !m.sync),
243 pull.map(m => toTimeBlock(m.ts, minsPerStep)),
244 pull.drain(ts => {
245 if (data.has(ts)) data.put(ts, data.get(ts) + 1)
246 else data.put(ts, 1)
247 })
248 )
249}
250
251function toTimeBlock (ts, minsPerStep) {
252 return Math.floor(ts / (minsPerStep * MINUTE)) * (minsPerStep * MINUTE)
253}
254
255function chartConfig ({ lower, upper }) {
256 const barColor = 'hsla(215, 57%, 60%, 1)'
257
258 return {
259 type: 'bar',
260 data: {
261 datasets: [{
262 backgroundColor: barColor,
263 borderColor: barColor,
264 data: []
265 }]
266 },
267 options: {
268 legend: {
269 display: false
270 },
271 scales: {
272 xAxes: [{
273 type: 'time',
274 distribution: 'linear',
275 time: {
276 // unit: 'day',
277 // min: lower,
278 // max: upper,
279 // tooltipFormat: 'MMMM D',
280 // stepSize: 7
281 unit: 'minute',
282 min: lower,
283 max: upper,
284 tooltipFormat: 'HH:mm',
285 stepSize: 4 * 60
286 // stepSize: 240
287 },
288 bounds: 'ticks',
289 ticks: {
290 // maxTicksLimit: 4 // already disabled
291 },
292 gridLines: {
293 display: false
294 }
295 // maxBarThickness: 2
296 }],
297
298 yAxes: [{
299 ticks: {
300 min: 0,
301 stepSize: GRAPH_Y_STEP,
302 suggestedMax: GRAPH_Y_MIN
303 }
304 }]
305 },
306 animation: {
307 // duration: 300
308 }
309 }
310 }
311}
312

Built with git-ssb-web