240 lines
6.1 KiB
JavaScript
240 lines
6.1 KiB
JavaScript
let agentData = [];
|
|
let isCyInitialized = false;
|
|
let cy;
|
|
|
|
|
|
function initializeCytoscape() {
|
|
if (isCyInitialized) return;
|
|
|
|
// cytoscape.use(cxtmenu);
|
|
cy = cytoscape({
|
|
container: document.getElementById('cyto-graph'),
|
|
|
|
/* --- single, central stylesheet -------------------------------- */
|
|
style: [
|
|
/* base node look (shared) */
|
|
/* background-opacity: 0 will remove the node color and background of the svg */
|
|
{
|
|
selector: 'node',
|
|
style: {
|
|
'width' : 50,
|
|
'height' : 50,
|
|
'label' : 'data(name)',
|
|
'text-outline-width' : 2,
|
|
'text-outline-color' : '#333',
|
|
'color' : '#fff',
|
|
'background-opacity' : 1,
|
|
'background-color': '#f8f9fa'
|
|
}
|
|
},
|
|
|
|
/* agent nodes — online / offline variants */
|
|
{
|
|
selector: 'node.online[type = "Agent"]',
|
|
style: {
|
|
'background-image' : 'url(static/img/computer-online.svg)',
|
|
'background-fit' : 'contain'
|
|
}
|
|
},
|
|
{
|
|
selector: 'node.offline[type = "Agent"]',
|
|
style: {
|
|
'background-image' : 'url(static/img/computer-offline.svg)',
|
|
'background-fit' : 'contain'
|
|
}
|
|
},
|
|
|
|
/* target / server node */
|
|
{
|
|
selector: 'node.server',
|
|
style: {
|
|
'background-image' : 'url(static/img/server-online.svg)',
|
|
'background-fit' : 'contain'
|
|
}
|
|
},
|
|
|
|
/* edge style */
|
|
{
|
|
selector: 'edge',
|
|
style: {
|
|
'width' : 3,
|
|
'line-color' : '#ccc',
|
|
'target-arrow-color': '#ccc',
|
|
'target-arrow-shape': 'triangle',
|
|
'curve-style': 'round-segments'
|
|
}
|
|
},
|
|
{
|
|
selector: 'node.darkTheme',
|
|
style : { 'background-color': '#212529' }
|
|
}
|
|
],
|
|
|
|
layout: { name: 'grid', rows: 2 }
|
|
});
|
|
|
|
isCyInitialized = true;
|
|
}
|
|
|
|
// Load the graph after the page has fully loaded
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initializeCytoscape();
|
|
loadGraphData();
|
|
|
|
document.body.addEventListener('htmx:afterSwap', function (event) {
|
|
if (event.target.id === 'agentList') {
|
|
// initializeCytoscape();
|
|
loadGraphData();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Load the graph after HTMX swap
|
|
|
|
async function updateGraph(agentData) {
|
|
if (!cy) {
|
|
console.error('Cytoscape not initialised');
|
|
return;
|
|
}
|
|
|
|
cy.elements().remove(); // clear existing
|
|
|
|
/* --- add agent nodes ------------------------------------------- */
|
|
agentData.forEach(agent => {
|
|
const id = agent.agentId;
|
|
const name = agent.agentName;
|
|
const online = agent.status === 'Connected';
|
|
|
|
if (!id || !name) return;
|
|
|
|
cy.add({
|
|
group : 'nodes',
|
|
data : {
|
|
id : id,
|
|
name : name,
|
|
type : 'Agent',
|
|
ip : agent.IPv4Address,
|
|
status: agent.status
|
|
},
|
|
classes: online ? 'online' : 'offline' // ← only classes
|
|
});
|
|
});
|
|
|
|
/* --- ensure target/server node exists --------------------------- */
|
|
const targetId = 'g2';
|
|
if (cy.getElementById(targetId).length === 0) {
|
|
cy.add({
|
|
group : 'nodes',
|
|
data : { id: targetId, name: 'g2' },
|
|
classes: 'server'
|
|
});
|
|
}
|
|
|
|
/* --- connect every agent to the target ------------------------- */
|
|
agentData.forEach(agent => {
|
|
if (agent.agentId) {
|
|
cy.add({
|
|
group: 'edges',
|
|
data : { source: agent.agentId, target: targetId }
|
|
});
|
|
}
|
|
});
|
|
|
|
/* --- relayout --------------------------------------------------- */
|
|
cy.layout({ name: 'grid', rows: 2 }).run();
|
|
|
|
/* --- CxtMenu --- */
|
|
if (window.nodeContextMenu) {
|
|
window.nodeContextMenu.destroy();
|
|
}
|
|
window.nodeContextMenu = cy.cxtmenu({
|
|
selector: 'node',
|
|
commands: [
|
|
{
|
|
content: 'Details',
|
|
select: function (ele) {
|
|
const data = ele.data();
|
|
alert(`Node Details:\n\nName: ${data.name}\nID: ${data.id}\nType: ${data.type}\nIP: ${data.ip}`);
|
|
}
|
|
},
|
|
{
|
|
content: 'Ping',
|
|
select: function(ele) {
|
|
const ip = ele.data().ip;
|
|
console.log(`Ping to ${ip} triggered!`);
|
|
}
|
|
}
|
|
],
|
|
menuRadius: 100,
|
|
fillColor: `rgba(0, 0, 0, 0.75)`,
|
|
activeFillColor: `rgba(255, 255, 255, 0.3)`,
|
|
activePadding: 10,
|
|
indicatorSize: 18,
|
|
separatorWidth: 3,
|
|
spotlightPadding: 4,
|
|
minSpotlightRadius: 20,
|
|
maxSpotlightRadius: 40,
|
|
openMenuEvents: 'cxttap',
|
|
});
|
|
|
|
window.edgeContextMenu = cy.cxtmenu({
|
|
selector: 'edge',
|
|
commands: [
|
|
{
|
|
content: 'View Info',
|
|
select: function (ele) {
|
|
const source = ele.source().data().name;
|
|
const target = ele.target().data().name;
|
|
alert(`Edge from "${source}" to "${target}"`);
|
|
}
|
|
},
|
|
{
|
|
content: 'Delete Edge',
|
|
select: function (ele) {
|
|
ele.remove();
|
|
}
|
|
}
|
|
],
|
|
menuRadius: 100,
|
|
});
|
|
|
|
}
|
|
|
|
async function fetchData() {
|
|
const url = "/agents";
|
|
try {
|
|
const response = await fetch(url, {headers: {Accept: 'application/json'}});
|
|
if (!response.ok) {
|
|
throw new Error(`Response status: ${response.status}`);
|
|
}
|
|
const data = await response.json();
|
|
return data; // Return the fetched data
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
return []; // Return an empty array on error
|
|
}
|
|
}
|
|
|
|
// Function to get agent data and update the graph
|
|
async function loadGraphData() {
|
|
// console.log("Function loadGraphData()");
|
|
|
|
var agentDataCurrent = ""
|
|
if (agentData.length > 0) {
|
|
agentDataCurrent = JSON.stringify(agentData);
|
|
}
|
|
// Fetch agent data asynchronously
|
|
agentData = await fetchData();
|
|
|
|
console.log('Extracted agent data:', agentData);
|
|
|
|
// Only update the graph if agent data is available
|
|
if (JSON.stringify(agentData) != agentDataCurrent) {
|
|
// if (agentData && agentData.length > 0) {
|
|
console.log("updating graph");
|
|
await updateGraph(agentData);
|
|
} else {
|
|
console.log('No agent data found or extracted.');
|
|
}
|
|
}
|