diff options
Diffstat (limited to 'glances')
26 files changed, 154 insertions, 244 deletions
diff --git a/glances/__init__.py b/glances/__init__.py index b49f0afa..038787d8 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -19,7 +19,7 @@ import sys # Global name # Version should start and end with a numerical char # See https://packaging.python.org/specifications/core-metadata/#version -__version__ = '3.3.0.4' +__version__ = '3.3.1' __author__ = 'Nicolas Hennion <nicolas@nicolargo.com>' __license__ = 'LGPLv3' diff --git a/glances/client.py b/glances/client.py index 46c81db4..d87fe658 100644 --- a/glances/client.py +++ b/glances/client.py @@ -9,7 +9,7 @@ """Manage the Glances client.""" -import json +import ujson import socket import sys import time @@ -119,7 +119,7 @@ class GlancesClient(object): if __version__.split('.')[0] == client_version.split('.')[0]: # Init stats self.stats = GlancesStatsClient(config=self.config, args=self.args) - self.stats.set_plugins(json.loads(self.client.getAllPlugins())) + self.stats.set_plugins(ujson.loads(self.client.getAllPlugins())) logger.debug("Client version: {} / Server version: {}".format(__version__, client_version)) else: self.log_and_exit( @@ -198,7 +198,7 @@ class GlancesClient(object): """ # Update the stats try: - server_stats = json.loads(self.client.getAll()) + server_stats = ujson.loads(self.client.getAll()) except socket.error: # Client cannot get server stats return "Disconnected" diff --git a/glances/client_browser.py b/glances/client_browser.py index 13161e3c..522ccc98 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -9,7 +9,7 @@ """Manage the Glances client browser (list of Glances server).""" -import json +import ujson import socket import threading @@ -98,12 +98,12 @@ class GlancesClientBrowser(object): # Mandatory stats try: # CPU% - cpu_percent = 100 - json.loads(s.getCpu())['idle'] + cpu_percent = 100 - ujson.loads(s.getCpu())['idle'] server['cpu_percent'] = '{:.1f}'.format(cpu_percent) # MEM% - server['mem_percent'] = json.loads(s.getMem())['percent'] + server['mem_percent'] = ujson.loads(s.getMem())['percent'] # OS (Human Readable name) - server['hr_name'] = json.loads(s.getSystem())['hr_name'] + server['hr_name'] = ujson.loads(s.getSystem())['hr_name'] except (socket.error, Fault, KeyError) as e: logger.debug("Error while grabbing stats form {}: {}".format(uri, e)) server['status'] = 'OFFLINE' @@ -123,7 +123,7 @@ class GlancesClientBrowser(object): # Optional stats (load is not available on Windows OS) try: # LOAD - load_min5 = json.loads(s.getLoad())['min5'] + load_min5 = ujson.loads(s.getLoad())['min5'] server['load_min5'] = '{:.2f}'.format(load_min5) except Exception as e: logger.warning("Error while grabbing stats form {}: {}".format(uri, e)) diff --git a/glances/exports/glances_csv.py b/glances/exports/glances_csv.py index eedb6541..d60f4223 100644 --- a/glances/exports/glances_csv.py +++ b/glances/exports/glances_csv.py @@ -81,10 +81,10 @@ class Export(GlancesExport): # Loop over plugins to export for plugin in self.plugins_to_export(stats): if isinstance(all_stats[plugin], list): - for stat in all_stats[plugin]: + for stat in sorted(all_stats[plugin], key=lambda x: x['key']): # First line: header if self.first_line: - csv_header += ('{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat) + csv_header += ['{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat] # Others lines: stats csv_data += itervalues(stat) elif isinstance(all_stats[plugin], dict): diff --git a/glances/exports/glances_export.py b/glances/exports/glances_export.py index 1b650901..e8e0667f 100644 --- a/glances/exports/glances_export.py +++ b/glances/exports/glances_export.py @@ -13,7 +13,7 @@ I am your father... ...for all Glances exports IF. """ -import json +from glances.globals import json_dumps from glances.compat import NoOptionError, NoSectionError, iteritems, iterkeys from glances.logger import logger @@ -185,7 +185,7 @@ class GlancesExport(object): # Walk through the dict for key, value in iteritems(stats): if isinstance(value, bool): - value = json.dumps(value) + value = json_dumps(value) if isinstance(value, list): try: value = value[0] diff --git a/glances/exports/glances_json.py b/glances/exports/glances_json.py index 82d344c2..6a9e5fe9 100644 --- a/glances/exports/glances_json.py +++ b/glances/exports/glances_json.py @@ -1,8 +1,8 @@ """JSON interface class.""" import sys -import json +from glances.globals import json_dumps from glances.compat import PY3, listkeys from glances.logger import logger from glances.exports.glances_export import GlancesExport @@ -55,10 +55,10 @@ class Export(GlancesExport): # Export stats to JSON file if PY3: with open(self.json_filename, "w") as self.json_file: - self.json_file.write("{}\n".format(json.dumps(self.buffer))) + self.json_file.write("{}\n".format(json_dumps(self.buffer))) else: with open(self.json_filename, "wb") as self.json_file: - self.json_file.write("{}\n".format(json.dumps(self.buffer))) + self.json_file.write("{}\n".format(json_dumps(self.buffer))) # Reset buffer self.buffer = {} diff --git a/glances/exports/glances_kafka.py b/glances/exports/glances_kafka.py index 275b9595..ccb51b43 100644 --- a/glances/exports/glances_kafka.py +++ b/glances/exports/glances_kafka.py @@ -13,9 +13,9 @@ import sys from glances.logger import logger from glances.exports.glances_export import GlancesExport +from glances.globals import json_dumps from kafka import KafkaProducer -import json class Export(GlancesExport): @@ -54,7 +54,7 @@ class Export(GlancesExport): try: s = KafkaProducer( bootstrap_servers=server_uri, - value_serializer=lambda v: json.dumps(v).encode('utf-8'), + value_serializer=lambda v: json_dumps(v).encode('utf-8'), compression_type=self.compression, ) except Exception as e: diff --git a/glances/exports/glances_mqtt.py b/glances/exports/glances_mqtt.py index 0f3d3587..ea2c7941 100644 --- a/glances/exports/glances_mqtt.py +++ b/glances/exports/glances_mqtt.py @@ -11,11 +11,11 @@ import socket import string -import json import sys from glances.logger import logger from glances.exports.glances_export import GlancesExport +from glances.globals import json_dumps # Import paho for MQTT from requests import certs @@ -116,7 +116,7 @@ class Export(GlancesExport): # Add the value current_level[split_key[len(split_key) - 1]] = sensor_values[key] - json_value = json.dumps(output_value) + json_value = json_dumps(output_value) self.client.publish(topic, json_value) except Exception as e: logger.error("Can not export stats to MQTT server (%s)" % e) diff --git a/glances/exports/glances_zeromq.py b/glances/exports/glances_zeromq.py index 0f2fa310..b7f8e1c0 100644 --- a/glances/exports/glances_zeromq.py +++ b/glances/exports/glances_zeromq.py @@ -10,11 +10,11 @@ """ZeroMQ interface class.""" import sys -import json from glances.compat import b from glances.logger import logger from glances.exports.glances_export import GlancesExport +from glances.globals import json_dumps import zmq from zmq.utils.strtypes import asbytes @@ -84,7 +84,7 @@ class Export(GlancesExport): # - First frame containing the following prefix (STRING) # - Second frame with the Glances plugin name (STRING) # - Third frame with the Glances plugin stats (JSON) - message = [b(self.prefix), b(name), asbytes(json.dumps(data))] + message = [b(self.prefix), b(name), asbytes(json_dumps(data))] # Write data to the ZeroMQ bus # Result can be view: tcp://host:port diff --git a/glances/globals.py b/glances/globals.py index c7506453..10a40e0d 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -13,7 +13,7 @@ import errno import os import sys import platform -import json +import ujson from operator import itemgetter # OS constants (some libraries/features are OS-dependent) @@ -53,9 +53,9 @@ def json_dumps(data): Manage the issue #815 for Windows OS with UnicodeDecodeError catching. """ try: - return json.dumps(data) + return ujson.dumps(data) except UnicodeDecodeError: - return json.dumps(data, ensure_ascii=False) + return ujson.dumps(data, ensure_ascii=False) def json_dumps_dictlist(data, item): diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index f2e030cb..5c475a7f 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -9,7 +9,6 @@ """Web interface class.""" -import json import os import sys import tempfile @@ -18,6 +17,7 @@ import webbrowser import zlib import socket +from glances.globals import json_dumps from glances.compat import b from glances.timer import Timer from glances.logger import logger @@ -240,7 +240,7 @@ class GlancesBottle(object): # Update the stat view_data = self.stats.get_plugin("help").get_view_data() try: - plist = json.dumps(view_data, sort_keys=True) + plist = json_dumps(view_data) except Exception as e: abort(404, "Cannot get help view data (%s)" % str(e)) return plist @@ -278,7 +278,7 @@ class GlancesBottle(object): self.__update__() try: - plist = json.dumps(self.plugins_list) + plist = json_dumps(self.plugins_list) except Exception as e: abort(404, "Cannot get plugin list (%s)" % str(e)) return plist @@ -307,7 +307,7 @@ class GlancesBottle(object): try: # Get the JSON value of the stat ID - statval = json.dumps(self.stats.getAllAsDict()) + statval = json_dumps(self.stats.getAllAsDict()) except Exception as e: abort(404, "Cannot get stats (%s)" % str(e)) @@ -326,7 +326,7 @@ class GlancesBottle(object): try: # Get the JSON value of the stat limits - limits = json.dumps(self.stats.getAllLimitsAsDict()) + limits = json_dumps(self.stats.getAllLimitsAsDict()) except Exception as e: abort(404, "Cannot get limits (%s)" % (str(e))) return limits @@ -344,7 +344,7 @@ class GlancesBottle(object): try: # Get the JSON value of the stat view - limits = json.dumps(self.stats.getAllViewsAsDict()) + limits = json_dumps(self.stats.getAllViewsAsDict()) except Exception as e: abort(404, "Cannot get views (%s)" % (str(e))) return limits @@ -528,7 +528,7 @@ class GlancesBottle(object): try: # Get the JSON value of the config' dict - args_json = json.dumps(self.config.as_dict()) + args_json = json_dumps(self.config.as_dict()) except Exception as e: abort(404, "Cannot get config (%s)" % str(e)) return args_json @@ -550,7 +550,7 @@ class GlancesBottle(object): try: # Get the JSON value of the config' dict - args_json = json.dumps(config_dict[item]) + args_json = json_dumps(config_dict[item]) except Exception as e: abort(404, "Cannot get config item (%s)" % str(e)) return args_json @@ -569,7 +569,7 @@ class GlancesBottle(object): # Get the JSON value of the args' dict # Use vars to convert namespace to dict # Source: https://docs.python.org/%s/library/functions.html#vars - args_json = json.dumps(vars(self.args)) + args_json = json_dumps(vars(self.args)) except Exception as e: abort(404, "Cannot get args (%s)" % str(e)) return args_json @@ -592,7 +592,7 @@ class GlancesBottle(object): # Get the JSON value of the args' dict # Use vars to convert namespace to dict # Source: https://docs.python.org/%s/library/functions.html#vars - args_json = json.dumps(vars(self.args)[item]) + args_json = json_dumps(vars(self.args)[item]) except Exception as e: abort(404, "Cannot get args item (%s)" % str(e)) return args_json diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index a2cbd0a5..f3be8980 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -680,7 +680,6 @@ class _GlancesCurses(object): new_filter = self.display_popup( 'Process filter pattern: \n\n' + 'Examples:\n' - + '- python\n' + '- .*python.*\n' + '- /usr/lib.*\n' + '- name:.*nautilus.*\n' @@ -1110,7 +1109,7 @@ class _GlancesCurses(object): def erase(self): """Erase the content of the screen.""" - self.term_window.erase() + self.term_window.clear() def flush(self, stats, cs_status=None): """Clear and update the screen. diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py index ee1474a3..5efd34f5 100644 --- a/glances/outputs/glances_stdout_apidoc.py +++ b/glances/outputs/glances_stdout_apidoc.py @@ -70,7 +70,7 @@ def print_plugins_list(stat): print('') -def print_plugin_export(plugin, stat_export): +def print_plugin_stats(plugin, stat): sub_title = 'GET {}'.format(plugin) print(sub_title) print('-' * len(sub_title)) @@ -79,7 +79,7 @@ def print_plugin_export(plugin, stat_export): print('Get plugin stats::') print('') print(' # curl {}/{}'.format(API_URL, plugin)) - print(indent_stat(stat_export)) + print(indent_stat(json.loads(stat.get_stats()))) print('') @@ -223,7 +223,7 @@ class GlancesStdoutApiDoc(object): stat_export = stat.get_export() if stat_export is None or stat_export == [] or stat_export == {}: continue - print_plugin_export(plugin, stat_export) + print_plugin_stats(plugin, stat) print_plugin_description(plugin, stat) print_plugin_item_value(plugin, stat, stat_export) diff --git a/glances/outputs/static/js/components/plugin-processlist.vue b/glances/outputs/static/js/components/plugin-processlist.vue index 6b52b96b..462fce61 100644 --- a/glances/outputs/static/js/components/plugin-processlist.vue +++ b/glances/outputs/static/js/components/plugin-processlist.vue @@ -199,7 +199,7 @@ export default { ((isWindows && process.nice != 32) || (!isWindows && process.nice != 0)); if (Array.isArray(process.cmdline)) { - process.cmdline = process.cmdline.join(' '); + process.cmdline = process.cmdline.join(' ').replace(/\n/g, ' '); } if (process.cmdline === null) { diff --git a/glances/outputs/static/package-lock.json b/glances/outputs/static/package-lock.json index c571b655..6359af4b 100644 --- a/glances/outputs/static/package-lock.json +++ b/glances/outputs/static/package-lock.json @@ -1332,6 +1332,15 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2514,6 +2523,15 @@ "optional": true, "peer": true }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -4508,6 +4526,18 @@ "optional": true, "peer": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -4653,6 +4683,20 @@ "node": ">=6.11.5" } }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -4911,12 +4955,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "node_modules/minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -7530,53 +7568,6 @@ } } }, - "node_modules/url-loader/node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/url-loader/node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/url-loader/node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/url-loader/node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7750,50 +7741,6 @@ "webpack": "^4.1.0 || ^5.0.0-0" } }, - "node_modules/vue-loader/node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/vue-loader/node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/vue-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/vue-loader/node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -9457,6 +9404,12 @@ "tweetnacl": "^0.14.3" } }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -10339,6 +10292,12 @@ "optional": true, "peer": true }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -11862,6 +11821,12 @@ "optional": true, "peer": true }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -11966,6 +11931,17 @@ "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", "dev": true }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqp |