admin: connections

Massimo Melina committed Feb 4, 2022 at 09:46 UTC 65eb65d4cf07693950fbaf6450847345b96a6574
4 files changed +87 -8
admin/src/MonitorPage.ts
+44 -8
@@ -1,19 +1,26 @@
1 import _ from "lodash"
2 import { isValidElement, createElement as h } from "react"
3 import { useApiComp } from "./api"
4 -import { Refresh } from '@mui/icons-material'
5 -import { Button } from '@mui/material'
4 +import { Lock, Refresh } from '@mui/icons-material'
5 +import { Button, Grid } from '@mui/material'
6 +import { DataGrid } from "@mui/x-data-grid"
7
8 export default function MonitorPage() {
9 const [res, reload] = useApiComp('get_status')
10 if (isValidElement(res))
11 return res
11 - return h('div', {},
12 - h(Button, { onClick: reload, startIcon:h(Refresh) }, 'Reload'),
13 - h('ul', {},
14 - pair('started'),
15 - pair('http', 'HTTP', v => v.active ? 'port '+v.port : 'off'),
16 - pair('https', 'HTTPS', v => v.active ? 'port '+v.port : 'off'),
12 + return h('div', { flex: 1, display: 'flex', flexDirection: 'column' },
13 + h('div', {},
14 + h(Button, { onClick: reload, startIcon:h(Refresh) }, 'Reload') ),
15 + h(Grid, { container: true, flex: 1 },
16 + h(Grid, { item: true, md: 6 },
17 + h('ul', {},
18 + pair('started'),
19 + pair('http', 'HTTP', v => v.active ? 'port '+v.port : 'off'),
20 + pair('https', 'HTTPS', v => v.active ? 'port '+v.port : 'off'),
21 + ) ),
22 + h(Grid, { item: true, md: 6 },
23 + h(Connections))
24 )
25 )
26
@@ -32,3 +39,32 @@ export default function MonitorPage() {
39 }
40
41 const isoDateRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
42 +
43 +function Connections() {
44 + const [res] = useApiComp('get_connections')
45 + if (isValidElement(res))
46 + return res
47 + const { list } = res
48 + const rows = list.map((x:any,id:number) => ({ id, ...x }))
49 + return h(DataGrid, {
50 + pageSize: 25,
51 + columns: [
52 + {
53 + field: 'ip',
54 + headerName: 'IP',
55 + flex: 1,
56 + valueGetter: ({ row, value }) => (row.v === 6 ? `[${value}]` : value) + ' :' + row.port
57 + },
58 + {
59 + field: 'v',
60 + headerName: 'Protocol',
61 + renderCell: ({ value, row }) => h('span', {}, 'IPv' + value, row.secure && h(Lock))
62 + },
63 + {
64 + field: 'started',
65 + valueGetter: ({ value }) => new Date(value).toLocaleTimeString()
66 + },
67 + ],
68 + rows,
69 + })
70 +}
src/adminApis.ts
+15
@@ -5,6 +5,7 @@ import { HFS_STARTED } from './index'
5 import { Server } from 'http'
6 import vfsApis from './api.vfs'
7 import accountsApis from './api.accounts'
8 +import { getConnections } from './connections'
9
10 export const adminApis: ApiHandlers = {
11
@@ -41,4 +42,18 @@ export const adminApis: ApiHandlers = {
42 }
43 },
44
45 + get_connections() {
46 + return {
47 + list: getConnections().map(({ socket, started, secure }) => {
48 + return {
49 + v: socket.remoteFamily?.endsWith('6') ? 6 : 4,
50 + port: socket.remotePort,
51 + ip: socket.remoteAddress,
52 + started,
53 + secure: secure || undefined, // undefined will save some space once json-ed
54 + }
55 + })
56 + }
57 + }
58 +
59 }
src/connections.ts new
+24
@@ -0,0 +1,24 @@
1 +import { Socket } from 'net'
2 +import { app } from './index'
3 +
4 +interface Connection {
5 + socket: Socket
6 + secure: boolean
7 + started: Date
8 +}
9 +
10 +const all: Connection[] = []
11 +
12 +export function newConnection(socket: Socket, secure:boolean) {
13 + const conn: Connection = { socket, secure, started: new Date() }
14 + all.push(conn)
15 + app.emit('connection', socket) // we'll use these events for SSE
16 + socket.on('close', () => {
17 + all.splice(all.indexOf(conn), 1)
18 + app.emit('connectionClosed', socket)
19 + })
20 +}
21 +
22 +export function getConnections(): Readonly<typeof all> {
23 + return all
24 +}
src/listen.ts
+4
@@ -4,6 +4,7 @@ import { app } from './index'
4 import * as https from 'https'
5 import { watchLoad } from './watchLoad'
6 import { networkInterfaces } from 'os';
7 +import { newConnection } from './connections'
8
9 let httpSrv: http.Server
10 let httpsSrv: http.Server
@@ -93,6 +94,9 @@ function startServer(srv: http.Server, port: number, secure:string='') {
94 console.error(code === 'EADDRINUSE' ? `couldn't listen on busy port ${port}` : e)
95 reject(e)
96 })
97 +
98 + srv.on('connection', socket =>
99 + newConnection(socket, Boolean(secure)))
100 }
101 catch(e) {
102 console.error("couldn't listen on port", port, String(e))