mirror of
https://github.com/fusionpbx/fusionpbx.git
synced 2025-12-30 00:53:50 +00:00
Update dashboard with cpu status websockets (#7400)
* Remove setting hardcoded timer * Use websockets for real-time CPU status on dashboard * Add system_dashboard service file * moved javascript file to core
This commit is contained in:
@@ -60,39 +60,38 @@
|
||||
|
||||
subscriber::save_token($token, [system_dashboard_service::get_service_name()]);
|
||||
|
||||
//break the caching with version
|
||||
$version = md5(file_get_contents(__DIR__, '/resources/javascript/websocket_client.js'));
|
||||
|
||||
//set script source
|
||||
echo "<script src='/app/system/resources/javascript/websocket_client.js?v=$version'></script>\n";
|
||||
|
||||
//add half doughnut chart
|
||||
if (!isset($dashboard_chart_type) || $dashboard_chart_type == "doughnut"): ?>
|
||||
<div class='hud_chart' style='width: 175px;'><canvas id='system_cpu_status_chart'></canvas></div>
|
||||
|
||||
<script>
|
||||
const authToken = {
|
||||
const cpu_status_auth_token = {
|
||||
name: "<?= $token['name']; ?>",
|
||||
hash: "<?= $token['hash']; ?>"
|
||||
}
|
||||
|
||||
const serviceName = '<?php echo system_dashboard_service::get_service_name(); ?>'
|
||||
const cpuStatusTopic = '<?php echo system_dashboard_service::CPU_STATUS_TOPIC; ?>';
|
||||
const cpu_status_subject = '<?php echo system_dashboard_service::CPU_STATUS_TOPIC; ?>';
|
||||
const dashboard_cpu_usage_chart_main_color = [
|
||||
'<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[0] ?? '#03c04a'); ?>',
|
||||
'<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[1] ?? '#ff9933'); ?>',
|
||||
'<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[2] ?? '#ea4c46'); ?>'
|
||||
];
|
||||
|
||||
function connectWebsocket() {
|
||||
client = new ws_client(`wss://${window.location.hostname}/websockets/`, authToken);
|
||||
function connect_cpu_status_websocket() {
|
||||
client = new ws_client(`wss://${window.location.hostname}/websockets/`, cpu_status_auth_token);
|
||||
client.ws.addEventListener("open", async () => {
|
||||
try {
|
||||
console.log('Connected');
|
||||
console.log('Requesting authentication');
|
||||
|
||||
// Wait until we are authenticated
|
||||
await client.request('authentication');
|
||||
client.onEvent(cpuStatusTopic, updateCpuChart);
|
||||
client.request(serviceName, cpuStatusTopic);
|
||||
console.log('authenticated');
|
||||
|
||||
// Bind event handler so websocket_client.js can call the function when it
|
||||
// receives the cpu_status event
|
||||
client.onEvent(cpu_status_subject, update_cpu_chart);
|
||||
|
||||
} catch (err) {
|
||||
console.error("WS setup failed: ", err);
|
||||
return;
|
||||
@@ -104,31 +103,33 @@
|
||||
});
|
||||
}
|
||||
|
||||
function bindEventHandlers(client) {
|
||||
client.onEvent(cpuStatusTopic, updateCpuChart);
|
||||
}
|
||||
|
||||
function updateCpuChart(payload) {
|
||||
let cpuPercent = payload.cpu_status;
|
||||
// Function is called automatically by the websocket_client.js when there is a CPU status update
|
||||
function update_cpu_chart(payload) {
|
||||
let cpu_status = payload.cpu_status;
|
||||
const chart = window.system_cpu_status_chart;
|
||||
|
||||
if (!chart) return;
|
||||
|
||||
// Update chart data
|
||||
cpuPercent = Math.round(cpuPercent);
|
||||
chart.data.datasets[0].data = [cpuPercent, 100 - cpuPercent];
|
||||
cpu_rounded = Math.round(cpu_status);
|
||||
chart.data.datasets[0].data = [cpu_rounded, 100 - cpu_rounded];
|
||||
|
||||
// Update color based on threshold
|
||||
if (cpuPercent <= 60) {
|
||||
chart.data.datasets[0].backgroundColor[0] = dashboard_cpu_usage_chart_main_color[0];
|
||||
} else if (cpuPercent <= 80) {
|
||||
chart.data.datasets[0].backgroundColor[0] = dashboard_cpu_usage_chart_main_color[1];
|
||||
if (cpu_rounded <= 60) {
|
||||
chart.data.datasets[0].backgroundColor[0] = '<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[0] ?? '#03c04a'); ?>';
|
||||
} else if (cpu_rounded <= 80) {
|
||||
chart.data.datasets[0].backgroundColor[0] = '<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[1] ?? '#ff9933'); ?>';
|
||||
} else {
|
||||
chart.data.datasets[0].backgroundColor[0] = dashboard_cpu_usage_chart_main_color[2];
|
||||
chart.data.datasets[0].backgroundColor[0] = '<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[2] ?? '#ea4c46'); ?>';
|
||||
}
|
||||
|
||||
chart.options.plugins.chart_number_2.text = cpuPercent;
|
||||
chart.options.plugins.chart_number_2.text = cpu_rounded;
|
||||
chart.update();
|
||||
|
||||
// Update the row data
|
||||
const td_cpu_status = document.getElementById('td_system_cpu_status_chart');
|
||||
if (!td_cpu_status) { return; }
|
||||
td_cpu_status.textContent = `${payload.cpu_status}%`;
|
||||
}
|
||||
|
||||
window.system_cpu_status_chart = new Chart(
|
||||
@@ -182,7 +183,7 @@
|
||||
}
|
||||
);
|
||||
|
||||
connectWebsocket();
|
||||
connect_cpu_status_websocket();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
@@ -203,7 +204,7 @@
|
||||
if (!empty($percent_cpu)) {
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-cpu_usage']."</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$percent_cpu."%</td>\n";
|
||||
echo "<td id='td_system_cpu_status_chart' valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$percent_cpu."%</td>\n";
|
||||
echo "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -63,12 +63,61 @@
|
||||
$used_space = $tmp[2] ?? '-';
|
||||
$total_space = $tmp[1] ?? '-';
|
||||
$percent_disk_usage = '';
|
||||
|
||||
|
||||
foreach ($tmp as $stat) {
|
||||
if (substr_count($stat, '%') > 0) { $percent_disk_usage = rtrim($stat,'%'); break; }
|
||||
}
|
||||
}
|
||||
|
||||
//include websocket real-time updates
|
||||
$token = (new token())->create($_SERVER['PHP_SELF']);
|
||||
echo " <input id='token' type='hidden' name='" . $token['name'] . "' value='" . $token['hash'] . "'>\n";
|
||||
subscriber::save_token($token, [system_dashboard_service::get_service_name()]);
|
||||
?>
|
||||
<script>
|
||||
const system_status_auth_token = {
|
||||
name: "<?= $token['name']; ?>",
|
||||
hash: "<?= $token['hash']; ?>"
|
||||
}
|
||||
|
||||
function connect_system_cpu_status_websocket() {
|
||||
system_status_client = new ws_client(`wss://${window.location.hostname}/websockets/`, system_status_auth_token);
|
||||
system_status_client.ws.addEventListener("open", async () => {
|
||||
try {
|
||||
console.log('Connected');
|
||||
console.log('Requesting authentication');
|
||||
|
||||
// Wait until we are authenticated
|
||||
await system_status_client.request('authentication');
|
||||
console.log('authenticated');
|
||||
|
||||
// call the update_system_cpu_status function when a cpu usage event occurs
|
||||
system_status_client.onEvent('<?php echo system_dashboard_service::CPU_STATUS_TOPIC; ?>', update_system_cpu_status);
|
||||
} catch (err) {
|
||||
console.error("WS setup failed: ", err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
system_status_client.ws.addEventListener("close", async () => {
|
||||
console.warn("Websocket Disconnected");
|
||||
});
|
||||
}
|
||||
|
||||
function update_system_cpu_status(payload) {
|
||||
const progress = document.getElementById('cpu_status_progress_bar');
|
||||
|
||||
if (!progress) return;
|
||||
|
||||
// Update progress bar
|
||||
cpu_percent = Math.round(payload.cpu_status);
|
||||
progress.style.width = `${cpu_percent}%`;
|
||||
progress.innerHTML = `${cpu_percent}%`;
|
||||
}
|
||||
|
||||
connect_system_cpu_status_websocket();
|
||||
</script>
|
||||
<?php
|
||||
|
||||
//show the results
|
||||
echo " <div class='hud_content' ".($dashboard_details_state == "disabled" ?: "onclick=\"$('#hud_system_status_details').slideToggle('fast'); toggle_grid_row_end('".$dashboard_name."')\"").">\n";
|
||||
@@ -137,7 +186,7 @@
|
||||
if ($dashboard_row_span > 1) {
|
||||
echo " <span class='hud_title cpu_usage' style='text-align: left; font-size: 11px; line-height: 1.8; font-weight: unset; padding-left: 10%;'>".$text['label-processor_usage']."</span>\n";
|
||||
echo " <div class='progress_container' style='width: 80%; height: 15px; border-radius: 10px; background: ".($settings->get('theme', 'dashboard_cpu_usage_chart_sub_color') ?? '#d4d4d4').";'>\n";
|
||||
echo " <div class='progress_bar' style='width: ".($percent_cpu > 100 ? 100 : $percent_cpu)."%; height: 15px; border-radius: 10px; font-size: x-small; color: ".$row['dashboard_number_text_color']."; background: ".($settings->get('theme', 'dashboard_cpu_usage_chart_main_color') ?? '#03c04a').";'>".($percent_cpu > 100 ? 100 : round($percent_cpu))."%</div>\n";
|
||||
echo " <div id='cpu_status_progress_bar' class='progress_bar' style='width: ".($percent_cpu > 100 ? 100 : $percent_cpu)."%; height: 15px; border-radius: 10px; font-size: x-small; color: ".$row['dashboard_number_text_color']."; background: ".($settings->get('theme', 'dashboard_cpu_usage_chart_main_color') ?? '#03c04a').";'>".($percent_cpu > 100 ? 100 : round($percent_cpu))."%</div>\n";
|
||||
echo " </div>\n";
|
||||
echo " <div style='width: 100%; height: 15px'> </div>\n";
|
||||
}
|
||||
@@ -185,15 +234,15 @@
|
||||
if (file_exists('/etc/os-release')) {
|
||||
$os_release = parse_ini_file('/etc/os-release');
|
||||
$os_info = $os_release['PRETTY_NAME'] ?? '';
|
||||
}
|
||||
}
|
||||
// Fallback to basic uname info
|
||||
elseif (function_exists('php_uname')) {
|
||||
$os_info = php_uname('s') . ' ' . php_uname('r'); // e.g. "Linux 5.10.0"
|
||||
}
|
||||
|
||||
|
||||
// Clean up the output
|
||||
$os_info = str_replace('"', '', $os_info); // Remove quotes if present
|
||||
|
||||
|
||||
if (!empty($os_info)) {
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-os_version']."</td>\n";
|
||||
@@ -224,18 +273,18 @@
|
||||
if (!empty($meminfo)) {
|
||||
$meminfo = preg_replace('/\s+/', ' ', trim($meminfo));
|
||||
$parts = explode(' ', $meminfo);
|
||||
|
||||
|
||||
$total = $parts[1];
|
||||
$used = $parts[2];
|
||||
$percent_memory = round(($used / $total) * 100, 1);
|
||||
|
||||
// Set style color based on thresholds
|
||||
$style = ($percent_memory > 90) ? "color: red;" : (($percent_memory > 75) ? "color: orange;" : "");
|
||||
|
||||
|
||||
// Format with used/total (e.g. "40% (3.2G/8G)")
|
||||
$total_h = round($total / (1024*1024*1024), 1) . 'G';
|
||||
$used_h = round($used / (1024*1024*1024), 1) . 'G';
|
||||
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-memory_usage']."</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right; $style'>".$percent_memory."% (".$used_h." / ".$total_h.")"."</td>\n";
|
||||
@@ -250,10 +299,10 @@
|
||||
if (!empty($swapinfo)) {
|
||||
$swapinfo = preg_replace('/\s+/', ' ', trim($swapinfo));
|
||||
$parts = explode(' ', $swapinfo);
|
||||
|
||||
|
||||
$swap_total = $parts[1];
|
||||
$swap_used = $parts[2];
|
||||
|
||||
|
||||
// Only show swap if it exists (total > 0)
|
||||
if ($swap_total > 0) {
|
||||
$percent_swap = round(($swap_used / $swap_total) * 100, 1);
|
||||
@@ -262,7 +311,7 @@
|
||||
|
||||
// Set style color based on thresholds
|
||||
$style = ($percent_swap > 90) ? "color: red;" : (($percent_swap > 75) ? "color: orange;" : "");
|
||||
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-swap_usage']."</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right; $style'>".$percent_swap."% (".$swap_used_h." / ".$swap_total_h.")"."</td>\n";
|
||||
@@ -274,11 +323,11 @@
|
||||
|
||||
//disk usage display
|
||||
if (stristr(PHP_OS, 'Linux') || stristr(PHP_OS, 'FreeBSD')) {
|
||||
|
||||
|
||||
if (!empty($percent_disk_usage) && $used_space != '-' && $total_space != '-') {
|
||||
// Set style color based on thresholds
|
||||
$style = ($percent_disk_usage > 90) ? "color: red;" : (($percent_disk_usage > 75) ? "color: orange;" : "");
|
||||
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-disk_usage']."</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right; $style'>".$percent_disk_usage."% (".$used_space." / ".$total_space.")"."</td>\n";
|
||||
@@ -305,30 +354,30 @@
|
||||
$connections = sizeof($tmp) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($sql_current) && !empty($sql_max)) {
|
||||
if (!isset($database)) { $database = new database; }
|
||||
|
||||
|
||||
// Get current connections
|
||||
$current_connections = $database->select($sql_current, null, 'column');
|
||||
|
||||
|
||||
// Get max connections (handles both PostgreSQL & MySQL)
|
||||
$max_result = $database->select($sql_max, null, ($db_type == 'pgsql') ? 'column' : 'row');
|
||||
$max_connections = ($db_type == 'mysql') ? $max_result['Value'] : $max_result;
|
||||
|
||||
|
||||
// Format as "current/max"
|
||||
$connections = ($current_connections !== false && $max_connections !== false)
|
||||
? "Current: " . $current_connections . ", Max: " . $max_connections
|
||||
$connections = ($current_connections !== false && $max_connections !== false)
|
||||
? "Current: " . $current_connections . ", Max: " . $max_connections
|
||||
: "N/A";
|
||||
|
||||
|
||||
unset($sql_current, $sql_max);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($connections)) {
|
||||
// Set style color based on thresholds
|
||||
$ratio = $current_connections / $max_connections;
|
||||
$style = ($ratio > 0.9) ? "color: red;" : (($ratio > 0.75) ? "color: orange;" : "");
|
||||
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='" . $row_style[$c] . " hud_text'>" . $text['label-database_connections'] . "</td>\n";
|
||||
echo "<td valign='top' class='" . $row_style[$c] . " hud_text' style='text-align: right; $style'>" . $connections . "</td>\n";
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
; Author: Mark J Crane <markjcrane@fusionpbx.com>
|
||||
; cp /var/www/fusionpbx/app/system/resources/service/debian.service /etc/systemd/system/system_dashboard.service
|
||||
; systemctl daemon-reload
|
||||
; systemctl enable --now system_dashboard
|
||||
|
||||
[Unit]
|
||||
Description=FusionPBX System Dashboard Information Service
|
||||
Wants=network-online.target
|
||||
Requires=network.target local-fs.target
|
||||
;Requires=network.target local-fs.target postgresql.service
|
||||
After=network.target network-online.target local-fs.target
|
||||
;After=network.target network-online.target local-fs.target postgresql.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
;Type=forking
|
||||
PIDFile=/var/run/fusionpbx/system_dashboard.pid
|
||||
WorkingDirectory=/var/www/fusionpbx
|
||||
;Environment="USER=www-data"
|
||||
;Environment="GROUP=www-data"
|
||||
;EnvironmentFile=-/etc/default/fusionpbx
|
||||
ExecStartPre=/bin/mkdir -p /var/run/fusionpbx
|
||||
;ExecStartPre=/bin/chown -R ${USER}:${GROUP} /var/www/fusionpbx
|
||||
ExecStart=/usr/bin/php /var/www/fusionpbx/app/system/resources/service/system.php --no-fork
|
||||
User=www-data
|
||||
Group=www-data
|
||||
TimeoutSec=55s
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Also=
|
||||
@@ -171,6 +171,10 @@
|
||||
$document['title'] = $text['title-dashboard'];
|
||||
require_once "resources/header.php";
|
||||
|
||||
//include websockets
|
||||
$version = md5(file_get_contents(__DIR__, '/resources/javascript/ws_client.js'));
|
||||
echo "<script src='/core/dashboard/resources/javascript/ws_client.js?v=$version'></script>\n";
|
||||
|
||||
//include sortablejs
|
||||
echo "<script src='/resources/sortablejs/sortable.min.js'></script>";
|
||||
|
||||
|
||||
@@ -142,8 +142,6 @@ abstract class base_websocket_system_service extends service implements websocke
|
||||
// Timers can be set by child classes
|
||||
if ($this->timer_expire_time !== null && time() >= $this->timer_expire_time) {
|
||||
$this->on_timer();
|
||||
// Set another timer to fire again
|
||||
$this->set_timer(3);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user