sd
This commit is contained in:
@@ -15,6 +15,7 @@ import NFProxy from './pages/NFProxy';
|
||||
import ServiceDetailsNFProxy from './pages/NFProxy/ServiceDetails';
|
||||
import TrafficViewer from './pages/NFProxy/TrafficViewer';
|
||||
import TrafficViewerMain from './pages/TrafficViewer';
|
||||
import SetupPage from './pages/Setup';
|
||||
import { useAuthStore } from './js/store';
|
||||
|
||||
function App() {
|
||||
@@ -179,6 +180,7 @@ const PageRouting = ({ getStatus }:{ getStatus:()=>void }) => {
|
||||
<Route path="traffic" element={<TrafficViewerMain />} />
|
||||
<Route path="firewall" element={<Firewall />} />
|
||||
<Route path="porthijack" element={<PortHijack />} />
|
||||
<Route path="setup" element={<SetupPage />} />
|
||||
<Route path="*" element={<HomeRedirector />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -26,7 +26,7 @@ function AddEditService({ opened, onClose, edit }:{ opened:boolean, onClose:()=>
|
||||
validate:{
|
||||
name: (value) => edit? null : value !== "" ? null : "Service name is required",
|
||||
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
|
||||
proto: (value) => ["tcp","http"].includes(value) ? null : "Invalid protocol",
|
||||
proto: (value) => ["tcp","http","udp"].includes(value) ? null : "Invalid protocol",
|
||||
ip_int: (value) => (value.match(regex_ipv6) || value.match(regex_ipv4)) ? null : "Invalid IP address",
|
||||
}
|
||||
})
|
||||
@@ -115,6 +115,7 @@ function AddEditService({ opened, onClose, edit }:{ opened:boolean, onClose:()=>
|
||||
data={[
|
||||
{ label: 'TCP', value: 'tcp' },
|
||||
{ label: 'HTTP', value: 'http' },
|
||||
{ label: 'UDP', value: 'udp' },
|
||||
]}
|
||||
{...form.getInputProps('proto')}
|
||||
/>}
|
||||
|
||||
@@ -72,7 +72,7 @@ export const HELP_NFPROXY_SIM = `➤ fgex nfproxy -h
|
||||
│ * port INTEGER The port of the target to proxy [default: None] [required] │
|
||||
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ --proto [tcp|http] The protocol to proxy [default: tcp] │
|
||||
│ --proto [tcp|http|udp] The protocol to proxy [default: tcp] │
|
||||
│ --from-address TEXT The address of the local server [default: None] │
|
||||
│ --from-port INTEGER The port of the local server [default: 7474] │
|
||||
│ -6 Use IPv6 for the connection │
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PiWallLight } from "react-icons/pi";
|
||||
import { useNavbarStore } from "../../js/store";
|
||||
import { getMainPath } from "../../js/utils";
|
||||
import { BsRegex } from "react-icons/bs";
|
||||
import { MdVisibility } from "react-icons/md";
|
||||
import { MdVisibility, MdSettings } from "react-icons/md";
|
||||
|
||||
function NavBarButton({ navigate, closeNav, name, icon, color, disabled, onClick }:
|
||||
{ navigate?: string, closeNav: () => void, name: string, icon: any, color: MantineColor, disabled?: boolean, onClick?: CallableFunction }) {
|
||||
@@ -42,6 +42,7 @@ export default function NavBar() {
|
||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="blue" icon={<GrDirections size={19} />} />
|
||||
<NavBarButton navigate="nfproxy" closeNav={closeNav} name="Netfilter Proxy" color="lime" icon={<TbPlugConnected size={19} />} />
|
||||
<NavBarButton navigate="traffic" closeNav={closeNav} name="Traffic Viewer" color="cyan" icon={<MdVisibility size={19} />} />
|
||||
<NavBarButton navigate="setup" closeNav={closeNav} name="Setup Import/Export" color="teal" icon={<MdSettings size={19} />} />
|
||||
{/* <Box px="xs" mt="lg">
|
||||
<Title order={5}>Experimental Features 🧪</Title>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ActionIcon, Badge, Box, Code, Divider, Grid, LoadingOverlay, Modal, ScrollArea, Space, Table, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { ActionIcon, Badge, Box, Code, Divider, Grid, Group, LoadingOverlay, Modal, ScrollArea, Select, Space, Table, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { nfproxy, nfproxyServiceQuery } from '../../components/NFProxy/utils';
|
||||
@@ -30,6 +30,9 @@ export default function TrafficViewer() {
|
||||
const [events, eventsHandlers] = useListState<TrafficEvent>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filterText, setFilterText] = useState('');
|
||||
const [filterDirection, setFilterDirection] = useState<string | null>(null);
|
||||
const [filterProto, setFilterProto] = useState<string | null>(null);
|
||||
const [filterVerdict, setFilterVerdict] = useState<string | null>(null);
|
||||
const [selectedEvent, setSelectedEvent] = useState<TrafficEvent | null>(null);
|
||||
const [modalOpened, setModalOpened] = useState(false);
|
||||
|
||||
@@ -87,15 +90,37 @@ export default function TrafficViewer() {
|
||||
};
|
||||
|
||||
const filteredEvents = events.filter((e: TrafficEvent) => {
|
||||
if (!filterText) return true;
|
||||
const search = filterText.toLowerCase();
|
||||
return (
|
||||
e.src_ip?.toLowerCase().includes(search) ||
|
||||
e.dst_ip?.toLowerCase().includes(search) ||
|
||||
e.verdict?.toLowerCase().includes(search) ||
|
||||
e.filter?.toLowerCase().includes(search) ||
|
||||
e.proto?.toLowerCase().includes(search)
|
||||
);
|
||||
// Text filter
|
||||
if (filterText) {
|
||||
const search = filterText.toLowerCase();
|
||||
const matchesText = (
|
||||
e.src_ip?.toLowerCase().includes(search) ||
|
||||
e.dst_ip?.toLowerCase().includes(search) ||
|
||||
e.verdict?.toLowerCase().includes(search) ||
|
||||
e.filter?.toLowerCase().includes(search) ||
|
||||
e.proto?.toLowerCase().includes(search) ||
|
||||
e.src_port?.toString().includes(search) ||
|
||||
e.dst_port?.toString().includes(search)
|
||||
);
|
||||
if (!matchesText) return false;
|
||||
}
|
||||
|
||||
// Direction filter
|
||||
if (filterDirection && e.direction !== filterDirection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Protocol filter
|
||||
if (filterProto && e.proto !== filterProto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verdict filter
|
||||
if (filterVerdict && e.verdict !== filterVerdict) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const formatTimestamp = (ts: number) => {
|
||||
@@ -147,13 +172,55 @@ export default function TrafficViewer() {
|
||||
<Divider my="md" />
|
||||
|
||||
<Box px="md">
|
||||
<TextInput
|
||||
placeholder="Filter by IP, verdict, filter name, or protocol..."
|
||||
value={filterText}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFilterText(e.currentTarget.value)}
|
||||
leftSection={<FaFilter />}
|
||||
mb="md"
|
||||
/>
|
||||
<Grid mb="md">
|
||||
<Grid.Col span={{ base: 12, md: 6 }}>
|
||||
<TextInput
|
||||
placeholder="Search by IP, port, verdict, filter name, or protocol..."
|
||||
value={filterText}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFilterText(e.currentTarget.value)}
|
||||
leftSection={<FaFilter />}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 2 }}>
|
||||
<Select
|
||||
placeholder="Direction"
|
||||
clearable
|
||||
value={filterDirection}
|
||||
onChange={setFilterDirection}
|
||||
data={[
|
||||
{ value: 'in', label: 'Incoming' },
|
||||
{ value: 'out', label: 'Outgoing' }
|
||||
]}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 2 }}>
|
||||
<Select
|
||||
placeholder="Protocol"
|
||||
clearable
|
||||
value={filterProto}
|
||||
onChange={setFilterProto}
|
||||
data={[
|
||||
{ value: 'tcp', label: 'TCP' },
|
||||
{ value: 'udp', label: 'UDP' },
|
||||
{ value: 'http', label: 'HTTP' }
|
||||
]}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 2 }}>
|
||||
<Select
|
||||
placeholder="Verdict"
|
||||
clearable
|
||||
value={filterVerdict}
|
||||
onChange={setFilterVerdict}
|
||||
data={[
|
||||
{ value: 'accept', label: 'Accept' },
|
||||
{ value: 'drop', label: 'Drop' },
|
||||
{ value: 'reject', label: 'Reject' },
|
||||
{ value: 'edited', label: 'Edited' }
|
||||
]}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<ScrollArea style={{ height: 'calc(100vh - 280px)' }}>
|
||||
<Table striped highlightOnHover>
|
||||
|
||||
@@ -1,20 +1,107 @@
|
||||
import { ActionIcon, Badge, Box, Card, Divider, Group, LoadingOverlay, Space, Text, ThemeIcon, Title, Tooltip } from '@mantine/core';
|
||||
import { ActionIcon, Badge, Box, Card, Divider, Group, LoadingOverlay, Select, Space, Text, TextInput, ThemeIcon, Title, Tooltip } from '@mantine/core';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { nfproxyServiceQuery } from '../../components/NFProxy/utils';
|
||||
import { nfregexServiceQuery } from '../../components/NFRegex/utils';
|
||||
import { isMediumScreen } from '../../js/utils';
|
||||
import { MdDoubleArrow, MdVisibility } from 'react-icons/md';
|
||||
import { TbPlugConnected } from 'react-icons/tb';
|
||||
import { FaServer } from 'react-icons/fa';
|
||||
import { FaFilter, FaServer } from 'react-icons/fa';
|
||||
import { BsRegex } from 'react-icons/bs';
|
||||
import { useState } from 'react';
|
||||
|
||||
type UnifiedService = {
|
||||
service_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
port: number;
|
||||
proto: string;
|
||||
ip_int: string;
|
||||
type: 'nfproxy' | 'nfregex';
|
||||
stats: {
|
||||
edited_packets?: number;
|
||||
blocked_packets?: number;
|
||||
n_packets?: number;
|
||||
};
|
||||
};
|
||||
|
||||
export default function TrafficViewer() {
|
||||
const services = nfproxyServiceQuery();
|
||||
const nfproxyServices = nfproxyServiceQuery();
|
||||
const nfregexServices = nfregexServiceQuery();
|
||||
const navigate = useNavigate();
|
||||
const isMedium = isMediumScreen();
|
||||
const [filterText, setFilterText] = useState('');
|
||||
const [filterType, setFilterType] = useState<string | null>(null);
|
||||
const [filterProto, setFilterProto] = useState<string | null>(null);
|
||||
const [filterStatus, setFilterStatus] = useState<string | null>(null);
|
||||
|
||||
if (services.isLoading) return <LoadingOverlay visible={true} />;
|
||||
if (nfproxyServices.isLoading || nfregexServices.isLoading) {
|
||||
return <LoadingOverlay visible={true} />;
|
||||
}
|
||||
|
||||
const activeServices = services.data?.filter(s => s.status === 'active') || [];
|
||||
const stoppedServices = services.data?.filter(s => s.status !== 'active') || [];
|
||||
// Combine services from both modules
|
||||
const allServices: UnifiedService[] = [
|
||||
...(nfproxyServices.data?.map(s => ({
|
||||
service_id: s.service_id,
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
port: s.port,
|
||||
proto: s.proto,
|
||||
ip_int: s.ip_int,
|
||||
type: 'nfproxy' as const,
|
||||
stats: {
|
||||
edited_packets: s.edited_packets,
|
||||
blocked_packets: s.blocked_packets
|
||||
}
|
||||
})) || []),
|
||||
...(nfregexServices.data?.map(s => ({
|
||||
service_id: s.service_id,
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
port: s.port,
|
||||
proto: s.proto,
|
||||
ip_int: s.ip_int,
|
||||
type: 'nfregex' as const,
|
||||
stats: {
|
||||
n_packets: s.n_packets
|
||||
}
|
||||
})) || [])
|
||||
];
|
||||
|
||||
// Apply filters
|
||||
const filteredServices = allServices.filter(service => {
|
||||
// Text filter
|
||||
if (filterText) {
|
||||
const search = filterText.toLowerCase();
|
||||
const matchesText = (
|
||||
service.name.toLowerCase().includes(search) ||
|
||||
service.service_id.toLowerCase().includes(search) ||
|
||||
service.port.toString().includes(search) ||
|
||||
service.ip_int.toLowerCase().includes(search)
|
||||
);
|
||||
if (!matchesText) return false;
|
||||
}
|
||||
|
||||
// Type filter
|
||||
if (filterType && service.type !== filterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Protocol filter
|
||||
if (filterProto && service.proto !== filterProto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if (filterStatus) {
|
||||
if (filterStatus === 'active' && service.status !== 'active') return false;
|
||||
if (filterStatus === 'stopped' && service.status === 'active') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const activeServices = filteredServices.filter(s => s.status === 'active');
|
||||
const stoppedServices = filteredServices.filter(s => s.status !== 'active');
|
||||
|
||||
return <>
|
||||
<Box px="md" mt="lg">
|
||||
@@ -26,20 +113,72 @@ export default function TrafficViewer() {
|
||||
Traffic Viewer
|
||||
</Title>
|
||||
<Text c="dimmed" mt="sm">
|
||||
Monitor live network traffic for all NFProxy services
|
||||
Monitor live network traffic for NFProxy and NFRegex services
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
{services.data?.length === 0 ? (
|
||||
<Box px="md" mb="lg">
|
||||
<Group grow>
|
||||
<TextInput
|
||||
placeholder="Search by name, ID, port, or IP..."
|
||||
value={filterText}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFilterText(e.currentTarget.value)}
|
||||
leftSection={<FaFilter />}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Service Type"
|
||||
clearable
|
||||
value={filterType}
|
||||
onChange={setFilterType}
|
||||
data={[
|
||||
{ value: 'nfproxy', label: 'Netfilter Proxy' },
|
||||
{ value: 'nfregex', label: 'Netfilter Regex' }
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Protocol"
|
||||
clearable
|
||||
value={filterProto}
|
||||
onChange={setFilterProto}
|
||||
data={[
|
||||
{ value: 'tcp', label: 'TCP' },
|
||||
{ value: 'udp', label: 'UDP' },
|
||||
{ value: 'http', label: 'HTTP' }
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Status"
|
||||
clearable
|
||||
value={filterStatus}
|
||||
onChange={setFilterStatus}
|
||||
data={[
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'stopped', label: 'Stopped' }
|
||||
]}
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{allServices.length === 0 ? (
|
||||
<Box px="md">
|
||||
<Title order={3} className='center-flex' style={{ textAlign: "center" }}>
|
||||
No NFProxy services found
|
||||
No services found
|
||||
</Title>
|
||||
<Space h="xs" />
|
||||
<Text className='center-flex' style={{ textAlign: "center" }} c="dimmed">
|
||||
Create a service in the Netfilter Proxy section to start monitoring traffic
|
||||
Create services in Netfilter Proxy or Netfilter Regex to start monitoring traffic
|
||||
</Text>
|
||||
</Box>
|
||||
) : filteredServices.length === 0 ? (
|
||||
<Box px="md">
|
||||
<Title order={3} className='center-flex' style={{ textAlign: "center" }}>
|
||||
No services match your filters
|
||||
</Title>
|
||||
<Space h="xs" />
|
||||
<Text className='center-flex' style={{ textAlign: "center" }} c="dimmed">
|
||||
Try adjusting your filter criteria
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -51,15 +190,32 @@ export default function TrafficViewer() {
|
||||
Running Services
|
||||
</Title>
|
||||
{activeServices.map(service => (
|
||||
<Card key={service.service_id} shadow="sm" padding="lg" radius="md" withBorder mb="md">
|
||||
<Card key={`${service.type}-${service.service_id}`} shadow="sm" padding="lg" radius="md" withBorder mb="md">
|
||||
<Group justify="space-between">
|
||||
<Box>
|
||||
<Group>
|
||||
<ThemeIcon color="lime" variant="light" size="lg">
|
||||
<TbPlugConnected size={20} />
|
||||
<ThemeIcon
|
||||
color={service.type === 'nfproxy' ? 'lime' : 'grape'}
|
||||
variant="light"
|
||||
size="lg"
|
||||
>
|
||||
{service.type === 'nfproxy' ? (
|
||||
<TbPlugConnected size={20} />
|
||||
) : (
|
||||
<BsRegex size={20} />
|
||||
)}
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={700} size="lg">{service.name}</Text>
|
||||
<Group gap="xs">
|
||||
<Text fw={700} size="lg">{service.name}</Text>
|
||||
<Badge
|
||||
size="xs"
|
||||
color={service.type === 'nfproxy' ? 'lime' : 'grape'}
|
||||
variant="dot"
|
||||
>
|
||||
{service.type === 'nfproxy' ? 'Proxy' : 'Regex'}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs" mt={4}>
|
||||
<Badge color="cyan" size="sm">:{service.port}</Badge>
|
||||
<Badge color="violet" size="sm">{service.proto}</Badge>
|
||||
@@ -71,13 +227,21 @@ export default function TrafficViewer() {
|
||||
<Box>
|
||||
<Group>
|
||||
<Box style={{ textAlign: 'right' }}>
|
||||
<Badge color="orange" size="sm" mb={4}>
|
||||
{service.edited_packets} edited
|
||||
</Badge>
|
||||
<br />
|
||||
<Badge color="yellow" size="sm">
|
||||
{service.blocked_packets} blocked
|
||||
</Badge>
|
||||
{service.type === 'nfproxy' ? (
|
||||
<>
|
||||
<Badge color="orange" size="sm" mb={4}>
|
||||
{service.stats.edited_packets || 0} edited
|
||||
</Badge>
|
||||
<br />
|
||||
<Badge color="yellow" size="sm">
|
||||
{service.stats.blocked_packets || 0} blocked
|
||||
</Badge>
|
||||
</>
|
||||
) : (
|
||||
<Badge color="yellow" size="sm">
|
||||
{service.stats.n_packets || 0} blocked
|
||||
</Badge>
|
||||
)}
|
||||
</Box>
|
||||
<Tooltip label="View traffic">
|
||||
<ActionIcon
|
||||
@@ -85,11 +249,12 @@ export default function TrafficViewer() {
|
||||
size="xl"
|
||||
radius="md"
|
||||
variant="filled"
|
||||
onClick={() => navigate(`/nfproxy/${service.service_id}/traffic`)}
|
||||
onClick={() => navigate(`/${service.type}/${service.service_id}/traffic`)}
|
||||
>
|
||||
<MdDoubleArrow size="24px" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
@@ -110,11 +275,16 @@ export default function TrafficViewer() {
|
||||
<Group justify="space-between">
|
||||
<Box>
|
||||
<Group>
|
||||
<ThemeIcon color="gray" variant="light" size="lg">
|
||||
<FaServer size={18} />
|
||||
<ThemeIcon color={service.type === 'nfproxy' ? 'lime' : 'grape'} variant="light" size="lg">
|
||||
{service.type === 'nfproxy' ? <TbPlugConnected size={18} /> : <BsRegex size={18} />}
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={500} size="lg" c="dimmed">{service.name}</Text>
|
||||
<Group gap="xs">
|
||||
<Text fw={500} size="lg" c="dimmed">{service.name}</Text>
|
||||
<Badge color="gray" size="sm">
|
||||
{service.type === 'nfproxy' ? 'Proxy' : 'Regex'}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs" mt={4}>
|
||||
<Badge color="gray" size="sm">:{service.port}</Badge>
|
||||
<Badge color="gray" size="sm">{service.proto}</Badge>
|
||||
|
||||
Reference in New Issue
Block a user