From 76744aaaf56562781d03cfb3a840c4bfce92f2e0 Mon Sep 17 00:00:00 2001 From: markjcrane Date: Thu, 30 Apr 2026 07:46:04 -0600 Subject: [PATCH] Add a new feature to manage services --- core/services/app_config.php | 94 +++ core/services/app_languages.php | 420 +++++++++++ core/services/app_menu.php | 19 + core/services/resources/classes/services.php | 736 +++++++++++++++++++ core/services/service_edit.php | 281 +++++++ core/services/services.php | 293 ++++++++ core/upgrade/upgrade.php | 12 +- 7 files changed, 1852 insertions(+), 3 deletions(-) create mode 100644 core/services/app_config.php create mode 100644 core/services/app_languages.php create mode 100644 core/services/app_menu.php create mode 100644 core/services/resources/classes/services.php create mode 100644 core/services/service_edit.php create mode 100644 core/services/services.php diff --git a/core/services/app_config.php b/core/services/app_config.php new file mode 100644 index 000000000..c2b069008 --- /dev/null +++ b/core/services/app_config.php @@ -0,0 +1,94 @@ + \ No newline at end of file diff --git a/core/services/app_menu.php b/core/services/app_menu.php new file mode 100644 index 000000000..1687e7ab1 --- /dev/null +++ b/core/services/app_menu.php @@ -0,0 +1,19 @@ + diff --git a/core/services/resources/classes/services.php b/core/services/resources/classes/services.php new file mode 100644 index 000000000..c0797ec44 --- /dev/null +++ b/core/services/resources/classes/services.php @@ -0,0 +1,736 @@ + + Portions created by the Initial Developer are Copyright (C) 2026 + the Initial Developer. All Rights Reserved. + + Contributor(s): + Mark J Crane +*/ + +/** + * services class + * + * @method null delete + * @method null toggle + * @method null copy + */ +class services { + + /** + * declare constant variables + */ + const app_name = 'services'; + const app_uuid = '540c3ec2-4f0c-467f-a09d-d644439c96f2'; + + /** + * Set in the constructor. Must be a database object and cannot be null. + * + * @var database Database Object + */ + private $database; + + /** + * declare private variables + */ + private $name; + private $table; + private $toggle_field; + private $toggle_values; + private $description_field; + private $location; + + /** + * Constructor for the class. + * + * This method initializes the object with setting_array and session data. + * + * @param array $setting_array An optional array of settings to override default values. Defaults to []. + */ + public function __construct(array $setting_array = []) { + // Set objects + $this->database = $setting_array['database'] ?? database::new(); + + // Assign the variables + $this->app_name = 'services'; + $this->app_uuid = '540c3ec2-4f0c-467f-a09d-d644439c96f2'; + $this->name = 'service'; + $this->table = 'services'; + $this->toggle_field = 'service_enabled'; + $this->toggle_values = ['true','false']; + $this->description_field = 'service_description'; + $this->location = 'services.php'; + } + + /** + * called when there are no references to a particular object + * unset the variables used in the class + */ + public function __destruct() { + foreach ($this as $key => $value) { + unset($this->$key); + } + } + + /** + * Deletes one or multiple records. + * + * @param array $records An array of record IDs to delete, where each ID is an associative array + * containing 'uuid' and 'checked' keys. The 'checked' value indicates + * whether the corresponding checkbox was checked for deletion. + * + * @return void No return value; this method modifies the database state and sets a message. + */ + public function delete($records) { + // Permission not found + if (permission_exists($this->name.'_delete')) { + return; + } + + // Add multi-lingual support + $language = new text; + $text = $language->get(); + + // Validate the token + $token = new token; + if (!$token->validate($_SERVER['PHP_SELF'])) { + message::add($text['message-invalid_token'],'negative'); + header('Location: '.$this->location); + exit; + } + + // Delete multiple records + if (is_array($records) && @sizeof($records) != 0) { + // Build the delete array + $x = 0; + foreach ($records as $record) { + // Add to the array + if ($record['checked'] == 'true' && is_uuid($record['uuid'])) { + $array[$this->table][$x][$this->name.'_uuid'] = $record['uuid']; + } + + // Increment the id + $x++; + } + + // Delete the checked rows + if (is_array($array) && @sizeof($array) != 0) { + //execute delete + $this->database->delete($array); + unset($array); + + // Set the message + message::add($text['message-delete']); + } + unset($records); + } + } + + /** + * Toggles the state of one or more records. + * + * @param array $records An array of record IDs to delete, where each ID is an associative array + * containing 'uuid' and 'checked' keys. The 'checked' value indicates + * whether the corresponding checkbox was checked for deletion. + * + * @return void No return value; this method modifies the database state and sets a message. + */ + public function toggle($records) { + // Permission not found + if (permission_exists($this->name.'_edit')) { + return; + } + + // Add multi-lingual support + $language = new text; + $text = $language->get(); + + // Validate the token + $token = new token; + if (!$token->validate($_SERVER['PHP_SELF'])) { + message::add($text['message-invalid_token'],'negative'); + header('Location: '.$this->location); + exit; + } + + // Toggle the checked records + if (is_array($records) && @sizeof($records) != 0) { + // Get current toggle state + foreach($records as $record) { + if ($record['checked'] == 'true' && is_uuid($record['uuid'])) { + $uuids[] = "'".$record['uuid']."'"; + } + } + if (is_array($uuids) && @sizeof($uuids) != 0) { + $sql = "select ".$this->name."_uuid as uuid, ".$this->toggle_field." as toggle from v_".$this->table." "; + $sql .= "where ".$this->name."_uuid in (".implode(', ', $uuids).") "; + $database = new database; + $rows = $database->select($sql, $parameters, 'all'); + if (is_array($rows) && @sizeof($rows) != 0) { + foreach ($rows as $row) { + $states[$row['uuid']] = $row['toggle']; + } + } + unset($sql, $parameters, $rows, $row); + } + + // Build the update array + $x = 0; + foreach($states as $uuid => $state) { + // Create the array + $array[$this->table][$x][$this->name.'_uuid'] = $uuid; + $array[$this->table][$x][$this->toggle_field] = $state == $this->toggle_values[0] ? $this->toggle_values[1] : $this->toggle_values[0]; + + // Increment the id + $x++; + } + + // Save the changes + if (is_array($array) && @sizeof($array) != 0) { + // Save the array + $this->database->save($array); + unset($array); + + // Set the message + message::add($text['message-toggle']); + } + unset($records, $states); + } + } + + /** + * Get the list of services + * + * This function iterates through all service files found in the application's + * core and app directories, and builds an array of the services + * + * @return array Return a list of services with name + */ + public function get_services($details = false, $source = 'database') { + + if ($source == 'files') { + // Get the list of services + $core_files = glob(dirname(__DIR__, 4) . "/core/*/resources/service/*.service"); + $app_files = glob(dirname(__DIR__, 4) . "/app/*/resources/service/*.service"); + $service_files = array_merge($core_files, $app_files); + + // Build the services array + $services = []; + if (stristr(PHP_OS, 'Linux')) { + $i = 0; + foreach($service_files as $file) { + // Get the service name + $service_name = $this->find_service_name($file); + + // Get the service status + if ($details) { + $service_status = $this->is_running($service_name); + } + + // Build the services array + $services[$i]['name'] = $service_name; + $services[$i]['file'] = $file; + + // Add the service status to the array + if ($details) { + $services[$i]['pid'] = $service_status['pid']; + $services[$i]['status'] = $service_status['status']; + $services[$i]['etime'] = $service_status['etime']; + } + + // Increment + $i++; + } + } + } + + if ($source == 'database') { + // Get the list + $sql = "select "; + $sql .= " service_uuid, "; + $sql .= " service_name, "; + $sql .= " service_category, "; + $sql .= " service_file, "; + $sql .= " cast(service_enabled as text), "; + $sql .= " service_description "; + $sql .= "from v_services "; + $sql .= "order by service_name asc"; + $database_services = $this->database->select($sql, $parameters ?? null, 'all'); + unset($sql, $parameters); + + $services = []; + if (stristr(PHP_OS, 'Linux')) { + $i = 0; + foreach($database_services as $row) { + // Get the service status + if ($details) { + $service_status = $this->is_running($row['service_name']); + } + + // Build the services array + $services[$i]['name'] = $row['service_name']; + $services[$i]['file'] = $row['service_file']; + $services[$i]['enabled'] = $row['service_enabled']; + //$services[$i]['file'] = $file; + + // Add the service status to the array + if ($details) { + $services[$i]['pid'] = $service_status['pid']; + $services[$i]['status'] = $service_status['status']; + $services[$i]['etime'] = $service_status['etime']; + } + + // Increment + $i++; + } + } + } + + // Return the service array + return $services; + } + + /** + * Add missing services into the database + * + * This function adds missing services + * into the services table in the database + * + * @return void No return value; this method modifies the database. + */ + public function add_missing() { + // Get the list of services + $service_array = $this->get_services(false, 'files'); + + // Service mapped to the category + $service_map['api'] = 'system'; + $service_map['call_center_callbacks'] = 'switch'; + $service_map['campaign_logs'] = 'switch'; + $service_map['campaign_queue'] = 'switch'; + $service_map['websockets'] = 'websockets'; + $service_map['active_calls'] = 'websockets'; + $service_map['active_conferences'] = 'websockets'; + $service_map['maintenance_service'] = 'system'; + $service_map['operator_panel'] = 'websockets'; + $service_map['system_status'] = 'websockets'; + $service_map['message_events'] = 'system'; + $service_map['message_queue'] = 'system'; + $service_map['transcribe_queue'] = 'queue'; + + // Get the list + $sql = "select "; + $sql .= " service_uuid, "; + $sql .= " service_name, "; + $sql .= " service_category, "; + $sql .= " cast(service_enabled as text), "; + $sql .= " service_description "; + $sql .= "from v_services "; + $sql .= "order by service_name asc"; + $database_services = $this->database->select($sql, $parameters ?? null, 'all'); + unset($sql, $parameters); + + // Create an array to store service names from the database + $service_names = array_column($database_services, 'service_name'); + + // Add services that are not in the database + $i = 0; + $array = []; + foreach ($service_array as $service) { + // Sanitize the service name + $service_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $service['name']); + + // Built the array to save to the database + if (!is_array($service_array) || !in_array($service_name, $service_names)) { + // Get the category + $service_category = ''; + if (class_exists($service_name)) { + if (method_exists($service_name, 'get_category')) { + $service_category = $service_name::get_category(); + } + } + + // Alternate method to get the category + if (empty($service_category)) { + $service_category = $service_map[$service_name]; + } + + // Prepare the array + $array['services'][$i]['service_uuid'] = uuid(); + $array['services'][$i]['service_name'] = $service_name; + $array['services'][$i]['service_category'] = $service_category; + $array['services'][$i]['service_file'] = $service['file']; + $array['services'][$i]['service_enabled'] = 'true'; + $i++; + } + } + + // Add missing services into the database + if (!empty($array)) { + // Set temporary permissions + $p = permissions::new(); + $p->add('service_add', 'temp'); + + // Save to the database + $this->database->save($array); + unset($array); + + // Remove temporary permissions + $p->delete('service_add', 'temp'); + } + } + + /** + * Upgrade services by copying and enabling them. + * + * This function iterates through all service files found in the application's + * core and app directories, copies each one to /etc/systemd/system, reloads + * the daemon, and enables the service. + * + * @return void No return value; + */ + public function upgrade($name = 'all') { + // Get the list of services + $services = $this->get_services(); + + // Update the services + foreach($services as $service) { + // Skip upgrade if not enabled + if ($service['enabled'] != 'true') { + continue; + } + + // Skip if specific service requested and not this one + if ($name !== 'all' && $service['name'] !== $name) { + continue; + } + + // Validate the service is in the $services array + if ($name !== 'all') { + $service_found = false; + foreach ($services as $service) { + if ($service['name'] === $name) { + $service_found = true; + break; + } + } + if (!$service_found) { + return; + } + } + + // Sanitize the service name + $service_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $service['name']); + + // Output to the console + if (PHP_SAPI === 'cli') { + echo " ".$service_name."\n"; + } + + // Upgrade the service + if (stristr(PHP_OS, 'Linux')) { + system("cp " . escapeshellarg($service['file']) . " /etc/systemd/system/" . escapeshellarg($service['name']) . ".service"); + system("systemctl daemon-reload"); + system("systemctl enable " . escapeshellarg($service['name'])); + system("systemctl start " . escapeshellarg($service['name'])); + } + if (stristr(PHP_OS, 'BSD')) { + if ($service['enabled'] == 'true') { + system("service ".escapeshellarg($service['name']). "start"); + } + } + } + + } + + /** + * Starts running services by name. + * + * This function iterates over all service files, extracts the service names, + * and starts each service. + * + * @param string $name Service name to start, or 'all' to start all services + * @return void + */ + public function start($name = 'all') { + // Get the list of services + $services = $this->get_services(); + + // Validate the service is in the services array + if ($name !== 'all') { + $service_found = false; + foreach ($services as $service) { + if ($service['name'] === $name) { + $service_found = true; + break; + } + } + if (!$service_found) { + return; + } + } + + // Stop all services if equal to all stop one service if the name is not equal to all + foreach($services as $service) { + // Skip start if not enabled + if ($service['enabled'] !== 'true') { + continue; + } + + // Skip if specific service requested and not this one + if ($name !== 'all' && $service['name'] !== $name) { + continue; + } + + // Sanitize the service name + $service_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $service['name']); + + // Output to the console + if (PHP_SAPI === 'cli') { + echo " ".$service_name."\n"; + } + + // Run the start command + if (stristr(PHP_OS, 'Linux')) { + system("systemctl start ".escapeshellarg($service_name)); + } + if (stristr(PHP_OS, 'BSD')) { + system("service ".escapeshellarg($service_name). "start"); + } + } + } + + /** + * Restarts all services + * + * This function restarts all core and app services. + * + * @return void No return value; + */ + public function restart($name = 'all') { + // Get the list of services + $services = $this->get_services(); + + // Validate the service is in the services array + if ($name !== 'all') { + $service_found = false; + foreach ($services as $service) { + if ($service['name'] === $name) { + $service_found = true; + break; + } + } + if (!$service_found) { + return; + } + } + + // Restart all services + foreach($services as $service) { + // Skip restart if not enabled + if ($service['enabled'] !== 'true') { + continue; + } + + // Skip if specific service requested and not this one + if ($name !== 'all' && $service['name'] !== $name) { + continue; + } + + // Sanitize the service name + $service_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $service['name']); + + // Output to the console + if (PHP_SAPI === 'cli') { + echo " ".$service_name."\n"; + } + + // Run the restart command + if (stristr(PHP_OS, 'Linux')) { + system("systemctl restart ".escapeshellarg($service_name)); + } + if (stristr(PHP_OS, 'BSD')) { + system("service ".escapeshellarg(service_name). "restart"); + } + } + } + + /** + * Stops running services by name. + * + * This function iterates over all service files, extracts the service names, + * and stops each service. + * + * @return void No return value; + */ + public function stop($name = 'all') { + // Get the list of services + $services = $this->get_services(); + + // Validate the service is in the $services array + if ($name !== 'all') { + $service_found = false; + foreach ($services as $service) { + if ($service['name'] === $name) { + $service_found = true; + break; + } + } + if (!$service_found) { + return; + } + } + + // Stop all services + foreach($services as $service) { + // Skip if specific service requested and not this one + if ($name !== 'all' && $service['name'] !== $name) { + continue; + } + + // Sanitize the service name + $service_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $service['name']); + + // Output to the console + if (PHP_SAPI === 'cli') { + echo " ".$service_name."\n"; + } + + // Run the stop command + if (stristr(PHP_OS, 'Linux')) { + system("systemctl stop ".escapeshellarg($service['name'])); + } + if (stristr(PHP_OS, 'BSD')) { + system("service ".escapeshellarg($service['name']). "stop"); + } + } + } + + /** + * Finds the service name in an INI file from a given file. + * + * @param string $file The fully qualified path and file containing the ExecStart command. + * + * @return string|null The service name if found, otherwise an empty string. + */ + public function find_service_name(string $file) { + $parsed = parse_ini_file($file); + $exec_cmd = $parsed['ExecStart']; + $parts = explode(' ', $exec_cmd); + $php_file = $parts[1] ?? ''; + if (!empty($php_file)) { + $path_info = pathinfo($php_file); + return $path_info['filename']; + } + return ''; + } + + /** + * Checks whether the current user is the root user or not. + * + * @return bool True if the current user has root privileges, false otherwise. + */ + public function is_root(): bool { + return posix_getuid() === 0; + } + + /** + * Retrieves the name of a PHP class from an ExecStart directive in a service file. + * + * @param string $file Path to the service file. + * + * @return string The name of the PHP class, or empty string if not found. + */ + public function get_class_name(string $file) { + if (!file_exists($file)) { + return ''; + } + $parsed = parse_ini_file($file); + $exec_cmd = $parsed['ExecStart'] ?? ''; + $parts = explode(' ', $exec_cmd ?? ''); + $php_file = $parts[1] ?? ''; + if (!empty($php_file)) { + return $php_file; + } + return ''; + } + + /** + * Checks if a process with the given name is currently running. + * + * @param string $name The name of the process to check for. + * + * @return array An array containing information about the process's status, + * including whether it's status, its PID, and how long it's been running. + */ + public function is_running(string $name) { + $name = escapeshellarg($name); + $command = "ps -aux | grep $name | grep -v grep | awk '{print \$2}' | head -n 1"; + $pid = trim(shell_exec($command ?? '')); + if ($pid && is_numeric($pid)) { + $command = "ps -p $pid -o etime= | tr -d '\n'"; + $etime = trim(shell_exec($command) ?? ''); + return ['status' => true, 'pid' => $pid, 'etime' => $etime]; + } + return ['status' => false, 'pid' => $pid, 'etime' => $etime]; + } + + /** + * Formats a time duration string into a human-readable format. + * + * The input string can be in one of the following formats: + * - dd-hh:mm:ss + * - hh:mm:ss + * - mm:ss + * - seconds (no units) + * + * If the input string is empty or invalid, an empty string will be returned. + * + * @param string $etime Time duration string to format. + * + * @return string Formatted time duration string in human-readable format. + */ + public function format_etime($etime) { + // Format: [[dd-]hh:]mm:ss + if (empty($etime)) return '-'; + + $days = 0; $hours = 0; $minutes = 0; $seconds = 0; + + // Handle dd-hh:mm:ss + if (preg_match('/^(\d+)-(\d+):(\d+):(\d+)$/', $etime, $m)) { + [$_, $days, $hours, $minutes, $seconds] = $m; + } + // Handle hh:mm:ss + elseif (preg_match('/^(\d+):(\d+):(\d+)$/', $etime, $m)) { + [$_, $hours, $minutes, $seconds] = $m; + } + // Handle mm:ss + elseif (preg_match('/^(\d+):(\d+)$/', $etime, $m)) { + [$_, $minutes, $seconds] = $m; + } + + $out = []; + if ($days) $out[] = $days . 'd'; + if ($hours) $out[] = $hours . 'h'; + if ($minutes) $out[] = $minutes . 'm'; + if ($seconds || empty($out)) $out[] = $seconds . 's'; + + return implode(' ', $out); + } + +} diff --git a/core/services/service_edit.php b/core/services/service_edit.php new file mode 100644 index 000000000..883e4cc7a --- /dev/null +++ b/core/services/service_edit.php @@ -0,0 +1,281 @@ + + Portions created by the Initial Developer are Copyright (C) 2018 - 2020 + the Initial Developer. All Rights Reserved. +*/ + + +// includes files + require_once dirname(__DIR__, 2) . "/resources/require.php"; + require_once "resources/check_auth.php"; + +// check permissions + if (!(permission_exists('service_add') || permission_exists('service_edit'))) { + echo "access denied"; + exit; + } + +// add multi-lingual support + $language = new text; + $text = $language->get(); + +// add the settings object + $settings = new settings(["domain_uuid" => $_SESSION['domain_uuid'], "user_uuid" => $_SESSION['user_uuid']]); + +// set from session variables + $button_icon_back = $settings->get('theme', 'button_icon_back', ''); + $button_icon_copy = $settings->get('theme', 'button_icon_copy', ''); + $button_icon_delete = $settings->get('theme', 'button_icon_delete', ''); + $button_icon_save = $settings->get('theme', 'button_icon_save', ''); + $input_toggle_style = $settings->get('theme', 'input_toggle_style', 'switch round'); + +// action add or update + if (is_uuid($_REQUEST["id"])) { + $action = "update"; + $service_uuid = $_REQUEST["id"]; + $id = $_REQUEST["id"]; + } + else { + $action = "add"; + } + +// get http post variables and set them to php variables + if (!empty($_POST)) { + $service_name = $_POST["service_name"]; + $service_category = $_POST["service_category"]; + $service_enabled = $_POST["service_enabled"]; + $service_description = $_POST["service_description"]; + } + +// process the data and save it to the database + if (!empty($_POST) && empty($_POST["persistformvar"])) { + + // validate the token + $token = new token; + if (!$token->validate($_SERVER['PHP_SELF'])) { + message::add($text['message-invalid_token'],'negative'); + header('Location: services.php'); + exit; + } + + // process the http post data by submitted action + if ($_POST['action'] != '' && strlen($_POST['action']) > 0) { + + // prepare the array(s) + switch ($_POST['action']) { + case 'delete': + if (permission_exists('service_delete')) { + $obj = new services; + $obj->delete($array); + } + break; + case 'toggle': + if (permission_exists('service_update')) { + $obj = new services; + $obj->toggle($array); + } + break; + } + + // redirect the user + if (in_array($_POST['action'], array('copy', 'delete', 'toggle'))) { + header('Location: service_edit.php?id='.$id); + exit; + } + } + + // check for all required data + $msg = ''; + if (strlen($service_name) == 0) { $msg .= $text['message-required']." ".$text['label-service_name']."
\n"; } + if (strlen($service_category) == 0) { $msg .= $text['message-required']." ".$text['label-service_category']."
\n"; } + if (strlen($service_enabled) == 0) { $msg .= $text['message-required']." ".$text['label-service_enabled']."
\n"; } + // if (strlen($service_description) == 0) { $msg .= $text['message-required']." ".$text['label-service_description']."
\n"; } + if (strlen($msg) > 0 && strlen($_POST["persistformvar"]) == 0) { + require_once "resources/header.php"; + require_once "resources/persist_form_var.php"; + echo "
\n"; + echo "
\n"; + echo $msg."
"; + echo "
\n"; + persistformvar($_POST); + echo "
\n"; + require_once "resources/footer.php"; + return; + } + + // add the service_uuid + if (!is_uuid($_POST["service_uuid"])) { + $service_uuid = uuid(); + } + + // prepare the array + $array['services'][0]['service_uuid'] = $service_uuid; + $array['services'][0]['service_name'] = $service_name; + $array['services'][0]['service_category'] = $service_category; + $array['services'][0]['service_enabled'] = $service_enabled; + $array['services'][0]['service_description'] = $service_description; + + // save the data + $database->save($array); + + // redirect the user + if (isset($action)) { + if ($action == "add") { + $_SESSION["message"] = $text['message-add']; + } + if ($action == "update") { + $_SESSION["message"] = $text['message-update']; + } + // header('Location: services.php'); + header('Location: service_edit.php?id='.urlencode($service_uuid)); + return; + } + } + +// pre-populate the form + if (is_array($_GET) && $_POST["persistformvar"] != "true") { + $sql = "select "; + $sql .= " service_uuid, "; + $sql .= " service_name, "; + $sql .= " service_category, "; + $sql .= " service_enabled , "; + $sql .= " service_description "; + $sql .= "from v_services "; + $sql .= "where service_uuid = :service_uuid "; + $parameters['service_uuid'] = $service_uuid; + $row = $database->select($sql, $parameters, 'row'); + if (is_array($row) && @sizeof($row) != 0) { + $service_name = $row["service_name"]; + $service_category = $row["service_category"]; + $service_enabled = $row["service_enabled"]; + $service_description = $row["service_description"]; + } + unset($sql, $parameters, $row); + } + +// create token + $object = new token; + $token = $object->create($_SERVER['PHP_SELF']); + +// show the header + $document['title'] = $text['title-service']; + require_once "resources/header.php"; + +// show the content + echo "
\n"; + echo "\n"; + + echo "
\n"; + echo "
".$text['title-service']."
\n"; + echo "
\n"; + echo button::create(['type'=>'button','label'=>$text['button-back'],'icon'=>$button_icon_back,'id'=>'btn_back','collapse'=>'hide-xs','style'=>'margin-right: 15px;','link'=>'services.php']); + if ($action == 'update') { + if (permission_exists('_add')) { + echo button::create(['type'=>'button','label'=>$text['button-copy'],'icon'=>$button_icon_copy,'id'=>'btn_copy','name'=>'btn_copy','style'=>'display: none;','onclick'=>"modal_open('modal-copy','btn_copy');"]); + } + if (permission_exists('_delete')) { + echo button::create(['type'=>'button','label'=>$text['button-delete'],'icon'=>$button_icon_delete,'id'=>'btn_delete','name'=>'btn_delete','style'=>'display: none; margin-right: 15px;','onclick'=>"modal_open('modal-delete','btn_delete');"]); + } + } + echo button::create(['type'=>'submit','label'=>$text['button-save'],'icon'=>$button_icon_save,'id'=>'btn_save','collapse'=>'hide-xs']); + echo "
\n"; + echo "
\n"; + echo "
\n"; + + echo $text['title_description-services']."\n"; + echo "

\n"; + + if ($action == 'update') { + if (permission_exists('service_add')) { + echo modal::create(['id'=>'modal-copy','type'=>'copy','actions'=>button::create(['type'=>'submit','label'=>$text['button-continue'],'icon'=>'check','id'=>'btn_copy','style'=>'float: right; margin-left: 15px;','collapse'=>'never','name'=>'action','value'=>'copy','onclick'=>"modal_close();"])]); + } + if (permission_exists('service_delete')) { + echo modal::create(['id'=>'modal-delete','type'=>'delete','actions'=>button::create(['type'=>'submit','label'=>$text['button-continue'],'icon'=>'check','id'=>'btn_delete','style'=>'float: right; margin-left: 15px;','collapse'=>'never','name'=>'action','value'=>'delete','onclick'=>"modal_close();"])]); + } + } + + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + echo "
\n"; + echo " ".$text['label-service_name']."\n"; + echo "\n"; + echo " \n"; + echo "
\n"; + echo $text['description-service_name']."\n"; + echo "
\n"; + echo " ".$text['label-service_category']."\n"; + echo "\n"; + echo " \n"; + echo "
\n"; + echo $text['description-service_category']."\n"; + echo "
\n"; + echo " ".$text['label-service_enabled']."\n"; + echo "\n"; + if ($input_toggle_style_switch) { + echo " \n"; + } + echo " \n"; + if ($input_toggle_style_switch) { + echo " \n"; + echo " \n"; + } + echo "
\n"; + echo $text['description-service_enabled']."\n"; + echo "
\n"; + echo " ".$text['label-service_description']."\n"; + echo "\n"; + echo " \n"; + echo "
\n"; + echo $text['description-service_description']."\n"; + echo "
"; + echo "

"; + + echo "\n"; + + echo "
"; + +// include the footer + require_once "resources/footer.php"; + +?> \ No newline at end of file diff --git a/core/services/services.php b/core/services/services.php new file mode 100644 index 000000000..146717bd0 --- /dev/null +++ b/core/services/services.php @@ -0,0 +1,293 @@ + + Portions created by the Initial Developer are Copyright (C) 2025 + the Initial Developer. All Rights Reserved. +*/ + +// includes files + require_once dirname(__DIR__, 2) . "/resources/require.php"; + require_once "resources/check_auth.php"; + +// check permissions + if (!permission_exists('service_view')) { + echo "access denied"; + exit; + } + +// add multi-lingual support + $language = new text; + $text = $language->get(); + +// add the settings object + $settings = new settings(["domain_uuid" => $_SESSION['domain_uuid'], "user_uuid" => $_SESSION['user_uuid']]); + +// set from session variables + $list_row_edit_button = $settings->get('theme', 'list_row_edit_button', 'false'); + +// get the http post data + if (!empty($_POST['services']) && is_array($_POST['services'])) { + $action = $_POST['action']; + $search = $_POST['search']; + $services = $_POST['services']; + } + +// process the http post data by action + if (!empty($action) && !empty($services) && is_array($services) && @sizeof($services) != 0) { + + // validate the token + $token = new token; + if (!$token->validate($_SERVER['PHP_SELF'])) { + message::add($text['message-invalid_token'],'negative'); + header('Location: services.php'); + exit; + } + + // prepare the array + if (!empty($services)) { + foreach ($services as $row) { + $array['services'][$x]['checked'] = $row['checked']; + $array['services'][$x]['service_uuid'] = $row['service_uuid']; + $array['services'][$x]['service_enabled'] = $row['service_enabled']; + $x++; + } + } + + // prepare the database object + $database->app_name = 'services'; + $database->app_uuid = '540c3ec2-4f0c-467f-a09d-d644439c96f2'; + + // send the array to the database class + switch ($action) { + case 'toggle': + if (permission_exists('service_edit')) { + $database->toggle($array); + // $obj = new services; + // $obj->toggle($services); + } + break; + case 'delete': + if (permission_exists('service_delete')) { + $database->delete($array); + // $obj = new services; + // $obj->delete($services); + } + break; + } + + // redirect the user + header('Location: services.php'.($search != '' ? '?search='.urlencode($search) : null)); + exit; + } + +// get order and order by + $order_by = $_GET["order_by"] ?? null; + $order = $_GET["order"] ?? null; + +// define the variables + $search = ''; + $show = ''; + $list_row_url = ''; + +// add the search variable + if (!empty($_GET["search"])) { + $search = strtolower($_GET["search"]); + } + +// add the show variable + if (!empty($_GET["show"])) { + $show = $_GET["show"]; + } + +// get the status of the services + $service_object = new services; + $service_object->add_missing(); + +// get the status of the services + $service_array = $service_object->get_services('true'); + +// get the count + $sql = "select count(service_uuid) "; + $sql .= "from v_services "; + $sql .= "where true "; + if (!empty($search)) { + $sql .= "and ( "; + $sql .= " lower(service_name) like :search "; + $sql .= " or lower(service_category) like :search "; + $sql .= " or lower(service_description) like :search "; + $sql .= ") "; + $parameters['search'] = '%'.$search.'%'; + } + $num_rows = $database->select($sql, $parameters ?? null, 'column'); + unset($sql, $parameters); + +// get the list + $sql = "select "; + $sql .= "service_uuid, "; + $sql .= "service_name, "; + $sql .= "service_category, "; + $sql .= "cast(service_enabled as text), "; + $sql .= "service_description "; + $sql .= "from v_services "; + $sql .= "where true "; + if (!empty($search)) { + $sql .= "and ( "; + $sql .= " lower(service_name) like :search "; + $sql .= " or lower(service_category) like :search "; + $sql .= " or lower(service_description) like :search "; + $sql .= ") "; + $parameters['search'] = '%'.$search.'%'; + } + $sql .= order_by($order_by, $order, 'service_name', 'asc'); + $sql .= limit_offset($rows_per_page, $offset); + $services = $database->select($sql, $parameters ?? null, 'all'); + unset($sql, $parameters); + +// add the service details to the services array + foreach($services as $i => $service) { + foreach ($service_array as $row) { + if ($service['service_name'] == $row['name']) { + $services[$i]['service_status'] = $row['status'] ? 'true' : 'false'; + $services[$i]['service_pid'] = $row['pid']; + $services[$i]['service_etime'] = $row['etime']; + break; + } + } + } + +// create token + $object = new token; + $token = $object->create($_SERVER['PHP_SELF']); + +// additional includes + $document['title'] = $text['title-services']; + require_once "resources/header.php"; + +// show the content + echo "
\n"; + echo "
".$text['title-services']."
".$num_rows."
\n"; + echo "
\n"; + if (permission_exists('service_edit') && $services) { + echo button::create(['type'=>'button','label'=>$text['button-toggle'],'icon'=>$_SESSION['theme']['button_icon_toggle'],'id'=>'btn_toggle','name'=>'btn_toggle','style'=>'display:none;','onclick'=>"modal_open('modal-toggle','btn_toggle');"]); + } + if (permission_exists('service_delete') && $services) { + echo button::create(['type'=>'button','label'=>$text['button-delete'],'icon'=>$_SESSION['theme']['button_icon_delete'],'id'=>'btn_delete','name'=>'btn_delete','style'=>'display:none;','onclick'=>"modal_open('modal-delete','btn_delete');"]); + } + echo "\n"; + echo "
\n"; + echo "
\n"; + echo "
\n"; + + if (permission_exists('service_add') && $services) { + echo modal::create(['id'=>'modal-copy','type'=>'copy','actions'=>button::create(['type'=>'button','label'=>$text['button-continue'],'icon'=>'check','id'=>'btn_copy','style'=>'float: right; margin-left: 15px;','collapse'=>'never','onclick'=>"modal_close(); list_action_set('copy'); list_form_submit('form_list');"])]); + } + if (permission_exists('service_edit') && $services) { + echo modal::create(['id'=>'modal-toggle','type'=>'toggle','actions'=>button::create(['type'=>'button','label'=>$text['button-continue'],'icon'=>'check','id'=>'btn_toggle','style'=>'float: right; margin-left: 15px;','collapse'=>'never','onclick'=>"modal_close(); list_action_set('toggle'); list_form_submit('form_list');"])]); + } + if (permission_exists('service_delete') && $services) { + echo modal::create(['id'=>'modal-delete','type'=>'delete','actions'=>button::create(['type'=>'button','label'=>$text['button-continue'],'icon'=>'check','id'=>'btn_delete','style'=>'float: right; margin-left: 15px;','collapse'=>'never','onclick'=>"modal_close(); list_action_set('delete'); list_form_submit('form_list');"])]); + } + + echo $text['title_description-services']."\n"; + echo "

\n"; + + echo "
\n"; + echo "\n"; + echo "\n"; + + echo "\n"; + echo "\n"; + if (permission_exists('service_add') || permission_exists('service_edit') || permission_exists('service_delete')) { + echo " \n"; + } + echo th_order_by('service_name', $text['label-service_name'], $order_by, $order); + echo th_order_by('service_status', $text['label-service_status'], $order_by, $order); + echo th_order_by('service_category', $text['label-service_category'], $order_by, $order); + echo " \n"; + echo th_order_by('service_enabled', $text['label-enabled'], $order_by, $order, null, "class='center'"); + echo " \n"; + if (permission_exists('service_edit') && $list_row_edit_button == 'true') { + echo " \n"; + } + echo "\n"; + + if (!empty($services) && is_array($services) && @sizeof($services) != 0) { + $x = 0; + foreach ($services as $row) { + if (permission_exists('service_edit')) { + $list_row_url = "service_edit.php?id=".urlencode($row['service_uuid']); + } + echo "\n"; + if (permission_exists('service_add') || permission_exists('service_edit') || permission_exists('service_delete')) { + echo " \n"; + } + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; + if (permission_exists('service_edit')) { + echo " \n"; + echo " \n"; + if (permission_exists('service_edit') && $list_row_edit_button == 'true') { + echo " \n"; + } + echo "\n"; + $x++; + } + unset($services); + } + + echo "
\n"; + echo " \n"; + echo " ".$text['label-service_runtime']."".$text['label-service_description']." 
\n"; + echo " \n"; + echo " \n"; + echo " \n"; + if (permission_exists('service_edit')) { + echo " ".escape($row['service_name'])."\n"; + } + else { + echo " ".escape($row['service_name']); + } + echo " ".escape($row['service_status'])."".escape($row['service_category'])."".escape($row['service_etime'])."\n"; + echo $text['label-'.$row['service_enabled']]; + } + echo " ".escape($row['service_description'])."\n"; + echo button::create(['type'=>'button','title'=>$text['button-edit'],'icon'=>$_SESSION['theme']['button_icon_edit'],'link'=>$list_row_url]); + echo "
\n"; + echo "
\n"; + echo "
".$paging_controls."
\n"; + echo "\n"; + echo "
\n"; + +// include the footer + require_once "resources/footer.php"; + +?> \ No newline at end of file diff --git a/core/upgrade/upgrade.php b/core/upgrade/upgrade.php index 10b47f66f..d82cdfb33 100644 --- a/core/upgrade/upgrade.php +++ b/core/upgrade/upgrade.php @@ -470,25 +470,31 @@ if (empty($argv[2]) || $argv[2] == 'update') { //send a message to the console echo "[ Update ] Update default services\n"; + //echo ($text['description-upgrade_services'] ?? "")."\n"; //add or update all the services - upgrade_services($text, $settings); + $object = new services(); + $object->upgrade('all'); } //send a message to the console if (empty($argv[2]) || $argv[2] == 'stop') { echo "[ Update ] Stop services\n"; + //echo ($text['description-stop_services'] ?? "")."\n"; //stop all the services - stop_services($text, $settings); + $object = new services(); + $object->stop('all'); } //send a message to the console if (empty($argv[2]) || $argv[2] == 'restart') { echo "[ Update ] Restart services\n"; + //echo ($text['description-restart_services'] ?? "")."\n"; //restart all the services - restart_services($text, $settings); + $object = new services(); + $object->restart('all'); } }