@samitouri / QOSami-HFS / commits / 830159b4

admin/internet: internet reachability test

Massimo Melina committed Aug 17, 2023 at 19:24 UTC 830159b438a44ac12485daa4bed7b86fbe2b19b9
2 files changed +51 -32
admin/src/InternetPage.ts
+38 -19
@@ -1,38 +1,57 @@
1 -import { createElement as h } from 'react'
2 -import { Box, Button, Link } from '@mui/material'
1 +import { createElement as h, useState } from 'react'
2 +import { Alert, Box, Button, CircularProgress, LinearProgress, Link } from '@mui/material'
3 import { HomeWorkTwoTone, PublicTwoTone, RouterTwoTone } from '@mui/icons-material'
4 import { apiCall, useApiEx } from './api'
5 import { closeDialog, with_ } from '@hfs/shared'
6 import { Flex } from './misc'
7 -import { confirmDialog, promptDialog, toast, waitDialog } from './dialog'
7 +import { alertDialog, confirmDialog, promptDialog, toast, waitDialog } from './dialog'
8 import { NumberField } from '@hfs/mui-grid-form'
9 import md from './md'
10
11 export default function InternetPage() {
12 + const [checkResult, setCheckResult] = useState<boolean | undefined>()
13 + const [checking, setChecking] = useState(false)
14 const { data: status } = useApiEx('get_status')
15 const localColor = with_([status?.http?.error, status?.https?.error], ([h, s]) =>
16 h && s ? 'error' : h || s ? 'warning' : 'success')
15 - const { data: nat, reload } = useApiEx('get_nat')
16 - const port = nat?.port
17 + const { data: nat, reload, error } = useApiEx('get_nat')
18 + const port = nat?.internalPort
19 const wrongMap = nat?.mapped && nat.mapped.private.port !== port
20 return h(Box, {},
21 h(Box, { mb: 2 }, "This page helps you making your server work on the internet"),
20 - h(Flex, { justifyContent: 'space-around', alignItems: 'center', maxWidth: '40em' },
21 - h(Device, { name: "Local network", icon: HomeWorkTwoTone, color: localColor, ip: nat?.local_ip,
22 - below: port && h(Box, { fontSize: 'smaller' }, "port ", port),
23 - }),
24 - h(Sep),
25 - h(Device, {
26 - name: "Router", icon: RouterTwoTone, ip: nat?.gateway_ip,
27 - color: nat?.mapped && (wrongMap ? 'warning' : 'success'),
28 - below: h(Link, { fontSize: 'smaller', display: 'block', onClick: configure, sx: { cursor: 'pointer' } },
29 - "port ", wrongMap ? 'is wrong' : nat?.mapped ? nat.mapped.public.port : "unknown"),
30 - }),
31 - h(Sep),
32 - h(Device, { name: "Internet", icon: PublicTwoTone, ip: nat?.public_ip }),
33 - ),
22 + error ? h(Alert, { severity: 'warning' }, "Cannot analyze network because UPnP unavailable")
23 + : !nat ? h(CircularProgress)
24 + : h(Flex, { justifyContent: 'space-around', alignItems: 'center', maxWidth: '40em' },
25 + h(Device, { name: "Local network", icon: HomeWorkTwoTone, color: localColor, ip: nat?.localIp,
26 + below: port && h(Box, { fontSize: 'smaller' }, "port ", port),
27 + }),
28 + h(Sep),
29 + h(Device, {
30 + name: "Router", icon: RouterTwoTone, ip: nat?.gatewayIp,
31 + color: nat?.mapped && (wrongMap ? 'warning' : 'success'),
32 + below: h(Link, { fontSize: 'smaller', display: 'block', onClick: configure, sx: { cursor: 'pointer' } },
33 + "port ", wrongMap ? 'is wrong' : nat?.mapped ? nat.mapped.public.port : "unknown"),
34 + }),
35 + h(Sep),
36 + h(Device, { name: "Internet", icon: PublicTwoTone, ip: nat?.publicIp,
37 + color: checkResult === undefined ? undefined : checkResult ? 'success' : 'warning',
38 + below: checking ? h(LinearProgress, { sx: { height: '1em' } }) : h(Box, { fontSize: 'smaller' }, checkResult ? "Working!" : checkResult === false ? "Failed!" : '',
39 + ' ',
40 + h(Link, { onClick: verify, sx: { cursor: 'pointer' } }, "Verify")
41 + )
42 + }),
43 + ),
44 )
45
46 + async function verify() {
47 + setChecking(true)
48 + const { success } = await apiCall('check_server', {}).finally(() => setChecking(false))
49 + setCheckResult(success)
50 + if (success)
51 + return toast("Your server is responding correctly over the internet", 'success')
52 + alertDialog("We couldn't reach your server from the internet", 'warning')
53 + }
54 +
55 async function configure() {
56 if (wrongMap) {
57 if (!await confirmDialog(`There is a port mapping but it is pointing to the wrong port (${nat.mapped.private.port})`, { confirmText: "Fix it" })) return
src/api.net.ts
+13 -13
@@ -2,7 +2,7 @@
2
3 import { ApiError, ApiHandlers } from './apiMiddleware'
4 import { Client } from 'nat-upnp'
5 -import { HTTP_SERVICE_UNAVAILABLE, HTTP_SERVER_ERROR } from './const'
5 +import { HTTP_SERVICE_UNAVAILABLE } from './const'
6 import axios from 'axios'
7 import {parse} from 'node-html-parser'
8 import _ from 'lodash'
@@ -25,16 +25,17 @@ async function get_nat() {
25 throw new ApiError(HTTP_SERVICE_UNAVAILABLE, 'upnp failed')
26 })
27 const status = await getServerStatus()
28 - const port = status?.https?.listening && status.https.port || status?.http?.listening && status.http.port
28 + const internalPort = status?.https?.listening && status.https.port || status?.http?.listening && status.http.port
29 const mappings = await client.getMappings().catch(() => null)
30 - const mapped = _.find(mappings, x => x.private.host === address && x.private.port === port || x.description === 'hfs')
30 + const mapped = _.find(mappings, x => x.private.host === address && x.private.port === internalPort || x.description === 'hfs')
31 return {
32 - local_ip: address,
33 - gateway_ip: new URL(gateway.description).hostname,
34 - public_ip: await client.getPublicIp().catch(() => null),
32 + localIp: address,
33 + gatewayIp: new URL(gateway.description).hostname,
34 + publicIp: await client.getPublicIp().catch(() => null),
35 mapped,
36 mappings,
37 - port,
37 + internalPort,
38 + externalPort: mapped?.public.port,
39 }
40 }
41
@@ -42,23 +43,22 @@ const apis: ApiHandlers = {
43 get_nat,
44
45 async map_port({ external }) {
45 - const { mapped, port } = await get_nat()
46 + const { mapped, internalPort } = await get_nat()
47 const client = new Client()
48 if (mapped)
49 await client.removeMapping({ private: mapped.private.port, public: mapped.public.port, protocol: 'tcp' })
50 if (external)
50 - await client.createMapping({ private: port, public: external, description: 'hfs', ttl: 0 })
51 + await client.createMapping({ private: internalPort, public: external, description: 'hfs', ttl: 0 })
52 return {}
53 },
54
55 async check_server() {
56 const noop = () => null
56 - const client = new Client()
57 - const publicIp = await client.getPublicIp().catch(noop)
57 + const { publicIp, internalPort, externalPort } = await get_nat()
58 if (!publicIp) return new ApiError(HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip')
59 - const {http, https} = await getServerStatus()
60 - const port = https.port > 0 ? https.port : http.port
59 const prjInfo = await getProjectInfo()
60 + const port = externalPort || internalPort
61 + console.log(`checking server ${publicIp}:${port}`)
62 for (const svc of _.shuffle<PortScannerService>(prjInfo.checkServerServices)) {
63 const api = (axios as any)[svc.method]
64 const body = svc.body?.replace('$IP', publicIp).replace('$PORT', port) || ''