admin/logs/ips: delete button

Massimo Melina committed Nov 12, 2024 at 01:01 UTC 8d7b0467a63164732e144d3bfffbe10780b1df26
2 files changed +44 -6
admin/src/LogsPage.ts
+21 -5
@@ -6,8 +6,7 @@ import { API_URL, apiCall, useApi, useApiList } from './api'
6 import { DataTable, DataTableProps } from './DataTable'
7 import {
8 CFG, Dict, formatBytes, HTTP_UNAUTHORIZED, newDialog, prefix, shortenAgent, splitAt, tryJson, md,
9 - typedKeys, NBSP, _dbg, mapFilter, safeDecodeURIComponent
10 -, stringAfter
9 + typedKeys, NBSP, _dbg, mapFilter, safeDecodeURIComponent, stringAfter, onlyTruthy, formatTimestamp
10 } from '@hfs/shared'
11 import {
12 NetmaskField, Flex, IconBtn, useBreakpoint, usePauseButton, useToggleButton, WildcardsSupported, Country,
@@ -15,10 +14,10 @@ import {
14 } from './mui';
15 import { GridColDef } from '@mui/x-data-grid'
16 import _ from 'lodash'
18 -import { ClearAll, Download, Settings, SmartToy } from '@mui/icons-material'
17 +import { AutoDelete, ClearAll, Delete, Download, Settings, SmartToy } from '@mui/icons-material'
18 import { ConfigForm } from './ConfigForm'
19 import { BoolField, SelectField } from '@hfs/mui-grid-form'
21 -import { toast, useDialogBarColors } from './dialog'
20 +import { alertDialog, toast, useDialogBarColors } from './dialog'
21 import { useBlockIp } from './useBlockIp'
22 import { ALL as COUNTRIES } from './countries'
23
@@ -151,13 +150,30 @@ export function LogFile({ file, footerSide, hidden, limit, ...rest }: { limit?:
150 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
151 const blockIp = useBlockIp()
152 const isConsole = file === 'console'
153 + const isIps = file === 'ips'
154 return hidden ? null : h(DataTable, {
155 error,
156 loading: connecting,
157 rows,
158 compact: true,
159 actionsProps: { hideUnder: 'md' },
160 - actions: ({ row }) => [ !isConsole && blockIp.iconBtn(row.ip, "From log") ],
160 + actions: isConsole ? undefined : (({ row }) => onlyTruthy([
161 + blockIp.iconBtn(row.ip, "From log"),
162 + isIps && h(Btn, {
163 + icon: Delete,
164 + confirm: true,
165 + title: `Delete ${row.ip}`,
166 + doneMessage: true,
167 + onClick: () => apiCall('delete_ips', { ip: row.ip }).then(() => setList(was => was.filter(x => x.ip !== row.ip)))
168 + }),
169 + isIps && h(Btn, {
170 + icon: AutoDelete,
171 + confirm: true,
172 + title: `Delete all records up to ${formatTimestamp(row.ts)}`,
173 + onClick: () => apiCall('delete_ips', { ts: row.ts }).then(res => toast(`${res.n} deleted`)).then(reload)
174 + }),
175 + ])),
176 + initialState: isIps ? { sorting: { sortModel: [{ field: 'ts', sort: 'desc' }] } } : undefined,
177 ...rest,
178 footerSide: width => h(Box, {}, // 4 icons don't fit the tabs row on mobile
179 pauseButton,
src/api.log.ts
+23 -1
@@ -1,7 +1,8 @@
1 import { ApiHandlers } from './apiMiddleware'
2 import _ from 'lodash'
3 import { consoleLog } from './consoleLog'
4 -import { HTTP_NOT_ACCEPTABLE, HTTP_NOT_FOUND, wait } from './cross'
4 +import { HTTP_BAD_REQUEST, HTTP_NOT_ACCEPTABLE, HTTP_NOT_FOUND, wait } from './cross'
5 +import { apiAssertTypes } from './misc'
6 import events from './events'
7 import { loggers } from './log'
8 import { SendListReadable } from './SendList'
@@ -56,6 +57,27 @@ export default {
57
58 },
59
60 + async delete_ips({ ip, ts }) {
61 + apiAssertTypes({ string_undefined: { ip, ts } })
62 + if (ip) {
63 + if (!ips.has(ip))
64 + throw HTTP_NOT_FOUND
65 + ips.del(ip)
66 + return {}
67 + }
68 + if (ts) {
69 + ts = new Date(ts)
70 + let n = 0
71 + for await (const [k, rec] of ips.iterator())
72 + if (rec.ts <= ts) {
73 + ips.del(k)
74 + ++n
75 + }
76 + return { n }
77 + }
78 + throw HTTP_BAD_REQUEST
79 + },
80 +
81 reset_ips() {
82 return ips.clear()
83 },