flow id sessions prefix
This commit is contained in:
@@ -157,6 +157,16 @@ export const fetchPackets = async (limit = 100): Promise<FetchPacketsResponse> =
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getPacketsWebSocketUrl = (subscribeRecent = 0): string => {
|
||||
const url = new URL(BASE);
|
||||
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
url.pathname = `${url.pathname.replace(/\/$/, '')}/packets/ws/packets`;
|
||||
if (subscribeRecent > 0) {
|
||||
url.searchParams.set('subscribe_recent', String(subscribeRecent));
|
||||
}
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
export const clearPackets = async (): Promise<any> => {
|
||||
const res = await api.delete('/packets/packets');
|
||||
return res.data;
|
||||
|
||||
@@ -7,25 +7,27 @@ import {
|
||||
Descriptions,
|
||||
Modal,
|
||||
Row,
|
||||
Segmented,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
Typography,
|
||||
message,
|
||||
theme,
|
||||
} from 'antd';
|
||||
import { ReactElement, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { clearPackets, fetchPackets } from '../api/apiClient';
|
||||
import { clearPackets, fetchPackets, getPacketsWebSocketUrl } from '../api/apiClient';
|
||||
import type { PacketRow } from '../types/packets';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
const DEFAULT_LIMIT = 200;
|
||||
const DEFAULT_PAGE_SIZE = 25;
|
||||
const MAX_PACKETS = 2000; // in-memory cap
|
||||
|
||||
const ARP_OPCODE_LABELS: Record<number, string> = {
|
||||
@@ -915,10 +917,13 @@ function formatTimestamp(ts?: string) {
|
||||
* PacketViewer component
|
||||
*/
|
||||
export default function PacketViewer(): ReactElement {
|
||||
const { token } = theme.useToken();
|
||||
const [packets, setPackets] = useState<PacketRow[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [statusLoading, setStatusLoading] = useState<boolean>(false);
|
||||
const [limit, setLimit] = useState<number>(DEFAULT_LIMIT);
|
||||
const [mode, setMode] = useState<'live' | 'history'>('live');
|
||||
const [pageSize, setPageSize] = useState<number>(DEFAULT_PAGE_SIZE);
|
||||
const [paused, setPaused] = useState<boolean>(false);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const [hexModalOpen, setHexModalOpen] = useState(false);
|
||||
@@ -967,18 +972,20 @@ export default function PacketViewer(): ReactElement {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// open websocket
|
||||
const openWs = useCallback(() => {
|
||||
const closeWs = useCallback(() => {
|
||||
if (wsRef.current) {
|
||||
try {
|
||||
wsRef.current.close();
|
||||
} catch {}
|
||||
wsRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loc = window.location;
|
||||
const protocol = loc.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = `ws://mitm.lan/api/packets/ws/packets?subscribe_recent=20`;
|
||||
// open websocket
|
||||
const openWs = useCallback((subscribeRecent: number) => {
|
||||
closeWs();
|
||||
|
||||
const wsUrl = getPacketsWebSocketUrl(subscribeRecent);
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
@@ -1014,7 +1021,7 @@ export default function PacketViewer(): ReactElement {
|
||||
ws.onclose = () => {
|
||||
wsRef.current = null;
|
||||
};
|
||||
}, [paused, pushNew]);
|
||||
}, [closeWs, paused, pushNew]);
|
||||
|
||||
// pause handling: when unpausing, flush queuedDuringPause into list
|
||||
useEffect(() => {
|
||||
@@ -1027,26 +1034,28 @@ export default function PacketViewer(): ReactElement {
|
||||
}
|
||||
}, [paused, pushNew]);
|
||||
|
||||
// start up: fetch history and open ws
|
||||
// start up / mode change: fetch history, optionally attach to live websocket
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchHistory(limit).then(() => {
|
||||
openWs();
|
||||
if (mode === 'live') {
|
||||
openWs(limit);
|
||||
} else {
|
||||
closeWs();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
try {
|
||||
wsRef.current.close();
|
||||
} catch {}
|
||||
wsRef.current = null;
|
||||
}
|
||||
closeWs();
|
||||
};
|
||||
}, [fetchHistory, limit, openWs]);
|
||||
}, [closeWs, fetchHistory, limit, mode, openWs]);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await fetchHistory(limit);
|
||||
if (mode === 'live') {
|
||||
openWs(limit);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -1365,19 +1374,44 @@ export default function PacketViewer(): ReactElement {
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.packet-viewer .packet-mode-toggle.ant-segmented {
|
||||
background: ${token.colorFillTertiary};
|
||||
}
|
||||
|
||||
.packet-viewer .packet-mode-toggle .ant-segmented-item-selected {
|
||||
background: ${token.colorPrimary};
|
||||
color: ${token.colorTextLightSolid};
|
||||
}
|
||||
|
||||
.packet-viewer .packet-mode-toggle .ant-segmented-item-selected:hover {
|
||||
color: ${token.colorTextLightSolid};
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<Row justify="space-between" align="middle" style={{ marginBottom: 12 }}>
|
||||
<Col>
|
||||
<Title level={3}>Packets</Title>
|
||||
<div>
|
||||
<Text type="secondary">Live packet viewer — history + live stream</Text>
|
||||
<Text type="secondary">
|
||||
{mode === 'live' ? 'Live packet viewer via websocket stream' : 'Historical packet viewer from database'}
|
||||
</Text>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Space>
|
||||
<Text>History</Text>
|
||||
<Segmented
|
||||
className="packet-mode-toggle"
|
||||
value={mode}
|
||||
onChange={(value) => setMode(value as 'live' | 'history')}
|
||||
options={[
|
||||
{ label: 'Live packets', value: 'live' },
|
||||
{ label: 'History', value: 'history' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Text>{mode === 'live' ? 'Recent buffer' : 'History size'}</Text>
|
||||
<Select value={limit} onChange={(v) => setLimit(Number(v))} style={{ width: 120 }}>
|
||||
<Option value={50}>50</Option>
|
||||
<Option value={100}>100</Option>
|
||||
@@ -1385,8 +1419,19 @@ export default function PacketViewer(): ReactElement {
|
||||
<Option value={500}>500</Option>
|
||||
</Select>
|
||||
|
||||
<Text>Live</Text>
|
||||
<Switch checked={!paused} onChange={(checked) => handlePauseToggle(!checked ? true : false)} />
|
||||
<Text>Page size</Text>
|
||||
<Select value={pageSize} onChange={(v) => setPageSize(Number(v))} style={{ width: 120 }}>
|
||||
<Option value={25}>25</Option>
|
||||
<Option value={50}>50</Option>
|
||||
<Option value={100}>100</Option>
|
||||
<Option value={200}>200</Option>
|
||||
</Select>
|
||||
|
||||
{mode === 'live' ? (
|
||||
<Button onClick={() => handlePauseToggle(!paused)}>
|
||||
{paused ? 'Resume live' : 'Pause live'}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button icon={<SyncOutlined />} onClick={handleRefresh} loading={statusLoading} />
|
||||
<Button
|
||||
@@ -1415,7 +1460,10 @@ export default function PacketViewer(): ReactElement {
|
||||
rowKey={(rec: PacketTableRow) => rec.key}
|
||||
dataSource={tableRows}
|
||||
columns={columns as any}
|
||||
pagination={{ pageSize: 25 }}
|
||||
pagination={{
|
||||
pageSize,
|
||||
showSizeChanger: false,
|
||||
}}
|
||||
size="small"
|
||||
bordered
|
||||
expandable={{
|
||||
|
||||
Reference in New Issue
Block a user