Hi, I am trying to render a network of my data using react-viz in the dashboard of my Splunk App . For the past few days, I have been trying various things to get the code to work, but all I see is a blank screen. I have pasted my code below. Please let me know if you can identify where I might be going wrong. network_dashboard.js: require([
'jquery',
'splunkjs/mvc',
'splunkjs/mvc/simplexml/ready!'
], function($, mvc) {
function loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src=url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function waitForReact() {
return new Promise((resolve) => {
const checkReact = () => {
if (window.React && window.ReactDOM && window.vis) {
resolve();
} else {
setTimeout(checkReact, 100);
}
};
checkReact();
});
}
Promise.all([
loadScript('https://unpkg.com/react@17/umd/react.production.min.js'),
loadScript('https://unpkg.com/react-dom@17/umd/react-dom.production.min.js'),
loadScript('https://unpkg.com/vis-network/dist/vis-network.min.js')
])
.then(waitForReact)
.then(() => {
console.log('React, ReactDOM, and vis-network are loaded and available');
initApp();
})
.catch(error => {
console.error('Error loading scripts:', error);
});
function initApp() {
const NetworkPage = () => {
const [nodes, setNodes] = React.useState([]);
const [edges, setEdges] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [clickedEdge, setClickedEdge] = React.useState(null);
const [clickedNode, setClickedNode] = React.useState(null);
const [showTransparent, setShowTransparent] = React.useState(false);
React.useEffect(() => {
// Static data for debugging
const staticNodes = [
{'id': 1, 'label': 'wininit.exe', 'type': 'process', 'rank': 0},
{'id': 2, 'label': 'services.exe', 'type': 'process', 'rank': 1},
{'id': 3, 'label': 'sysmon.exe', 'type': 'process', 'rank': 2},
{'id': 4, 'label': 'comb-file', 'type': 'file', 'rank': 1, 'nodes': [
'c:\\windows\\system32\\mmc.exe',
'c:\\mozillafirefox\\firefox.exe',
'c:\\windows\\system32\\cmd.exe',
'c:\\windows\\system32\\dllhost.exe',
'c:\\windows\\system32\\conhost.exe',
'c:\\wireshark\\tshark.exe',
'c:\\confer\\repwmiutils.exe',
'c:\\windows\\system32\\searchprotocolhost.exe',
'c:\\windows\\system32\\searchfilterhost.exe',
'c:\\windows\\system32\\consent.exe',
'c:\\python27\\python.exe',
'c:\\windows\\system32\\audiodg.exe',
'c:\\confer\\repux.exe',
'c:\\windows\\system32\\taskhost.exe'
]},
{'id': 5, 'label': 'c:\\wireshark\\dumpcap.exe', 'type': 'file', 'rank': 1},
{'id': 6, 'label': 'c:\\windows\\system32\\audiodg.exe', 'type': 'file', 'rank': 1}
];
const staticEdges = [
{'source': 1, 'target': 2, 'label': 'procstart', 'alname': null, 'time': '2022-07-19 16:00:17.074477', 'transparent': false},
{'source': 2, 'target': 3, 'label': 'procstart', 'alname': null, 'time': '2022-07-19 16:00:17.531504', 'transparent': false},
{'source': 4, 'target': 3, 'label': 'moduleload', 'alname': null, 'time': '2022-07-19 16:01:03.194938', 'transparent': false},
{'source': 5, 'target': 3, 'label': 'moduleload', 'alname': 'Execution - SysInternals Use', 'time': '2022-07-19 16:01:48.497418', 'transparent': false},
{'source': 6, 'target': 3, 'label': 'moduleload', 'alname': 'Execution - SysInternals Use', 'time': '2022-07-19 16:05:04.581065', 'transparent': false}
];
const sortedEdges = staticEdges.sort((a, b) => new Date(a.time) - new Date(b.time));
const nodesByRank = staticNodes.reduce((acc, node) => {
const rank = node.rank || 0;
if (!acc[rank]) acc[rank] = [];
acc[rank].push(node);
return acc;
}, {});
const nodePositions = {};
const rankSpacingX = 200;
const ySpacing = 100;
Object.keys(nodesByRank).forEach(rank => {
const nodesInRank = nodesByRank[rank];
nodesInRank.sort((a, b) => {
const aEdges = staticEdges.filter(edge => edge.source === a.id || edge.target === a.id);
const bEdges = staticEdges.filter(edge => edge.source === b.id || edge.target === b.id);
return aEdges.length - bEdges.length;
});
const totalNodesInRank = nodesInRank.length;
nodesInRank.forEach((node, index) => {
nodePositions[node.id] = {
x: rank * rankSpacingX,
y: index * ySpacing - (totalNodesInRank * ySpacing) / 2,
};
});
});
const positionedNodes = staticNodes.map(node => ({
...node,
x: nodePositions[node.id].x,
y: nodePositions[node.id].y,
}));
setNodes(positionedNodes);
setEdges(sortedEdges);
setLoading(false);
}, []);
const handleNodeClick = (event) => {
const { nodes: clickedNodes } = event;
if (clickedNodes.length > 0) {
const nodeId = clickedNodes[0];
const clickedNode = nodes.find(node => node.id === nodeId);
setClickedNode(clickedNode || null);
}
};
const handleEdgeClick = (event) => {
const { edges: clickedEdges } = event;
if (clickedEdges.length > 0) {
const edgeId = clickedEdges[0];
const clickedEdge = edges.find(edge => `${edge.source}-${edge.target}` === edgeId);
setClickedEdge(clickedEdge || null);
}
};
const handleClosePopup = () => {
setClickedEdge(null);
setClickedNode(null);
};
const toggleTransparentEdges = () => {
setShowTransparent(prevState => !prevState);
};
if (loading) {
return React.createElement('div', null, 'Loading...');
}
const formatFilePath = (filePath) => {
const parts = filePath.split('\\');
if (filePath.length > 12 && parts[0] !== 'comb-file') {
return `${parts[0]}\\...`;
}
return filePath;
};
const filteredNodes = showTransparent ? nodes : nodes.filter(node =>
edges.some(edge => (edge.source === node.id || edge.target === node.id) && !edge.transparent)
);
const filteredEdges = showTransparent ? edges : edges.filter(edge => !edge.transparent);
const options = {
layout: { hierarchical: false },
edges: {
color: { color: '#000000', highlight: '#ff0000', hover: '#ff0000' },
arrows: { to: { enabled: true, scaleFactor: 1 } },
smooth: { type: 'cubicBezier', roundness: 0.2 },
font: { align: 'top', size: 12 },
},
nodes: {
shape: 'dot',
size: 20,
font: { size: 14, face: 'Arial' },
},
interaction: {
dragNodes: true,
hover: true,
selectConnectedEdges: false,
},
physics: {
enabled: false,
stabilization: { enabled: true, iterations: 300, updateInterval: 50 },
},
};
const graphData = {
nodes: filteredNodes.map(node => {
let label = node.label;
if (node.type === 'file' && node.label !== 'comb-file') {
label = formatFilePath(node.label);
}
return {
id: node.id,
label: label,
title: node.type === 'file' ? node.label : '',
x: node.x,
y: node.y,
shape: node.type === 'process' ? 'circle' :
node.type === 'socket' ? 'diamond' :
'box',
size: node.type === 'socket' ? 40 : 20,
font: { size: node.type === 'socket' ? 10 : 14, vadjust: node.type === 'socket' ? -50 : 0 },
color: {
background: node.transparent ? "rgba(151, 194, 252, 0.5)" : "rgb(151, 194, 252)",
border: "#2B7CE9",
highlight: { background: node.transparent ? "rgba(210, 229, 255, 0.1)" : "#D2E5FF", border: "#2B7CE9" },
},
className: node.transparent && !showTransparent ? 'transparent' : '',
};
}),
edges: filteredEdges.map(edge => ({
from: edge.source,
to: edge.target,
label: edge.label,
color: edge.alname && edge.transparent ? '#ff9999' :
edge.alname ? '#ff0000' :
edge.transparent ? '#d3d3d3' :
'#000000',
id: `${edge.source}-${edge.target}`,
font: { size: 12, align: 'horizontal', background: 'white', strokeWidth: 0 },
className: edge.transparent && !showTransparent ? 'transparent' : '',
})),
};
// Render the network visualization
return React.createElement(
'div',
{ className: 'network-container' },
React.createElement(
'button',
{ className: 'toggle-button', onClick: toggleTransparentEdges },
showTransparent ? "Hide Transparent Edges" : "Show Transparent Edges"
),
React.createElement(
'div',
{ id: 'network' },
React.createElement(vis.Network, {
graph: graphData,
options: options,
events: {
select: handleNodeClick,
doubleClick: handleEdgeClick
}
})
),
clickedNode && React.createElement('div', { className: 'popup' },
React.createElement('button', { onClick: handleClosePopup }, 'Close'),
React.createElement('h2', null, `Node: ${clickedNode.label}`),
React.createElement('p', null, `Type: ${clickedNode.type}`)
),
clickedEdge && React.createElement('div', { className: 'popup' },
React.createElement('button', { onClick: handleClosePopup }, 'Close'),
React.createElement('h2', null, `Edge: ${clickedEdge.label}`),
React.createElement('p', null, `AL Name: ${clickedEdge.alname || 'N/A'}`)
)
);
};
const rootElement = document.getElementById('root');
if (rootElement) {
ReactDOM.render(React.createElement(NetworkPage), rootElement);
} else {
console.error('Root element not found');
}
}
}); network_dashboard.css: /* src/components/NetworkPage.css */
.network-container {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
#network-visualization {
height: 100%;
width: 100%;
}
/* Toggle button styling */
.toggle-button {
/* position: absolute;*/
top: 10px;
left: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 20px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.toggle-button:hover {
background-color: #0056b3;
}
/* Popup styling */
.popup {
background-color: white;
border: 1px solid #ccc;
padding: 10px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
font-size: 14px;
width: 100%;
height: 100%;
position: relative;
}
/* Custom Scrollbar Styles */
.scrollable-popup {
max-height: 150px;
overflow-y: auto;
scrollbar-width: thin; /* Firefox */
scrollbar-color: transparent; /* Firefox */
}
.scrollable-popup::-webkit-scrollbar {
width: 8px; /* WebKit */
}
.scrollable-popup::-webkit-scrollbar-track {
background: transparent; /* WebKit */
}
.scrollable-popup::-webkit-scrollbar-thumb {
background: grey; /* WebKit */
border-radius: 8px;
}
.scrollable-popup::-webkit-scrollbar-thumb:hover {
background: darkgrey; /* WebKit */
}
/* Popup edge and node styling */
.popup-edge {
border: 2px solid #ff0000;
color: #333;
}
.popup-node {
border: 2px solid #007bff;
color: #007bff;
}
.close-button {
position: absolute;
top: 5px;
right: 5px;
background: transparent;
border: none;
font-size: 16px;
cursor: pointer;
}
.close-button:hover {
color: red;
} network_dashboard.xml <dashboard script="network_dashboard.js" stylesheet="network_dashboard.css">
<label>Network Visualization</label>
<row>
<panel>
<html>
<div id="root" style="height: 800px;"></div>
</html>
</panel>
</row>
</dashboard>
... View more