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
+}