sysmonk/templates/
monitor.rs

1/// Get the HTML content to render the monitor page.
2///
3/// # See Also
4///
5/// - This page is served as a response for the `/` entry point once authenticated.
6///
7/// # Returns
8///
9/// A `String` version of the HTML, CSS and JS content.
10pub fn get_content() -> String {
11    r###"<!DOCTYPE html>
12<html lang="en">
13<head>
14    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
15    <title>SysMonk - System Monitor - v{{ version }}</title>
16    <meta property="og:type" content="SystemMonitor">
17    <meta name="keywords" content="Rust, Monitor, actix, JavaScript, HTML, CSS">
18    <meta name="author" content="Vignesh Rao">
19    <!-- Favicon.ico and Apple Touch Icon -->
20    <link rel="icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.ico">
21    <link rel="apple-touch-icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.png">
22    <meta content="width=device-width, initial-scale=1" name="viewport">
23    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
24    <!-- CSS and JS for night mode -->
25    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
26    <script type="text/javascript" src="https://thevickypedia.github.io/open-source/nightmode/night.js" defer></script>
27    <link rel="stylesheet" type="text/css" href="https://thevickypedia.github.io/open-source/nightmode/night.css">
28    <!-- Font Awesome icons -->
29    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/font-awesome.min.css">
30    <!--suppress CssUnusedSymbol -->
31    <style id="main-css">
32        body {
33            font-family: Arial, sans-serif;
34            overflow-x: hidden;
35        }
36
37        .docker-stats {
38            height: 100%;
39            margin: 2%;
40            display: none;  /* Hide the container initially */
41            align-items: center;
42            justify-content: center;
43            flex-direction: column;  /* Ensure vertical alignment */
44        }
45
46        .docker-stats h3 {
47            text-align: center;
48            margin-bottom: 20px;
49        }
50
51        table {
52            width: 80%;
53            border-collapse: collapse;
54            display: none;  /* Hide the table initially */
55        }
56
57        table, th, td {
58            border: 1px solid #ccc;
59        }
60
61        th, td {
62            padding: 10px;
63            text-align: left;
64        }
65
66        .container {
67            display: flex;
68            justify-content: space-between;
69            margin-top: 50px;
70        }
71
72        .box {
73            border: 1px solid #ccc;
74            padding: 20px;
75            width: 30%;
76            text-align: center;
77            margin: 1%;
78        }
79
80        .progress {
81            width: 100%;
82            background-color: transparent;
83            border-radius: 5px;
84            overflow: hidden;
85            transition: background-color 0.5s ease;
86        }
87
88        .progress-bar {
89            height: 25px;
90            transition: width 0.5s ease, background-color 0.5s ease;
91            width: 0;
92        }
93
94        .progress-bar-green {
95            background-color: #4caf50;
96        }
97
98        .progress-bar-yellow {
99            background-color: #ffeb3b;
100        }
101
102        .progress-bar-orange {
103            background-color: #ff9800;
104        }
105
106        .progress-bar-red {
107            background-color: #f44336;
108        }
109
110        .chart-container {
111            position: relative;
112            height: 200px;
113            width: 80%;
114            margin: 0 auto;
115            max-width: 100%;
116        }
117
118        canvas {
119            width: 100% !important;
120            height: inherit !important;
121            max-height: 100% !important;
122        }
123
124        .tooltip-button {
125            padding: 5px 5px;
126            font-size: 14px;
127            cursor: pointer;
128            border: 1px solid #ccc;
129            border-radius: 5px;
130            background-color: #f0f0f0;
131        }
132
133        .tooltip-button:hover {
134            background-color: #e0e0e0;
135        }
136
137        .center-container {
138            width: 100%;
139            margin-left: 40%;
140        }
141
142        .center-container details {
143            text-align: left;
144        }
145
146        h1 {
147            width: 100%;
148            text-align: center;
149            align-content: center;
150        }
151
152        .logout {
153            position: absolute;
154            top: 3.8%;
155            right: 30px;
156            border: none;
157            padding: 10px 14px;
158            font-size: 16px;
159            cursor: pointer;
160        }
161
162        footer {
163            width: 100%;
164            text-align: center;
165            align-content: center;
166            font-size: 14px;
167            font-style: italic;
168        }
169
170        .graph-canvas {
171            max-width: 600px;
172        }
173    </style>
174    <noscript>
175        <style>
176            body {
177                width: 100%;
178                height: 100%;
179                overflow: hidden;
180            }
181        </style>
182        <div style="position: fixed; text-align:center; height: 100%; width: 100%; background-color: #151515;">
183            <h2 style="margin-top:5%">This page requires JavaScript
184                to be enabled.
185                <br><br>
186                Please refer <a href="https://www.enable-javascript.com/">enable-javascript</a> for how to.
187            </h2>
188            <form>
189                <button type="submit" onClick="<meta httpEquiv='refresh' content='0'>">RETRY</button>
190            </form>
191        </div>
192    </noscript>
193</head>
194<body translate="no">
195<div class="toggler fa fa-moon-o"></div>
196<button class="logout" onclick="logOut()"><i class="fa fa-sign-out"></i> Logout</button>
197<h1>SysMonk - System Monitor</h1>
198<div class="center-container">
199    <details>
200        <summary><strong>System Information</strong></summary>
201        <br>
202        {% for key, value in sys_info_basic|items() %}
203        <strong>{{ key }}: </strong>{{ value }}<br>
204        {% endfor %}
205    </details>
206    <br>
207    <details>
208        <summary><strong>Memory and Storage</strong></summary>
209        <br>
210        {% for key, value in sys_info_mem_storage|items() %}
211        <strong>{{ key }}: </strong>{{ value }}<br>
212        {% endfor %}
213    </details>
214    <br>
215    <details>
216        <summary><strong>Network Information</strong></summary>
217        <br>
218        {% for key, value in sys_info_network|items() %}
219        <strong>{{ key }}: </strong>{{ value }}<br>
220        {% endfor %}
221    </details>
222    {% if sys_info_disks %}
223    <br>
224    <details>
225        <summary><strong>Disk Information</strong></summary>
226        {% for disk_info in sys_info_disks %}
227        <br>
228        {% for key, value in disk_info|items() %}
229        <strong>{{ key }}: </strong>{{ value }}<br>
230        {% endfor %}
231        {% endfor %}
232    </details>
233    {% endif %}
234</div>
235<div class="container">
236    <!-- Box to display utilization per CPU -->
237    <div class="box">
238        <h3>CPU Usage</h3>
239        <div class="cpu-box" id="cpuUsageContainer">
240            <!-- CPU Usage will be dynamically added here -->
241        </div>
242    </div>
243    <!-- Box to display Memory, Swap and Disk usage along with CPU load avg -->
244    <div class="box">
245        <h3>Memory Usage</h3>
246        <div class="progress">
247            <div id="memoryUsage" class="progress-bar"></div>
248        </div>
249        <p id="memoryUsageText">Memory: 0%</p>
250
251        {% if 'Swap' in sys_info_mem_storage %}
252        <h3>Swap Usage</h3>
253        <div class="progress">
254            <div id="swapUsage" class="progress-bar"></div>
255        </div>
256        <p id="swapUsageText">Swap: 0%</p>
257        {% endif %}
258
259        <h3>Disk Usage</h3>
260        <div class="progress">
261            <div id="diskUsage" class="progress-bar"></div>
262        </div>
263        <p id="diskUsageText">Disk: 0%</p>
264
265        <div class="graph">
266            <h3>CPU Load Averages</h3>
267            <canvas class="graph-canvas" id="loadChart" width="400" height="200"></canvas>
268        </div>
269    </div>
270    <!-- Box to display Memory, Swap and Disk usage as Pie charts -->
271    <div class="box">
272        <h3>Memory Usage</h3>
273        <h5 id="memoryTotal"></h5>
274        <div class="chart-container">
275            <canvas id="memoryChart"></canvas>
276        </div>
277        {% if 'Swap' in sys_info_mem_storage %}
278        <h3>Swap Usage</h3>
279        <h5 id="swapTotal"></h5>
280        <div class="chart-container">
281            <canvas id="swapChart"></canvas>
282        </div>
283        {% endif %}
284        <h3>Disk Usage</h3>
285        <h5 id="diskTotal"></h5>
286        <div class="chart-container">
287            <canvas id="diskChart"></canvas>
288        </div>
289    </div>
290</div>
291<div id="docker-stats" class="docker-stats">
292    <h3>Docker Stats</h3>
293    <table id="dockerStatsTable">
294        <thead>
295            <tr>
296                <th>Container ID</th>
297                <th>Container Name</th>
298                <th>CPU %</th>
299                <th>Memory Usage</th>
300                <th>Memory %</th>
301                <th>Net I/O</th>
302                <th>Block I/O</th>
303                <th>PIDs</th>
304            </tr>
305        </thead>
306        <tbody>
307        </tbody>
308    </table>
309</div>
310<script>
311    document.addEventListener('DOMContentLoaded', function () {
312        const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
313        const wsHost = window.location.host;
314        const ws = new WebSocket(`${wsProtocol}://${wsHost}/ws/system`);
315
316        ws.onopen = () => {
317            console.log('WebSocket connection established');
318        };
319        ws.onclose = () => {
320            console.log('WebSocket connection closed');
321            alert('WebSocket connection closed by the server!');
322            logOut();
323            return;
324        };
325
326        let memoryChartInstance = null;
327        let swapChartInstance = null;
328        let diskChartInstance = null;
329        let loadChartInstance = null;
330
331        ws.onmessage = function (event) {
332            let data;
333            try {
334                data = JSON.parse(event.data);
335            } catch (error) {
336                console.warn('Error parsing JSON data:', error);
337                alert(event.data);
338                logOut();
339                return;
340            }
341
342            const dockerStatsJSON = data.docker_stats;
343            // Check if dockerStatsJSON is valid
344            if (dockerStatsJSON && dockerStatsJSON.length > 0) {
345                // Show the container and the table
346                const statsContainer = document.getElementById("docker-stats");
347                statsContainer.style.display = "flex";
348                const table = document.getElementById("dockerStatsTable");
349                table.style.display = "table";
350                // Get reference to the table body
351                const tableBody = document.querySelector('#dockerStatsTable tbody');
352                // Clear the existing table rows
353                tableBody.innerHTML = '';
354                // Loop through the JSON data and populate the table
355                dockerStatsJSON.forEach(container => {
356                    const row = document.createElement('tr');
357                    row.innerHTML = `
358                        <td>${container.ID}</td>
359                        <td>${container.Name}</td>
360                        <td>${container.CPUPerc}</td>
361                        <td>${container.MemUsage}</td>
362                        <td>${container.MemPerc}</td>
363                        <td>${container.NetIO}</td>
364                        <td>${container.BlockIO}</td>
365                        <td>${container.PIDs}</td>
366                    `;
367                    tableBody.appendChild(row);
368                });
369            } else {
370                // Hide the container if no data is available
371                document.getElementById("docker-stats").style.display = "none";
372            }
373
374            // Update CPU usage
375            const cpuUsage = data.cpu_usage;
376            const cpuContainer = document.getElementById('cpuUsageContainer');
377            cpuContainer.innerHTML = ''; // Clear previous content
378            cpuUsage.forEach((usage, index) => {
379                const cpuDiv = document.createElement('div');
380                cpuDiv.innerHTML = `
381                        <strong>CPU ${index + 1}:</strong> ${usage}%
382                        <div class="progress">
383                            <div id="cpu${index}" class="progress-bar"></div>
384                        </div>
385                    `;
386                cpuContainer.appendChild(cpuDiv);
387                updateProgressBar(`cpu${index}`, usage);
388            });
389
390            // Memory Usage Progress Bar
391            const memoryInfo = data.memory_info;
392            const memoryUsage = (memoryInfo.used / memoryInfo.total) * 100;
393            document.getElementById('memoryUsage').style.width = memoryUsage.toFixed(2) + '%';
394            document.getElementById('memoryUsageText').innerText = `Memory: ${memoryUsage.toFixed(2)}%`;
395            updateProgressBar('memoryUsage', memoryUsage);
396
397            // Swap Usage Progress Bar
398            const swapInfo = data.swap_info;
399            if (swapInfo) {
400                const swapUsage = (swapInfo.used / swapInfo.total) * 100;
401                document.getElementById('swapUsage').style.width = swapUsage.toFixed(2) + '%';
402                document.getElementById('swapUsageText').innerText = `Swap: ${swapUsage.toFixed(2)}%`;
403                updateProgressBar('swapUsage', swapUsage);
404            }
405
406            // Disk Usage Progress Bar
407            const diskInfo = data.disk_info;
408            const diskUsage = (diskInfo.used / diskInfo.total) * 100;
409            document.getElementById('diskUsage').style.width = diskUsage.toFixed(2) + '%';
410            document.getElementById('diskUsageText').innerText = `Disk: ${diskUsage.toFixed(2)}%`;
411            updateProgressBar('diskUsage', diskUsage);
412
413            // CPU Load Avg Graph
414            const loadAverages = data.load_averages;
415            if (loadChartInstance) {
416                loadChartInstance.data.datasets[0].data = [loadAverages["m1"], loadAverages["m5"], loadAverages["m15"]];
417                loadChartInstance.update();
418            } else {
419                const ctx = document.getElementById('loadChart').getContext('2d');
420                loadChartInstance = new Chart(ctx, {
421                    type: 'bar',
422                    data: {
423                        labels: ['1 minute', '5 minutes', '15 minutes'],
424                        datasets: [{
425                            label: 'Load Average',
426                            data: [loadAverages["m1"], loadAverages["m5"], loadAverages["m15"]],
427                            backgroundColor: [
428                                'rgba(75, 192, 192, 0.2)',
429                                'rgba(153, 102, 255, 0.2)',
430                                'rgba(255, 159, 64, 0.2)'
431                            ],
432                            borderColor: [
433                                'rgba(75, 192, 192, 1)',
434                                'rgba(153, 102, 255, 1)',
435                                'rgba(255, 159, 64, 1)'
436                            ],
437                            borderWidth: 1
438                        }]
439                    },
440                    options: {
441                        plugins: {
442                            // Hide the legend
443                            legend: {
444                                display: false
445                            }
446                        },
447                        scales: {
448                            y: {
449                                beginAtZero: true,
450                                title: {
451                                    display: true,
452                                    text: 'Number of Processes'
453                                },
454                                ticks: {
455                                    // Set integer step size
456                                    stepSize: 1,
457                                    callback: function (value) {
458                                        return Number.isInteger(value) ? value : '';
459                                    }
460                                }
461                            }
462                        }
463                    }
464                });
465            }
466
467            // Memory Chart
468            document.getElementById("memoryTotal").innerText = `Total: ${formatBytes(memoryInfo.total)}`;
469            if (memoryChartInstance) {
470                memoryChartInstance.data.datasets[0].data = [memoryInfo.used, memoryInfo.total - memoryInfo.used];
471                memoryChartInstance.update();
472            } else {
473                const memoryChart = document.getElementById('memoryChart').getContext('2d');
474                memoryChartInstance = new Chart(memoryChart, {
475                    type: 'pie',
476                    data: {
477                        labels: ['Used', 'Free'],
478                        datasets: [{
479                            label: 'Memory Usage',
480                            data: [memoryInfo.used, memoryInfo.total - memoryInfo.used],
481                            backgroundColor: ['#FF6384', '#36A2EB']
482                        }]
483                    },
484                    options: {
485                        responsive: true,
486                        plugins: {
487                            tooltip: {
488                                callbacks: {
489                                    label: function (tooltipItem) {
490                                        const value = tooltipItem.raw;
491                                        const formattedValue = formatBytes(value);
492                                        return `${tooltipItem.label}: ${formattedValue}`;
493                                    }
494                                }
495                            }
496                        }
497                    }
498                });
499            }
500
501            // Swap Chart
502            const swapChart = document.getElementById('swapChart');
503            if (swapChart) {
504                document.getElementById("swapTotal").innerText = `Total: ${formatBytes(swapInfo.total)}`;
505            }
506            if (swapChartInstance) {
507                swapChartInstance.data.datasets[0].data = [swapInfo.used, swapInfo.total - swapInfo.used];
508                swapChartInstance.update();
509            } else {
510                if (swapChart) {
511                    const swapContext = swapChart.getContext('2d')
512                    swapChartInstance = new Chart(swapContext, {
513                        type: 'pie',
514                        data: {
515                            labels: ['Used', 'Free'],
516                            datasets: [{
517                                label: 'Swap Usage',
518                                data: [swapInfo.used, swapInfo.total - swapInfo.used],
519                                backgroundColor: ['#FFCE56', '#E7E9ED']
520                            }]
521                        },
522                        options: {
523                            responsive: true,
524                            plugins: {
525                                tooltip: {
526                                    callbacks: {
527                                        label: function (tooltipItem) {
528                                            const value = tooltipItem.raw;
529                                            const formattedValue = formatBytes(value);
530                                            return `${tooltipItem.label}: ${formattedValue}`;
531                                        }
532                                    }
533                                }
534                            }
535                        }
536                    });
537                }
538            }
539
540            // Disk Chart
541            document.getElementById("diskTotal").innerText = `Total: ${formatBytes(diskInfo.total)}`;
542            if (diskChartInstance) {
543                diskChartInstance.data.datasets[0].data = [diskInfo.used, diskInfo.total - diskInfo.used];
544                diskChartInstance.update();
545            } else {
546                const diskChart = document.getElementById('diskChart').getContext('2d');
547                diskChartInstance = new Chart(diskChart, {
548                    type: 'pie',
549                    data: {
550                        labels: ['Used', 'Free'],
551                        datasets: [{
552                            label: 'Disk Usage',
553                            data: [diskInfo.used, diskInfo.total - diskInfo.used],
554                            backgroundColor: ['#63950d', '#ca7b00']
555                        }]
556                    },
557                    options: {
558                        responsive: true,
559                        plugins: {
560                            tooltip: {
561                                callbacks: {
562                                    label: function (tooltipItem) {
563                                        const value = tooltipItem.raw;
564                                        const formattedValue = formatBytes(value);
565                                        return `${tooltipItem.label}: ${formattedValue}`;
566                                    }
567                                }
568                            }
569                        }
570                    }
571                });
572            }
573
574        };
575
576        function updateProgressBar(id, percentage) {
577            const bar = document.getElementById(id);
578            bar.style.width = percentage + '%';
579
580            // Remove old color classes
581            bar.classList.remove('progress-bar-green', 'progress-bar-yellow', 'progress-bar-orange', 'progress-bar-red');
582
583            // Add new color class based on percentage
584            if (percentage <= 50) {
585                bar.classList.add('progress-bar-green');
586            } else if (percentage <= 70) {
587                bar.classList.add('progress-bar-yellow');
588            } else if (percentage <= 90) {
589                bar.classList.add('progress-bar-orange');
590            } else {
591                bar.classList.add('progress-bar-red');
592            }
593        }
594
595        function formatBytes(bytes) {
596            const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
597            let unitIndex = 0;
598            while (bytes >= 1024 && unitIndex < units.length - 1) {
599                bytes /= 1024;
600                unitIndex++;
601            }
602            return bytes.toFixed(2) + ' ' + units[unitIndex];
603        }
604
605    });
606
607    function logOut() {
608        window.location.href = window.location.origin + "{{ logout }}";
609    }
610</script>
611<footer>
612    Generated by <a href="https://github.com/thevickypedia/SysMonk/releases/tag/v{{ version }}">SysMonk - v{{ version }}</a>
613</footer>
614</body>
615</html>
616"###.to_string()
617}