admin/logs: "block IP" button
Massimo Melina committed
May 3, 2024 at 11:24 UTC
b1642858870f6fc26ebf936d3b90d299a5d5a998
6 files changed
+42
-17
admin/src/LogsPage.ts
+6
-1
@@ -17,6 +17,7 @@ import { ClearAll, Download, Settings, SmartToy } from '@mui/icons-material'
17
import { ConfigForm } from './ConfigForm'
18
import { BoolField, SelectField } from '@hfs/mui-grid-form'
19
import { toast, useDialogBarColors } from './dialog'
20
+import { useBlockIp } from './useBlockIp'
21
22
export default function LogsPage() {
23
const [tab, setTab] = useState(0)
@@ -130,11 +131,15 @@ function LogFile({ file, addToFooter, hidden }: { hidden?: boolean, file: string
131
renderCell: ({ value }) => h(Fragment, {}, value.toLocaleDateString(), h('br'), value.toLocaleTimeString())
132
}
133
const rows = useMemo(() => showApi || list?.[0]?.uri === undefined ? list : list.filter(x => !x.uri.startsWith(API_URL)), [list, showApi]) //TODO TypeError: l.uri is undefined
134
+ const blockIp = useBlockIp()
135
+ const isConsole = file === 'console'
136
return hidden ? null : h(DataTable, {
137
error,
138
loading: connecting,
139
rows,
140
compact: true,
141
+ actionsProps: { hideUnder: 'md' },
142
+ actions: ({ row }) => [ !isConsole && blockIp.iconBtn(row.ip, "From log") ],
143
addToFooter: h(Box, {}, // 4 icons don't fit the tabs row on mobile
144
pauseButton,
145
showApiButton,
@@ -149,7 +154,7 @@ function LogFile({ file, addToFooter, hidden }: { hidden?: boolean, file: string
154
}, "Load whole log"),
155
addToFooter,
156
),
152
- columns: file === 'console' ? [
157
+ columns: isConsole ? [
158
tsColumn,
159
{
160
field: 'k',
admin/src/MonitorPage.ts
+5
-13
@@ -3,16 +3,17 @@
3
import _ from "lodash"
4
import { createElement as h, useMemo, Fragment, useState } from "react"
5
import { apiCall, useApiEvents, useApiEx, useApiList } from "./api"
6
-import { LinkOff, Lock, Block, FolderZip, Upload, Download, ChevronRight, ChevronLeft } from '@mui/icons-material'
6
+import { LinkOff, Lock, FolderZip, Upload, Download, ChevronRight, ChevronLeft } from '@mui/icons-material'
7
import { Box, Chip, ChipProps } from '@mui/material'
8
import { DataTable } from './DataTable'
9
-import { formatBytes, ipForUrl, manipulateConfig, CFG, formatSpeed, with_, createDurationFormatter, formatTimestamp,
9
+import { formatBytes, ipForUrl, CFG, formatSpeed, with_, createDurationFormatter, formatTimestamp,
10
formatPerc, md } from "./misc"
11
import { IconBtn, IconProgress, iconTooltip, usePauseButton, useBreakpoint, Country, hTooltip } from './mui'
12
import { Field, SelectField } from '@hfs/mui-grid-form'
13
import { StandardCSSProperties } from '@mui/system/styleFunctionSx/StandardCssProperties'
14
import { agentIcons } from './LogsPage'
15
import { state, useSnapState } from './state'
16
+import { useBlockIp } from './useBlockIp'
17
18
export default function MonitorPage() {
19
return h(Fragment, {},
@@ -96,6 +97,7 @@ function Connections() {
97
const rows = useMemo(() =>
98
list?.filter((x: any) => !monitorOnlyFiles || x.op).map((x: any, id: number) => ({ id, ...x })),
99
[!pause && list, monitorOnlyFiles]) //eslint-disable-line
100
+ const blockIp = useBlockIp()
101
return h(Fragment, {},
102
h(Box, { display: 'flex', alignItems: 'center' },
103
h(SelectField as Field<boolean>, {
@@ -203,22 +205,12 @@ function Connections() {
205
doneMessage: true,
206
onClick: () => apiCall('disconnect', _.pick(row, ['ip', 'port'])).then(x => x.result > 0)
207
}),
206
- h(IconBtn, {
207
- icon: Block,
208
- title: "Block IP",
209
- confirm: "Block address " + row.ip,
210
- disabled: row.ip === props?.you,
211
- onClick: () => blockIp(row.ip),
212
- }),
208
+ blockIp.iconBtn(row.ip, "From monitoring", { disabled: row.ip === props?.you }),
209
]
210
})
211
)
212
}
213
218
-function blockIp(ip: string) {
219
- return manipulateConfig('block', data => [...data, { ip }])
220
-}
221
-
214
function formatSpeedK(value: number | undefined) {
215
return value === undefined ? '' : formatSpeed(value * 1000, { digits: 1 })
216
}
admin/src/mui.ts
+1
-1
@@ -117,7 +117,7 @@ function useRefPass<T=unknown>(forwarded: ForwardedRef<any>) {
117
})
118
}
119
120
-interface IconBtnProps extends Omit<BtnProps, 'icon' | 'children'> { icon: SvgIconComponent }
120
+export interface IconBtnProps extends Omit<BtnProps, 'icon' | 'children'> { icon: SvgIconComponent }
121
export const IconBtn = forwardRef((props: IconBtnProps, ref: ForwardedRef<HTMLButtonElement>) =>
122
h(Btn, { ref, ...props }))
123
admin/src/useBlockIp.ts
new
+26
@@ -0,0 +1,26 @@
1
+import { apiCall, useApi } from '@hfs/shared/api'
2
+import { createElement as h, useCallback } from 'react'
3
+import { BlockingRule } from '../../src/block'
4
+import { toast } from './dialog'
5
+import _ from 'lodash'
6
+import { IconBtn, IconBtnProps } from './mui'
7
+import { Block } from '@mui/icons-material'
8
+
9
+export function useBlockIp() {
10
+ const { data, reload } = useApi('get_config', { only: ['block'] })
11
+ const block = useCallback((ip: string, more: Partial<BlockingRule>={}, showResult=true) =>
12
+ apiCall('set_config', { values: { block: [...data.block, { ip, ...more }] } })
13
+ .then(reload).then(() => showResult && toast("Blocked", 'success')),
14
+ [data, reload])
15
+ const isBlocked = useCallback((ip: string) => _.find(data?.block, { ip }), [data])
16
+ return {
17
+ iconBtn: (ip: string, comment: string, options: Partial<IconBtnProps>={}) => h(IconBtn, {
18
+ icon: Block,
19
+ title: "Block IP",
20
+ confirm: "Block address " + ip,
21
+ ...isBlocked(ip) && { disabled: true, title: "Blocked" },
22
+ ...options,
23
+ onClick: () => block(ip, { comment }),
24
+ }),
25
+ }
26
+}
src/block.ts
+1
-1
@@ -5,7 +5,7 @@ import { disconnect, getConnections, normalizeIp } from './connections'
5
import { makeNetMatcher, MINUTE, onlyTruthy } from './misc'
6
import { Socket } from 'net'
7
8
-interface BlockingRule { ip: string, comment?: string, expire?: Date, disabled?: boolean }
8
+export interface BlockingRule { ip: string, comment?: string, expire?: Date, disabled?: boolean }
9
10
export const block = defineConfig('block', [] as BlockingRule[], rules => {
11
const now = new Date()
src/events.ts
+3
-1
@@ -3,4 +3,6 @@
3
import EventEmitter from 'events'
4
5
// app-wide events
6
-export default new EventEmitter().setMaxListeners(100)
\ No newline at end of file
6
+const ee = new EventEmitter().setMaxListeners(100)
7
+
8
+export default ee
\ No newline at end of file