feat(security): add queued IP intelligence observation and filtered retrieval for network and security views

This commit is contained in:
2026-05-21 01:56:17 +00:00
parent ca5c57a329
commit 98913c1977
10 changed files with 342 additions and 69 deletions
+82 -48
View File
@@ -582,6 +582,52 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
};
});
const backgroundRefreshesInFlight = new Set<string>();
function runBackgroundRefresh(key: string, errorMessage: string, task: () => Promise<void>): void {
if (backgroundRefreshesInFlight.has(key)) return;
backgroundRefreshesInFlight.add(key);
void task()
.catch((error) => console.error(errorMessage, error))
.finally(() => backgroundRefreshesInFlight.delete(key));
}
function refreshNetworkIpIntelligence(identity: interfaces.data.IIdentity, ipAddresses: string[]): void {
const ips = [...new Set(ipAddresses.filter(Boolean))].slice(0, 100);
if (ips.length === 0) return;
runBackgroundRefresh('networkIpIntelligence', 'IP intelligence refresh failed:', async () => {
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const intelligenceResponse = await intelligenceRequest.fire({
identity,
ipAddresses: ips,
limit: Math.max(100, ips.length),
});
networkStatePart.setState({
...networkStatePart.getState()!,
ipIntelligence: intelligenceResponse.records || [],
});
});
}
function refreshSecurityIpIntelligence(identity: interfaces.data.IIdentity): void {
runBackgroundRefresh('securityIpIntelligence', 'Security IP intelligence refresh failed:', async () => {
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const intelligenceResponse = await intelligenceRequest.fire({
identity,
limit: 500,
});
securityPolicyStatePart.setState({
...securityPolicyStatePart.getState()!,
ipIntelligence: intelligenceResponse.records || [],
});
});
}
// Fetch Network Stats Action
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg): Promise<INetworkState> => {
const context = getActionContext();
@@ -594,18 +640,9 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
interfaces.requests.IReq_GetNetworkStats
>('/typedrequest', 'getNetworkStats');
const ipIntelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const [networkStatsResponse, ipIntelligenceResponse] = await Promise.all([
networkStatsRequest.fire({
identity: context.identity,
}),
ipIntelligenceRequest.fire({
identity: context.identity,
}),
]);
const networkStatsResponse = await networkStatsRequest.fire({
identity: context.identity,
});
// Use the connections data for the connection list
// and network stats for throughput and IP analytics
@@ -637,6 +674,12 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
};
});
refreshNetworkIpIntelligence(context.identity, [
...Object.keys(connectionsByIP),
...(networkStatsResponse.topIPs || []).map((item) => item.ip),
...(networkStatsResponse.topIPsByBandwidth || []).map((item) => item.ip),
]);
return {
connections,
connectionsByIP,
@@ -647,7 +690,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
topIPs: networkStatsResponse.topIPs || [],
topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
throughputByIP: networkStatsResponse.throughputByIP || [],
ipIntelligence: ipIntelligenceResponse.records || [],
ipIntelligence: currentState.ipIntelligence,
domainActivity: networkStatsResponse.domainActivity || [],
throughputHistory: networkStatsResponse.throughputHistory || [],
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
@@ -683,9 +726,6 @@ export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
const rulesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListSecurityBlockRules
>('/typedrequest', 'listSecurityBlockRules');
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const compiledPolicyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCompiledSecurityPolicy
>('/typedrequest', 'getCompiledSecurityPolicy');
@@ -693,16 +733,17 @@ export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
interfaces.requests.IReq_ListSecurityPolicyAudit
>('/typedrequest', 'listSecurityPolicyAudit');
const [rulesResponse, intelligenceResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
const [rulesResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
rulesRequest.fire({ identity: context.identity }),
intelligenceRequest.fire({ identity: context.identity }),
compiledPolicyRequest.fire({ identity: context.identity }),
auditRequest.fire({ identity: context.identity, limit: 100 }),
]);
refreshSecurityIpIntelligence(context.identity);
return {
rules: rulesResponse.rules || [],
ipIntelligence: intelligenceResponse.records || [],
ipIntelligence: currentState.ipIntelligence,
compiledPolicy: compiledPolicyResponse.policy,
auditEvents: auditResponse.events || [],
isLoading: false,
@@ -835,7 +876,15 @@ export const refreshIpIntelligenceAction = securityPolicyStatePart.createAction<
if (!response.success) {
return { ...currentState, error: response.message || 'Failed to refresh IP intelligence' };
}
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
const refreshedState = await actionContext!.dispatch(fetchSecurityPolicyAction, null);
if (!response.record) return refreshedState;
return {
...refreshedState,
ipIntelligence: [
response.record,
...refreshedState.ipIntelligence.filter((record) => record.ipAddress !== response.record!.ipAddress),
],
};
} catch (error: unknown) {
return {
...currentState,
@@ -3112,53 +3161,38 @@ async function dispatchCombinedRefreshActionInner() {
error: null,
});
try {
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const intelligenceResponse = await intelligenceRequest.fire({ identity: context.identity });
networkStatePart.setState({
...networkStatePart.getState()!,
ipIntelligence: intelligenceResponse.records || [],
});
} catch (error) {
console.error('IP intelligence refresh failed:', error);
}
refreshNetworkIpIntelligence(context.identity, [
...network.connectionDetails.map((conn) => conn.remoteAddress),
...network.topEndpoints.map((endpoint) => endpoint.endpoint),
...(network.topEndpointsByBandwidth || []).map((endpoint) => endpoint.endpoint),
]);
}
if (currentView === 'security') {
try {
runBackgroundRefresh('securityPolicy', 'Security policy refresh failed:', async () => {
await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
} catch (error) {
console.error('Security policy refresh failed:', error);
}
});
}
// Refresh certificate data if on Domains > Certificates subview
if (currentView === 'domains' && currentSubview === 'certificates') {
try {
runBackgroundRefresh('certificates', 'Certificate refresh failed:', async () => {
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
} catch (error) {
console.error('Certificate refresh failed:', error);
}
});
}
// Refresh remote ingress data if on the Network → Remote Ingress subview
if (currentView === 'network' && currentSubview === 'remoteingress') {
try {
runBackgroundRefresh('remoteIngress', 'Remote ingress refresh failed:', async () => {
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
} catch (error) {
console.error('Remote ingress refresh failed:', error);
}
});
}
// Refresh VPN data if on the Network → VPN subview
if (currentView === 'network' && currentSubview === 'vpn') {
try {
runBackgroundRefresh('vpn', 'VPN refresh failed:', async () => {
await vpnStatePart.dispatchAction(fetchVpnAction, null);
} catch (error) {
console.error('VPN refresh failed:', error);
}
});
}
} catch (error) {
console.error('Combined refresh failed:', error);