Remove non-needed live view
This commit is contained in:
420
graph.js
420
graph.js
@@ -336,426 +336,6 @@ app.get('/api/graph/full-report/:containerId', async (req, res) => {
|
|||||||
const Docker = require('dockerode');
|
const Docker = require('dockerode');
|
||||||
const docker = new Docker(); // Make sure Docker is properly configured
|
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) => {
|
app.get('/api/processes/:containerId', async (req, res) => {
|
||||||
const { containerId } = req.params;
|
const { containerId } = req.params;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user