From 5e9e1e9c9504f430920a7de70fe5b2136e967eec Mon Sep 17 00:00:00 2001 From: Shane Mc Cormack Date: Tue, 10 Sep 2024 20:54:52 +0100 Subject: [PATCH 01/14] Fix detecting stacks in unstacked switches. (#16384) Fix #16374 --- includes/discovery/sensors/state/cisco.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/discovery/sensors/state/cisco.inc.php b/includes/discovery/sensors/state/cisco.inc.php index 8619064727..8b5ee3b1eb 100644 --- a/includes/discovery/sensors/state/cisco.inc.php +++ b/includes/discovery/sensors/state/cisco.inc.php @@ -40,7 +40,7 @@ $repsegmentnumber = 0; foreach ($tables as $tablevalue) { //Some switches on 15.x expose this information regardless if they are stacked or not, we try to mitigate that by doing the following. - if (($tablevalue['oid'] == 'cswGlobals' || $tablevalue['oid'] == 'cswSwitchRole' || $tablevalue['oid'] == 'cswSwitchState' || $tablevalue['oid'] == 'cswStackPortOperStatus') && $redundant_data == 'false' && count($role_data) <= 1) { + if (in_array($tablevalue['oid'], ['CISCO-STACKWISE-MIB::cswGlobals', 'CISCO-STACKWISE-MIB::cswSwitchRole', 'CISCO-STACKWISE-MIB::cswSwitchState', 'CISCO-STACKWISE-MIB::cswStackPortOperStatus']) && $redundant_data == 'false' && count($role_data) <= 1) { continue; } From f7142980d2c4e39ec20c54cf1213bb51cafa30e0 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Wed, 11 Sep 2024 15:12:11 -0500 Subject: [PATCH 02/14] python3-command-runner is only available in Ubuntu 24.04 (#16390) --- doc/Installation/Install-LibreNMS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Installation/Install-LibreNMS.md b/doc/Installation/Install-LibreNMS.md index 80da623e96..f419a69910 100644 --- a/doc/Installation/Install-LibreNMS.md +++ b/doc/Installation/Install-LibreNMS.md @@ -27,7 +27,7 @@ Connect to the server command line and follow the instructions below. === "Ubuntu 22.04" === "NGINX" ``` - apt install acl curl fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php-cli php-curl php-fpm php-gd php-gmp php-json php-mbstring php-mysql php-snmp php-xml php-zip rrdtool snmp snmpd unzip python3-command-runner python3-pymysql python3-dotenv python3-redis python3-setuptools python3-psutil python3-systemd python3-pip whois traceroute + apt install acl curl fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php-cli php-curl php-fpm php-gd php-gmp php-json php-mbstring php-mysql php-snmp php-xml php-zip rrdtool snmp snmpd unzip python3-pymysql python3-dotenv python3-redis python3-setuptools python3-psutil python3-systemd python3-pip whois traceroute ``` === "Ubuntu 20.04" From 4c72856046846c1a3088823128c57344936a2474 Mon Sep 17 00:00:00 2001 From: jcamos Date: Thu, 12 Sep 2024 16:13:21 +0100 Subject: [PATCH 03/14] Matrix: critical alerts now notify (#16355) * Update Matrix.php Changing 'm.text' to 'm.notice' so that when an alert is issued, you get notified on your Matrix client. The current 'm.text' parameter will silently send you the message, whereas 'm.notice' will send you a message with an alert. * Enabling notifications on Matrix client Changing 'm.text' to 'm.notice' (line 47) so that when an alert is issued, you get notified on your Matrix client. The current 'm.text' parameter will silently send you a message, whereas 'm.notice' will send you a message with an alert. * Update Matrix.php * Update Matrix.php --------- Co-authored-by: Tony Murray --- LibreNMS/Alert/Transport/Matrix.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibreNMS/Alert/Transport/Matrix.php b/LibreNMS/Alert/Transport/Matrix.php index 02e65659e3..24ef1a8ecc 100644 --- a/LibreNMS/Alert/Transport/Matrix.php +++ b/LibreNMS/Alert/Transport/Matrix.php @@ -44,7 +44,7 @@ class Matrix extends Transport $message = SimpleTemplate::parse($message, $alert_data); - $body = ['body' => strip_tags($message), 'formatted_body' => "$message", 'msgtype' => 'm.text', 'format' => 'org.matrix.custom.html']; + $body = ['body' => strip_tags($message), 'formatted_body' => "$message", 'msgtype' => 'm.notice', 'format' => 'org.matrix.custom.html']; $res = Http::client() ->withToken($authtoken) From 00d6fc64d67e385991861f8b7ea6c9ad46539858 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Thu, 12 Sep 2024 11:20:50 -0500 Subject: [PATCH 04/14] Fix sensor discover when device_id is omitted (#16389) --- app/Discovery/Sensor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Discovery/Sensor.php b/app/Discovery/Sensor.php index b2fbc3a9e4..32c301eb73 100644 --- a/app/Discovery/Sensor.php +++ b/app/Discovery/Sensor.php @@ -47,6 +47,7 @@ class Sensor public function discover(\App\Models\Sensor $sensor): static { + $sensor->device_id ??= \DeviceCache::getPrimary()->device_id; $this->models->push($sensor); $this->discovered[$sensor->syncGroup()] = false; From 64517d0c43e8807d462bf090d5f6f034340fff44 Mon Sep 17 00:00:00 2001 From: Oldemar Jesus Date: Thu, 12 Sep 2024 22:10:19 +0100 Subject: [PATCH 05/14] apply styleci spaces fix --- LibreNMS/Data/Store/Kafka.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LibreNMS/Data/Store/Kafka.php b/LibreNMS/Data/Store/Kafka.php index 71c0b7a0c9..9ee9658d6b 100644 --- a/LibreNMS/Data/Store/Kafka.php +++ b/LibreNMS/Data/Store/Kafka.php @@ -74,11 +74,12 @@ class Kafka extends BaseDatastore if (in_array($measurement, $excluded_measurement_arr)) { Log::warning('KAFKA: Skipped parsing to Kafka, measurement is in measurement-excluded: ' . $measurement); + return; } } - if($excluded_device_fields != null && strlen($excluded_device_fields) > 0) { + if ($excluded_device_fields != null && strlen($excluded_device_fields) > 0) { // convert into array $excluded_device_fields_arr = explode(',', $excluded_device_fields); } From 2501b7a4dbe46543cfceed07e0039c339f810a6f Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Fri, 13 Sep 2024 06:22:01 -0500 Subject: [PATCH 06/14] Remove internal usages of config_to_json.php (#16388) * Remove internal usages of config_to_json.php * Appease lint --- LibreNMS/__init__.py | 2 +- snmp-scan.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/LibreNMS/__init__.py b/LibreNMS/__init__.py index 293b20554a..ed9eed3c5d 100644 --- a/LibreNMS/__init__.py +++ b/LibreNMS/__init__.py @@ -167,7 +167,7 @@ def get_config_data(base_dir): ) logger.debug("Traceback:", exc_info=True) - config_cmd = ["/usr/bin/env", "php", "%s/config_to_json.php" % base_dir] + config_cmd = ["/usr/bin/env", "php", "%s/lnms" % base_dir, "config:get", "--dump"] try: exit_code, output = command_runner(config_cmd, timeout=300, stderr=False) if exit_code != 0: diff --git a/snmp-scan.py b/snmp-scan.py index fd90b5798b..4121f7ed6b 100755 --- a/snmp-scan.py +++ b/snmp-scan.py @@ -33,6 +33,7 @@ from sys import stdout from time import time Result = namedtuple("Result", ["ip", "hostname", "outcome", "output"]) +args = {} class Outcome: @@ -263,7 +264,9 @@ Example: 192.168.0.1/32 will be treated as a single host address""", chdir(install_dir) try: CONFIG = json.loads( - check_output(["/usr/bin/env", "php", "config_to_json.php"]).decode() + check_output( + ["/usr/bin/env", "php", "lnms", "config:get", "--dump"] + ).decode() ) except CalledProcessError as e: parser.error( From 13da0aef4ce9fabfc2d1426c380a7381a57c3b3a Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Fri, 13 Sep 2024 09:25:23 -0500 Subject: [PATCH 07/14] Remove legacy db config (#16385) * Remove legacy db config Should be configured via the environment or .env. * Lint fix * Remove call to removed method --- LibreNMS/Config.php | 15 --------------- LibreNMS/config.py | 24 +++++++++++++++++------- config_to_json.php | 3 --- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/LibreNMS/Config.php b/LibreNMS/Config.php index 2a631fd306..190f771c3f 100644 --- a/LibreNMS/Config.php +++ b/LibreNMS/Config.php @@ -478,9 +478,6 @@ class Config } self::populateTime(); - - // populate legacy DB credentials, just in case something external uses them. Maybe remove this later - self::populateLegacyDbCredentials(); } /** @@ -561,18 +558,6 @@ class Config self::set('time.twoyear', $now - 63072000); // time() - (2 * 365 * 24 * 60 * 60); } - public static function populateLegacyDbCredentials() - { - $db = config('database.default'); - - self::set('db_host', config("database.connections.$db.host", 'localhost')); - self::set('db_name', config("database.connections.$db.database", 'librenms')); - self::set('db_user', config("database.connections.$db.username", 'librenms')); - self::set('db_pass', config("database.connections.$db.password")); - self::set('db_port', config("database.connections.$db.port", 3306)); - self::set('db_socket', config("database.connections.$db.unix_socket")); - } - /** * Check if the config has been loaded yet * diff --git a/LibreNMS/config.py b/LibreNMS/config.py index ed884cf6c6..50a85b4776 100644 --- a/LibreNMS/config.py +++ b/LibreNMS/config.py @@ -1,3 +1,6 @@ +import os + + class DBConfig: """ Bare minimal config class for LibreNMS.DB class usage @@ -14,10 +17,17 @@ class DBConfig: db_ssl_ca = "/etc/ssl/certs/ca-certificates.crt" def populate(self, _config): - for key, val in _config.items(): - if key == "db_port": - # Special case: port number - self.db_port = int(val) - elif key.startswith("db_"): - # Prevent prototype pollution by enforcing prefix - setattr(DBConfig, key, val) + self.db_host = os.getenv("DB_HOST", _config.get("db_host", self.db_host)) + self.db_name = os.getenv("DB_DATABASE", _config.get("db_name", self.db_name)) + self.db_pass = os.getenv("DB_PASSWORD", _config.get("db_pass", self.db_pass)) + self.db_port = int(os.getenv("DB_PORT", _config.get("db_port", self.db_port))) + self.db_socket = os.getenv( + "DB_SOCKET", _config.get("db_socket", self.db_socket) + ) + self.db_user = os.getenv("DB_USERNAME", _config.get("db_user", self.db_user)) + self.db_sslmode = os.getenv( + "DB_SSLMODE", _config.get("db_sslmode", self.db_sslmode) + ) + self.db_ssl_ca = os.getenv( + "MYSQL_ATTR_SSL_CA", _config.get("db_ssl_ca", self.db_ssl_ca) + ) diff --git a/config_to_json.php b/config_to_json.php index 21bebf385f..4e04dd6091 100755 --- a/config_to_json.php +++ b/config_to_json.php @@ -13,8 +13,5 @@ $init_modules = ['nodb']; require __DIR__ . '/includes/init.php'; if (App::runningInConsole()) { - // fill in db variables for legacy external scripts - Config::populateLegacyDbCredentials(); - echo Config::toJson(); } From a0587154c4f0b82b4a38569a029b7d2dab168dd9 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Fri, 13 Sep 2024 10:23:17 -0500 Subject: [PATCH 08/14] Sensors move can skip and output into singleton (#16392) * Sensors move can skip and output into singleton * style * rrd_type --------- Co-authored-by: PipoCanaja <38363551+PipoCanaja@users.noreply.github.com> --- app/Discovery/Sensor.php | 30 ++++++++++++++++++++++++ app/Models/Sensor.php | 19 +++++++++++++++ includes/discovery/functions.inc.php | 35 +--------------------------- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/app/Discovery/Sensor.php b/app/Discovery/Sensor.php index 32c301eb73..a7d9b48cd0 100644 --- a/app/Discovery/Sensor.php +++ b/app/Discovery/Sensor.php @@ -27,6 +27,8 @@ namespace App\Discovery; use App\Models\Device; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; +use LibreNMS\Config; use LibreNMS\DB\SyncsModels; class Sensor @@ -47,10 +49,19 @@ class Sensor public function discover(\App\Models\Sensor $sensor): static { + if ($this->canSkip($sensor)) { + Log::info('~'); + Log::debug("Skipped Sensor: $sensor"); + + return $this; + } $sensor->device_id ??= \DeviceCache::getPrimary()->device_id; $this->models->push($sensor); $this->discovered[$sensor->syncGroup()] = false; + Log::debug("Discovered Sensor: $sensor"); + Log::info("$sensor->sensor_descr: Cur $sensor->sensor_current, Low: $sensor->sensor_limit_low, Low Warn: $sensor->sensor_limit_low_warn, Warn: $sensor->sensor_limit_warn, High: $sensor->sensor_limit"); + return $this; } @@ -77,4 +88,23 @@ class Sensor { return $this->models; } + + public function canSkip(\App\Models\Sensor $sensor): bool + { + if (! empty($sensor->sensor_class) && (Config::getOsSetting($this->device->os, "disabled_sensors.$sensor->sensor_class") || Config::get("disabled_sensors.$sensor->sensor_class"))) { + return true; + } + foreach (Config::getCombined($this->device->os, 'disabled_sensors_regex') as $skipRegex) { + if (! empty($sensor->sensor_descr) && preg_match($skipRegex, $sensor->sensor_descr)) { + return true; + } + } + foreach (Config::getCombined($this->device->os, "disabled_sensors_regex.$sensor->sensor_class") as $skipRegex) { + if (! empty($sensor->sensor_descr) && preg_match($skipRegex, $sensor->sensor_descr)) { + return true; + } + } + + return false; + } } diff --git a/app/Models/Sensor.php b/app/Models/Sensor.php index 85225d15cf..9926ceb393 100644 --- a/app/Models/Sensor.php +++ b/app/Models/Sensor.php @@ -143,4 +143,23 @@ class Sensor extends DeviceRelatedModel implements Keyable { return "$this->sensor_class-$this->poller_type"; } + + public function __toString() + { + $data = $this->only([ + 'sensor_oid', + 'sensor_index', + 'sensor_type', + 'sensor_descr', + 'poller_type', + 'sensor_divisor', + 'sensor_multiplier', + 'entPhysicalIndex', + 'sensor_current', + ]); + $data[] = "(limits: LL: $this->sensor_limit_low, LW: $this->sensor_limit_low_warn, W: $this->sensor_limit_warn, H: $this->sensor_limit)"; + $data[] = "rrd_type = $this->rrd_type"; + + return implode(', ', $data); + } } diff --git a/includes/discovery/functions.inc.php b/includes/discovery/functions.inc.php index c2fe81e50b..fdb1323434 100644 --- a/includes/discovery/functions.inc.php +++ b/includes/discovery/functions.inc.php @@ -208,11 +208,6 @@ function discover_sensor($unused, $class, $device, $oid, $index, $type, $descr, if (! is_numeric($divisor)) { $divisor = 1; } - if (can_skip_sensor($device, $class, $descr)) { - return false; - } - - d_echo("Discover sensor: $oid, $index, $type, $descr, $poller_type, $divisor, $multiplier, $entPhysicalIndex, $current, (limits: LL: $low_limit, LW: $low_warn_limit, W: $warn_limit, H: $high_limit), rrd_type = $rrd_type \n"); app('sensor-discovery')->discover(new \App\Models\Sensor([ 'poller_type' => $poller_type, @@ -635,7 +630,7 @@ function discovery_process($os, $sensor_class, $pre_cache) $discovery = $os->getDiscovery('sensors'); $device = $os->getDeviceArray(); - if (! empty($discovery[$sensor_class]) && ! can_skip_sensor($device, $sensor_class, '')) { + if (! empty($discovery[$sensor_class]) && ! app('sensor-discovery')->canSkip(new \App\Models\Sensor(['sensor_class' => $sensor_class]))) { $sensor_options = []; if (isset($discovery[$sensor_class]['options'])) { $sensor_options = $discovery[$sensor_class]['options']; @@ -747,7 +742,6 @@ function discovery_process($os, $sensor_class, $pre_cache) $value = ($value / $divisor) * $multiplier; } - echo "$descr: Cur $value, Low: $low_limit, Low Warn: $low_warn_limit, Warn: $warn_limit, High: $high_limit" . PHP_EOL; $entPhysicalIndex = YamlDiscovery::replaceValues('entPhysicalIndex', $index, null, $data, $pre_cache) ?: null; $entPhysicalIndex_measured = isset($data['entPhysicalIndex_measured']) ? $data['entPhysicalIndex_measured'] : null; @@ -974,33 +968,6 @@ function add_cbgp_peer($device, $peer, $afi, $safi) } } -/** - * check if we should skip this sensor from discovery - * - * @param $device - * @param string $sensor_class - * @param string $sensor_descr - * @return bool - */ -function can_skip_sensor($device, $sensor_class = '', $sensor_descr = '') -{ - if (! empty($sensor_class) && (Config::getOsSetting($device['os'], "disabled_sensors.$sensor_class") || Config::get("disabled_sensors.$sensor_class"))) { - return true; - } - foreach (Config::getCombined($device['os'], 'disabled_sensors_regex') as $skipRegex) { - if (! empty($sensor_descr) && preg_match($skipRegex, $sensor_descr)) { - return true; - } - } - foreach (Config::getCombined($device['os'], "disabled_sensors_regex.$sensor_class") as $skipRegex) { - if (! empty($sensor_descr) && preg_match($skipRegex, $sensor_descr)) { - return true; - } - } - - return false; -} - /** * check if we should skip this device from discovery * From a6b69c9c4de9cb7d01cdfab15d5e35d23059482b Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Fri, 13 Sep 2024 17:50:37 -0500 Subject: [PATCH 09/14] Save guessed limits (#16396) * Save guessed limits Previous code was guessing, then not saving the guess * Move to creating, which revealed that limits were swapped * Apply fixes from StyleCI --------- Co-authored-by: Tony Murray --- app/Models/Sensor.php | 4 ++-- app/Observers/SensorObserver.php | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Models/Sensor.php b/app/Models/Sensor.php index 9926ceb393..0aba83c81f 100644 --- a/app/Models/Sensor.php +++ b/app/Models/Sensor.php @@ -99,7 +99,7 @@ class Sensor extends DeviceRelatedModel implements Keyable public function guessLimits(): void { - $this->sensor_limit = match ($this->sensor_class) { + $this->sensor_limit_low = match ($this->sensor_class) { 'temperature' => $this->sensor_current - 10, 'voltage' => $this->sensor_current * 0.85, 'humidity' => 30, @@ -110,7 +110,7 @@ class Sensor extends DeviceRelatedModel implements Keyable default => null, }; - $this->sensor_limit_low = match ($this->sensor_class) { + $this->sensor_limit = match ($this->sensor_class) { 'temperature' => $this->sensor_current + 20, 'voltage' => $this->sensor_current * 1.15, 'humidity' => 70, diff --git a/app/Observers/SensorObserver.php b/app/Observers/SensorObserver.php index ea31c2188d..43504d45bb 100644 --- a/app/Observers/SensorObserver.php +++ b/app/Observers/SensorObserver.php @@ -5,6 +5,7 @@ namespace App\Observers; use App\Models\Eventlog; use App\Models\Sensor; use Illuminate\Foundation\Application; +use Illuminate\Support\Facades\Log; use LibreNMS\Enum\Severity; class SensorObserver @@ -20,6 +21,8 @@ class SensorObserver { // fix inverted limits if ($sensor->sensor_limit !== null && $sensor->sensor_limit_low !== null && $sensor->sensor_limit_low > $sensor->sensor_limit) { + Log::error('Fixing swapped sensor limits'); + // Fix high/low thresholds (i.e. on negative numbers) [$sensor->sensor_limit, $sensor->sensor_limit_low] = [$sensor->sensor_limit_low, $sensor->sensor_limit]; } @@ -29,6 +32,14 @@ class SensorObserver } } + public function creating(Sensor $sensor): void + { + $guess_limits = \LibreNMS\Config::get('sensors.guess_limits', true); + if ($guess_limits && $sensor->sensor_current !== null && $sensor->sensor_limit === null && $sensor->sensor_limit_low === null) { + $sensor->guessLimits(); + } + } + /** * Handle the service "created" event. * @@ -37,11 +48,6 @@ class SensorObserver */ public function created(Sensor $sensor): void { - $guess_limits = \LibreNMS\Config::get('sensors.guess_limits', true); - if ($guess_limits && $sensor->sensor_current !== null && $sensor->sensor_limit === null && $sensor->sensor_limit_low === null) { - $sensor->guessLimits(); - } - EventLog::log('Sensor Added: ' . $sensor->sensor_class . ' ' . $sensor->sensor_type . ' ' . $sensor->sensor_index . ' ' . $sensor->sensor_descr, $sensor->device_id, 'sensor', Severity::Notice, $sensor->sensor_id); if ($this->runningInConsole) { From 47cd0e75de3f036120603035e0e094cb544867e1 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Sat, 14 Sep 2024 18:42:33 -0500 Subject: [PATCH 10/14] Fix module tests (#16397) --- tests/OSModulesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OSModulesTest.php b/tests/OSModulesTest.php index fe4b819587..c012fb1c4a 100644 --- a/tests/OSModulesTest.php +++ b/tests/OSModulesTest.php @@ -118,7 +118,7 @@ class OSModulesTest extends DBTestCase // output all discovery and poller output if debug mode is enabled for phpunit $phpunit_debug = in_array('--debug', $_SERVER['argv'], true); - foreach ($modules as $module) { + foreach ($modules as $module => $module_status) { $expected = $expected_data[$module]['discovery'] ?? []; $actual = $results[$module]['discovery'] ?? []; $this->checkTestData($expected, $actual, 'Discovered', $os, $module, $filename, $helper, $phpunit_debug); From 7d450345dff29d31d52eeaf7193028c6d5ffbf67 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Sat, 14 Sep 2024 19:13:11 -0500 Subject: [PATCH 11/14] Fix sensor state translations (#16393) * Fix sensor state translations * Fix up lint/style * Set state_index_id * Apply fixes from StyleCI * Wrong call * just use a loop * Wrong id column * Missing fillable * Handle sensors missing state translations * Before making a state index * Can't map to a state index if it doesn't exist * Apply fixes from StyleCI * ies5000 overflowing tinyint * Accept state translations directly add that in the translation * handle duplicate state names, but with different case (skip no way to work there) * Apply fixes from StyleCI * Fix type stuffs --------- Co-authored-by: Tony Murray --- LibreNMS/DB/SyncsModels.php | 11 ++- app/Discovery/Sensor.php | 79 +++++++++++++++++ app/Models/Sensor.php | 8 +- app/Models/SensorToStateIndex.php | 24 +++++ app/Models/StateIndex.php | 28 ++++++ app/Models/StateTranslation.php | 25 +++++- includes/definitions/discovery/ies5000.yaml | 2 +- includes/functions.php | 98 ++------------------- 8 files changed, 174 insertions(+), 101 deletions(-) create mode 100644 app/Models/SensorToStateIndex.php create mode 100644 app/Models/StateIndex.php diff --git a/LibreNMS/DB/SyncsModels.php b/LibreNMS/DB/SyncsModels.php index 0a01a95619..fe29491441 100644 --- a/LibreNMS/DB/SyncsModels.php +++ b/LibreNMS/DB/SyncsModels.php @@ -26,7 +26,6 @@ namespace LibreNMS\DB; use App\Models\Device; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Support\Collection; use LibreNMS\Interfaces\Models\Keyable; @@ -37,15 +36,15 @@ trait SyncsModels * Sync several models for a device's relationship * Model must implement \LibreNMS\Interfaces\Models\Keyable interface * - * @param \App\Models\Device $device + * @param \Illuminate\Database\Eloquent\Model $parentModel * @param string $relationship * @param \Illuminate\Support\Collection $models \LibreNMS\Interfaces\Models\Keyable * @return \Illuminate\Support\Collection */ - protected function syncModels($device, $relationship, $models, $existing = null): Collection + protected function syncModels($parentModel, $relationship, $models, $existing = null): Collection { $models = $models->keyBy->getCompositeKey(); - $existing = ($existing ?? $device->$relationship)->groupBy->getCompositeKey(); + $existing = ($existing ?? $parentModel->$relationship)->groupBy->getCompositeKey(); foreach ($existing as $exist_key => $existing_rows) { if ($models->offsetExists($exist_key)) { @@ -67,12 +66,12 @@ trait SyncsModels } $new = $models->diffKeys($existing); - if (is_a($device->$relationship(), HasManyThrough::class)) { + if (is_a($parentModel->$relationship(), HasManyThrough::class)) { // if this is a distant relation, the models need the intermediate relationship set // just save assuming things are correct $new->each->save(); } else { - $device->$relationship()->saveMany($new); + $parentModel->$relationship()->saveMany($new); } return $existing->map->first()->merge($new); diff --git a/app/Discovery/Sensor.php b/app/Discovery/Sensor.php index a7d9b48cd0..f69abab669 100644 --- a/app/Discovery/Sensor.php +++ b/app/Discovery/Sensor.php @@ -26,10 +26,16 @@ namespace App\Discovery; use App\Models\Device; +use App\Models\Eventlog; +use App\Models\SensorToStateIndex; +use App\Models\StateIndex; +use App\Models\StateTranslation; +use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use LibreNMS\Config; use LibreNMS\DB\SyncsModels; +use LibreNMS\Enum\Severity; class Sensor { @@ -40,6 +46,8 @@ class Sensor private array $discovered = []; private string $relationship = 'sensors'; private Device $device; + /** @var array> */ + private array $states = []; public function __construct(Device $device) { @@ -65,6 +73,18 @@ class Sensor return $this; } + /** + * @param string $stateName + * @param StateTranslation[]|Collection $states + * @return $this + */ + public function withStateTranslations(string $stateName, array|Collection $states): static + { + $this->states[$stateName] = new Collection($states); + + return $this; + } + public function isDiscovered(string $type): bool { return $this->discovered[$type] ?? false; @@ -78,6 +98,8 @@ class Sensor $synced = $this->syncModelsByGroup($this->device, 'sensors', $this->getModels(), $params); $this->discovered[$type] = true; + $this->syncStates($synced); + return $synced; } @@ -107,4 +129,61 @@ class Sensor return false; } + + private function syncStates(Collection $sensors): void + { + $stateSensors = $sensors->where('sensor_class', 'state'); + + if ($stateSensors->isEmpty()) { + return; + } + + $usedStates = $stateSensors->pluck('sensor_type'); + $existingStateIndexes = StateIndex::whereIn('state_name', $usedStates)->get()->keyBy('state_name'); + + foreach ($usedStates as $stateName) { + // make sure the state translations were given for this state name + if (! isset($this->states[$stateName])) { + Log::error("Non existent state name ($stateName) set by sensor: " . $stateSensors->where('sensor_type', $stateName)->first()?->sensor_descr); + + continue; + } + + $stateIndex = $existingStateIndexes->get($stateName); + + // create new state indexes + if ($stateIndex == null) { + try { + $stateIndex = StateIndex::create(['state_name' => $stateName]); + $existingStateIndexes->put($stateName, $stateIndex); + } catch (UniqueConstraintViolationException) { + Eventlog::log("Duplicate state name $stateName (with case mismatch)", $this->device, 'sensor', Severity::Error, $stateSensors->where('sensor_type', $stateName)->first()?->sensor_id); + + continue; + } + } + + // set state_index_id + $stateTranslations = $this->states[$stateName]; + foreach ($stateTranslations as $translation) { + $translation->state_index_id = $stateIndex->state_index_id; + } + + // sync the translations to make sure they are up to date + $this->syncModels($stateIndex, 'translations', $stateTranslations); + } + + // update sensor to state indexes + foreach ($stateSensors as $stateSensor) { + $state_index_id = $existingStateIndexes->get($stateSensor->sensor_type)?->state_index_id; + + // only map if sensor gave a valid state name + if ($state_index_id) { + SensorToStateIndex::updateOrCreate( + ['sensor_id' => $stateSensor->sensor_id], + ['state_index_id' => $state_index_id], + ); + } + } + } } diff --git a/app/Models/Sensor.php b/app/Models/Sensor.php index 0aba83c81f..107a2d135a 100644 --- a/app/Models/Sensor.php +++ b/app/Models/Sensor.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\MorphMany; use LibreNMS\Interfaces\Models\Keyable; @@ -129,9 +130,14 @@ class Sensor extends DeviceRelatedModel implements Keyable return $this->morphMany(Eventlog::class, 'events', 'type', 'reference'); } + public function stateIndex(): HasOneThrough + { + return $this->hasOneThrough(StateIndex::class, SensorToStateIndex::class, 'sensor_id', 'state_index_id', 'sensor_id', 'state_index_id'); + } + public function translations(): BelongsToMany { - return $this->belongsToMany(StateTranslation::class, 'sensors_to_state_indexes', 'sensor_id', 'state_index_id'); + return $this->belongsToMany(StateTranslation::class, 'sensors_to_state_indexes', 'sensor_id', 'state_index_id', 'sensor_id', 'state_index_id'); } public function getCompositeKey(): string diff --git a/app/Models/SensorToStateIndex.php b/app/Models/SensorToStateIndex.php new file mode 100644 index 0000000000..f4711f2912 --- /dev/null +++ b/app/Models/SensorToStateIndex.php @@ -0,0 +1,24 @@ +hasOne(Sensor::class, 'sensor_id', 'sensor_id'); + } + + public function stateIndex(): HasOne + { + return $this->hasOne(StateIndex::class, 'state_index_id', 'state_index_id'); + } +} diff --git a/app/Models/StateIndex.php b/app/Models/StateIndex.php new file mode 100644 index 0000000000..50367cc131 --- /dev/null +++ b/app/Models/StateIndex.php @@ -0,0 +1,28 @@ +hasManyThrough(Sensor::class, SensorToStateIndex::class, 'state_index_id', 'sensor_id', 'state_index_id', 'sensor_id'); + } + + public function translations(): HasMany + { + return $this->hasMany(StateTranslation::class, 'state_index_id', 'state_index_id'); + } +} diff --git a/app/Models/StateTranslation.php b/app/Models/StateTranslation.php index 0be7de8d2e..c1abc6a13f 100644 --- a/app/Models/StateTranslation.php +++ b/app/Models/StateTranslation.php @@ -26,9 +26,28 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use LibreNMS\Interfaces\Models\Keyable; -class StateTranslation extends Model +class StateTranslation extends Model implements Keyable { - public $timestamps = false; - protected $primaryKey = 'state_index_id'; + const CREATED_AT = null; + const UPDATED_AT = 'state_lastupdated'; + protected $primaryKey = 'state_translation_id'; + protected $fillable = [ + 'state_descr', + 'state_draw_graph', + 'state_value', + 'state_generic_value', + ]; + + public function stateIndex(): BelongsTo + { + return $this->belongsTo(StateIndex::class, 'state_index_id', 'state_index_id'); + } + + public function getCompositeKey() + { + return $this->state_value; + } } diff --git a/includes/definitions/discovery/ies5000.yaml b/includes/definitions/discovery/ies5000.yaml index 864ba12916..58d2f322a7 100644 --- a/includes/definitions/discovery/ies5000.yaml +++ b/includes/definitions/discovery/ies5000.yaml @@ -105,7 +105,7 @@ modules: - { value: 4096, generic: 2, graph: 0, descr: macSpoof } - { value: 8192, generic: 2, graph: 0, descr: cpuHigh } - { value: 16384, generic: 2, graph: 0, descr: memoryUsageHigh } - - { value: 32768, generic: 2, graph: 0, descr: packetBufferUsageHigh } +# - { value: 32768, generic: 2, graph: 0, descr: packetBufferUsageHigh } # state value larger than smallint 32767 - oid: ledAlarmStatus value: ledAlarmStatus diff --git a/includes/functions.php b/includes/functions.php index 4ab951e006..ad07633aee 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -8,6 +8,7 @@ * @copyright (C) 2006 - 2012 Adam Armstrong */ +use App\Models\StateTranslation; use Illuminate\Support\Str; use LibreNMS\Config; use LibreNMS\Enum\Severity; @@ -493,106 +494,23 @@ function dnslookup($device, $type = false, $return = false) * * @param string $state_name the unique name for this state translation * @param array $states array of states, each must contain keys: descr, graph, value, generic - * @return int|null + * @return void */ -function create_state_index($state_name, $states = []) +function create_state_index($state_name, $states = []): void { - $state_index_id = dbFetchCell('SELECT `state_index_id` FROM state_indexes WHERE state_name = ? LIMIT 1', [$state_name]); - if (! is_numeric($state_index_id)) { - $state_index_id = dbInsert(['state_name' => $state_name], 'state_indexes'); - - // legacy code, return index so states are created - if (empty($states)) { - return $state_index_id; - } - } - - // check or synchronize states - if (empty($states)) { - $translations = dbFetchRows('SELECT * FROM `state_translations` WHERE `state_index_id` = ?', [$state_index_id]); - if (count($translations) == 0) { - // If we don't have any translations something has gone wrong so return the state_index_id so they get created. - return $state_index_id; - } - } else { - sync_sensor_states($state_index_id, $states); - } - - return null; -} - -/** - * Synchronize the sensor state translations with the database - * - * @param int $state_index_id index of the state - * @param array $states array of states, each must contain keys: descr, graph, value, generic - */ -function sync_sensor_states($state_index_id, $states) -{ - $new_translations = array_reduce($states, function ($array, $state) use ($state_index_id) { - $array[$state['value']] = [ - 'state_index_id' => $state_index_id, + app('sensor-discovery')->withStateTranslations($state_name, array_map(function ($state) { + return new StateTranslation([ 'state_descr' => $state['descr'], 'state_draw_graph' => $state['graph'], 'state_value' => $state['value'], 'state_generic_value' => $state['generic'], - ]; - - return $array; - }, []); - - $existing_translations = dbFetchRows( - 'SELECT `state_index_id`,`state_descr`,`state_draw_graph`,`state_value`,`state_generic_value` FROM `state_translations` WHERE `state_index_id`=?', - [$state_index_id] - ); - - foreach ($existing_translations as $translation) { - $value = $translation['state_value']; - if (isset($new_translations[$value])) { - if ($new_translations[$value] != $translation) { - dbUpdate( - $new_translations[$value], - 'state_translations', - '`state_index_id`=? AND `state_value`=?', - [$state_index_id, $value] - ); - } - - // this translation is synchronized, it doesn't need to be inserted - unset($new_translations[$value]); - } else { - dbDelete('state_translations', '`state_index_id`=? AND `state_value`=?', [$state_index_id, $value]); - } - } - - // insert any new translations - dbBulkInsert($new_translations, 'state_translations'); + ]); + }, $states)); } function create_sensor_to_state_index($device, $state_name, $index) { - $sensor_entry = dbFetchRow('SELECT sensor_id FROM `sensors` WHERE `sensor_class` = ? AND `device_id` = ? AND `sensor_type` = ? AND `sensor_index` = ?', [ - 'state', - $device['device_id'], - $state_name, - $index, - ]); - $state_indexes_entry = dbFetchRow('SELECT state_index_id FROM `state_indexes` WHERE `state_name` = ?', [ - $state_name, - ]); - if (! empty($sensor_entry['sensor_id']) && ! empty($state_indexes_entry['state_index_id'])) { - $insert = [ - 'sensor_id' => $sensor_entry['sensor_id'], - 'state_index_id' => $state_indexes_entry['state_index_id'], - ]; - foreach ($insert as $key => $val_check) { - if (! isset($val_check)) { - unset($insert[$key]); - } - } - - dbInsert($insert, 'sensors_to_state_indexes'); - } + // no op } function delta_to_bits($delta, $period) From 7034fd7a40bba3a2d447fac5198e2ca07b5dc710 Mon Sep 17 00:00:00 2001 From: Dag Bakke Date: Tue, 17 Sep 2024 17:26:32 +0200 Subject: [PATCH 12/14] fix for wrong graph being referenced (#16400) Co-authored-by: Dag B --- includes/definitions/eatonupsm2.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/definitions/eatonupsm2.yaml b/includes/definitions/eatonupsm2.yaml index a49b502145..a58447507e 100644 --- a/includes/definitions/eatonupsm2.yaml +++ b/includes/definitions/eatonupsm2.yaml @@ -8,7 +8,7 @@ mib_dir: eaton over: - { graph: device_voltage, text: Voltage } - { graph: device_current, text: Current } - - { graph: device_frequency, text: Load } + - { graph: device_load, text: Load } discovery: - sysObjectID: .1.3.6.1.4.1.534.1 From 867c31e18f634c005801c9e8c150ad768c774f58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:54:16 +0200 Subject: [PATCH 13/14] Bump send and express (#16406) Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `send` from 0.18.0 to 0.19.0 - [Release notes](https://github.com/pillarjs/send/releases) - [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md) - [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: send dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 122 ++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 714e860893..32aa131266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3107,9 +3107,9 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -3120,7 +3120,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -3154,21 +3154,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -4487,9 +4472,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" @@ -4704,37 +4689,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -4760,21 +4745,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4893,13 +4863,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6368,10 +6338,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-source-map": { "version": "1.1.0", @@ -7083,9 +7056,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, "node_modules/path-type": { @@ -7886,9 +7859,9 @@ "dev": true }, "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { "side-channel": "^1.0.6" @@ -8455,9 +8428,9 @@ "dev": true }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -8493,6 +8466,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -8587,15 +8569,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0"