Files
graphs/graph.js
2025-07-22 20:00:00 -04:00

780 lines
28 KiB
JavaScript

const express = require('express');
const { ChartJSNodeCanvas } = require('chartjs-node-canvas');
const { createCanvas, loadImage } = require('canvas');
const axios = require('axios');
const cors = require('cors'); // Import CORS middleware
require('dotenv').config()
const app = express();
const port = 6666;
app.use(cors()); // Allows all origins (wildcard *)
const metricWidth = 1900;
const metricHeight = 400;
const titleHeight = 100;
const graphMargin = 30;
app.use((req, res, next) => {
res.set({
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Surrogate-Control': 'no-store'
});
next();
});
const chartJSMetricCanvas = new ChartJSNodeCanvas({ width: metricWidth, height: metricHeight, backgroundColour: 'black' });
const getEndpoints = (containerId, timeframe) => {
const after = -(timeframe * 60);
return {
cpu: `${process.env.API_BASE_URL}/api/v3/data?chart=cgroup_${containerId}.cpu&format=json&after=${after}&dimensions=user,system`,
memory: `${process.env.API_BASE_URL}/api/v3/data?chart=cgroup_${containerId}.mem_usage&format=json&after=${after}&dimensions=used`,
io: `${process.env.API_BASE_URL}/api/v3/data?chart=cgroup_${containerId}.io&format=json&after=${after}&dimensions=read,write`,
pids: `${process.env.API_BASE_URL}/api/v3/data?chart=cgroup_${containerId}.pids_current&format=json&after=${after}&dimensions=current`,
network: `${process.env.API_BASE_URL}/api/v3/data?chart=cgroup_${containerId}.net_eth0&format=json&after=${after}&dimensions=received,sent`,
};
};
const fetchMetricData = async (metric, containerId, timeframe = 5) => {
const endpoints = getEndpoints(containerId, timeframe);
try {
const response = await axios.get(endpoints[metric]);
return response.data;
} catch (error) {
console.error(`Error fetching ${metric} data for container ${containerId}:`, error);
throw new Error(`Failed to fetch ${metric} data.`);
}
};
const extractMetrics = (data, metric) => {
const labels = data.result.data.map((entry) => new Date(entry[0] * 1000).toLocaleTimeString());
let values;
switch (metric) {
case 'cpu':
values = data.result.data.map(entry => entry[1] + entry[2]);
break;
case 'memory':
values = data.result.data.map(entry => entry[1] / 1024); // Convert KiB to MB
break;
case 'io':
values = {
read: data.result.data.map(entry => entry[1] / 1024), // Convert KiB/s to MB/s
write: data.result.data.map(entry => -entry[2] / 1024), // Convert KiB/s to MB/s and make positive
};
break;
case 'pids':
values = data.result.data.map(entry => entry[1]);
break;
case 'network':
values = {
received: data.result.data.map(entry => entry[1] / 8), // Convert Kbits/s to KB/s
sent: data.result.data.map(entry => -entry[2] / 8), // Convert Kbits/s to KB/s and make positive
};
break;
default:
values = [];
}
return { labels, values };
};
const generateMetricGraph = async (metric, labels, label, borderColor) => {
const configuration = {
type: 'line',
data: {
labels: labels,
datasets: [{
label: label,
data: metric,
borderColor: borderColor,
fill: false,
tension: 0.1,
}],
},
options: {
scales: {
x: {
title: {
display: true,
text: 'Time',
color: 'white',
},
},
y: {
title: {
display: true,
text: `${label} Usage`,
color: 'white',
},
},
},
plugins: {
legend: {
labels: {
color: 'white',
},
},
},
},
};
return chartJSMetricCanvas.renderToBuffer(configuration);
};
// Draw title on the canvas
const drawTitle = (ctx, text, yPos) => {
ctx.fillStyle = 'white'; // Set text color
ctx.font = 'bold 40px Arial'; // Set font size and style
const textWidth = ctx.measureText(text).width; // Measure the width of the text
ctx.fillText(text, (metricWidth - textWidth) / 2, yPos); // Center the text horizontally
};
// CPU Usage
app.get('/api/graph/cpu/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const data = await fetchMetricData('cpu', containerId, timeframe);
if (format === 'json') {
return res.json(data);
}
const { labels, values } = extractMetrics(data, 'cpu');
const imageBuffer = await generateMetricGraph(values, labels, 'CPU Usage (%)', 'rgba(255, 99, 132, 1)');
res.set('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
res.status(500).send(`Error generating CPU graph: ${error.message}`);
}
});
// Memory Usage
app.get('/api/graph/memory/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const data = await fetchMetricData('memory', containerId, timeframe);
if (format === 'json') {
return res.json(data);
}
const { labels, values } = extractMetrics(data, 'memory');
const imageBuffer = await generateMetricGraph(values, labels, 'Memory Usage (MB)', 'rgba(54, 162, 235, 1)');
res.set('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
res.status(500).send(`Error generating memory graph: ${error.message}`);
}
});
// Disk I/O
app.get('/api/graph/io/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const data = await fetchMetricData('io', containerId, timeframe);
if (format === 'json') {
return res.json(data);
}
const { labels, values } = extractMetrics(data, 'io');
const readBuffer = await generateMetricGraph(values.read, labels, 'Disk Read (MB/s)', 'rgba(54, 255, 132, 1)');
const writeBuffer = await generateMetricGraph(values.write, labels, 'Disk Write (MB/s)', 'rgba(255, 99, 255, 1)');
const canvas = createCanvas(metricWidth, metricHeight * 2 + 100);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTitle(ctx, `Disk Read for ${containerId}`, 40);
let img = await loadImage(readBuffer);
ctx.drawImage(img, 0, 50, metricWidth, metricHeight);
drawTitle(ctx, `Disk Write for ${containerId}`, metricHeight + 100);
img = await loadImage(writeBuffer);
ctx.drawImage(img, 0, metricHeight + 110, metricWidth, metricHeight);
res.set('Content-Type', 'image/png');
res.send(canvas.toBuffer());
} catch (error) {
res.status(500).send('Error generating disk I/O graphs.');
}
});
// PIDs
app.get('/api/graph/pids/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const data = await fetchMetricData('pids', containerId, timeframe);
if (format === 'json') {
return res.json(data);
}
const { labels, values } = extractMetrics(data, 'pids');
const imageBuffer = await generateMetricGraph(values, labels, 'PIDs (Processes)', 'rgba(153, 102, 255, 1)');
res.set('Content-Type', 'image/png');
res.send(imageBuffer);
} catch (error) {
res.status(500).send(`Error generating PIDs graph: ${error.message}`);
}
});
// Network Traffic
app.get('/api/graph/network/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const data = await fetchMetricData('network', containerId, timeframe);
if (format === 'json') {
return res.json(data);
}
const { labels, values } = extractMetrics(data, 'network');
const receivedBuffer = await generateMetricGraph(values.received, labels, 'Network Received (KB/s)', 'rgba(75, 192, 192, 1)');
const sentBuffer = await generateMetricGraph(values.sent, labels, 'Network Sent (KB/s)', 'rgba(255, 159, 64, 1)');
const canvas = createCanvas(metricWidth, metricHeight * 2 + 100);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTitle(ctx, `Network Received for ${containerId}`, 40);
let img = await loadImage(receivedBuffer);
ctx.drawImage(img, 0, 50, metricWidth, metricHeight);
drawTitle(ctx, `Network Sent for ${containerId}`, metricHeight + 100);
img = await loadImage(sentBuffer);
ctx.drawImage(img, 0, metricHeight + 110, metricWidth, metricHeight);
res.set('Content-Type', 'image/png');
res.send(canvas.toBuffer());
} catch (error) {
res.status(500).send('Error generating network graphs.');
}
});
// Full Report
app.get('/api/graph/full-report/:containerId', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 5;
const format = req.query.format || 'graph';
try {
const cpuData = await fetchMetricData('cpu', containerId, timeframe);
const memoryData = await fetchMetricData('memory', containerId, timeframe);
const ioData = await fetchMetricData('io', containerId, timeframe);
const pidsData = await fetchMetricData('pids', containerId, timeframe);
const networkData = await fetchMetricData('network', containerId, timeframe);
if (format === 'json') {
return res.json({
cpu: cpuData,
memory: memoryData,
io: ioData,
pids: pidsData,
network: networkData,
});
}
const cpuMetrics = extractMetrics(cpuData, 'cpu');
const memoryMetrics = extractMetrics(memoryData, 'memory');
const ioMetrics = extractMetrics(ioData, 'io');
const pidsMetrics = extractMetrics(pidsData, 'pids');
const networkMetrics = extractMetrics(networkData, 'network');
const cpuBuffer = await generateMetricGraph(cpuMetrics.values, cpuMetrics.labels, 'CPU Usage (%)', 'rgba(255, 99, 132, 1)');
const memoryBuffer = await generateMetricGraph(memoryMetrics.values, memoryMetrics.labels, 'Memory Usage (MB)', 'rgba(54, 162, 235, 1)');
const ioReadBuffer = await generateMetricGraph(ioMetrics.values.read, ioMetrics.labels, 'Disk Read (MB/s)', 'rgba(54, 255, 132, 1)');
const ioWriteBuffer = await generateMetricGraph(ioMetrics.values.write, ioMetrics.labels, 'Disk Write (MB/s)', 'rgba(255, 99, 255, 1)');
const pidsBuffer = await generateMetricGraph(pidsMetrics.values, pidsMetrics.labels, 'PIDs (Processes)', 'rgba(153, 102, 255, 1)');
const networkReceivedBuffer = await generateMetricGraph(networkMetrics.values.received, networkMetrics.labels, 'Network Received (KB/s)', 'rgba(75, 192, 192, 1)');
const networkSentBuffer = await generateMetricGraph(networkMetrics.values.sent, networkMetrics.labels, 'Network Sent (KB/s)', 'rgba(255, 159, 64, 1)');
const numGraphs = 7;
const fullReportHeight = titleHeight + (numGraphs * (metricHeight + graphMargin));
const canvas = createCanvas(metricWidth, fullReportHeight);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTitle(ctx, `Full Report for ${containerId} (Last ${timeframe} minutes)`, 50);
const graphs = [cpuBuffer, memoryBuffer, ioReadBuffer, ioWriteBuffer, pidsBuffer, networkReceivedBuffer, networkSentBuffer];
let yPosition = titleHeight + 20;
for (const imageBuffer of graphs) {
const img = await loadImage(imageBuffer);
ctx.drawImage(img, 0, yPosition, metricWidth, metricHeight);
yPosition += metricHeight + graphMargin;
}
res.set('Content-Type', 'image/png');
res.send(canvas.toBuffer());
} catch (error) {
res.status(500).send('Error generating full report.');
}
});
const Docker = require('dockerode');
const docker = new Docker(); // Make sure Docker is properly configured
app.get('/api/graph/full-report/:containerId/live', async (req, res) => {
const { containerId } = req.params;
const timeframe = parseInt(req.query.timeframe) || 2;
const maxPoints = 30; // Limit to the last 120 seconds (2 minutes)
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Report for ${containerId}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/particles.js"></script>
<style>
body {
background-color: #1c1c1c;
color: white;
font-family: Arial, sans-serif;
position: relative;
}
.chart-container {
position: relative;
height: 250px;
}
h1, h3 {
color: #f0f0f0;
}
canvas {
background-color: #2a2a2a;
border-radius: 8px;
}
.process-table {
margin-top: 20px;
color: white;
}
.process-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 0.9rem;
background-color: #1e1e1e;
}
.process-table th, .process-table td {
padding: 10px;
text-align: center;
border: 1px solid #444;
}
.process-table th {
background-color: #2e2e2e;
font-weight: 600;
color: #cfcfcf;
}
.process-table td {
background-color: #262626;
color: #bbbbbb;
}
.process-table tr:nth-child(even) {
background-color: #2a2a2a;
}
.process-table tr:hover {
background-color: #333;
color: #f1f1f1;
}
/* Particle Container for Full Page Coverage */
.particle-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1; /* Always behind content */
pointer-events: none;
}
/* Styling for search box */
#processSearch {
width: 100%;
padding: 8px;
margin-bottom: 12px;
background-color: #333;
border: 1px solid #555;
color: #fff;
border-radius: 4px;
}
/* Focus effect with dark grey */
#processSearch:focus {
border-color: #3a3a3a; /* Dark grey color for the focus border */
box-shadow: 0 0 5px rgba(58, 58, 58, 0.6); /* Dark grey shadow */
}
/* Scrollbar styling for WebKit-based browsers (Chrome, Safari, Edge) */
::-webkit-scrollbar {
width: 8px; /* Thin scrollbar */
}
::-webkit-scrollbar-track {
background-color: #2a2a2a; /* Dark background for the track */
}
::-webkit-scrollbar-thumb {
background-color: #444; /* Dark grey for the scrollbar thumb */
border-radius: 10px; /* Rounded corners for the scrollbar thumb */
}
::-webkit-scrollbar-thumb:hover {
background-color: #555; /* Slightly lighter grey on hover */
}
/* Styling for search box */
#processSearch {
width: 100%;
padding: 8px;
margin-bottom: 12px;
background-color: #333;
border: 1px solid #3a3a3a; /* Dark grey border */
color: #fff;
border-radius: 4px;
outline: none; /* Remove default outline */
}
/* Focus effect with dark grey */
#processSearch:focus {
border-color: #3a3a3a; /* Dark grey color for the focus border */
box-shadow: 0 0 5px rgba(58, 58, 58, 0.6); /* Dark grey shadow */
}
</style>
</head>
<body>
<div class="container mt-4">
<h3 class="text-center">Live Report for ${containerId}</h3>
<div class="row">
<div class="col-md-4">
<h3 class="text-center">CPU Usage</h3>
<div class="chart-container">
<canvas id="cpuChart"></canvas>
</div>
</div>
<div class="col-md-4">
<h3 class="text-center">Memory Usage</h3>
<div class="chart-container">
<canvas id="memoryChart"></canvas>
</div>
</div>
<div class="col-md-4">
<h3 class="text-center">Network Traffic</h3>
<div class="chart-container">
<canvas id="networkChart"></canvas>
</div>
</div>
</div>
<!-- Search Box -->
<input type="text" id="processSearch" placeholder="Search processes..." onkeyup="filterProcessTable()" autofocus>
<table class="process-table">
<thead>
<tr>
<th>PID</th>
<th>User</th>
<th>Command</th>
</tr>
</thead>
<tbody id="processTableBody">
</tbody>
</table>
</div>
<div id="fullPageParticles" class="particle-container"></div>
<script>
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
const networkCtx = document.getElementById('networkChart').getContext('2d');
let lastSearch = '';
// Function to create particle effects
function createParticles(particleId) {
particlesJS(particleId, {
particles: {
number: { value: 200, density: { enable: true, value_area: 800 } },
color: { value: "#ffffff" },
shape: {
type: "circle",
stroke: { width: 0, color: "#000000" },
},
opacity: { value: 0.5, anim: { enable: true, speed: 1 } },
size: { value: 3, random: true },
line_linked: { enable: false },
move: {
enable: true,
speed: 1.5,
direction: "none",
random: false,
out_mode: "out",
bounce: false
}
},
retina_detect: true
});
}
// Initialize particle effects for the entire page
createParticles('fullPageParticles');
// Function to keep data points limited for smoother performance
function updateChartData(chart, labels, dataSetIndex, newData) {
chart.data.labels.push(labels);
chart.data.datasets[dataSetIndex].data.push(newData);
if (chart.data.labels.length > ${maxPoints}) {
chart.data.labels.shift();
chart.data.datasets[dataSetIndex].data.shift();
}
chart.update();
}
// Charts initialization
const cpuChart = new Chart(cpuCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'CPU Usage (%)',
data: [],
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
pointRadius: 3,
fill: false,
}]
},
options: {
animation: { duration: 500 },
responsive: true,
maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: '', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }},
y: { title: { display: true, text: 'CPU (%)', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }}
},
plugins: { legend: { display: false } }
}
});
const memoryChart = new Chart(memoryCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Memory Usage (MB)',
data: [],
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2,
pointRadius: 3,
fill: false,
}]
},
options: {
animation: { duration: 500 },
responsive: true,
maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: '', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }},
y: { title: { display: true, text: 'Memory (MB)', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }}
},
plugins: { legend: { display: false } }
}
});
let currentNetworkUnit = 'KB'; // Initial unit is KB
const networkChart = new Chart(networkCtx, {
type: 'line',
data: {
labels: [],
datasets: [
{ label: 'Network Received (KB/s)', data: [], borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 2, pointRadius: 3, fill: false },
{ label: 'Network Sent (KB/s)', data: [], borderColor: 'rgba(255, 159, 64, 1)', borderWidth: 2, pointRadius: 3, fill: false }
]
},
options: {
animation: { duration: 500 },
responsive: true,
maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: '', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }},
y: { title: { display: true, text: 'Network (KB/s)', color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }}
},
plugins: { legend: { display: true } }
}
});
function updateNetworkUnits(received, sent) {
// Check if traffic exceeds 1024 KB and should switch to MB
if (received > 1024 || sent > 1024) {
if (currentNetworkUnit === 'KB') {
networkChart.options.scales.y.title.text = 'Network (MB/s)';
networkChart.data.datasets[0].label = 'Network Received (MB/s)';
networkChart.data.datasets[1].label = 'Network Sent (MB/s)';
currentNetworkUnit = 'MB';
}
return { received: received / 1024, sent: sent / 1024 };
} else {
if (currentNetworkUnit === 'MB') {
networkChart.options.scales.y.title.text = 'Network (KB/s)';
networkChart.data.datasets[0].label = 'Network Received (KB/s)';
networkChart.data.datasets[1].label = 'Network Sent (KB/s)';
currentNetworkUnit = 'KB';
}
return { received, sent };
}
}
// Function to filter process table based on input
function filterProcessTable() {
const searchInput = document.getElementById('processSearch').value.toLowerCase();
lastSearch = searchInput; // Store the search input to persist it on data reload
const table = document.getElementById('processTableBody');
const rows = table.getElementsByTagName('tr');
for (let i = 0; i < rows.length; i++) {
const rowData = rows[i].innerText.toLowerCase();
if (rowData.includes(searchInput)) {
rows[i].style.display = '';
} else {
rows[i].style.display = 'none';
}
}
}
// Update process list
async function updateProcessList() {
const processResponse = await fetch('https://process-list.syscall.lol/${containerId}');
const processList = await processResponse.json();
const processTableBody = document.getElementById('processTableBody');
// Clear current table content
processTableBody.innerHTML = '';
// Populate table with new process data
processList.forEach(proc => {
const command = proc[proc.length - 1].toLowerCase(); // Command
if (!command.includes("holesail") && !command.includes("null") && !command.includes("/start.sh")) {
const row = document.createElement('tr');
row.classList.add('fadeIn');
const pidCell = document.createElement('td');
const userCell = document.createElement('td');
const commandCell = document.createElement('td');
pidCell.textContent = proc[1]; // PID
userCell.textContent = proc[0]; // User
commandCell.textContent = proc[proc.length - 1]; // Command
row.appendChild(pidCell);
row.appendChild(userCell);
row.appendChild(commandCell);
processTableBody.appendChild(row);
}
});
// Reapply the search filter after updating the table
filterProcessTable();
}
// Live update logic for graphs
async function updateGraphs() {
const response = await fetch('https://g.syscall.lol/full-report/${containerId}?format=json&timeframe=1');
const data = await response.json();
const currentTime = Math.floor(Date.now() / 1000); // Current time in epoch seconds
// Helper function to find the index of the epoch closest to the current time
function findClosestEpoch(dataArray) {
let closestIndex = 0;
let smallestDifference = Math.abs(dataArray[0][0] - currentTime);
for (let i = 1; i < dataArray.length; i++) {
const timeDiff = Math.abs(dataArray[i][0] - currentTime);
if (timeDiff < smallestDifference) {
closestIndex = i;
smallestDifference = timeDiff;
}
}
return closestIndex;
}
// Find the index of the closest epoch timestamp for each metric
const cpuIndex = findClosestEpoch(data.cpu.result.data);
const memoryIndex = findClosestEpoch(data.memory.result.data);
const networkIndex = findClosestEpoch(data.network.result.data);
// Extract the timestamp and data for CPU, Memory, and Network based on the closest epoch
const latestTime = new Date(data.cpu.result.data[cpuIndex][0] * 1000).toLocaleTimeString();
const latestCPU = data.cpu.result.data[cpuIndex][1] + data.cpu.result.data[cpuIndex][2]; // Assuming this is user + system
updateChartData(cpuChart, latestTime, 0, latestCPU);
const latestMemory = data.memory.result.data[memoryIndex][1] / 1024; // Convert KiB to MB
updateChartData(memoryChart, latestTime, 0, latestMemory);
const latestReceived = data.network.result.data[networkIndex][1] / 8; // Convert Kbits/s to KB/s
const latestSent = -data.network.result.data[networkIndex][2] / 8; // Convert Kbits/s to KB/s and make positive
const updatedValues = updateNetworkUnits(latestReceived, latestSent);
updateChartData(networkChart, latestTime, 0, updatedValues.received);
updateChartData(networkChart, latestTime, 1, updatedValues.sent);
}
// Update process list every 3 seconds
setInterval(updateProcessList, 3000);
// Update graphs every second
setInterval(updateGraphs, 1000);
</script>
</body>
</html>
`;
res.send(html);
});
app.get('/api/processes/:containerId', async (req, res) => {
const { containerId } = req.params;
// Fetch processes running in the container
let processList = [];
try {
const container = docker.getContainer(containerId);
const processes = await container.top(); // Fetch running processes in the container
// console.log(processes)
processList = processes.Processes || [];
} catch (err) {
console.error(`Error fetching processes for container ${containerId}:`, err);
return res.status(500).json({ error: 'Failed to fetch processes' });
}
// Send the process list as a JSON response
res.json(processList);
});
app.listen(port, "0.0.0.0", () => {
console.log(`Server running on http://localhost:${port}`);
});