add more sankey options
This commit is contained in:
@@ -20,6 +20,17 @@ class InterfaceHostEvidence(BaseModel):
|
||||
destination_on_egress_count: int = Field(..., description="Packets where this endpoint appeared as the destination on egress.")
|
||||
|
||||
|
||||
class ProtocolLayerPathEvidence(BaseModel):
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Ethernet protocol label for this path, if known.")
|
||||
ip_protocol: Optional[str] = Field(None, description="IP protocol label for this path, if known.")
|
||||
packet_count: int = Field(..., description="Packet observations supporting this path.")
|
||||
last_seen: datetime = Field(..., description="Most recent packet timestamp supporting this path.")
|
||||
accept_count: int = Field(0, description="Packets with verdict=accept for this path.")
|
||||
drop_count: int = Field(0, description="Packets with verdict=drop for this path.")
|
||||
reject_count: int = Field(0, description="Packets with verdict=reject for this path.")
|
||||
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
|
||||
|
||||
|
||||
class ProtocolEvidence(BaseModel):
|
||||
protocol: str = Field(..., description="Detected application or fallback transport/network protocol.")
|
||||
packet_count: int = Field(..., description="Packet observations supporting this interface-host-protocol mapping.")
|
||||
@@ -30,23 +41,12 @@ class ProtocolEvidence(BaseModel):
|
||||
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Dominant Ethernet protocol associated with this protocol evidence.")
|
||||
ip_protocol: Optional[str] = Field(None, description="Dominant IP protocol associated with this protocol evidence.")
|
||||
layer_paths: List["ProtocolLayerPathEvidence"] = Field(
|
||||
layer_paths: List[ProtocolLayerPathEvidence] = Field(
|
||||
default_factory=list,
|
||||
description="Optional Ethernet/IP breakdown contributing to this protocol evidence.",
|
||||
)
|
||||
|
||||
|
||||
class ProtocolLayerPathEvidence(BaseModel):
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Ethernet protocol label for this path, if known.")
|
||||
ip_protocol: Optional[str] = Field(None, description="IP protocol label for this path, if known.")
|
||||
packet_count: int = Field(..., description="Packet observations supporting this path.")
|
||||
last_seen: datetime = Field(..., description="Most recent packet timestamp supporting this path.")
|
||||
accept_count: int = Field(0, description="Packets with verdict=accept for this path.")
|
||||
drop_count: int = Field(0, description="Packets with verdict=drop for this path.")
|
||||
reject_count: int = Field(0, description="Packets with verdict=reject for this path.")
|
||||
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
|
||||
|
||||
|
||||
class InterfaceHostProtocolEvidence(InterfaceHostEvidence):
|
||||
protocols: List[ProtocolEvidence] = Field(default_factory=list, description="Protocols observed for this host on the interface.")
|
||||
|
||||
|
||||
@@ -32,19 +32,35 @@ def _analysis_protocol_name(app_protocol: Any, ip_proto_raw: Any, eth_type_raw:
|
||||
if app_protocol not in (None, ""):
|
||||
return str(app_protocol)
|
||||
|
||||
ip_protocol_name = _analysis_ip_protocol_name(ip_proto_raw)
|
||||
if ip_protocol_name is not None:
|
||||
return ip_protocol_name
|
||||
|
||||
ethernet_protocol_name = _analysis_ethernet_protocol_name(eth_type_raw)
|
||||
if ethernet_protocol_name is not None:
|
||||
return ethernet_protocol_name
|
||||
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def _analysis_ip_protocol_name(ip_proto_raw: Any) -> Optional[str]:
|
||||
if ip_proto_raw is not None:
|
||||
try:
|
||||
return str(protocol_from_number(int(ip_proto_raw)))
|
||||
except Exception:
|
||||
return f"IP_PROTO_{ip_proto_raw}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _analysis_ethernet_protocol_name(eth_type_raw: Any) -> Optional[str]:
|
||||
if eth_type_raw is not None:
|
||||
try:
|
||||
return str(ethertype_from_int(int(eth_type_raw)))
|
||||
except Exception:
|
||||
return f"ETH_TYPE_{eth_type_raw}"
|
||||
|
||||
return "UNKNOWN"
|
||||
return None
|
||||
|
||||
|
||||
def _serialize_row_for_broadcast(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -958,6 +974,8 @@ class DatabasePool:
|
||||
)
|
||||
if protocol_name not in (None, ""):
|
||||
protocol_key = str(protocol_name)
|
||||
ip_protocol_name = _analysis_ip_protocol_name(record.get("ip_proto_raw"))
|
||||
ethernet_protocol_name = _analysis_ethernet_protocol_name(record.get("eth_type_raw"))
|
||||
protocol_last_seen_raw = record.get("protocol_last_seen")
|
||||
protocol_last_seen = (
|
||||
protocol_last_seen_raw.isoformat()
|
||||
@@ -975,6 +993,10 @@ class DatabasePool:
|
||||
"drop_count": 0,
|
||||
"reject_count": 0,
|
||||
"unknown_count": 0,
|
||||
"ethernet_protocol": ethernet_protocol_name,
|
||||
"ip_protocol": ip_protocol_name,
|
||||
"layer_paths": [],
|
||||
"_layer_index": {},
|
||||
}
|
||||
protocol_index[protocol_key] = protocol_record
|
||||
host_record["protocols"].append(protocol_record)
|
||||
@@ -989,11 +1011,54 @@ class DatabasePool:
|
||||
or str(protocol_last_seen) > str(protocol_record.get("last_seen"))
|
||||
):
|
||||
protocol_record["last_seen"] = protocol_last_seen
|
||||
if protocol_record.get("ethernet_protocol") is None and ethernet_protocol_name is not None:
|
||||
protocol_record["ethernet_protocol"] = ethernet_protocol_name
|
||||
if protocol_record.get("ip_protocol") is None and ip_protocol_name is not None:
|
||||
protocol_record["ip_protocol"] = ip_protocol_name
|
||||
|
||||
layer_key = f"{ethernet_protocol_name or 'no-eth'}|{ip_protocol_name or 'no-ip'}"
|
||||
layer_index = protocol_record["_layer_index"]
|
||||
layer_record = layer_index.get(layer_key)
|
||||
if layer_record is None:
|
||||
layer_record = {
|
||||
"ethernet_protocol": ethernet_protocol_name,
|
||||
"ip_protocol": ip_protocol_name,
|
||||
"packet_count": 0,
|
||||
"last_seen": protocol_last_seen,
|
||||
"accept_count": 0,
|
||||
"drop_count": 0,
|
||||
"reject_count": 0,
|
||||
"unknown_count": 0,
|
||||
}
|
||||
layer_index[layer_key] = layer_record
|
||||
protocol_record["layer_paths"].append(layer_record)
|
||||
|
||||
layer_record["packet_count"] += int(record.get("protocol_packet_count") or 0)
|
||||
layer_record["accept_count"] += int(record.get("accept_count") or 0)
|
||||
layer_record["drop_count"] += int(record.get("drop_count") or 0)
|
||||
layer_record["reject_count"] += int(record.get("reject_count") or 0)
|
||||
layer_record["unknown_count"] += int(record.get("unknown_count") or 0)
|
||||
if protocol_last_seen and (
|
||||
layer_record.get("last_seen") in (None, "")
|
||||
or str(protocol_last_seen) > str(layer_record.get("last_seen"))
|
||||
):
|
||||
layer_record["last_seen"] = protocol_last_seen
|
||||
|
||||
result: List[Dict[str, Any]] = []
|
||||
for iface, hosts in sorted(grouped.items()):
|
||||
for host in hosts.values():
|
||||
host.pop("_protocol_index", None)
|
||||
for protocol in host["protocols"]:
|
||||
protocol.pop("_layer_index", None)
|
||||
protocol["layer_paths"] = sorted(
|
||||
protocol["layer_paths"],
|
||||
key=lambda item: (
|
||||
-int(item.get("packet_count") or 0),
|
||||
str(item.get("last_seen") or ""),
|
||||
str(item.get("ethernet_protocol") or ""),
|
||||
str(item.get("ip_protocol") or ""),
|
||||
),
|
||||
)
|
||||
host["protocols"] = sorted(
|
||||
host["protocols"],
|
||||
key=lambda item: (
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Col,
|
||||
Empty,
|
||||
InputNumber,
|
||||
@@ -31,11 +32,12 @@ import type {
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
|
||||
type GraphNodeKind = 'interface' | 'host' | 'protocol';
|
||||
type TopologyLayerKind = GraphNodeKind | 'ethernet' | 'ip';
|
||||
|
||||
type TopologyNode = {
|
||||
id: string;
|
||||
label: string;
|
||||
kind: GraphNodeKind;
|
||||
kind: TopologyLayerKind;
|
||||
packetCount: number;
|
||||
interfaceName?: string;
|
||||
ipAddress?: string | null;
|
||||
@@ -81,6 +83,11 @@ type TopologyData = {
|
||||
tableRows: ProtocolTableRow[];
|
||||
};
|
||||
|
||||
type TopologyOptions = {
|
||||
includeEthernetLayer: boolean;
|
||||
includeIpLayer: boolean;
|
||||
};
|
||||
|
||||
type ForceNode = d3.SimulationNodeDatum & TopologyNode;
|
||||
type ForceLink = d3.SimulationLinkDatum<ForceNode> & TopologyLink;
|
||||
|
||||
@@ -133,7 +140,38 @@ function sankeyVisualWeight(packetCount: number) {
|
||||
return Math.max(1, Math.sqrt(Math.max(0, packetCount)));
|
||||
}
|
||||
|
||||
function buildTopologyData(interfaces: InterfaceProtocolAttachment[]): TopologyData {
|
||||
function addOrUpdateLink(links: Map<string, TopologyLink>, source: string, target: string, packetCount: number, label: string) {
|
||||
const linkId = `${source}->${target}`;
|
||||
const existing = links.get(linkId);
|
||||
if (existing) {
|
||||
existing.packetCount += packetCount;
|
||||
existing.value = sankeyVisualWeight(existing.packetCount);
|
||||
existing.label = `${existing.label.split(' (')[0]} (${existing.packetCount})`;
|
||||
return;
|
||||
}
|
||||
links.set(linkId, {
|
||||
source,
|
||||
target,
|
||||
value: sankeyVisualWeight(packetCount),
|
||||
packetCount,
|
||||
label: `${label} (${packetCount})`,
|
||||
});
|
||||
}
|
||||
|
||||
function ensureProtocolNode(nodes: Map<string, TopologyNode>, id: string, label: string, kind: TopologyLayerKind) {
|
||||
if (!nodes.has(id)) {
|
||||
nodes.set(id, {
|
||||
id,
|
||||
label,
|
||||
kind,
|
||||
packetCount: 0,
|
||||
protocol: label,
|
||||
});
|
||||
}
|
||||
return nodes.get(id)!;
|
||||
}
|
||||
|
||||
function buildTopologyData(interfaces: InterfaceProtocolAttachment[], options: TopologyOptions): TopologyData {
|
||||
const nodes = new Map<string, TopologyNode>();
|
||||
const links = new Map<string, TopologyLink>();
|
||||
const heatmapByHost = new Map<string, HeatmapRow>();
|
||||
@@ -179,28 +217,74 @@ function buildTopologyData(interfaces: InterfaceProtocolAttachment[]): TopologyD
|
||||
};
|
||||
|
||||
for (const protocol of host.protocols) {
|
||||
const protocolId = `protocol:${protocol.protocol}`;
|
||||
protocols.add(protocol.protocol);
|
||||
if (!nodes.has(protocolId)) {
|
||||
nodes.set(protocolId, {
|
||||
id: protocolId,
|
||||
label: protocol.protocol,
|
||||
kind: 'protocol',
|
||||
packetCount: 0,
|
||||
protocol: protocol.protocol,
|
||||
});
|
||||
}
|
||||
const existingProtocolNode = nodes.get(protocolId)!;
|
||||
existingProtocolNode.packetCount += protocol.packet_count;
|
||||
const layerPaths =
|
||||
protocol.layer_paths.length > 0
|
||||
? protocol.layer_paths
|
||||
: [
|
||||
{
|
||||
ethernet_protocol: protocol.ethernet_protocol ?? null,
|
||||
ip_protocol: protocol.ip_protocol ?? null,
|
||||
packet_count: protocol.packet_count,
|
||||
last_seen: protocol.last_seen,
|
||||
accept_count: protocol.accept_count,
|
||||
drop_count: protocol.drop_count,
|
||||
reject_count: protocol.reject_count,
|
||||
unknown_count: protocol.unknown_count,
|
||||
},
|
||||
];
|
||||
|
||||
const hostProtocolLinkId = `${hostId}->${protocolId}`;
|
||||
links.set(hostProtocolLinkId, {
|
||||
source: hostId,
|
||||
target: protocolId,
|
||||
value: sankeyVisualWeight(protocol.packet_count),
|
||||
packetCount: protocol.packet_count,
|
||||
label: `${host.ip_address ?? host.mac_address ?? 'host'} -> ${protocol.protocol} (${protocol.packet_count})`,
|
||||
});
|
||||
for (const layerPath of layerPaths) {
|
||||
let currentNodeId = hostId;
|
||||
let currentLabel = host.ip_address ?? host.mac_address ?? 'host';
|
||||
|
||||
if (
|
||||
options.includeEthernetLayer &&
|
||||
layerPath.ethernet_protocol &&
|
||||
layerPath.ethernet_protocol !== protocol.protocol
|
||||
) {
|
||||
const ethernetId = `ethernet:${layerPath.ethernet_protocol}`;
|
||||
const ethernetNode = ensureProtocolNode(nodes, ethernetId, layerPath.ethernet_protocol, 'ethernet');
|
||||
ethernetNode.packetCount += layerPath.packet_count;
|
||||
addOrUpdateLink(
|
||||
links,
|
||||
currentNodeId,
|
||||
ethernetId,
|
||||
layerPath.packet_count,
|
||||
`${currentLabel} -> ${layerPath.ethernet_protocol}`,
|
||||
);
|
||||
currentNodeId = ethernetId;
|
||||
currentLabel = layerPath.ethernet_protocol;
|
||||
}
|
||||
|
||||
if (options.includeIpLayer && layerPath.ip_protocol && layerPath.ip_protocol !== currentLabel && layerPath.ip_protocol !== protocol.protocol) {
|
||||
const ipId = `ip:${layerPath.ip_protocol}`;
|
||||
const ipNode = ensureProtocolNode(nodes, ipId, layerPath.ip_protocol, 'ip');
|
||||
ipNode.packetCount += layerPath.packet_count;
|
||||
addOrUpdateLink(
|
||||
links,
|
||||
currentNodeId,
|
||||
ipId,
|
||||
layerPath.packet_count,
|
||||
`${currentLabel} -> ${layerPath.ip_protocol}`,
|
||||
);
|
||||
currentNodeId = ipId;
|
||||
currentLabel = layerPath.ip_protocol;
|
||||
}
|
||||
|
||||
if (currentLabel !== protocol.protocol || currentNodeId === hostId) {
|
||||
const protocolId = `protocol:${protocol.protocol}`;
|
||||
const protocolNode = ensureProtocolNode(nodes, protocolId, protocol.protocol, 'protocol');
|
||||
protocolNode.packetCount += layerPath.packet_count;
|
||||
addOrUpdateLink(
|
||||
links,
|
||||
currentNodeId,
|
||||
protocolId,
|
||||
layerPath.packet_count,
|
||||
`${currentLabel} -> ${protocol.protocol}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
heatmapRow.values[protocol.protocol] = protocol.packet_count;
|
||||
|
||||
@@ -243,9 +327,11 @@ function SankeyTopology({ data }: { data: TopologyData }) {
|
||||
const nodesPerLayer = {
|
||||
interface: data.nodes.filter((node) => node.kind === 'interface').length,
|
||||
host: data.nodes.filter((node) => node.kind === 'host').length,
|
||||
ethernet: data.nodes.filter((node) => node.kind === 'ethernet').length,
|
||||
ip: data.nodes.filter((node) => node.kind === 'ip').length,
|
||||
protocol: data.nodes.filter((node) => node.kind === 'protocol').length,
|
||||
};
|
||||
const maxNodesInLayer = Math.max(nodesPerLayer.interface, nodesPerLayer.host, nodesPerLayer.protocol, 1);
|
||||
const maxNodesInLayer = Math.max(nodesPerLayer.interface, nodesPerLayer.host, nodesPerLayer.ethernet, nodesPerLayer.ip, nodesPerLayer.protocol, 1);
|
||||
const height = clamp(maxNodesInLayer * 56 + 120, 260, 760);
|
||||
const svg = d3.select(svgRef.current);
|
||||
svg.selectAll('*').remove();
|
||||
@@ -279,7 +365,7 @@ function SankeyTopology({ data }: { data: TopologyData }) {
|
||||
.attr('d', sankeyLinkHorizontal())
|
||||
.attr('stroke', (link) => {
|
||||
const target = link.target as SankeyNodeDatum;
|
||||
if (target.kind === 'protocol' && target.protocol) {
|
||||
if ((target.kind === 'protocol' || target.kind === 'ethernet' || target.kind === 'ip') && target.protocol) {
|
||||
return protocolColor(target.protocol);
|
||||
}
|
||||
return '#9aa7b5';
|
||||
@@ -303,6 +389,8 @@ function SankeyTopology({ data }: { data: TopologyData }) {
|
||||
.attr('fill', (d) => {
|
||||
if (d.kind === 'interface') return '#20405d';
|
||||
if (d.kind === 'host') return '#d7e7f5';
|
||||
if (d.kind === 'ethernet') return '#d7c09c';
|
||||
if (d.kind === 'ip') return '#a8c8df';
|
||||
return d.protocol ? protocolColor(d.protocol) : '#d8d8d8';
|
||||
})
|
||||
.attr('stroke', (d) => (d.kind === 'host' ? '#9bb8d6' : 'none'))
|
||||
@@ -353,6 +441,8 @@ function ForceTopology({ data }: { data: TopologyData }) {
|
||||
const groupedNodes = {
|
||||
interface: nodes.filter((node) => node.kind === 'interface').sort((left, right) => left.label.localeCompare(right.label)),
|
||||
host: nodes.filter((node) => node.kind === 'host').sort((left, right) => (left.ipAddress ?? left.macAddress ?? left.label).localeCompare(right.ipAddress ?? right.macAddress ?? right.label)),
|
||||
ethernet: nodes.filter((node) => node.kind === 'ethernet').sort((left, right) => left.label.localeCompare(right.label)),
|
||||
ip: nodes.filter((node) => node.kind === 'ip').sort((left, right) => left.label.localeCompare(right.label)),
|
||||
protocol: nodes.filter((node) => node.kind === 'protocol').sort((left, right) => left.label.localeCompare(right.label)),
|
||||
};
|
||||
|
||||
@@ -374,8 +464,11 @@ function ForceTopology({ data }: { data: TopologyData }) {
|
||||
|
||||
const interfaceY = distributedY(groupedNodes.interface, 120, height - 120);
|
||||
const hostY = distributedY(groupedNodes.host, 90, height - 90);
|
||||
const ethernetY = distributedY(groupedNodes.ethernet, 120, height - 120);
|
||||
const ipY = distributedY(groupedNodes.ip, 120, height - 120);
|
||||
const protocolY = distributedY(groupedNodes.protocol, 120, height - 120);
|
||||
const targetY = (node: ForceNode) => interfaceY.get(node.id) ?? hostY.get(node.id) ?? protocolY.get(node.id) ?? height / 2;
|
||||
const targetY = (node: ForceNode) =>
|
||||
interfaceY.get(node.id) ?? hostY.get(node.id) ?? ethernetY.get(node.id) ?? ipY.get(node.id) ?? protocolY.get(node.id) ?? height / 2;
|
||||
|
||||
const simulation = d3
|
||||
.forceSimulation<ForceNode>(nodes)
|
||||
@@ -397,13 +490,17 @@ function ForceTopology({ data }: { data: TopologyData }) {
|
||||
.force('collision', d3.forceCollide<ForceNode>().radius((node) => {
|
||||
if (node.kind === 'interface') return 52;
|
||||
if (node.kind === 'host') return 44;
|
||||
if (node.kind === 'ethernet') return 36;
|
||||
if (node.kind === 'ip') return 35;
|
||||
return 34;
|
||||
}))
|
||||
.force(
|
||||
'x',
|
||||
d3.forceX<ForceNode>().x((node) => {
|
||||
if (node.kind === 'interface') return 180;
|
||||
if (node.kind === 'host') return width / 2;
|
||||
if (node.kind === 'host') return 420;
|
||||
if (node.kind === 'ethernet') return 690;
|
||||
if (node.kind === 'ip') return 930;
|
||||
return width - 200;
|
||||
}).strength(0.42),
|
||||
)
|
||||
@@ -420,11 +517,13 @@ function ForceTopology({ data }: { data: TopologyData }) {
|
||||
.join('line')
|
||||
.attr('stroke', (d) => {
|
||||
const target = d.target as ForceNode;
|
||||
return target.kind === 'protocol' && target.protocol ? protocolColor(target.protocol) : '#92a1b2';
|
||||
return (target.kind === 'protocol' || target.kind === 'ethernet' || target.kind === 'ip') && target.protocol
|
||||
? protocolColor(target.protocol)
|
||||
: '#92a1b2';
|
||||
})
|
||||
.attr('stroke-width', (d) => Math.max(1.5, Math.sqrt(d.value)));
|
||||
|
||||
link.append('title').text((d) => `${d.label}\nPackets: ${d.value}`);
|
||||
link.append('title').text((d) => `${d.label}\nPackets: ${d.packetCount}`);
|
||||
|
||||
const node = svg
|
||||
.append('g')
|
||||
@@ -437,11 +536,15 @@ function ForceTopology({ data }: { data: TopologyData }) {
|
||||
.attr('r', (d) => {
|
||||
if (d.kind === 'interface') return 26;
|
||||
if (d.kind === 'host') return 22;
|
||||
if (d.kind === 'ethernet') return 19;
|
||||
if (d.kind === 'ip') return 18;
|
||||
return 18;
|
||||
})
|
||||
.attr('fill', (d) => {
|
||||
if (d.kind === 'interface') return '#20405d';
|
||||
if (d.kind === 'host') return '#d7e7f5';
|
||||
if (d.kind === 'ethernet') return '#d7c09c';
|
||||
if (d.kind === 'ip') return '#a8c8df';
|
||||
return d.protocol ? protocolColor(d.protocol) : '#cfd7df';
|
||||
})
|
||||
.attr('stroke', (d) => (d.kind === 'host' ? '#8aa8c6' : '#ffffff'))
|
||||
@@ -582,6 +685,8 @@ export default function Analysis(): ReactElement {
|
||||
const [sinceMinutes, setSinceMinutes] = useState<number | null>(null);
|
||||
const [limitPerInterface, setLimitPerInterface] = useState(50);
|
||||
const [limitProtocolsPerHost, setLimitProtocolsPerHost] = useState(12);
|
||||
const [includeEthernetLayer, setIncludeEthernetLayer] = useState(false);
|
||||
const [includeIpLayer, setIncludeIpLayer] = useState(false);
|
||||
const [data, setData] = useState<InterfaceHostProtocolAnalysisResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -601,7 +706,14 @@ export default function Analysis(): ReactElement {
|
||||
loadData().catch(() => undefined);
|
||||
}, [loadData]);
|
||||
|
||||
const topologyData = useMemo(() => buildTopologyData(data?.interfaces ?? []), [data]);
|
||||
const topologyData = useMemo(
|
||||
() =>
|
||||
buildTopologyData(data?.interfaces ?? [], {
|
||||
includeEthernetLayer,
|
||||
includeIpLayer,
|
||||
}),
|
||||
[data, includeEthernetLayer, includeIpLayer],
|
||||
);
|
||||
|
||||
const columns = useMemo<ColumnsType<ProtocolTableRow>>(
|
||||
() => [
|
||||
@@ -680,6 +792,12 @@ export default function Analysis(): ReactElement {
|
||||
<Text>Max protocols per host</Text>
|
||||
<InputNumber min={1} max={100} value={limitProtocolsPerHost} onChange={(value) => setLimitProtocolsPerHost(value ?? 12)} />
|
||||
</Space>
|
||||
<Checkbox checked={includeEthernetLayer} onChange={(event) => setIncludeEthernetLayer(event.target.checked)}>
|
||||
Ethernet layer
|
||||
</Checkbox>
|
||||
<Checkbox checked={includeIpLayer} onChange={(event) => setIncludeIpLayer(event.target.checked)}>
|
||||
IP layer
|
||||
</Checkbox>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => loadData()} loading={loading} type="primary">
|
||||
Refresh
|
||||
</Button>
|
||||
|
||||
@@ -26,6 +26,20 @@ export interface ProtocolEvidence {
|
||||
drop_count: number;
|
||||
reject_count: number;
|
||||
unknown_count: number;
|
||||
ethernet_protocol?: string | null;
|
||||
ip_protocol?: string | null;
|
||||
layer_paths: ProtocolLayerPathEvidence[];
|
||||
}
|
||||
|
||||
export interface ProtocolLayerPathEvidence {
|
||||
ethernet_protocol?: string | null;
|
||||
ip_protocol?: string | null;
|
||||
packet_count: number;
|
||||
last_seen: string;
|
||||
accept_count: number;
|
||||
drop_count: number;
|
||||
reject_count: number;
|
||||
unknown_count: number;
|
||||
}
|
||||
|
||||
export interface InterfaceHostProtocolEvidence extends InterfaceHostEvidence {
|
||||
|
||||
Reference in New Issue
Block a user