mirror of
https://github.com/fusionpbx/fusionpbx.git
synced 2025-12-30 00:53:50 +00:00
Websockets (#7393)
* Initial commit of websockets * Move app_menu to the active_calls websockets * Fix hangup function * Remove connection wait-state on web socket server so events can process * Add timestamp and debug level to console for service debug output * Remove debug exit * Fix typo for ws_client instead of ws_server * Update app_config.php * Fix typo and remove empty function * Remove call to empty function * Fix the menu to point to the correct location * Remove Logging Class * Rename service file * Rename service file * Fix the in progress browser request * Fix browser reload and implement 'active_calls' default values * Add apply_filter function * Create new permission_filter object * In progress active calls now use filter * Add invalid_uuid_exception class * add event_key_filter to honor user permissions * add and_link and or_link for filters * Fix disconnected subscriber and add filters to honor permissions * Add $key and $value for filter * define a service name * catch throwable instead of exception * Add $key and $value for filter and allow returning null * Update permission checks when loading page * Add apply_filter function to honor subscriber permissions * Add create_filter_chain_for function to honor subscriber permissions * Add apply_filter function to honor subscriber permissions * Add apply_filter function to honor subscriber permissions * create interface to allow filterable payload * create interface to define functions required for websocket services * Pass in service class when creating a service token * Allow key/name and return null for filter * Adjust subscriber exceptions to return the ID of the subscriber * Add event filter to filter chain * Add command line options for ip and port for websockets and switch * update service to use is_a syntax * initial commit of base class for websockets system services * initial commit of the system cpu status service * remove extra line feed * fix path on active_calls * initial proof of concept for cpu status updated by websockets * Allow returning null * Use default settings to set the interval for cpu status broadcast * Improve the CPU percent function for Linux systems * Show more debug information * Allow child processes to re-connect to the web socket service * Fix websockets as plural instead of singular * Add class name list-row * Update active_calls.php * Update active_calls.php * Update websocket_client.js * Update app_config.php * Update app_menu.php * Update debian-websockets.service * Update debian-active_calls.service --------- Co-authored-by: FusionPBX <markjcrane@gmail.com>
This commit is contained in:
55
app/system/resources/classes/bsd_system_information.php
Normal file
55
app/system/resources/classes/bsd_system_information.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2025 Tim Fry <tim@fusionpbx.com>.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Description of bsd_system_information
|
||||
*
|
||||
* @author Tim Fry <tim@fusionpbx.com>
|
||||
*/
|
||||
class bsd_system_information extends system_information {
|
||||
|
||||
public function get_cpu_cores(): int {
|
||||
$result = shell_exec("dmesg | grep -i --max-count 1 CPUs | sed 's/[^0-9]*//g'");
|
||||
$cpu_cores = trim($result);
|
||||
return $cpu_cores;
|
||||
}
|
||||
|
||||
//get the CPU details
|
||||
public function get_cpu_percent(): float {
|
||||
$result = shell_exec('ps -A -o pcpu');
|
||||
$percent_cpu = 0;
|
||||
foreach (explode("\n", $result) as $value) {
|
||||
if (is_numeric($value)) {
|
||||
$percent_cpu = $percent_cpu + $value;
|
||||
}
|
||||
}
|
||||
return $percent_cpu;
|
||||
}
|
||||
|
||||
public function get_uptime() {
|
||||
return shell_exec('uptime');
|
||||
}
|
||||
}
|
||||
78
app/system/resources/classes/linux_system_information.php
Normal file
78
app/system/resources/classes/linux_system_information.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2025 Tim Fry <tim@fusionpbx.com>.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Description of linux_system_information
|
||||
*
|
||||
* @author Tim Fry <tim@fusionpbx.com>
|
||||
*/
|
||||
class linux_system_information extends system_information {
|
||||
|
||||
public function get_cpu_cores(): int {
|
||||
$result = @trim(shell_exec("grep -P '^processor' /proc/cpuinfo"));
|
||||
$cpu_cores = count(explode("\n", $result));
|
||||
return $cpu_cores;
|
||||
}
|
||||
|
||||
//get the CPU details
|
||||
public function get_cpu_percent(): float {
|
||||
$stat1 = file_get_contents('/proc/stat');
|
||||
usleep(500000);
|
||||
$stat2 = file_get_contents('/proc/stat');
|
||||
|
||||
$lines1 = explode("\n", trim($stat1));
|
||||
$lines2 = explode("\n", trim($stat2));
|
||||
|
||||
$percent_cpu = 0;
|
||||
$core_count = 0;
|
||||
|
||||
foreach ($lines1 as $i => $line1) {
|
||||
if (strpos($line1, 'cpu') !== 0 || $line1 === 'cpu') continue;
|
||||
|
||||
$parts1 = preg_split('/\s+/', $line1);
|
||||
$parts2 = preg_split('/\s+/', $lines2[$i]);
|
||||
|
||||
$total1 = array_sum(array_slice($parts1, 1));
|
||||
$total2 = array_sum(array_slice($parts2, 1));
|
||||
|
||||
$idle1 = $parts1[4];
|
||||
$idle2 = $parts2[4];
|
||||
|
||||
$total_delta = $total2 - $total1;
|
||||
$idle_delta = $idle2 - $idle1;
|
||||
|
||||
$cpu_usage = ($total_delta - $idle_delta) / $total_delta * 100;
|
||||
$percent_cpu += $cpu_usage;
|
||||
$core_count++;
|
||||
}
|
||||
|
||||
return round($percent_cpu / $core_count, 2);
|
||||
}
|
||||
|
||||
public function get_uptime() {
|
||||
return shell_exec('uptime');
|
||||
}
|
||||
}
|
||||
134
app/system/resources/classes/system_dashboard_service.php
Normal file
134
app/system/resources/classes/system_dashboard_service.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
class system_dashboard_service extends base_websocket_system_service {
|
||||
|
||||
const PERMISSIONS = [
|
||||
'system_view_cpu',
|
||||
'system_view_backup',
|
||||
'system_view_database',
|
||||
'system_view_hdd',
|
||||
'system_view_info',
|
||||
'system_view_memcache',
|
||||
'system_view_ram',
|
||||
'system_view_support',
|
||||
];
|
||||
|
||||
const CPU_STATUS_TOPIC = 'cpu_status';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var system_information $system_information
|
||||
*/
|
||||
protected static $system_information;
|
||||
|
||||
/**
|
||||
* Settings object
|
||||
* @var settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Integer representing the number of seconds to broadcast the CPU usage
|
||||
* @var int
|
||||
*/
|
||||
private $cpu_status_refresh_interval;
|
||||
|
||||
protected function reload_settings(): void {
|
||||
static::set_system_information();
|
||||
|
||||
// re-read the config file to get any possible changes
|
||||
parent::$config->read();
|
||||
|
||||
// re-connect to the websocket server if required
|
||||
$this->connect_to_ws_server();
|
||||
|
||||
// Connect to the database
|
||||
$database = new database(['config' => parent::$config]);
|
||||
|
||||
// get the interval
|
||||
$this->settings = new settings(['database' => $database]);
|
||||
|
||||
// get the settings from the global defaults
|
||||
$this->cpu_status_refresh_interval = $this->settings->get('dashboard', 'cpu_status_refresh_interval', 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override base_websocket_system_service
|
||||
* @return void
|
||||
*/
|
||||
protected function on_timer(): void {
|
||||
// Send the CPU status
|
||||
$this->on_cpu_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes once
|
||||
* @return void
|
||||
*/
|
||||
protected function register_topics(): void {
|
||||
|
||||
// get the settings from the global defaults
|
||||
$this->reload_settings();
|
||||
|
||||
// Create a system information object that can tell us the cpu regardless of OS
|
||||
self::$system_information = system_information::new();
|
||||
|
||||
// Register the call back to respond to cpu_status requests
|
||||
$this->on_topic('cpu_status', [$this, 'on_cpu_status']);
|
||||
|
||||
// Set a timer
|
||||
$this->set_timer($this->cpu_status_refresh_interval);
|
||||
|
||||
// Notify the user of the interval
|
||||
$this->info("Broadcasting CPU Status every {$this->cpu_status_refresh_interval}s");
|
||||
}
|
||||
|
||||
public function on_cpu_status($message = null): void {
|
||||
// Get the CPU status
|
||||
$cpu_percent = self::$system_information->get_cpu_percent();
|
||||
|
||||
// Send the response
|
||||
$response = new websocket_message();
|
||||
$response
|
||||
->payload([self::CPU_STATUS_TOPIC => $cpu_percent])
|
||||
->service_name(self::get_service_name())
|
||||
->topic(self::CPU_STATUS_TOPIC);
|
||||
|
||||
// Check if we are responding
|
||||
if ($message !== null && $message instanceof websocket_message) {
|
||||
$response->id($message->id());
|
||||
}
|
||||
|
||||
// Log the broadcast
|
||||
$this->debug("Broadcasting CPU percent '$cpu_percent'");
|
||||
|
||||
// Send to subscribers
|
||||
$this->respond($response);
|
||||
}
|
||||
|
||||
public static function get_service_name(): string {
|
||||
return "dashboard.system.information";
|
||||
}
|
||||
|
||||
public static function create_filter_chain_for(subscriber $subscriber): ?filter {
|
||||
// Get the subscriber permissions
|
||||
$permissions = $subscriber->get_permissions();
|
||||
|
||||
// Create a filter
|
||||
$filter = filter_chain::and_link([new permission_filter()]);
|
||||
|
||||
// Match them to create a filter
|
||||
foreach (self::PERMISSIONS as $permission) {
|
||||
if (in_array($permission, $permissions)) {
|
||||
$filter->add_permission($permission);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the filter with user permissions to ensure they can't receive information they shouldn't
|
||||
return $filter;
|
||||
}
|
||||
|
||||
public static function set_system_information(): void {
|
||||
self::$system_information = system_information::new();
|
||||
}
|
||||
}
|
||||
51
app/system/resources/classes/system_information.php
Normal file
51
app/system/resources/classes/system_information.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2025 Tim Fry <tim@fusionpbx.com>.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Description of system_information
|
||||
*
|
||||
* @author Tim Fry <tim@fusionpbx.com>
|
||||
*/
|
||||
abstract class system_information {
|
||||
|
||||
abstract public function get_cpu_cores(): int;
|
||||
abstract public function get_uptime();
|
||||
abstract public function get_cpu_percent(): float;
|
||||
|
||||
public function get_load_average() {
|
||||
return sys_getloadavg();
|
||||
}
|
||||
|
||||
public static function new(): ?system_information {
|
||||
if (stristr(PHP_OS, 'BSD')) {
|
||||
return new bsd_system_information();
|
||||
}
|
||||
if (stristr(PHP_OS, 'Linux')) {
|
||||
return new linux_system_information();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
//includes files
|
||||
//includes files
|
||||
require_once dirname(__DIR__, 4) . "/resources/require.php";
|
||||
|
||||
//check permisions
|
||||
//check permisions
|
||||
require_once "resources/check_auth.php";
|
||||
if (permission_exists('xml_cdr_view')) {
|
||||
//access granted
|
||||
@@ -13,19 +13,19 @@
|
||||
exit;
|
||||
}
|
||||
|
||||
//add multi-lingual support
|
||||
//add multi-lingual support
|
||||
$language = new text;
|
||||
$text = $language->get($_SESSION['domain']['language']['code'], 'app/system');
|
||||
|
||||
//system cpu status
|
||||
//system cpu status
|
||||
echo "<div class='hud_box'>\n";
|
||||
|
||||
//set the row style class names
|
||||
//set the row style class names
|
||||
$c = 0;
|
||||
$row_style["0"] = "row_style0";
|
||||
$row_style["1"] = "row_style1";
|
||||
|
||||
//get the CPU details
|
||||
//get the CPU details
|
||||
if (stristr(PHP_OS, 'BSD') || stristr(PHP_OS, 'Linux')) {
|
||||
|
||||
$result = shell_exec('ps -A -o pcpu');
|
||||
@@ -50,17 +50,88 @@
|
||||
|
||||
}
|
||||
|
||||
//show the content
|
||||
//show the content
|
||||
echo "<div class='hud_content' ".($dashboard_details_state == "disabled" ?: "onclick=\"$('#hud_system_cpu_status_details').slideToggle('fast'); toggle_grid_row_end('".$dashboard_name."')\"").">\n";
|
||||
echo " <span class='hud_title'><a onclick=\"document.location.href='".PROJECT_PATH."/app/system/system.php'\">".$text['label-cpu_usage']."</a></span>\n";
|
||||
|
||||
//add half doughnut chart
|
||||
if (!isset($dashboard_chart_type) || $dashboard_chart_type == "doughnut") {
|
||||
?>
|
||||
$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()]);
|
||||
|
||||
//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 system_cpu_status_chart = new Chart(
|
||||
const authToken = {
|
||||
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 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);
|
||||
client.ws.addEventListener("open", async () => {
|
||||
try {
|
||||
console.log('Connected');
|
||||
console.log('Requesting authentication');
|
||||
await client.request('authentication');
|
||||
client.onEvent(cpuStatusTopic, updateCpuChart);
|
||||
client.request(serviceName, cpuStatusTopic);
|
||||
} catch (err) {
|
||||
console.error("WS setup failed: ", err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
client.ws.addEventListener("close", async () => {
|
||||
console.warn("Websocket Disconnected");
|
||||
});
|
||||
}
|
||||
|
||||
function bindEventHandlers(client) {
|
||||
client.onEvent(cpuStatusTopic, updateCpuChart);
|
||||
}
|
||||
|
||||
function updateCpuChart(payload) {
|
||||
let cpuPercent = 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];
|
||||
|
||||
// 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];
|
||||
} else {
|
||||
chart.data.datasets[0].backgroundColor[0] = dashboard_cpu_usage_chart_main_color[2];
|
||||
}
|
||||
|
||||
chart.options.plugins.chart_number_2.text = cpuPercent;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
window.system_cpu_status_chart = new Chart(
|
||||
document.getElementById('system_cpu_status_chart').getContext('2d'),
|
||||
{
|
||||
type: 'doughnut',
|
||||
@@ -80,7 +151,7 @@
|
||||
'<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_sub_color') ?? '#d4d4d4'); ?>'
|
||||
],
|
||||
borderColor: '<?php echo $settings->get('theme', 'dashboard_chart_border_color'); ?>',
|
||||
borderWidth: '<?php echo $settings->get('theme', 'dashboard_chart_border_width'); ?>',
|
||||
borderWidth: '<?php echo $settings->get('theme', 'dashboard_chart_border_width'); ?>'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
@@ -92,7 +163,7 @@
|
||||
},
|
||||
tooltip: {
|
||||
yAlign: 'bottom',
|
||||
displayColors: false,
|
||||
displayColors: false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -110,9 +181,11 @@
|
||||
}]
|
||||
}
|
||||
);
|
||||
|
||||
connectWebsocket();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
if ($dashboard_chart_type == "number") {
|
||||
echo "<span class='hud_stat'>".round($percent_cpu)."%</span>";
|
||||
}
|
||||
|
||||
173
app/system/resources/dashboard/system_cpu_status_1.php
Normal file
173
app/system/resources/dashboard/system_cpu_status_1.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
//includes files
|
||||
require_once dirname(__DIR__, 4) . "/resources/require.php";
|
||||
|
||||
//check permisions
|
||||
require_once "resources/check_auth.php";
|
||||
if (permission_exists('xml_cdr_view')) {
|
||||
//access granted
|
||||
}
|
||||
else {
|
||||
echo "access denied";
|
||||
exit;
|
||||
}
|
||||
|
||||
//add multi-lingual support
|
||||
$language = new text;
|
||||
$text = $language->get($_SESSION['domain']['language']['code'], 'app/system');
|
||||
|
||||
//system cpu status
|
||||
echo "<div class='hud_box'>\n";
|
||||
|
||||
//set the row style class names
|
||||
$c = 0;
|
||||
$row_style["0"] = "row_style0";
|
||||
$row_style["1"] = "row_style1";
|
||||
|
||||
//get the CPU details
|
||||
if (stristr(PHP_OS, 'BSD') || stristr(PHP_OS, 'Linux')) {
|
||||
|
||||
$result = shell_exec('ps -A -o pcpu');
|
||||
$percent_cpu = 0;
|
||||
foreach (explode("\n", $result) as $value) {
|
||||
if (is_numeric($value)) { $percent_cpu = $percent_cpu + $value; }
|
||||
}
|
||||
if (stristr(PHP_OS, 'BSD')) {
|
||||
$result = shell_exec("dmesg | grep -i --max-count 1 CPUs | sed 's/[^0-9]*//g'");
|
||||
$cpu_cores = trim($result);
|
||||
}
|
||||
if (stristr(PHP_OS, 'Linux')) {
|
||||
$result = @trim(shell_exec("grep -P '^processor' /proc/cpuinfo"));
|
||||
$cpu_cores = count(explode("\n", $result));
|
||||
}
|
||||
if ($cpu_cores > 1) { $percent_cpu = $percent_cpu / $cpu_cores; }
|
||||
$percent_cpu = round($percent_cpu, 2);
|
||||
|
||||
//uptime
|
||||
$result = shell_exec('uptime');
|
||||
$load_average = sys_getloadavg();
|
||||
|
||||
}
|
||||
|
||||
//show the content
|
||||
echo "<div class='hud_content' ".($dashboard_details_state == "disabled" ?: "onclick=\"$('#hud_system_cpu_status_details').slideToggle('fast'); toggle_grid_row_end('".$dashboard_name."')\"").">\n";
|
||||
echo " <span class='hud_title'><a onclick=\"document.location.href='".PROJECT_PATH."/app/system/system.php'\">".$text['label-cpu_usage']."</a></span>\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 system_cpu_status_chart = new Chart(
|
||||
document.getElementById('system_cpu_status_chart').getContext('2d'),
|
||||
{
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: ['<?php echo $percent_cpu; ?>', 100 - '<?php echo $percent_cpu; ?>'],
|
||||
backgroundColor: [
|
||||
<?php
|
||||
if ($percent_cpu <= 60) {
|
||||
echo "'".($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[0] ?? '#03c04a')."',\n";
|
||||
} else if ($percent_cpu <= 80) {
|
||||
echo "'".($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[1] ?? '#ff9933')."',\n";
|
||||
} else if ($percent_cpu > 80) {
|
||||
echo "'".($settings->get('theme', 'dashboard_cpu_usage_chart_main_color')[2] ?? '#ea4c46')."',\n";
|
||||
}
|
||||
?>
|
||||
'<?php echo ($settings->get('theme', 'dashboard_cpu_usage_chart_sub_color') ?? '#d4d4d4'); ?>'
|
||||
],
|
||||
borderColor: '<?php echo $settings->get('theme', 'dashboard_chart_border_color'); ?>',
|
||||
borderWidth: '<?php echo $settings->get('theme', 'dashboard_chart_border_width'); ?>',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
circumference: 180,
|
||||
rotation: 270,
|
||||
plugins: {
|
||||
chart_number_2: {
|
||||
text: '<?php echo round($percent_cpu); ?>'
|
||||
},
|
||||
tooltip: {
|
||||
yAlign: 'bottom',
|
||||
displayColors: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
id: 'chart_number_2',
|
||||
beforeDraw(chart, args, options){
|
||||
const {ctx, chartArea: {top, right, bottom, left, width, height} } = chart;
|
||||
ctx.font = chart_text_size + ' ' + chart_text_font;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '<?php echo $dashboard_number_text_color; ?>';
|
||||
ctx.fillText(options.text + '%', width / 2, top + (height / 2) + 35);
|
||||
ctx.save();
|
||||
}
|
||||
}]
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
if ($dashboard_chart_type == "number") {
|
||||
echo "<span class='hud_stat'>".round($percent_cpu)."%</span>";
|
||||
}
|
||||
echo "</div>\n";
|
||||
|
||||
if ($dashboard_details_state != 'disabled') {
|
||||
echo "<div class='hud_details hud_box' id='hud_system_cpu_status_details'>";
|
||||
echo "<table class='tr_hover' width='100%' cellpadding='0' cellspacing='0' border='0'>\n";
|
||||
echo "<tr>\n";
|
||||
echo "<th class='hud_heading' width='50%'>".$text['label-name']."</th>\n";
|
||||
echo "<th class='hud_heading' style='text-align: right;'>".$text['label-value']."</th>\n";
|
||||
echo "</tr>\n";
|
||||
|
||||
if (PHP_OS == 'FreeBSD' || PHP_OS == 'Linux') {
|
||||
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 "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (!empty($cpu_cores)) {
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-cpu_cores']."</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$cpu_cores."</td>\n";
|
||||
echo "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
}
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-load_average']." (1)</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$load_average[0]."</td>\n";
|
||||
echo "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-load_average']." (5)</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$load_average[1]."</td>\n";
|
||||
echo "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
|
||||
echo "<tr class='tr_link_void'>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text'>".$text['label-load_average']." (15)</td>\n";
|
||||
echo "<td valign='top' class='".$row_style[$c]." hud_text' style='text-align: right;'>".$load_average[2]."</td>\n";
|
||||
echo "</tr>\n";
|
||||
$c = ($c) ? 0 : 1;
|
||||
}
|
||||
|
||||
echo "</table>\n";
|
||||
echo "</div>";
|
||||
//$n++;
|
||||
|
||||
echo "<span class='hud_expander' onclick=\"$('#hud_system_cpu_status_details').slideToggle('fast'); toggle_grid_row_end('".$dashboard_name."')\"><span class='fas fa-ellipsis-h'></span></span>";
|
||||
}
|
||||
echo "</div>\n";
|
||||
|
||||
?>
|
||||
116
app/system/resources/javascript/websocket_client.js
Normal file
116
app/system/resources/javascript/websocket_client.js
Normal file
@@ -0,0 +1,116 @@
|
||||
class ws_client {
|
||||
constructor(url, token) {
|
||||
this.ws = new WebSocket(url);
|
||||
this.ws.addEventListener('message', this._onMessage.bind(this));
|
||||
this._nextId = 1;
|
||||
this._pending = new Map();
|
||||
this._eventHandlers = new Map();
|
||||
// The token is submitted on every request
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
// internal message handler called when event occurs on the socket
|
||||
_onMessage(ev) {
|
||||
let message;
|
||||
let switch_event;
|
||||
try {
|
||||
console.log(ev.data);
|
||||
message = JSON.parse(ev.data);
|
||||
// check for authentication request
|
||||
if (message.status_code === 407) {
|
||||
console.log('Authentication Required');
|
||||
return;
|
||||
}
|
||||
switch_event = message.payload;
|
||||
//console.log('envelope received: ',env);
|
||||
} catch (err) {
|
||||
console.error('Error parsing JSON data:', err);
|
||||
//console.error('Invalid JSON:', ev.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pull out the request_id first
|
||||
const rid = message.request_id ?? null;
|
||||
|
||||
// If this is the response to a pending request
|
||||
if (rid && this._pending.has(rid)) {
|
||||
// Destructure with defaults in case they're missing
|
||||
const {
|
||||
service,
|
||||
topic = '',
|
||||
status = 'ok',
|
||||
code = 200,
|
||||
payload = {}
|
||||
} = message;
|
||||
|
||||
const {resolve, reject} = this._pending.get(rid);
|
||||
this._pending.delete(rid);
|
||||
|
||||
if (status === 'ok' && code >= 200 && code < 300) {
|
||||
resolve({service, topic, payload, code, message});
|
||||
} else {
|
||||
const err = new Error(message || `Error ${code}`);
|
||||
err.code = code;
|
||||
reject(err);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise it's a server‑pushed event…
|
||||
// e.g. env.service === 'event' or env.topic is your event name
|
||||
this._dispatchEvent(message);
|
||||
}
|
||||
|
||||
// Send a request to the websocket server using JSON string
|
||||
request(service, topic = null, payload = {}) {
|
||||
const request_id = String(this._nextId++);
|
||||
const env = {
|
||||
request_id: request_id,
|
||||
service,
|
||||
...(topic !== null ? {topic} : {}),
|
||||
token: this.token,
|
||||
payload: payload
|
||||
};
|
||||
const raw = JSON.stringify(env);
|
||||
this.ws.send(raw);
|
||||
return new Promise((resolve, reject) => {
|
||||
this._pending.set(request_id, {resolve, reject});
|
||||
// TODO: get timeout working to reject if no response in X ms
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(topic) {
|
||||
return this.request('active.calls', topic);
|
||||
}
|
||||
|
||||
unsubscribe(topic) {
|
||||
return this.request('active.calls', topic);
|
||||
}
|
||||
|
||||
// register a callback for server-pushes
|
||||
onEvent(topic, handler) {
|
||||
console.log('registering event listener for ' + topic);
|
||||
if (!this._eventHandlers.has(topic)) {
|
||||
this._eventHandlers.set(topic, []);
|
||||
}
|
||||
this._eventHandlers.get(topic).push(handler);
|
||||
}
|
||||
/**
|
||||
* Dispatch a server‑push event envelope to all registered handlers.
|
||||
* @param {object} env
|
||||
*/
|
||||
_dispatchEvent(message) {
|
||||
const service = message.service_name;
|
||||
const topic = message.topic;
|
||||
const handlers = this._eventHandlers.get(topic) || [];
|
||||
for (const fn of handlers) {
|
||||
try {
|
||||
fn(message.payload);
|
||||
} catch (err) {
|
||||
console.error(`Error in handler for "${topic}":`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
47
app/system/resources/services/system.php
Normal file
47
app/system/resources/services/system.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* FusionPBX
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FusionPBX
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mark J Crane <markjcrane@fusionpbx.com>
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008-2025
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mark J Crane <markjcrane@fusionpbx.com>
|
||||
* Tim Fry <tim@fusionpbx.com>
|
||||
*/
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.1.0', '<')) {
|
||||
die("This script requires PHP 7.1.0 or higher. You are running " . PHP_VERSION . "\n");
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 4) . '/resources/require.php';
|
||||
|
||||
try {
|
||||
|
||||
// Create the service
|
||||
$system_dashboard_service = system_dashboard_service::create();
|
||||
|
||||
// Exit using whatever status run returns
|
||||
exit($system_dashboard_service->run());
|
||||
|
||||
} catch (Throwable $ex) {
|
||||
echo "Error occurred in " . $ex->getFile() . ' (' . $ex->getLine() . '):' . $ex->getMessage();
|
||||
// Exit with error code
|
||||
exit($ex->getCode());
|
||||
}
|
||||
Reference in New Issue
Block a user