admin/internet

Massimo Melina committed Aug 10, 2023 at 09:15 UTC e7cb6c269659b456e4e0c1e24d7e0068c68abc2f
7 files changed +85 -1
admin/src/InternetPage.ts new
+36
@@ -0,0 +1,36 @@
1 +import { createElement as h } from 'react'
2 +import { Box } from '@mui/material'
3 +import { HomeWorkTwoTone, PublicTwoTone, RouterTwoTone } from '@mui/icons-material'
4 +import { useApiEx } from './api'
5 +import { with_ } from '@hfs/shared'
6 +import { Flex } from './misc'
7 +
8 +export default function InternetPage() {
9 + const { data: status } = useApiEx('get_status')
10 + const localColor = with_([status?.http?.error, status?.https?.error], ([h, s]) =>
11 + h && s ? 'error' : h || s ? 'warning' : 'success')
12 + const { data: nat } = useApiEx('get_nat')
13 + return h(Box, {},
14 + h(Box, { mb: 2 }, "This page helps you making your server work on the internet"),
15 + h(Flex, { justifyContent: 'space-around', alignItems: 'center', maxWidth: '40em' },
16 + h(Device, { name: "Local network", icon: HomeWorkTwoTone, color: localColor, ip: nat?.local_ip }),
17 + h(Sep),
18 + h(Device, { name: "Router", icon: RouterTwoTone, ip: nat?.gateway_ip }),
19 + h(Sep),
20 + h(Device, { name: "Internet", icon: PublicTwoTone, ip: nat?.public_ip }),
21 + ),
22 + )
23 +}
24 +
25 +function Sep() {
26 + return h(Box, { flex: 1, className: 'animated-dashed-line' })
27 +}
28 +
29 +function Device({ name, icon, color, ip }: any) {
30 + const fontSize = 'min(20vw, 10vh)'
31 + return h(Box, { display: 'inline-block', textAlign: 'center' },
32 + h(icon, { color, sx: { fontSize, mb: '-0.1em' } }),
33 + h(Box, { fontSize: 'larger' }, name),
34 + h(Box, { fontSize: 'smaller' }, ip || '…')
35 + )
36 +}
\ No newline at end of file
admin/src/MainMenu.ts
+4 -1
@@ -6,6 +6,7 @@ import {
6 AccountTree,
7 Extension,
8 History,
9 + Home,
10 Logout,
11 ManageAccounts,
12 Monitor,
@@ -28,6 +29,7 @@ import LogsPage from './LogsPage';
29 import PluginsPage from './PluginsPage';
30 import { useApi } from './api'
31 import CustomHtmlPage from './CustomHtmlPage';
32 +import InternetPage from './InternetPage'
33
34 interface MenuEntry {
35 path: string
@@ -38,10 +40,11 @@ interface MenuEntry {
40 }
41
42 export const mainMenu: MenuEntry[] = [
41 - { path: '', icon: Public, label: "Home", title: "Admin panel", comp: HomePage },
43 + { path: '', icon: Home, label: "Home", title: "Admin panel", comp: HomePage },
44 { path: 'fs', icon: AccountTree, label: "Shared files", comp: VfsPage },
45 { path: 'accounts', icon: ManageAccounts, comp: AccountsPage },
46 { path: 'options', icon: Settings, comp: OptionsPage },
47 + { path: 'internet', icon: Public, comp: InternetPage },
48 { path: 'monitoring', icon: Monitor, comp: MonitorPage },
49 { path: 'logs', icon: History, comp: LogsPage },
50 { path: 'language', icon: Translate, comp: LangPage },
admin/src/index.css
+9
@@ -26,3 +26,12 @@ code {
26 }
27
28 ol, ul { margin-top: .2em }
29 +
30 +.animated-dashed-line {
31 + width: 100%;
32 + height: 2px;
33 + background-image: linear-gradient(to right, currentColor 50%, transparent 50%);
34 + background-size: 10px 100%; /* 20px is the length of each dash + gap */
35 + animation: animate-dash 1s alternate linear infinite;
36 +}
37 +@keyframes animate-dash { to { background-position: 20px 0; } }
\ No newline at end of file
package.json
+1
@@ -76,6 +76,7 @@
76 "limiter": "^2.1.0",
77 "lodash": "^4.17.21",
78 "minimist": "^1.2.6",
79 + "nat-upnp": "github:kaden-sharpin/node-nat-upnp",
80 "open": "^8.4.0",
81 "tssrp6a": "^3.0.0",
82 "unzip-stream": "^0.3.1",
src/adminApis.ts
+2
@@ -17,6 +17,7 @@ import accountsApis from './api.accounts'
17 import pluginsApis from './api.plugins'
18 import monitorApis from './api.monitor'
19 import langApis from './api.lang'
20 +import netApis from './api.net'
21 import { getConnections } from './connections'
22 import { debounceAsync, isLocalHost, makeNetMatcher, onOff, wait, waitFor } from './misc'
23 import events from './events'
@@ -41,6 +42,7 @@ export const adminApis: ApiHandlers = {
42 ...pluginsApis,
43 ...monitorApis,
44 ...langApis,
45 + ...netApis,
46
47 async set_config({ values: v }) {
48 if (v) {
src/api.net.ts new
+20
@@ -0,0 +1,20 @@
1 +// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2 +
3 +import { ApiHandlers } from './apiMiddleware'
4 +import { Client } from 'nat-upnp'
5 +
6 +const apis: ApiHandlers = {
7 +
8 + async get_nat() {
9 + const client = new Client()
10 + const { gateway, address} = await client.getGateway()
11 + return {
12 + local_ip: address,
13 + gateway_ip: new URL(gateway.description).hostname,
14 + public_ip: await client.getPublicIp()
15 + }
16 + },
17 +
18 +}
19 +
20 +export default apis
\ No newline at end of file
src/misc.ts
+13
@@ -195,6 +195,11 @@ export function makeNetMatcher(mask: string, emptyMaskReturns=false) {
195 neg !== all.some(x => cidr.contains(x, ip))
196 }
197
198 +export function netmaskToCIDR(netmask: string) {
199 + return netmask.split('.').map(x => Number(x).toString(2)) // to binary
200 + .join('').split('1').length - 1 // Count '1' bits
201 +}
202 +
203 export function makeMatcher(mask: string, emptyMaskReturns=false) {
204 return mask ? matcher(mask.replace(/^(!)?/, '$1(') + ')') // adding () will allow us to use the pipe at root level
205 : () => emptyMaskReturns
@@ -246,3 +251,11 @@ export function try_(cb: () => any, onException?: (e:any) => any) {
251 export function throw_(err: any) {
252 throw err
253 }
254 +
255 +export function findFirst<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string | number)=>O): any {
256 + if (a) for (const k in a) {
257 + const ret = cb((a as any)[k] as I, k)
258 + if (ret !== undefined)
259 + return ret
260 + }
261 +}