diff --git a/.github/workflows/builddevdoc.yml b/.github/workflows/builddevdoc.yml index 6065d0fcca..1bfaee0e52 100644 --- a/.github/workflows/builddevdoc.yml +++ b/.github/workflows/builddevdoc.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.8' ] + python-version: [ '3.9' ] name: Python ${{ matrix.python-version }} steps: - name: update OS (Ubuntu) diff --git a/.github/workflows/pr_unittests.yml b/.github/workflows/pr_unittests.yml new file mode 100644 index 0000000000..0a15841388 --- /dev/null +++ b/.github/workflows/pr_unittests.yml @@ -0,0 +1,63 @@ +name: "Unittests Core" +#on: [workflow_dispatch, push] +on: + workflow_dispatch: + pull_request: + branches: + - 'develop' + +jobs: + build: + runs-on: ubuntu-20.04 #latest + strategy: + fail-fast: false + matrix: + python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + name: Python ${{ matrix.python-version }} + steps: + - name: Setup OS (Ubuntu) + run: | + sudo apt-get update + sudo apt-get install libudev-dev + sudo apt-get install librrd-dev libpython3-dev + sudo apt-get install gcc --only-upgrade + + - name: Get branch name + run: | + echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + echo ${GITHUB_REF#refs/heads/} + id: extract_branch + + - name: Checkout core from ${{steps.extract_branch.outputs.branch}} branch + uses: actions/checkout@v3 + with: + repository: smarthomeNG/smarthome + ref: ${{steps.extract_branch.outputs.branch}} + + - name: Checkout plugins from develop branch + uses: actions/checkout@v3 + with: + repository: smarthomeNG/plugins + ref: develop + path: plugins + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + - run: python3 -m pip install --upgrade pip + + - name: Install requirements for unit testing + run: pip install -r tests/requirements.txt + - name: Build Requirements for SmartHomeNG + run: python3 tools/build_requirements.py + - name: Install SmartHomeNG base requirements + # base requirements are needed for pytest to run + run: pip install -r requirements/base.txt + + # --- up to here, the workflow is identical for CORE and PLUGINS --- + + - name: '>>> Run Python Unittests for CORE <<<' + working-directory: ./tests + run: pytest diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 282ebbc02b..3eceb4ac74 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -2,9 +2,6 @@ name: "Unittests Core" #on: [workflow_dispatch, push] on: workflow_dispatch: - pull_request: - branches: - - 'develop' push: branches: - '*' @@ -16,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10' ] + python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] name: Python ${{ matrix.python-version }} steps: - name: Setup OS (Ubuntu) @@ -37,7 +34,8 @@ jobs: with: repository: smarthomeNG/smarthome ref: ${{steps.extract_branch.outputs.branch}} - - name: Checkout plugins from ${{steps.extract_branch.outputs.branch}} branch + + - name: Checkout plugins from ${{steps.extract_branch.outputs.branch}} branch (${{ github.event.pull_request.base.ref }}) uses: actions/checkout@v3 with: repository: smarthomeNG/plugins diff --git a/README.md b/README.md index 31f6b07490..a33343da42 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![GitHub license](https://img.shields.io/github/license/smarthomeNG/smarthome.svg)](https://github.com/smarthomeNG/smarthome/blob/master/LICENSE) ![Github Tag](https://img.shields.io/github/v/release/smarthomeng/smarthome?sort=semver) -[![Aktuelles Release](https://img.shields.io/github/workflow/status/smarthomeNG/smarthome/Unittests%20Core/master)](https://github.com/smarthomeNG/smarthome/actions/workflows/unittests.yml) +[![Aktuelles Release](https://img.shields.io/github/actions/workflow/status/smarthomeNG/smarthome/unittests.yml?branch=master)](https://github.com/smarthomeNG/smarthome/actions/workflows/unittests.yml) SmartHomeNG [1] ist eine Software die eine Basis für eine Heimautomation bereitstellt. Über Plugins können spezielle Schnittstellen angesprochen und damit die Funktionalität des Gesamtsystems erweitert werden. diff --git a/bin/locale.yaml b/bin/locale.yaml index 1ad8f159a6..d6802c6bda 100644 --- a/bin/locale.yaml +++ b/bin/locale.yaml @@ -15,6 +15,7 @@ global_translations: 'Firmware': {'de': '=', 'en': '=', 'fr': 'Micrologiciel'} 'Name': {'de': '=', 'en': '=', 'fr': 'Nom'} 'Version': {'de': '=', 'en': '=', 'fr': '='} + 'unbekannt': {'de': '=', 'en': 'unknown', 'fr': 'inconnue'} 'Tag': {'de': '=', 'en': 'day', 'fr': 'jour'} 'Stunde': {'de': '=', 'en': 'hour', 'fr': 'heure'} @@ -24,6 +25,7 @@ global_translations: 'Stunden': {'de': '=', 'en': 'hours', 'fr': 'heures'} 'Minuten': {'de': '=', 'en': 'minutes', 'fr': 'minutes'} 'Sekunden': {'de': '=', 'en': 'seconds', 'fr': 'secondes'} + 'Sek.': {'de': '=', 'en': 'sec.', 'fr': 'secondes'} 'Montag': {'de': '=', 'en': 'Monday', 'fr': 'Lundi'} 'Dienstag': {'de': '=', 'en': 'Tuesday', 'fr': 'Mardi'} @@ -33,6 +35,19 @@ global_translations: 'Samstag': {'de': '=', 'en': 'Saturday', 'fr': 'Samedi'} 'Sonntag': {'de': '=', 'en': 'Sunday', 'fr': 'Dimanche'} + 'Januar': {'de': '=', 'en': 'January', 'fr': 'Janvier'} + 'Februar': {'de': '=', 'en': 'February', 'fr': 'Février'} + 'März': {'de': '=', 'en': 'March', 'fr': 'Mars'} + 'April': {'de': '=', 'en': 'April', 'fr': 'Avril'} + 'Mai': {'de': '=', 'en': 'May', 'fr': 'Mai'} + 'Juni': {'de': '=', 'en': 'June', 'fr': 'Juin'} + 'Juli': {'de': '=', 'en': 'July', 'fr': 'Juillet'} + 'August': {'de': '=', 'en': 'August', 'fr': 'Août'} + 'September': {'de': '=', 'en': 'September', 'fr': 'Septembre'} + 'Oktober': {'de': '=', 'en': 'October', 'fr': 'Octobre'} + 'November': {'de': '=', 'en': 'November', 'fr': 'Novembre'} + 'Dezember': {'de': '=', 'en': 'December', 'fr': 'Décembre'} + 'Host': {'de': '=', 'en': '=', 'fr': 'Hôte'} 'Port': {'de': '=', 'en': '=', 'fr': '='} 'IP': {'de': '=', 'en': '=', 'fr': '='} @@ -74,6 +89,7 @@ global_translations: 'Letztes Update': {'de': '=', 'en': 'Last Update', 'fr': ''} 'Letzter Change': {'de': '=', 'en': 'Last Change', 'fr': ''} + 'Letzte Änderung': {'de': '=', 'en': 'Last Change', 'fr': ''} #Translations for lib.scene: "A second 'scenes' object has been created. There should only be ONE instance of class 'Scenes'!!! Called from: {frame1} ({frame2})": diff --git a/bin/shngversion.py b/bin/shngversion.py index fc27efa1d9..88cb4d0571 100644 --- a/bin/shngversion.py +++ b/bin/shngversion.py @@ -87,10 +87,17 @@ # Update auf 1.9.2.2 wg. Globals innerhalb von Logiken" # Update auf 1.9.3 wg. Release +# Update auf 1.9.3.1 wg. Kennzeichnung des Repo Stands als "nach dem v1.9.3 Release" +# Update auf 1.9.3.2 wg. weitere Loglevel für Plugins; Nutzung Überstzungen für module/http" +# Update auf 1.9.3.3 wg. Änderungen an SmartPlugin +# Update auf 1.9.3.4 wg. Änderungen an SmartPlugin: device_command -> mapping +# Update auf 1.9.3.5 wg. Veränderungen am websocket Modul -shNG_version = '1.9.3' +# Update auf 1.9.4 wg. Release + +shNG_version = '1.9.4' shNG_branch = 'master' -shNG_releasedate = '31. Oktober 2022' # Muss beim Release für den master branch auf das Release Datum gesetzt werden +shNG_releasedate = '14. März 2023' # Muss beim Release für den master branch auf das Release Datum gesetzt werden # --------------------------------------------------------------------------------- FileBASE = None @@ -134,6 +141,9 @@ def get_shng_plugins_version(): plgversion = get_plugins_version().split('-')[0] return Version.format( plgversion ) +def get_shng_version(): + return shNG_branch + def get_shng_version(): commit, commit_short, branch, describe = _get_git_data() VERSION = get_shng_main_version() diff --git a/bin/smarthome.py b/bin/smarthome.py index b95c944674..a079cef0e1 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -28,8 +28,8 @@ # Check mimimum Python Version ##################################################################### import sys -if sys.hexversion < 0x03060000: - print("Sorry your python interpreter ({0}.{1}) is too old. Please update to 3.6 or newer.".format(sys.version_info[0], sys.version_info[1])) +if sys.hexversion < 0x03070000: + print("Sorry your python interpreter ({0}.{1}) is too old. Please update to 3.7 or newer.".format(sys.version_info[0], sys.version_info[1])) exit() diff --git a/dev/sample_mqttplugin/__init__.py b/dev/sample_mqttplugin/__init__.py index 6ade250c04..b86ef1050b 100644 --- a/dev/sample_mqttplugin/__init__.py +++ b/dev/sample_mqttplugin/__init__.py @@ -59,18 +59,6 @@ def __init__(self, sh): # Call init code of parent class (MqttPlugin) super().__init__() - # get the parameters for the plugin (as defined in metadata plugin.yaml): - try: - # webif_pagelength should be included in all plugins using a web interface - # It is used to overwrite the default max number of entries per page in the tables - self.webif_pagelength = self.get_parameter_value('webif_pagelength') - # self.param1 = self.get_parameter_value('param1') - pass - except KeyError as e: - self.logger.critical("Plugin '{}': Inconsistent plugin (invalid metadata definition: {} not defined)".format(self.get_shortname(), e)) - self._init_complete = False - return - # Initialization code goes here # On initialization error use: diff --git a/dev/sample_mqttplugin/plugin.yaml b/dev/sample_mqttplugin/plugin.yaml old mode 100644 new mode 100755 index f395d02ed8..0e8b112a11 --- a/dev/sample_mqttplugin/plugin.yaml +++ b/dev/sample_mqttplugin/plugin.yaml @@ -3,13 +3,13 @@ plugin: # Global plugin attributes type: unknown # plugin type (gateway, interface, protocol, system, web) description: - de: 'Beispiel Plugin mit MQTT Protokoll Nutzung für SmartHomeNG v1.7 und höher' - en: 'Sample plugin using MQTT protocol for SmartHomeNG v1.7 and up' + de: 'Beispiel Plugin mit MQTT Protokoll Nutzung für SmartHomeNG v1.8 und höher' + en: 'Sample plugin using MQTT protocol for SmartHomeNG v1.8 and up' maintainer: msinn # tester: # Who tests this plugin? state: develop # change to ready when done with development # keywords: iot xyz -# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page +# documentation: '' # An url to optional plugin doc - NOT the url to user_doc!!! # support: https://knx-user-forum.de/forum/supportforen/smarthome-py version: 1.0.0 # Plugin version @@ -23,29 +23,7 @@ plugin: parameters: # Definition of parameters to be configured in etc/plugin.yaml (enter 'parameters: NONE', if section should be empty) - # This parameter should be included if plugin provides a web interface. It lets users configure the standard number of entries per page - webif_pagelength: - type: int - default: 100 - valid_list: - - -1 - - 0 - - 25 - - 50 - - 100 - description: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden. - 0 = automatisch, -1 = alle' - en: 'Amount of items being listed in a web interface table per page by default. - 0 = automatic, -1 = all' - description_long: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden.\n - Bei 0 wird die Tabelle automatisch an die Höhe des Browserfensters angepasst.\n - Bei -1 werden alle Tabelleneinträge auf einer Seite angezeigt.' - en: 'Amount of items being listed in a web interface table per page by default.\n - 0 adjusts the table height automatically based on the height of the browser windows.\n - -1 shows all table entries on one page.' - + item_attributes: # Definition of item attributes defined by this plugin (enter 'item_attributes: NONE', if section should be empty) diff --git a/dev/sample_mqttplugin/user_doc.rst b/dev/sample_mqttplugin/user_doc.rst index f4bd6054e6..322e74393d 100644 --- a/dev/sample_mqttplugin/user_doc.rst +++ b/dev/sample_mqttplugin/user_doc.rst @@ -5,6 +5,8 @@ sample_mqtt =========== +Hier sollte eine allgemeine Beschreibung stehen, wozu das Plugin gut ist (was es tut). + .. image:: webif/static/img/plugin_logo.png :alt: plugin logo :width: 300px @@ -14,11 +16,13 @@ sample_mqtt Anforderungen -------------- +============= + Anforderungen des Plugins auflisten. Werden spezielle Soft- oder Hardwarekomponenten benötigt? + Notwendige Software -~~~~~~~~~~~~~~~~~~~ +------------------- * die * benötigte @@ -28,50 +32,67 @@ Notwendige Software Dies beinhaltet Python- und SmartHomeNG-Module Unterstützte Geräte -~~~~~~~~~~~~~~~~~~~ +------------------- * die * unterstütze * Hardware * auflisten +| Konfiguration -------------- +============= + +Die Plugin Parameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind +unter :doc:`/plugins_doc/config/sample_mqtt` beschrieben. plugin.yaml -~~~~~~~~~~~ +----------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. items.yaml -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. logic.yaml -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. Funktionen -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. +| Beispiele ---------- +========= -Hier können ausführlichere Beispiele und Anwendungsfälle beschrieben werden. +Hier können ausführlichere Beispiele und Anwendungsfälle beschrieben werden. (Sonst ist der Abschnitt zu löschen) +| Web Interface -------------- +============= + +Die Datei ``dev/sample_plugin/webif/templates/index.html`` sollte als Grundlage für Webinterfaces genutzt werden. Um Tabelleninhalte nach Spalten filtern und sortieren zu können, muss der entsprechende Code Block mit Referenz auf die relevante Table ID eingefügt werden (siehe Doku). SmartHomeNG liefert eine Reihe Komponenten von Drittherstellern mit, die für die Gestaltung des Webinterfaces genutzt werden können. Erweiterungen dieser Komponenten usw. finden sich im Ordner ``/modules/http/webif/gstatic``. Wenn das Plugin darüber hinaus noch Komponenten benötigt, werden diese im Ordner ``webif/static`` des Plugins abgelegt. + +| + +Version History +=============== + +In diesem Abschnitt kann die Versionshistorie dokumentiert werden, falls der Plugin Autor dieses möchte. Diese Abschnitt +ist optional. + diff --git a/dev/sample_mqttplugin/webif/__init__.py b/dev/sample_mqttplugin/webif/__init__.py old mode 100644 new mode 100755 index 51506fc8d4..82fd82574c --- a/dev/sample_mqttplugin/webif/__init__.py +++ b/dev/sample_mqttplugin/webif/__init__.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab ######################################################################### -# Copyright 2020- +# Copyright 2023- ######################################################################### # This file is part of SmartHomeNG. # https://www.smarthomeNG.de # https://knx-user-forum.de/forum/supportforen/smarthome-py # -# Sample plugin for new plugins to run with SmartHomeNG version 1.5 and -# upwards. +# This file implements the web interface for the Sample plugin. # # SmartHomeNG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +27,7 @@ import datetime import time import os +import json from lib.item import Items from lib.model.mqttplugin import SmartPluginWebIf @@ -71,11 +71,8 @@ def index(self, reload=None): :return: contents of the template after beeing rendered """ self.plugin.get_broker_info() - # Setting pagelength (max. number of table entries per page) for web interface - try: - pagelength = self.plugin.webif_pagelength - except Exception: - pagelength = 100 + # try to get the webif pagelength from the module.yaml configuration + pagelength = self.plugin.get_parameter_value('webif_pagelength') tmpl = self.tplenv.get_template('index.html') # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...) return tmpl.render(p=self.plugin, @@ -109,4 +106,4 @@ def get_data_html(self, dataSet=None): self.logger.error("get_data_html exception: {}".format(e)) return {} - return + return {} diff --git a/dev/sample_mqttplugin/webif/templates/index.html b/dev/sample_mqttplugin/webif/templates/index.html old mode 100644 new mode 100755 index 992ae6453c..2eebfc0564 --- a/dev/sample_mqttplugin/webif/templates/index.html +++ b/dev/sample_mqttplugin/webif/templates/index.html @@ -10,35 +10,39 @@ --> {% block pluginscripts %} @@ -58,7 +63,6 @@ {% block headtable %} - @@ -121,16 +125,27 @@ {% set tab1title = "" ~ p.get_shortname() ~ " Items (" ~ item_count ~ ")" %} {% block bodytab1 %}
- {{ _('Hier kommt der Inhalt des Webinterfaces hin.') }} -
+ +
+ {{ _('Hier kommt der Inhalt des Webinterfaces hin.') }} (optional) +
+ +
+ + + + + + {% for item in items %} {% if p.has_iattr(item.conf, 'mqtt_topic_in') or p.has_iattr(item.conf, 'mqtt_topic_out') %} + @@ -142,6 +157,10 @@ {% endif %} {% endfor %}
ItemItem PathItem ValueTopic In/OutLast UpdateLast Change
{{ item._path }} {{ item._type }} {{ item() }}
+ +
+ Etwaige Informationen unterhalb der Tabelle (optional) +
{% endblock bodytab1 %} diff --git a/dev/sample_plugin/__init__.py b/dev/sample_plugin/__init__.py index 4dec48c427..2df83d96a3 100644 --- a/dev/sample_plugin/__init__.py +++ b/dev/sample_plugin/__init__.py @@ -63,17 +63,6 @@ def __init__(self, sh): # Call init code of parent class (SmartPlugin) super().__init__() - # get the parameters for the plugin (as defined in metadata plugin.yaml): - try: - # webif_pagelength should be included in all plugins using a web interface - # It is used to overwrite the default max number of entries per page in the tables - self.webif_pagelength = self.get_parameter_value('webif_pagelength') - # self.param1 = self.get_parameter_value('param1') - except KeyError as e: - self.logger.critical("Plugin '{}': Inconsistent plugin (invalid metadata definition: {} not defined)".format(self.get_shortname(), e)) - self._init_complete = False - return - # cycle time in seconds, only needed, if hardware/interface needs to be # polled for value changes by adding a scheduler entry in the run method of this plugin # (maybe you want to make it a plugin parameter?) @@ -85,7 +74,6 @@ def __init__(self, sh): # self._init_complete = False # return - # if plugin should start even without web interface self.init_webinterface(WebInterface) # if plugin should not start without web interface # if not self.init_webinterface(): diff --git a/dev/sample_plugin/plugin.yaml b/dev/sample_plugin/plugin.yaml old mode 100644 new mode 100755 index 0b690a332e..c4987179ae --- a/dev/sample_plugin/plugin.yaml +++ b/dev/sample_plugin/plugin.yaml @@ -3,13 +3,13 @@ plugin: # Global plugin attributes type: unknown # plugin type (gateway, interface, protocol, system, web) description: - de: 'Beispiel Plugin für SmartHomeNG v1.5 und höher' - en: 'Sample plugin for SmartHomeNG v1.5 and up' + de: 'Beispiel Plugin für SmartHomeNG v1.8 und höher' + en: 'Sample plugin for SmartHomeNG v1.8 and up' maintainer: # tester: # Who tests this plugin? state: develop # change to ready when done with development # keywords: iot xyz -# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page +# documentation: '' # An url to optional plugin doc - NOT the url to user_doc!!! # support: https://knx-user-forum.de/forum/supportforen/smarthome-py version: 1.0.0 # Plugin version (must match the version specified in __init__.py) @@ -24,29 +24,6 @@ plugin: parameters: # Definition of parameters to be configured in etc/plugin.yaml (enter 'parameters: NONE', if section should be empty) - # This parameter should be included if plugin provides a web interface. It lets users configure the standard number of entries per page - webif_pagelength: - type: int - default: 100 - valid_list: - - -1 - - 0 - - 25 - - 50 - - 100 - description: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden. - 0 = automatisch, -1 = alle' - en: 'Amount of items being listed in a web interface table per page by default. - 0 = automatic, -1 = all' - description_long: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden.\n - Bei 0 wird die Tabelle automatisch an die Höhe des Browserfensters angepasst.\n - Bei -1 werden alle Tabelleneinträge auf einer Seite angezeigt.' - en: 'Amount of items being listed in a web interface table per page by default.\n - 0 adjusts the table height automatically based on the height of the browser windows.\n - -1 shows all table entries on one page.' - param1: type: str description: diff --git a/dev/sample_plugin/user_doc.rst b/dev/sample_plugin/user_doc.rst index 6faa5e249b..4a0b5a2179 100644 --- a/dev/sample_plugin/user_doc.rst +++ b/dev/sample_plugin/user_doc.rst @@ -5,6 +5,8 @@ sample ====== +Hier sollte eine allgemeine Beschreibung stehen, wozu das Plugin gut ist (was es tut). + .. image:: webif/static/img/plugin_logo.png :alt: plugin logo :width: 300px @@ -14,11 +16,13 @@ sample Anforderungen -------------- +============= + Anforderungen des Plugins auflisten. Werden spezielle Soft- oder Hardwarekomponenten benötigt? + Notwendige Software -~~~~~~~~~~~~~~~~~~~ +------------------- * die * benötigte @@ -28,49 +32,55 @@ Notwendige Software Dies beinhaltet Python- und SmartHomeNG-Module Unterstützte Geräte -~~~~~~~~~~~~~~~~~~~ +------------------- * die * unterstütze * Hardware * auflisten +| Konfiguration -------------- +============= + +Die Plugin Parameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind +unter :doc:`/plugins_doc/config/sample` beschrieben. plugin.yaml -~~~~~~~~~~~ +----------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. items.yaml -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. logic.yaml -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. Funktionen -~~~~~~~~~~ +---------- Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde. +| Beispiele ---------- +========= -Hier können ausführlichere Beispiele und Anwendungsfälle beschrieben werden. +Hier können ausführlichere Beispiele und Anwendungsfälle beschrieben werden. (Sonst ist der Abschnitt zu löschen) +| Web Interface -------------- +============= Die Datei ``dev/sample_plugin/webif/templates/index.html`` sollte als Grundlage für Webinterfaces genutzt werden. Um Tabelleninhalte nach Spalten filtern und sortieren zu können, muss der entsprechende Code Block mit Referenz auf die relevante Table ID eingefügt werden (siehe Doku). @@ -78,7 +88,11 @@ SmartHomeNG liefert eine Reihe Komponenten von Drittherstellern mit, die für di Wenn das Plugin darüber hinaus noch Komponenten benötigt, werden diese im Ordner ``webif/static`` des Plugins abgelegt. +| Version History ---------------- +=============== + +In diesem Abschnitt kann die Versionshistorie dokumentiert werden, falls der Plugin Autor dieses möchte. Diese Abschnitt +ist optional. diff --git a/dev/sample_plugin/webif/__init__.py b/dev/sample_plugin/webif/__init__.py old mode 100644 new mode 100755 index e020419771..0e2ed3e904 --- a/dev/sample_plugin/webif/__init__.py +++ b/dev/sample_plugin/webif/__init__.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab ######################################################################### -# Copyright 2020- +# Copyright 2023- ######################################################################### # This file is part of SmartHomeNG. # https://www.smarthomeNG.de # https://knx-user-forum.de/forum/supportforen/smarthome-py # -# Sample plugin for new plugins to run with SmartHomeNG version 1.5 and -# upwards. +# This file implements the web interface for the Sample plugin. # # SmartHomeNG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +27,7 @@ import datetime import time import os +import json from lib.item import Items from lib.model.smartplugin import SmartPluginWebIf @@ -70,12 +70,8 @@ def index(self, reload=None): :return: contents of the template after beeing rendered """ + pagelength = self.plugin.get_parameter_value('webif_pagelength') tmpl = self.tplenv.get_template('index.html') - # Setting pagelength (max. number of table entries per page) for web interface - try: - pagelength = self.plugin.webif_pagelength - except Exception: - pagelength = 100 # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...) return tmpl.render(p=self.plugin, webif_pagelength=pagelength, @@ -93,6 +89,15 @@ def get_data_html(self, dataSet=None): :param dataSet: Dataset for which the data should be returned (standard: None) :return: dict with the data needed to update the web page. """ + # if dataSets are used, define them here + if dataSet == 'overview': + # get the new data from the plugin variable called _webdata + data = self.plugin._webdata + try: + data = json.dumps(data) + return data + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") if dataSet is None: # get the new data data = {} diff --git a/dev/sample_plugin/webif/templates/index.html b/dev/sample_plugin/webif/templates/index.html old mode 100644 new mode 100755 index f3054dded5..220b56dac5 --- a/dev/sample_plugin/webif/templates/index.html +++ b/dev/sample_plugin/webif/templates/index.html @@ -4,6 +4,33 @@ {% set update_interval = 0 %} + +{% set update_interval = (200 * (log_array | length)) %} + + +{% set dataSet = 'devices_info' %} + + +{% set update_params = item_id %} + + +{% set buttons = false %} + + +{% set autorefresh_buttons = false %} + + +{% set reload_button = false %} + + +{% set close_button = false %} + + +{% set row_count = true %} + + +{% set initial_update = true %} - - + @@ -176,12 +194,17 @@ {% set tab1title = "" ~ p.get_shortname() ~ " Items (" ~ item_count ~ ")" %} {% block bodytab1 %}
- {{ _('Hier kommt der Inhalt des Webinterfaces hin.') }} + +
+ {{ _('Hier kommt der Inhalt des Webinterfaces hin.') }} (optional) +
-
+
+ + @@ -190,6 +213,8 @@ {% for item in items %} {% if p.has_iattr(item.conf, '') %} + + @@ -197,7 +222,10 @@ {% endfor %}
{{ _('Item') }} {{ _('Wert') }}
{{ item._path }} {{ item() }}
- + +
+ Etwaige Informationen unterhalb der Tabelle (optional) +
{% endblock bodytab1 %} diff --git a/doc/user/source/conf.py b/doc/user/source/conf.py index 1b8571b96f..9cd7f83083 100644 --- a/doc/user/source/conf.py +++ b/doc/user/source/conf.py @@ -83,7 +83,7 @@ # General information about the project. #project = u'SmartHomeNG' project = u'Dokumentation ' -copyright = u'2016-2022 SmartHomeNG Team - SmartHomeNG is based on smarthome.py © Marcus Popp' +copyright = u'2016-2023 SmartHomeNG Team - SmartHomeNG is based on smarthome.py © Marcus Popp' # The full version, including alpha/beta/rc tags. #release = '1.3a dev (as of 13. October 2017)' 13. October 2017 is replaced by makefile with a date in the form of '2. September 2017' diff --git a/doc/user/source/entwicklung/plugins/kurzanleitung.rst b/doc/user/source/entwicklung/plugins/kurzanleitung.rst new file mode 100644 index 0000000000..7d2224fe96 --- /dev/null +++ b/doc/user/source/entwicklung/plugins/kurzanleitung.rst @@ -0,0 +1,141 @@ + +Kurzanleitung +============= + +Weitere Informationen über die zu erstellenden Methoden und ihre Parameter können in der folgenden Kurzanleitung +gefunden werden. Die Libraries im Verzeichnis ``/lib`` stellen den Kern der Funktionalitäten von smarthomeng bereit. + +.. toctree:: + :maxdepth: 1 + :titlesonly: + :hidden: + + plugin_in5minutes.rst + + +Alle Dateien eines Plugins befinden sich in einem Unterverzeichnis des Verzeichnisses ``/plugins``, welches den Namen des Plugins trägt (nur Kleinbuchstaben). Ein Plugin besteht mindestens den folgenden zwei Dateien: + ++--------------------------+---------------------------------------------------------------------------------+ +| File | Description | ++==========================+=================================================================================+ +| ``__init__.py`` | Der Programmcode des Plugins | ++--------------------------+---------------------------------------------------------------------------------+ +| ``plugin.yaml`` | Die (mehrsprachige) Beschreibung der Metadaten für das Plugin | ++--------------------------+---------------------------------------------------------------------------------+ + + +Je nach Umfang und Erfordernissen sind folgende optionale Dateien hinzuzufügen: + ++--------------------------+---------------------------------------------------------------------------------+ +| File | Description | ++==========================+=================================================================================+ +| ``locale.yaml`` | Enthält die Übersetzungen für die mehrsprachige Implementierung des Web | +| | Interfaces | ++--------------------------+---------------------------------------------------------------------------------+ +| ``requirements.txt`` | Falls ein Plugin ein Python Package nutzt, welches nicht in der | +| | Standardinstallation von Python enthalten ist, muss diese Anforderung in der | +| | Datei ``requirements.txt`` dokumentiert werden. (Für eine Beschreibung der | +| | Dateiformats bitte in der Dokumentation des ``pip`` Kommandos nachlesen: | +| | (https://pip.pypa.io/en/stable/cli/pip_install/) | ++--------------------------+---------------------------------------------------------------------------------+ +| ``user_doc.rst`` | Weitergehende Dokumentation des Plugins, die in die Anwender-Dokumentation | +| | integriert wird. Falls die Dokumentation in ``user_doc.rst`` Bilder oder andere | +| | Assets enthalten soll, ist im Plugin Verzeichnis ein Verzeichnis ``assets`` | +| | anzulegen, welches diese Dateien aufnimmt. | +| | | +| | Die Dokumentationsdatei ``user_doc.rst`` muss nicht in den Metadaten | +| | (``plugin.yaml``) referenziert werden. Sie wird automatisch bei der Generierung | +| | der Anwender Dokumentation von SmartHomeNG integriert. | ++--------------------------+---------------------------------------------------------------------------------+ +| ``developer_doc.rst`` | Weitergehende Entwickler-Dokumentation des Plugins. | ++--------------------------+---------------------------------------------------------------------------------+ +| ``README.md``? | Das in früheren Versionen verwendete ``README``-Format für die Dokumentation | +| | von Plugins ist veraltet. Ein Großteil der Dokumentation ist in die Metadaten- | +| | Dokumentation in ``plugin.yaml`` übergegangen. Die restliche Dokumentation | +| | sollte nur noch im ``user_doc``-Format erfolgen. | +| | Soweit möglich, sollten bestehende ``README`` im Rahmen von Aktualisierungen | +| | in entsprechende ``user_doc`` überführt werden. | ++--------------------------+---------------------------------------------------------------------------------+ + + +.. important:: + + Die erste Überschrift in der Datei ``user_doc.rst`` MUSS dem Short-Name des Plugins in Kleinbuchstaben + entsprechen (identisch zum Namen des Verzeichnisses in dem die Plugin Dateien gespeichert sind. + + Diese Überschrift wird als Eintrag in der Navigation der Dokumentation verwendet. Wenn eine andere Überschrift + gewählt würde, würde die Navigation der Dokumentation inkonsistent werden. + + +Ein Plugin Verzeichnis kann die folgenden Unterverzeichnisse haben: + ++--------------------------+-----------------------------------------------------------------------+ +| Directory | Description | ++==========================+=======================================================================+ +| ``_pv_`` | Ein Verzeichnis, welches eine vorangegangene Version des Plugins | +| | enthält (bei größeren Überarbeitungen des Plugins). Die ```` | +| | muss der Versionsnummer des Plugins entsprechen, wobei die Punkte | +| | durch Unterstriche ersetzt werden. (z.B.: 1.3.5 -> 1_3_5) | ++--------------------------+-----------------------------------------------------------------------+ +| ``assets`` | Verzeichnis für Bilder und Assets der Doku (``user_doc``) | ++--------------------------+-----------------------------------------------------------------------+ +| ``webif`` | Enthält die Dateien eines Webinterfaces, falls das Plugin eines | +| | implementiert. (Es sollte die Unterverzeichnisse ``static`` und | +| | ``templates`` enthalten.) | ++--------------------------+-----------------------------------------------------------------------+ +| ``webif/static`` | Enthält die eigentlichen Dateien des Webinterfaces | ++--------------------------+-----------------------------------------------------------------------+ +| ``webif/static/css`` | Optional, falls cascading style sheets genutzt werden. | ++--------------------------+-----------------------------------------------------------------------+ +| ``webif/static/img`` | Optional, falls das Webinterface Bilder enthält | ++--------------------------+-----------------------------------------------------------------------+ +| ``webif/templates`` | Dieses Verzeichnis enthält die Jinja2 Templates des Webinterfaces und | +| | sollte mindestens ``index.html`` enthalten. | ++--------------------------+-----------------------------------------------------------------------+ + + +Ein Plugin implementiert im Code eine Klasse, welche von der ``class SmartPlugin`` abgeleitet ist. Die Methoden +von ``SmartPlugin`` sind hier dokumentiert: + +.. toctree:: + :maxdepth: 5 + :titlesonly: + + smartplugin + +Plugins welche MQTT nutzen, sollten stattdessen von ``class MqttPlugin`` abgeleitet werden. ``MqttPlugin`` ist +eine Unterklasse von ``SmartPlugin``, die um Methoden zur MQTT-Nutzung erweitert ist. Die Methoden von +``MqttPlugin`` sind hier dokumentiert: + +.. toctree:: + :maxdepth: 5 + :titlesonly: + + mqttplugin + + +.. toctree:: + :maxdepth: 5 + :titlesonly: + :hidden: + + plugin_metadata + plugin_documentation_files + webinterface + multilanguage + sampleplugin + samplemqttplugin + libraries_plugins + + + +Weitergehende und sehr spezifische Informationen über einzelne Plugins sind hier verfügbar: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + /plugins/visu_smartvisu/developer_doc + /plugins/visu_websocket/developer_doc + + diff --git a/doc/user/source/entwicklung/plugins/mqttplugin.rst b/doc/user/source/entwicklung/plugins/mqttplugin.rst index 59c5f1accb..f0746de62e 100644 --- a/doc/user/source/entwicklung/plugins/mqttplugin.rst +++ b/doc/user/source/entwicklung/plugins/mqttplugin.rst @@ -5,11 +5,11 @@ .. role:: redsup .. role:: bluesup -Class MqttPlugin :redsup:`new` -============================== +Class MqttPlugin +================ -Die Klasse MqttPlugin implementiert die Basisklasse aller SmartPlugins die MQTT nutzen. +Die Klasse MqttPlugin implementiert die Basisklasse aller SmartPlugins die MQTT nutzen. Die vorhandenen Methoden sind im Folgenden beschrieben diff --git a/doc/user/source/entwicklung/plugins/plugin_checker.rst b/doc/user/source/entwicklung/plugins/plugin_checker.rst new file mode 100644 index 0000000000..12198e3559 --- /dev/null +++ b/doc/user/source/entwicklung/plugins/plugin_checker.rst @@ -0,0 +1,54 @@ + +.. index:: Plugin-Checker +.. index:: Plugin Checker +.. index:: check_plugin.py + +.. role:: redsup +.. role:: bluesup + +============================ +Plugin-Checker :redsup:`neu` +============================ + +Mit der SmartHomeNG Version 1.9.4 ist ein Tool hinzugekommen, welches Entwickler von Plugins unterstützt. Der +Plugin-Checker führt eine Reihe von formalen Prüfungen durch und gibt Hinweise, wie Fehler und Warnungen beseitigt +werden können. + +Das Tool prüft beim Aufruf für ein Plugin: + +- die Metadaten (nahezu vollständig) +- den Code des Plugins (bisher nur eine rudimentäre Prüfung) +- die Dokumentation user_doc.rst (bisher nur eine rudimentäre Prüfung) + +Der Plugin-Checker liegt im ``tools`` Verzeichnis von SmartHomeNG und wird folgendermaßen aufgerufen: + +.. code-block:: bash + + $ tools/check_plugin.py + +Der Name das Plugins entspricht dem Namen der Verzeichnisses in dem das Plugin liegt. Standardmäßig werden Metadaten, +Code und Dokumentation geprüft. Falls nur eine Teil-Prüfung durchgeführt werden soll, kann die Prüfung durch die +Angabe einer Option eingeschränkt werden. + +.. code-block:: bash + + $ tools/check_plugin.py + + check_plugin.py v0.6.3 - Check plugin for formal errors or improvement potential + + usage: check_plugin.py [-all] [-m | -d | -c | -cd] [pluginname] + + positional arguments: + pluginname name of the plugin to check + + optional arguments: + -all check all plugins + -m check only the metadata of a plugin + -d check only the documentation of a plugin + -c check only the code of a plugin + -cd check only the code and documentation of a plugin + +.. note:: + + Der Umfang der Prüfungen wird successive von Release zu Release ausgebaut. + diff --git a/doc/user/source/entwicklung/plugins/plugins.rst b/doc/user/source/entwicklung/plugins/plugins.rst index 7c602787ee..ad19095353 100644 --- a/doc/user/source/entwicklung/plugins/plugins.rst +++ b/doc/user/source/entwicklung/plugins/plugins.rst @@ -20,145 +20,6 @@ Abschnitt und den links in der Navigation verlinkten Seiten gefunden werden. vorueberlegungen.rst /dev/README.md - - -Kurzanleitung -============= - -Weitere Informationen über die zu erstellenden Methoden und ihre Parameter können in der folgenden Kurzanleitung -gefunden werden. Die Libraries im Verzeichnis ``/lib`` stellen den Kern der Funktionalitäten von smarthomeng bereit. - -.. toctree:: - :maxdepth: 1 - :titlesonly: - :hidden: - - plugin_in5minutes.rst - - -Alle Dateien eines Plugins befinden sich in einem Unterverzeichnis des Verzeichnisses ``/plugins``, welches den Namen des Plugins trägt (nur Kleinbuchstaben). Ein Plugin besteht mindestens den folgenden zwei Dateien: - -+--------------------------+---------------------------------------------------------------------------------+ -| File | Description | -+==========================+=================================================================================+ -| ``__init__.py`` | Der Programmcode des Plugins | -+--------------------------+---------------------------------------------------------------------------------+ -| ``plugin.yaml`` | Die (mehrsprachige) Beschreibung der Metadaten für das Plugin | -+--------------------------+---------------------------------------------------------------------------------+ - - -Je nach Umfang und Erfordernissen sind folgende optionale Dateien hinzuzufügen: - -+--------------------------+---------------------------------------------------------------------------------+ -| File | Description | -+==========================+=================================================================================+ -| ``locale.yaml`` | Enthält die Übersetzungen für die mehrsprachige Implementierung des Web | -| | Interfaces | -+--------------------------+---------------------------------------------------------------------------------+ -| ``requirements.txt`` | Falls ein Plugin ein Python Package nutzt, welches nicht in der | -| | Standardinstallation von Python enthalten ist, muss diese Anforderung in der | -| | Datei ``requirements.txt`` dokumentiert werden. (Für eine Beschreibung der | -| | Dateiformats bitte in der Dokumentation des ``pip`` Kommandos nachlesen: | -| | (https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format) | -+--------------------------+---------------------------------------------------------------------------------+ -| ``user_doc.rst`` | Weitergehende Dokumentation des Plugins, die in die Anwender-Dokumentation | -| | integriert wird. Falls die Dokumentation in ``user_doc.rst`` Bilder oder andere | -| | Assets enthalten soll, ist im Plugin Verzeichnis ein Verzeichnis ``assets`` | -| | anzulegen, welches diese Dateien aufnimmt. | -| | | -| | Die Dokumentationsdatei ``user_doc.rst`` muss nicht in den Metadaten | -| | (``plugin.yaml``) referenziert werden. Sie wird automatisch bei der Generierung | -| | der Anwender Dokumentation von SmartHomeNG integriert. | -+--------------------------+---------------------------------------------------------------------------------+ -| ``developer_doc.rst`` | Weitergehende Entwickler-Dokumentation des Plugins. | -+--------------------------+---------------------------------------------------------------------------------+ -| ``README.md``? | Das in früheren Versionen verwendete ``README``-Format für die Dokumentation | -| | von Plugins ist veraltet. Ein Großteil der Dokumentation ist in die Metadaten- | -| | Dokumentation in ``plugin.yaml`` übergegangen. Die restliche Dokumentation | -| | sollte nur noch im ``user_doc``-Format erfolgen. | -| | Soweit möglich, sollten bestehende ``README`` im Rahmen von Aktualisierungen | -| | in entsprechende ``user_doc`` überführt werden. | -+--------------------------+---------------------------------------------------------------------------------+ - - -.. important:: - - Die erste Überschrift in der Datei ``user_doc.rst`` MUSS dem Short-Name des Plugins in Kleinbuchstaben - entsprechen (identisch zum Namen des Verzeichnisses in dem die Plugin Dateien gespeichert sind. - - Diese Überschrift wird als Eintrag in der Navigation der Dokumentation verwendet. Wenn eine andere Überschrift - gewählt würde, würde die Navigation der Dokumentation inkonsistent werden. - - -Ein Plugin Verzeichnis kann die folgenden Unterverzeichnisse haben: - -+--------------------------+-----------------------------------------------------------------------+ -| Directory | Description | -+==========================+=======================================================================+ -| ``_pv_`` | Ein Verzeichnis, welches eine vorangegangene Version des Plugins | -| | enthält (bei größeren Überarbeitungen des Plugins). Die ```` | -| | muss der Versionsnummer des Plugins entsprechen, wobei die Punkte | -| | durch Unterstriche ersetzt werden. (z.B.: 1.3.5 -> 1_3_5) | -+--------------------------+-----------------------------------------------------------------------+ -| ``assets`` | Verzeichnis für Bilder und Assets der Doku (``user_doc``) | -+--------------------------+-----------------------------------------------------------------------+ -| ``webif`` | Enthält die Dateien eines Webinterfaces, falls das Plugin eines | -| | implementiert. (Es sollte die Unterverzeichnisse ``static`` und | -| | ``templates`` enthalten.) | -+--------------------------+-----------------------------------------------------------------------+ -| ``webif/static`` | Enthält die eigentlichen Dateien des Webinterfaces | -+--------------------------+-----------------------------------------------------------------------+ -| ``webif/static/css`` | Optional, falls cascading style sheets genutzt werden. | -+--------------------------+-----------------------------------------------------------------------+ -| ``webif/static/img`` | Optional, falls das Webinterface Bilder enthält | -+--------------------------+-----------------------------------------------------------------------+ -| ``webif/templates`` | Dieses Verzeichnis enthält die Jinja2 Templates des Webinterfaces und | -| | sollte mindestens ``index.html`` enthalten. | -+--------------------------+-----------------------------------------------------------------------+ - - -Ein Plugin implementiert im Code eine Klasse, welche von der ``class SmartPlugin`` abgeleitet ist. Die Methoden -von ``SmartPlugin`` sind hier dokumentiert: - -.. toctree:: - :maxdepth: 5 - :titlesonly: - - smartplugin - -Plugins welche MQTT nutzen, sollten stattdessen von ``class MqttPlugin`` abgeleitet werden. ``MqttPlugin`` ist -eine Unterklasse von ``SmartPlugin``, die um Methoden zur MQTT-Nutzung erweitert ist. Die Methoden von -``MqttPlugin`` sind hier dokumentiert: - -.. toctree:: - :maxdepth: 5 - :titlesonly: - - mqttplugin - - -.. toctree:: - :maxdepth: 5 - :titlesonly: - :hidden: - - plugin_metadata - plugin_documentation_files - webinterface - multilanguage - sampleplugin - samplemqttplugin - libraries_plugins - - - -Weitergehende und sehr spezifische Informationen über einzelne Plugins sind hier verfügbar: - -.. toctree:: - :maxdepth: 1 - :titlesonly: - - /plugins/visu_smartvisu/developer_doc - /plugins/visu_websocket/developer_doc - + plugin_checker.rst + kurzanleitung.rst diff --git a/doc/user/source/entwicklung/plugins/samplemqttplugin.rst b/doc/user/source/entwicklung/plugins/samplemqttplugin.rst index f531e39d6e..880f1ce034 100644 --- a/doc/user/source/entwicklung/plugins/samplemqttplugin.rst +++ b/doc/user/source/entwicklung/plugins/samplemqttplugin.rst @@ -6,12 +6,12 @@ .. role:: redsup .. role:: bluesup -Beispielplugin mit MQTT-Unterstützung :redsup:`new` -=================================================== +Beispielplugin mit MQTT-Unterstützung +===================================== Dieser Abschnitt zeigt ein Beispielplugin mit MQTT-Unterstützung. Es kann mit oder ohne Webinterface umgesetzt werden. -Das komplette Plugin mit allen Dateien kann auf github unter https://github.com/smarthomeng/smarthome im ``/dev``-Ordner gefunden werden. +Das komplette Plugin mit allen Dateien kann auf github unter https://github.com/smarthomeng/smarthome im ``/dev``-Ordner gefunden werden. :Note: Diese Dokumentation bezieht sich auf Versionen ab v1.7.0. Sie gilt nicht für Versionen vor v1.7.0 @@ -65,15 +65,15 @@ Die Datei für Mehrsprachigkeit: Die Dokumentation: ------------------ -Die folgende Datei skizziert den Mindestumfang für die Plugin-Dokumentation. +Die folgende Datei skizziert den Mindestumfang für die Plugin-Dokumentation. .. literalinclude:: /dev/sample_mqttplugin/user_doc.rst :caption: user_doc.rst .. hint:: - Das in früheren Versionen verwendete **README**-Format für die Dokumentation von Plugins ist veraltet. - Ein Großteil der Dokumentation ist in die Metadaten-Dokumentation in **plugin.yaml** übergegangen. + Das in früheren Versionen verwendete **README**-Format für die Dokumentation von Plugins ist veraltet. + Ein Großteil der Dokumentation ist in die Metadaten-Dokumentation in **plugin.yaml** übergegangen. Die restliche Dokumentation sollte nur noch im **user_doc**-Format erfolgen. Soweit möglich, sollten bestehende **README** im Rahmen von Aktualisierungen in entsprechende **user_doc** überführt werden. diff --git a/doc/user/source/entwicklung/plugins/vorueberlegungen.rst b/doc/user/source/entwicklung/plugins/vorueberlegungen.rst index 919203779d..e3253fb023 100644 --- a/doc/user/source/entwicklung/plugins/vorueberlegungen.rst +++ b/doc/user/source/entwicklung/plugins/vorueberlegungen.rst @@ -1,22 +1,90 @@ +=============== Vorüberlegungen =============== Fragestellungen ---------------- +=============== Bevor die Erstellung eines Plugins beginnt, muss man sich einige Fragen stellen und beantworten: - Welchen Typ soll das Plugin haben, welches erstellt werden soll? +- Soll das Plugin über MQTT mit dem/den anzusteuernden Device(s) kommunizieren? - Soll das externe System direkt angesteuert werden oder soll auf existierende Python Packages zurück gegriffen werden? +- Unter welcher Python Version soll das Plugin laufen? + +| Typ des Plugins ---------------- +=============== + +In SmartHomeNG gibt es folgende Plugin Typen: + +System Plugin +------------- + +System Plugins steuern keine externen Devices an, sondern erweitern die Funktionalität von SmartHomeNG. + +Beispiele für System Plugins sind: **cli**, **database** oder **stateengine** + + +Interface Plugin +---------------- + +Ein Interface Plugin verbindet SmartHomeNG mit genau einem externen Device. Beispiele sind Plugins, die über eine +serielle Schnittstelle kommunizieren. + +Es können aber auch Devices sein, die das Netzwer oder ein Bus System kommunizieren, wenn das Plugin nur genau **ein** +Device ansteuern kann. Ein Interface Plugin kann als Multi-Instance Plugin ausgelegt werden. Dann kann je Instanz ein +Device angeseteuert werden. + +Beispiele für Interface Plugins sind: **avdevice** oder **viessmann** + + +Gateway Plugin +-------------- + +Ein Gateway Plugin verbindet SmartHomeNG mit mit einer Reihe von externen Devices, die über die selbe Art kommunizieren. +Diese Plugins sprechen ein Gateway an, welches dann über ein Bussystem die eigentlichen Devices anspricht (z.B. KNX, +Philips Hue oder Homematic). -... +Bei Gateway Plugins wird über ein Item Attribut gesteuert, welches Device angesprochen werden soll. + +Beispiele für Gateway Plugins sind: **knx**, **hue2**, **onewire** oder **tasmota** + + +Protokoll Plugin +---------------- + +Protokoll Plugins steuern keine Devices direkt an, sondern ermöglichen die Kommunikation über ein Protokoll. + +Beispiele für Protokoll Plugins sind: **mqtt**, **network**, **snmp** oder **wol** + + +Web/Cloud Plugin +---------------- + +Web/Cloud Plugins kommunizieren mit Web- bzw. Cloud Diensten, um Informationen in SmartHomeNG einzulesen bzw. +Informationen aus SmartHomeNG auszugeben. + +Beispiele für Web/Cloud Plugins sind: **alexa4p3**, **ical**, **jsonread** oder **piratewthr** + +| + +Plugin Vorlagen +=============== + +Um ein neues Plugin zu erstellen, ist es am besten die Vorlage zu verwenden, die unter /dev/sample_plugin zu finden ist. +Falls das Plugin über MQTT kommunizieren soll, ist es besser, die Vorlage zu verwenden, die unter /dev/sample_mqttplugin +zu finden ist. + +Falls über MQTT kommuniziert werden soll, wäre es für einfache Aufgabenstellungen auch zu überlegen, kein eigenes Plugin +zu schreiben, sondern das generische mqtt Plugin zu nutzen. + +| Nutzung von Python Packages ---------------------------- +=========================== Die Frage, ob fertige 3rd Party Packages eingesetzt werden sollen, ist gut abzuwägen. Als Vorteil ist anzuführen, dass man sich evtl. erhebliche Programmierung spart. Allerdings kommt es immer wieder vor, dass Python Packages im @@ -29,11 +97,12 @@ Package Version mehr gibt, die installiert werden könnte. Python Version --------------- +============== Je nachdem unter welcher Python Version SmartHomeNG läuft, stehen Python Features zur Verfügung, oder eben auch nicht. -Es sollten nach Möglichkeit nur Python Features genutzt werden, die bereits in der ältesten unterstützten Python -Version (siehe ...) zur Verfügung stehen. Dadurch ist das Plugin am felxibelsten einsetzbar. +Es sollten nach Möglichkeit nur Python Features genutzt werden, die bereits in der ältesten von SmartHomeNG +unterstützten Python Version (siehe :ref:`Hard- u. Software Anforderungen `) zur Verfügung stehen. +Dadurch ist das Plugin am felxibelsten einsetzbar. Sollte auf ein Python Features zurück gegriffen werden (müssen), welches erst in neueren Python Versionen zur Verfügung steht, ist es wichtig, die minimale Python Version unter der das Plugin lauffähig ist, in den Metadaten diff --git a/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst b/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst old mode 100644 new mode 100755 index 838450f522..1380d16ef5 --- a/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst @@ -48,6 +48,14 @@ Die Klasse ``WebInterface`` im Plugin Code muss so erweitert werden, dass sie di :param dataSet: Dataset for which the data should be returned (standard: None) :return: dict with the data needed to update the web page. """ + if dataSet == 'overview': + # get the new data from a variable _webdata + data = self.plugin._webdata + try: + data = json.dumps(data) + return data + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") if dataSet is None: # get the new data data = {} @@ -61,14 +69,15 @@ Die Klasse ``WebInterface`` im Plugin Code muss so erweitert werden, dass sie di try: return json.dumps(data) except Exception as e: - self.logger.error("get_data_html exception: {}".format(e)) + self.logger.error(f"get_data_html exception: {e}") return {} -Die optionale Möglichkeit einen ``dataSet`` anzugeben, ist für zukünftige Erweiterungen vorgesehen. -Darüber soll es möglich werden, Daten in unterschiedlichen Zyklen zu aktualisieren -(z.B. für Daten, deren Ermittlung eine längere Zeit in Anspruch nimmt). +Die optionale Möglichkeit ein ``dataSet`` kann genutzt werden, z.B. Daten in unterschiedlichen +Zyklen zu aktualisieren (z.B. für Daten, deren Ermittlung eine längere Zeit in Anspruch nimmt) +oder nur bestimmte Daten anzufordern. Dieses Feature ist im database Plugin implementiert und +kann dort im Detail angesehen werden. IDs an DOM-Elemente zuweisen @@ -79,7 +88,7 @@ Normalerweise sieht das ``headtable`` wie folgt aus: .. code-block:: html+jinja {% block headtable %} - +
@@ -93,7 +102,7 @@ Normalerweise sieht das ``headtable`` wie folgt aus:
Scanne von IP
{% endblock headtable %} -Bei Tabellen werden die einzelnen Datenzeilen beim Rendern durch die for-Schleife befüllt: +Bei Tabellen im bodytab werden die einzelnen Datenzeilen beim Rendern durch die for-Schleife befüllt: .. code-block:: html+jinja @@ -102,6 +111,7 @@ Bei Tabellen werden die einzelnen Datenzeilen beim Rendern durch die for-Schleif + @@ -111,6 +121,7 @@ Bei Tabellen werden die einzelnen Datenzeilen beim Rendern durch die for-Schleif {% for item in items %} + @@ -125,12 +136,13 @@ Bei Tabellen werden die einzelnen Datenzeilen beim Rendern durch die for-Schleif Um die Werte in die ").insertAfter(H));r.nTBody=fa[0];H=t.children("tfoot");0===H.length&&0").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0/g,Dc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,Ec=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,vb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,aa=function(a){return a&&!0!==a&&"-"!== -a?!1:!0},nc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},oc=function(a,b){xb[b]||(xb[b]=new RegExp(ob(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(xb[b],"."):a},yb=function(a,b,c){var d="string"===typeof a;if(aa(a))return!0;b&&d&&(a=oc(a,b));c&&d&&(a=a.replace(vb,""));return!isNaN(parseFloat(a))&&isFinite(a)},pc=function(a,b,c){return aa(a)?!0:aa(a)||"string"===typeof a?yb(a.replace(Ya,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length; -if(c!==q)for(;ea.length)){var b=a.slice().sort();for(var c=b[0], -d=1,e=b.length;d")[0],Bc=Sa.textContent!==q,Cc=/<.*?>/g,mb=u.util.throttle,tc=[],N=Array.prototype,Fc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e= -l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e=l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=Fc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;ea?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan=na(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},wc=u.util.throttle(function(a){Da(a[0])}, -500),Cb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),wc(c))},xc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(),l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),Ic(c[0]),wc(c))}},Ic=function(a){var b=new B(a), -c=a.aoData;b.off("draw.dt.DT_details column-sizing.dt.DT_details destroy.dt.DT_details");0g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ta(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column];g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g; -g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};z("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=Ab(b);var c=this.iterator("table",function(d){return Kc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh},1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c, -d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",yc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Fa(b.aoData,h,"search"===a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows", -function(a,b,c,d,e){return Fa(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;fd;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b=l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot? -l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;z("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(),b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){z(b+"()",function(){var c=Array.prototype.slice.call(arguments); -c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes());d[b].apply(d,c);return this})});z("clear()",function(){return this.iterator("table",function(a){Ma(a)})});z("settings()",function(){return new B(this.context,this.context)});z("init()",function(){var a=this.context;return a.length?a[0].oInit:null});z("data()",function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});z("destroy()",function(a){a=a|| -!1;return this.iterator("table",function(b){var c=b.oClasses,d=b.nTable,e=b.nTBody,h=b.nTHead,f=b.nTFoot,g=l(d);e=l(e);var k=l(b.nTableWrapper),m=l.map(b.aoData,function(p){return p.nTr}),n;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");l(y).off(".DT-"+b.sInstance);d!=h.parentNode&&(g.children("thead").detach(),g.append(h));f&&d!=f.parentNode&&(g.children("tfoot").detach(),g.append(f));b.aaSorting=[];b.aaSortingFixed= -[];Va(b);l(m).removeClass(b.asStripeClasses.join(" "));l("th, td",h).removeClass(c.sSortable+" "+c.sSortableAsc+" "+c.sSortableDesc+" "+c.sSortableNone);e.children().detach();e.append(m);h=b.nTableWrapper.parentNode;f=a?"remove":"detach";g[f]();k[f]();!a&&h&&(h.insertBefore(d,b.nTableReinsertBefore),g.css("width",b.sDestroyWidth).removeClass(c.sTable),(n=b.asDestroyStripes.length)&&e.children().each(function(p){l(this).addClass(b.asDestroyStripes[p%n])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c, -1)})});l.each(["column","row","cell"],function(a,b){z(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});z("i18n()",function(a,b,c){var d=this.context[0];a=ma(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)});u.version="1.12.1";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1}; -u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null, -sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g, -this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+ -a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries", -sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:l.extend({},u.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};E(u.defaults); -u.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};E(u.defaults.column);u.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null, -bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[], -aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null, -fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+ -a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.12.1/cr-1.5.6/fh-3.2.4/r-2.3.0",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[], -search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty", -sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner", -sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ic=u.ext.pager;l.extend(ic,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Ea(a,b)]},simple_numbers:function(a,b){return["previous", -Ea(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Ea(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Ea(a,b),"last"]},_numbers:Ea,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate,k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){Ta(a,I.data.action,!0)};var ba=0;for(r=w.length;ba").appendTo(x);t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0===h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId, -"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),sb(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}});l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return yb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!Dc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||aa(a)?"date": -null},function(a,b){b=b.oLanguage.sDecimal;return yb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return aa(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);l.extend(u.ext.type.search,{html:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," ").replace(Ya,""):""},string:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," "): -a}});var Xa=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=oc(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return aa(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return aa(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a, -b){return ab?-1:0}});bb("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c,d){l("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx, -b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"==g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var $a=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g, -"""):a},kc=!1,zc=",",Ac=".";if(Intl)try{for(var Ha=(new Intl.NumberFormat).formatToParts(100000.1),ra=0;rah?"-":"",g=parseFloat(h);if(isNaN(g))return $a(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&&(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:$a,filter:$a}}}; -l.extend(u.ext.internal,{_fnExternApiFunc:lc,_fnBuildAjax:Qa,_fnAjaxUpdate:Kb,_fnAjaxParameters:Tb,_fnAjaxUpdateDraw:Ub,_fnAjaxDataSrc:za,_fnAddColumn:cb,_fnColumnOptions:Ia,_fnAdjustColumnSizing:sa,_fnVisibleToColumnIndex:ta,_fnColumnIndexToVisible:ua,_fnVisbleColumns:na,_fnGetColumns:Ka,_fnColumnTypes:eb,_fnApplyColumnDefs:Hb,_fnHungarianMap:E,_fnCamelToHungarian:P,_fnLanguageCompat:la,_fnBrowserDetect:Fb,_fnAddData:ia,_fnAddTr:La,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex: -null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Ib,_fnSplitObjNotation:hb,_fnGetObjectDataFn:ma,_fnSetObjectDataFn:ha,_fnGetDataMaster:ib,_fnClearTable:Ma,_fnDeleteIndex:Na,_fnInvalidate:va,_fnGetRowElements:gb,_fnCreateTr:fb,_fnBuildHead:Jb,_fnDrawHead:xa,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Mb,_fnDetectHeader:wa,_fnGetUniqueThs:Pa,_fnFeatureHtmlFilter:Ob,_fnFilterComplete:ya,_fnFilterCustom:Xb,_fnFilterColumn:Wb,_fnFilter:Vb, -_fnFilterCreateSearch:nb,_fnEscapeRegex:ob,_fnFilterData:Yb,_fnFeatureHtmlInfo:Rb,_fnUpdateInfo:ac,_fnInfoMacros:bc,_fnInitialise:Aa,_fnInitComplete:Ra,_fnLengthChange:pb,_fnFeatureHtmlLength:Nb,_fnFeatureHtmlPaginate:Sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:Pb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Qb,_fnScrollDraw:Ja,_fnApplyToChildren:da,_fnCalculateColumnWidths:db,_fnThrottle:mb,_fnConvertToWidth:cc,_fnGetWidestNode:dc,_fnGetMaxLenString:ec,_fnStringToCss:K,_fnSortFlatten:oa,_fnSort:Lb, -_fnSortAria:gc,_fnSortListener:rb,_fnSortAttachListener:kb,_fnSortingClasses:Va,_fnSortData:fc,_fnSaveState:Da,_fnLoadState:hc,_fnImplementState:tb,_fnSettingsFromNode:Wa,_fnLog:ea,_fnMap:Y,_fnBindAction:sb,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:qb,_fnRenderer:lb,_fnDataSource:Q,_fnRowAttributes:jb,_fnExtend:ub,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$=l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()}; -l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); - - -/*! - DataTables styling integration - ©2018 SpryMedia Ltd - datatables.net/license -*/ -(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);b&&b.fn.dataTable||(b=require("datatables.net")(a,b).$);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c,a,b,d){return c.fn.dataTable}); - - -/*! - SpryMedia Ltd. - - This source file is free software, available under the following license: - MIT license - http://datatables.net/license/mit - - This source file is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - - For details please refer to: http://www.datatables.net - ColReorder 1.5.6 - ©2010-2022 SpryMedia Ltd - datatables.net/license -*/ -(function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(u){return e(u,window,document)}):"object"===typeof exports?module.exports=function(u,t){u||(u=window);t&&t.fn.dataTable||(t=require("datatables.net")(u,t).$);return e(t,u,u.document)}:e(jQuery,window,document)})(function(e,u,t,z){function y(a){for(var b=[],c=0,f=a.length;cb||b>=k)this.oApi._fnLog(a,1,"ColReorder 'from' index is out of bounds: "+b);else if(0>c||c>=k)this.oApi._fnLog(a,1,"ColReorder 'to' index is out of bounds: "+ -c);else{var l=[];var d=0;for(g=k;dthis.s.fixed-1&&fMath.pow(Math.pow(this._fnCursorPosition(a,"pageX")-this.s.mouse.startX,2)+Math.pow(this._fnCursorPosition(a,"pageY")-this.s.mouse.startY,2),.5))return;this._fnCreateDragNode()}this.dom.drag.css({left:this._fnCursorPosition(a,"pageX")-this.s.mouse.offsetX,top:this._fnCursorPosition(a,"pageY")-this.s.mouse.offsetY});var c=this.s.mouse.toIndex;a=this._fnCursorPosition(a,"pageX");for(var f=function(d){for(;0<= -d;){d--;if(0>=d)return null;if(b.s.aoTargets[d+1].x!==b.s.aoTargets[d].x)return b.s.aoTargets[d]}},h=function(){for(var d=0;dp){l= -k;break}}l?(this.dom.pointer.css("left",l.x),this.s.mouse.toIndex=l.to):(this.dom.pointer.css("left",g().x),this.s.mouse.toIndex=g().to);this.s.init.bRealtime&&c!==this.s.mouse.toIndex&&(this.s.dt.oInstance.fnColReorder(this.s.mouse.fromIndex,this.s.mouse.toIndex),this.s.mouse.fromIndex=this.s.mouse.toIndex,""===this.s.dt.oScroll.sX&&""===this.s.dt.oScroll.sY||this.s.dt.oInstance.fnAdjustColumnSizing(!1),this._fnRegions())},_fnMouseUp:function(a){e(t).off(".ColReorder");null!==this.dom.drag&&(this.dom.drag.remove(), -this.dom.pointer.remove(),this.dom.drag=null,this.dom.pointer=null,this.s.dt.oInstance.fnColReorder(this.s.mouse.fromIndex,this.s.mouse.toIndex,!0),this._fnSetColumnIndexes(),""===this.s.dt.oScroll.sX&&""===this.s.dt.oScroll.sY||this.s.dt.oInstance.fnAdjustColumnSizing(!1),this.s.dt.oInstance.oApi._fnSaveState(this.s.dt),null!==this.s.reorderCallback&&this.s.reorderCallback.call(this))},_fnRegions:function(){var a=this.s.dt.aoColumns,b=this._fnIsLtr();this.s.aoTargets.splice(0,this.s.aoTargets.length); -var c=e(this.s.dt.nTable).offset().left,f=[];e.each(a,function(m,k){if(k.bVisible&&"none"!==k.nTh.style.display){k=e(k.nTh);var p=k.offset().left;b&&(p+=k.outerWidth());f.push({index:m,bound:p});c=p}else f.push({index:m,bound:c})});var h=f[0];a=e(a[h.index].nTh).outerWidth();this.s.aoTargets.push({to:0,x:h.bound-a});for(h=0;h
").addClass("DTCR_pointer").css({position:"absolute",top:a?e(e(this.s.dt.nScrollBody).parent()).offset().top:e(this.s.dt.nTable).offset().top,height:a?e(e(this.s.dt.nScrollBody).parent()).height():e(this.s.dt.nTable).height()}).appendTo("body")},_fnSetColumnIndexes:function(){e.each(this.s.dt.aoColumns,function(a,b){e(b.nTh).attr("data-column-index",a)})},_fnCursorPosition:function(a,b){return-1!==a.type.indexOf("touch")?a.originalEvent.touches[0][b]:a[b]},_fnIsLtr:function(){return"rtl"!== -e(this.s.dt.nTable).css("direction")}});r.defaults={aiOrder:null,bEnable:!0,bRealtime:!0,iFixedColumnsLeft:0,iFixedColumnsRight:0,fnReorderCallback:null};r.version="1.5.6";e.fn.dataTable.ColReorder=r;e.fn.DataTable.ColReorder=r;"function"==typeof e.fn.dataTable&&"function"==typeof e.fn.dataTableExt.fnVersionCheck&&e.fn.dataTableExt.fnVersionCheck("1.10.8")?e.fn.dataTableExt.aoFeatures.push({fnInit:function(a){var b=a.oInstance;a._colReorder?b.oApi._fnLog(a,1,"ColReorder attempted to initialise twice. Ignoring second"): -(b=a.oInit,new r(a,b.colReorder||b.oColReorder||{}));return null},cFeature:"R",sFeature:"ColReorder"}):alert("Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download");e(t).on("preInit.dt.colReorder",function(a,b){if("dt"===a.namespace){a=b.oInit.colReorder;var c=D.defaults.colReorder;if(a||c)c=e.extend({},a,c),!1!==a&&new r(b,c)}});e.fn.dataTable.Api.register("colReorder.reset()",function(){return this.iterator("table",function(a){a._colReorder.fnReset()})});e.fn.dataTable.Api.register("colReorder.order()", -function(a,b){return a?this.iterator("table",function(c){c._colReorder.fnOrder(a,b)}):this.context.length?this.context[0]._colReorder.fnOrder():null});e.fn.dataTable.Api.register("colReorder.transpose()",function(a,b){return this.context.length&&this.context[0]._colReorder?this.context[0]._colReorder.fnTranspose(a,b):a});e.fn.dataTable.Api.register("colReorder.move()",function(a,b,c,f){this.context.length&&(this.context[0]._colReorder.s.dt.oInstance.fnColReorder(a,b,c,f),this.context[0]._colReorder._fnSetColumnIndexes()); -return this});e.fn.dataTable.Api.register("colReorder.enable()",function(a){return this.iterator("table",function(b){b._colReorder&&b._colReorder.fnEnable(a)})});e.fn.dataTable.Api.register("colReorder.disable()",function(){return this.iterator("table",function(a){a._colReorder&&a._colReorder.fnDisable()})});return r}); - - -/*! - SpryMedia Ltd. - - This source file is free software, available under the following license: - MIT license - http://datatables.net/license/mit - - This source file is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - - For details please refer to: http://www.datatables.net - FixedHeader 3.2.4 - ©2009-2022 SpryMedia Ltd - datatables.net/license -*/ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,h,g){b instanceof String&&(b=String(b));for(var l=b.length,k=0;k'), -placeholder:null},footer:{host:null,floating:null,floatingParent:b('
'),placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();a=a.settings()[0];if(a._fixedHeader)throw"FixedHeader already initialised on table "+a.nTable.id;a._fixedHeader=this;this._constructor()};b.extend(t.prototype,{destroy:function(){var a=this.dom;this.s.dt.off(".dtfc");b(h).off(this.s.namespace);a.header.rightBlocker&&a.header.rightBlocker.remove(); -a.header.leftBlocker&&a.header.leftBlocker.remove();a.footer.rightBlocker&&a.footer.rightBlocker.remove();a.footer.leftBlocker&&a.footer.leftBlocker.remove();this.c.header&&this._modeChange("in-place","header",!0);this.c.footer&&a.tfoot.length&&this._modeChange("in-place","footer",!0)},enable:function(a,d){this.s.enable=a;if(d||d===l)this._positions(),this._scroll(!0)},enabled:function(){return this.s.enable},headerOffset:function(a){a!==l&&(this.c.headerOffset=a,this.update());return this.c.headerOffset}, -footerOffset:function(a){a!==l&&(this.c.footerOffset=a,this.update());return this.c.footerOffset},update:function(a){if(this.s.enable){var d=this.s.dt.table().node();b(d).is(":visible")?this.enable(!0,!1):this.enable(!1,!1);0!==b(d).children("thead").length&&(this._positions(),this._scroll(a!==l?a:!0))}},_constructor:function(){var a=this,d=this.s.dt;b(h).on("scroll"+this.s.namespace,function(){a._scroll()}).on("resize"+this.s.namespace,k.util.throttle(function(){a.s.position.windowHeight=b(h).height(); -a.update()},50));var c=b(".fh-fixedHeader");!this.c.headerOffset&&c.length&&(this.c.headerOffset=c.outerHeight());c=b(".fh-fixedFooter");!this.c.footerOffset&&c.length&&(this.c.footerOffset=c.outerHeight());d.on("column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc",function(f,e){a.update()}).on("draw.dt.dtfc",function(f,e){a.update(e===d.settings()[0]?!1:!0)});d.on("destroy.dtfc",function(){a.destroy()});this._positions();this._scroll()},_clone:function(a, -d){var c=this,f=this.s.dt,e=this.dom[a],n="header"===a?this.dom.thead:this.dom.tfoot;if("footer"!==a||!this._scrollEnabled())if(!d&&e.floating)e.floating.removeClass("fixedHeader-floating fixedHeader-locked");else{d=b(g).scrollLeft();var q=b(g).scrollTop();e.floating&&(null!==e.placeholder&&e.placeholder.remove(),this._unsize(a),e.floating.children().detach(),e.floating.remove());var p=b(f.table().node()),r=b(p.parent()),m=this._scrollEnabled();e.floating=b(f.table().node().cloneNode(!1)).attr("aria-hidden", -"true").css({"table-layout":"fixed",top:0,left:0}).removeAttr("id").append(n);e.floatingParent.css({width:r.width(),overflow:"hidden",height:"fit-content",position:"fixed",left:m?p.offset().left+r.scrollLeft():0}).css("header"===a?{top:this.c.headerOffset,bottom:""}:{top:"",bottom:this.c.footerOffset}).addClass("footer"===a?"dtfh-floatingparentfoot":"dtfh-floatingparenthead").append(e.floating).appendTo("body");this._stickyPosition(e.floating,"-");a=function(){var u=r.scrollLeft();c.s.scrollLeft= -{footer:u,header:u};e.floatingParent.scrollLeft(c.s.scrollLeft.header)};a();r.off("scroll.dtfh").on("scroll.dtfh",a);e.placeholder=n.clone(!1);e.placeholder.find("*[id]").removeAttr("id");e.host.prepend(e.placeholder);this._matchWidths(e.placeholder,e.floating);b(g).scrollTop(q).scrollLeft(d)}},_stickyPosition:function(a,d){if(this._scrollEnabled()){var c=this,f="rtl"===b(c.s.dt.table().node()).css("direction");a.find("th").each(function(){if("sticky"===b(this).css("position")){var e=b(this).css("right"), -n=b(this).css("left");"auto"===e||f?"auto"!==n&&f&&(e=+n.replace(/px/g,"")+("-"===d?-1:1)*c.s.dt.settings()[0].oBrowser.barWidth,b(this).css("left",0u?e.tfootHeight:z+e.tfootHeight-u:c+this.c.headerOffset+e.theadHeight-m;m="header"===d?"top":"bottom";c=this.c[d+ -"Offset"]-(0u&&q+this.c.headerOffset+m.theadHeighte||this.dom.header.floatingParent===l?a=!0:this.dom.header.floatingParent.css({top:this.c.headerOffset, -position:"fixed"}).append(this.dom.header.floating)):p="below":p="in-place",(a||p!==this.s.headerMode)&&this._modeChange(p,"header",a),this._horizontal("header",n));var x={offset:{top:0,left:0},height:0},y={offset:{top:0,left:0},height:0};this.c.footer&&this.dom.tfoot.length&&(this.s.enable?!m.visible||m.tfootBottom+this.c.footerOffset<=r?m="in-place":e+m.tfootHeight+this.c.footerOffset>r&&u+this.c.footerOffsetq&&(d=q-f.top,r=r+(d>-x.height?d:0)-(x.offset.top+(d<-x.height?x.height:0)+y.height),0>r&&(r=0),c.outerHeight(r),Math.round(c.outerHeight())>=Math.round(r)?b(this.dom.tfoot.parent()).addClass("fixedHeader-floating"):b(this.dom.tfoot.parent()).removeClass("fixedHeader-floating"))); -this.dom.header.floating&&this.dom.header.floatingParent.css("left",z-n);this.dom.footer.floating&&this.dom.footer.floatingParent.css("left",z-n);this.s.dt.settings()[0]._fixedColumns!==l&&(c=function(A,C,v){v===l&&(v=b("div.dtfc-"+A+"-"+C+"-blocker"),v=0===v.length?null:v.clone().appendTo("body").css("z-index",1));null!==v&&v.css({top:"top"===C?x.offset.top:y.offset.top,left:"right"===A?z+B-v.width():z});return v},this.dom.header.rightBlocker=c("right","top",this.dom.header.rightBlocker),this.dom.header.leftBlocker= -c("left","top",this.dom.header.leftBlocker),this.dom.footer.rightBlocker=c("right","bottom",this.dom.footer.rightBlocker),this.dom.footer.leftBlocker=c("left","bottom",this.dom.footer.leftBlocker))}},_scrollEnabled:function(){var a=this.s.dt.settings()[0].oScroll;return""!==a.sY||""!==a.sX?!0:!1}});t.version="3.2.4";t.defaults={header:!0,footer:!1,headerOffset:0,footerOffset:0};b.fn.dataTable.FixedHeader=t;b.fn.DataTable.FixedHeader=t;b(g).on("init.dt.dtfh",function(a,d,c){"dt"===a.namespace&&(a= -d.oInit.fixedHeader,c=k.defaults.fixedHeader,!a&&!c||d._fixedHeader||(c=b.extend({},c,a),!1!==a&&new t(d,c)))});k.Api.register("fixedHeader()",function(){});k.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(a){(a=a._fixedHeader)&&a.update()})});k.Api.register("fixedHeader.enable()",function(a){return this.iterator("table",function(d){d=d._fixedHeader;a=a!==l?a:!0;d&&a!==d.enabled()&&d.enable(a)})});k.Api.register("fixedHeader.enabled()",function(){if(this.context.length){var a= -this.context[0]._fixedHeader;if(a)return a.enabled()}return!1});k.Api.register("fixedHeader.disable()",function(){return this.iterator("table",function(a){(a=a._fixedHeader)&&a.enabled()&&a.enable(!1)})});b.each(["header","footer"],function(a,d){k.Api.register("fixedHeader."+d+"Offset()",function(c){var f=this.context;return c===l?f.length&&f[0]._fixedHeader?f[0]._fixedHeader[d+"Offset"]():l:this.iterator("table",function(e){if(e=e._fixedHeader)e[d+"Offset"](c)})})});return t}); - - -/*! - SpryMedia Ltd. +/*! DataTables 1.13.3 + * ©2008-2023 SpryMedia Ltd - datatables.net/license + */ +!function(n){"use strict";"function"==typeof define&&define.amd?define(["jquery"],function(t){return n(t,window,document)}):"object"==typeof exports?module.exports=function(t,e){return t=t||window,e=e||("undefined"!=typeof window?require("jquery"):require("jquery")(t)),n(e,t,t.document)}:window.DataTable=n(jQuery,window,document)}(function(P,j,y,N){"use strict";function d(t){var e=parseInt(t,10);return!isNaN(e)&&isFinite(t)?e:null}function l(t,e,n){var a=typeof t,r="string"==a;return"number"==a||"bigint"==a||!!h(t)||(e&&r&&(t=G(t,e)),n&&r&&(t=t.replace(q,"")),!isNaN(parseFloat(t))&&isFinite(t))}function a(t,e,n){var a;return!!h(t)||(h(a=t)||"string"==typeof a)&&!!l(t.replace(V,""),e,n)||null}function m(t,e,n,a){var r=[],o=0,i=e.length;if(a!==N)for(;o").appendTo(l)),h.nTHead=n[0],l.children("tbody")),n=(0===a.length&&(a=P("
").insertAfter(n)),h.nTBody=a[0],l.children("tfoot"));if(0===(n=0===n.length&&0").appendTo(l):n).length||0===n.children().length?l.addClass(p.sNoFooter):0/g,X=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,J=new RegExp("(\\"+["/",".","*","+","?","|","(",")","[","]","{","}","\\","$","^","-"].join("|\\")+")","g"),q=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,h=function(t){return!t||!0===t||"-"===t},G=function(t,e){return c[e]||(c[e]=new RegExp(Ot(e),"g")),"string"==typeof t&&"."!==e?t.replace(/\./g,"").replace(c[e],"."):t},H=function(t,e,n){var a=[],r=0,o=t.length;if(n!==N)for(;r").css({position:"fixed",top:0,left:-1*P(j).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(P("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(P("
").css({width:"100%",height:10}))).appendTo("body")).children()).children(),e.barWidth=a[0].offsetWidth-a[0].clientWidth,e.bScrollOversize=100===r[0].offsetWidth&&100!==a[0].clientWidth,e.bScrollbarLeft=1!==Math.round(r.offset().left),e.bBounding=!!n[0].getBoundingClientRect().width,n.remove()),P.extend(t.oBrowser,C.__browser),t.oScroll.iBarWidth=C.__browser.barWidth}function et(t,e,n,a,r,o){var i,l=a,s=!1;for(n!==N&&(i=n,s=!0);l!==r;)t.hasOwnProperty(l)&&(i=s?e(i,t[l],l,t):t[l],s=!0,l+=o);return i}function nt(t,e){var n=C.defaults.column,a=t.aoColumns.length,n=P.extend({},C.models.oColumn,n,{nTh:e||y.createElement("th"),sTitle:n.sTitle||(e?e.innerHTML:""),aDataSort:n.aDataSort||[a],mData:n.mData||a,idx:a}),n=(t.aoColumns.push(n),t.aoPreSearchCols);n[a]=P.extend({},C.models.oSearch,n[a]),at(t,a,P(e).data())}function at(t,e,n){function a(t){return"string"==typeof t&&-1!==t.indexOf("@")}var e=t.aoColumns[e],r=t.oClasses,o=P(e.nTh),i=(!e.sWidthOrig&&(e.sWidthOrig=o.attr("width")||null,u=(o.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/))&&(e.sWidthOrig=u[1]),n!==N&&null!==n&&(Q(n),w(C.defaults.column,n,!0),n.mDataProp===N||n.mData||(n.mData=n.mDataProp),n.sType&&(e._sManualType=n.sType),n.className&&!n.sClass&&(n.sClass=n.className),n.sClass&&o.addClass(n.sClass),u=e.sClass,P.extend(e,n),F(e,n,"sWidth","sWidthOrig"),u!==e.sClass&&(e.sClass=u+" "+e.sClass),n.iDataSort!==N&&(e.aDataSort=[n.iDataSort]),F(e,n,"aDataSort")),e.mData),l=A(i),s=e.mRender?A(e.mRender):null,u=(e._bAttrSrc=P.isPlainObject(i)&&(a(i.sort)||a(i.type)||a(i.filter)),e._setter=null,e.fnGetData=function(t,e,n){var a=l(t,e,N,n);return s&&e?s(a,e,t,n):a},e.fnSetData=function(t,e,n){return b(i)(t,e,n)},"number"!=typeof i&&(t._rowReadObject=!0),t.oFeatures.bSort||(e.bSortable=!1,o.addClass(r.sSortableNone)),-1!==P.inArray("asc",e.asSorting)),n=-1!==P.inArray("desc",e.asSorting);e.bSortable&&(u||n)?u&&!n?(e.sSortingClass=r.sSortableAsc,e.sSortingClassJUI=r.sSortJUIAscAllowed):!u&&n?(e.sSortingClass=r.sSortableDesc,e.sSortingClassJUI=r.sSortJUIDescAllowed):(e.sSortingClass=r.sSortable,e.sSortingClassJUI=r.sSortJUI):(e.sSortingClass=r.sSortableNone,e.sSortingClassJUI="")}function O(t){if(!1!==t.oFeatures.bAutoWidth){var e=t.aoColumns;ee(t);for(var n=0,a=e.length;ne&&t[r]--;-1!=a&&n===N&&t.splice(a,1)}function bt(n,a,t,e){function r(t,e){for(;t.childNodes.length;)t.removeChild(t.firstChild);t.innerHTML=S(n,a,e,"display")}var o,i,l=n.aoData[a];if("dom"!==t&&(t&&"auto"!==t||"dom"!==l.src)){var s=l.anCells;if(s)if(e!==N)r(s[e],e);else for(o=0,i=s.length;o").appendTo(r)),c=0,f=s.length;c=s.fnRecordsDisplay()?0:l,s.iInitDisplayStart=-1);var n=R(t,"aoPreDrawCallback","preDraw",[t]);if(-1!==P.inArray(!1,n))D(t,!1);else{var a=[],r=0,o=t.asStripeClasses,i=o.length,l=t.oLanguage,s="ssp"==E(t),u=t.aiDisplay,n=t._iDisplayStart,c=t.fnDisplayEnd();if(t.bDrawing=!0,t.bDeferLoading)t.bDeferLoading=!1,t.iDraw++,D(t,!1);else if(s){if(!t.bDestroying&&!e)return void xt(t)}else t.iDraw++;if(0!==u.length)for(var f=s?t.aoData.length:c,d=s?0:n;d",{class:i?o[0]:""}).append(P("
").addClass(e),P("td",n).addClass(e).html(t)[0].colSpan=T(o),l.push(n[0]))}var l=[];i(e,n),t._details&&t._details.detach(),t._details=P(l),t._detailsShow&&t._details.insertAfter(t.nTr)}function xe(t,e){var n=t.context;if(n.length&&t.length){var a=n[0].aoData[t[0]];if(a._details){(a._detailsShow=e)?(a._details.insertAfter(a.nTr),P(a.nTr).addClass("dt-hasChild")):(a._details.detach(),P(a.nTr).removeClass("dt-hasChild")),R(n[0],null,"childRow",[e,t.row(t[0])]);var s=n[0],r=new B(s),a=".dt.DT_details",e="draw"+a,t="column-sizing"+a,a="destroy"+a,u=s.aoData;if(r.off(e+" "+t+" "+a),H(u,"_details").length>0){r.on(e,function(t,e){if(s!==e)return;r.rows({page:"current"}).eq(0).each(function(t){var e=u[t];if(e._detailsShow)e._details.insertAfter(e.nTr)})});r.on(t,function(t,e,n,a){if(s!==e)return;var r,o=T(e);for(var i=0,l=u.length;it?new B(e[t],this[t]):null},filter:function(t){var e=[];if(o.filter)e=o.filter.call(this,t,this);else for(var n=0,a=this.length;n").appendTo(t);p(u,n)}else{switch(g=null,b=n,a=c.iTabIndex,n){case"ellipsis":t.append('');break;case"first":g=S.sFirst,0===d&&(a=-1,b+=" "+o);break;case"previous":g=S.sPrevious,0===d&&(a=-1,b+=" "+o);break;case"next":g=S.sNext,0!==h&&d!==h-1||(a=-1,b+=" "+o);break;case"last":g=S.sLast,0!==h&&d!==h-1||(a=-1,b+=" "+o);break;default:g=c.fnFormatNumber(n+1),b=d===n?m.sPageButtonActive:""}null!==g&&(u=c.oInit.pagingTag||"a",r=-1!==b.indexOf(o),me(P("<"+u+">",{class:m.sPageButton+" "+b,"aria-controls":c.sTableId,"aria-disabled":r?"true":null,"aria-label":v[n],"aria-role":"link","aria-current":b===m.sPageButtonActive?"page":null,"data-dt-idx":n,tabindex:a,id:0===f&&"string"==typeof n?c.sTableId+"_"+n:null}).html(g).appendTo(t),{action:n},i))}}var g,b,n,m=c.oClasses,S=c.oLanguage.oPaginate,v=c.oLanguage.oAria.paginate||{};try{n=P(t).find(y.activeElement).data("dt-idx")}catch(t){}p(P(t).empty(),e),n!==N&&P(t).find("[data-dt-idx="+n+"]").trigger("focus")}}}),P.extend(C.ext.type.detect,[function(t,e){e=e.oLanguage.sDecimal;return l(t,e)?"num"+e:null},function(t,e){var n;return(!t||t instanceof Date||X.test(t))&&(null!==(n=Date.parse(t))&&!isNaN(n)||h(t))?"date":null},function(t,e){e=e.oLanguage.sDecimal;return l(t,e,!0)?"num-fmt"+e:null},function(t,e){e=e.oLanguage.sDecimal;return a(t,e)?"html-num"+e:null},function(t,e){e=e.oLanguage.sDecimal;return a(t,e,!0)?"html-num-fmt"+e:null},function(t,e){return h(t)||"string"==typeof t&&-1!==t.indexOf("<")?"html":null}]),P.extend(C.ext.type.search,{html:function(t){return h(t)?t:"string"==typeof t?t.replace(U," ").replace(V,""):""},string:function(t){return!h(t)&&"string"==typeof t?t.replace(U," "):t}});function ke(t,e,n,a){var r;return 0===t||t&&"-"!==t?"number"==(r=typeof t)||"bigint"==r?t:+(t=(t=e?G(t,e):t).replace&&(n&&(t=t.replace(n,"")),a)?t.replace(a,""):t):-1/0}function Me(n){P.each({num:function(t){return ke(t,n)},"num-fmt":function(t){return ke(t,n,q)},"html-num":function(t){return ke(t,n,V)},"html-num-fmt":function(t){return ke(t,n,V,q)}},function(t,e){p.type.order[t+n+"-pre"]=e,t.match(/^html\-/)&&(p.type.search[t+n]=p.type.search.html)})}P.extend(p.type.order,{"date-pre":function(t){t=Date.parse(t);return isNaN(t)?-1/0:t},"html-pre":function(t){return h(t)?"":t.replace?t.replace(/<.*?>/g,"").toLowerCase():t+""},"string-pre":function(t){return h(t)?"":"string"==typeof t?t.toLowerCase():t.toString?t.toString():""},"string-asc":function(t,e){return t").addClass(l.sSortJUIWrapper).append(o.contents()).append(P("").addClass(l.sSortIcon+" "+i.sSortingClassJUI)).appendTo(o),P(r.nTable).on("order.dt.DT",function(t,e,n,a){r===e&&(e=i.idx,o.removeClass(l.sSortAsc+" "+l.sSortDesc).addClass("asc"==a[e]?l.sSortAsc:"desc"==a[e]?l.sSortDesc:i.sSortingClass),o.find("span."+l.sSortIcon).removeClass(l.sSortJUIAsc+" "+l.sSortJUIDesc+" "+l.sSortJUI+" "+l.sSortJUIAscAllowed+" "+l.sSortJUIDescAllowed).addClass("asc"==a[e]?l.sSortJUIAsc:"desc"==a[e]?l.sSortJUIDesc:i.sSortingClassJUI))})}}});function We(t){return"string"==typeof(t=Array.isArray(t)?t.join(","):t)?t.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):t}function Ee(t,e,n,a,r){return j.moment?t[e](r):j.luxon?t[n](r):a?t[a](r):t}var Be=!1;function Ue(t,e,n){var a;if(j.moment){if(!(a=j.moment.utc(t,e,n,!0)).isValid())return null}else if(j.luxon){if(!(a=e&&"string"==typeof t?j.luxon.DateTime.fromFormat(t,e):j.luxon.DateTime.fromISO(t)).isValid)return null;a.setLocale(n)}else e?(Be||alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17"),Be=!0):a=new Date(t);return a}function Ve(s){return function(a,r,o,i){0===arguments.length?(o="en",a=r=null):1===arguments.length?(o="en",r=a,a=null):2===arguments.length&&(o=r,r=a,a=null);var l="datetime-"+r;return C.ext.type.order[l]||(C.ext.type.detect.unshift(function(t){return t===l&&l}),C.ext.type.order[l+"-asc"]=function(t,e){t=t.valueOf(),e=e.valueOf();return t===e?0:tthis.s.fixed-1&&t").addClass("DTCR_pointer").css({position:"absolute",top:R(t?R(this.s.dt.nScrollBody).parent():this.s.dt.nTable).offset().top,height:R(t?R(this.s.dt.nScrollBody).parent():this.s.dt.nTable).height()}).appendTo("body")},_fnSetColumnIndexes:function(){R.each(this.s.dt.aoColumns,function(t,e){R(e.nTh).attr("data-column-index",t)})},_fnCursorPosition:function(t,e){return(-1!==t.type.indexOf("touch")?t.originalEvent.touches[0]:t)[e]},_fnIsLtr:function(){return"rtl"!==R(this.s.dt.nTable).css("direction")}}),i.defaults={aiOrder:null,bEnable:!0,bRealtime:!0,iFixedColumnsLeft:0,iFixedColumnsRight:0,fnReorderCallback:null},i.version="1.6.1",R.fn.dataTable.ColReorder=i,R.fn.DataTable.ColReorder=i,"function"==typeof R.fn.dataTable&&"function"==typeof R.fn.dataTableExt.fnVersionCheck&&R.fn.dataTableExt.fnVersionCheck("1.10.8")?R.fn.dataTableExt.aoFeatures.push({fnInit:function(t){var e=t.oInstance;return t._colReorder?e.oApi._fnLog(t,1,"ColReorder attempted to initialise twice. Ignoring second"):(e=(e=t.oInit).colReorder||e.oColReorder||{},new i(t,e)),null},cFeature:"R",sFeature:"ColReorder"}):alert("Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download"),R(s).on("preInit.dt.colReorder",function(t,e){var o,n;"dt"===t.namespace&&(t=e.oInit.colReorder,o=r.defaults.colReorder,t||o)&&(n=R.extend({},t,o),!1!==t)&&new i(e,n)}),R.fn.dataTable.Api.register("colReorder.reset()",function(){return this.iterator("table",function(t){t._colReorder.fnReset()})}),R.fn.dataTable.Api.register("colReorder.order()",function(e,o){return e?this.iterator("table",function(t){t._colReorder.fnOrder(e,o)}):this.context.length?this.context[0]._colReorder.fnOrder():null}),R.fn.dataTable.Api.register("colReorder.transpose()",function(t,e){return this.context.length&&this.context[0]._colReorder?this.context[0]._colReorder.fnTranspose(t,e):t}),R.fn.dataTable.Api.register("colReorder.move()",function(t,e,o,n){return this.context.length&&(this.context[0]._colReorder.s.dt.oInstance.fnColReorder(t,e,o,n),this.context[0]._colReorder._fnSetColumnIndexes()),this}),R.fn.dataTable.Api.register("colReorder.enable()",function(e){return this.iterator("table",function(t){t._colReorder&&t._colReorder.fnEnable(e)})}),R.fn.dataTable.Api.register("colReorder.disable()",function(){return this.iterator("table",function(t){t._colReorder&&t._colReorder.fnDisable()})}),r}); - For details please refer to: http://www.datatables.net - Responsive 2.3.0 - 2014-2022 SpryMedia Ltd - datatables.net/license -*/ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,k,m){b instanceof String&&(b=String(b));for(var n=b.length,p=0;ptd, >th", -f).each(function(e){e=c.column.index("toData",e);!1===a.s.current[e]&&b(this).css("display","none")})});c.on("destroy.dtr",function(){c.off(".dtr");b(c.table().body()).off(".dtr");b(k).off("resize.dtr orientationchange.dtr");c.cells(".dtr-control").nodes().to$().removeClass("dtr-control");b.each(a.s.current,function(f,l){!1===l&&a._setColumnVis(f,!0)})});this.c.breakpoints.sort(function(f,l){return f.widthl.width?-1:0});this._classLogic();this._resizeAuto();d=this.c.details;!1!== -d.type&&(a._detailsInit(),c.on("column-visibility.dtr",function(){a._timer&&clearTimeout(a._timer);a._timer=setTimeout(function(){a._timer=null;a._classLogic();a._resizeAuto();a._resize(!0);a._redrawChildren()},100)}),c.on("draw.dtr",function(){a._redrawChildren()}),b(c.table().node()).addClass("dtr-"+d.type));c.on("column-reorder.dtr",function(f,l,h){a._classLogic();a._resizeAuto();a._resize(!0)});c.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});c.on("column-calc.dt",function(f, -l){f=a.s.current;for(var h=0;hh-d[q].minWidth?(r=!0,l[q]=!1):l[q]=!0,h-=d[q].minWidth)}f=!1;e=0;for(g=d.length;e=q&&f(h,c[e].name);else if("not-"===r)for(e=0,r=c.length;eg?c.columns().eq(0).length+ -g:g;if(c.cell(this).index().column!==l)return}l=c.row(b(this).closest("tr"));"click"===f.type?a._detailsDisplay(l,!1):"mousedown"===f.type?b(this).css("outline","none"):"mouseup"===f.type&&b(this).trigger("blur").css("outline","")}})},_detailsObj:function(a){var c=this,d=this.s.dt;return b.map(this.s.columns,function(g,f){if(!g.never&&!g.control)return g=d.settings()[0].aoColumns[f],{className:g.sClass,columnIndex:f,data:d.cell(a,f).render(c.c.orthogonal),hidden:d.column(f).visible()&&!c.s.current[f], -rowIndex:a,title:null!==g.sTitle?g.sTitle:b(d.column(f).header()).text()}})},_find:function(a){for(var c=this.c.breakpoints,d=0,g=c.length;d").append(h).appendTo(f)}b("").append(l).appendTo(g);"inline"===this.c.details.type&&b(d).addClass("dtr-inline collapsed");b(d).find("[name]").removeAttr("name");b(d).css("position", -"relative");d=b("
").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(d);d.insertBefore(a.table().node());l.each(function(e){e=a.column.index("fromVisible",e);c[e].minWidth=this.offsetWidth||0});d.remove()}},_responsiveOnlyHidden:function(){var a=this.s.dt;return b.map(this.s.current,function(c,d){return!1===a.column(d).visible()?!0:c})},_setColumnVis:function(a,c){var d=this.s.dt,g=c?"":"none";b(d.column(a).header()).css("display",g).toggleClass("dtr-hidden",!c);b(d.column(a).footer()).css("display", -g).toggleClass("dtr-hidden",!c);d.column(a).nodes().to$().css("display",g).toggleClass("dtr-hidden",!c);b.isEmptyObject(A)||d.cells(null,a).indexes().each(function(f){y(d,f.row,f.column)})},_tabIndexes:function(){var a=this.s.dt,c=a.cells({page:"current"}).nodes().to$(),d=a.settings()[0],g=this.c.details.target;c.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");"number"===typeof g?a.cells(null,g,{page:"current"}).nodes().to$().attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1):("td:first-child, th:first-child"=== -g&&(g=">td:first-child, >th:first-child"),b(g,a.rows({page:"current"}).nodes()).attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1))}});u.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];u.display={childRow:function(a,c,d){if(c){if(b(a.node()).hasClass("parent"))return a.child(d(),"child").show(),!0}else{if(a.child.isShown())return a.child(!1),b(a.node()).removeClass("parent"),!1;a.child(d(), -"child").show();b(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,c,d){if(!c&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),b(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();b(a.node()).addClass("parent");return!0},modal:function(a){return function(c,d,g){if(d)b("div.dtr-modal-content").empty().append(g());else{var f=function(){l.remove();b(m).off("keypress.dtr")},l=b('
').append(b('
').append(b('
').append(g())).append(b('
×
').click(function(){f()}))).append(b('
').click(function(){f()})).appendTo("body"); -b(m).on("keyup.dtr",function(h){27===h.keyCode&&(h.stopPropagation(),f())})}a&&a.header&&b("div.dtr-modal-content").prepend("

"+a.header(c)+"

")}}};var A={};u.renderer={listHiddenNodes:function(){return function(a,c,d){var g=b('
    '),f=!1;b.each(d,function(l,h){h.hidden&&(b("
  • '+ -h.title+"
  • ").append(b('').append(p(a,h.rowIndex,h.columnIndex))).appendTo(g),f=!0)});return f?g:!1}},listHidden:function(){return function(a,c,d){return(a=b.map(d,function(g){var f=g.className?'class="'+g.className+'"':"";return g.hidden?"
  • '+g.title+' '+g.data+"
  • ":""}).join(""))?b('
      ').append(a):!1}},tableAll:function(a){a=b.extend({tableClass:""},a);return function(c,d,g){c=b.map(g,function(f){return"
"}).join("");return b('
{{ _('Item') }} {{ _('Typ') }} {{ _('knx_dpt') }}
{{ item._path }} {{ item._type }} {{ item.conf['knx_dpt'] }}-Elemente schreiben zu können, nachdem die Webseite erstellt wurde, müssen die -Elemente jeweils mit einer ID ergänzt werden. Um sicherzustellen, -dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hier: der Item Name) verwendet: +dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hier: der Item Name) verwendet. Es ist wichtig, bei Datentabellen (nicht bei normalen Tabellen!) +pro Zeile eine leere Zelle einzufügen! Bei headtables sollten leere Spalten vermieden werden. .. code-block:: html+jinja {% block headtable %} - +
@@ -149,6 +161,7 @@ dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hie
Scanne von IP
+ ... @@ -156,6 +169,7 @@ dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hie {% for item in items %} + ... @@ -167,6 +181,14 @@ dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hie Jetzt können die DOM-Elemente über die IDs ``fromip`` und ``_value`` angesprochen werden. +.. warning:: + + Damit die Anzeige und Adaption der Datatables einwandfrei funktioniert, ist es elementar, den + Aufbau sauber und exakt aus dem Sampleplugin zu übernehmen. So muss beispielsweise die Tabelle + selbst in ein div gepackt werden, dem die Klasse ``table-resize`` zugewiesen ist. Außerdem + müssen leere Zellen am Anfang jeder Zeile eingefügt werden. Ein Angabe von Klassen ist nicht nötig, + da dies automatisch passiert. + Erweitern der JavaScript-Funktion handleUpdatedData() ----------------------------------------------------- @@ -215,9 +237,11 @@ Die Parameter der shngInsertText-Funktion sind dabei wie folgt: for (var item in objResponse) { shngInsertText(item+'_value', objResponse['item'][item]['value'], null, 2); - // bei Tabellen mit datatables Funktion sollte die Zeile lauten: + // bei Tabellen mit datatables Funktion sollte die Zeile lauten (ID ersetzen!): // shngInsertText(item+'_value', objResponse['item'][item]['value'], 'maintable', 2); } + // Bei Datatables sollte nach der Schleife die gesamte Tabelle ein Mal aktualisert werden + // $('#maintable').DataTable().draw(false); // ID entsprechend ersetzen } } @@ -236,12 +260,10 @@ Damit die neuen Daten auch von datatables.js erkannt und korrekt sortiert werden dem Aufruf ``shngInsertText`` die Tabellen-ID als dritten Parameter mitzugeben (im Beispiel 'maintable'). Standardmäßig werden die Spalten automatisch so skaliert, dass sie sich den Inhalten anpassen. Dies kann -va. in Kombination mit dem standardmäßig aktivierten ``responsive`` Modul der Datatables zu +va. in Kombination mit dem ``responsive`` Modul der Datatables zu unerwünschten Ergebnissen führen. Insofern ist es empfehlenswert, bestimmten Spalten eine konkrete Breite vorzugeben. Dazu sollte im Block ``pluginstyles`` entsprechender -Code eingefügt werden. Außerdem sind die ```` und ```` sowie der jeweiligen End-Tags. Außerdem muss jeder Tabelle eine einzigartige ID vergeben werden. + Sowohl im Tablehead als auch Tablebody ist eine leere erste Spalte einzufügen, die für das responsive + Feature der Datatables genutzt wird. Die Klasse``table-resize`` ist zwingend dem ``
`` Tag, in dem sich die Tabelle befindet, hinzuzufügen, um die automatische Anpassung der Datentabelle an die Fensterhöhe zu ermöglichen - (siehe auch index.html im Example-Plugin). + (siehe auch index.html im Example-Plugin). Sollen ober- oder unterhalb der Tabelle zusätzliche Informationen + angezeigt werden, müssen diese in einem ``
`` Tag stehen. .. code-block:: html+jinja
+
+ Informationen oberhalb der Tabelle +
{{ _('Wert') }}
{{ item._value }}
`` Tags natürlich mit den entsprechenden Klassen zu bestücken. -Außerdem macht es Sinn, der Tabelle selbst die Klasse ``dataTableAdditional`` hinzuzufügen. Dadurch wird -das table-layout auf fixed gestellt und die Tabelle erst angezeigt, wenn sie fertig initialisiert ist. +Code eingefügt werden. .. code-block:: css+jinja @@ -258,19 +280,21 @@ das table-layout auf fixed gestellt und die Tabelle erst angezeigt, wenn sie fer {% endblock pluginstyles %} +Außerdem ist den Spalten die entsprechende Klasse zuzuweisen. Dies ist +durch Angabe mittels class-Attribut in den ```` Tags möglich. Alternativ - und der bessere Ansatz - +ist es, die Klassen bei der Initialisierung der Tabelle zuzuweisen. Sollte der Inhalt einer Spalte erwartungsgemäß sehr breit sein, kann die Spalte stattdessen auch -als ausklappbare Informationszeile konfiguriert werden. Die datatables.js defaults sorgen dafür, -dass die Tabelle erst nach der kompletten Inititalisierung angezeigt wird. Dadurch wird ein -mögliches Flackern der Seite beim Aufbau verhindert. Die Deklaration der Tabelle im pluginscripts +durch Zuweisen der Klasse "none" als ausklappbare Informationszeile konfiguriert werden. +Die Deklaration der Tabelle im pluginscripts Block hat dabei wie folgt auszusehen, wobei bei ``targets`` die interne Nummerierung der Spalten -anzugeben ist (0 wäre die erste Tabellenspalte, 2 die zweite, etc.). +anzugeben ist (0 wäre die erste Tabellenspalte, 1 die zweite, etc.). .. code-block:: html+jinja table = $('#maintable').DataTable( { "pageLength": webif_pagelength, "pageResize": resize, - "columnDefs": [{ "targets": 0, "className": "none"}].concat($.fn.dataTable.defaults.columnDefs) + "columnDefs": [{ "targets": 1, "className": "none"}].concat($.fn.dataTable.defaults.columnDefs) } ); @@ -282,8 +306,8 @@ beispielsweise, wenn die letzten durchgeführten Commandos oder Logeinträge erg Hierzu ist es nötig, die Funktion ``handleUpdatedData`` entsprechend anzupassen. In der ersten if-Abfrage wird evaluiert, ob bereits ein Element mit entsprechender ID existiert. -Falls nicht, wird die Zeile neu angelegt und sanft eingeblendet. Im untenstehenden Code wird zuerst gecheckt, -ob es eine Datentablle mit der ID "maintable" gibt. +Falls nicht, wird die Zeile neu angelegt und sanft eingeblendet. Im unten stehenden Code wird zuerst gecheckt, +ob es eine Datentabelle mit der ID "maintable" gibt. In der Zeile ``if ( $.fn.dataTable.isDataTable('#maintable') )`` sowie in der darauf folgenden Zeile muss '#maintable' durch die tatsächliche ID der zu aktualisierenden Tabelle ersetzt werden. Falls nun eine entsprechende Tabelle auf der Seite gefunden wurde, wird diese @@ -296,7 +320,8 @@ Anschließend wird der zweiten Spalte die relevante ID hinzugefügt, um zukünft aktualisieren zu können. Möchte man weiteren Spalten ebenfalls eine ID zuweisen, ist die Codezeile zu kopieren und die Zahl beim Eintrag ``td:eq(1)`` entsprechend zu ändern (0 = erste Spalte, 1 = zweite Spalte, etc.). Abschließend wird der leere Wert schließlich -mittels ``shngInsertText`` aktualisert und dank Angabe einer Zahl als 4. Parameter x Sekunden lang farblich markiert. +mittels ``shngInsertText`` aktualisiert und dank Angabe einer Zahl als 4. Parameter x Sekunden lang farblich markiert. Nach der for-Schleife sollte die Tabelle neu gezeichnet werden (siehe Beispiel). + .. code-block:: html+jinja @@ -309,7 +334,8 @@ mittels ``shngInsertText`` aktualisert und dank Angabe einer Zahl als 4. Paramet if (!document.getElementById(item+'_value')) { if ( $.fn.dataTable.isDataTable('#maintable') ) { table_to_update = $('#maintable').DataTable(); - newRow = table_to_update.row.add( [ item, '' ] ).draw().node(); + let newRow = table_to_update.row.add( [ item, '' ] ).draw(false).node(); + newRow.id = objResponse['item'][item]+"_row"; $('td:eq(1)', newRow).attr('id', objResponse['item'][item]+'_value'); shngInsertText(item+'_value', objResponse['item'][item]['value'], 'maintable', 5); } @@ -320,6 +346,7 @@ mittels ``shngInsertText`` aktualisert und dank Angabe einer Zahl als 4. Paramet } } + $('#maintable').DataTable().draw(false); } } @@ -352,8 +379,8 @@ im Block ``pluginStyles`` bei Bedarf überschrieben werden. {% endblock pluginstyles %} -Festlegen des Aktualisierungsintervalls, dataSets und weiteren Parametern -------------------------------------------------------------------------- +Festlegen von Aktualisierungsintervall, dataSets und weiteren Parametern +------------------------------------------------------------------------ Zu Beginn der Templatedatei ``webif/templates/index.html`` finden sich die folgenden Zeilen: @@ -363,12 +390,19 @@ Zu Beginn der Templatedatei ``webif/templates/index.html`` finden sich die folge {% set update_active = false %} {% set dataSet = 'item_details' %} {% set update_params = item_id %} + {% set buttons = True %} + {% set autorefresh_buttons = true %} + {% set reload_button = true %} + {% set close_button = true %} + {% set row_count = false %} + {% set initial_update = true %} Das Intervall wird via ``update_interval`` auf den gewünschten Wert in Millisekunden gesetzt. Dabei muss sichergestellt sein, dass das gewählte Intervall lang genug ist, dass die Python-Methode ``get_data_html()`` des Plugins die Daten liefern kann, bevor das Intervall abläuft. Wenn nur Daten zurückgegeben werden, die von anderen Routinen und Threads des Plugins bereits bereitgestellt wurden, kann ein Update-Intervall von ca. 1000 ms gewählt werden. Wenn die Python-Methode ``get_data_html()`` selbst noch weitere Routinen ausführen muss, sollte das Update-Intervall wahrscheinlich nicht kleiner als 5000 ms sein. .. warning:: - Das Intervall darf nicht zu klein sein. Die Dauer **MUSS** länger sein als die notwendige Zeit zur Ausführung der Python-Methode ``get_data_html()``. + Das Intervall darf nicht zu klein sein. Die Dauer **MUSS** länger sein als die notwendige Zeit zur Ausführung der Python-Methode ``get_data_html()``. Bei datenintensiven Plugins macht es u.U. Sinn, + das Intervall abhängig von der Anzahl an Datensätzen festzulegen. Durch ``update_active`` wird festgelegt, ob die automatische Aktualisierung zum Start aktiviert oder deaktiviert sein soll. Dies kann hilfreich sein, um z.B. ein optimales Updateintervall anzugeben, aber dem User zu überlassen, die automatische Aktualisierung @@ -381,6 +415,20 @@ Dazu sollte die Methode get_data_html in der webif __init__.py entsprechend ange Database Plugin entnommen, das zwei Tabs mit verschiedenen Daten anzeigt, die eben auch unterschiedliche Rückmeldungen aus dem Plugin erhalten. +Die Angaben zu den Buttons sind optional und können +genutzt werden, um die Schalter und Auto-Refresh Funktionen im Header zu verstecken, wenn sie nicht +gebraucht werden. + +Durch Definieren der ``row_count`` Variable wird beim Anzeigen einer Tabelle automatisch die Anzahl +an Datenreihen ermittelt und in die Variable ``window.row_count`` gespeichert. Auf diesen Wert kann +in einem eigenen Javascript zugegriffen werden. Wird diese Funktion nicht gebraucht, sollte die entsprechende +Zeile gelöscht bzw. nicht gesetzt werden. + +Wenn ``initial_update`` auf "true" gesetzt ist, werden automatisch beim Laden jeder Seite die Daten mittels +get_data_html abgefragt. Dies ist insbesondere dann sinnvoll, wenn anfangs die Tabelle mit Dummy-Daten +oder ohne Inhalt befüllt wurde. + + .. code-block:: python @cherrypy.expose @@ -419,8 +467,8 @@ Plugin erhalten. return {} -Dynamische Anpassung des Aktualisierungsintervalls, dataSets und weiteren Parametern ------------------------------------------------------------------------------------- +Dynamische Anpassung von Aktualisierungsintervall, dataSets und weiteren Parametern +----------------------------------------------------------------------------------- Unter Umständen ist es sinnvoll, diverse Parameter der automatischen Aktualisierung durch ein Script (oder einen Button) anzupassen. Die Parameter werden dabei durch Aufruf von ``window.refresh.update({});`` in Form eines Dictionary aktualisiert. @@ -445,5 +493,5 @@ wenn wir den 12.12.2022 haben (was vermutlich wenig Sinn macht und daher angepas var today_date = String(today.getDate()) + String(today.getMonth() + 1) + today.getFullYear(); let test_date = "12122022"; if (today_date === test_date) - window.refresh.update({dataSet:'test', update_params:'specialitem', update_interval: 2000, update_active:false}); + window.refresh.update({dataSet:'test', update_params:'specialitem', update_interval:2000, update_active:false}); diff --git a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst old mode 100644 new mode 100755 index fc037cf583..6defcc62a9 --- a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst @@ -41,10 +41,7 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: :return: contents of the template after beeing rendered """ - try: - pagelength = self.plugin.webif_pagelength - except Exception: - pagelength = 100 + pagelength = self.plugin.get_parameter_value('webif_pagelength') tmpl = self.tplenv.get_template('index.html') # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...) return tmpl.render(webif_pagelength=pagelength, p=self.plugin) @@ -63,39 +60,11 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: ) - 2. Die Variable ``webif_pagelength`` sollte genutzt werden, um die Anzahl an Einträgen + 2. Die Variable ``webif_pagelength`` wird genutzt, um die Anzahl an Einträgen pro Seite im Web Interface über die plugin.yaml konfigurierbar zu machen. - Hierzu ist es notwendig, das ``__init__.py`` File des Plugins (nicht im webif Ordner!) und ``plugin.yaml`` wie folgt anzupassen. - - .. code-block:: python - - self.webif_pagelength = self.get_parameter_value('webif_pagelength') - - Die plugin.yaml sollte hiermit ergänzt werden: - - .. code-block:: yaml - - webif_pagelength: - type: int - default: 100 - description: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden. - 0 = automatisch, -1 = alle' - en: 'Amount of items being listed in a web interface table per page by default. - 0 = automatic, -1 = all' - description_long: - de: 'Anzahl an Items, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden.\n - Bei 0 wird die Tabelle automatisch an die Höhe des Browserfensters angepasst.\n - Bei -1 werden alle Tabelleneinträge auf einer Seite angezeigt.' - en: 'Amount of items being listed in a web interface table per page by default.\n - 0 adjusts the table height automatically based on the height of the browser windows.\n - -1 shows all table entries on one page.' - valid_list: - - -1 - - 0 - - 25 - - 50 - - 100 + Diese Variable kann vom User global im http-Modul gesetzt, aber auch individuell pro Plugin + überschrieben werden. Bei der Entwicklung eines Plugins ist dabei nichts Weiteres zu + beachten, da der Parameter automatisch jedem Plugin hinzugefügt wird. 3. Im Template ``webif/templates/index.html`` werden Anzahl und Titel der Tabs sowie der Starttab konfiguriert. @@ -117,16 +86,23 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: Um im ersten Tab des Webinterface die Items anzuzeigen, die der obige Beispielcode zusammengestellt hat, wird der folgende Code zwischen ``{% block bodytab1 %}`` und ``{% endblock bodytab1 %}`` eingefügt. Es ist sicherzustellen, dass korrekter HTML Code für die Tabellen genutzt wird, ua. durch Nutzen der Tags ``
+ @@ -135,6 +111,7 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: {% for item in items %} + @@ -142,6 +119,9 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: {% endfor %}
{{ _('Item') }} {{ _('Typ') }} {{ _('knx_dpt') }}
{{ item._path }} {{ item._type }} {{ item.conf['knx_dpt'] }}
+
+ Informationen unterhalb der Tabelle +
@@ -160,25 +140,17 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: - .. code-block:: html+jinja - - - {% block headtable %} - - - 6. Das Logo oben links auf der Seite wird automatisch durch das Logo des konfigurierten Plugin-Typs ersetzt. Wenn das Webinterface ein eigenes Logo mitbringen soll, muss das entsprechende Bild im Verzeichnis ``webif/static/img`` mit dem Namen ``plugin_logo`` abgelegt sein. Die zulässigen Dateiformate sind **.png**, **.jpg** oder **.svg**. Dabei sollte die Größe der Bilddatei die Größe des angezeigten Logos (derzeit ca. 180x150 Pixel) nicht überschreiten, um unnötige Datenübertragungen zu vermeiden. diff --git a/doc/user/source/entwicklung/plugins/webinterface_plugin_interaction.rst b/doc/user/source/entwicklung/plugins/webinterface_plugin_interaction.rst old mode 100644 new mode 100755 index a352fed463..099b446538 --- a/doc/user/source/entwicklung/plugins/webinterface_plugin_interaction.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_plugin_interaction.rst @@ -98,12 +98,13 @@ kann in der Javascript-Methode dann unmittelbar angesprochen werden, wenn das Di IDs an DOM-Elemente zuweisen ---------------------------- -Normalerweise sieht das ``headtable`` wie folgt aus: +Normalerweise sieht das ``headtable`` wie folgt aus. Die Angabe einer min-width +ist optional, aber empfohlen, um das responsive Design zu optimieren. .. code-block:: html+jinja {% block headtable %} - +
@@ -121,6 +122,11 @@ Normalerweise sieht das ``headtable`` wie folgt aus: Tabellen in einem ``bodytab?`` können mit einer Schleife befüllt werden, das ist auf der Seite :doc:`Webinterface mit Inhalt füllen ` näher beschrieben: +.. hint:: + + Sowohl im thead als auch tbody ist jeweils eine leere Tabellenzelle einzufügen, um das + Responsive Feature der Datentabellen korrekt anzuzeigen. + .. code-block:: html+jinja @@ -130,6 +136,7 @@ Tabellen in einem ``bodytab?`` können mit einer Schleife befüllt werden, das i
Scanne von IP
+ @@ -139,6 +146,7 @@ Tabellen in einem ``bodytab?`` können mit einer Schleife befüllt werden, das i {% for elem in data %} + @@ -159,9 +167,10 @@ Damit die IDs in den Wertetabellen eindeutig sind, verwenden wir die Variable au .. code-block:: html+jinja {% block headtable %} -
{{ _('Attribut 1') }} {{ _('Attribut 2') }} {{ _('aktualisieren') }}
{{ data[elem]['attr1'] }} {{ data[elem]['attr2'] }}
+
+ ... @@ -178,6 +187,7 @@ Damit die IDs in den Wertetabellen eindeutig sind, verwenden wir die Variable au
Scanne von IP {{ p.fromip }}
+ @@ -187,6 +197,7 @@ Damit die IDs in den Wertetabellen eindeutig sind, verwenden wir die Variable au {% for elem in data %} + @@ -235,6 +246,7 @@ in jeder Zeile stehen soll, dann bietet es sich an, statt einzelnen Button-Eleme
{{ _('Attribut 1') }} {{ _('Attribut 2') }} {{ _('aktualisieren') }}
{{ data[elem]['attr1'] }} {{ data[elem]['attr2'] }}
+ @@ -244,6 +256,7 @@ in jeder Zeile stehen soll, dann bietet es sich an, statt einzelnen Button-Eleme {% for elem in data %} + \n \n '})],e)}();t.ColumnHeaders=g;var m=function(){function e(e){this.dt=e}return l([o.Input("pColumnFooters"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Component({selector:"[pColumnFooters]",template:'\n \n '})],e)}();t.ColumnFooters=m;var v=function(){function e(e){this.dt=e}return e.prototype.visibleColumns=function(){return this.columns?this.columns.filter(function(e){return!e.hidden}):[]},l([o.Input("pTableBody"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Input(),i("design:type",Array)],e.prototype,"data",void 0),l([o.Component({selector:"[pTableBody]",template:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n '})],e)}();t.TableBody=v;var y=function(){function e(e,t,n,l){this.dt=e,this.el=t,this.renderer=n,this.zone=l,this.onVirtualScroll=new o.EventEmitter}return e.prototype.ngAfterViewInit=function(){this.initScrolling()},e.prototype.ngAfterViewChecked=function(){var e=this;if(this.virtualScroll&&!this.rowHeight){var t=d.DomHandler.findSingle(this.scrollTable,"tr.ui-widget-content:not(.ui-datatable-emptymessage-row)");t&&(this.rowHeight=d.DomHandler.getOuterHeight(t))}this.frozen||this.zone.runOutsideAngular(function(){setTimeout(function(){e.alignScrollBar()},1)})},e.prototype.initScrolling=function(){var e=this;this.scrollHeader=this.scrollHeaderViewChild.nativeElement,this.scrollHeaderBox=this.scrollHeaderBoxViewChild.nativeElement,this.scrollBody=this.scrollBodyViewChild.nativeElement,this.scrollTable=this.scrollTableViewChild.nativeElement,this.scrollTableWrapper=this.scrollTableWrapperViewChild.nativeElement,this.scrollFooter=this.scrollFooterViewChild?this.scrollFooterViewChild.nativeElement:null,this.scrollFooterBox=this.scrollFooterBoxViewChild?this.scrollFooterBoxViewChild.nativeElement:null,this.setScrollHeight(),this.frozen||this.zone.runOutsideAngular(function(){e.scrollHeader.addEventListener("scroll",e.onHeaderScroll.bind(e)),e.scrollBody.addEventListener("scroll",e.onBodyScroll.bind(e))}),this.frozen?this.scrollBody.style.paddingBottom=d.DomHandler.calculateScrollbarWidth()+"px":this.alignScrollBar()},e.prototype.onBodyScroll=function(e){var t=this,n=this.el.nativeElement.previousElementSibling;if(n)var l=d.DomHandler.findSingle(n,".ui-datatable-scrollable-body");if(this.scrollHeaderBox.style.marginLeft=-1*this.scrollBody.scrollLeft+"px",this.scrollFooterBox&&(this.scrollFooterBox.style.marginLeft=-1*this.scrollBody.scrollLeft+"px"),l&&(l.scrollTop=this.scrollBody.scrollTop),this.virtualScroll){var i=d.DomHandler.getOuterHeight(this.scrollBody),o=d.DomHandler.getOuterHeight(this.scrollTable),r=this.rowHeight*this.dt.rows,u=d.DomHandler.getOuterHeight(this.scrollTableWrapper)/r||1;if(this.scrollBody.scrollTop+i>parseFloat(this.scrollTable.style.top)+o||this.scrollBody.scrollTopd.DomHandler.getOuterHeight(this.scrollBody)},e.prototype.alignScrollBar=function(){var e=this.hasVerticalOverflow()?d.DomHandler.calculateScrollbarWidth():0;this.scrollHeaderBox.style.marginRight=e+"px",this.scrollFooterBox&&(this.scrollFooterBox.style.marginRight=e+"px")},e.prototype.ngOnDestroy=function(){this.scrollHeader.removeEventListener("scroll",this.onHeaderScroll),this.scrollBody.removeEventListener("scroll",this.onBodyScroll)},l([o.Input("pScrollableView"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Input(),i("design:type",c.HeaderColumnGroup)],e.prototype,"headerColumnGroup",void 0),l([o.Input(),i("design:type",c.HeaderColumnGroup)],e.prototype,"footerColumnGroup",void 0),l([o.ViewChild("scrollHeader"),i("design:type",o.ElementRef)],e.prototype,"scrollHeaderViewChild",void 0),l([o.ViewChild("scrollHeaderBox"),i("design:type",o.ElementRef)],e.prototype,"scrollHeaderBoxViewChild",void 0),l([o.ViewChild("scrollBody"),i("design:type",o.ElementRef)],e.prototype,"scrollBodyViewChild",void 0),l([o.ViewChild("scrollTable"),i("design:type",o.ElementRef)],e.prototype,"scrollTableViewChild",void 0),l([o.ViewChild("scrollTableWrapper"),i("design:type",o.ElementRef)],e.prototype,"scrollTableWrapperViewChild",void 0),l([o.ViewChild("scrollFooter"),i("design:type",o.ElementRef)],e.prototype,"scrollFooterViewChild",void 0),l([o.ViewChild("scrollFooterBox"),i("design:type",o.ElementRef)],e.prototype,"scrollFooterBoxViewChild",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"frozen",void 0),l([o.Input(),i("design:type",String)],e.prototype,"width",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"virtualScroll",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onVirtualScroll",void 0),l([o.Component({selector:"[pScrollableView]",template:'\n
\n
\n
{{ _('Attribut 1') }} {{ _('Attribut 2') }} {{ _('aktualisieren') }}
{{ data[elem]['attr1'] }} {{ data[elem]['attr2'] }} diff --git a/doc/user/source/index.rst b/doc/user/source/index.rst index 228e2b45f6..af87325156 100644 --- a/doc/user/source/index.rst +++ b/doc/user/source/index.rst @@ -64,5 +64,5 @@ oder im `Chat auf gitter.im `_ . genindex impressum datenschutz - English translation of this documentation + English translation of this documentation diff --git a/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst b/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst index 4c4ea1a6fa..a7751f32c3 100644 --- a/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst +++ b/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst @@ -46,6 +46,8 @@ unter dem später SmartHomeNG laufen soll, **nicht als root**. git clone https://github.com/smarthomeNG/smarthome.git . git clone https://github.com/smarthomeNG/plugins.git plugins + bash tools/setpermissions + Bitte auf den **Punkt** am Ende des ersten **git clone** Kommandos achten! diff --git a/doc/user/source/konfiguration/logging.rst b/doc/user/source/konfiguration/logging.rst index 0409c2e646..186567aa44 100644 --- a/doc/user/source/konfiguration/logging.rst +++ b/doc/user/source/konfiguration/logging.rst @@ -114,16 +114,18 @@ Die einzelnen Konfigurationseinträge haben die folgende Bedeutung: | | Klammern zu setzen, auch wenn nur ein Filter zum Einsatz kommen soll. | | | Jeder Filter kann durch bis zu drei Parameter definiert werden, wobei diese nach AND Logik | | | evaluiert werden: | +| | | | | - name: Loggername (z.B. lib.item) | -| | - module: Loggermodul, va. bei Plugins u.U. relevant (z.B. item) | -| | - timestamp: Uhrzeit/Datum (z.B. "23:00") | +| | - module: Loggermodul, va. bei Plugins u.U. relevant (z.B. item, metadata, etc. OHNE `lib.`) | +| | - timestamp: Uhrzeit/Datum (z.B. "23:00" oder ein regulärer Ausdruck) | | | - msg: Der tatsächliche Logeintrag als RegEx, z.B. "Result = (.\*) \(for attribute 'eval'\)" | +| | | | | Durch die Angabe von invert: True werden NUR die passenden Messages geloggt und sonst nichts. | | | Ein Beispiel ist unter :doc:`Logging - Best Practices ` zu finden. | +-----------------+----------------------------------------------------------------------------------------------------+ | **loggers:** | Hier werden die einzelnen Logger definiert und was mit diesen Einträgen passiert, | | | welche Handler und formatter verwendet werden. Das Level konfiguriert dabei die | -| | Logtiefe für die einzelne Komponente. Bei den loggern ist es nun möglich einzelne | +| | Logtiefe für die einzelne Komponente. Bei den Loggern ist es nun möglich einzelne | | | Plugins oder Libs im Debug protokollieren zu lassen. Dazu sind in der Konfiguration | | | bereits einige Beispiele. | +-----------------+----------------------------------------------------------------------------------------------------+ @@ -142,7 +144,6 @@ Eintrag im Handler **file:** erfolgen. Der Eintrag `level: WARNING` führt dazu, Handler **file:** nur Ausgaben für Fehler und Warnungen erfolgen. INFO und DEBUG Ausgaben erfolgen dann nur noch über den zusätzlichen Handler. -| Logging Handler und Filter ========================== @@ -180,11 +181,11 @@ in der `__init__` Methode instanziert werden. Das ist inzwischen nicht mehr notw Klasse erzeugt den Logger inzwischen selbst. Ein **import logging** ist nicht mehr notwendig und die Initialisierung des Loggers in der `__init__` Methode sollte auch weggelassen werden. -Der Logger konn/muss in der ``etc/logging,yaml`` konfiguriert werden. Der Name des Loggers ist ``plugins.``. +Der Logger kann/muss in der ``etc/logging.yaml`` konfiguriert werden. Der Name des Loggers ist ``plugins.``. Für die Entwickler von Logiken: -Verwendet man zur Instanziierung einen eigenen Namen (nicht empfohlen), wie z.B. +Verwendet man zur Instanzierung einen eigenen Namen (nicht empfohlen), wie z.B. .. code-block:: python @@ -201,7 +202,7 @@ muss in der config auch dieser Name verwendet werden. Ohne `plugin.` oder `logic DWD: level: DEBUG -Standardmäßig haben Logiken bereits einen Logger, der in der ``etc/logging,yaml`` konfiguriert werden kann/muss. +Standardmäßig haben Logiken bereits einen Logger, der in der ``etc/logging.yaml`` konfiguriert werden kann/muss. Der Name des Loggers ist ``logics.``, wobei der Name der Logik, der in der Konfiguration festgelegte Name ist und nicht der Name des Python Skriptes. diff --git a/doc/user/source/konfiguration/logging_best_practices.rst b/doc/user/source/konfiguration/logging_best_practices.rst index 40fe1c7f46..e804e25f6d 100644 --- a/doc/user/source/konfiguration/logging_best_practices.rst +++ b/doc/user/source/konfiguration/logging_best_practices.rst @@ -217,8 +217,9 @@ Logmessage ausgegeben: datefmt: '%Y-%m-%d %H:%M:%S' Folgende Einstellung (wie oben im Beispiel bereits angegeben) schreibt -statt des Thread Names (der nicht besonders hilfeich ist), den Python -Modul Namen in das log (also z.B.: lib.plugin, plugins.knx, …): +statt des Thread Names (der nicht besonders hilfreich ist), den Python +Modulnamen in das Log (also z.B.: item, metadata, plugin - jeweils OHNE `lib.` -, +plugins.knx, etc): .. code-block:: yaml @@ -241,7 +242,13 @@ Logging Filter ~~~~~~~~~~~~~~ Filter können dazu genutzt werden, nur bestimmte Logeinträge anzuzeigen bzw. -diese eben auch zu verstecken. Hierzu wird zuerst ein Filter angelegt: +diese eben auch zu verstecken. Hierzu wird zuerst ein Filter angelegt. +Zu beachten ist, dass alle Einträge außer invert als Regular Expression angegeben +werden müssen. Im einfachsten Fall sind die Einträge module und name 1:1 identisch +mit den gewünschten Modulen oder Namen, wodurch keine besonderen Ausdrücke notwendig +werden. Modulnamen wie item oder metadata müssen ohne voranstehendem `lib.` angegeben +werden. Bei `msg` muss jedenfalls `(.*)` am Beginn und/oder Ende angegeben sein, +wenn die abzufangende Nachricht nicht komplett 1:1 identisch ist. Hier ein Beispiel: .. code-block:: yaml @@ -281,7 +288,7 @@ Stateengine Plugin in die Datei stateengine.log geschrieben werden. Auf Grund des Filters werden sämtliche Einträge ignoriert, die: - vom Modul StateEngine (s und e können sowohl groß, als auch klein geschrieben - werden) stammen + werden) stammen. Andere mögliche Module wären z.B. item, metadata, etc. (ohne lib.) - vom Logger mit dem Namen 'plugins.stateengine.licht.test' stammen - am Ende der Zeile "Item not found!" beinhalten diff --git a/doc/user/source/konfiguration/module/module_http.rst b/doc/user/source/konfiguration/module/module_http.rst index 503911c5f8..271b6d54ef 100644 --- a/doc/user/source/konfiguration/module/module_http.rst +++ b/doc/user/source/konfiguration/module/module_http.rst @@ -18,7 +18,7 @@ Der vom Module http zur Verfügung gestellte Webserver wird genutzt um die Admin Weiterhin erlaubt es dieses Modul in Plugins eine Webschnittstelle zu implementieren. Die API wird weiter unten beschrieben. Es implementiert auch die Basisauthentifizierung für den Zugriff auf Webinterfaces und Webservices. -Über Webinterfaces können Plugins Webseiten zur Verfügung stellen, um bei Befarf eine GUI für +Über Webinterfaces können Plugins Webseiten zur Verfügung stellen, um bei Bedarf eine GUI für bestimmte Features anzubieten. Über Webservices können Plugins Dienste zu Verfügung stellen, welche die Kommunikation mit anderen @@ -44,7 +44,8 @@ Datei *../etc/module.yaml* # threads: 8 # showtraceback: False # showpluginlist: True - + # webif_pagelength: 0 + # port: 8383 # user: admin # password: geheim @@ -120,6 +121,12 @@ Datei *../etc/module.yaml* | | das Passwort zu erstellen, kann die Funktion **Passwort-Hash erzeugen** auf der Seite | | | **Dienste** im Backend verwendet werden. | +-------------------------+------------------------------------------------------------------------------------------------------+ +| webif_pagelength | **Optional**: Anzahl an Tabellenreihen, die in den Plugin Webinterfaces standardmäßig pro Seite | +| | angezeigt werden sollen. Bei **-1** werden alle Einträge auf einer Seite gezeigt, bei **0** (default)| +| | so viele, dass sie genau auf die Seite ohne Scrolling passen. Weitere mögliche Werte sind 25, 50, 100| +| | Der hier angegebene Wert kann pro Plugin im /etc/plugin.yaml File über den gleichnamigen Parameter | +| | überschrieben werden. | ++-------------------------+------------------------------------------------------------------------------------------------------+ .. note:: @@ -128,5 +135,3 @@ Datei *../etc/module.yaml* kann trotzdem die Übersichtsseite mit der Liste aller geledenen Plugins, die ein Webinterface registriert haben über **http://smarthomeNG.local:8383/plugins** angezeigt werden. Außer, man hat über ``showpluginlist: False`` diese Übersichtsseite deaktiviert. - - diff --git a/doc/user/source/konfiguration/plugins.rst b/doc/user/source/konfiguration/plugins.rst index e15272d21a..23312328b9 100644 --- a/doc/user/source/konfiguration/plugins.rst +++ b/doc/user/source/konfiguration/plugins.rst @@ -5,7 +5,7 @@ Plugins Das Grundsystem von SmartHomeNG kann durch den Einsatz von Plugins erweitert werden. Ein Plugin ist ein Zusatzmodul in einem Unterverzeichnis unterhalb des Verzeichnisses **../plugins**. Um ein Plugin in SmartHomeNG zu verwenden (eine Instanz des Plugins zu laden) muss eine Sektion -für das gewünschte Plugin in der Datei **etc/plugin.conf** erstellt werden. +für das gewünschte Plugin in der Datei **etc/plugin.yaml** erstellt werden. Für das oft benutzte KNX-Plugin sieht das z.B. so aus: @@ -17,6 +17,7 @@ Für das oft benutzte KNX-Plugin sieht das z.B. so aus: # instance: knx_1 # host: 127.0.0.1 # port: 6720 + # webif_pagelength: 0 send_time: 600 # update date/time every 600 seconds, default none time_ga: 8/0/0 date_ga: 8/0/1 @@ -32,6 +33,7 @@ bzw. im alten Format: # instance = knx_1 # host = 127.0.0.1 # port = 6720 + # webif_pagelength = 0 send_time = 600 # update date/time every 600 seconds, default none time_ga = 8/0/0 date_ga = 8/0/1 @@ -43,42 +45,50 @@ sollte auch so gewählt werden, dass es zu keiner Namensgleichheit mit Top-Level Es gibt folgende allgemeine Parameter im Abschnitt eines Plugins: -+----------------+-------------------------------------------------------------------------------------+ -| Parameter | Bedeutung | -+================+=====================================================================================+ -| plugin_name | Der Kurzname des Plugins, das eingebunden werden soll (Name des Verzeichnisses | -| | im **../plugins** Verzeichnis). Statt des Parameters **plugin_name** konnten bisher | -| | auch die Parameter **class_name** und **class_path** angegeben werden. | -+----------------+-------------------------------------------------------------------------------------+ -| plugin_enabled | Wenn dieser Parameter auf **False** gesetzt wird, wird das Plugin nicht geladen, die| -| | Konfiguration bleibt jedoch erhalten und die Verwendung von Item-Attributen dieses | -| | Plugins in den Item Konfigurationen erzeugt keine Warnungen im Log. | -+----------------+-------------------------------------------------------------------------------------+ -| class_name | **DEPRECATED**: Name der Klasse in der Plugin Datei. Was hier einzutragen ist, | -| | steht in der Dokumentation zum jeweiligen Plugin. Stattdessen ist die | -| | Konfiguration über den Parameter **plugin_name** vorzunehmen. | -+----------------+-------------------------------------------------------------------------------------+ -| class_path | **DEPRECATED**: Pfad zur Plugin Datei. Was hier einzutragen ist, steht in der | -| | Dokumentation zum jeweiligen Plugin. Stattdessen ist die Konfiguration über den | -| | Parameter **plugin_name** vorzunehmen. | -+----------------+-------------------------------------------------------------------------------------+ -| instance | Optional: Dieser Parameter muss nur verwendet werden, wenn mehrere Instanzen des | -| | selben Plugins geladen werden sollen. Das Plugin selbst muss dazu **Multiinstance** | -| | fähig sein. Damit die Items der richtigen Plugin-Instanz zugeordnet werden, muss | -| | in der jeweiligen Item Definition der Name des Plugin-spezifische Attributes um | -| | die Angabe der Instanz ergänzt werden. Also z.B.: Statt **avm_data_type: uptime** | -| | muss **avm_data_type@: uptime** geschrieben werden. | -+----------------+-------------------------------------------------------------------------------------+ -| plugin_version | Wenn im Plugin Repository mehrere Versionen eines Plugins zur Verfügung stehen, | -| | kann über diesen Parameter eine andere als die neueste Version des Plugins geladen | -| | werden. Dazu muss die Versionsnummer des Plugins angegeben werden. | -| | (z.B. **plugin_version: 1.4.9**) | -+----------------+-------------------------------------------------------------------------------------+ ++------------------+-------------------------------------------------------------------------------------+ +| Parameter | Bedeutung | ++==================+=====================================================================================+ +| plugin_name | Der Kurzname des Plugins, das eingebunden werden soll (Name des Verzeichnisses | +| | im **../plugins** Verzeichnis). Statt des Parameters **plugin_name** konnten bisher | +| | auch die Parameter **class_name** und **class_path** angegeben werden. | ++------------------+-------------------------------------------------------------------------------------+ +| plugin_enabled | Optional: Wenn dieser Parameter auf **False** gesetzt wird, wird das Plugin nicht | +| | geladen, die Konfiguration bleibt jedoch erhalten und die Verwendung von Item- | +| | Attributen dieses Plugins in den Itemkonfigurationen erzeugt keine Warnungen im Log.| ++------------------+-------------------------------------------------------------------------------------+ +| class_name | **DEPRECATED**: Name der Klasse in der Plugin Datei. Was hier einzutragen ist, | +| | steht in der Dokumentation zum jeweiligen Plugin. Stattdessen ist die | +| | Konfiguration über den Parameter **plugin_name** vorzunehmen. | ++------------------+-------------------------------------------------------------------------------------+ +| class_path | **DEPRECATED**: Pfad zur Plugin Datei. Was hier einzutragen ist, steht in der | +| | Dokumentation zum jeweiligen Plugin. Stattdessen ist die Konfiguration über den | +| | Parameter **plugin_name** vorzunehmen. | ++------------------+-------------------------------------------------------------------------------------+ +| instance | Optional: Dieser Parameter muss nur verwendet werden, wenn mehrere Instanzen des | +| | selben Plugins geladen werden sollen. Das Plugin selbst muss dazu **Multiinstance** | +| | fähig sein. Damit die Items der richtigen Plugin-Instanz zugeordnet werden, muss | +| | in der jeweiligen Item Definition der Name des Plugin-spezifische Attributes um | +| | die Angabe der Instanz ergänzt werden. Also z.B.: Statt **avm_data_type: uptime** | +| | muss **avm_data_type@: uptime** geschrieben werden. | ++------------------+-------------------------------------------------------------------------------------+ +| plugin_version | Optional: Wenn im Plugin Repository mehrere Versionen eines Plugins zur Verfügung | +| | stehen, kann über diesen Parameter eine andere als die neueste Version des Plugins | +| | geladen werden. Dazu muss die Versionsnummer des Plugins angegeben werden. | +| | (z.B. **plugin_version: 1.4.9**) | ++------------------+-------------------------------------------------------------------------------------+ +| webif_pagelength | Optional: Anzahl an Tabellenreihen, die im Plugin Webinterface standardmäßig | +| | pro Seite angezeigt werden sollen. Bei **-1** werden alle Einträge auf einer Seite | +| | gezeigt, bei **0** so viele, dass sie genau auf die Seite ohne Scrolling passen. | +| | Weitere mögliche Werte sind 25, 50, 100. Der hier angegebene Wert überschreibt den | +| | für das **http Modul** im /etc/module.yaml File über den gleichnamigen Parameter | | +| | definierten globalen Wert. | ++------------------+-------------------------------------------------------------------------------------+ + Die weiteren Einträge sind Plugin spezifisch. Welche Parameter ein Plugin kennt ist auch der -README.md des Plugins zu entnehmen. Je nach Plugin können sie verpflichtend oder optional sein. -Im obigen Beispiel sind sie alle optional. Diese Parameter werden beim Start von SmartHomeNG an -das Plugin übergeben. +plugin.yaml Datei bzw. Doku des Plugins zu entnehmen. Je nach Plugin können sie verpflichtend oder +optional sein. Im obigen Beispiel sind sie alle (außer plugin_name) optional. Diese Parameter werden +beim Start von SmartHomeNG an das Plugin übergeben. Ein **#** wirkt wie auch bei den Konfigurationsdateien der Items als Beginn eines Kommentars. diff --git a/doc/user/source/konfiguration/userfunctions.rst b/doc/user/source/konfiguration/userfunctions.rst index dc05e21182..ade76d05f6 100644 --- a/doc/user/source/konfiguration/userfunctions.rst +++ b/doc/user/source/konfiguration/userfunctions.rst @@ -8,9 +8,9 @@ .. index:: Konfiguration; Functions .. index:: Userfunctions; Konfiguration -=========================== -Userfunctions :redsup:`Neu` -=========================== +============= +Userfunctions +============= Ab Version 1.9 von SmartHomeNG ist die Möglichkeit implementiert, benutzerdefinierte Funktionen (Userfunctions) zu schreiben und in eval Statements sowie in Logiken zu verwenden. Diese Funktionen können zur Laufzeit verändert und diff --git a/doc/user/source/lib/scheduler.rst b/doc/user/source/lib/scheduler.rst index e77da52b55..c5eac7bdda 100644 --- a/doc/user/source/lib/scheduler.rst +++ b/doc/user/source/lib/scheduler.rst @@ -37,3 +37,4 @@ lib.scheduler :undoc-members: :show-inheritance: :member-order: bysource + diff --git a/doc/user/source/referenz/items/standard_attribute/eval.rst b/doc/user/source/referenz/items/standard_attribute/eval.rst index 254448e5d3..051491b2eb 100644 --- a/doc/user/source/referenz/items/standard_attribute/eval.rst +++ b/doc/user/source/referenz/items/standard_attribute/eval.rst @@ -138,6 +138,17 @@ auf andere Items beziehen. weitere ausführliche Beispiele zu finden. +Nutzung von Userfunctions +------------------------- + +Bei komplexeren Berechnungen kann es sinnvoll sein, diese in eine :doc:`Userfunction ` +auszulagern und im eval Ausdruck nur die :doc:`Userfunction ` aufzurufen. +Das bietet sich besonders an, wenn die gleiche Berechnung in mehreren Items durchgeführt werden soll. + +Ein weiterer Vorteil von Userfunctions ist, dass Userfunctions modifiziert und neu geladen werden können, ohne SmartHomeNG +komplett neu starten zu müssen. Das hilft besonders während der Entwicklung. + + Eval Syntax ----------- diff --git a/doc/user/source/referenz/logging/logging_filter.rst b/doc/user/source/referenz/logging/logging_filter.rst index de659a31b6..e12e783f47 100644 --- a/doc/user/source/referenz/logging/logging_filter.rst +++ b/doc/user/source/referenz/logging/logging_filter.rst @@ -95,12 +95,15 @@ Benutzung des Filters --------------------- In der Datei ``../etc/logging.yaml`` wird der **Filter** im Abschnitt ``filters:`` zur Nutzung -konfiguriert. +konfiguriert. Dabei ist die erste Zeile `(): lib.logutils.Filter` zwingend anzugeben, +außerdem zumindest eine der darauffolgenden Zeilen (module, msg, timestamp). Die Angaben +sind dabei als reguläre Ausdrücke anzugeben und können sogar in Listenform erfolgen. +Bei Modulen wie item, metadata, etc. ist KEIN voranstehendes `lib.` anzugeben. .. code-block:: yaml filters: - duplicatefilter: + examplefilter: (): lib.logutils.Filter name: [] module: [] diff --git a/doc/user/source/referenz/logging/logging_handler.rst b/doc/user/source/referenz/logging/logging_handler.rst index 7ed34facc0..bb97864ada 100644 --- a/doc/user/source/referenz/logging/logging_handler.rst +++ b/doc/user/source/referenz/logging/logging_handler.rst @@ -107,7 +107,7 @@ In der Datei ``../etc/logging.yaml`` wird der **ShngMemLogHandler** im Abschnitt angesprochen werden kann. - ``maxlen:`` - Legt fest, wie viele Einträge ein Memory Log aufnehmen kann, bevor der älteste Eintrag gelöscht wird. - - ``level:`` - Legt den minmalen Log Level fest, der in das Memory Log geschrieben wird + - ``level:`` - Legt den minimalen Log Level fest, der in das Memory Log geschrieben wird | @@ -119,4 +119,3 @@ gefügt. loggers: heizung: handlers: [shng_heizung_file, memory_heizung] - diff --git a/doc/user/source/referenz/logging/logging_textformatierung.rst b/doc/user/source/referenz/logging/logging_textformatierung.rst index 30de7c2cd8..311ba39a7f 100644 --- a/doc/user/source/referenz/logging/logging_textformatierung.rst +++ b/doc/user/source/referenz/logging/logging_textformatierung.rst @@ -61,7 +61,7 @@ die Lesbarkeit des Python Codes deutlich, besonders bei Verwendung vieler Variab SmartHomeNG genutzt werden. Diese Art der Formatierung wird ausdrücklich empfohlen. Es ist jedoch nicht notwendig, in älterem Code alle - älteren Formatierungs-Optionen zu ersetzen. + älteren Formatierungsoptionen zu ersetzen. .. code-block:: python @@ -77,4 +77,3 @@ Auch hier ist die Formatierung der Variableninhalte möglich: menge = 24 preis = 19.98 logger.info(f"Artikelmenge: {menge:3d}, Preis: {preis:6.2f}, Gesamt: {menge * preis:8.2f}") - diff --git a/doc/user/source/referenz/metadata/item_structs.rst b/doc/user/source/referenz/metadata/item_structs.rst index 77252d809c..69df323424 100644 --- a/doc/user/source/referenz/metadata/item_structs.rst +++ b/doc/user/source/referenz/metadata/item_structs.rst @@ -93,11 +93,11 @@ Wenn Attribute die redefiniert werden Listen sind, findet kein überschreiben de die Listen aneinander gehängt. Das geschieht in der Reihenfolge in der die Attribut Definitionen eingelesen werden. -Definitions for multi-instance plugins -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Definitionen für multi-instance Plugins +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Wenn ein Plugin Multi-Instance fähig ist, ist es wahrscheinlich, dass Item Strukturen Instanz-spezifische Attribute -enthalten. In Item Definitionen wird bei solchen Attributen `'@`` an den Attribut Namen angefügt. +enthalten. In Item Definitionen wird bei solchen Attributen ``@`` an den Attribut Namen angefügt. Um in Strukturen zu kennzeichnen, welche Attribute einen Instanz Namen hinzugefügt bekommen sollen, wird bei diesen Attributen der konstante String ``@instance`` hinzugefügt. Dieser String wird beim Aufbau des Item Trees durch den @@ -147,4 +147,3 @@ Datei ``plugin.yaml`` angezeigt: .. hint:: Bitte beachten, dass hier ``NONE`` vollständig in Großbuchstaben geschrieben werden muss. - diff --git a/doc/user/source/referenz/metadata/parameters.rst b/doc/user/source/referenz/metadata/parameters.rst index 0c8a878241..7a87d6f1d7 100644 --- a/doc/user/source/referenz/metadata/parameters.rst +++ b/doc/user/source/referenz/metadata/parameters.rst @@ -42,6 +42,11 @@ zur Dokumentation und zur Konfiguration in der Admin GUI benutzt. type: ... +.. hint:: + + Der Parameter ``webif_pagelength`` wird jedem Plugin automatisch hinzugefügt und sollte + daher NICHT manuell im plugin.yaml File hinterlegt werden. + .. include:: /referenz/metadata/parameter_keys.rst diff --git a/doc/user/source/referenz/metadata/plugin_global.rst b/doc/user/source/referenz/metadata/plugin_global.rst index 4d92e51f2d..dd356cefe0 100644 --- a/doc/user/source/referenz/metadata/plugin_global.rst +++ b/doc/user/source/referenz/metadata/plugin_global.rst @@ -24,7 +24,7 @@ Der globale Metadaten Abschnitt ``plugin:`` kennt die folgenden Schlüsselbegrif # tester: # Optional: Who tests this plugin? state: qa-passed keywords: weather # keywords, where applicable - documentation: https://github.com/smarthomeNG/... # url of additional wiki page (in addition to user_doc.rst of plugin + documentation: 'https://...' # An url to optional plugin doc - NOT the url to user_doc!!! # support: https://knx-user-forum.de/forum/supportforen/smarthome-py # url of the support thread or forum version: 1.4.3 @@ -33,7 +33,8 @@ Der globale Metadaten Abschnitt ``plugin:`` kennt die folgenden Schlüsselbegrif # py_minversion: 3.6 # minimum Python version needed for this plugin (leave empty if no special requirement) # py_maxversion: # maximum Python version to use this plugin (leave empty if no special requirement) multi_instance: true # plugin supports multi instance (if not specified, False is assumed) - + restartebly: unknown + # startorder: normal # should only be set on special plugins configuration_needed: true # False: The plugin will be enabled by the Admin GUI without configuration classname: # Name of the class that implements the plugin @@ -60,20 +61,34 @@ Beschreibung der Schlüsselbegriffe im Abschnitt ``plugin:`` - ``tester:`` Optional können hier die Nutzer angegeben werden, die sich bereit erklärt haben das Plugin zu testen - ``state:`` Entwicklungs-Status des Plugins (gültige Werte: ``develop``, ``ready``, ``qa-passed``) - ``keywords:`` Liste der Schlüsselwörter die das Plugin beschreiben (durch Leerzeichen getrennt) - - ``documentation:`` url die auf eine weiterführende Dokumentation verweist (damit sind nicht die Dateien user_doc.rst, developer_doc.rst oder die veraltete README.md gemeint) + - ``documentation:`` url die auf eine weiterführende Dokumentation verweist (damit sind **nicht** die Dateien + user_doc.rst oder die veraltete README.md gemeint) - ``support:`` url die auf einen Support Thread oder ein Support Forum verweist - - ``version:`` VersionsNumber des Plugins. Sie wird beim Laden mit der Versionsnummer die im Code definierert ist verglichen. - - ``sh_minversion:`` Minimale SmartHomeNG Version mit der das Plugin kmpatibel ist. Falls `sh_minversion`` leer ist, nimmt SmartHomeNG an, dass das Plugin mit jeder älteren Version von SmartHomeNG kompatibel ist. - - ``sh_maxversion:`` Maximale SmartHomeNG Version mit der das Plugin kmpatibel ist. Falls `sh_maxversion`` leer ist, nimmt SmartHomeNG an, dass das Plugin mit jeder neueren Version von SmartHomeNG kompatibel ist. - - ``py_minversion:`` Minimale Python Version mit der das Plugin kmpatibel ist. Falls `py_minversion`` leer ist, nimmt SmartHomeNG an, dass das Plugin mit jeder älteren Python Version komatibel ist die von SmartHomeNG unterstützt wird. - - ``py_maxversion:`` Maximale Python Version mit der das Plugin kmpatibel ist. Falls `py_maxversion`` leer ist, nimmt SmartHomeNG an, dass das Plugin mit jeder neueren Python Version komatibel ist die von SmartHomeNG unterstützt wird. + - ``version:`` VersionsNumber des Plugins. Sie wird beim Laden mit der Versionsnummer die im Code definierert ist + verglichen. + - ``sh_minversion:`` Minimale SmartHomeNG Version mit der das Plugin kmpatibel ist. Falls `sh_minversion`` leer + ist, nimmt SmartHomeNG an, dass das Plugin mit jeder älteren Version von SmartHomeNG kompatibel ist. + - ``sh_maxversion:`` Maximale SmartHomeNG Version mit der das Plugin kmpatibel ist. Falls `sh_maxversion`` leer + ist, nimmt SmartHomeNG an, dass das Plugin mit jeder neueren Version von SmartHomeNG kompatibel ist. + - ``py_minversion:`` Minimale Python Version mit der das Plugin kmpatibel ist. Falls `py_minversion`` leer ist, + nimmt SmartHomeNG an, dass das Plugin mit jeder älteren Python Version komatibel ist die von SmartHomeNG + unterstützt wird. + - ``py_maxversion:`` Maximale Python Version mit der das Plugin kmpatibel ist. Falls `py_maxversion`` leer ist, + nimmt SmartHomeNG an, dass das Plugin mit jeder neueren Python Version komatibel ist die von SmartHomeNG + unterstützt wird. - ``multi_instance:`` Ist das Plugin in der Lage in mehreren Instanzen gestartet zu werden? (``True``, ``False``) - - ``configuration_needed:`` Normalerweise wird in der Admin GUI ein Plugin im Status disabled hinzugefügt und muss erst konfiguriert werden. - Wenn dieser Parameter auf ``False`` gesetzt wird, wird das Plugin im Status ``enabled`` hinzugefügt. Das ist sinnvoll bei Plugins, die - ohne Konfiguration lauffähig sind. (``True``, ``False``) + - ``configuration_needed:`` Normalerweise wird in der Admin GUI ein Plugin im Status disabled hinzugefügt und muss + erst konfiguriert werden. Wenn dieser Parameter auf ``False`` gesetzt wird, wird das Plugin im + Status ``enabled`` hinzugefügt. Das ist sinnvoll bei Plugins, die ohne Konfiguration lauffähig sind. + (``True``, ``False``) - ``restartable:`` Is the Plugin Restart bzw. Reload fählg? (gültige Werte: ``True``, ``False``, ``unknown``) - - ``classname:`` Name der Python Klasse die das Plugin implementiert un die zum Start des Plugins initialisiert wird - - - ``classpath:`` **Wird normalerweise nicht angegeben** - Nur angeben, wenn das Plugin außerhalb des ``/plugins`` Verzeichnisses gespeichert ist, + - ``startorder`` Dieser Parameter darf nur bei speziellen Plugins gesetzt werden, die besondere Anforderungen + an die Startreihenfolge haben (wie z.B. das database Plugin). Gültige Werte sind ``early``, ``normal`` + und ``late``. Plugins mit ``startorder`` ``early`` werden vor den anderen Plugins gestartet (und beim + Beenden nach den anderen Plugins beendet) + - ``classname:`` Name der Python Klasse die das Plugin implementiert un die zum Start des Plugins initialisiert + wird. + - ``classpath:`` **Wird normalerweise nicht angegeben** - Nur angeben, wenn das Plugin außerhalb des ``/plugins`` + Verzeichnisses gespeichert ist, diff --git a/doc/user/source/referenz/smarthomeng/ablauf_startup #2.rst b/doc/user/source/referenz/smarthomeng/ablauf_startup #2.rst new file mode 100644 index 0000000000..d922b1c1c9 --- /dev/null +++ b/doc/user/source/referenz/smarthomeng/ablauf_startup #2.rst @@ -0,0 +1,204 @@ + +.. index:: Ablauf des Startups + +.. role:: bluesup +.. role:: redsup + + +================================= +Ablauf des Starts von SmartHomeNG +================================= + +Im folgenden ist der Ablauf der einzelnen Schritte beschrieben, die beim Start von SmartHomeNG abgearbeitet werden. + + +Erste Schritte der Initialisierung +================================== + +Als allererstes werden die Python Requirements des Core geprüft. Falls sie nicht erfüllt sind (dann kann SmartHomeNG +evtl. nicht mal die Konfigurationsdateien lesen), werden die unbedingt benötigten Packages installiert und +SmartHomeNG wird neu gestartet. (In diesem Stadium erfolgen die Log Ausgaben noch auf den Bildschirm) + +Anschließend werden Kommandozeilen Parameter ausgewertet, falls welche angegeben wurden. +Danach wird das smarthome Objekt erzeugt und wie im Folgenden beschrieben initialisert. + + +Initialisierung des smarthome Objektes +====================================== + +.. index:: SmartHomeNG Initialisierung + +Die Schritte der Objekt Initialisierung sind in Gruppen zusammengefasst. Der Aktuelle Stand (welche Gruppe gerade +abgearbeitet wird bzw. abgearbeitet wurde) wird in der AdminGUI auf der Seite **Dienste** angezeigt. + +Die einzelnen Stati haben zur Identifizierung innerhalb von SmartHomeNG jeweils eine eindeutige Nummer, die im +folgenden mit angegeben ist. Nummern kleiner als 10 identifizieren Schritte innerhalb der Objekt Initialisierung. +Schritte größer-gleich 10 und kleiner als 20 identifizieren Schritte innerhalb der start() Methode. + + +0 - Initialisierung +------------------- + +Das smarthome Objekt wird erzeugt und initialisiert. Dazu werden die grundlegenden Variablen initialisiert. +Anschließend wird geprüft ob die benötigten Konfigurationsdateien existieren. Falls nicht, werden die +entsprechenden **.yaml.default** Dateien umkopiert. + +Es werden die Konfigurationsdateienb **smarthome.yaml** und **logging.yaml** eingelesen und das Logging wird +initialisiert. + + +1 - Initialisierung: Logging initialisiert +------------------------------------------ + +Dis initialisierung des Logging ist abgeschlossen (ab nun erfolgen die Log Ausgaben in die konfigurieten Log-Dateien) + +SmartHomeNG wird "daemonized", läuft ab jetzt also im Hintergrund. Anschließend wird der initiale Log Eintrag mit +Versionsinformationen geschrieben. + +Die Mehrsprachen Unterstützung wird initialisiert. + +Es wird geprüft, ob die benötigten Packages für die Module und die konfigurierten Plugins installiert sind. Falls +nicht, werden die Packages installiert und SmartHomeNG wird neu gestartet. + + +2 - Initialisierung: Voraussetzungen überprüft +---------------------------------------------- + +Die benötigten Python Packages sind installiert. + +Es werden die definierten Feiertage geladen. + +Es wird geprüft, ob die Uhrzeit des Systems gesetzt ist. Falls nicht, wird gewartet bis das Datum des Systems +einen plausiblen Wert hat. + +Es werden die Speicherstrukturen für das Memory-Log initialisiert. Diese Strukturen werden für das memlog Plugin +benötigt. + + +10 - Startet +------------ + +Es wird der Scheduler initialisiert und gestartet. +Es wird die Class für lib.connections initialisiert. + + +11 - Startet: Initialisiert und startet ladbare Module +------------------------------------------------------ + +Es werden die konfigurierten Module in der Reihenfolge in der sie in der etc/module.yaml konfiguriert sind geladen +und initialisiert. + +Es wird die Wrapper Class für die Items initialisiert. + + +12 - Startet: Initialisiert Plugins +----------------------------------- + +Es werden die konfigurierten Plugins geladen und es wird je geladenem Plugin ein Worker-Thread erzeugt (falls +die Plugins nicht disabled sind)- + + +13 - Startet: Lädt Item Definitionen +------------------------------------ + +Es wird für jedes definierte Item ein Objekt erzeugt. Der Wert des Items wird aus den folgenden Quellen +(in der angegebenen Reihenfolge) initialisiert: + +- aus dem **initial_value:** bzw. **value:** Attribut +- aus dem Cache, falls das Attribut **cache** auf **True** gesetzt ist. +- evtl. aus den parse_item() Methoden der geladenen Plugins. + (Das wird z.B. im database Plugin genutzt, falls dort das Item Attribut **database:** auf **init** konfiguriert ist.) + + +14 - Startet: Geladene Items vorbereiten +---------------------------------------- + +Es wird für alle geladenen Items die _init_prerun() Methode aufgerufen. Diese erzeugt eval Ausdrücke und Trigger +vor dem ersten Lauf, falls Spezial-Funktionen (**and**, **or**, **sum**, **avg**, **min** oder **max**) verwendet +wurden. + +Es wird für alle geladenen Items Scheduler gestartet, falls für das Item das Attribut **crontab:** oder **cycle:** +gesetzt wurde. + +Es wird für alle geladenen Items bei denen eval-Ausdrücke **und** eval-Trigger definiert sind, die Berechungen +des eval-Ausdrucks ausgelöst. + + +15 - Startet: Initialisiert Logiken +----------------------------------- + +Es werden die parse_logic() Methoden aller geladenen Plugins aufgerufen und es werden für alle geladenen Logiken +die in **watch_items** definierten Trigger gesetzt. + +Anschließend werden die Szenen initialisiert. +Weiterhin wird der **Connections** Scheduler für lib.connection gestartet. + + +16 - Startet: Startet Plugins +----------------------------- + +Es wird die start() Methode aller geladenen Plugins aufgerufen. + +Anschließend wird der Maintenance Scheduler **sh.gc** von SmartHomeNG gestartet, der regelmäßig eine Garbage +Collection durchführt. + + +Laufzeit von SmartHomeNG +======================== + +Der folgende Status tritt nach der Initialisierung ein und bleibt erhalten, bis SmartHomeNG beendet wird: + +20 - Aktiv +---------- + +Die Initialisierung ist abgeschlossen und SmartHomeNG arbeitet normal. + + +Beendigung von SmartHomeNG +========================== + +Die folgenden Stati treten auf, wenn SmartHomeNG beendet oder neu gestartet wird: + + +30 - Startet neu +---------------- + +Es wurde ein Neustart von SmartHomeNG ausgelöst. + + +31 - Stoppen +------------ + +SmartHomeNG wird beendet. + + +32 - Stoppen: Threads beenden +----------------------------- + +SmartHomeNG beendet laufende Threads. + + +33 - Angehalten +--------------- + +SmartHomeNG wurde beendet. + + +Das shng_status dict +==================== + +.. index:: shng_status +.. index:: SmartHomeNG Status + +Die oben beschriebenen Stati können über das ```hng_status``` dict des smarthome Objektes abgerufen werden. +Das Dict enthält zwei Einträge (``code`` und ``text``). Über den Key ``code`` kann auf den numerischen Wert des +Status von SmartHomeNG zugegriffen werden. Unter dem Key ``text`` ist der Status im Klartext hinterlegt. Diese +Texte werden in der Admin GUI unter Dienste angezeigt. + +Um sicher zu stellen, dass bestimmter Code nur ausgeführt wird, wenn SmartHomeNG vollständig initialisiert ist und +nicht im Begriff ist sich zu beenden, kann abgefragt werden, ob der Status-Code 20 ist: + +.. code-block:: python + + if self._sh.shng_status['code'] == 20: + diff --git a/doc/user/source/referenz/smarthomeng/ablauf_startup.rst b/doc/user/source/referenz/smarthomeng/ablauf_startup.rst index 1ab2dc7c49..49bced6389 100644 --- a/doc/user/source/referenz/smarthomeng/ablauf_startup.rst +++ b/doc/user/source/referenz/smarthomeng/ablauf_startup.rst @@ -1,4 +1,4 @@ -d bleibt wehalten, bis SmartHomeNG beendet wi + .. index:: Ablauf des Startups .. role:: bluesup @@ -26,6 +26,8 @@ Danach wird das smarthome Objekt erzeugt und wie im Folgenden beschrieben initia Initialisierung des smarthome Objektes ====================================== +.. index:: SmartHomeNG Initialisierung + Die Schritte der Objekt Initialisierung sind in Gruppen zusammengefasst. Der Aktuelle Stand (welche Gruppe gerade abgearbeitet wird bzw. abgearbeitet wurde) wird in der AdminGUI auf der Seite **Dienste** angezeigt. @@ -73,6 +75,15 @@ Es werden die Speicherstrukturen für das Memory-Log initialisiert. Diese Strukt benötigt. +3 - Initialisierung: Prüfung der Prozessor-Geschwindigkeit +---------------------------------------------------------- + +SmartHomeNG prüft die CPU Geschwindigkeit. Dieses dient zur Optimierung einzelner Abläufe. + +Die gemessene Geschwindigkeit wird persistent gespeichert, so dass die Messung bei einem Neustart nicht erneut +durchgeführt wird, es sei denn die Hardware hat sich geändert. + + 10 - Startet ------------ @@ -180,3 +191,25 @@ SmartHomeNG beendet laufende Threads. --------------- SmartHomeNG wurde beendet. + +| + +Das shng_status dict +==================== + +.. index:: shng_status +.. index:: SmartHomeNG Status + +Die oben beschriebenen Stati können über das ``shng_status`` dict des smarthome Objektes abgerufen werden. +Das Dict enthält zwei Einträge (``code`` und ``text``). Über den Key ``code`` kann auf den numerischen Wert des +Status von SmartHomeNG zugegriffen werden. Unter dem Key ``text`` ist der Status im Klartext hinterlegt. Diese +Texte werden in der Admin GUI unter Dienste angezeigt. + +Um sicher zu stellen, dass bestimmter Code nur ausgeführt wird, wenn SmartHomeNG vollständig initialisiert ist und +nicht im Begriff ist sich zu beenden, kann abgefragt werden, ob der Status-Code 20 ist: + +.. code-block:: python + + if self._sh.shng_status['code'] == 20: + ... + diff --git a/doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst b/doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst index 15811c0b85..a283c561c4 100644 --- a/doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst +++ b/doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst @@ -121,6 +121,8 @@ Die Funktionen für das Datums-Handling sind folgende: +-----------------------------------------------+----------------------------------------------------------------------------------+ | shtime.current_month(offset=0) | Liefert den aktuellen Monat | +-----------------------------------------------+----------------------------------------------------------------------------------+ +| shtime.current_monthname(offset=0) | Liefert den Namens des aktuellen Monats | ++-----------------------------------------------+----------------------------------------------------------------------------------+ | shtime.current_day(offset=0) | Liefert den aktuellen Tag | +-----------------------------------------------+----------------------------------------------------------------------------------+ | shtime.day_of_year(date=None, offset=0) | Liefert als Ergebnis, der wievielte Tag im Jahr das angegebene Datum ist | diff --git a/doc/user/source/referenz/userfunctions/userfunctions.rst b/doc/user/source/referenz/userfunctions/userfunctions.rst index 26dddcfbcd..a7ff84374a 100644 --- a/doc/user/source/referenz/userfunctions/userfunctions.rst +++ b/doc/user/source/referenz/userfunctions/userfunctions.rst @@ -3,9 +3,9 @@ .. role:: greensup .. role:: redsup -=========================== -Userfunctions :redsup:`Neu` -=========================== +============= +Userfunctions +============= Ab Version 1.9 von SmartHomeNG ist die Möglichkeit implementiert, benutzerdefinierte Funktionen (Userfunctions) zu schreiben und in eval Statements sowie in Logiken zu verwenden. @@ -249,3 +249,5 @@ man **uf.reload('anhalter')** eingeben und **Prüfen** klicken. Man kann auch alle benutzerdefinierte Dateien neu laden, indem man **uf.reload_all()** eingibt und **Prüfen** klickt. +Über die reload Funktionalität für Userfunctions können auch Userfunction Module neu geladen werden, die erst nach dem Start von +SmartHomeNG erstellt wurden. diff --git a/doc/user/source/release/1_9_3.rst b/doc/user/source/release/1_9_3.rst index e3f98c022e..e0a2ffc4e8 100644 --- a/doc/user/source/release/1_9_3.rst +++ b/doc/user/source/release/1_9_3.rst @@ -10,15 +10,6 @@ sowie Updates zu einigen Plugins. Dieses Release ist ein Feature-Release. Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. -.. note:: - - Diese Release Notes sind ein Arbeitsstand. - - - Berücksichtigt sind Commits im smarthome Repository bis incl. 29. Okt 2022 - (http module: Improve datatables, ...) - - Berücksichtigt sind Commits im plugins Repository bis incl. 29. Okt 2022 - (Merge pull request #641 from ivan73/develop ...) - Überblick ========= @@ -30,10 +21,10 @@ folgenden in diesen Release Notes beschrieben. Minimum Python Version ---------------------- -Die absolute Minimum Python Version in der SmartHomeNG startet, ist **Python 3.6**. +Die absolute Minimum Python Version in der SmartHomeNG startet, ist **Python 3.7**. -Für das SmartHomeNG Release 1.10 wird die absolute Minimum Python Version auf **Python 3.7** angehoben, da der -Community Support für Python 3.6 am 23. Dezember 2021 endete. +Für das SmartHomeNG Release 1.10 wird die absolute Minimum Python Version auf **Python 3.8** angehoben, da der +Community Support für Python 3.7 am 27. Juni 2023 endete. Bei einer Neuinstallation wird jedoch empfohlen auf einer der neueren Python Versionen (3.8 oder 3.9) aufzusetzen. @@ -51,6 +42,13 @@ zu unterstützten Python Versionen) Allgemeine Änderungen ===================== +.. attention:: + + ACHTUNG: Nach dem Update unbedingt den Browser Cache löschen !!! + + Sonst können die Darstellungen des Webinterfaces fehlerhaft sein, + + * Die automatisierten Unit Tests von SmartHomeNG wurden von Travis auf Github Workflows umgestellt. | @@ -197,7 +195,7 @@ http://www.smarthomeng.de/user/plugins_all.html konsultieren. * Fix bug with timestamp being None * Translate rest of user_doc into german * Added parameter default_maxage - *Increased default value for max_delete_logentries to 1500 + * Increased default value for max_delete_logentries to 1500 * enocean: @@ -457,6 +455,3 @@ Dokumentation * Added include /etc/nginx/proxy_params; to reverse proxy docu * Multiple updates * Allow shpinx versions 5.x (<6) - - - diff --git a/doc/user/source/release/1_9_4.rst b/doc/user/source/release/1_9_4.rst new file mode 100644 index 0000000000..9e2d0b97bf --- /dev/null +++ b/doc/user/source/release/1_9_4.rst @@ -0,0 +1,722 @@ +============================= +Release 1.9.4 - 14. März 2023 +============================= + +Dieses Release ist ein Wartungs-Release. Außer Bugfixes gibt es einige neue Features im Core von SmartHomeNG, +sowie einige neue Plugins. + +.. only: comment + + Dieses Release ist ein Feature-Release. Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. + +| + +.. only: develop + .. note:: + + Diese Release Notes sind ein Arbeitsstand. + + - Berücksichtigt sind Commits im smarthome Repository bis incl. 13. März 2023 + (http module: introduce ...) + - Berücksichtigt sind Commits im plugins Repository bis incl. 11. März 2023 + (telegram plugin: fix webif_pagelength, bump version to 1.7.1) + + +Überblick +========= + +Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.3 sind im +folgenden in diesen Release Notes beschrieben. + + +Minimum Python Version +---------------------- + +Die absolute Minimum Python Version ist in der Dokumentation unter +:ref:`Hard- u. Software Anforderungen ` im Abschnitt **Python Versionen** dokumentiert. + +Für das SmartHomeNG Release 1.10 wird die absolute Minimum Python Version evtl. auf **Python 3.8** angehoben, zumal +der Security support für Pyton 3.7 im Juni 2023 endet. + +Bei einer Neuinstallation wird jedoch empfohlen auf einer der neueren Python Versionen (3.9, 3.10 oder 3.11) aufzusetzen. + + +Unterstützte Python Versionen +----------------------------- + +Die älteste offiziell unterstützte Python Version für SmartHomeNG Release 1.9.x ist **Python 3.7**. +Automatisierte Tests von SmartHomeNG werden nur in den unterstützten Python Versionen durchgeführt. +(Siehe auch :ref:`Hard- u. Software Anforderungen ` im Abschnitt **Installation** +zu unterstützten Python Versionen) + +| + +Änderungen am Core +================== + +Allgmein +-------- + +* Workflows: + + * Added Github actions workflow to successfully test pull requests + + +Updates in the CORE +------------------- + +* Configuration: + + * logging.yaml.default: Modified LEVEL for plugins: to NOTICE to reflect the ability of plugins to log NOTICEs + +* Libs: + + * lib.config: + + * Adjusting log levels for missing module/plugin config + * Fix startup error on empty plugins/modules config + + * lib-daemon: + + * Fix for issue #498 - Error on restarting if SmartHomeNG is not running + + * lib.item: + + * Fix startup error on empty plugins/modules config + * Delete invalid cahe files (size=0 bytes), so a new cache file is created + * Bugfix for relative path expansion + * Bugfix for relative destination item pathes in on_update/on_change if '=' assignement is used + + * lib.metadata: + + * Moved definition of global plugin parameter 'instance' from shngadmin to lib.metadata + * Implemented global plugin parameter 'webif_pagelength' (takes definition from parameter with same name in module.http) + * Added plugin parameter 'startorder' + + * lib.model.smartplugin: + + * Preparing SmartPlugin for editable items + * Minor fixes + * Fix to create local _plg_item_dict and _item_lookup_dict for each plugin (that calls super().__init__) + * Adjust add_item functionality + * add_item(): Updating parameter + * add_item(): Prevent default parameters from being changed in method + * Added SmartPlugin doc (to temporary location at lib.model) + * add_item() corrected, get_device_commands() added and get_items() renamed to get_item_list() + * Changed 'device_command' to 'mapping' + * added get_item_mapping() + * Added filter_key and filter_value as arguments for get_item_list() and get_item_path_list() + * Renamed get_items_for_command() to get_items_for_mapping() + * Updated inline documentation + + * lib.network: + + * Catch (ignore) errors if socket is no longer connected on shutdown + + * lib.plugin: + + * Implemented extended logging levels (NOTICE, DBGHIGH, DBGMED, DBGLOW) for plugins too + * Added plugin parameter 'startorder' + * Reversed stop order of plugins. Now they are stopped in the oposite order they were started + * Info added to plugin exception log entries + + * lib.scheduler: + + * Catch more exceptions outside scheduler to prevent thread end + * Adjusted logging on leaving run method + * Added set_worker_warn_count() method + + * lib.shpypi: + + * Bugfix + + * lib.shtime: + + * current_monthname() implemented + + * lib.smarthome: + + * Added reload_translations() to smarthome object to enable reload of translations during runtime through + eval syntax-checker (evaluate: sh.reload_translations()) + * Info added to log entires of not catched exceptions + * Modifications to use lib.systeminfo + * Added call lib.systeminfo to measure cpu speed and set worker-warn-count in scheduler acordingly + * Adjusted startorder + + * lib.systeminfo: + + * New library to get info about the system, SmartHomeNG is running on + + * lib.triggertimes: + + * Added info to error log message + + * lib.translation: + + * Enhanced translations to accept module specific translations (for module/http) + + * lib.utils: + + * Corrected handling of is_knx_groupaddress() when parameter 'groupaddress' is no string + + +* Modules: + + * admin: + + * Fix for reading of logfiles if folder contains files without extension .log + * Updated module to reflect new url of documentation and allow access to documentation of defelop branch + * Updated shngAdmin to v0.6.1 + * Moved definition of global plugin parameter 'instance' from shngadmin to lib.metadata + * Updated shngAdmin to v0.6.3 + * Fix for displaying on_update and on_change in item details + * Updated shngAdmin to v0.6.4 + * Fixed issue #522: Warning after changing plugin loglevel + * shngadmin: Modifications to system ovwerview (show if running in virtual environment, ...) + * Updated shngAdmin to v0.6.5 + * Modified to use systeminfo from smarthome object + + * http: + + * Datatables: put responsive hide/show child into separate first column to avoid conflicts with truncate + functionality in first column. Be aware: a new th/td html element needs to be put into all datatables! + * Made top row/table responsive using javascript + * Margins for autorefresh elements + * Make headtable responsive - calculate table min-width automatically if not provided manually + * Improve table td widths + * Implement webif_pagelength to globally define standard page length of + datatables (set to "auto fit to page height by default) + * Implement option to disable buttons in header + * Throw console warning if first column has some content (it should be empty!) + * Make web interfaces more responsive (width of top table, height of header) - updates + * Bump version to 1.7.0 (as lots of changes were made ;)) + * Show auto refresh options only if update_interval is defined + * Avoid errors and warnings if auto refresh options are not shown as well as when table has no data + * Add some more date/time formats for correct sorting + * Fix header min-width calculation + * Fix and improve responsive header + * Webinterfaces: Bug fix for reload-button + * Webinterfaces: Improved datatable implementation (fix responsive issues on tab load/change, include
above datatable in resize calculation) + * Webinterfaces: Fix resize calculation.. consider all divs with class mb-2 for resize + * Webinterfaces: Instant update table refresh if checkbox for autoupdate is activated + * Webinterfaces: Included javascript file for codemirror + * Webinterfaces: When creating an icon on the homescreen on iPhones and iPads, the created icon is the plugin logo + if the plugin logo is an png file + * Introduce connectionretries parameter, improve datatables handling + + * mqtt: + + * Fixed a runtime error "dictionary changed size during iteration" + + * websocket: + + * Added canceling of all smartVISU abos when websocket connection is dropped + * Adjusted log levels for lost connections + * Added cancel_log to sv payload protocol + * Bumped sv payload protocol version to 4.1 + * Fixed bug trying do cancel a log, when no logs are monitored for that client + * Seperated example protocol 'sync example' to it's own class + * Improved handling in cancel_log, if log subscription is not found + * Some code streamlining + * Completed isolation of payload protocols + * Bumped to v1.0.9 + * Improved handling in cancel_log, if log subscription is not found + + +* tests: + + * Updated core mockup for extended log levels for plugins + +| + +Änderungen bei Plugins +====================== + +Allgmein +-------- + +* Workflows: + + * Added Python 3.11 to unit tests + * Added Github actions workflow to successfully test pull requests + + +.. _release194_neue_plugins: + +Neue Plugins +------------ + +Für Details zu den neuen Plugins, bitte die Dokumentation des jeweiligen Plugins unter +http://www.smarthomeng.de/user/plugins_all.html konsultieren. + +* indego4shng: Plugin, the original indego-plugin is deprecated +* piratewthr: Weather plugin as a replacement for the darksky plugin. It is (almost) a dropin replacement +* rcs1000n: Plugin for 433 MHz wireless sockets e.g. for Brennenstuhl RCS 1000 N +* sml2: Plugin to read out smartmeter information with SML protocol +* webpush: Plugin to send web push messages to clients +* db_addon: Add-On for the database plugin for data evaluation + +| + +Plugin Updates +-------------- + +Für Details zu den Änderungen an den einzelnen Plugins, bitte die Dokumentation des jeweiligen Plugins unter +http://www.smarthomeng.de/user/plugins_all.html konsultieren. + + * avdevice: + + * Fix web interface to work with newest datatables changes + * Update and improve web interface and webif_pagelength handling + + * avm: + + * Minor clean-up for user_doc and metadata + * Complete re-writing of plugin, putting TR064, AHA und CallMonitor in separate Classes for better readability + and maintenance + * Available services and actions will be read at startup from device itself and are in general available within + the plugin all known item attributes and functions are available + * Deprecated warning for some item attributes will be put to logger + * Please change to new attributes (e.g. ain --> avm_ain) + * Items with avm_attribute causing errors (e.g. read of deflection of not set) will be blacklisted + after error occurred 3 times, Reset of blacklist is available during runtime via WebIF + * Some new functions implemented + + * beolink: + + * Bugfix in webinterface + + * blockly: + + * Fix for failing unittests. The unittests imported tests from the (now retired) backend plugin instead of + defining the tests localy. + + * casambi: + + * Fixed switch on/off state readback due to API or firmware change + * Added handling for networkLog events from backend as debug outputs + * Separated WebIf into separate file + * Cleaned-up user_doc + * Added changelog to user_doc + * Minor change to user_doc + + * cli: + + * Adjust web interface + * Updates to documentation + + * database: + + * Include webif_pagelength from module.yaml, move column classes to dt init function + * Minor webif fixes and improvements + * Simplify and improve web interface + * Added parameter to set a default maxage for items w/o individual one + * Optimized remove of log entries older as maxage (extreme speed increase) + * Reimplemented cleanup (removal of orphans) to be able to run parallel to normal operation + * Corrected download filename for csv dump + * Implemented sql dump for sqlite3 databases + * Added orphan-tab to web interface; enhanced/fixed ude of http-module translations; + * Code cleanup + * Updated user dorumentation + * Implemented separate db connection for maintenance (orphan handling) to fix problems with mysql driver (whiwch allows only one cursor per connection) + * Fix showing of orphaned items if logcount is disabled. + * Bumped version to 1.6.9 + * Minor adjustments in metadata and documentation + * Adjusted a log message + * Set startorder to 'early' + + * dlms: + + * Add logo to user_doc + + * enocean: + + * Separated WebIf from main file + * New user_doc + * Fixed EEP F6_10_00, further work on user_doc + * Improved user_doc + + * executor: + + * Remove eval capability, add script listbox, load, save and delete + * Enhancement - autocomplete for codemirror + + * gpio: + + * Fix minor typos, add user doc, implement webif_pagelength and correct automatic page length on browser window resize + * Adjust webif_pagelength handling + + * harmony: + + * Added item attribute to metadata + * Bumped version to 1.4.2 + * Fixed bug 'PowerOff handling not correct' from issue #674 + + * helios: + + * Changes to Helios Modbus RTU plugin (metadata only) + + * helios_tcp: + + * Fixes to work with newer pymodbus versions + + * homeconnect: + + * Clean-up for code and metadata + + * hue2: + + * Implemented for lights: read from bridge für items with function 'dict' + * Added Modell ID to web interface + * Added more verbose log messages for deCONZ software bridge + * Added function 'modify_scene' + * Incorporated fixes/additions from pr #667 for bridge discovery, typo for "hue_refence_light_id" + * Implementation On-State retrieval for groups through parsing groups "any_on" state + * Added handling for sensors and a couple of hue2_functions for sensors + * Bump to v2.3.0 + * Added reading of some config parameters for sensors + * Removed some logging + * Added enforce_updates to dict item in struct _light_basic + + * husky2: + + * Updated refresh of token + + * ical: + + * Fixed decoding of calendar events that have the same identifiers, e.g. happens if single + events of a calendar series are rescheduled once. + * Minor adaptions in user_doc + + * influxdata: + + * Set startorder to 'early' + + * influxdb: + + * Set startorder to 'early' + + * influxdb2: + + * Updates to documentation + * Set startorder to 'early' + + * join: + + * Added say and language parameters, see issue #673 + + * jsonread: + + * Adjusted requirements.txt + + * knx: + + * Improve structure (introduce globals, put webif in separate init file) + * Introduce webif_pagelength and improve web interface + * Improve web interface + * Fix values being None for plugin with instance defined + * Adjust webif_pagelength handling, first preparation for automatic data update in webif + * Webif: Adapt handling of knx_Listen: If knx_listen just contains 1 entry, it will be shown directly, if knx_listen contains more than 1 entry, it will be shown as list. + * Updated metadata definition + * Corrected description of knx_cache in metadata + * Fixed call to _cacheread() in handle_connect(), if ga is an empty string + * Bumped version to 1.8.5 + + * kostalmodbus: + + * Fixes to work with new version of pymodbus and add registers includes #658 and adds missing registeres for KSEM and Kostal inverters + * Fixes to work with newer pymodbus versions + + * ksemmodbus: + + * Fixes to work with new version of pymodbus and add registers includes #658 and adds missing registeres for KSEM and Kostal inverters + * Fixes to work with newer pymodbus versions + + * Modbus_tcp: + + * Fixes to work with newer pymodbus versions + + * mqtt: + + * Update webif_pagelength implementation, small webif adjustments + * Adjust webif_pagelength handling, minor fixes in user_doc and webif + + * neato: + + * Log only one error if robot is not reachable via backend + * Seperated WebInterface into separate file + * Cleaned-up user_doc + * Added warning message for server return code 500 + + * network: + + * Minor adaptations in user_doc + + * onewire: + + * Several enhancements and fixes + * Vastly extended webinterface + * Documentation updates + * Implemented auto refresh for the webinterface + * Added plugin parameter 'warn_after' + * Disabled reload button in web interface (the web interface now does a complete auto update for all data) + * Clean-up for user_doc and metadata + * Streamlined handling of webif_pagelength + * Changed to use new SmartPlugin (core v1.9.3.4) + * Bumped version to 1.9.1 + * Update for changed SmartPlugin method name get_items_for_mapping() + * Modified some log messages + + * openweathermap: + + * Update Error-Handling mechanism and set version to 1.8.6 + * Improve error handling + * Made API version configurable. Recently registered new users shall use API version 3.0 instead of version 2.5 which will run out + * Degraded subsequent error messages following login errors to debug level to avoid spamming of the error log file. + * Partly reverted API version configuration: Make only the OnceCall API configurable to 2.5 or 3.0, all other APIs(pollution, weather etc.) remain at version 2.5 + * Clean-up for user_doc and metadata + + * philips_tv: + + * Separated WebIf + * Cleaned-up user_doc + * Mark plugin as ready + * Fixed translation in locale.yaml + + * pluggit: + + * Fixes to work with newer pymodbus versions + + * resol: + + * Catched socket timeout exception which occurs if ethernet is temporarily not available. + * Extended user_doc + + * rtr2: + + * F2ixes the configuration of Kp and Ki for individual rtrs using the rtr2_controller_settings item attribute + * Added english descriptions vor plugin parameters + * Added a section to user_doc.rst from PR #648 + * Added 'cache: True' to item stellwert' in struct to prevent mis-regulations when restarting SmartHomeNG + * Implemented autoupdate for webinterface + * Bumped version to 2.2.0 + * Disabled reload button in web interface (the web interface now does a complete auto update for all data) + * Added plugin-logo to user_doc.rst + * Changed default values in metadata + + * sma_mb: + + * Fixes to work with newer pymodbus versions + + * smartvisu: + + * Updates to documentation + * Implemented auto refresh for clients tab in web interface; + * Extended protocol documentation (log_cancel) + * Modified to work with websocket module 1.0.9 + * Set sh_minversion to 1.9.3.5 + * Fixed bug 'error reading deprecated.yaml' from issue #670 + * Simplify webif_pagelength in index.html + * Support for darkmode handling of smartvisu 3.3 and above added + * Bumped version to 1.8.9 + + * sml: + + * Added missing requirement for pyserial + + * smlx: + + * Add files via upload + + * solarforecast: + + * Catch error, if solarforecast webservice is temporarily not reachable + * Separated WebIf from main file + * Cleaned-up user_doc and metadata + + * sonos: + + * Fix for mute does not work for coupled speakers. -> Fixed by making mute a group command. + * Updated underlying SoCo framework to version 29.0, which amongst others fixes exception in unsubscribe function. + Catched pipe error that could occur in play_snippet for local sound files. + * Added play_sharelink command (to support Spotify sharelinks) + * Added user_doc + * Separated WebIf from main file + * Massive rework of user_doc + * Several changes (description in commit 6761e43) + * Added methods to class speaker + * Added handling of play_favorite_title, play_favorite_number, play_favorite_radio_number, + play_favorite_radio_title, _play_sonos_radio to class Sonos + * Added attribute 'send_attributes' to metadata + * Documentation update + * Bumped version to 1.8.0 + * minor fix for play_favorite_number function, catching exception that occurs if index position 0 is passed as argument + + * stateengine: + + * Improve web interface - automatic refresh, show loglevel on item basis, show visu on button click. + Needs most recent http module update (Nov, 8) to work correctly + * Implement webif_pagelength from module, slight webif adjustments + * Bump to v 1.9.3 + * Fix log message for delay + * Fix error on startup (important) + * Fix metadata and implement webif in docu + * Adjust webif_pagelength handling + * Simplify webif_pagelength in index.html + + * tasmota: + + * Bumped to Version 1.3 + * Implement Cluster und Group Commands for Zigbee + * Improved parsing of discovery message to detect and identify existing devices + * Improved start of subscriptions to notice all discovery messages + * Easy Zigbee data handling (more flat dict structure) + * Define allowed attribute values for tasmota_zb_attr + * Added support for SHT3x sensor connected to Tasmota device + * Updated WebIF to latest http-module + * Code Cleanup and beautifying for better readability + * Implement Tasmota SML; Add further Zigbee attributes + * Update WebIF + * Use pagelength for http module + * Support ESP32 temps and temps an analog pin + * User docu update + + * tankerkoenig: + + * Bumped to Version 2 + * Added support of items (No need for logic anymore) but keep recent public functions identical + * Add WebIF + * Reducing available functions + * Named _full function back to old name + * Added cast to float to rad value + * Added check for Nonetype results + + * telegram: + + * Control SH-item in the bot + * Example in readme.rst.off + * Bak to control-menu after "on" or "off" + * Small optical cosmetics + * Adjust web interface, include webif_pagelength + * Bump version to 1.7.1 + + * trovis557x: + + * Changes to Samson Trovis 557x plugin (for new pymodbus versions) + + * uzsu: + + * Get webif_pagelength from module.yaml if possible, adjust webif accordingly + * Bump to v1.6.4 + * Adjusted requirements.txt for scipy to run unit tests under Python 3.11 + * Adjust webif_pagelength handling to new parameter handling + * Removed deprecated required_package check for scipy module + + * xiaomi_vac: + + * Fix docu, adjust web interface, implement webif_pagelength from http module + * Adjust webif_pagelength handling + * Fix docu + +| + +Outdated Plugins +---------------- + +Die folgenden Plugins wurden bereits in v1.6 als *deprecated* (veraltet) markiert. Das bedeutet, dass diese +Plugins zwar noch funktionsfähig sind, aber nicht mehr weiter entwickelt werden. Sie werden in einem kommenden +Release von SmartHomeNG entfernt werden. Anwender dieser Plugins sollten auf geeignete Nachfolge-Plugins +wechseln. + +* System Plugins + + * sqlite_visu2_8 - switch to the **database** plugin + +* Web Plugins + + * wunderground - The free API is not provided anymore by Wunderground + * darksky - The api will stop working on end of march 2023 - switch to **piratewthr** plugin + + +Die folgenden Plugins wurden in v1.7 als *deprecated* (veraltet) markiert, weil kein Nutzer oder Tester +dieser Plugins gefunden werden konnte. Falls sich kein Nutzer findet, werden sie mit **SmartHomeNG v1.10** retired: + +* Gateway Plugins + + * ecmd + * elro + * iaqstick + * snom + * tellstick + +* Interface Plugins + + * easymeter + * smawb + * vr100 + +* Web Plugins + + * nma + +Weiterhin wurde das bisherige mqtt Plugin zu mqtt1 umbenannt und als *deprecated* markiert, da das neue mqtt +Plugin diese Funktionalität übernimmt. Das neue mqtt Plugin nutzt dazu das mqtt Modul des aktuellen Cores +von SmartHomeNG. + +| + +Retired Plugins +--------------- + +Die folgenden Plugins wurden *retired* (in den RUhestand geschickt). Sie waren in einem der vorangegangenen +Releases von SmartHomeNG als *deprecated* markiert worden. Diese Plugins wurden aus dem **plugins** Repository +entfernt, stehen jedoch als Referenz weiterhin zur Verfügung. Diese Plugins wurden in das **plugin_archive** +Repositiory aufgenommen. + +* backend: Since there is a security flaw in the depricated plugin, it has been retired + +| + +Weitere Änderungen +================== + +Tools +----- + +* backup_restore.py: + + * Proposed fix for tarfile extractall + +* check_plugin.py: + + * Initial development version of check_plugin.py (a formal plugin checker) + * Option added to check all plugins + +* setpermissions: + + * New tool to set permissions after checkout from github + +* measure_cpu_speed.py: + + * New tool to set permissions after checkout from github + +| + +Dokumentation +------------- + +* Changed builddevdoc to use Python 3.9 +* Updates to icons and widgets in docu "visu: automatich generation" +* fix and improve logging (filter) docu +* Corrected link for Google translation of documentation + +* Sample Plugins: + + * Adjust mqtt and standard sample plugin to reflect the newest changes (webif_pagelength, additional + column for responsive in datatables) + +* http module: + + * Update documentation (include webif_pagelength updates, minor fixes, additional options to show/hide buttons, etc.) + + diff --git a/doc/user/source/release/1_x_x.rst.vorlage b/doc/user/source/release/1_x_x.rst.vorlage index b7b492637b..3b1f9ae500 100644 --- a/doc/user/source/release/1_x_x.rst.vorlage +++ b/doc/user/source/release/1_x_x.rst.vorlage @@ -9,33 +9,35 @@ sowie einige neue Plugins. Dieses Release ist ein Feature-Release. Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. +| -.. note:: +.. only: develop + .. note:: - Diese Release Notes sind ein Arbeitsstand. + Diese Release Notes sind ein Arbeitsstand. - - Berücksichtigt sind Commits im smarthome Repository bis incl. ... - (...) - - Berücksichtigt sind Commits im plugins Repository bis incl. ... - (...) + - Berücksichtigt sind Commits im smarthome Repository bis incl. ... + (...) + - Berücksichtigt sind Commits im plugins Repository bis incl. ... + (...) Überblick ========= -Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.2 sind im +Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.4 sind im folgenden in diesen Release Notes beschrieben. Minimum Python Version ---------------------- -Die absolute Minimum Python Version in der SmartHomeNG startet, ist **Python 3.6**. +Die absolute Minimum Python Version in der SmartHomeNG startet, ist **Python 3.7**. -Für das SmartHomeNG Release 1.10 wird die absolute Minimum Python Version auf **Python 3.7** angehoben, da der -Community Support für Python 3.6 am 23. Dezember 2021 endete. +Für das SmartHomeNG Release 1.10 wird die absolute Minimum Python Version auf **Python 3.8** angehoben, da der +Community Support für Python 3.7 am 27. Juni 2023 endete. -Bei einer Neuinstallation wird jedoch empfohlen auf einer der neueren Python Versionen (3.8 oder 3.9) aufzusetzen. +Bei einer Neuinstallation wird jedoch empfohlen auf einer der neueren Python Versionen (3.9 oder 3.10) aufzusetzen. Unterstützte Python Versionen @@ -51,6 +53,14 @@ zu unterstützten Python Versionen) Änderungen am Core ================== +Allgmein +-------- + +* Workflows: + + * ... + + Bugfixes in the CORE -------------------- @@ -93,6 +103,14 @@ Updates in the CORE Änderungen bei Plugins ====================== +Allgmein +-------- + +* Workflows: + + * ... + + Neue Plugins ------------ diff --git a/doc/user/source/visualisierung/automatic_generation.rst b/doc/user/source/visualisierung/automatic_generation.rst index 7a4a70194e..5940a62881 100644 --- a/doc/user/source/visualisierung/automatic_generation.rst +++ b/doc/user/source/visualisierung/automatic_generation.rst @@ -73,9 +73,9 @@ Am Beispiel der Küche zeigt die folgende Konfiguration, wie die zusätzlichen I kochen: name: Kochen sv_page: room - sv_img: scene_cooking.png + sv_img: scene_cooking.svg sv_nav_aside: "{{ basic.print('m_kochen.ist', 'wohnung.kochen.heizung.ist', '°') }}" - sv_nav_aside2: "{{ basic.symbol('m_kochen_kaffee2', 'wohnung.kochen.kaffeeautomat.status', '', 'icons/ws/scene_coffee_maker_automatic.png', '2') }} {{ basic.symbol('m_kochen_kaffee3', 'wohnung.kochen.kaffeeautomat.status', '', 'icons/or/scene_coffee_maker_automatic.png', '3') }} {{ basic.symbol('m_kochen_heizen', 'wohnung.kochen.heizung.heizen', '', icon1~'sani_heating.png') }}" + sv_nav_aside2: "{{ basic.symbol('m_kochen_kaffee2', 'wohnung.kochen.kaffeeautomat.status', '', 'scene_coffee_maker_automatic.svg', [2,3],'',['icon0','icon1']) }} {{ basic.symbol('m_kochen_heizen', 'wohnung.kochen.heizung.heizen', '', 'sani_heating.svg', 1, '', 'icon1') }}" Wie in den bisherigen Releases: @@ -130,17 +130,17 @@ Konfigurationsdatei, wie die Navigation konfiguriert wird: konfiguration: name: Konfiguration sv_page: category - sv_img: control_all_on_off.png + sv_img: control_all_on_off.svg beschattung: name: Beschattung sv_page: category - sv_img: fts_shutter_40.png + sv_img: fts_shutter_40.svg beleuchtung: name: Beleuchtungsautomatik sv_page: category - sv_img: light_light_dim_00.png + sv_img: light_light_dim_00.svg ``sv_page`` ist zum Generieren eines Eintrages für die @@ -180,7 +180,7 @@ wie Trenner konfiguriert werden: verteilung: name: Verteilung sv_page: category - sv_img: measure_current.png + sv_img: measure_current.svg separator_test: name: Tests @@ -189,7 +189,7 @@ wie Trenner konfiguriert werden: fritzboxen: name: Fritzboxen sv_page: category - sv_img: it_router.png + sv_img: it_router.svg ``sv_page`` ist zum generieren eines Trenners auf einen speziellen Seitentyp einzustellen. - Wenn ein Trenner in die normale Raumnavigation @@ -274,7 +274,7 @@ wie Trenner konfiguriert werden: verbraucher: name: Verbraucher sv_blocksize: 1 - sv_widget: "{{ basic.switch('wohnung.buero.tv', 'wohnung.buero.tv', icon0~'control_on_off.png', icon0~'control_standby.png') }}
{{ basic.switch('wohnung.buero.computer', 'wohnung.buero.computer', icon0~'control_on_off.png', icon0~'control_standby.png') }}
{{ basic.switch('wohnung.buero.schrank', 'wohnung.buero.schrank', icon0~'control_on_off.png', icon0~'control_standby.png') }}
{{ basic.switch('wohnung.buero.steckdose_tuer', 'wohnung.buero.steckdose_tuer', icon0~'control_on_off.png', icon0~'control_standby.png') }}" + sv_widget: "{{ basic.stateswitch('wohnung.buero.tv', 'wohnung.buero.tv', '', '', ['control_on_off.svg', 'control_standby.svg'], '', ['icon0','icon1']) }}
{{ basic.stateswitch('wohnung.buero.computer', 'wohnung.buero.computer', '', '', ['control_on_off.svg', 'control_standby.svg'], '', ['icon0','icon1']) }}
{{ basic.stateswitch('wohnung.buero.schrank', 'wohnung.buero.schrank', '', '', ['control_on_off.svg', 'control_standby.svg'], '', ['icon0','icon1']) }}
{{ basic.stateswitch('wohnung.buero.steckdose_tuer', 'wohnung.buero.steckdose_tuer', '', '', ['control_on_off.svg', 'control_standby.svg'], '', ['icon0','icon1']) }}" ``sv_blocksize`` dient zur Einstellung der (minimalen) Blockhöhe und darf die Werte 1, 2 oder 3 annehmen. Wird ``sv_blocksize``\ nicht diff --git a/doc/user/source/visualisierung/item_attributes.rst b/doc/user/source/visualisierung/item_attributes.rst index b4de077c94..c2007bee53 100644 --- a/doc/user/source/visualisierung/item_attributes.rst +++ b/doc/user/source/visualisierung/item_attributes.rst @@ -131,14 +131,14 @@ Example sleeping: name: Sleeping Room sv_page: room - sv_img: scene_sleeping.png - sv_nav_aside: "{{ basic.float('sleep_temp_id', 'second.sleeping.temp', '°') }}" + sv_img: scene_sleeping.svg + sv_nav_aside: "{{ basic.print('sleep_temp_id', 'second.sleeping.temp', '°') }}" light: name: Light type: bool visu_acl: rw - sv_widget: "& ## 123;{ device.dimmer('second.sleeping.light', 'Light', 'second.sleeping.light', 'second.sleeping.light.level') }}" + sv_widget: "{{ device.dimmer('second.sleeping.light', 'Light', 'second.sleeping.light', 'second.sleeping.light.level') }}" knx_dpt: 1 knx_listen: 3/2/12 knx_send: 3/2/12 @@ -162,7 +162,7 @@ The page generator will replace it with the current path. This way you could eas sleeping: name: Sleeping Room sv_page: room - sv_img: scene_sleeping.png + sv_img: scene_sleeping.svg light: name: Light diff --git a/doc/user/source/visualisierung/plugin_widgets.rst b/doc/user/source/visualisierung/plugin_widgets.rst index d245c07316..c5df3d1d8c 100644 --- a/doc/user/source/visualisierung/plugin_widgets.rst +++ b/doc/user/source/visualisierung/plugin_widgets.rst @@ -15,8 +15,9 @@ haben, in welchem die Plugins abgelegt werden. Alle Dateien aus diesem Verzeichn in die smartVISU Installation kopiert. Falls Ab smartVISU v2.9 gibt es in der smartVISU hierzu ein Verzeichnis ``dropins``, wo die Dateien des Widgets abgelegt -werden. Die eigentlichen Widgets werden in ``dropins/widgets`` kopiert. Falls ein Widget Icons mitbringt -(\*.png oder \*.svg), werden diese in ``dropins/icons/ws`` kopiert. +werden. Die eigentlichen Widgets werden in ``dropins/widgets`` kopiert. Ab smartVISU v3.0 und shNG v1.9.2 wird das +Verzeichnis ``dropins/shwidgets`` verwendet. Falls ein Widget Icons mitbringt (\*.png oder \*.svg), werden diese in +``dropins/icons/ws`` kopiert. .. note:: diff --git a/doc/user/source/was_ist_neu.rst b/doc/user/source/was_ist_neu.rst index 660eee0dc6..d24149785a 100644 --- a/doc/user/source/was_ist_neu.rst +++ b/doc/user/source/was_ist_neu.rst @@ -11,7 +11,7 @@ Eine vollständige Übersicht der Änderungen ist den den :doc:`Release Notes ` @@ -25,4 +25,17 @@ Auch bei den Plugins hat es größere Änderungen gegeben: | -(Diese Seite muss vom Layout noch überarbeitet werden) +Neuerungen im Release v1.9.4 +---------------------------- + +Ab dem Release v1.9.4 misst SmartHomeNG beim ersten Start die Geschwindigkeit der CPU, um je nach Geschwindigkeit +interne Konfigurationen vorzunehmen. Die Messung wird nur wiederholt, wenn sich die Hardware geändert hat. + +Die Messung nimmt, je nach CPU, einige Zeit in Anspruch. Auf einem Raspberry Pi 2 zum Beispiel, verlängert sich dadurch +die Start Zeit beim ersten Start von SmartHomeNG um ca. 3 Minuten. + +Es sind auch :ref:`sechs neue Plugins ` hinzugekommen. + + +Die vollständigen Änderungen können in den :doc:`Release Notes ` nachgelesen werden. + diff --git a/etc/logging.yaml.default b/etc/logging.yaml.default index 28ca6746f1..e332291f5e 100644 --- a/etc/logging.yaml.default +++ b/etc/logging.yaml.default @@ -168,7 +168,7 @@ loggers: plugins: # Default logger for SmartHomeNG plugins handlers: [shng_details_file] - level: WARNING + level: NOTICE # ------------------------------------------ diff --git a/lib/config.py b/lib/config.py index 868b70b147..b44a531f68 100644 --- a/lib/config.py +++ b/lib/config.py @@ -69,9 +69,9 @@ def parse_basename(basename, configtype=''): if config == {}: if not (configtype == 'logics'): if configtype == 'module': - logger.error("No valid file '{}.yaml' found with {} configuration".format(basename, configtype)) + logger.warning("No valid file '{}.yaml' found with {} configuration".format(basename, configtype)) else: - logger.critical("No valid file '{}.*' found with {} configuration".format(basename, configtype)) + logger.error("No valid file '{}.*' found with {} configuration".format(basename, configtype)) return config diff --git a/lib/constants.py b/lib/constants.py index 9c252f7f6a..e7e910d840 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -63,8 +63,9 @@ KEY_STRUCT = 'struct' KEY_REMARK = 'remark' -#config params for plugins -KEY_INSTANCE = 'instance' +#global config params for plugins +KEY_INSTANCE = 'instance' +KEY_WEBIF_PAGELENGTH = 'webif_pagelength' KEY_CLASS_PATH = 'class_path' KEY_CLASS_NAME = 'class_name' diff --git a/lib/cpuinfo.py b/lib/cpuinfo.py index 3f5d6ed39b..291080ed47 100644 --- a/lib/cpuinfo.py +++ b/lib/cpuinfo.py @@ -1,8 +1,7 @@ #!/usr/bin/env python -# -*- coding: UTF-8 -*- -# Copyright (c) 2014-2019, Matthew Brennan Jones -# Py-cpuinfo gets CPU info with pure Python 2 & 3 +# Copyright (c) 2014-2022 Matthew Brennan Jones +# Py-cpuinfo gets CPU info with pure Python # It uses the MIT License # It is hosted at: https://github.com/workhorsy/py-cpuinfo # @@ -24,10 +23,8 @@ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Additions for Raspberry Pi by msinn -# -CPUINFO_VERSION = (5, 0, 0) + +CPUINFO_VERSION = (9, 0, 0) CPUINFO_VERSION_STRING = '.'.join([str(n) for n in CPUINFO_VERSION]) import os, sys @@ -35,2387 +32,2808 @@ import multiprocessing import ctypes -try: - import _winreg as winreg -except ImportError as err: - try: - import winreg - except ImportError as err: - pass -IS_PY2 = sys.version_info[0] == 2 CAN_CALL_CPUID_IN_SUBPROCESS = True +g_trace = None + + +class Trace: + def __init__(self, is_active, is_stored_in_string): + self._is_active = is_active + if not self._is_active: + return + + from datetime import datetime + from io import StringIO + + if is_stored_in_string: + self._output = StringIO() + else: + date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f") + self._output = open('cpuinfo_trace_{0}.trace'.format(date), 'w') + + self._stdout = StringIO() + self._stderr = StringIO() + self._err = None + + def header(self, msg): + if not self._is_active: return + + from inspect import stack + frame = stack()[1] + file = frame[1] + line = frame[2] + self._output.write("{0} ({1} {2})\n".format(msg, file, line)) + self._output.flush() + + def success(self): + if not self._is_active: return + + from inspect import stack + frame = stack()[1] + file = frame[1] + line = frame[2] + + self._output.write("Success ... ({0} {1})\n\n".format(file, line)) + self._output.flush() + + def fail(self, msg): + if not self._is_active: return + + from inspect import stack + frame = stack()[1] + file = frame[1] + line = frame[2] + + if isinstance(msg, str): + msg = ''.join(['\t' + line for line in msg.split('\n')]) + '\n' + + self._output.write(msg) + self._output.write("Failed ... ({0} {1})\n\n".format(file, line)) + self._output.flush() + elif isinstance(msg, Exception): + from traceback import format_exc + err_string = format_exc() + self._output.write("\tFailed ... ({0} {1})\n".format(file, line)) + self._output.write(''.join(['\t\t{0}\n'.format(n) for n in err_string.split('\n')]) + '\n') + self._output.flush() + + def command_header(self, msg): + if not self._is_active: return + + from inspect import stack + frame = stack()[3] + file = frame[1] + line = frame[2] + self._output.write("\t{0} ({1} {2})\n".format(msg, file, line)) + self._output.flush() + + def command_output(self, msg, output): + if not self._is_active: return + + self._output.write("\t\t{0}\n".format(msg)) + self._output.write(''.join(['\t\t\t{0}\n'.format(n) for n in output.split('\n')]) + '\n') + self._output.flush() + + def keys(self, keys, info, new_info): + if not self._is_active: return + + from inspect import stack + frame = stack()[2] + file = frame[1] + line = frame[2] + + # List updated keys + self._output.write("\tChanged keys ({0} {1})\n".format(file, line)) + changed_keys = [key for key in keys if key in info and key in new_info and info[key] != new_info[key]] + if changed_keys: + for key in changed_keys: + self._output.write('\t\t{0}: {1} to {2}\n'.format(key, info[key], new_info[key])) + else: + self._output.write('\t\tNone\n') + + # List new keys + self._output.write("\tNew keys ({0} {1})\n".format(file, line)) + new_keys = [key for key in keys if key in new_info and key not in info] + if new_keys: + for key in new_keys: + self._output.write('\t\t{0}: {1}\n'.format(key, new_info[key])) + else: + self._output.write('\t\tNone\n') + + self._output.write('\n') + self._output.flush() + + def write(self, msg): + if not self._is_active: return + + self._output.write(msg + '\n') + self._output.flush() + + def to_dict(self, info, is_fail): + return { + 'output' : self._output.getvalue(), + 'stdout' : self._stdout.getvalue(), + 'stderr' : self._stderr.getvalue(), + 'info' : info, + 'err' : self._err, + 'is_fail' : is_fail + } + +class DataSource: + bits = platform.architecture()[0] + cpu_count = multiprocessing.cpu_count() + is_windows = platform.system().lower() == 'windows' + arch_string_raw = platform.machine() + uname_string_raw = platform.uname()[5] + can_cpuid = True + + @staticmethod + def has_proc_cpuinfo(): + return os.path.exists('/proc/cpuinfo') + + @staticmethod + def has_dmesg(): + return len(_program_paths('dmesg')) > 0 + + @staticmethod + def has_var_run_dmesg_boot(): + uname = platform.system().strip().strip('"').strip("'").strip().lower() + return 'linux' in uname and os.path.exists('/var/run/dmesg.boot') + + @staticmethod + def has_cpufreq_info(): + return len(_program_paths('cpufreq-info')) > 0 + + @staticmethod + def has_sestatus(): + return len(_program_paths('sestatus')) > 0 + + @staticmethod + def has_sysctl(): + return len(_program_paths('sysctl')) > 0 + + @staticmethod + def has_isainfo(): + return len(_program_paths('isainfo')) > 0 + + @staticmethod + def has_kstat(): + return len(_program_paths('kstat')) > 0 + + @staticmethod + def has_sysinfo(): + uname = platform.system().strip().strip('"').strip("'").strip().lower() + is_beos = 'beos' in uname or 'haiku' in uname + return is_beos and len(_program_paths('sysinfo')) > 0 + + @staticmethod + def has_lscpu(): + return len(_program_paths('lscpu')) > 0 + + @staticmethod + def has_ibm_pa_features(): + return len(_program_paths('lsprop')) > 0 + + @staticmethod + def has_wmic(): + returncode, output = _run_and_get_stdout(['wmic', 'os', 'get', 'Version']) + return returncode == 0 and len(output) > 0 + + @staticmethod + def cat_proc_cpuinfo(): + return _run_and_get_stdout(['cat', '/proc/cpuinfo']) + + @staticmethod + def cpufreq_info(): + return _run_and_get_stdout(['cpufreq-info']) + + @staticmethod + def sestatus_b(): + return _run_and_get_stdout(['sestatus', '-b']) + + @staticmethod + def dmesg_a(): + return _run_and_get_stdout(['dmesg', '-a']) + + @staticmethod + def cat_var_run_dmesg_boot(): + return _run_and_get_stdout(['cat', '/var/run/dmesg.boot']) + + @staticmethod + def sysctl_machdep_cpu_hw_cpufrequency(): + return _run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency']) + + @staticmethod + def isainfo_vb(): + return _run_and_get_stdout(['isainfo', '-vb']) + + @staticmethod + def kstat_m_cpu_info(): + return _run_and_get_stdout(['kstat', '-m', 'cpu_info']) + + @staticmethod + def sysinfo_cpu(): + return _run_and_get_stdout(['sysinfo', '-cpu']) + + @staticmethod + def lscpu(): + return _run_and_get_stdout(['lscpu']) + + @staticmethod + def ibm_pa_features(): + import glob + + ibm_features = glob.glob('/proc/device-tree/cpus/*/ibm,pa-features') + if ibm_features: + return _run_and_get_stdout(['lsprop', ibm_features[0]]) + + @staticmethod + def wmic_cpu(): + return _run_and_get_stdout(['wmic', 'cpu', 'get', 'Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer', '/format:list']) + + @staticmethod + def winreg_processor_brand(): + processor_brand = _read_windows_registry_key(r"Hardware\Description\System\CentralProcessor\0", "ProcessorNameString") + return processor_brand.strip() + + @staticmethod + def winreg_vendor_id_raw(): + vendor_id_raw = _read_windows_registry_key(r"Hardware\Description\System\CentralProcessor\0", "VendorIdentifier") + return vendor_id_raw + + @staticmethod + def winreg_arch_string_raw(): + arch_string_raw = _read_windows_registry_key(r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", "PROCESSOR_ARCHITECTURE") + return arch_string_raw + + @staticmethod + def winreg_hz_actual(): + hz_actual = _read_windows_registry_key(r"Hardware\Description\System\CentralProcessor\0", "~Mhz") + hz_actual = _to_decimal_string(hz_actual) + return hz_actual -class DataSource(object): - bits = platform.architecture()[0] - cpu_count = multiprocessing.cpu_count() - is_windows = platform.system().lower() == 'windows' - arch_string_raw = platform.machine() - uname_string_raw = platform.uname()[5] - can_cpuid = True - - @staticmethod - def has_proc_cpuinfo(): - return os.path.exists('/proc/cpuinfo') - - @staticmethod - def has_dmesg(): - return len(_program_paths('dmesg')) > 0 - - @staticmethod - def has_var_run_dmesg_boot(): - uname = platform.system().strip().strip('"').strip("'").strip().lower() - return 'linux' in uname and os.path.exists('/var/run/dmesg.boot') - - @staticmethod - def has_cpufreq_info(): - return len(_program_paths('cpufreq-info')) > 0 - - @staticmethod - def has_sestatus(): - return len(_program_paths('sestatus')) > 0 - - @staticmethod - def has_sysctl(): - return len(_program_paths('sysctl')) > 0 - - @staticmethod - def has_isainfo(): - return len(_program_paths('isainfo')) > 0 - - @staticmethod - def has_kstat(): - return len(_program_paths('kstat')) > 0 - - @staticmethod - def has_sysinfo(): - return len(_program_paths('sysinfo')) > 0 - - @staticmethod - def has_lscpu(): - return len(_program_paths('lscpu')) > 0 - - @staticmethod - def has_ibm_pa_features(): - return len(_program_paths('lsprop')) > 0 - - @staticmethod - def has_wmic(): - returncode, output = _run_and_get_stdout(['wmic', 'os', 'get', 'Version']) - return returncode == 0 and len(output) > 0 - - @staticmethod - def cat_proc_cpuinfo(): - return _run_and_get_stdout(['cat', '/proc/cpuinfo']) - - @staticmethod - def cpufreq_info(): - return _run_and_get_stdout(['cpufreq-info']) - - @staticmethod - def sestatus_b(): - return _run_and_get_stdout(['sestatus', '-b']) - - @staticmethod - def dmesg_a(): - return _run_and_get_stdout(['dmesg', '-a']) - - @staticmethod - def cat_var_run_dmesg_boot(): - return _run_and_get_stdout(['cat', '/var/run/dmesg.boot']) - - @staticmethod - def sysctl_machdep_cpu_hw_cpufrequency(): - return _run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency']) - - @staticmethod - def isainfo_vb(): - return _run_and_get_stdout(['isainfo', '-vb']) - - @staticmethod - def kstat_m_cpu_info(): - return _run_and_get_stdout(['kstat', '-m', 'cpu_info']) - - @staticmethod - def sysinfo_cpu(): - return _run_and_get_stdout(['sysinfo', '-cpu']) - - @staticmethod - def lscpu(): - return _run_and_get_stdout(['lscpu']) - - @staticmethod - def ibm_pa_features(): - import glob - - ibm_features = glob.glob('/proc/device-tree/cpus/*/ibm,pa-features') - if ibm_features: - return _run_and_get_stdout(['lsprop', ibm_features[0]]) - - @staticmethod - def wmic_cpu(): - return _run_and_get_stdout(['wmic', 'cpu', 'get', 'Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer', '/format:list']) - - @staticmethod - def winreg_processor_brand(): - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") - processor_brand = winreg.QueryValueEx(key, "ProcessorNameString")[0] - winreg.CloseKey(key) - return processor_brand.strip() - - @staticmethod - def winreg_vendor_id_raw(): - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") - vendor_id_raw = winreg.QueryValueEx(key, "VendorIdentifier")[0] - winreg.CloseKey(key) - return vendor_id_raw - - @staticmethod - def winreg_arch_string_raw(): - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment") - arch_string_raw = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE")[0] - winreg.CloseKey(key) - return arch_string_raw - - @staticmethod - def winreg_hz_actual(): - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") - hz_actual = winreg.QueryValueEx(key, "~Mhz")[0] - winreg.CloseKey(key) - hz_actual = _to_decimal_string(hz_actual) - return hz_actual - - @staticmethod - def winreg_feature_bits(): - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") - feature_bits = winreg.QueryValueEx(key, "FeatureSet")[0] - winreg.CloseKey(key) - return feature_bits + @staticmethod + def winreg_feature_bits(): + feature_bits = _read_windows_registry_key(r"Hardware\Description\System\CentralProcessor\0", "FeatureSet") + return feature_bits def _program_paths(program_name): - paths = [] - exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)) - path = os.environ['PATH'] - for p in os.environ['PATH'].split(os.pathsep): - p = os.path.join(p, program_name) - if os.access(p, os.X_OK): - paths.append(p) - for e in exts: - pext = p + e - if os.access(pext, os.X_OK): - paths.append(pext) - return paths + paths = [] + exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)) + for p in os.environ['PATH'].split(os.pathsep): + p = os.path.join(p, program_name) + if os.access(p, os.X_OK): + paths.append(p) + for e in exts: + pext = p + e + if os.access(pext, os.X_OK): + paths.append(pext) + return paths def _run_and_get_stdout(command, pipe_command=None): - from subprocess import Popen, PIPE - - if not pipe_command: - p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) - output = p1.communicate()[0] - if not IS_PY2: - output = output.decode(encoding='UTF-8') - return p1.returncode, output - else: - p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) - p2 = Popen(pipe_command, stdin=p1.stdout, stdout=PIPE, stderr=PIPE) - p1.stdout.close() - output = p2.communicate()[0] - if not IS_PY2: - output = output.decode(encoding='UTF-8') - return p2.returncode, output + from subprocess import Popen, PIPE + + g_trace.command_header('Running command "' + ' '.join(command) + '" ...') + + # Run the command normally + if not pipe_command: + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + # Run the command and pipe it into another command + else: + p2 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + p1 = Popen(pipe_command, stdin=p2.stdout, stdout=PIPE, stderr=PIPE) + p2.stdout.close() + + # Get the stdout and stderr + stdout_output, stderr_output = p1.communicate() + stdout_output = stdout_output.decode(encoding='UTF-8') + stderr_output = stderr_output.decode(encoding='UTF-8') + + # Send the result to the logger + g_trace.command_output('return code:', str(p1.returncode)) + g_trace.command_output('stdout:', stdout_output) + + # Return the return code and stdout + return p1.returncode, stdout_output + +def _read_windows_registry_key(key_name, field_name): + g_trace.command_header('Reading Registry key "{0}" field "{1}" ...'.format(key_name, field_name)) + + try: + import _winreg as winreg + except ImportError as err: + try: + import winreg + except ImportError as err: + pass + + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_name) + value = winreg.QueryValueEx(key, field_name)[0] + winreg.CloseKey(key) + g_trace.command_output('value:', str(value)) + return value # Make sure we are running on a supported system def _check_arch(): - arch, bits = _parse_arch(DataSource.arch_string_raw) - if not arch in ['X86_32', 'X86_64', 'ARM_7', 'ARM_8', 'PPC_64', 'S390X']: - raise Exception("py-cpuinfo currently only works on X86 and some ARM/PPC/S390X CPUs.") + arch, bits = _parse_arch(DataSource.arch_string_raw) + if not arch in ['ARM_7', 'ARM_8', + 'LOONG_32', 'LOONG_64', + 'MIPS_32', 'MIPS_64', + 'PPC_32', 'PPC_64', + 'RISCV_32', 'RISCV_64', + 'SPARC_32', 'SPARC_64', + 'S390X', + 'X86_32', 'X86_64']: + raise Exception("py-cpuinfo currently only works on X86 " + "and some ARM/LoongArch/MIPS/PPC/RISCV/SPARC/S390X CPUs.") def _obj_to_b64(thing): - import pickle - import base64 + import pickle + import base64 - a = thing - b = pickle.dumps(a) - c = base64.b64encode(b) - d = c.decode('utf8') - return d + a = thing + b = pickle.dumps(a) + c = base64.b64encode(b) + d = c.decode('utf8') + return d def _b64_to_obj(thing): - import pickle - import base64 + import pickle + import base64 - try: - a = base64.b64decode(thing) - b = pickle.loads(a) - return b - except: - return {} + try: + a = base64.b64decode(thing) + b = pickle.loads(a) + return b + except Exception: + return {} def _utf_to_str(input): - if IS_PY2 and isinstance(input, unicode): - return input.encode('utf-8') - elif isinstance(input, list): - return [_utf_to_str(element) for element in input] - elif isinstance(input, dict): - return {_utf_to_str(key): _utf_to_str(value) - for key, value in input.items()} - else: - return input + if isinstance(input, list): + return [_utf_to_str(element) for element in input] + elif isinstance(input, dict): + return {_utf_to_str(key): _utf_to_str(value) + for key, value in input.items()} + else: + return input def _copy_new_fields(info, new_info): - keys = [ - # MSinn Additions begin - #'vendor_id_raw', 'hardware_raw', 'brand_raw', 'hz_advertised_friendly', 'hz_actual_friendly', - 'vendor_id_raw', 'hardware_raw', 'revision_raw', 'serial_raw', 'brand_raw', 'hz_advertised_friendly', 'hz_actual_friendly', - # MSinn Additions end - 'hz_advertised', 'hz_actual', 'arch', 'bits', 'count', - 'arch_string_raw', 'uname_string_raw', - 'l2_cache_size', 'l2_cache_line_size', 'l2_cache_associativity', - 'stepping', 'model', 'family', - 'processor_type', 'extended_model', 'extended_family', 'flags', - 'l3_cache_size', 'l1_data_cache_size', 'l1_instruction_cache_size' - ] - - for key in keys: - if new_info.get(key, None) and not info.get(key, None): - info[key] = new_info[key] - elif key == 'flags' and new_info.get('flags'): - for f in new_info['flags']: - if f not in info['flags']: info['flags'].append(f) - info['flags'].sort() + keys = [ + 'vendor_id_raw', 'hardware_raw', 'brand_raw', 'hz_advertised_friendly', 'hz_actual_friendly', + 'hz_advertised', 'hz_actual', 'arch', 'bits', 'count', + 'arch_string_raw', 'uname_string_raw', + 'l2_cache_size', 'l2_cache_line_size', 'l2_cache_associativity', + 'stepping', 'model', 'family', + 'processor_type', 'flags', + 'l3_cache_size', 'l1_data_cache_size', 'l1_instruction_cache_size' + ] + + g_trace.keys(keys, info, new_info) + + # Update the keys with new values + for key in keys: + if new_info.get(key, None) and not info.get(key, None): + info[key] = new_info[key] + elif key == 'flags' and new_info.get('flags'): + for f in new_info['flags']: + if f not in info['flags']: info['flags'].append(f) + info['flags'].sort() def _get_field_actual(cant_be_number, raw_string, field_names): - for line in raw_string.splitlines(): - for field_name in field_names: - field_name = field_name.lower() - if ':' in line: - left, right = line.split(':', 1) - left = left.strip().lower() - right = right.strip() - if left == field_name and len(right) > 0: - if cant_be_number: - if not right.isdigit(): - return right - else: - return right - - return None + for line in raw_string.splitlines(): + for field_name in field_names: + field_name = field_name.lower() + if ':' in line: + left, right = line.split(':', 1) + left = left.strip().lower() + right = right.strip() + if left == field_name and len(right) > 0: + if cant_be_number: + if not right.isdigit(): + return right + else: + return right + + return None def _get_field(cant_be_number, raw_string, convert_to, default_value, *field_names): - retval = _get_field_actual(cant_be_number, raw_string, field_names) + retval = _get_field_actual(cant_be_number, raw_string, field_names) - # Convert the return value - if retval and convert_to: - try: - retval = convert_to(retval) - except: - retval = default_value + # Convert the return value + if retval and convert_to: + try: + retval = convert_to(retval) + except Exception: + retval = default_value - # Return the default if there is no return value - if retval is None: - retval = default_value + # Return the default if there is no return value + if retval is None: + retval = default_value - return retval + return retval def _to_decimal_string(ticks): - try: - # Convert to string - ticks = '{0}'.format(ticks) + try: + # Convert to string + ticks = '{0}'.format(ticks) + # Sometimes ',' is used as a decimal separator + ticks = ticks.replace(',', '.') + + # Strip off non numbers and decimal places + ticks = "".join(n for n in ticks if n.isdigit() or n=='.').strip() + if ticks == '': + ticks = '0' + + # Add decimal if missing + if '.' not in ticks: + ticks = '{0}.0'.format(ticks) + + # Remove trailing zeros + ticks = ticks.rstrip('0') + + # Add one trailing zero for empty right side + if ticks.endswith('.'): + ticks = '{0}0'.format(ticks) + + # Make sure the number can be converted to a float + ticks = float(ticks) + ticks = '{0}'.format(ticks) + return ticks + except Exception: + return '0.0' - # Strip off non numbers and decimal places - ticks = "".join(n for n in ticks if n.isdigit() or n=='.').strip() - if ticks == '': - ticks = '0' +def _hz_short_to_full(ticks, scale): + try: + # Make sure the number can be converted to a float + ticks = float(ticks) + ticks = '{0}'.format(ticks) + + # Scale the numbers + hz = ticks.lstrip('0') + old_index = hz.index('.') + hz = hz.replace('.', '') + hz = hz.ljust(scale + old_index+1, '0') + new_index = old_index + scale + hz = '{0}.{1}'.format(hz[:new_index], hz[new_index:]) + left, right = hz.split('.') + left, right = int(left), int(right) + return (left, right) + except Exception: + return (0, 0) - # Add decimal if missing - if '.' not in ticks: - ticks = '{0}.0'.format(ticks) +def _hz_friendly_to_full(hz_string): + try: + hz_string = hz_string.strip().lower() + hz, scale = (None, None) - # Remove trailing zeros - ticks = ticks.rstrip('0') + if hz_string.endswith('ghz'): + scale = 9 + elif hz_string.endswith('mhz'): + scale = 6 + elif hz_string.endswith('hz'): + scale = 0 - # Add one trailing zero for empty right side - if ticks.endswith('.'): - ticks = '{0}0'.format(ticks) + hz = "".join(n for n in hz_string if n.isdigit() or n=='.').strip() + if not '.' in hz: + hz += '.0' - # Make sure the number can be converted to a float - ticks = float(ticks) - ticks = '{0}'.format(ticks) - return ticks - except: - return '0.0' + hz, scale = _hz_short_to_full(hz, scale) -def _hz_short_to_full(ticks, scale): - try: - # Make sure the number can be converted to a float - ticks = float(ticks) - ticks = '{0}'.format(ticks) - - # Scale the numbers - hz = ticks.lstrip('0') - old_index = hz.index('.') - hz = hz.replace('.', '') - hz = hz.ljust(scale + old_index+1, '0') - new_index = old_index + scale - hz = '{0}.{1}'.format(hz[:new_index], hz[new_index:]) - left, right = hz.split('.') - left, right = int(left), int(right) - return (left, right) - except: - return (0, 0) + return (hz, scale) + except Exception: + return (0, 0) -def _hz_friendly_to_full(hz_string): - try: - hz_string = hz_string.strip().lower() - hz, scale = (None, None) +def _hz_short_to_friendly(ticks, scale): + try: + # Get the raw Hz as a string + left, right = _hz_short_to_full(ticks, scale) + result = '{0}.{1}'.format(left, right) + + # Get the location of the dot, and remove said dot + dot_index = result.index('.') + result = result.replace('.', '') + + # Get the Hz symbol and scale + symbol = "Hz" + scale = 0 + if dot_index > 9: + symbol = "GHz" + scale = 9 + elif dot_index > 6: + symbol = "MHz" + scale = 6 + elif dot_index > 3: + symbol = "KHz" + scale = 3 + + # Get the Hz with the dot at the new scaled point + result = '{0}.{1}'.format(result[:-scale-1], result[-scale-1:]) + + # Format the ticks to have 4 numbers after the decimal + # and remove any superfluous zeroes. + result = '{0:.4f} {1}'.format(float(result), symbol) + result = result.rstrip('0') + return result + except Exception: + return '0.0000 Hz' - if hz_string.endswith('ghz'): - scale = 9 - elif hz_string.endswith('mhz'): - scale = 6 - elif hz_string.endswith('hz'): - scale = 0 +def _to_friendly_bytes(input): + import re - hz = "".join(n for n in hz_string if n.isdigit() or n=='.').strip() - if not '.' in hz: - hz += '.0' + if not input: + return input + input = "{0}".format(input) - hz, scale = _hz_short_to_full(hz, scale) + formats = { + r"^[0-9]+B$" : 'B', + r"^[0-9]+K$" : 'KB', + r"^[0-9]+M$" : 'MB', + r"^[0-9]+G$" : 'GB' + } - return (hz, scale) - except: - return (0, 0) + for pattern, friendly_size in formats.items(): + if re.match(pattern, input): + return "{0} {1}".format(input[ : -1].strip(), friendly_size) -def _hz_short_to_friendly(ticks, scale): - try: - # Get the raw Hz as a string - left, right = _hz_short_to_full(ticks, scale) - result = '{0}.{1}'.format(left, right) - - # Get the location of the dot, and remove said dot - dot_index = result.index('.') - result = result.replace('.', '') - - # Get the Hz symbol and scale - symbol = "Hz" - scale = 0 - if dot_index > 9: - symbol = "GHz" - scale = 9 - elif dot_index > 6: - symbol = "MHz" - scale = 6 - elif dot_index > 3: - symbol = "KHz" - scale = 3 - - # Get the Hz with the dot at the new scaled point - result = '{0}.{1}'.format(result[:-scale-1], result[-scale-1:]) - - # Format the ticks to have 4 numbers after the decimal - # and remove any superfluous zeroes. - result = '{0:.4f} {1}'.format(float(result), symbol) - result = result.rstrip('0') - return result - except: - return '0.0000 Hz' + return input -def _to_friendly_bytes(input): - import re +def _friendly_bytes_to_int(friendly_bytes): + input = friendly_bytes.lower() + + formats = [ + {'gib' : 1024 * 1024 * 1024}, + {'mib' : 1024 * 1024}, + {'kib' : 1024}, + + {'gb' : 1024 * 1024 * 1024}, + {'mb' : 1024 * 1024}, + {'kb' : 1024}, - if not input: - return input - input = "{0}".format(input) + {'g' : 1024 * 1024 * 1024}, + {'m' : 1024 * 1024}, + {'k' : 1024}, + {'b' : 1}, + ] - formats = { - r"^[0-9]+B$" : 'B', - r"^[0-9]+K$" : 'KB', - r"^[0-9]+M$" : 'MB', - r"^[0-9]+G$" : 'GB' - } + try: + for entry in formats: + pattern = list(entry.keys())[0] + multiplier = list(entry.values())[0] + if input.endswith(pattern): + return int(input.split(pattern)[0].strip()) * multiplier - for pattern, friendly_size in formats.items(): - if re.match(pattern, input): - return "{0} {1}".format(input[ : -1].strip(), friendly_size) + except Exception as err: + pass - return input + return friendly_bytes def _parse_cpu_brand_string(cpu_string): - # Just return 0 if the processor brand does not have the Hz - if not 'hz' in cpu_string.lower(): - return ('0.0', 0) + # Just return 0 if the processor brand does not have the Hz + if not 'hz' in cpu_string.lower(): + return ('0.0', 0) - hz = cpu_string.lower() - scale = 0 + hz = cpu_string.lower() + scale = 0 - if hz.endswith('mhz'): - scale = 6 - elif hz.endswith('ghz'): - scale = 9 - if '@' in hz: - hz = hz.split('@')[1] - else: - hz = hz.rsplit(None, 1)[1] + if hz.endswith('mhz'): + scale = 6 + elif hz.endswith('ghz'): + scale = 9 + if '@' in hz: + hz = hz.split('@')[1] + else: + hz = hz.rsplit(None, 1)[1] - hz = hz.rstrip('mhz').rstrip('ghz').strip() - hz = _to_decimal_string(hz) + hz = hz.rstrip('mhz').rstrip('ghz').strip() + hz = _to_decimal_string(hz) - return (hz, scale) + return (hz, scale) def _parse_cpu_brand_string_dx(cpu_string): - import re - - # Find all the strings inside brackets () - starts = [m.start() for m in re.finditer('\(', cpu_string)] - ends = [m.start() for m in re.finditer('\)', cpu_string)] - insides = {k: v for k, v in zip(starts, ends)} - insides = [cpu_string[start+1 : end] for start, end in insides.items()] - - # Find all the fields - vendor_id, stepping, model, family = (None, None, None, None) - for inside in insides: - for pair in inside.split(','): - pair = [n.strip() for n in pair.split(':')] - if len(pair) > 1: - name, value = pair[0], pair[1] - if name == 'origin': - vendor_id = value.strip('"') - elif name == 'stepping': - stepping = int(value.lstrip('0x'), 16) - elif name == 'model': - model = int(value.lstrip('0x'), 16) - elif name in ['fam', 'family']: - family = int(value.lstrip('0x'), 16) - - # Find the Processor Brand - # Strip off extra strings in brackets at end - brand = cpu_string.strip() - is_working = True - while is_working: - is_working = False - for inside in insides: - full = "({0})".format(inside) - if brand.endswith(full): - brand = brand[ :-len(full)].strip() - is_working = True - - # Find the Hz in the brand string - hz_brand, scale = _parse_cpu_brand_string(brand) - - # Find Hz inside brackets () after the brand string - if hz_brand == '0.0': - for inside in insides: - hz = inside - for entry in ['GHz', 'MHz', 'Hz']: - if entry in hz: - hz = "CPU @ " + hz[ : hz.find(entry) + len(entry)] - hz_brand, scale = _parse_cpu_brand_string(hz) - break - - return (hz_brand, scale, brand, vendor_id, stepping, model, family) + import re + + # Find all the strings inside brackets () + starts = [m.start() for m in re.finditer(r"\(", cpu_string)] + ends = [m.start() for m in re.finditer(r"\)", cpu_string)] + insides = {k: v for k, v in zip(starts, ends)} + insides = [cpu_string[start+1 : end] for start, end in insides.items()] + + # Find all the fields + vendor_id, stepping, model, family = (None, None, None, None) + for inside in insides: + for pair in inside.split(','): + pair = [n.strip() for n in pair.split(':')] + if len(pair) > 1: + name, value = pair[0], pair[1] + if name == 'origin': + vendor_id = value.strip('"') + elif name == 'stepping': + stepping = int(value.lstrip('0x'), 16) + elif name == 'model': + model = int(value.lstrip('0x'), 16) + elif name in ['fam', 'family']: + family = int(value.lstrip('0x'), 16) + + # Find the Processor Brand + # Strip off extra strings in brackets at end + brand = cpu_string.strip() + is_working = True + while is_working: + is_working = False + for inside in insides: + full = "({0})".format(inside) + if brand.endswith(full): + brand = brand[ :-len(full)].strip() + is_working = True + + # Find the Hz in the brand string + hz_brand, scale = _parse_cpu_brand_string(brand) + + # Find Hz inside brackets () after the brand string + if hz_brand == '0.0': + for inside in insides: + hz = inside + for entry in ['GHz', 'MHz', 'Hz']: + if entry in hz: + hz = "CPU @ " + hz[ : hz.find(entry) + len(entry)] + hz_brand, scale = _parse_cpu_brand_string(hz) + break + + return (hz_brand, scale, brand, vendor_id, stepping, model, family) def _parse_dmesg_output(output): - try: - # Get all the dmesg lines that might contain a CPU string - lines = output.split(' CPU0:')[1:] + \ - output.split(' CPU1:')[1:] + \ - output.split(' CPU:')[1:] + \ - output.split('\nCPU0:')[1:] + \ - output.split('\nCPU1:')[1:] + \ - output.split('\nCPU:')[1:] - lines = [l.split('\n')[0].strip() for l in lines] - - # Convert the lines to CPU strings - cpu_strings = [_parse_cpu_brand_string_dx(l) for l in lines] - - # Find the CPU string that has the most fields - best_string = None - highest_count = 0 - for cpu_string in cpu_strings: - count = sum([n is not None for n in cpu_string]) - if count > highest_count: - highest_count = count - best_string = cpu_string - - # If no CPU string was found, return {} - if not best_string: - return {} - - hz_actual, scale, processor_brand, vendor_id, stepping, model, family = best_string - - # Origin - if ' Origin=' in output: - fields = output[output.find(' Origin=') : ].split('\n')[0] - fields = fields.strip().split() - fields = [n.strip().split('=') for n in fields] - fields = [{n[0].strip().lower() : n[1].strip()} for n in fields] - - for field in fields: - name = list(field.keys())[0] - value = list(field.values())[0] - - if name == 'origin': - vendor_id = value.strip('"') - elif name == 'stepping': - stepping = int(value.lstrip('0x'), 16) - elif name == 'model': - model = int(value.lstrip('0x'), 16) - elif name in ['fam', 'family']: - family = int(value.lstrip('0x'), 16) - - # Features - flag_lines = [] - for category in [' Features=', ' Features2=', ' AMD Features=', ' AMD Features2=']: - if category in output: - flag_lines.append(output.split(category)[1].split('\n')[0]) - - flags = [] - for line in flag_lines: - line = line.split('<')[1].split('>')[0].lower() - for flag in line.split(','): - flags.append(flag) - flags.sort() - - # Convert from GHz/MHz string to Hz - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - - # If advertised hz not found, use the actual hz - if hz_advertised == '0.0': - scale = 6 - hz_advertised = _to_decimal_string(hz_actual) - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'flags' : flags - } - - if hz_advertised and hz_advertised != '0.0': - info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) - info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, scale) - - if hz_advertised and hz_advertised != '0.0': - info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) - info['hz_actual'] = _hz_short_to_full(hz_actual, scale) - - return {k: v for k, v in info.items() if v} - except: - #raise - pass - - return {} + try: + # Get all the dmesg lines that might contain a CPU string + lines = output.split(' CPU0:')[1:] + \ + output.split(' CPU1:')[1:] + \ + output.split(' CPU:')[1:] + \ + output.split('\nCPU0:')[1:] + \ + output.split('\nCPU1:')[1:] + \ + output.split('\nCPU:')[1:] + lines = [l.split('\n')[0].strip() for l in lines] + + # Convert the lines to CPU strings + cpu_strings = [_parse_cpu_brand_string_dx(l) for l in lines] + + # Find the CPU string that has the most fields + best_string = None + highest_count = 0 + for cpu_string in cpu_strings: + count = sum([n is not None for n in cpu_string]) + if count > highest_count: + highest_count = count + best_string = cpu_string + + # If no CPU string was found, return {} + if not best_string: + return {} + + hz_actual, scale, processor_brand, vendor_id, stepping, model, family = best_string + + # Origin + if ' Origin=' in output: + fields = output[output.find(' Origin=') : ].split('\n')[0] + fields = fields.strip().split() + fields = [n.strip().split('=') for n in fields] + fields = [{n[0].strip().lower() : n[1].strip()} for n in fields] + + for field in fields: + name = list(field.keys())[0] + value = list(field.values())[0] + + if name == 'origin': + vendor_id = value.strip('"') + elif name == 'stepping': + stepping = int(value.lstrip('0x'), 16) + elif name == 'model': + model = int(value.lstrip('0x'), 16) + elif name in ['fam', 'family']: + family = int(value.lstrip('0x'), 16) + + # Features + flag_lines = [] + for category in [' Features=', ' Features2=', ' AMD Features=', ' AMD Features2=']: + if category in output: + flag_lines.append(output.split(category)[1].split('\n')[0]) + + flags = [] + for line in flag_lines: + line = line.split('<')[1].split('>')[0].lower() + for flag in line.split(','): + flags.append(flag) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + + # If advertised hz not found, use the actual hz + if hz_advertised == '0.0': + scale = 6 + hz_advertised = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + if hz_advertised and hz_advertised != '0.0': + info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, scale) + + if hz_advertised and hz_advertised != '0.0': + info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) + info['hz_actual'] = _hz_short_to_full(hz_actual, scale) + + return {k: v for k, v in info.items() if v} + except Exception as err: + g_trace.fail(err) + #raise + + return {} def _parse_arch(arch_string_raw): - import re - - arch, bits = None, None - arch_string_raw = arch_string_raw.lower() - - # X86 - if re.match('^i\d86$|^x86$|^x86_32$|^i86pc$|^ia32$|^ia-32$|^bepc$', arch_string_raw): - arch = 'X86_32' - bits = 32 - elif re.match('^x64$|^x86_64$|^x86_64t$|^i686-64$|^amd64$|^ia64$|^ia-64$', arch_string_raw): - arch = 'X86_64' - bits = 64 - # ARM - elif re.match('^armv8-a|aarch64$', arch_string_raw): - arch = 'ARM_8' - bits = 64 - elif re.match('^armv7$|^armv7[a-z]$|^armv7-[a-z]$|^armv6[a-z]$', arch_string_raw): - arch = 'ARM_7' - bits = 32 - elif re.match('^armv8$|^armv8[a-z]$|^armv8-[a-z]$', arch_string_raw): - arch = 'ARM_8' - bits = 32 - # PPC - elif re.match('^ppc32$|^prep$|^pmac$|^powermac$', arch_string_raw): - arch = 'PPC_32' - bits = 32 - elif re.match('^powerpc$|^ppc64$|^ppc64le$', arch_string_raw): - arch = 'PPC_64' - bits = 64 - # SPARC - elif re.match('^sparc32$|^sparc$', arch_string_raw): - arch = 'SPARC_32' - bits = 32 - elif re.match('^sparc64$|^sun4u$|^sun4v$', arch_string_raw): - arch = 'SPARC_64' - bits = 64 - # S390X - elif re.match('^s390x$', arch_string_raw): - arch = 'S390X' - bits = 64 - - return (arch, bits) + import re + + arch, bits = None, None + arch_string_raw = arch_string_raw.lower() + + # X86 + if re.match(r'^i\d86$|^x86$|^x86_32$|^i86pc$|^ia32$|^ia-32$|^bepc$', arch_string_raw): + arch = 'X86_32' + bits = 32 + elif re.match(r'^x64$|^x86_64$|^x86_64t$|^i686-64$|^amd64$|^ia64$|^ia-64$', arch_string_raw): + arch = 'X86_64' + bits = 64 + # ARM + elif re.match(r'^armv8-a|aarch64|arm64$', arch_string_raw): + arch = 'ARM_8' + bits = 64 + elif re.match(r'^armv7$|^armv7[a-z]$|^armv7-[a-z]$|^armv6[a-z]$', arch_string_raw): + arch = 'ARM_7' + bits = 32 + elif re.match(r'^armv8$|^armv8[a-z]$|^armv8-[a-z]$', arch_string_raw): + arch = 'ARM_8' + bits = 32 + # PPC + elif re.match(r'^ppc32$|^prep$|^pmac$|^powermac$', arch_string_raw): + arch = 'PPC_32' + bits = 32 + elif re.match(r'^powerpc$|^ppc64$|^ppc64le$', arch_string_raw): + arch = 'PPC_64' + bits = 64 + # SPARC + elif re.match(r'^sparc32$|^sparc$', arch_string_raw): + arch = 'SPARC_32' + bits = 32 + elif re.match(r'^sparc64$|^sun4u$|^sun4v$', arch_string_raw): + arch = 'SPARC_64' + bits = 64 + # S390X + elif re.match(r'^s390x$', arch_string_raw): + arch = 'S390X' + bits = 64 + # MIPS + elif re.match(r'^mips$', arch_string_raw): + arch = 'MIPS_32' + bits = 32 + elif re.match(r'^mips64$', arch_string_raw): + arch = 'MIPS_64' + bits = 64 + # RISCV + elif re.match(r'^riscv$|^riscv32$|^riscv32be$', arch_string_raw): + arch = 'RISCV_32' + bits = 32 + elif re.match(r'^riscv64$|^riscv64be$', arch_string_raw): + arch = 'RISCV_64' + bits = 64 + # LoongArch + elif re.match(r'^loongarch32$', arch_string_raw): + arch = 'LOONG_32' + bits = 32 + elif re.match(r'^loongarch64$', arch_string_raw): + arch = 'LOONG_64' + bits = 64 + + return (arch, bits) def _is_bit_set(reg, bit): - mask = 1 << bit - is_set = reg & mask > 0 - return is_set - - -def _is_selinux_enforcing(): - # Just return if the SE Linux Status Tool is not installed - if not DataSource.has_sestatus(): - return False - - # Run the sestatus, and just return if it failed to run - returncode, output = DataSource.sestatus_b() - if returncode != 0: - return False - - # Figure out if explicitly in enforcing mode - for line in output.splitlines(): - line = line.strip().lower() - if line.startswith("current mode:"): - if line.endswith("enforcing"): - return True - else: - return False - - # Figure out if we can execute heap and execute memory - can_selinux_exec_heap = False - can_selinux_exec_memory = False - for line in output.splitlines(): - line = line.strip().lower() - if line.startswith("allow_execheap") and line.endswith("on"): - can_selinux_exec_heap = True - elif line.startswith("allow_execmem") and line.endswith("on"): - can_selinux_exec_memory = True - - return (not can_selinux_exec_heap or not can_selinux_exec_memory) - - -class CPUID(object): - def __init__(self): - self.prochandle = None - - # Figure out if SE Linux is on and in enforcing mode - self.is_selinux_enforcing = _is_selinux_enforcing() - - def _asm_func(self, restype=None, argtypes=(), byte_code=[]): - byte_code = bytes.join(b'', byte_code) - address = None - - if DataSource.is_windows: - # Allocate a memory segment the size of the byte code, and make it executable - size = len(byte_code) - # Alloc at least 1 page to ensure we own all pages that we want to change protection on - if size < 0x1000: size = 0x1000 - MEM_COMMIT = ctypes.c_ulong(0x1000) - PAGE_READWRITE = ctypes.c_ulong(0x4) - pfnVirtualAlloc = ctypes.windll.kernel32.VirtualAlloc - pfnVirtualAlloc.restype = ctypes.c_void_p - address = pfnVirtualAlloc(None, ctypes.c_size_t(size), MEM_COMMIT, PAGE_READWRITE) - if not address: - raise Exception("Failed to VirtualAlloc") - - # Copy the byte code into the memory segment - memmove = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)(ctypes._memmove_addr) - if memmove(address, byte_code, size) < 0: - raise Exception("Failed to memmove") - - # Enable execute permissions - PAGE_EXECUTE = ctypes.c_ulong(0x10) - old_protect = ctypes.c_ulong(0) - pfnVirtualProtect = ctypes.windll.kernel32.VirtualProtect - res = pfnVirtualProtect(ctypes.c_void_p(address), ctypes.c_size_t(size), PAGE_EXECUTE, ctypes.byref(old_protect)) - if not res: - raise Exception("Failed VirtualProtect") - - # Flush Instruction Cache - # First, get process Handle - if not self.prochandle: - pfnGetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess - pfnGetCurrentProcess.restype = ctypes.c_void_p - self.prochandle = ctypes.c_void_p(pfnGetCurrentProcess()) - # Actually flush cache - res = ctypes.windll.kernel32.FlushInstructionCache(self.prochandle, ctypes.c_void_p(address), ctypes.c_size_t(size)) - if not res: - raise Exception("Failed FlushInstructionCache") - else: - # Allocate a memory segment the size of the byte code - size = len(byte_code) - pfnvalloc = ctypes.pythonapi.valloc - pfnvalloc.restype = ctypes.c_void_p - address = pfnvalloc(ctypes.c_size_t(size)) - if not address: - raise Exception("Failed to valloc") - - # Mark the memory segment as writeable only - if not self.is_selinux_enforcing: - WRITE = 0x2 - if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE) < 0: - raise Exception("Failed to mprotect") - - # Copy the byte code into the memory segment - if ctypes.pythonapi.memmove(ctypes.c_void_p(address), byte_code, ctypes.c_size_t(size)) < 0: - raise Exception("Failed to memmove") - - # Mark the memory segment as writeable and executable only - if not self.is_selinux_enforcing: - WRITE_EXECUTE = 0x2 | 0x4 - if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE_EXECUTE) < 0: - raise Exception("Failed to mprotect") - - # Cast the memory segment into a function - functype = ctypes.CFUNCTYPE(restype, *argtypes) - fun = functype(address) - return fun, address - - def _run_asm(self, *byte_code): - # Convert the byte code into a function that returns an int - restype = ctypes.c_uint32 - argtypes = () - func, address = self._asm_func(restype, argtypes, byte_code) - - # Call the byte code like a function - retval = func() - - byte_code = bytes.join(b'', byte_code) - size = ctypes.c_size_t(len(byte_code)) - - # Free the function memory segment - if DataSource.is_windows: - MEM_RELEASE = ctypes.c_ulong(0x8000) - ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(address), ctypes.c_size_t(0), MEM_RELEASE) - else: - # Remove the executable tag on the memory - READ_WRITE = 0x1 | 0x2 - if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, READ_WRITE) < 0: - raise Exception("Failed to mprotect") - - ctypes.pythonapi.free(ctypes.c_void_p(address)) - - return retval - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID - def get_vendor_id(self): - # EBX - ebx = self._run_asm( - b"\x31\xC0", # xor eax,eax - b"\x0F\xA2" # cpuid - b"\x89\xD8" # mov ax,bx - b"\xC3" # ret - ) - - # ECX - ecx = self._run_asm( - b"\x31\xC0", # xor eax,eax - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - # EDX - edx = self._run_asm( - b"\x31\xC0", # xor eax,eax - b"\x0f\xa2" # cpuid - b"\x89\xD0" # mov ax,dx - b"\xC3" # ret - ) - - # Each 4bits is a ascii letter in the name - vendor_id = [] - for reg in [ebx, edx, ecx]: - for n in [0, 8, 16, 24]: - vendor_id.append(chr((reg >> n) & 0xFF)) - vendor_id = ''.join(vendor_id) - - return vendor_id - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits - def get_info(self): - # EAX - eax = self._run_asm( - b"\xB8\x01\x00\x00\x00", # mov eax,0x1" - b"\x0f\xa2" # cpuid - b"\xC3" # ret - ) - - # Get the CPU info - stepping = (eax >> 0) & 0xF # 4 bits - model = (eax >> 4) & 0xF # 4 bits - family = (eax >> 8) & 0xF # 4 bits - processor_type = (eax >> 12) & 0x3 # 2 bits - extended_model = (eax >> 16) & 0xF # 4 bits - extended_family = (eax >> 20) & 0xFF # 8 bits - - return { - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'processor_type' : processor_type, - 'extended_model' : extended_model, - 'extended_family' : extended_family - } - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000000h:_Get_Highest_Extended_Function_Supported - def get_max_extension_support(self): - # Check for extension support - max_extension_support = self._run_asm( - b"\xB8\x00\x00\x00\x80" # mov ax,0x80000000 - b"\x0f\xa2" # cpuid - b"\xC3" # ret - ) - - return max_extension_support - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits - def get_flags(self, max_extension_support): - # EDX - edx = self._run_asm( - b"\xB8\x01\x00\x00\x00", # mov eax,0x1" - b"\x0f\xa2" # cpuid - b"\x89\xD0" # mov ax,dx - b"\xC3" # ret - ) - - # ECX - ecx = self._run_asm( - b"\xB8\x01\x00\x00\x00", # mov eax,0x1" - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - # Get the CPU flags - flags = { - 'fpu' : _is_bit_set(edx, 0), - 'vme' : _is_bit_set(edx, 1), - 'de' : _is_bit_set(edx, 2), - 'pse' : _is_bit_set(edx, 3), - 'tsc' : _is_bit_set(edx, 4), - 'msr' : _is_bit_set(edx, 5), - 'pae' : _is_bit_set(edx, 6), - 'mce' : _is_bit_set(edx, 7), - 'cx8' : _is_bit_set(edx, 8), - 'apic' : _is_bit_set(edx, 9), - #'reserved1' : _is_bit_set(edx, 10), - 'sep' : _is_bit_set(edx, 11), - 'mtrr' : _is_bit_set(edx, 12), - 'pge' : _is_bit_set(edx, 13), - 'mca' : _is_bit_set(edx, 14), - 'cmov' : _is_bit_set(edx, 15), - 'pat' : _is_bit_set(edx, 16), - 'pse36' : _is_bit_set(edx, 17), - 'pn' : _is_bit_set(edx, 18), - 'clflush' : _is_bit_set(edx, 19), - #'reserved2' : _is_bit_set(edx, 20), - 'dts' : _is_bit_set(edx, 21), - 'acpi' : _is_bit_set(edx, 22), - 'mmx' : _is_bit_set(edx, 23), - 'fxsr' : _is_bit_set(edx, 24), - 'sse' : _is_bit_set(edx, 25), - 'sse2' : _is_bit_set(edx, 26), - 'ss' : _is_bit_set(edx, 27), - 'ht' : _is_bit_set(edx, 28), - 'tm' : _is_bit_set(edx, 29), - 'ia64' : _is_bit_set(edx, 30), - 'pbe' : _is_bit_set(edx, 31), - - 'pni' : _is_bit_set(ecx, 0), - 'pclmulqdq' : _is_bit_set(ecx, 1), - 'dtes64' : _is_bit_set(ecx, 2), - 'monitor' : _is_bit_set(ecx, 3), - 'ds_cpl' : _is_bit_set(ecx, 4), - 'vmx' : _is_bit_set(ecx, 5), - 'smx' : _is_bit_set(ecx, 6), - 'est' : _is_bit_set(ecx, 7), - 'tm2' : _is_bit_set(ecx, 8), - 'ssse3' : _is_bit_set(ecx, 9), - 'cid' : _is_bit_set(ecx, 10), - #'reserved3' : _is_bit_set(ecx, 11), - 'fma' : _is_bit_set(ecx, 12), - 'cx16' : _is_bit_set(ecx, 13), - 'xtpr' : _is_bit_set(ecx, 14), - 'pdcm' : _is_bit_set(ecx, 15), - #'reserved4' : _is_bit_set(ecx, 16), - 'pcid' : _is_bit_set(ecx, 17), - 'dca' : _is_bit_set(ecx, 18), - 'sse4_1' : _is_bit_set(ecx, 19), - 'sse4_2' : _is_bit_set(ecx, 20), - 'x2apic' : _is_bit_set(ecx, 21), - 'movbe' : _is_bit_set(ecx, 22), - 'popcnt' : _is_bit_set(ecx, 23), - 'tscdeadline' : _is_bit_set(ecx, 24), - 'aes' : _is_bit_set(ecx, 25), - 'xsave' : _is_bit_set(ecx, 26), - 'osxsave' : _is_bit_set(ecx, 27), - 'avx' : _is_bit_set(ecx, 28), - 'f16c' : _is_bit_set(ecx, 29), - 'rdrnd' : _is_bit_set(ecx, 30), - 'hypervisor' : _is_bit_set(ecx, 31) - } - - # Get a list of only the flags that are true - flags = [k for k, v in flags.items() if v] - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D7.2C_ECX.3D0:_Extended_Features - if max_extension_support >= 7: - # EBX - ebx = self._run_asm( - b"\x31\xC9", # xor ecx,ecx - b"\xB8\x07\x00\x00\x00" # mov eax,7 - b"\x0f\xa2" # cpuid - b"\x89\xD8" # mov ax,bx - b"\xC3" # ret - ) - - # ECX - ecx = self._run_asm( - b"\x31\xC9", # xor ecx,ecx - b"\xB8\x07\x00\x00\x00" # mov eax,7 - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - # Get the extended CPU flags - extended_flags = { - #'fsgsbase' : _is_bit_set(ebx, 0), - #'IA32_TSC_ADJUST' : _is_bit_set(ebx, 1), - 'sgx' : _is_bit_set(ebx, 2), - 'bmi1' : _is_bit_set(ebx, 3), - 'hle' : _is_bit_set(ebx, 4), - 'avx2' : _is_bit_set(ebx, 5), - #'reserved' : _is_bit_set(ebx, 6), - 'smep' : _is_bit_set(ebx, 7), - 'bmi2' : _is_bit_set(ebx, 8), - 'erms' : _is_bit_set(ebx, 9), - 'invpcid' : _is_bit_set(ebx, 10), - 'rtm' : _is_bit_set(ebx, 11), - 'pqm' : _is_bit_set(ebx, 12), - #'FPU CS and FPU DS deprecated' : _is_bit_set(ebx, 13), - 'mpx' : _is_bit_set(ebx, 14), - 'pqe' : _is_bit_set(ebx, 15), - 'avx512f' : _is_bit_set(ebx, 16), - 'avx512dq' : _is_bit_set(ebx, 17), - 'rdseed' : _is_bit_set(ebx, 18), - 'adx' : _is_bit_set(ebx, 19), - 'smap' : _is_bit_set(ebx, 20), - 'avx512ifma' : _is_bit_set(ebx, 21), - 'pcommit' : _is_bit_set(ebx, 22), - 'clflushopt' : _is_bit_set(ebx, 23), - 'clwb' : _is_bit_set(ebx, 24), - 'intel_pt' : _is_bit_set(ebx, 25), - 'avx512pf' : _is_bit_set(ebx, 26), - 'avx512er' : _is_bit_set(ebx, 27), - 'avx512cd' : _is_bit_set(ebx, 28), - 'sha' : _is_bit_set(ebx, 29), - 'avx512bw' : _is_bit_set(ebx, 30), - 'avx512vl' : _is_bit_set(ebx, 31), - - 'prefetchwt1' : _is_bit_set(ecx, 0), - 'avx512vbmi' : _is_bit_set(ecx, 1), - 'umip' : _is_bit_set(ecx, 2), - 'pku' : _is_bit_set(ecx, 3), - 'ospke' : _is_bit_set(ecx, 4), - #'reserved' : _is_bit_set(ecx, 5), - 'avx512vbmi2' : _is_bit_set(ecx, 6), - #'reserved' : _is_bit_set(ecx, 7), - 'gfni' : _is_bit_set(ecx, 8), - 'vaes' : _is_bit_set(ecx, 9), - 'vpclmulqdq' : _is_bit_set(ecx, 10), - 'avx512vnni' : _is_bit_set(ecx, 11), - 'avx512bitalg' : _is_bit_set(ecx, 12), - #'reserved' : _is_bit_set(ecx, 13), - 'avx512vpopcntdq' : _is_bit_set(ecx, 14), - #'reserved' : _is_bit_set(ecx, 15), - #'reserved' : _is_bit_set(ecx, 16), - #'mpx0' : _is_bit_set(ecx, 17), - #'mpx1' : _is_bit_set(ecx, 18), - #'mpx2' : _is_bit_set(ecx, 19), - #'mpx3' : _is_bit_set(ecx, 20), - #'mpx4' : _is_bit_set(ecx, 21), - 'rdpid' : _is_bit_set(ecx, 22), - #'reserved' : _is_bit_set(ecx, 23), - #'reserved' : _is_bit_set(ecx, 24), - #'reserved' : _is_bit_set(ecx, 25), - #'reserved' : _is_bit_set(ecx, 26), - #'reserved' : _is_bit_set(ecx, 27), - #'reserved' : _is_bit_set(ecx, 28), - #'reserved' : _is_bit_set(ecx, 29), - 'sgx_lc' : _is_bit_set(ecx, 30), - #'reserved' : _is_bit_set(ecx, 31) - } - - # Get a list of only the flags that are true - extended_flags = [k for k, v in extended_flags.items() if v] - flags += extended_flags - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits - if max_extension_support >= 0x80000001: - # EBX - ebx = self._run_asm( - b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 - b"\x0f\xa2" # cpuid - b"\x89\xD8" # mov ax,bx - b"\xC3" # ret - ) - - # ECX - ecx = self._run_asm( - b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - # Get the extended CPU flags - extended_flags = { - 'fpu' : _is_bit_set(ebx, 0), - 'vme' : _is_bit_set(ebx, 1), - 'de' : _is_bit_set(ebx, 2), - 'pse' : _is_bit_set(ebx, 3), - 'tsc' : _is_bit_set(ebx, 4), - 'msr' : _is_bit_set(ebx, 5), - 'pae' : _is_bit_set(ebx, 6), - 'mce' : _is_bit_set(ebx, 7), - 'cx8' : _is_bit_set(ebx, 8), - 'apic' : _is_bit_set(ebx, 9), - #'reserved' : _is_bit_set(ebx, 10), - 'syscall' : _is_bit_set(ebx, 11), - 'mtrr' : _is_bit_set(ebx, 12), - 'pge' : _is_bit_set(ebx, 13), - 'mca' : _is_bit_set(ebx, 14), - 'cmov' : _is_bit_set(ebx, 15), - 'pat' : _is_bit_set(ebx, 16), - 'pse36' : _is_bit_set(ebx, 17), - #'reserved' : _is_bit_set(ebx, 18), - 'mp' : _is_bit_set(ebx, 19), - 'nx' : _is_bit_set(ebx, 20), - #'reserved' : _is_bit_set(ebx, 21), - 'mmxext' : _is_bit_set(ebx, 22), - 'mmx' : _is_bit_set(ebx, 23), - 'fxsr' : _is_bit_set(ebx, 24), - 'fxsr_opt' : _is_bit_set(ebx, 25), - 'pdpe1gp' : _is_bit_set(ebx, 26), - 'rdtscp' : _is_bit_set(ebx, 27), - #'reserved' : _is_bit_set(ebx, 28), - 'lm' : _is_bit_set(ebx, 29), - '3dnowext' : _is_bit_set(ebx, 30), - '3dnow' : _is_bit_set(ebx, 31), - - 'lahf_lm' : _is_bit_set(ecx, 0), - 'cmp_legacy' : _is_bit_set(ecx, 1), - 'svm' : _is_bit_set(ecx, 2), - 'extapic' : _is_bit_set(ecx, 3), - 'cr8_legacy' : _is_bit_set(ecx, 4), - 'abm' : _is_bit_set(ecx, 5), - 'sse4a' : _is_bit_set(ecx, 6), - 'misalignsse' : _is_bit_set(ecx, 7), - '3dnowprefetch' : _is_bit_set(ecx, 8), - 'osvw' : _is_bit_set(ecx, 9), - 'ibs' : _is_bit_set(ecx, 10), - 'xop' : _is_bit_set(ecx, 11), - 'skinit' : _is_bit_set(ecx, 12), - 'wdt' : _is_bit_set(ecx, 13), - #'reserved' : _is_bit_set(ecx, 14), - 'lwp' : _is_bit_set(ecx, 15), - 'fma4' : _is_bit_set(ecx, 16), - 'tce' : _is_bit_set(ecx, 17), - #'reserved' : _is_bit_set(ecx, 18), - 'nodeid_msr' : _is_bit_set(ecx, 19), - #'reserved' : _is_bit_set(ecx, 20), - 'tbm' : _is_bit_set(ecx, 21), - 'topoext' : _is_bit_set(ecx, 22), - 'perfctr_core' : _is_bit_set(ecx, 23), - 'perfctr_nb' : _is_bit_set(ecx, 24), - #'reserved' : _is_bit_set(ecx, 25), - 'dbx' : _is_bit_set(ecx, 26), - 'perftsc' : _is_bit_set(ecx, 27), - 'pci_l2i' : _is_bit_set(ecx, 28), - #'reserved' : _is_bit_set(ecx, 29), - #'reserved' : _is_bit_set(ecx, 30), - #'reserved' : _is_bit_set(ecx, 31) - } - - # Get a list of only the flags that are true - extended_flags = [k for k, v in extended_flags.items() if v] - flags += extended_flags - - flags.sort() - return flags - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000002h.2C80000003h.2C80000004h:_Processor_Brand_String - def get_processor_brand(self, max_extension_support): - processor_brand = "" - - # Processor brand string - if max_extension_support >= 0x80000004: - instructions = [ - b"\xB8\x02\x00\x00\x80", # mov ax,0x80000002 - b"\xB8\x03\x00\x00\x80", # mov ax,0x80000003 - b"\xB8\x04\x00\x00\x80" # mov ax,0x80000004 - ] - for instruction in instructions: - # EAX - eax = self._run_asm( - instruction, # mov ax,0x8000000? - b"\x0f\xa2" # cpuid - b"\x89\xC0" # mov ax,ax - b"\xC3" # ret - ) - - # EBX - ebx = self._run_asm( - instruction, # mov ax,0x8000000? - b"\x0f\xa2" # cpuid - b"\x89\xD8" # mov ax,bx - b"\xC3" # ret - ) - - # ECX - ecx = self._run_asm( - instruction, # mov ax,0x8000000? - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - # EDX - edx = self._run_asm( - instruction, # mov ax,0x8000000? - b"\x0f\xa2" # cpuid - b"\x89\xD0" # mov ax,dx - b"\xC3" # ret - ) - - # Combine each of the 4 bytes in each register into the string - for reg in [eax, ebx, ecx, edx]: - for n in [0, 8, 16, 24]: - processor_brand += chr((reg >> n) & 0xFF) - - # Strip off any trailing NULL terminators and white space - processor_brand = processor_brand.strip("\0").strip() - - return processor_brand - - # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features - def get_cache(self, max_extension_support): - cache_info = {} - - # Just return if the cache feature is not supported - if max_extension_support < 0x80000006: - return cache_info - - # ECX - ecx = self._run_asm( - b"\xB8\x06\x00\x00\x80" # mov ax,0x80000006 - b"\x0f\xa2" # cpuid - b"\x89\xC8" # mov ax,cx - b"\xC3" # ret - ) - - cache_info = { - 'size_kb' : ecx & 0xFF, - 'line_size_b' : (ecx >> 12) & 0xF, - 'associativity' : (ecx >> 16) & 0xFFFF - } - - return cache_info - - def get_ticks(self): - retval = None - - if DataSource.bits == '32bit': - # Works on x86_32 - restype = None - argtypes = (ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) - get_ticks_x86_32, address = self._asm_func(restype, argtypes, - [ - b"\x55", # push bp - b"\x89\xE5", # mov bp,sp - b"\x31\xC0", # xor ax,ax - b"\x0F\xA2", # cpuid - b"\x0F\x31", # rdtsc - b"\x8B\x5D\x08", # mov bx,[di+0x8] - b"\x8B\x4D\x0C", # mov cx,[di+0xc] - b"\x89\x13", # mov [bp+di],dx - b"\x89\x01", # mov [bx+di],ax - b"\x5D", # pop bp - b"\xC3" # ret - ] - ) - - high = ctypes.c_uint32(0) - low = ctypes.c_uint32(0) - - get_ticks_x86_32(ctypes.byref(high), ctypes.byref(low)) - retval = ((high.value << 32) & 0xFFFFFFFF00000000) | low.value - elif DataSource.bits == '64bit': - # Works on x86_64 - restype = ctypes.c_uint64 - argtypes = () - get_ticks_x86_64, address = self._asm_func(restype, argtypes, - [ - b"\x48", # dec ax - b"\x31\xC0", # xor ax,ax - b"\x0F\xA2", # cpuid - b"\x0F\x31", # rdtsc - b"\x48", # dec ax - b"\xC1\xE2\x20", # shl dx,byte 0x20 - b"\x48", # dec ax - b"\x09\xD0", # or ax,dx - b"\xC3", # ret - ] - ) - retval = get_ticks_x86_64() - - return retval - - def get_raw_hz(self): - import time - - start = self.get_ticks() - - time.sleep(1) - - end = self.get_ticks() - - ticks = (end - start) - - return ticks + mask = 1 << bit + is_set = reg & mask > 0 + return is_set + + +def _is_selinux_enforcing(trace): + # Just return if the SE Linux Status Tool is not installed + if not DataSource.has_sestatus(): + trace.fail('Failed to find sestatus.') + return False + + # Run the sestatus, and just return if it failed to run + returncode, output = DataSource.sestatus_b() + if returncode != 0: + trace.fail('Failed to run sestatus. Skipping ...') + return False + + # Figure out if explicitly in enforcing mode + for line in output.splitlines(): + line = line.strip().lower() + if line.startswith("current mode:"): + if line.endswith("enforcing"): + return True + else: + return False + + # Figure out if we can execute heap and execute memory + can_selinux_exec_heap = False + can_selinux_exec_memory = False + for line in output.splitlines(): + line = line.strip().lower() + if line.startswith("allow_execheap") and line.endswith("on"): + can_selinux_exec_heap = True + elif line.startswith("allow_execmem") and line.endswith("on"): + can_selinux_exec_memory = True + + trace.command_output('can_selinux_exec_heap:', can_selinux_exec_heap) + trace.command_output('can_selinux_exec_memory:', can_selinux_exec_memory) + + return (not can_selinux_exec_heap or not can_selinux_exec_memory) + +def _filter_dict_keys_with_empty_values(info, acceptable_values = {}): + filtered_info = {} + for key in info: + value = info[key] + + # Keep if value is acceptable + if key in acceptable_values: + if acceptable_values[key] == value: + filtered_info[key] = value + continue + + # Filter out None, 0, "", (), {}, [] + if not value: + continue + + # Filter out (0, 0) + if value == (0, 0): + continue + + # Filter out -1 + if value == -1: + continue + + # Filter out strings that start with "0.0" + if type(value) == str and value.startswith('0.0'): + continue + + filtered_info[key] = value + + return filtered_info + +class ASM: + def __init__(self, restype=None, argtypes=(), machine_code=[]): + self.restype = restype + self.argtypes = argtypes + self.machine_code = machine_code + self.prochandle = None + self.mm = None + self.func = None + self.address = None + self.size = 0 + + def compile(self): + machine_code = bytes.join(b'', self.machine_code) + self.size = ctypes.c_size_t(len(machine_code)) + + if DataSource.is_windows: + # Allocate a memory segment the size of the machine code, and make it executable + size = len(machine_code) + # Alloc at least 1 page to ensure we own all pages that we want to change protection on + if size < 0x1000: size = 0x1000 + MEM_COMMIT = ctypes.c_ulong(0x1000) + PAGE_READWRITE = ctypes.c_ulong(0x4) + pfnVirtualAlloc = ctypes.windll.kernel32.VirtualAlloc + pfnVirtualAlloc.restype = ctypes.c_void_p + self.address = pfnVirtualAlloc(None, ctypes.c_size_t(size), MEM_COMMIT, PAGE_READWRITE) + if not self.address: + raise Exception("Failed to VirtualAlloc") + + # Copy the machine code into the memory segment + memmove = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)(ctypes._memmove_addr) + if memmove(self.address, machine_code, size) < 0: + raise Exception("Failed to memmove") + + # Enable execute permissions + PAGE_EXECUTE = ctypes.c_ulong(0x10) + old_protect = ctypes.c_ulong(0) + pfnVirtualProtect = ctypes.windll.kernel32.VirtualProtect + res = pfnVirtualProtect(ctypes.c_void_p(self.address), ctypes.c_size_t(size), PAGE_EXECUTE, ctypes.byref(old_protect)) + if not res: + raise Exception("Failed VirtualProtect") + + # Flush Instruction Cache + # First, get process Handle + if not self.prochandle: + pfnGetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess + pfnGetCurrentProcess.restype = ctypes.c_void_p + self.prochandle = ctypes.c_void_p(pfnGetCurrentProcess()) + # Actually flush cache + res = ctypes.windll.kernel32.FlushInstructionCache(self.prochandle, ctypes.c_void_p(self.address), ctypes.c_size_t(size)) + if not res: + raise Exception("Failed FlushInstructionCache") + else: + from mmap import mmap, MAP_PRIVATE, MAP_ANONYMOUS, PROT_WRITE, PROT_READ, PROT_EXEC + + # Allocate a private and executable memory segment the size of the machine code + machine_code = bytes.join(b'', self.machine_code) + self.size = len(machine_code) + self.mm = mmap(-1, self.size, flags=MAP_PRIVATE | MAP_ANONYMOUS, prot=PROT_WRITE | PROT_READ | PROT_EXEC) + + # Copy the machine code into the memory segment + self.mm.write(machine_code) + self.address = ctypes.addressof(ctypes.c_int.from_buffer(self.mm)) + + # Cast the memory segment into a function + functype = ctypes.CFUNCTYPE(self.restype, *self.argtypes) + self.func = functype(self.address) + + def run(self): + # Call the machine code like a function + retval = self.func() + + return retval + + def free(self): + # Free the function memory segment + if DataSource.is_windows: + MEM_RELEASE = ctypes.c_ulong(0x8000) + ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(self.address), ctypes.c_size_t(0), MEM_RELEASE) + else: + self.mm.close() + + self.prochandle = None + self.mm = None + self.func = None + self.address = None + self.size = 0 + + +class CPUID: + def __init__(self, trace=None): + if trace is None: + trace = Trace(False, False) + + # Figure out if SE Linux is on and in enforcing mode + self.is_selinux_enforcing = _is_selinux_enforcing(trace) + + def _asm_func(self, restype=None, argtypes=(), machine_code=[]): + asm = ASM(restype, argtypes, machine_code) + asm.compile() + return asm + + def _run_asm(self, *machine_code): + asm = ASM(ctypes.c_uint32, (), machine_code) + asm.compile() + retval = asm.run() + asm.free() + return retval + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID + def get_vendor_id(self): + # EBX + ebx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0F\xA2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # EDX + edx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # Each 4bits is a ascii letter in the name + vendor_id = [] + for reg in [ebx, edx, ecx]: + for n in [0, 8, 16, 24]: + vendor_id.append(chr((reg >> n) & 0xFF)) + vendor_id = ''.join(vendor_id) + + return vendor_id + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits + def get_info(self): + # EAX + eax = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\xC3" # ret + ) + + # Get the CPU info + stepping_id = (eax >> 0) & 0xF # 4 bits + model = (eax >> 4) & 0xF # 4 bits + family_id = (eax >> 8) & 0xF # 4 bits + processor_type = (eax >> 12) & 0x3 # 2 bits + extended_model_id = (eax >> 16) & 0xF # 4 bits + extended_family_id = (eax >> 20) & 0xFF # 8 bits + family = 0 + + if family_id in [15]: + family = extended_family_id + family_id + else: + family = family_id + + if family_id in [6, 15]: + model = (extended_model_id << 4) + model + + return { + 'stepping' : stepping_id, + 'model' : model, + 'family' : family, + 'processor_type' : processor_type + } + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000000h:_Get_Highest_Extended_Function_Supported + def get_max_extension_support(self): + # Check for extension support + max_extension_support = self._run_asm( + b"\xB8\x00\x00\x00\x80" # mov ax,0x80000000 + b"\x0f\xa2" # cpuid + b"\xC3" # ret + ) + + return max_extension_support + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits + def get_flags(self, max_extension_support): + # EDX + edx = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the CPU flags + flags = { + 'fpu' : _is_bit_set(edx, 0), + 'vme' : _is_bit_set(edx, 1), + 'de' : _is_bit_set(edx, 2), + 'pse' : _is_bit_set(edx, 3), + 'tsc' : _is_bit_set(edx, 4), + 'msr' : _is_bit_set(edx, 5), + 'pae' : _is_bit_set(edx, 6), + 'mce' : _is_bit_set(edx, 7), + 'cx8' : _is_bit_set(edx, 8), + 'apic' : _is_bit_set(edx, 9), + #'reserved1' : _is_bit_set(edx, 10), + 'sep' : _is_bit_set(edx, 11), + 'mtrr' : _is_bit_set(edx, 12), + 'pge' : _is_bit_set(edx, 13), + 'mca' : _is_bit_set(edx, 14), + 'cmov' : _is_bit_set(edx, 15), + 'pat' : _is_bit_set(edx, 16), + 'pse36' : _is_bit_set(edx, 17), + 'pn' : _is_bit_set(edx, 18), + 'clflush' : _is_bit_set(edx, 19), + #'reserved2' : _is_bit_set(edx, 20), + 'dts' : _is_bit_set(edx, 21), + 'acpi' : _is_bit_set(edx, 22), + 'mmx' : _is_bit_set(edx, 23), + 'fxsr' : _is_bit_set(edx, 24), + 'sse' : _is_bit_set(edx, 25), + 'sse2' : _is_bit_set(edx, 26), + 'ss' : _is_bit_set(edx, 27), + 'ht' : _is_bit_set(edx, 28), + 'tm' : _is_bit_set(edx, 29), + 'ia64' : _is_bit_set(edx, 30), + 'pbe' : _is_bit_set(edx, 31), + + 'pni' : _is_bit_set(ecx, 0), + 'pclmulqdq' : _is_bit_set(ecx, 1), + 'dtes64' : _is_bit_set(ecx, 2), + 'monitor' : _is_bit_set(ecx, 3), + 'ds_cpl' : _is_bit_set(ecx, 4), + 'vmx' : _is_bit_set(ecx, 5), + 'smx' : _is_bit_set(ecx, 6), + 'est' : _is_bit_set(ecx, 7), + 'tm2' : _is_bit_set(ecx, 8), + 'ssse3' : _is_bit_set(ecx, 9), + 'cid' : _is_bit_set(ecx, 10), + #'reserved3' : _is_bit_set(ecx, 11), + 'fma' : _is_bit_set(ecx, 12), + 'cx16' : _is_bit_set(ecx, 13), + 'xtpr' : _is_bit_set(ecx, 14), + 'pdcm' : _is_bit_set(ecx, 15), + #'reserved4' : _is_bit_set(ecx, 16), + 'pcid' : _is_bit_set(ecx, 17), + 'dca' : _is_bit_set(ecx, 18), + 'sse4_1' : _is_bit_set(ecx, 19), + 'sse4_2' : _is_bit_set(ecx, 20), + 'x2apic' : _is_bit_set(ecx, 21), + 'movbe' : _is_bit_set(ecx, 22), + 'popcnt' : _is_bit_set(ecx, 23), + 'tscdeadline' : _is_bit_set(ecx, 24), + 'aes' : _is_bit_set(ecx, 25), + 'xsave' : _is_bit_set(ecx, 26), + 'osxsave' : _is_bit_set(ecx, 27), + 'avx' : _is_bit_set(ecx, 28), + 'f16c' : _is_bit_set(ecx, 29), + 'rdrnd' : _is_bit_set(ecx, 30), + 'hypervisor' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D7.2C_ECX.3D0:_Extended_Features + if max_extension_support >= 7: + # EBX + ebx = self._run_asm( + b"\x31\xC9", # xor ecx,ecx + b"\xB8\x07\x00\x00\x00" # mov eax,7 + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\x31\xC9", # xor ecx,ecx + b"\xB8\x07\x00\x00\x00" # mov eax,7 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the extended CPU flags + extended_flags = { + #'fsgsbase' : _is_bit_set(ebx, 0), + #'IA32_TSC_ADJUST' : _is_bit_set(ebx, 1), + 'sgx' : _is_bit_set(ebx, 2), + 'bmi1' : _is_bit_set(ebx, 3), + 'hle' : _is_bit_set(ebx, 4), + 'avx2' : _is_bit_set(ebx, 5), + #'reserved' : _is_bit_set(ebx, 6), + 'smep' : _is_bit_set(ebx, 7), + 'bmi2' : _is_bit_set(ebx, 8), + 'erms' : _is_bit_set(ebx, 9), + 'invpcid' : _is_bit_set(ebx, 10), + 'rtm' : _is_bit_set(ebx, 11), + 'pqm' : _is_bit_set(ebx, 12), + #'FPU CS and FPU DS deprecated' : _is_bit_set(ebx, 13), + 'mpx' : _is_bit_set(ebx, 14), + 'pqe' : _is_bit_set(ebx, 15), + 'avx512f' : _is_bit_set(ebx, 16), + 'avx512dq' : _is_bit_set(ebx, 17), + 'rdseed' : _is_bit_set(ebx, 18), + 'adx' : _is_bit_set(ebx, 19), + 'smap' : _is_bit_set(ebx, 20), + 'avx512ifma' : _is_bit_set(ebx, 21), + 'pcommit' : _is_bit_set(ebx, 22), + 'clflushopt' : _is_bit_set(ebx, 23), + 'clwb' : _is_bit_set(ebx, 24), + 'intel_pt' : _is_bit_set(ebx, 25), + 'avx512pf' : _is_bit_set(ebx, 26), + 'avx512er' : _is_bit_set(ebx, 27), + 'avx512cd' : _is_bit_set(ebx, 28), + 'sha' : _is_bit_set(ebx, 29), + 'avx512bw' : _is_bit_set(ebx, 30), + 'avx512vl' : _is_bit_set(ebx, 31), + + 'prefetchwt1' : _is_bit_set(ecx, 0), + 'avx512vbmi' : _is_bit_set(ecx, 1), + 'umip' : _is_bit_set(ecx, 2), + 'pku' : _is_bit_set(ecx, 3), + 'ospke' : _is_bit_set(ecx, 4), + #'reserved' : _is_bit_set(ecx, 5), + 'avx512vbmi2' : _is_bit_set(ecx, 6), + #'reserved' : _is_bit_set(ecx, 7), + 'gfni' : _is_bit_set(ecx, 8), + 'vaes' : _is_bit_set(ecx, 9), + 'vpclmulqdq' : _is_bit_set(ecx, 10), + 'avx512vnni' : _is_bit_set(ecx, 11), + 'avx512bitalg' : _is_bit_set(ecx, 12), + #'reserved' : _is_bit_set(ecx, 13), + 'avx512vpopcntdq' : _is_bit_set(ecx, 14), + #'reserved' : _is_bit_set(ecx, 15), + #'reserved' : _is_bit_set(ecx, 16), + #'mpx0' : _is_bit_set(ecx, 17), + #'mpx1' : _is_bit_set(ecx, 18), + #'mpx2' : _is_bit_set(ecx, 19), + #'mpx3' : _is_bit_set(ecx, 20), + #'mpx4' : _is_bit_set(ecx, 21), + 'rdpid' : _is_bit_set(ecx, 22), + #'reserved' : _is_bit_set(ecx, 23), + #'reserved' : _is_bit_set(ecx, 24), + #'reserved' : _is_bit_set(ecx, 25), + #'reserved' : _is_bit_set(ecx, 26), + #'reserved' : _is_bit_set(ecx, 27), + #'reserved' : _is_bit_set(ecx, 28), + #'reserved' : _is_bit_set(ecx, 29), + 'sgx_lc' : _is_bit_set(ecx, 30), + #'reserved' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + extended_flags = [k for k, v in extended_flags.items() if v] + flags += extended_flags + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits + if max_extension_support >= 0x80000001: + # EBX + ebx = self._run_asm( + b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the extended CPU flags + extended_flags = { + 'fpu' : _is_bit_set(ebx, 0), + 'vme' : _is_bit_set(ebx, 1), + 'de' : _is_bit_set(ebx, 2), + 'pse' : _is_bit_set(ebx, 3), + 'tsc' : _is_bit_set(ebx, 4), + 'msr' : _is_bit_set(ebx, 5), + 'pae' : _is_bit_set(ebx, 6), + 'mce' : _is_bit_set(ebx, 7), + 'cx8' : _is_bit_set(ebx, 8), + 'apic' : _is_bit_set(ebx, 9), + #'reserved' : _is_bit_set(ebx, 10), + 'syscall' : _is_bit_set(ebx, 11), + 'mtrr' : _is_bit_set(ebx, 12), + 'pge' : _is_bit_set(ebx, 13), + 'mca' : _is_bit_set(ebx, 14), + 'cmov' : _is_bit_set(ebx, 15), + 'pat' : _is_bit_set(ebx, 16), + 'pse36' : _is_bit_set(ebx, 17), + #'reserved' : _is_bit_set(ebx, 18), + 'mp' : _is_bit_set(ebx, 19), + 'nx' : _is_bit_set(ebx, 20), + #'reserved' : _is_bit_set(ebx, 21), + 'mmxext' : _is_bit_set(ebx, 22), + 'mmx' : _is_bit_set(ebx, 23), + 'fxsr' : _is_bit_set(ebx, 24), + 'fxsr_opt' : _is_bit_set(ebx, 25), + 'pdpe1gp' : _is_bit_set(ebx, 26), + 'rdtscp' : _is_bit_set(ebx, 27), + #'reserved' : _is_bit_set(ebx, 28), + 'lm' : _is_bit_set(ebx, 29), + '3dnowext' : _is_bit_set(ebx, 30), + '3dnow' : _is_bit_set(ebx, 31), + + 'lahf_lm' : _is_bit_set(ecx, 0), + 'cmp_legacy' : _is_bit_set(ecx, 1), + 'svm' : _is_bit_set(ecx, 2), + 'extapic' : _is_bit_set(ecx, 3), + 'cr8_legacy' : _is_bit_set(ecx, 4), + 'abm' : _is_bit_set(ecx, 5), + 'sse4a' : _is_bit_set(ecx, 6), + 'misalignsse' : _is_bit_set(ecx, 7), + '3dnowprefetch' : _is_bit_set(ecx, 8), + 'osvw' : _is_bit_set(ecx, 9), + 'ibs' : _is_bit_set(ecx, 10), + 'xop' : _is_bit_set(ecx, 11), + 'skinit' : _is_bit_set(ecx, 12), + 'wdt' : _is_bit_set(ecx, 13), + #'reserved' : _is_bit_set(ecx, 14), + 'lwp' : _is_bit_set(ecx, 15), + 'fma4' : _is_bit_set(ecx, 16), + 'tce' : _is_bit_set(ecx, 17), + #'reserved' : _is_bit_set(ecx, 18), + 'nodeid_msr' : _is_bit_set(ecx, 19), + #'reserved' : _is_bit_set(ecx, 20), + 'tbm' : _is_bit_set(ecx, 21), + 'topoext' : _is_bit_set(ecx, 22), + 'perfctr_core' : _is_bit_set(ecx, 23), + 'perfctr_nb' : _is_bit_set(ecx, 24), + #'reserved' : _is_bit_set(ecx, 25), + 'dbx' : _is_bit_set(ecx, 26), + 'perftsc' : _is_bit_set(ecx, 27), + 'pci_l2i' : _is_bit_set(ecx, 28), + #'reserved' : _is_bit_set(ecx, 29), + #'reserved' : _is_bit_set(ecx, 30), + #'reserved' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + extended_flags = [k for k, v in extended_flags.items() if v] + flags += extended_flags + + flags.sort() + return flags + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000002h.2C80000003h.2C80000004h:_Processor_Brand_String + def get_processor_brand(self, max_extension_support): + processor_brand = "" + + # Processor brand string + if max_extension_support >= 0x80000004: + instructions = [ + b"\xB8\x02\x00\x00\x80", # mov ax,0x80000002 + b"\xB8\x03\x00\x00\x80", # mov ax,0x80000003 + b"\xB8\x04\x00\x00\x80" # mov ax,0x80000004 + ] + for instruction in instructions: + # EAX + eax = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xC0" # mov ax,ax + b"\xC3" # ret + ) + + # EBX + ebx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # EDX + edx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # Combine each of the 4 bytes in each register into the string + for reg in [eax, ebx, ecx, edx]: + for n in [0, 8, 16, 24]: + processor_brand += chr((reg >> n) & 0xFF) + + # Strip off any trailing NULL terminators and white space + processor_brand = processor_brand.strip("\0").strip() + + return processor_brand + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features + def get_cache(self, max_extension_support): + cache_info = {} + + # Just return if the cache feature is not supported + if max_extension_support < 0x80000006: + return cache_info + + # ECX + ecx = self._run_asm( + b"\xB8\x06\x00\x00\x80" # mov ax,0x80000006 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + cache_info = { + 'size_b' : (ecx & 0xFF) * 1024, + 'associativity' : (ecx >> 12) & 0xF, + 'line_size_b' : (ecx >> 16) & 0xFFFF + } + + return cache_info + + def get_ticks_func(self): + retval = None + + if DataSource.bits == '32bit': + # Works on x86_32 + restype = None + argtypes = (ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + get_ticks_x86_32 = self._asm_func(restype, argtypes, + [ + b"\x55", # push bp + b"\x89\xE5", # mov bp,sp + b"\x31\xC0", # xor ax,ax + b"\x0F\xA2", # cpuid + b"\x0F\x31", # rdtsc + b"\x8B\x5D\x08", # mov bx,[di+0x8] + b"\x8B\x4D\x0C", # mov cx,[di+0xc] + b"\x89\x13", # mov [bp+di],dx + b"\x89\x01", # mov [bx+di],ax + b"\x5D", # pop bp + b"\xC3" # ret + ] + ) + + # Monkey patch func to combine high and low args into one return + old_func = get_ticks_x86_32.func + def new_func(): + # Pass two uint32s into function + high = ctypes.c_uint32(0) + low = ctypes.c_uint32(0) + old_func(ctypes.byref(high), ctypes.byref(low)) + + # Shift the two uint32s into one uint64 + retval = ((high.value << 32) & 0xFFFFFFFF00000000) | low.value + return retval + get_ticks_x86_32.func = new_func + + retval = get_ticks_x86_32 + elif DataSource.bits == '64bit': + # Works on x86_64 + restype = ctypes.c_uint64 + argtypes = () + get_ticks_x86_64 = self._asm_func(restype, argtypes, + [ + b"\x48", # dec ax + b"\x31\xC0", # xor ax,ax + b"\x0F\xA2", # cpuid + b"\x0F\x31", # rdtsc + b"\x48", # dec ax + b"\xC1\xE2\x20", # shl dx,byte 0x20 + b"\x48", # dec ax + b"\x09\xD0", # or ax,dx + b"\xC3", # ret + ] + ) + + retval = get_ticks_x86_64 + return retval + + def get_raw_hz(self): + from time import sleep + + ticks_fn = self.get_ticks_func() + + start = ticks_fn.func() + sleep(1) + end = ticks_fn.func() + + ticks = (end - start) + ticks_fn.free() + + return ticks def _get_cpu_info_from_cpuid_actual(): - ''' - Warning! This function has the potential to crash the Python runtime. - Do not call it directly. Use the _get_cpu_info_from_cpuid function instead. - It will safely call this function in another process. - ''' - - # Get the CPU arch and bits - arch, bits = _parse_arch(DataSource.arch_string_raw) - - # Return none if this is not an X86 CPU - if not arch in ['X86_32', 'X86_64']: - return {} - - # Return none if SE Linux is in enforcing mode - cpuid = CPUID() - if cpuid.is_selinux_enforcing: - return {} - - # Get the cpu info from the CPUID register - max_extension_support = cpuid.get_max_extension_support() - cache_info = cpuid.get_cache(max_extension_support) - info = cpuid.get_info() - - processor_brand = cpuid.get_processor_brand(max_extension_support) - - # Get the Hz and scale - hz_actual = cpuid.get_raw_hz() - hz_actual = _to_decimal_string(hz_actual) - - # Get the Hz and scale - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - info = { - 'vendor_id_raw' : cpuid.get_vendor_id(), - 'hardware_raw' : '', - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, 0), - - 'l2_cache_size' : _to_friendly_bytes(cache_info['size_kb']), - 'l2_cache_line_size' : cache_info['line_size_b'], - 'l2_cache_associativity' : hex(cache_info['associativity']), - - 'stepping' : info['stepping'], - 'model' : info['model'], - 'family' : info['family'], - 'processor_type' : info['processor_type'], - 'extended_model' : info['extended_model'], - 'extended_family' : info['extended_family'], - 'flags' : cpuid.get_flags(max_extension_support) - } - - info = {k: v for k, v in info.items() if v} - return info + ''' + Warning! This function has the potential to crash the Python runtime. + Do not call it directly. Use the _get_cpu_info_from_cpuid function instead. + It will safely call this function in another process. + ''' + + from io import StringIO + + trace = Trace(True, True) + info = {} + + # Pipe stdout and stderr to strings + sys.stdout = trace._stdout + sys.stderr = trace._stderr + + try: + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + # Return none if this is not an X86 CPU + if not arch in ['X86_32', 'X86_64']: + trace.fail('Not running on X86_32 or X86_64. Skipping ...') + return trace.to_dict(info, True) + + # Return none if SE Linux is in enforcing mode + cpuid = CPUID(trace) + if cpuid.is_selinux_enforcing: + trace.fail('SELinux is enforcing. Skipping ...') + return trace.to_dict(info, True) + + # Get the cpu info from the CPUID register + max_extension_support = cpuid.get_max_extension_support() + cache_info = cpuid.get_cache(max_extension_support) + info = cpuid.get_info() + + processor_brand = cpuid.get_processor_brand(max_extension_support) + + # Get the Hz and scale + hz_actual = cpuid.get_raw_hz() + hz_actual = _to_decimal_string(hz_actual) + + # Get the Hz and scale + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + info = { + 'vendor_id_raw' : cpuid.get_vendor_id(), + 'hardware_raw' : '', + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'l2_cache_size' : cache_info['size_b'], + 'l2_cache_line_size' : cache_info['line_size_b'], + 'l2_cache_associativity' : cache_info['associativity'], + + 'stepping' : info['stepping'], + 'model' : info['model'], + 'family' : info['family'], + 'processor_type' : info['processor_type'], + 'flags' : cpuid.get_flags(max_extension_support) + } + + info = _filter_dict_keys_with_empty_values(info) + trace.success() + except Exception as err: + from traceback import format_exc + err_string = format_exc() + trace._err = ''.join(['\t\t{0}\n'.format(n) for n in err_string.split('\n')]) + '\n' + return trace.to_dict(info, True) + + return trace.to_dict(info, False) def _get_cpu_info_from_cpuid_subprocess_wrapper(queue): - # Pipe all output to nothing - sys.stdout = open(os.devnull, 'w') - sys.stderr = open(os.devnull, 'w') + orig_stdout = sys.stdout + orig_stderr = sys.stderr - info = _get_cpu_info_from_cpuid_actual() + output = _get_cpu_info_from_cpuid_actual() - queue.put(_obj_to_b64(info)) + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + queue.put(_obj_to_b64(output)) def _get_cpu_info_from_cpuid(): - ''' - Returns the CPU info gathered by querying the X86 cpuid register in a new process. - Returns {} on non X86 cpus. - Returns {} if SELinux is in enforcing mode. - ''' - from multiprocessing import Process, Queue - - # Return {} if can't cpuid - if not DataSource.can_cpuid: - return {} - - # Get the CPU arch and bits - arch, bits = _parse_arch(DataSource.arch_string_raw) - - # Return {} if this is not an X86 CPU - if not arch in ['X86_32', 'X86_64']: - return {} - - try: - if CAN_CALL_CPUID_IN_SUBPROCESS: - # Start running the function in a subprocess - queue = Queue() - p = Process(target=_get_cpu_info_from_cpuid_subprocess_wrapper, args=(queue,)) - p.start() - - # Wait for the process to end, while it is still alive - while p.is_alive(): - p.join(0) - - # Return {} if it failed - if p.exitcode != 0: - return {} - - # Return the result, only if there is something to read - if not queue.empty(): - output = queue.get() - return _b64_to_obj(output) - else: - return _get_cpu_info_from_cpuid_actual() - except: - pass - - # Return {} if everything failed - return {} + ''' + Returns the CPU info gathered by querying the X86 cpuid register in a new process. + Returns {} on non X86 cpus. + Returns {} if SELinux is in enforcing mode. + ''' + + g_trace.header('Tying to get info from CPUID ...') + + from multiprocessing import Process, Queue + + # Return {} if can't cpuid + if not DataSource.can_cpuid: + g_trace.fail('Can\'t CPUID. Skipping ...') + return {} + + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + # Return {} if this is not an X86 CPU + if not arch in ['X86_32', 'X86_64']: + g_trace.fail('Not running on X86_32 or X86_64. Skipping ...') + return {} + + try: + if CAN_CALL_CPUID_IN_SUBPROCESS: + # Start running the function in a subprocess + queue = Queue() + p = Process(target=_get_cpu_info_from_cpuid_subprocess_wrapper, args=(queue,)) + p.start() + + # Wait for the process to end, while it is still alive + while p.is_alive(): + p.join(0) + + # Return {} if it failed + if p.exitcode != 0: + g_trace.fail('Failed to run CPUID in process. Skipping ...') + return {} + + # Return {} if no results + if queue.empty(): + g_trace.fail('Failed to get anything from CPUID process. Skipping ...') + return {} + # Return the result, only if there is something to read + else: + output = _b64_to_obj(queue.get()) + import pprint + pp = pprint.PrettyPrinter(indent=4) + #pp.pprint(output) + + if 'output' in output and output['output']: + g_trace.write(output['output']) + + if 'stdout' in output and output['stdout']: + sys.stdout.write('{0}\n'.format(output['stdout'])) + sys.stdout.flush() + + if 'stderr' in output and output['stderr']: + sys.stderr.write('{0}\n'.format(output['stderr'])) + sys.stderr.flush() + + if 'is_fail' not in output: + g_trace.fail('Failed to get is_fail from CPUID process. Skipping ...') + return {} + + # Fail if there was an exception + if 'err' in output and output['err']: + g_trace.fail('Failed to run CPUID in process. Skipping ...') + g_trace.write(output['err']) + g_trace.write('Failed ...') + return {} + + if 'is_fail' in output and output['is_fail']: + g_trace.write('Failed ...') + return {} + + if 'info' not in output or not output['info']: + g_trace.fail('Failed to get return info from CPUID process. Skipping ...') + return {} + + return output['info'] + else: + # FIXME: This should write the values like in the above call to actual + orig_stdout = sys.stdout + orig_stderr = sys.stderr + + output = _get_cpu_info_from_cpuid_actual() + + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + g_trace.success() + return output['info'] + except Exception as err: + g_trace.fail(err) + + # Return {} if everything failed + return {} def _get_cpu_info_from_proc_cpuinfo(): - ''' - Returns the CPU info gathered from /proc/cpuinfo. - Returns {} if /proc/cpuinfo is not found. - ''' - try: - # Just return {} if there is no cpuinfo - if not DataSource.has_proc_cpuinfo(): - return {} - - returncode, output = DataSource.cat_proc_cpuinfo() - if returncode != 0: - return {} - - # Various fields - vendor_id = _get_field(False, output, None, '', 'vendor_id', 'vendor id', 'vendor') - processor_brand = _get_field(True, output, None, None, 'model name','cpu', 'processor') - cache_size = _get_field(False, output, None, '', 'cache size') - stepping = _get_field(False, output, int, 0, 'stepping') - model = _get_field(False, output, int, 0, 'model') - family = _get_field(False, output, int, 0, 'cpu family') - hardware = _get_field(False, output, None, '', 'Hardware') - # MSinn Additions begin - revision = _get_field(False, output, None, '', 'Revision') - serial = _get_field(False, output, None, '', 'Serial') - # MSinn Additions end - # Flags - flags = _get_field(False, output, None, None, 'flags', 'Features') - if flags: - flags = flags.split() - flags.sort() - - # Check for other cache format - if not cache_size: - try: - for i in range(0, 10): - name = "cache{0}".format(i) - value = _get_field(False, output, None, None, name) - if value: - value = [entry.split('=') for entry in value.split(' ')] - value = dict(value) - if 'level' in value and value['level'] == '3' and 'size' in value: - cache_size = value['size'] - break - except Exception: - pass - - # Convert from MHz string to Hz - hz_actual = _get_field(False, output, None, '', 'cpu MHz', 'cpu speed', 'clock', 'cpu MHz dynamic', 'cpu MHz static') - hz_actual = hz_actual.lower().rstrip('mhz').strip() - hz_actual = _to_decimal_string(hz_actual) - - # Convert from GHz/MHz string to Hz - hz_advertised, scale = (None, 0) - try: - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - except Exception: - pass - - info = { - 'hardware_raw' : hardware, - # MSinn Additions begin - 'revision_raw' : revision, - 'serial_raw' : serial, - # MSinn Additions end - 'brand_raw' : processor_brand, - - 'l3_cache_size' : _to_friendly_bytes(cache_size), - 'flags' : flags, - 'vendor_id_raw' : vendor_id, - 'stepping' : stepping, - 'model' : model, - 'family' : family, - } - - # Make the Hz the same for actual and advertised if missing any - if not hz_advertised or hz_advertised == '0.0': - hz_advertised = hz_actual - scale = 6 - elif not hz_actual or hz_actual == '0.0': - hz_actual = hz_advertised - - # Add the Hz if there is one - if _hz_short_to_full(hz_advertised, scale) > (0, 0): - info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) - info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) - if _hz_short_to_full(hz_actual, scale) > (0, 0): - info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, 6) - info['hz_actual'] = _hz_short_to_full(hz_actual, 6) - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from /proc/cpuinfo. + Returns {} if /proc/cpuinfo is not found. + ''' + + g_trace.header('Tying to get info from /proc/cpuinfo ...') + + try: + # Just return {} if there is no cpuinfo + if not DataSource.has_proc_cpuinfo(): + g_trace.fail('Failed to find /proc/cpuinfo. Skipping ...') + return {} + + returncode, output = DataSource.cat_proc_cpuinfo() + if returncode != 0: + g_trace.fail('Failed to run cat /proc/cpuinfo. Skipping ...') + return {} + + # Various fields + vendor_id = _get_field(False, output, None, '', 'vendor_id', 'vendor id', 'vendor') + processor_brand = _get_field(True, output, None, None, 'model name', 'cpu', 'processor', 'uarch') + cache_size = _get_field(False, output, None, '', 'cache size') + stepping = _get_field(False, output, int, -1, 'stepping') + model = _get_field(False, output, int, -1, 'model') + family = _get_field(False, output, int, -1, 'cpu family') + hardware = _get_field(False, output, None, '', 'Hardware') + + # Flags + flags = _get_field(False, output, None, None, 'flags', 'Features', 'ASEs implemented') + if flags: + flags = flags.split() + flags.sort() + + # Check for other cache format + if not cache_size: + try: + for i in range(0, 10): + name = "cache{0}".format(i) + value = _get_field(False, output, None, None, name) + if value: + value = [entry.split('=') for entry in value.split(' ')] + value = dict(value) + if 'level' in value and value['level'] == '3' and 'size' in value: + cache_size = value['size'] + break + except Exception: + pass + + # Convert from MHz string to Hz + hz_actual = _get_field(False, output, None, '', 'cpu MHz', 'cpu speed', 'clock', 'cpu MHz dynamic', 'cpu MHz static') + hz_actual = hz_actual.lower().rstrip('mhz').strip() + hz_actual = _to_decimal_string(hz_actual) + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = (None, 0) + try: + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + except Exception: + pass + + info = { + 'hardware_raw' : hardware, + 'brand_raw' : processor_brand, + + 'l3_cache_size' : _friendly_bytes_to_int(cache_size), + 'flags' : flags, + 'vendor_id_raw' : vendor_id, + 'stepping' : stepping, + 'model' : model, + 'family' : family, + } + + # Make the Hz the same for actual and advertised if missing any + if not hz_advertised or hz_advertised == '0.0': + hz_advertised = hz_actual + scale = 6 + elif not hz_actual or hz_actual == '0.0': + hz_actual = hz_advertised + + # Add the Hz if there is one + if _hz_short_to_full(hz_advertised, scale) > (0, 0): + info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) + info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) + if _hz_short_to_full(hz_actual, scale) > (0, 0): + info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, 6) + info['hz_actual'] = _hz_short_to_full(hz_actual, 6) + + info = _filter_dict_keys_with_empty_values(info, {'stepping':0, 'model':0, 'family':0}) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_cpufreq_info(): - ''' - Returns the CPU info gathered from cpufreq-info. - Returns {} if cpufreq-info is not found. - ''' - try: - hz_brand, scale = '0.0', 0 - - if not DataSource.has_cpufreq_info(): - return {} - - returncode, output = DataSource.cpufreq_info() - if returncode != 0: - return {} - - hz_brand = output.split('current CPU frequency is')[1].split('\n')[0] - i = hz_brand.find('Hz') - assert(i != -1) - hz_brand = hz_brand[0 : i+2].strip().lower() - - if hz_brand.endswith('mhz'): - scale = 6 - elif hz_brand.endswith('ghz'): - scale = 9 - hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip() - hz_brand = _to_decimal_string(hz_brand) - - info = { - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_brand, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_brand, scale), - 'hz_advertised' : _hz_short_to_full(hz_brand, scale), - 'hz_actual' : _hz_short_to_full(hz_brand, scale), - } - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from cpufreq-info. + Returns {} if cpufreq-info is not found. + ''' + + g_trace.header('Tying to get info from cpufreq-info ...') + + try: + hz_brand, scale = '0.0', 0 + + if not DataSource.has_cpufreq_info(): + g_trace.fail('Failed to find cpufreq-info. Skipping ...') + return {} + + returncode, output = DataSource.cpufreq_info() + if returncode != 0: + g_trace.fail('Failed to run cpufreq-info. Skipping ...') + return {} + + hz_brand = output.split('current CPU frequency is')[1].split('\n')[0] + i = hz_brand.find('Hz') + assert(i != -1) + hz_brand = hz_brand[0 : i+2].strip().lower() + + if hz_brand.endswith('mhz'): + scale = 6 + elif hz_brand.endswith('ghz'): + scale = 9 + hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip() + hz_brand = _to_decimal_string(hz_brand) + + info = { + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_brand, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_brand, scale), + 'hz_advertised' : _hz_short_to_full(hz_brand, scale), + 'hz_actual' : _hz_short_to_full(hz_brand, scale), + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_lscpu(): - ''' - Returns the CPU info gathered from lscpu. - Returns {} if lscpu is not found. - ''' - try: - if not DataSource.has_lscpu(): - return {} - - returncode, output = DataSource.lscpu() - if returncode != 0: - return {} - - info = {} - - new_hz = _get_field(False, output, None, None, 'CPU max MHz', 'CPU MHz') - if new_hz: - new_hz = _to_decimal_string(new_hz) - scale = 6 - info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) - info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) - info['hz_advertised'] = _hz_short_to_full(new_hz, scale) - info['hz_actual'] = _hz_short_to_full(new_hz, scale) - - new_hz = _get_field(False, output, None, None, 'CPU dynamic MHz', 'CPU static MHz') - if new_hz: - new_hz = _to_decimal_string(new_hz) - scale = 6 - info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) - info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) - info['hz_advertised'] = _hz_short_to_full(new_hz, scale) - info['hz_actual'] = _hz_short_to_full(new_hz, scale) - - vendor_id = _get_field(False, output, None, None, 'Vendor ID') - if vendor_id: - info['vendor_id_raw'] = vendor_id - - brand = _get_field(False, output, None, None, 'Model name') - if brand: - info['brand_raw'] = brand - - family = _get_field(False, output, None, None, 'CPU family') - if family and family.isdigit(): - info['family'] = int(family) - - stepping = _get_field(False, output, None, None, 'Stepping') - if stepping and stepping.isdigit(): - info['stepping'] = int(stepping) - - model = _get_field(False, output, None, None, 'Model') - if model and model.isdigit(): - info['model'] = int(model) - - l1_data_cache_size = _get_field(False, output, None, None, 'L1d cache') - if l1_data_cache_size: - info['l1_data_cache_size'] = _to_friendly_bytes(l1_data_cache_size) - - l1_instruction_cache_size = _get_field(False, output, None, None, 'L1i cache') - if l1_instruction_cache_size: - info['l1_instruction_cache_size'] = _to_friendly_bytes(l1_instruction_cache_size) - - l2_cache_size = _get_field(False, output, None, None, 'L2 cache', 'L2d cache') - if l2_cache_size: - info['l2_cache_size'] = _to_friendly_bytes(l2_cache_size) - - l3_cache_size = _get_field(False, output, None, None, 'L3 cache') - if l3_cache_size: - info['l3_cache_size'] = _to_friendly_bytes(l3_cache_size) - - # Flags - flags = _get_field(False, output, None, None, 'flags', 'Features') - if flags: - flags = flags.split() - flags.sort() - info['flags'] = flags - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from lscpu. + Returns {} if lscpu is not found. + ''' + + g_trace.header('Tying to get info from lscpu ...') + + try: + if not DataSource.has_lscpu(): + g_trace.fail('Failed to find lscpu. Skipping ...') + return {} + + returncode, output = DataSource.lscpu() + if returncode != 0: + g_trace.fail('Failed to run lscpu. Skipping ...') + return {} + + info = {} + + new_hz = _get_field(False, output, None, None, 'CPU max MHz', 'CPU MHz') + if new_hz: + new_hz = _to_decimal_string(new_hz) + scale = 6 + info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_advertised'] = _hz_short_to_full(new_hz, scale) + info['hz_actual'] = _hz_short_to_full(new_hz, scale) + + new_hz = _get_field(False, output, None, None, 'CPU dynamic MHz', 'CPU static MHz') + if new_hz: + new_hz = _to_decimal_string(new_hz) + scale = 6 + info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_advertised'] = _hz_short_to_full(new_hz, scale) + info['hz_actual'] = _hz_short_to_full(new_hz, scale) + + vendor_id = _get_field(False, output, None, None, 'Vendor ID') + if vendor_id: + info['vendor_id_raw'] = vendor_id + + brand = _get_field(False, output, None, None, 'Model name') + if brand: + info['brand_raw'] = brand + else: + brand = _get_field(False, output, None, None, 'Model') + if brand and not brand.isdigit(): + info['brand_raw'] = brand + + family = _get_field(False, output, None, None, 'CPU family') + if family and family.isdigit(): + info['family'] = int(family) + + stepping = _get_field(False, output, None, None, 'Stepping') + if stepping and stepping.isdigit(): + info['stepping'] = int(stepping) + + model = _get_field(False, output, None, None, 'Model') + if model and model.isdigit(): + info['model'] = int(model) + + l1_data_cache_size = _get_field(False, output, None, None, 'L1d cache') + if l1_data_cache_size: + l1_data_cache_size = l1_data_cache_size.split('(')[0].strip() + info['l1_data_cache_size'] = _friendly_bytes_to_int(l1_data_cache_size) + + l1_instruction_cache_size = _get_field(False, output, None, None, 'L1i cache') + if l1_instruction_cache_size: + l1_instruction_cache_size = l1_instruction_cache_size.split('(')[0].strip() + info['l1_instruction_cache_size'] = _friendly_bytes_to_int(l1_instruction_cache_size) + + l2_cache_size = _get_field(False, output, None, None, 'L2 cache', 'L2d cache') + if l2_cache_size: + l2_cache_size = l2_cache_size.split('(')[0].strip() + info['l2_cache_size'] = _friendly_bytes_to_int(l2_cache_size) + + l3_cache_size = _get_field(False, output, None, None, 'L3 cache') + if l3_cache_size: + l3_cache_size = l3_cache_size.split('(')[0].strip() + info['l3_cache_size'] = _friendly_bytes_to_int(l3_cache_size) + + # Flags + flags = _get_field(False, output, None, None, 'flags', 'Features', 'ASEs implemented') + if flags: + flags = flags.split() + flags.sort() + info['flags'] = flags + + info = _filter_dict_keys_with_empty_values(info, {'stepping':0, 'model':0, 'family':0}) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_dmesg(): - ''' - Returns the CPU info gathered from dmesg. - Returns {} if dmesg is not found or does not have the desired info. - ''' + ''' + Returns the CPU info gathered from dmesg. + Returns {} if dmesg is not found or does not have the desired info. + ''' + + g_trace.header('Tying to get info from the dmesg ...') - # Just return {} if this arch has an unreliable dmesg log - arch, bits = _parse_arch(DataSource.arch_string_raw) - if arch in ['S390X']: - return {} + # Just return {} if this arch has an unreliable dmesg log + arch, bits = _parse_arch(DataSource.arch_string_raw) + if arch in ['S390X']: + g_trace.fail('Running on S390X. Skipping ...') + return {} - # Just return {} if there is no dmesg - if not DataSource.has_dmesg(): - return {} + # Just return {} if there is no dmesg + if not DataSource.has_dmesg(): + g_trace.fail('Failed to find dmesg. Skipping ...') + return {} - # If dmesg fails return {} - returncode, output = DataSource.dmesg_a() - if output == None or returncode != 0: - return {} + # If dmesg fails return {} + returncode, output = DataSource.dmesg_a() + if output is None or returncode != 0: + g_trace.fail('Failed to run \"dmesg -a\". Skipping ...') + return {} - return _parse_dmesg_output(output) + info = _parse_dmesg_output(output) + g_trace.success() + return info # https://openpowerfoundation.org/wp-content/uploads/2016/05/LoPAPR_DRAFT_v11_24March2016_cmt1.pdf # page 767 def _get_cpu_info_from_ibm_pa_features(): - ''' - Returns the CPU info gathered from lsprop /proc/device-tree/cpus/*/ibm,pa-features - Returns {} if lsprop is not found or ibm,pa-features does not have the desired info. - ''' - try: - # Just return {} if there is no lsprop - if not DataSource.has_ibm_pa_features(): - return {} - - # If ibm,pa-features fails return {} - returncode, output = DataSource.ibm_pa_features() - if output == None or returncode != 0: - return {} - - # Filter out invalid characters from output - value = output.split("ibm,pa-features")[1].lower() - value = [s for s in value if s in list('0123456789abcfed')] - value = ''.join(value) - - # Get data converted to Uint32 chunks - left = int(value[0 : 8], 16) - right = int(value[8 : 16], 16) - - # Get the CPU flags - flags = { - # Byte 0 - 'mmu' : _is_bit_set(left, 0), - 'fpu' : _is_bit_set(left, 1), - 'slb' : _is_bit_set(left, 2), - 'run' : _is_bit_set(left, 3), - #'reserved' : _is_bit_set(left, 4), - 'dabr' : _is_bit_set(left, 5), - 'ne' : _is_bit_set(left, 6), - 'wtr' : _is_bit_set(left, 7), - - # Byte 1 - 'mcr' : _is_bit_set(left, 8), - 'dsisr' : _is_bit_set(left, 9), - 'lp' : _is_bit_set(left, 10), - 'ri' : _is_bit_set(left, 11), - 'dabrx' : _is_bit_set(left, 12), - 'sprg3' : _is_bit_set(left, 13), - 'rislb' : _is_bit_set(left, 14), - 'pp' : _is_bit_set(left, 15), - - # Byte 2 - 'vpm' : _is_bit_set(left, 16), - 'dss_2.05' : _is_bit_set(left, 17), - #'reserved' : _is_bit_set(left, 18), - 'dar' : _is_bit_set(left, 19), - #'reserved' : _is_bit_set(left, 20), - 'ppr' : _is_bit_set(left, 21), - 'dss_2.02' : _is_bit_set(left, 22), - 'dss_2.06' : _is_bit_set(left, 23), - - # Byte 3 - 'lsd_in_dscr' : _is_bit_set(left, 24), - 'ugr_in_dscr' : _is_bit_set(left, 25), - #'reserved' : _is_bit_set(left, 26), - #'reserved' : _is_bit_set(left, 27), - #'reserved' : _is_bit_set(left, 28), - #'reserved' : _is_bit_set(left, 29), - #'reserved' : _is_bit_set(left, 30), - #'reserved' : _is_bit_set(left, 31), - - # Byte 4 - 'sso_2.06' : _is_bit_set(right, 0), - #'reserved' : _is_bit_set(right, 1), - #'reserved' : _is_bit_set(right, 2), - #'reserved' : _is_bit_set(right, 3), - #'reserved' : _is_bit_set(right, 4), - #'reserved' : _is_bit_set(right, 5), - #'reserved' : _is_bit_set(right, 6), - #'reserved' : _is_bit_set(right, 7), - - # Byte 5 - 'le' : _is_bit_set(right, 8), - 'cfar' : _is_bit_set(right, 9), - 'eb' : _is_bit_set(right, 10), - 'lsq_2.07' : _is_bit_set(right, 11), - #'reserved' : _is_bit_set(right, 12), - #'reserved' : _is_bit_set(right, 13), - #'reserved' : _is_bit_set(right, 14), - #'reserved' : _is_bit_set(right, 15), - - # Byte 6 - 'dss_2.07' : _is_bit_set(right, 16), - #'reserved' : _is_bit_set(right, 17), - #'reserved' : _is_bit_set(right, 18), - #'reserved' : _is_bit_set(right, 19), - #'reserved' : _is_bit_set(right, 20), - #'reserved' : _is_bit_set(right, 21), - #'reserved' : _is_bit_set(right, 22), - #'reserved' : _is_bit_set(right, 23), - - # Byte 7 - #'reserved' : _is_bit_set(right, 24), - #'reserved' : _is_bit_set(right, 25), - #'reserved' : _is_bit_set(right, 26), - #'reserved' : _is_bit_set(right, 27), - #'reserved' : _is_bit_set(right, 28), - #'reserved' : _is_bit_set(right, 29), - #'reserved' : _is_bit_set(right, 30), - #'reserved' : _is_bit_set(right, 31), - } - - # Get a list of only the flags that are true - flags = [k for k, v in flags.items() if v] - flags.sort() - - info = { - 'flags' : flags - } - info = {k: v for k, v in info.items() if v} - - return info - except: - return {} + ''' + Returns the CPU info gathered from lsprop /proc/device-tree/cpus/*/ibm,pa-features + Returns {} if lsprop is not found or ibm,pa-features does not have the desired info. + ''' + + g_trace.header('Tying to get info from lsprop ...') + + try: + # Just return {} if there is no lsprop + if not DataSource.has_ibm_pa_features(): + g_trace.fail('Failed to find lsprop. Skipping ...') + return {} + + # If ibm,pa-features fails return {} + returncode, output = DataSource.ibm_pa_features() + if output is None or returncode != 0: + g_trace.fail('Failed to glob /proc/device-tree/cpus/*/ibm,pa-features. Skipping ...') + return {} + + # Filter out invalid characters from output + value = output.split("ibm,pa-features")[1].lower() + value = [s for s in value if s in list('0123456789abcfed')] + value = ''.join(value) + + # Get data converted to Uint32 chunks + left = int(value[0 : 8], 16) + right = int(value[8 : 16], 16) + + # Get the CPU flags + flags = { + # Byte 0 + 'mmu' : _is_bit_set(left, 0), + 'fpu' : _is_bit_set(left, 1), + 'slb' : _is_bit_set(left, 2), + 'run' : _is_bit_set(left, 3), + #'reserved' : _is_bit_set(left, 4), + 'dabr' : _is_bit_set(left, 5), + 'ne' : _is_bit_set(left, 6), + 'wtr' : _is_bit_set(left, 7), + + # Byte 1 + 'mcr' : _is_bit_set(left, 8), + 'dsisr' : _is_bit_set(left, 9), + 'lp' : _is_bit_set(left, 10), + 'ri' : _is_bit_set(left, 11), + 'dabrx' : _is_bit_set(left, 12), + 'sprg3' : _is_bit_set(left, 13), + 'rislb' : _is_bit_set(left, 14), + 'pp' : _is_bit_set(left, 15), + + # Byte 2 + 'vpm' : _is_bit_set(left, 16), + 'dss_2.05' : _is_bit_set(left, 17), + #'reserved' : _is_bit_set(left, 18), + 'dar' : _is_bit_set(left, 19), + #'reserved' : _is_bit_set(left, 20), + 'ppr' : _is_bit_set(left, 21), + 'dss_2.02' : _is_bit_set(left, 22), + 'dss_2.06' : _is_bit_set(left, 23), + + # Byte 3 + 'lsd_in_dscr' : _is_bit_set(left, 24), + 'ugr_in_dscr' : _is_bit_set(left, 25), + #'reserved' : _is_bit_set(left, 26), + #'reserved' : _is_bit_set(left, 27), + #'reserved' : _is_bit_set(left, 28), + #'reserved' : _is_bit_set(left, 29), + #'reserved' : _is_bit_set(left, 30), + #'reserved' : _is_bit_set(left, 31), + + # Byte 4 + 'sso_2.06' : _is_bit_set(right, 0), + #'reserved' : _is_bit_set(right, 1), + #'reserved' : _is_bit_set(right, 2), + #'reserved' : _is_bit_set(right, 3), + #'reserved' : _is_bit_set(right, 4), + #'reserved' : _is_bit_set(right, 5), + #'reserved' : _is_bit_set(right, 6), + #'reserved' : _is_bit_set(right, 7), + + # Byte 5 + 'le' : _is_bit_set(right, 8), + 'cfar' : _is_bit_set(right, 9), + 'eb' : _is_bit_set(right, 10), + 'lsq_2.07' : _is_bit_set(right, 11), + #'reserved' : _is_bit_set(right, 12), + #'reserved' : _is_bit_set(right, 13), + #'reserved' : _is_bit_set(right, 14), + #'reserved' : _is_bit_set(right, 15), + + # Byte 6 + 'dss_2.07' : _is_bit_set(right, 16), + #'reserved' : _is_bit_set(right, 17), + #'reserved' : _is_bit_set(right, 18), + #'reserved' : _is_bit_set(right, 19), + #'reserved' : _is_bit_set(right, 20), + #'reserved' : _is_bit_set(right, 21), + #'reserved' : _is_bit_set(right, 22), + #'reserved' : _is_bit_set(right, 23), + + # Byte 7 + #'reserved' : _is_bit_set(right, 24), + #'reserved' : _is_bit_set(right, 25), + #'reserved' : _is_bit_set(right, 26), + #'reserved' : _is_bit_set(right, 27), + #'reserved' : _is_bit_set(right, 28), + #'reserved' : _is_bit_set(right, 29), + #'reserved' : _is_bit_set(right, 30), + #'reserved' : _is_bit_set(right, 31), + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + flags.sort() + + info = { + 'flags' : flags + } + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + return {} def _get_cpu_info_from_cat_var_run_dmesg_boot(): - ''' - Returns the CPU info gathered from /var/run/dmesg.boot. - Returns {} if dmesg is not found or does not have the desired info. - ''' - # Just return {} if there is no /var/run/dmesg.boot - if not DataSource.has_var_run_dmesg_boot(): - return {} + ''' + Returns the CPU info gathered from /var/run/dmesg.boot. + Returns {} if dmesg is not found or does not have the desired info. + ''' + + g_trace.header('Tying to get info from the /var/run/dmesg.boot log ...') + + # Just return {} if there is no /var/run/dmesg.boot + if not DataSource.has_var_run_dmesg_boot(): + g_trace.fail('Failed to find /var/run/dmesg.boot file. Skipping ...') + return {} - # If dmesg.boot fails return {} - returncode, output = DataSource.cat_var_run_dmesg_boot() - if output == None or returncode != 0: - return {} + # If dmesg.boot fails return {} + returncode, output = DataSource.cat_var_run_dmesg_boot() + if output is None or returncode != 0: + g_trace.fail('Failed to run \"cat /var/run/dmesg.boot\". Skipping ...') + return {} - return _parse_dmesg_output(output) + info = _parse_dmesg_output(output) + g_trace.success() + return info def _get_cpu_info_from_sysctl(): - ''' - Returns the CPU info gathered from sysctl. - Returns {} if sysctl is not found. - ''' - try: - # Just return {} if there is no sysctl - if not DataSource.has_sysctl(): - return {} - - # If sysctl fails return {} - returncode, output = DataSource.sysctl_machdep_cpu_hw_cpufrequency() - if output == None or returncode != 0: - return {} - - # Various fields - vendor_id = _get_field(False, output, None, None, 'machdep.cpu.vendor') - processor_brand = _get_field(True, output, None, None, 'machdep.cpu.brand_string') - cache_size = _get_field(False, output, None, None, 'machdep.cpu.cache.size') - stepping = _get_field(False, output, int, 0, 'machdep.cpu.stepping') - model = _get_field(False, output, int, 0, 'machdep.cpu.model') - family = _get_field(False, output, int, 0, 'machdep.cpu.family') - - # Flags - flags = _get_field(False, output, None, '', 'machdep.cpu.features').lower().split() - flags.extend(_get_field(False, output, None, '', 'machdep.cpu.leaf7_features').lower().split()) - flags.extend(_get_field(False, output, None, '', 'machdep.cpu.extfeatures').lower().split()) - flags.sort() - - # Convert from GHz/MHz string to Hz - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - hz_actual = _get_field(False, output, None, None, 'hw.cpufrequency') - hz_actual = _to_decimal_string(hz_actual) - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, 0), - - 'l2_cache_size' : _to_friendly_bytes(cache_size), - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'flags' : flags - } - - info = {k: v for k, v in info.items() if v} - return info - except: - return {} + ''' + Returns the CPU info gathered from sysctl. + Returns {} if sysctl is not found. + ''' + + g_trace.header('Tying to get info from sysctl ...') + + try: + # Just return {} if there is no sysctl + if not DataSource.has_sysctl(): + g_trace.fail('Failed to find sysctl. Skipping ...') + return {} + + # If sysctl fails return {} + returncode, output = DataSource.sysctl_machdep_cpu_hw_cpufrequency() + if output is None or returncode != 0: + g_trace.fail('Failed to run \"sysctl machdep.cpu hw.cpufrequency\". Skipping ...') + return {} + + # Various fields + vendor_id = _get_field(False, output, None, None, 'machdep.cpu.vendor') + processor_brand = _get_field(True, output, None, None, 'machdep.cpu.brand_string') + cache_size = _get_field(False, output, int, 0, 'machdep.cpu.cache.size') + stepping = _get_field(False, output, int, 0, 'machdep.cpu.stepping') + model = _get_field(False, output, int, 0, 'machdep.cpu.model') + family = _get_field(False, output, int, 0, 'machdep.cpu.family') + + # Flags + flags = _get_field(False, output, None, '', 'machdep.cpu.features').lower().split() + flags.extend(_get_field(False, output, None, '', 'machdep.cpu.leaf7_features').lower().split()) + flags.extend(_get_field(False, output, None, '', 'machdep.cpu.extfeatures').lower().split()) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + hz_actual = _get_field(False, output, None, None, 'hw.cpufrequency') + hz_actual = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'l2_cache_size' : int(cache_size) * 1024, + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + return {} def _get_cpu_info_from_sysinfo(): - ''' - Returns the CPU info gathered from sysinfo. - Returns {} if sysinfo is not found. - ''' - info = _get_cpu_info_from_sysinfo_v1() - info.update(_get_cpu_info_from_sysinfo_v2()) - return info + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + + info = _get_cpu_info_from_sysinfo_v1() + info.update(_get_cpu_info_from_sysinfo_v2()) + return info def _get_cpu_info_from_sysinfo_v1(): - ''' - Returns the CPU info gathered from sysinfo. - Returns {} if sysinfo is not found. - ''' - try: - # Just return {} if there is no sysinfo - if not DataSource.has_sysinfo(): - return {} - - # If sysinfo fails return {} - returncode, output = DataSource.sysinfo_cpu() - if output == None or returncode != 0: - return {} - - # Various fields - vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') - processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() - cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') - stepping = int(output.split(', stepping ')[1].split(',')[0].strip()) - model = int(output.split(', model ')[1].split(',')[0].strip()) - family = int(output.split(', family ')[1].split(',')[0].strip()) - - # Flags - flags = [] - for line in output.split('\n'): - if line.startswith('\t\t'): - for flag in line.strip().lower().split(): - flags.append(flag) - flags.sort() - - # Convert from GHz/MHz string to Hz - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - hz_actual = hz_advertised - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, scale), - - 'l2_cache_size' : _to_friendly_bytes(cache_size), - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'flags' : flags - } - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + + g_trace.header('Tying to get info from sysinfo version 1 ...') + + try: + # Just return {} if there is no sysinfo + if not DataSource.has_sysinfo(): + g_trace.fail('Failed to find sysinfo. Skipping ...') + return {} + + # If sysinfo fails return {} + returncode, output = DataSource.sysinfo_cpu() + if output is None or returncode != 0: + g_trace.fail('Failed to run \"sysinfo -cpu\". Skipping ...') + return {} + + # Various fields + vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') + processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() + cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') + stepping = int(output.split(', stepping ')[1].split(',')[0].strip()) + model = int(output.split(', model ')[1].split(',')[0].strip()) + family = int(output.split(', family ')[1].split(',')[0].strip()) + + # Flags + flags = [] + for line in output.split('\n'): + if line.startswith('\t\t'): + for flag in line.strip().lower().split(): + flags.append(flag) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + hz_actual = hz_advertised + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, scale), + + 'l2_cache_size' : _to_friendly_bytes(cache_size), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_sysinfo_v2(): - ''' - Returns the CPU info gathered from sysinfo. - Returns {} if sysinfo is not found. - ''' - try: - # Just return {} if there is no sysinfo - if not DataSource.has_sysinfo(): - return {} - - # If sysinfo fails return {} - returncode, output = DataSource.sysinfo_cpu() - if output == None or returncode != 0: - return {} - - # Various fields - vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') - processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() - cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') - signature = output.split('Signature:')[1].split('\n')[0].strip() - # - stepping = int(signature.split('stepping ')[1].split(',')[0].strip()) - model = int(signature.split('model ')[1].split(',')[0].strip()) - family = int(signature.split('family ')[1].split(',')[0].strip()) - - # Flags - def get_subsection_flags(output): - retval = [] - for line in output.split('\n')[1:]: - if not line.startswith(' ') and not line.startswith(' '): break - for entry in line.strip().lower().split(' '): - retval.append(entry) - return retval - - flags = get_subsection_flags(output.split('Features: ')[1]) + \ - get_subsection_flags(output.split('Extended Features (0x00000001): ')[1]) + \ - get_subsection_flags(output.split('Extended Features (0x80000001): ')[1]) - flags.sort() - - # Convert from GHz/MHz string to Hz - lines = [n for n in output.split('\n') if n] - raw_hz = lines[0].split('running at ')[1].strip().lower() - hz_advertised = raw_hz.rstrip('mhz').rstrip('ghz').strip() - hz_advertised = _to_decimal_string(hz_advertised) - hz_actual = hz_advertised - - scale = 0 - if raw_hz.endswith('mhz'): - scale = 6 - elif raw_hz.endswith('ghz'): - scale = 9 - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, scale), - - 'l2_cache_size' : _to_friendly_bytes(cache_size), - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'flags' : flags - } - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + + g_trace.header('Tying to get info from sysinfo version 2 ...') + + try: + # Just return {} if there is no sysinfo + if not DataSource.has_sysinfo(): + g_trace.fail('Failed to find sysinfo. Skipping ...') + return {} + + # If sysinfo fails return {} + returncode, output = DataSource.sysinfo_cpu() + if output is None or returncode != 0: + g_trace.fail('Failed to run \"sysinfo -cpu\". Skipping ...') + return {} + + # Various fields + vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') + processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() + cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') + signature = output.split('Signature:')[1].split('\n')[0].strip() + # + stepping = int(signature.split('stepping ')[1].split(',')[0].strip()) + model = int(signature.split('model ')[1].split(',')[0].strip()) + family = int(signature.split('family ')[1].split(',')[0].strip()) + + # Flags + def get_subsection_flags(output): + retval = [] + for line in output.split('\n')[1:]: + if not line.startswith(' ') and not line.startswith(' '): break + for entry in line.strip().lower().split(' '): + retval.append(entry) + return retval + + flags = get_subsection_flags(output.split('Features: ')[1]) + \ + get_subsection_flags(output.split('Extended Features (0x00000001): ')[1]) + \ + get_subsection_flags(output.split('Extended Features (0x80000001): ')[1]) + flags.sort() + + # Convert from GHz/MHz string to Hz + lines = [n for n in output.split('\n') if n] + raw_hz = lines[0].split('running at ')[1].strip().lower() + hz_advertised = raw_hz.rstrip('mhz').rstrip('ghz').strip() + hz_advertised = _to_decimal_string(hz_advertised) + hz_actual = hz_advertised + + scale = 0 + if raw_hz.endswith('mhz'): + scale = 6 + elif raw_hz.endswith('ghz'): + scale = 9 + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, scale), + + 'l2_cache_size' : _to_friendly_bytes(cache_size), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_wmic(): - ''' - Returns the CPU info gathered from WMI. - Returns {} if not on Windows, or wmic is not installed. - ''' - - try: - # Just return {} if not Windows or there is no wmic - if not DataSource.is_windows or not DataSource.has_wmic(): - return {} - - returncode, output = DataSource.wmic_cpu() - if output == None or returncode != 0: - return {} - - # Break the list into key values pairs - value = output.split("\n") - value = [s.rstrip().split('=') for s in value if '=' in s] - value = {k: v for k, v in value if v} - - # Get the advertised MHz - processor_brand = value.get('Name') - hz_advertised, scale_advertised = _parse_cpu_brand_string(processor_brand) - - # Get the actual MHz - hz_actual = value.get('CurrentClockSpeed') - scale_actual = 6 - if hz_actual: - hz_actual = _to_decimal_string(hz_actual) - - # Get cache sizes - l2_cache_size = value.get('L2CacheSize') - if l2_cache_size: - l2_cache_size = l2_cache_size + ' KB' - - l3_cache_size = value.get('L3CacheSize') - if l3_cache_size: - l3_cache_size = l3_cache_size + ' KB' - - # Get family, model, and stepping - family, model, stepping = '', '', '' - description = value.get('Description') or value.get('Caption') - entries = description.split(' ') - - if 'Family' in entries and entries.index('Family') < len(entries)-1: - i = entries.index('Family') - family = int(entries[i + 1]) - - if 'Model' in entries and entries.index('Model') < len(entries)-1: - i = entries.index('Model') - model = int(entries[i + 1]) - - if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: - i = entries.index('Stepping') - stepping = int(entries[i + 1]) - - info = { - 'vendor_id_raw' : value.get('Manufacturer'), - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale_advertised), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale_actual), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale_advertised), - 'hz_actual' : _hz_short_to_full(hz_actual, scale_actual), - - 'l2_cache_size' : l2_cache_size, - 'l3_cache_size' : l3_cache_size, - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - } - - info = {k: v for k, v in info.items() if v} - return info - except: - #raise # NOTE: To have this throw on error, uncomment this line - return {} + ''' + Returns the CPU info gathered from WMI. + Returns {} if not on Windows, or wmic is not installed. + ''' + g_trace.header('Tying to get info from wmic ...') + + try: + # Just return {} if not Windows or there is no wmic + if not DataSource.is_windows or not DataSource.has_wmic(): + g_trace.fail('Failed to find WMIC, or not on Windows. Skipping ...') + return {} + + returncode, output = DataSource.wmic_cpu() + if output is None or returncode != 0: + g_trace.fail('Failed to run wmic. Skipping ...') + return {} + + # Break the list into key values pairs + value = output.split("\n") + value = [s.rstrip().split('=') for s in value if '=' in s] + value = {k: v for k, v in value if v} + + # Get the advertised MHz + processor_brand = value.get('Name') + hz_advertised, scale_advertised = _parse_cpu_brand_string(processor_brand) + + # Get the actual MHz + hz_actual = value.get('CurrentClockSpeed') + scale_actual = 6 + if hz_actual: + hz_actual = _to_decimal_string(hz_actual) + + # Get cache sizes + l2_cache_size = value.get('L2CacheSize') # NOTE: L2CacheSize is in kilobytes + if l2_cache_size: + l2_cache_size = int(l2_cache_size) * 1024 + + l3_cache_size = value.get('L3CacheSize') # NOTE: L3CacheSize is in kilobytes + if l3_cache_size: + l3_cache_size = int(l3_cache_size) * 1024 + + # Get family, model, and stepping + family, model, stepping = '', '', '' + description = value.get('Description') or value.get('Caption') + entries = description.split(' ') + + if 'Family' in entries and entries.index('Family') < len(entries)-1: + i = entries.index('Family') + family = int(entries[i + 1]) + + if 'Model' in entries and entries.index('Model') < len(entries)-1: + i = entries.index('Model') + model = int(entries[i + 1]) + + if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: + i = entries.index('Stepping') + stepping = int(entries[i + 1]) + + info = { + 'vendor_id_raw' : value.get('Manufacturer'), + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale_advertised), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale_actual), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale_advertised), + 'hz_actual' : _hz_short_to_full(hz_actual, scale_actual), + + 'l2_cache_size' : l2_cache_size, + 'l3_cache_size' : l3_cache_size, + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + #raise # NOTE: To have this throw on error, uncomment this line + return {} def _get_cpu_info_from_registry(): - ''' - FIXME: Is missing many of the newer CPU flags like sse3 - Returns the CPU info gathered from the Windows Registry. - Returns {} if not on Windows. - ''' - try: - # Just return {} if not on Windows - if not DataSource.is_windows: - return {} - - # Get the CPU name - processor_brand = DataSource.winreg_processor_brand().strip() - - # Get the CPU vendor id - vendor_id = DataSource.winreg_vendor_id_raw() - - # Get the CPU arch and bits - arch_string_raw = DataSource.winreg_arch_string_raw() - arch, bits = _parse_arch(arch_string_raw) - - # Get the actual CPU Hz - hz_actual = DataSource.winreg_hz_actual() - hz_actual = _to_decimal_string(hz_actual) - - # Get the advertised CPU Hz - hz_advertised, scale = _parse_cpu_brand_string(processor_brand) - - # If advertised hz not found, use the actual hz - if hz_advertised == '0.0': - scale = 6 - hz_advertised = _to_decimal_string(hz_actual) - - # Get the CPU features - feature_bits = DataSource.winreg_feature_bits() - - def is_set(bit): - mask = 0x80000000 >> bit - retval = mask & feature_bits > 0 - return retval - - # http://en.wikipedia.org/wiki/CPUID - # http://unix.stackexchange.com/questions/43539/what-do-the-flags-in-proc-cpuinfo-mean - # http://www.lohninger.com/helpcsuite/public_constants_cpuid.htm - flags = { - 'fpu' : is_set(0), # Floating Point Unit - 'vme' : is_set(1), # V86 Mode Extensions - 'de' : is_set(2), # Debug Extensions - I/O breakpoints supported - 'pse' : is_set(3), # Page Size Extensions (4 MB pages supported) - 'tsc' : is_set(4), # Time Stamp Counter and RDTSC instruction are available - 'msr' : is_set(5), # Model Specific Registers - 'pae' : is_set(6), # Physical Address Extensions (36 bit address, 2MB pages) - 'mce' : is_set(7), # Machine Check Exception supported - 'cx8' : is_set(8), # Compare Exchange Eight Byte instruction available - 'apic' : is_set(9), # Local APIC present (multiprocessor operation support) - 'sepamd' : is_set(10), # Fast system calls (AMD only) - 'sep' : is_set(11), # Fast system calls - 'mtrr' : is_set(12), # Memory Type Range Registers - 'pge' : is_set(13), # Page Global Enable - 'mca' : is_set(14), # Machine Check Architecture - 'cmov' : is_set(15), # Conditional MOVe instructions - 'pat' : is_set(16), # Page Attribute Table - 'pse36' : is_set(17), # 36 bit Page Size Extensions - 'serial' : is_set(18), # Processor Serial Number - 'clflush' : is_set(19), # Cache Flush - #'reserved1' : is_set(20), # reserved - 'dts' : is_set(21), # Debug Trace Store - 'acpi' : is_set(22), # ACPI support - 'mmx' : is_set(23), # MultiMedia Extensions - 'fxsr' : is_set(24), # FXSAVE and FXRSTOR instructions - 'sse' : is_set(25), # SSE instructions - 'sse2' : is_set(26), # SSE2 (WNI) instructions - 'ss' : is_set(27), # self snoop - #'reserved2' : is_set(28), # reserved - 'tm' : is_set(29), # Automatic clock control - 'ia64' : is_set(30), # IA64 instructions - '3dnow' : is_set(31) # 3DNow! instructions available - } - - # Get a list of only the flags that are true - flags = [k for k, v in flags.items() if v] - flags.sort() - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 6), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, 6), - - 'flags' : flags - } - - info = {k: v for k, v in info.items() if v} - return info - except: - return {} + ''' + Returns the CPU info gathered from the Windows Registry. + Returns {} if not on Windows. + ''' + + g_trace.header('Tying to get info from Windows registry ...') + + try: + # Just return {} if not on Windows + if not DataSource.is_windows: + g_trace.fail('Not running on Windows. Skipping ...') + return {} + + # Get the CPU name + processor_brand = DataSource.winreg_processor_brand().strip() + + # Get the CPU vendor id + vendor_id = DataSource.winreg_vendor_id_raw() + + # Get the CPU arch and bits + arch_string_raw = DataSource.winreg_arch_string_raw() + arch, bits = _parse_arch(arch_string_raw) + + # Get the actual CPU Hz + hz_actual = DataSource.winreg_hz_actual() + hz_actual = _to_decimal_string(hz_actual) + + # Get the advertised CPU Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + + # If advertised hz not found, use the actual hz + if hz_advertised == '0.0': + scale = 6 + hz_advertised = _to_decimal_string(hz_actual) + + # Get the CPU features + feature_bits = DataSource.winreg_feature_bits() + + def is_set(bit): + mask = 0x80000000 >> bit + retval = mask & feature_bits > 0 + return retval + + # http://en.wikipedia.org/wiki/CPUID + # http://unix.stackexchange.com/questions/43539/what-do-the-flags-in-proc-cpuinfo-mean + # http://www.lohninger.com/helpcsuite/public_constants_cpuid.htm + flags = { + 'fpu' : is_set(0), # Floating Point Unit + 'vme' : is_set(1), # V86 Mode Extensions + 'de' : is_set(2), # Debug Extensions - I/O breakpoints supported + 'pse' : is_set(3), # Page Size Extensions (4 MB pages supported) + 'tsc' : is_set(4), # Time Stamp Counter and RDTSC instruction are available + 'msr' : is_set(5), # Model Specific Registers + 'pae' : is_set(6), # Physical Address Extensions (36 bit address, 2MB pages) + 'mce' : is_set(7), # Machine Check Exception supported + 'cx8' : is_set(8), # Compare Exchange Eight Byte instruction available + 'apic' : is_set(9), # Local APIC present (multiprocessor operation support) + 'sepamd' : is_set(10), # Fast system calls (AMD only) + 'sep' : is_set(11), # Fast system calls + 'mtrr' : is_set(12), # Memory Type Range Registers + 'pge' : is_set(13), # Page Global Enable + 'mca' : is_set(14), # Machine Check Architecture + 'cmov' : is_set(15), # Conditional MOVe instructions + 'pat' : is_set(16), # Page Attribute Table + 'pse36' : is_set(17), # 36 bit Page Size Extensions + 'serial' : is_set(18), # Processor Serial Number + 'clflush' : is_set(19), # Cache Flush + #'reserved1' : is_set(20), # reserved + 'dts' : is_set(21), # Debug Trace Store + 'acpi' : is_set(22), # ACPI support + 'mmx' : is_set(23), # MultiMedia Extensions + 'fxsr' : is_set(24), # FXSAVE and FXRSTOR instructions + 'sse' : is_set(25), # SSE instructions + 'sse2' : is_set(26), # SSE2 (WNI) instructions + 'ss' : is_set(27), # self snoop + #'reserved2' : is_set(28), # reserved + 'tm' : is_set(29), # Automatic clock control + 'ia64' : is_set(30), # IA64 instructions + '3dnow' : is_set(31) # 3DNow! instructions available + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + flags.sort() + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 6), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 6), + + 'flags' : flags + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + return {} def _get_cpu_info_from_kstat(): - ''' - Returns the CPU info gathered from isainfo and kstat. - Returns {} if isainfo or kstat are not found. - ''' - try: - # Just return {} if there is no isainfo or kstat - if not DataSource.has_isainfo() or not DataSource.has_kstat(): - return {} - - # If isainfo fails return {} - returncode, flag_output = DataSource.isainfo_vb() - if flag_output == None or returncode != 0: - return {} - - # If kstat fails return {} - returncode, kstat = DataSource.kstat_m_cpu_info() - if kstat == None or returncode != 0: - return {} - - # Various fields - vendor_id = kstat.split('\tvendor_id ')[1].split('\n')[0].strip() - processor_brand = kstat.split('\tbrand ')[1].split('\n')[0].strip() - stepping = int(kstat.split('\tstepping ')[1].split('\n')[0].strip()) - model = int(kstat.split('\tmodel ')[1].split('\n')[0].strip()) - family = int(kstat.split('\tfamily ')[1].split('\n')[0].strip()) - - # Flags - flags = flag_output.strip().split('\n')[-1].strip().lower().split() - flags.sort() - - # Convert from GHz/MHz string to Hz - scale = 6 - hz_advertised = kstat.split('\tclock_MHz ')[1].split('\n')[0].strip() - hz_advertised = _to_decimal_string(hz_advertised) - - # Convert from GHz/MHz string to Hz - hz_actual = kstat.split('\tcurrent_clock_Hz ')[1].split('\n')[0].strip() - hz_actual = _to_decimal_string(hz_actual) - - info = { - 'vendor_id_raw' : vendor_id, - 'brand_raw' : processor_brand, - - 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), - 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), - 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), - 'hz_actual' : _hz_short_to_full(hz_actual, 0), - - 'stepping' : stepping, - 'model' : model, - 'family' : family, - 'flags' : flags - } - - info = {k: v for k, v in info.items() if v} - return info - except: - return {} + ''' + Returns the CPU info gathered from isainfo and kstat. + Returns {} if isainfo or kstat are not found. + ''' + + g_trace.header('Tying to get info from kstat ...') + + try: + # Just return {} if there is no isainfo or kstat + if not DataSource.has_isainfo() or not DataSource.has_kstat(): + g_trace.fail('Failed to find isinfo or kstat. Skipping ...') + return {} + + # If isainfo fails return {} + returncode, flag_output = DataSource.isainfo_vb() + if flag_output is None or returncode != 0: + g_trace.fail('Failed to run \"isainfo -vb\". Skipping ...') + return {} + + # If kstat fails return {} + returncode, kstat = DataSource.kstat_m_cpu_info() + if kstat is None or returncode != 0: + g_trace.fail('Failed to run \"kstat -m cpu_info\". Skipping ...') + return {} + + # Various fields + vendor_id = kstat.split('\tvendor_id ')[1].split('\n')[0].strip() + processor_brand = kstat.split('\tbrand ')[1].split('\n')[0].strip() + stepping = int(kstat.split('\tstepping ')[1].split('\n')[0].strip()) + model = int(kstat.split('\tmodel ')[1].split('\n')[0].strip()) + family = int(kstat.split('\tfamily ')[1].split('\n')[0].strip()) + + # Flags + flags = flag_output.strip().split('\n')[-1].strip().lower().split() + flags.sort() + + # Convert from GHz/MHz string to Hz + scale = 6 + hz_advertised = kstat.split('\tclock_MHz ')[1].split('\n')[0].strip() + hz_advertised = _to_decimal_string(hz_advertised) + + # Convert from GHz/MHz string to Hz + hz_actual = kstat.split('\tcurrent_clock_Hz ')[1].split('\n')[0].strip() + hz_actual = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + return {} def _get_cpu_info_from_platform_uname(): - try: - uname = DataSource.uname_string_raw.split(',')[0] - - family, model, stepping = (None, None, None) - entries = uname.split(' ') - - if 'Family' in entries and entries.index('Family') < len(entries)-1: - i = entries.index('Family') - family = int(entries[i + 1]) - - if 'Model' in entries and entries.index('Model') < len(entries)-1: - i = entries.index('Model') - model = int(entries[i + 1]) - - if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: - i = entries.index('Stepping') - stepping = int(entries[i + 1]) - - info = { - 'family' : family, - 'model' : model, - 'stepping' : stepping - } - info = {k: v for k, v in info.items() if v} - return info - except: - return {} + + g_trace.header('Tying to get info from platform.uname ...') + + try: + uname = DataSource.uname_string_raw.split(',')[0] + + family, model, stepping = (None, None, None) + entries = uname.split(' ') + + if 'Family' in entries and entries.index('Family') < len(entries)-1: + i = entries.index('Family') + family = int(entries[i + 1]) + + if 'Model' in entries and entries.index('Model') < len(entries)-1: + i = entries.index('Model') + model = int(entries[i + 1]) + + if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: + i = entries.index('Stepping') + stepping = int(entries[i + 1]) + + info = { + 'family' : family, + 'model' : model, + 'stepping' : stepping + } + info = _filter_dict_keys_with_empty_values(info) + g_trace.success() + return info + except Exception as err: + g_trace.fail(err) + return {} def _get_cpu_info_internal(): - ''' - Returns the CPU info by using the best sources of information for your OS. - Returns {} if nothing is found. - ''' + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns {} if nothing is found. + ''' + + g_trace.write('!' * 80) + + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + friendly_maxsize = { 2**31-1: '32 bit', 2**63-1: '64 bit' }.get(sys.maxsize) or 'unknown bits' + friendly_version = "{0}.{1}.{2}.{3}.{4}".format(*sys.version_info) + PYTHON_VERSION = "{0} ({1})".format(friendly_version, friendly_maxsize) - # Get the CPU arch and bits - arch, bits = _parse_arch(DataSource.arch_string_raw) + info = { + 'python_version' : PYTHON_VERSION, + 'cpuinfo_version' : CPUINFO_VERSION, + 'cpuinfo_version_string' : CPUINFO_VERSION_STRING, + 'arch' : arch, + 'bits' : bits, + 'count' : DataSource.cpu_count, + 'arch_string_raw' : DataSource.arch_string_raw, + } - friendly_maxsize = { 2**31-1: '32 bit', 2**63-1: '64 bit' }.get(sys.maxsize) or 'unknown bits' - friendly_version = "{0}.{1}.{2}.{3}.{4}".format(*sys.version_info) - PYTHON_VERSION = "{0} ({1})".format(friendly_version, friendly_maxsize) + g_trace.write("python_version: {0}".format(info['python_version'])) + g_trace.write("cpuinfo_version: {0}".format(info['cpuinfo_version'])) + g_trace.write("arch: {0}".format(info['arch'])) + g_trace.write("bits: {0}".format(info['bits'])) + g_trace.write("count: {0}".format(info['count'])) + g_trace.write("arch_string_raw: {0}".format(info['arch_string_raw'])) - info = { - 'python_version' : PYTHON_VERSION, - 'cpuinfo_version' : CPUINFO_VERSION, - 'cpuinfo_version_string' : CPUINFO_VERSION_STRING, - 'arch' : arch, - 'bits' : bits, - 'count' : DataSource.cpu_count, - 'arch_string_raw' : DataSource.arch_string_raw, - } + # Try the Windows wmic + _copy_new_fields(info, _get_cpu_info_from_wmic()) - # Try the Windows wmic - _copy_new_fields(info, _get_cpu_info_from_wmic()) + # Try the Windows registry + _copy_new_fields(info, _get_cpu_info_from_registry()) - # Try the Windows registry - _copy_new_fields(info, _get_cpu_info_from_registry()) + # Try /proc/cpuinfo + _copy_new_fields(info, _get_cpu_info_from_proc_cpuinfo()) - # Try /proc/cpuinfo - _copy_new_fields(info, _get_cpu_info_from_proc_cpuinfo()) + # Try cpufreq-info + _copy_new_fields(info, _get_cpu_info_from_cpufreq_info()) - # Try cpufreq-info - _copy_new_fields(info, _get_cpu_info_from_cpufreq_info()) + # Try LSCPU + _copy_new_fields(info, _get_cpu_info_from_lscpu()) - # Try LSCPU - _copy_new_fields(info, _get_cpu_info_from_lscpu()) + # Try sysctl + _copy_new_fields(info, _get_cpu_info_from_sysctl()) - # Try sysctl - _copy_new_fields(info, _get_cpu_info_from_sysctl()) + # Try kstat + _copy_new_fields(info, _get_cpu_info_from_kstat()) - # Try kstat - _copy_new_fields(info, _get_cpu_info_from_kstat()) + # Try dmesg + _copy_new_fields(info, _get_cpu_info_from_dmesg()) - # Try dmesg - _copy_new_fields(info, _get_cpu_info_from_dmesg()) + # Try /var/run/dmesg.boot + _copy_new_fields(info, _get_cpu_info_from_cat_var_run_dmesg_boot()) - # Try /var/run/dmesg.boot - _copy_new_fields(info, _get_cpu_info_from_cat_var_run_dmesg_boot()) + # Try lsprop ibm,pa-features + _copy_new_fields(info, _get_cpu_info_from_ibm_pa_features()) - # Try lsprop ibm,pa-features - _copy_new_fields(info, _get_cpu_info_from_ibm_pa_features()) + # Try sysinfo + _copy_new_fields(info, _get_cpu_info_from_sysinfo()) - # Try sysinfo - _copy_new_fields(info, _get_cpu_info_from_sysinfo()) + # Try querying the CPU cpuid register + # FIXME: This should print stdout and stderr to trace log + _copy_new_fields(info, _get_cpu_info_from_cpuid()) - # Try querying the CPU cpuid register - _copy_new_fields(info, _get_cpu_info_from_cpuid()) + # Try platform.uname + _copy_new_fields(info, _get_cpu_info_from_platform_uname()) - # Try platform.uname - _copy_new_fields(info, _get_cpu_info_from_platform_uname()) + g_trace.write('!' * 80) - return info + return info def get_cpu_info_json(): - ''' - Returns the CPU info by using the best sources of information for your OS. - Returns the result in a json string - ''' + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns the result in a json string + ''' - import json + import json - output = None + output = None - # If running under pyinstaller, run normally - if getattr(sys, 'frozen', False): - info = _get_cpu_info_internal() - output = json.dumps(info) - output = "{0}".format(output) - # if not running under pyinstaller, run in another process. - # This is done because multiprocesing has a design flaw that - # causes non main programs to run multiple times on Windows. - else: - from subprocess import Popen, PIPE + # If running under pyinstaller, run normally + if getattr(sys, 'frozen', False): + info = _get_cpu_info_internal() + output = json.dumps(info) + output = "{0}".format(output) + # if not running under pyinstaller, run in another process. + # This is done because multiprocesing has a design flaw that + # causes non main programs to run multiple times on Windows. + else: + from subprocess import Popen, PIPE - command = [sys.executable, __file__, '--json'] - p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) - output = p1.communicate()[0] + command = [sys.executable, __file__, '--json'] + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + output = p1.communicate()[0] - if p1.returncode != 0: - return "{}" + if p1.returncode != 0: + return "{}" - if not IS_PY2: - output = output.decode(encoding='UTF-8') + output = output.decode(encoding='UTF-8') - return output + return output def get_cpu_info(): - ''' - Returns the CPU info by using the best sources of information for your OS. - Returns the result in a dict - ''' + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns the result in a dict + ''' - import json + import json - output = get_cpu_info_json() + output = get_cpu_info_json() - # Convert JSON to Python with non unicode strings - output = json.loads(output, object_hook = _utf_to_str) + # Convert JSON to Python with non unicode strings + output = json.loads(output, object_hook = _utf_to_str) - return output + return output def main(): - from argparse import ArgumentParser - import json - - # Parse args - parser = ArgumentParser(description='Gets CPU info with pure Python 2 & 3') - parser.add_argument('--json', action='store_true', help='Return the info in JSON format') - parser.add_argument('--version', action='store_true', help='Return the version of py-cpuinfo') - args = parser.parse_args() - - try: - _check_arch() - except Exception as err: - sys.stderr.write(str(err) + "\n") - sys.exit(1) - - info = _get_cpu_info_internal() - - if not info: - sys.stderr.write("Failed to find cpu info\n") - sys.exit(1) - - if args.json: - from pprint import pprint - pprint(info) - #print(json.dumps(info)) - elif args.version: - print(CPUINFO_VERSION_STRING) - else: - print('Python Version: {0}'.format(info.get('python_version', ''))) - print('Cpuinfo Version: {0}'.format(info.get('cpuinfo_version_string', ''))) - print('Vendor ID Raw: {0}'.format(info.get('vendor_id_raw', ''))) - print('Hardware Raw: {0}'.format(info.get('hardware_raw', ''))) - print('Brand Raw: {0}'.format(info.get('brand_raw', ''))) - print('Hz Advertised Friendly: {0}'.format(info.get('hz_advertised_friendly', ''))) - print('Hz Actual Friendly: {0}'.format(info.get('hz_actual_friendly', ''))) - print('Hz Advertised: {0}'.format(info.get('hz_advertised', ''))) - print('Hz Actual: {0}'.format(info.get('hz_actual', ''))) - print('Arch: {0}'.format(info.get('arch', ''))) - print('Bits: {0}'.format(info.get('bits', ''))) - print('Count: {0}'.format(info.get('count', ''))) - print('Arch String Raw: {0}'.format(info.get('arch_string_raw', ''))) - print('L1 Data Cache Size: {0}'.format(info.get('l1_data_cache_size', ''))) - print('L1 Instruction Cache Size: {0}'.format(info.get('l1_instruction_cache_size', ''))) - print('L2 Cache Size: {0}'.format(info.get('l2_cache_size', ''))) - print('L2 Cache Line Size: {0}'.format(info.get('l2_cache_line_size', ''))) - print('L2 Cache Associativity: {0}'.format(info.get('l2_cache_associativity', ''))) - print('L3 Cache Size: {0}'.format(info.get('l3_cache_size', ''))) - print('Stepping: {0}'.format(info.get('stepping', ''))) - print('Model: {0}'.format(info.get('model', ''))) - print('Family: {0}'.format(info.get('family', ''))) - print('Processor Type: {0}'.format(info.get('processor_type', ''))) - print('Extended Model: {0}'.format(info.get('extended_model', ''))) - print('Extended Family: {0}'.format(info.get('extended_family', ''))) - print('Flags: {0}'.format(', '.join(info.get('flags', '')))) + from argparse import ArgumentParser + import json + + # Parse args + parser = ArgumentParser(description='Gets CPU info with pure Python') + parser.add_argument('--json', action='store_true', help='Return the info in JSON format') + parser.add_argument('--version', action='store_true', help='Return the version of py-cpuinfo') + parser.add_argument('--trace', action='store_true', help='Traces code paths used to find CPU info to file') + args = parser.parse_args() + + global g_trace + g_trace = Trace(args.trace, False) + + try: + _check_arch() + except Exception as err: + sys.stderr.write(str(err) + "\n") + sys.exit(1) + + info = _get_cpu_info_internal() + + if not info: + sys.stderr.write("Failed to find cpu info\n") + sys.exit(1) + + if args.json: + print(json.dumps(info)) + elif args.version: + print(CPUINFO_VERSION_STRING) + else: + print('Python Version: {0}'.format(info.get('python_version', ''))) + print('Cpuinfo Version: {0}'.format(info.get('cpuinfo_version_string', ''))) + print('Vendor ID Raw: {0}'.format(info.get('vendor_id_raw', ''))) + print('Hardware Raw: {0}'.format(info.get('hardware_raw', ''))) + print('Brand Raw: {0}'.format(info.get('brand_raw', ''))) + print('Hz Advertised Friendly: {0}'.format(info.get('hz_advertised_friendly', ''))) + print('Hz Actual Friendly: {0}'.format(info.get('hz_actual_friendly', ''))) + print('Hz Advertised: {0}'.format(info.get('hz_advertised', ''))) + print('Hz Actual: {0}'.format(info.get('hz_actual', ''))) + print('Arch: {0}'.format(info.get('arch', ''))) + print('Bits: {0}'.format(info.get('bits', ''))) + print('Count: {0}'.format(info.get('count', ''))) + print('Arch String Raw: {0}'.format(info.get('arch_string_raw', ''))) + print('L1 Data Cache Size: {0}'.format(info.get('l1_data_cache_size', ''))) + print('L1 Instruction Cache Size: {0}'.format(info.get('l1_instruction_cache_size', ''))) + print('L2 Cache Size: {0}'.format(info.get('l2_cache_size', ''))) + print('L2 Cache Line Size: {0}'.format(info.get('l2_cache_line_size', ''))) + print('L2 Cache Associativity: {0}'.format(info.get('l2_cache_associativity', ''))) + print('L3 Cache Size: {0}'.format(info.get('l3_cache_size', ''))) + print('Stepping: {0}'.format(info.get('stepping', ''))) + print('Model: {0}'.format(info.get('model', ''))) + print('Family: {0}'.format(info.get('family', ''))) + print('Processor Type: {0}'.format(info.get('processor_type', ''))) + print('Flags: {0}'.format(', '.join(info.get('flags', '')))) if __name__ == '__main__': - main() + main() else: - _check_arch() + g_trace = Trace(False, False) + _check_arch() diff --git a/lib/cpuinfo_sh.py b/lib/cpuinfo_sh.py new file mode 100644 index 0000000000..ec38450fbf --- /dev/null +++ b/lib/cpuinfo_sh.py @@ -0,0 +1,2425 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- + +# Copyright (c) 2014-2019, Matthew Brennan Jones +# Py-cpuinfo gets CPU info with pure Python 2 & 3 +# It uses the MIT License +# It is hosted at: https://github.com/workhorsy/py-cpuinfo +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Additions for Raspberry Pi by msinn +# +CPUINFO_VERSION = (5, 0, 0) +CPUINFO_VERSION_STRING = '.'.join([str(n) for n in CPUINFO_VERSION]) + +import os, sys +import platform +import multiprocessing +import ctypes + +try: + import _winreg as winreg +except ImportError as err: + try: + import winreg + except ImportError as err: + pass + +IS_PY2 = sys.version_info[0] == 2 +CAN_CALL_CPUID_IN_SUBPROCESS = True + + +class DataSource(object): + bits = platform.architecture()[0] + cpu_count = multiprocessing.cpu_count() + is_windows = platform.system().lower() == 'windows' + arch_string_raw = platform.machine() + uname_string_raw = platform.uname()[5] + can_cpuid = True + + @staticmethod + def has_proc_cpuinfo(): + return os.path.exists('/proc/cpuinfo') + + @staticmethod + def has_dmesg(): + return len(_program_paths('dmesg')) > 0 + + @staticmethod + def has_var_run_dmesg_boot(): + uname = platform.system().strip().strip('"').strip("'").strip().lower() + return 'linux' in uname and os.path.exists('/var/run/dmesg.boot') + + @staticmethod + def has_cpufreq_info(): + return len(_program_paths('cpufreq-info')) > 0 + + @staticmethod + def has_sestatus(): + return len(_program_paths('sestatus')) > 0 + + @staticmethod + def has_sysctl(): + return len(_program_paths('sysctl')) > 0 + + @staticmethod + def has_isainfo(): + return len(_program_paths('isainfo')) > 0 + + @staticmethod + def has_kstat(): + return len(_program_paths('kstat')) > 0 + + @staticmethod + def has_sysinfo(): + return len(_program_paths('sysinfo')) > 0 + + @staticmethod + def has_lscpu(): + return len(_program_paths('lscpu')) > 0 + + @staticmethod + def has_ibm_pa_features(): + return len(_program_paths('lsprop')) > 0 + + @staticmethod + def has_wmic(): + returncode, output = _run_and_get_stdout(['wmic', 'os', 'get', 'Version']) + return returncode == 0 and len(output) > 0 + + @staticmethod + def cat_proc_cpuinfo(): + return _run_and_get_stdout(['cat', '/proc/cpuinfo']) + + @staticmethod + def cpufreq_info(): + return _run_and_get_stdout(['cpufreq-info']) + + @staticmethod + def sestatus_b(): + return _run_and_get_stdout(['sestatus', '-b']) + + @staticmethod + def dmesg_a(): + return _run_and_get_stdout(['dmesg', '-a']) + + @staticmethod + def cat_var_run_dmesg_boot(): + return _run_and_get_stdout(['cat', '/var/run/dmesg.boot']) + + @staticmethod + def sysctl_machdep_cpu_hw_cpufrequency(): + return _run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency']) + + @staticmethod + def isainfo_vb(): + return _run_and_get_stdout(['isainfo', '-vb']) + + @staticmethod + def kstat_m_cpu_info(): + return _run_and_get_stdout(['kstat', '-m', 'cpu_info']) + + @staticmethod + def sysinfo_cpu(): + return _run_and_get_stdout(['sysinfo', '-cpu']) + + @staticmethod + def lscpu(): + return _run_and_get_stdout(['lscpu']) + + @staticmethod + def ibm_pa_features(): + import glob + + ibm_features = glob.glob('/proc/device-tree/cpus/*/ibm,pa-features') + if ibm_features: + return _run_and_get_stdout(['lsprop', ibm_features[0]]) + + @staticmethod + def wmic_cpu(): + return _run_and_get_stdout(['wmic', 'cpu', 'get', 'Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer', '/format:list']) + + @staticmethod + def winreg_processor_brand(): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") + processor_brand = winreg.QueryValueEx(key, "ProcessorNameString")[0] + winreg.CloseKey(key) + return processor_brand.strip() + + @staticmethod + def winreg_vendor_id_raw(): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") + vendor_id_raw = winreg.QueryValueEx(key, "VendorIdentifier")[0] + winreg.CloseKey(key) + return vendor_id_raw + + @staticmethod + def winreg_arch_string_raw(): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment") + arch_string_raw = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE")[0] + winreg.CloseKey(key) + return arch_string_raw + + @staticmethod + def winreg_hz_actual(): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") + hz_actual = winreg.QueryValueEx(key, "~Mhz")[0] + winreg.CloseKey(key) + hz_actual = _to_decimal_string(hz_actual) + return hz_actual + + @staticmethod + def winreg_feature_bits(): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") + feature_bits = winreg.QueryValueEx(key, "FeatureSet")[0] + winreg.CloseKey(key) + return feature_bits + + +def _program_paths(program_name): + paths = [] + exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)) + path = os.environ['PATH'] + for p in os.environ['PATH'].split(os.pathsep): + p = os.path.join(p, program_name) + if os.access(p, os.X_OK): + paths.append(p) + for e in exts: + pext = p + e + if os.access(pext, os.X_OK): + paths.append(pext) + return paths + +def _run_and_get_stdout(command, pipe_command=None): + from subprocess import Popen, PIPE + + if not pipe_command: + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + output = p1.communicate()[0] + if not IS_PY2: + output = output.decode(encoding='UTF-8') + return p1.returncode, output + else: + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + p2 = Popen(pipe_command, stdin=p1.stdout, stdout=PIPE, stderr=PIPE) + p1.stdout.close() + output = p2.communicate()[0] + if not IS_PY2: + output = output.decode(encoding='UTF-8') + return p2.returncode, output + +# Make sure we are running on a supported system +def _check_arch(): + arch, bits = _parse_arch(DataSource.arch_string_raw) + if not arch in ['X86_32', 'X86_64', 'ARM_7', 'ARM_8', 'PPC_64', 'S390X']: + raise Exception(f"py-cpuinfo currently only works on X86 and some ARM/PPC/S390X CPUs, but not on {arch}") + +def _obj_to_b64(thing): + import pickle + import base64 + + a = thing + b = pickle.dumps(a) + c = base64.b64encode(b) + d = c.decode('utf8') + return d + +def _b64_to_obj(thing): + import pickle + import base64 + + try: + a = base64.b64decode(thing) + b = pickle.loads(a) + return b + except: + return {} + +def _utf_to_str(input): + if IS_PY2 and isinstance(input, unicode): + return input.encode('utf-8') + elif isinstance(input, list): + return [_utf_to_str(element) for element in input] + elif isinstance(input, dict): + return {_utf_to_str(key): _utf_to_str(value) + for key, value in input.items()} + else: + return input + +def _copy_new_fields(info, new_info): + keys = [ + # MSinn Additions begin - New fields: 'revision_raw', 'serial_raw' + #'vendor_id_raw', 'hardware_raw', 'brand_raw', 'hz_advertised_friendly', 'hz_actual_friendly', + 'vendor_id_raw', 'hardware_raw', 'revision_raw', 'serial_raw', 'brand_raw', 'hz_advertised_friendly', 'hz_actual_friendly', + # MSinn Additions end + 'hz_advertised', 'hz_actual', 'arch', 'bits', 'count', + 'arch_string_raw', 'uname_string_raw', + 'l2_cache_size', 'l2_cache_line_size', 'l2_cache_associativity', + 'stepping', 'model', 'family', + 'processor_type', 'extended_model', 'extended_family', 'flags', + 'l3_cache_size', 'l1_data_cache_size', 'l1_instruction_cache_size' + ] + + for key in keys: + if new_info.get(key, None) and not info.get(key, None): + info[key] = new_info[key] + elif key == 'flags' and new_info.get('flags'): + for f in new_info['flags']: + if f not in info['flags']: info['flags'].append(f) + info['flags'].sort() + +def _get_field_actual(cant_be_number, raw_string, field_names): + for line in raw_string.splitlines(): + for field_name in field_names: + field_name = field_name.lower() + if ':' in line: + left, right = line.split(':', 1) + left = left.strip().lower() + right = right.strip() + if left == field_name and len(right) > 0: + if cant_be_number: + if not right.isdigit(): + return right + else: + return right + + return None + +def _get_field(cant_be_number, raw_string, convert_to, default_value, *field_names): + retval = _get_field_actual(cant_be_number, raw_string, field_names) + + # Convert the return value + if retval and convert_to: + try: + retval = convert_to(retval) + except: + retval = default_value + + # Return the default if there is no return value + if retval is None: + retval = default_value + + return retval + +def _to_decimal_string(ticks): + try: + # Convert to string + ticks = '{0}'.format(ticks) + + # Strip off non numbers and decimal places + ticks = "".join(n for n in ticks if n.isdigit() or n=='.').strip() + if ticks == '': + ticks = '0' + + # Add decimal if missing + if '.' not in ticks: + ticks = '{0}.0'.format(ticks) + + # Remove trailing zeros + ticks = ticks.rstrip('0') + + # Add one trailing zero for empty right side + if ticks.endswith('.'): + ticks = '{0}0'.format(ticks) + + # Make sure the number can be converted to a float + ticks = float(ticks) + ticks = '{0}'.format(ticks) + return ticks + except: + return '0.0' + +def _hz_short_to_full(ticks, scale): + try: + # Make sure the number can be converted to a float + ticks = float(ticks) + ticks = '{0}'.format(ticks) + + # Scale the numbers + hz = ticks.lstrip('0') + old_index = hz.index('.') + hz = hz.replace('.', '') + hz = hz.ljust(scale + old_index+1, '0') + new_index = old_index + scale + hz = '{0}.{1}'.format(hz[:new_index], hz[new_index:]) + left, right = hz.split('.') + left, right = int(left), int(right) + return (left, right) + except: + return (0, 0) + +def _hz_friendly_to_full(hz_string): + try: + hz_string = hz_string.strip().lower() + hz, scale = (None, None) + + if hz_string.endswith('ghz'): + scale = 9 + elif hz_string.endswith('mhz'): + scale = 6 + elif hz_string.endswith('hz'): + scale = 0 + + hz = "".join(n for n in hz_string if n.isdigit() or n=='.').strip() + if not '.' in hz: + hz += '.0' + + hz, scale = _hz_short_to_full(hz, scale) + + return (hz, scale) + except: + return (0, 0) + +def _hz_short_to_friendly(ticks, scale): + try: + # Get the raw Hz as a string + left, right = _hz_short_to_full(ticks, scale) + result = '{0}.{1}'.format(left, right) + + # Get the location of the dot, and remove said dot + dot_index = result.index('.') + result = result.replace('.', '') + + # Get the Hz symbol and scale + symbol = "Hz" + scale = 0 + if dot_index > 9: + symbol = "GHz" + scale = 9 + elif dot_index > 6: + symbol = "MHz" + scale = 6 + elif dot_index > 3: + symbol = "KHz" + scale = 3 + + # Get the Hz with the dot at the new scaled point + result = '{0}.{1}'.format(result[:-scale-1], result[-scale-1:]) + + # Format the ticks to have 4 numbers after the decimal + # and remove any superfluous zeroes. + result = '{0:.4f} {1}'.format(float(result), symbol) + result = result.rstrip('0') + return result + except: + return '0.0000 Hz' + +def _to_friendly_bytes(input): + import re + + if not input: + return input + input = "{0}".format(input) + + formats = { + r"^[0-9]+B$" : 'B', + r"^[0-9]+K$" : 'KB', + r"^[0-9]+M$" : 'MB', + r"^[0-9]+G$" : 'GB' + } + + for pattern, friendly_size in formats.items(): + if re.match(pattern, input): + return "{0} {1}".format(input[ : -1].strip(), friendly_size) + + return input + +def _parse_cpu_brand_string(cpu_string): + # Just return 0 if the processor brand does not have the Hz + if not 'hz' in cpu_string.lower(): + return ('0.0', 0) + + hz = cpu_string.lower() + scale = 0 + + if hz.endswith('mhz'): + scale = 6 + elif hz.endswith('ghz'): + scale = 9 + if '@' in hz: + hz = hz.split('@')[1] + else: + hz = hz.rsplit(None, 1)[1] + + hz = hz.rstrip('mhz').rstrip('ghz').strip() + hz = _to_decimal_string(hz) + + return (hz, scale) + +def _parse_cpu_brand_string_dx(cpu_string): + import re + + # Find all the strings inside brackets () + starts = [m.start() for m in re.finditer('\(', cpu_string)] + ends = [m.start() for m in re.finditer('\)', cpu_string)] + insides = {k: v for k, v in zip(starts, ends)} + insides = [cpu_string[start+1 : end] for start, end in insides.items()] + + # Find all the fields + vendor_id, stepping, model, family = (None, None, None, None) + for inside in insides: + for pair in inside.split(','): + pair = [n.strip() for n in pair.split(':')] + if len(pair) > 1: + name, value = pair[0], pair[1] + if name == 'origin': + vendor_id = value.strip('"') + elif name == 'stepping': + stepping = int(value.lstrip('0x'), 16) + elif name == 'model': + model = int(value.lstrip('0x'), 16) + elif name in ['fam', 'family']: + family = int(value.lstrip('0x'), 16) + + # Find the Processor Brand + # Strip off extra strings in brackets at end + brand = cpu_string.strip() + is_working = True + while is_working: + is_working = False + for inside in insides: + full = "({0})".format(inside) + if brand.endswith(full): + brand = brand[ :-len(full)].strip() + is_working = True + + # Find the Hz in the brand string + hz_brand, scale = _parse_cpu_brand_string(brand) + + # Find Hz inside brackets () after the brand string + if hz_brand == '0.0': + for inside in insides: + hz = inside + for entry in ['GHz', 'MHz', 'Hz']: + if entry in hz: + hz = "CPU @ " + hz[ : hz.find(entry) + len(entry)] + hz_brand, scale = _parse_cpu_brand_string(hz) + break + + return (hz_brand, scale, brand, vendor_id, stepping, model, family) + +def _parse_dmesg_output(output): + try: + # Get all the dmesg lines that might contain a CPU string + lines = output.split(' CPU0:')[1:] + \ + output.split(' CPU1:')[1:] + \ + output.split(' CPU:')[1:] + \ + output.split('\nCPU0:')[1:] + \ + output.split('\nCPU1:')[1:] + \ + output.split('\nCPU:')[1:] + lines = [l.split('\n')[0].strip() for l in lines] + + # Convert the lines to CPU strings + cpu_strings = [_parse_cpu_brand_string_dx(l) for l in lines] + + # Find the CPU string that has the most fields + best_string = None + highest_count = 0 + for cpu_string in cpu_strings: + count = sum([n is not None for n in cpu_string]) + if count > highest_count: + highest_count = count + best_string = cpu_string + + # If no CPU string was found, return {} + if not best_string: + return {} + + hz_actual, scale, processor_brand, vendor_id, stepping, model, family = best_string + + # Origin + if ' Origin=' in output: + fields = output[output.find(' Origin=') : ].split('\n')[0] + fields = fields.strip().split() + fields = [n.strip().split('=') for n in fields] + fields = [{n[0].strip().lower() : n[1].strip()} for n in fields] + + for field in fields: + name = list(field.keys())[0] + value = list(field.values())[0] + + if name == 'origin': + vendor_id = value.strip('"') + elif name == 'stepping': + stepping = int(value.lstrip('0x'), 16) + elif name == 'model': + model = int(value.lstrip('0x'), 16) + elif name in ['fam', 'family']: + family = int(value.lstrip('0x'), 16) + + # Features + flag_lines = [] + for category in [' Features=', ' Features2=', ' AMD Features=', ' AMD Features2=']: + if category in output: + flag_lines.append(output.split(category)[1].split('\n')[0]) + + flags = [] + for line in flag_lines: + line = line.split('<')[1].split('>')[0].lower() + for flag in line.split(','): + flags.append(flag) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + + # If advertised hz not found, use the actual hz + if hz_advertised == '0.0': + scale = 6 + hz_advertised = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + if hz_advertised and hz_advertised != '0.0': + info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, scale) + + if hz_advertised and hz_advertised != '0.0': + info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) + info['hz_actual'] = _hz_short_to_full(hz_actual, scale) + + return {k: v for k, v in info.items() if v} + except: + #raise + pass + + return {} + +def _parse_arch(arch_string_raw): + import re + + arch, bits = None, None + arch_string_raw = arch_string_raw.lower() + + # X86 + if re.match('^i\d86$|^x86$|^x86_32$|^i86pc$|^ia32$|^ia-32$|^bepc$', arch_string_raw): + arch = 'X86_32' + bits = 32 + elif re.match('^x64$|^x86_64$|^x86_64t$|^i686-64$|^amd64$|^ia64$|^ia-64$', arch_string_raw): + arch = 'X86_64' + bits = 64 + # ARM + elif re.match('^armv8-a|aarch64$', arch_string_raw): + arch = 'ARM_8' + bits = 64 + elif re.match('^armv7$|^armv7[a-z]$|^armv7-[a-z]$|^armv6[a-z]$', arch_string_raw): + arch = 'ARM_7' + bits = 32 + elif re.match('^armv8$|^armv8[a-z]$|^armv8-[a-z]$', arch_string_raw): + arch = 'ARM_8' + bits = 32 + # PPC + elif re.match('^ppc32$|^prep$|^pmac$|^powermac$', arch_string_raw): + arch = 'PPC_32' + bits = 32 + elif re.match('^powerpc$|^ppc64$|^ppc64le$', arch_string_raw): + arch = 'PPC_64' + bits = 64 + # SPARC + elif re.match('^sparc32$|^sparc$', arch_string_raw): + arch = 'SPARC_32' + bits = 32 + elif re.match('^sparc64$|^sun4u$|^sun4v$', arch_string_raw): + arch = 'SPARC_64' + bits = 64 + # S390X + elif re.match('^s390x$', arch_string_raw): + arch = 'S390X' + bits = 64 + + return (arch, bits) + +def _is_bit_set(reg, bit): + mask = 1 << bit + is_set = reg & mask > 0 + return is_set + + +def _is_selinux_enforcing(): + # Just return if the SE Linux Status Tool is not installed + if not DataSource.has_sestatus(): + return False + + # Run the sestatus, and just return if it failed to run + returncode, output = DataSource.sestatus_b() + if returncode != 0: + return False + + # Figure out if explicitly in enforcing mode + for line in output.splitlines(): + line = line.strip().lower() + if line.startswith("current mode:"): + if line.endswith("enforcing"): + return True + else: + return False + + # Figure out if we can execute heap and execute memory + can_selinux_exec_heap = False + can_selinux_exec_memory = False + for line in output.splitlines(): + line = line.strip().lower() + if line.startswith("allow_execheap") and line.endswith("on"): + can_selinux_exec_heap = True + elif line.startswith("allow_execmem") and line.endswith("on"): + can_selinux_exec_memory = True + + return (not can_selinux_exec_heap or not can_selinux_exec_memory) + + +class CPUID(object): + def __init__(self): + self.prochandle = None + + # Figure out if SE Linux is on and in enforcing mode + self.is_selinux_enforcing = _is_selinux_enforcing() + + def _asm_func(self, restype=None, argtypes=(), byte_code=[]): + byte_code = bytes.join(b'', byte_code) + address = None + + if DataSource.is_windows: + # Allocate a memory segment the size of the byte code, and make it executable + size = len(byte_code) + # Alloc at least 1 page to ensure we own all pages that we want to change protection on + if size < 0x1000: size = 0x1000 + MEM_COMMIT = ctypes.c_ulong(0x1000) + PAGE_READWRITE = ctypes.c_ulong(0x4) + pfnVirtualAlloc = ctypes.windll.kernel32.VirtualAlloc + pfnVirtualAlloc.restype = ctypes.c_void_p + address = pfnVirtualAlloc(None, ctypes.c_size_t(size), MEM_COMMIT, PAGE_READWRITE) + if not address: + raise Exception("Failed to VirtualAlloc") + + # Copy the byte code into the memory segment + memmove = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)(ctypes._memmove_addr) + if memmove(address, byte_code, size) < 0: + raise Exception("Failed to memmove") + + # Enable execute permissions + PAGE_EXECUTE = ctypes.c_ulong(0x10) + old_protect = ctypes.c_ulong(0) + pfnVirtualProtect = ctypes.windll.kernel32.VirtualProtect + res = pfnVirtualProtect(ctypes.c_void_p(address), ctypes.c_size_t(size), PAGE_EXECUTE, ctypes.byref(old_protect)) + if not res: + raise Exception("Failed VirtualProtect") + + # Flush Instruction Cache + # First, get process Handle + if not self.prochandle: + pfnGetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess + pfnGetCurrentProcess.restype = ctypes.c_void_p + self.prochandle = ctypes.c_void_p(pfnGetCurrentProcess()) + # Actually flush cache + res = ctypes.windll.kernel32.FlushInstructionCache(self.prochandle, ctypes.c_void_p(address), ctypes.c_size_t(size)) + if not res: + raise Exception("Failed FlushInstructionCache") + else: + # Allocate a memory segment the size of the byte code + size = len(byte_code) + pfnvalloc = ctypes.pythonapi.valloc + pfnvalloc.restype = ctypes.c_void_p + address = pfnvalloc(ctypes.c_size_t(size)) + if not address: + raise Exception("Failed to valloc") + + # Mark the memory segment as writeable only + if not self.is_selinux_enforcing: + WRITE = 0x2 + if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE) < 0: + raise Exception("Failed to mprotect") + + # Copy the byte code into the memory segment + if ctypes.pythonapi.memmove(ctypes.c_void_p(address), byte_code, ctypes.c_size_t(size)) < 0: + raise Exception("Failed to memmove") + + # Mark the memory segment as writeable and executable only + if not self.is_selinux_enforcing: + WRITE_EXECUTE = 0x2 | 0x4 + if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE_EXECUTE) < 0: + raise Exception("Failed to mprotect") + + # Cast the memory segment into a function + functype = ctypes.CFUNCTYPE(restype, *argtypes) + fun = functype(address) + return fun, address + + def _run_asm(self, *byte_code): + # Convert the byte code into a function that returns an int + restype = ctypes.c_uint32 + argtypes = () + func, address = self._asm_func(restype, argtypes, byte_code) + + # Call the byte code like a function + retval = func() + + byte_code = bytes.join(b'', byte_code) + size = ctypes.c_size_t(len(byte_code)) + + # Free the function memory segment + if DataSource.is_windows: + MEM_RELEASE = ctypes.c_ulong(0x8000) + ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(address), ctypes.c_size_t(0), MEM_RELEASE) + else: + # Remove the executable tag on the memory + READ_WRITE = 0x1 | 0x2 + if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, READ_WRITE) < 0: + raise Exception("Failed to mprotect") + + ctypes.pythonapi.free(ctypes.c_void_p(address)) + + return retval + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID + def get_vendor_id(self): + # EBX + ebx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0F\xA2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # EDX + edx = self._run_asm( + b"\x31\xC0", # xor eax,eax + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # Each 4bits is a ascii letter in the name + vendor_id = [] + for reg in [ebx, edx, ecx]: + for n in [0, 8, 16, 24]: + vendor_id.append(chr((reg >> n) & 0xFF)) + vendor_id = ''.join(vendor_id) + + return vendor_id + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits + def get_info(self): + # EAX + eax = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\xC3" # ret + ) + + # Get the CPU info + stepping = (eax >> 0) & 0xF # 4 bits + model = (eax >> 4) & 0xF # 4 bits + family = (eax >> 8) & 0xF # 4 bits + processor_type = (eax >> 12) & 0x3 # 2 bits + extended_model = (eax >> 16) & 0xF # 4 bits + extended_family = (eax >> 20) & 0xFF # 8 bits + + return { + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'processor_type' : processor_type, + 'extended_model' : extended_model, + 'extended_family' : extended_family + } + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000000h:_Get_Highest_Extended_Function_Supported + def get_max_extension_support(self): + # Check for extension support + max_extension_support = self._run_asm( + b"\xB8\x00\x00\x00\x80" # mov ax,0x80000000 + b"\x0f\xa2" # cpuid + b"\xC3" # ret + ) + + return max_extension_support + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits + def get_flags(self, max_extension_support): + # EDX + edx = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\xB8\x01\x00\x00\x00", # mov eax,0x1" + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the CPU flags + flags = { + 'fpu' : _is_bit_set(edx, 0), + 'vme' : _is_bit_set(edx, 1), + 'de' : _is_bit_set(edx, 2), + 'pse' : _is_bit_set(edx, 3), + 'tsc' : _is_bit_set(edx, 4), + 'msr' : _is_bit_set(edx, 5), + 'pae' : _is_bit_set(edx, 6), + 'mce' : _is_bit_set(edx, 7), + 'cx8' : _is_bit_set(edx, 8), + 'apic' : _is_bit_set(edx, 9), + #'reserved1' : _is_bit_set(edx, 10), + 'sep' : _is_bit_set(edx, 11), + 'mtrr' : _is_bit_set(edx, 12), + 'pge' : _is_bit_set(edx, 13), + 'mca' : _is_bit_set(edx, 14), + 'cmov' : _is_bit_set(edx, 15), + 'pat' : _is_bit_set(edx, 16), + 'pse36' : _is_bit_set(edx, 17), + 'pn' : _is_bit_set(edx, 18), + 'clflush' : _is_bit_set(edx, 19), + #'reserved2' : _is_bit_set(edx, 20), + 'dts' : _is_bit_set(edx, 21), + 'acpi' : _is_bit_set(edx, 22), + 'mmx' : _is_bit_set(edx, 23), + 'fxsr' : _is_bit_set(edx, 24), + 'sse' : _is_bit_set(edx, 25), + 'sse2' : _is_bit_set(edx, 26), + 'ss' : _is_bit_set(edx, 27), + 'ht' : _is_bit_set(edx, 28), + 'tm' : _is_bit_set(edx, 29), + 'ia64' : _is_bit_set(edx, 30), + 'pbe' : _is_bit_set(edx, 31), + + 'pni' : _is_bit_set(ecx, 0), + 'pclmulqdq' : _is_bit_set(ecx, 1), + 'dtes64' : _is_bit_set(ecx, 2), + 'monitor' : _is_bit_set(ecx, 3), + 'ds_cpl' : _is_bit_set(ecx, 4), + 'vmx' : _is_bit_set(ecx, 5), + 'smx' : _is_bit_set(ecx, 6), + 'est' : _is_bit_set(ecx, 7), + 'tm2' : _is_bit_set(ecx, 8), + 'ssse3' : _is_bit_set(ecx, 9), + 'cid' : _is_bit_set(ecx, 10), + #'reserved3' : _is_bit_set(ecx, 11), + 'fma' : _is_bit_set(ecx, 12), + 'cx16' : _is_bit_set(ecx, 13), + 'xtpr' : _is_bit_set(ecx, 14), + 'pdcm' : _is_bit_set(ecx, 15), + #'reserved4' : _is_bit_set(ecx, 16), + 'pcid' : _is_bit_set(ecx, 17), + 'dca' : _is_bit_set(ecx, 18), + 'sse4_1' : _is_bit_set(ecx, 19), + 'sse4_2' : _is_bit_set(ecx, 20), + 'x2apic' : _is_bit_set(ecx, 21), + 'movbe' : _is_bit_set(ecx, 22), + 'popcnt' : _is_bit_set(ecx, 23), + 'tscdeadline' : _is_bit_set(ecx, 24), + 'aes' : _is_bit_set(ecx, 25), + 'xsave' : _is_bit_set(ecx, 26), + 'osxsave' : _is_bit_set(ecx, 27), + 'avx' : _is_bit_set(ecx, 28), + 'f16c' : _is_bit_set(ecx, 29), + 'rdrnd' : _is_bit_set(ecx, 30), + 'hypervisor' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D7.2C_ECX.3D0:_Extended_Features + if max_extension_support >= 7: + # EBX + ebx = self._run_asm( + b"\x31\xC9", # xor ecx,ecx + b"\xB8\x07\x00\x00\x00" # mov eax,7 + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\x31\xC9", # xor ecx,ecx + b"\xB8\x07\x00\x00\x00" # mov eax,7 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the extended CPU flags + extended_flags = { + #'fsgsbase' : _is_bit_set(ebx, 0), + #'IA32_TSC_ADJUST' : _is_bit_set(ebx, 1), + 'sgx' : _is_bit_set(ebx, 2), + 'bmi1' : _is_bit_set(ebx, 3), + 'hle' : _is_bit_set(ebx, 4), + 'avx2' : _is_bit_set(ebx, 5), + #'reserved' : _is_bit_set(ebx, 6), + 'smep' : _is_bit_set(ebx, 7), + 'bmi2' : _is_bit_set(ebx, 8), + 'erms' : _is_bit_set(ebx, 9), + 'invpcid' : _is_bit_set(ebx, 10), + 'rtm' : _is_bit_set(ebx, 11), + 'pqm' : _is_bit_set(ebx, 12), + #'FPU CS and FPU DS deprecated' : _is_bit_set(ebx, 13), + 'mpx' : _is_bit_set(ebx, 14), + 'pqe' : _is_bit_set(ebx, 15), + 'avx512f' : _is_bit_set(ebx, 16), + 'avx512dq' : _is_bit_set(ebx, 17), + 'rdseed' : _is_bit_set(ebx, 18), + 'adx' : _is_bit_set(ebx, 19), + 'smap' : _is_bit_set(ebx, 20), + 'avx512ifma' : _is_bit_set(ebx, 21), + 'pcommit' : _is_bit_set(ebx, 22), + 'clflushopt' : _is_bit_set(ebx, 23), + 'clwb' : _is_bit_set(ebx, 24), + 'intel_pt' : _is_bit_set(ebx, 25), + 'avx512pf' : _is_bit_set(ebx, 26), + 'avx512er' : _is_bit_set(ebx, 27), + 'avx512cd' : _is_bit_set(ebx, 28), + 'sha' : _is_bit_set(ebx, 29), + 'avx512bw' : _is_bit_set(ebx, 30), + 'avx512vl' : _is_bit_set(ebx, 31), + + 'prefetchwt1' : _is_bit_set(ecx, 0), + 'avx512vbmi' : _is_bit_set(ecx, 1), + 'umip' : _is_bit_set(ecx, 2), + 'pku' : _is_bit_set(ecx, 3), + 'ospke' : _is_bit_set(ecx, 4), + #'reserved' : _is_bit_set(ecx, 5), + 'avx512vbmi2' : _is_bit_set(ecx, 6), + #'reserved' : _is_bit_set(ecx, 7), + 'gfni' : _is_bit_set(ecx, 8), + 'vaes' : _is_bit_set(ecx, 9), + 'vpclmulqdq' : _is_bit_set(ecx, 10), + 'avx512vnni' : _is_bit_set(ecx, 11), + 'avx512bitalg' : _is_bit_set(ecx, 12), + #'reserved' : _is_bit_set(ecx, 13), + 'avx512vpopcntdq' : _is_bit_set(ecx, 14), + #'reserved' : _is_bit_set(ecx, 15), + #'reserved' : _is_bit_set(ecx, 16), + #'mpx0' : _is_bit_set(ecx, 17), + #'mpx1' : _is_bit_set(ecx, 18), + #'mpx2' : _is_bit_set(ecx, 19), + #'mpx3' : _is_bit_set(ecx, 20), + #'mpx4' : _is_bit_set(ecx, 21), + 'rdpid' : _is_bit_set(ecx, 22), + #'reserved' : _is_bit_set(ecx, 23), + #'reserved' : _is_bit_set(ecx, 24), + #'reserved' : _is_bit_set(ecx, 25), + #'reserved' : _is_bit_set(ecx, 26), + #'reserved' : _is_bit_set(ecx, 27), + #'reserved' : _is_bit_set(ecx, 28), + #'reserved' : _is_bit_set(ecx, 29), + 'sgx_lc' : _is_bit_set(ecx, 30), + #'reserved' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + extended_flags = [k for k, v in extended_flags.items() if v] + flags += extended_flags + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits + if max_extension_support >= 0x80000001: + # EBX + ebx = self._run_asm( + b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # Get the extended CPU flags + extended_flags = { + 'fpu' : _is_bit_set(ebx, 0), + 'vme' : _is_bit_set(ebx, 1), + 'de' : _is_bit_set(ebx, 2), + 'pse' : _is_bit_set(ebx, 3), + 'tsc' : _is_bit_set(ebx, 4), + 'msr' : _is_bit_set(ebx, 5), + 'pae' : _is_bit_set(ebx, 6), + 'mce' : _is_bit_set(ebx, 7), + 'cx8' : _is_bit_set(ebx, 8), + 'apic' : _is_bit_set(ebx, 9), + #'reserved' : _is_bit_set(ebx, 10), + 'syscall' : _is_bit_set(ebx, 11), + 'mtrr' : _is_bit_set(ebx, 12), + 'pge' : _is_bit_set(ebx, 13), + 'mca' : _is_bit_set(ebx, 14), + 'cmov' : _is_bit_set(ebx, 15), + 'pat' : _is_bit_set(ebx, 16), + 'pse36' : _is_bit_set(ebx, 17), + #'reserved' : _is_bit_set(ebx, 18), + 'mp' : _is_bit_set(ebx, 19), + 'nx' : _is_bit_set(ebx, 20), + #'reserved' : _is_bit_set(ebx, 21), + 'mmxext' : _is_bit_set(ebx, 22), + 'mmx' : _is_bit_set(ebx, 23), + 'fxsr' : _is_bit_set(ebx, 24), + 'fxsr_opt' : _is_bit_set(ebx, 25), + 'pdpe1gp' : _is_bit_set(ebx, 26), + 'rdtscp' : _is_bit_set(ebx, 27), + #'reserved' : _is_bit_set(ebx, 28), + 'lm' : _is_bit_set(ebx, 29), + '3dnowext' : _is_bit_set(ebx, 30), + '3dnow' : _is_bit_set(ebx, 31), + + 'lahf_lm' : _is_bit_set(ecx, 0), + 'cmp_legacy' : _is_bit_set(ecx, 1), + 'svm' : _is_bit_set(ecx, 2), + 'extapic' : _is_bit_set(ecx, 3), + 'cr8_legacy' : _is_bit_set(ecx, 4), + 'abm' : _is_bit_set(ecx, 5), + 'sse4a' : _is_bit_set(ecx, 6), + 'misalignsse' : _is_bit_set(ecx, 7), + '3dnowprefetch' : _is_bit_set(ecx, 8), + 'osvw' : _is_bit_set(ecx, 9), + 'ibs' : _is_bit_set(ecx, 10), + 'xop' : _is_bit_set(ecx, 11), + 'skinit' : _is_bit_set(ecx, 12), + 'wdt' : _is_bit_set(ecx, 13), + #'reserved' : _is_bit_set(ecx, 14), + 'lwp' : _is_bit_set(ecx, 15), + 'fma4' : _is_bit_set(ecx, 16), + 'tce' : _is_bit_set(ecx, 17), + #'reserved' : _is_bit_set(ecx, 18), + 'nodeid_msr' : _is_bit_set(ecx, 19), + #'reserved' : _is_bit_set(ecx, 20), + 'tbm' : _is_bit_set(ecx, 21), + 'topoext' : _is_bit_set(ecx, 22), + 'perfctr_core' : _is_bit_set(ecx, 23), + 'perfctr_nb' : _is_bit_set(ecx, 24), + #'reserved' : _is_bit_set(ecx, 25), + 'dbx' : _is_bit_set(ecx, 26), + 'perftsc' : _is_bit_set(ecx, 27), + 'pci_l2i' : _is_bit_set(ecx, 28), + #'reserved' : _is_bit_set(ecx, 29), + #'reserved' : _is_bit_set(ecx, 30), + #'reserved' : _is_bit_set(ecx, 31) + } + + # Get a list of only the flags that are true + extended_flags = [k for k, v in extended_flags.items() if v] + flags += extended_flags + + flags.sort() + return flags + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000002h.2C80000003h.2C80000004h:_Processor_Brand_String + def get_processor_brand(self, max_extension_support): + processor_brand = "" + + # Processor brand string + if max_extension_support >= 0x80000004: + instructions = [ + b"\xB8\x02\x00\x00\x80", # mov ax,0x80000002 + b"\xB8\x03\x00\x00\x80", # mov ax,0x80000003 + b"\xB8\x04\x00\x00\x80" # mov ax,0x80000004 + ] + for instruction in instructions: + # EAX + eax = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xC0" # mov ax,ax + b"\xC3" # ret + ) + + # EBX + ebx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xD8" # mov ax,bx + b"\xC3" # ret + ) + + # ECX + ecx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + # EDX + edx = self._run_asm( + instruction, # mov ax,0x8000000? + b"\x0f\xa2" # cpuid + b"\x89\xD0" # mov ax,dx + b"\xC3" # ret + ) + + # Combine each of the 4 bytes in each register into the string + for reg in [eax, ebx, ecx, edx]: + for n in [0, 8, 16, 24]: + processor_brand += chr((reg >> n) & 0xFF) + + # Strip off any trailing NULL terminators and white space + processor_brand = processor_brand.strip("\0").strip() + + return processor_brand + + # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features + def get_cache(self, max_extension_support): + cache_info = {} + + # Just return if the cache feature is not supported + if max_extension_support < 0x80000006: + return cache_info + + # ECX + ecx = self._run_asm( + b"\xB8\x06\x00\x00\x80" # mov ax,0x80000006 + b"\x0f\xa2" # cpuid + b"\x89\xC8" # mov ax,cx + b"\xC3" # ret + ) + + cache_info = { + 'size_kb' : ecx & 0xFF, + 'line_size_b' : (ecx >> 12) & 0xF, + 'associativity' : (ecx >> 16) & 0xFFFF + } + + return cache_info + + def get_ticks(self): + retval = None + + if DataSource.bits == '32bit': + # Works on x86_32 + restype = None + argtypes = (ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + get_ticks_x86_32, address = self._asm_func(restype, argtypes, + [ + b"\x55", # push bp + b"\x89\xE5", # mov bp,sp + b"\x31\xC0", # xor ax,ax + b"\x0F\xA2", # cpuid + b"\x0F\x31", # rdtsc + b"\x8B\x5D\x08", # mov bx,[di+0x8] + b"\x8B\x4D\x0C", # mov cx,[di+0xc] + b"\x89\x13", # mov [bp+di],dx + b"\x89\x01", # mov [bx+di],ax + b"\x5D", # pop bp + b"\xC3" # ret + ] + ) + + high = ctypes.c_uint32(0) + low = ctypes.c_uint32(0) + + get_ticks_x86_32(ctypes.byref(high), ctypes.byref(low)) + retval = ((high.value << 32) & 0xFFFFFFFF00000000) | low.value + elif DataSource.bits == '64bit': + # Works on x86_64 + restype = ctypes.c_uint64 + argtypes = () + get_ticks_x86_64, address = self._asm_func(restype, argtypes, + [ + b"\x48", # dec ax + b"\x31\xC0", # xor ax,ax + b"\x0F\xA2", # cpuid + b"\x0F\x31", # rdtsc + b"\x48", # dec ax + b"\xC1\xE2\x20", # shl dx,byte 0x20 + b"\x48", # dec ax + b"\x09\xD0", # or ax,dx + b"\xC3", # ret + ] + ) + retval = get_ticks_x86_64() + + return retval + + def get_raw_hz(self): + import time + + start = self.get_ticks() + + time.sleep(1) + + end = self.get_ticks() + + ticks = (end - start) + + return ticks + +def _get_cpu_info_from_cpuid_actual(): + ''' + Warning! This function has the potential to crash the Python runtime. + Do not call it directly. Use the _get_cpu_info_from_cpuid function instead. + It will safely call this function in another process. + ''' + + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + # Return none if this is not an X86 CPU + if not arch in ['X86_32', 'X86_64']: + return {} + + # Return none if SE Linux is in enforcing mode + cpuid = CPUID() + if cpuid.is_selinux_enforcing: + return {} + + # Get the cpu info from the CPUID register + max_extension_support = cpuid.get_max_extension_support() + cache_info = cpuid.get_cache(max_extension_support) + info = cpuid.get_info() + + processor_brand = cpuid.get_processor_brand(max_extension_support) + + # Get the Hz and scale + hz_actual = cpuid.get_raw_hz() + hz_actual = _to_decimal_string(hz_actual) + + # Get the Hz and scale + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + info = { + 'vendor_id_raw' : cpuid.get_vendor_id(), + 'hardware_raw' : '', + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'l2_cache_size' : _to_friendly_bytes(cache_info['size_kb']), + 'l2_cache_line_size' : cache_info['line_size_b'], + 'l2_cache_associativity' : hex(cache_info['associativity']), + + 'stepping' : info['stepping'], + 'model' : info['model'], + 'family' : info['family'], + 'processor_type' : info['processor_type'], + 'extended_model' : info['extended_model'], + 'extended_family' : info['extended_family'], + 'flags' : cpuid.get_flags(max_extension_support) + } + + info = {k: v for k, v in info.items() if v} + return info + +def _get_cpu_info_from_cpuid_subprocess_wrapper(queue): + # Pipe all output to nothing + sys.stdout = open(os.devnull, 'w') + sys.stderr = open(os.devnull, 'w') + + info = _get_cpu_info_from_cpuid_actual() + + queue.put(_obj_to_b64(info)) + +def _get_cpu_info_from_cpuid(): + ''' + Returns the CPU info gathered by querying the X86 cpuid register in a new process. + Returns {} on non X86 cpus. + Returns {} if SELinux is in enforcing mode. + ''' + from multiprocessing import Process, Queue + + # Return {} if can't cpuid + if not DataSource.can_cpuid: + return {} + + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + # Return {} if this is not an X86 CPU + if not arch in ['X86_32', 'X86_64']: + return {} + + try: + if CAN_CALL_CPUID_IN_SUBPROCESS: + # Start running the function in a subprocess + queue = Queue() + p = Process(target=_get_cpu_info_from_cpuid_subprocess_wrapper, args=(queue,)) + p.start() + + # Wait for the process to end, while it is still alive + while p.is_alive(): + p.join(0) + + # Return {} if it failed + if p.exitcode != 0: + return {} + + # Return the result, only if there is something to read + if not queue.empty(): + output = queue.get() + return _b64_to_obj(output) + else: + return _get_cpu_info_from_cpuid_actual() + except: + pass + + # Return {} if everything failed + return {} + +def _get_cpu_info_from_proc_cpuinfo(): + ''' + Returns the CPU info gathered from /proc/cpuinfo. + Returns {} if /proc/cpuinfo is not found. + ''' + try: + # Just return {} if there is no cpuinfo + if not DataSource.has_proc_cpuinfo(): + return {} + + returncode, output = DataSource.cat_proc_cpuinfo() + if returncode != 0: + return {} + + # Various fields + vendor_id = _get_field(False, output, None, '', 'vendor_id', 'vendor id', 'vendor') + processor_brand = _get_field(True, output, None, None, 'model name','cpu', 'processor') + cache_size = _get_field(False, output, None, '', 'cache size') + stepping = _get_field(False, output, int, 0, 'stepping') + model = _get_field(False, output, int, 0, 'model') + family = _get_field(False, output, int, 0, 'cpu family') + hardware = _get_field(False, output, None, '', 'Hardware') + # MSinn Additions begin + revision = _get_field(False, output, None, '', 'Revision') + serial = _get_field(False, output, None, '', 'Serial') + # MSinn Additions end + # Flags + flags = _get_field(False, output, None, None, 'flags', 'Features') + if flags: + flags = flags.split() + flags.sort() + + # Check for other cache format + if not cache_size: + try: + for i in range(0, 10): + name = "cache{0}".format(i) + value = _get_field(False, output, None, None, name) + if value: + value = [entry.split('=') for entry in value.split(' ')] + value = dict(value) + if 'level' in value and value['level'] == '3' and 'size' in value: + cache_size = value['size'] + break + except Exception: + pass + + # Convert from MHz string to Hz + hz_actual = _get_field(False, output, None, '', 'cpu MHz', 'cpu speed', 'clock', 'cpu MHz dynamic', 'cpu MHz static') + hz_actual = hz_actual.lower().rstrip('mhz').strip() + hz_actual = _to_decimal_string(hz_actual) + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = (None, 0) + try: + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + except Exception: + pass + + info = { + 'hardware_raw' : hardware, + # MSinn Additions begin + 'revision_raw' : revision, + 'serial_raw' : serial, + # MSinn Additions end + 'brand_raw' : processor_brand, + + 'l3_cache_size' : _to_friendly_bytes(cache_size), + 'flags' : flags, + 'vendor_id_raw' : vendor_id, + 'stepping' : stepping, + 'model' : model, + 'family' : family, + } + + # Make the Hz the same for actual and advertised if missing any + if not hz_advertised or hz_advertised == '0.0': + hz_advertised = hz_actual + scale = 6 + elif not hz_actual or hz_actual == '0.0': + hz_actual = hz_advertised + + # Add the Hz if there is one + if _hz_short_to_full(hz_advertised, scale) > (0, 0): + info['hz_advertised_friendly'] = _hz_short_to_friendly(hz_advertised, scale) + info['hz_advertised'] = _hz_short_to_full(hz_advertised, scale) + if _hz_short_to_full(hz_actual, scale) > (0, 0): + info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, 6) + info['hz_actual'] = _hz_short_to_full(hz_actual, 6) + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_cpufreq_info(): + ''' + Returns the CPU info gathered from cpufreq-info. + Returns {} if cpufreq-info is not found. + ''' + try: + hz_brand, scale = '0.0', 0 + + if not DataSource.has_cpufreq_info(): + return {} + + returncode, output = DataSource.cpufreq_info() + if returncode != 0: + return {} + + hz_brand = output.split('current CPU frequency is')[1].split('\n')[0] + i = hz_brand.find('Hz') + assert(i != -1) + hz_brand = hz_brand[0 : i+2].strip().lower() + + if hz_brand.endswith('mhz'): + scale = 6 + elif hz_brand.endswith('ghz'): + scale = 9 + hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip() + hz_brand = _to_decimal_string(hz_brand) + + info = { + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_brand, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_brand, scale), + 'hz_advertised' : _hz_short_to_full(hz_brand, scale), + 'hz_actual' : _hz_short_to_full(hz_brand, scale), + } + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_lscpu(): + ''' + Returns the CPU info gathered from lscpu. + Returns {} if lscpu is not found. + ''' + try: + if not DataSource.has_lscpu(): + return {} + + returncode, output = DataSource.lscpu() + if returncode != 0: + return {} + + info = {} + + new_hz = _get_field(False, output, None, None, 'CPU max MHz', 'CPU MHz') + if new_hz: + new_hz = _to_decimal_string(new_hz) + scale = 6 + info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_advertised'] = _hz_short_to_full(new_hz, scale) + info['hz_actual'] = _hz_short_to_full(new_hz, scale) + + new_hz = _get_field(False, output, None, None, 'CPU dynamic MHz', 'CPU static MHz') + if new_hz: + new_hz = _to_decimal_string(new_hz) + scale = 6 + info['hz_advertised_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_actual_friendly'] = _hz_short_to_friendly(new_hz, scale) + info['hz_advertised'] = _hz_short_to_full(new_hz, scale) + info['hz_actual'] = _hz_short_to_full(new_hz, scale) + + vendor_id = _get_field(False, output, None, None, 'Vendor ID') + if vendor_id: + info['vendor_id_raw'] = vendor_id + + brand = _get_field(False, output, None, None, 'Model name') + if brand: + info['brand_raw'] = brand + + family = _get_field(False, output, None, None, 'CPU family') + if family and family.isdigit(): + info['family'] = int(family) + + stepping = _get_field(False, output, None, None, 'Stepping') + if stepping and stepping.isdigit(): + info['stepping'] = int(stepping) + + model = _get_field(False, output, None, None, 'Model') + if model and model.isdigit(): + info['model'] = int(model) + + l1_data_cache_size = _get_field(False, output, None, None, 'L1d cache') + if l1_data_cache_size: + info['l1_data_cache_size'] = _to_friendly_bytes(l1_data_cache_size) + + l1_instruction_cache_size = _get_field(False, output, None, None, 'L1i cache') + if l1_instruction_cache_size: + info['l1_instruction_cache_size'] = _to_friendly_bytes(l1_instruction_cache_size) + + l2_cache_size = _get_field(False, output, None, None, 'L2 cache', 'L2d cache') + if l2_cache_size: + info['l2_cache_size'] = _to_friendly_bytes(l2_cache_size) + + l3_cache_size = _get_field(False, output, None, None, 'L3 cache') + if l3_cache_size: + info['l3_cache_size'] = _to_friendly_bytes(l3_cache_size) + + # Flags + flags = _get_field(False, output, None, None, 'flags', 'Features') + if flags: + flags = flags.split() + flags.sort() + info['flags'] = flags + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_dmesg(): + ''' + Returns the CPU info gathered from dmesg. + Returns {} if dmesg is not found or does not have the desired info. + ''' + + # Just return {} if this arch has an unreliable dmesg log + arch, bits = _parse_arch(DataSource.arch_string_raw) + if arch in ['S390X']: + return {} + + # Just return {} if there is no dmesg + if not DataSource.has_dmesg(): + return {} + + # If dmesg fails return {} + returncode, output = DataSource.dmesg_a() + if output == None or returncode != 0: + return {} + + return _parse_dmesg_output(output) + + +# https://openpowerfoundation.org/wp-content/uploads/2016/05/LoPAPR_DRAFT_v11_24March2016_cmt1.pdf +# page 767 +def _get_cpu_info_from_ibm_pa_features(): + ''' + Returns the CPU info gathered from lsprop /proc/device-tree/cpus/*/ibm,pa-features + Returns {} if lsprop is not found or ibm,pa-features does not have the desired info. + ''' + try: + # Just return {} if there is no lsprop + if not DataSource.has_ibm_pa_features(): + return {} + + # If ibm,pa-features fails return {} + returncode, output = DataSource.ibm_pa_features() + if output == None or returncode != 0: + return {} + + # Filter out invalid characters from output + value = output.split("ibm,pa-features")[1].lower() + value = [s for s in value if s in list('0123456789abcfed')] + value = ''.join(value) + + # Get data converted to Uint32 chunks + left = int(value[0 : 8], 16) + right = int(value[8 : 16], 16) + + # Get the CPU flags + flags = { + # Byte 0 + 'mmu' : _is_bit_set(left, 0), + 'fpu' : _is_bit_set(left, 1), + 'slb' : _is_bit_set(left, 2), + 'run' : _is_bit_set(left, 3), + #'reserved' : _is_bit_set(left, 4), + 'dabr' : _is_bit_set(left, 5), + 'ne' : _is_bit_set(left, 6), + 'wtr' : _is_bit_set(left, 7), + + # Byte 1 + 'mcr' : _is_bit_set(left, 8), + 'dsisr' : _is_bit_set(left, 9), + 'lp' : _is_bit_set(left, 10), + 'ri' : _is_bit_set(left, 11), + 'dabrx' : _is_bit_set(left, 12), + 'sprg3' : _is_bit_set(left, 13), + 'rislb' : _is_bit_set(left, 14), + 'pp' : _is_bit_set(left, 15), + + # Byte 2 + 'vpm' : _is_bit_set(left, 16), + 'dss_2.05' : _is_bit_set(left, 17), + #'reserved' : _is_bit_set(left, 18), + 'dar' : _is_bit_set(left, 19), + #'reserved' : _is_bit_set(left, 20), + 'ppr' : _is_bit_set(left, 21), + 'dss_2.02' : _is_bit_set(left, 22), + 'dss_2.06' : _is_bit_set(left, 23), + + # Byte 3 + 'lsd_in_dscr' : _is_bit_set(left, 24), + 'ugr_in_dscr' : _is_bit_set(left, 25), + #'reserved' : _is_bit_set(left, 26), + #'reserved' : _is_bit_set(left, 27), + #'reserved' : _is_bit_set(left, 28), + #'reserved' : _is_bit_set(left, 29), + #'reserved' : _is_bit_set(left, 30), + #'reserved' : _is_bit_set(left, 31), + + # Byte 4 + 'sso_2.06' : _is_bit_set(right, 0), + #'reserved' : _is_bit_set(right, 1), + #'reserved' : _is_bit_set(right, 2), + #'reserved' : _is_bit_set(right, 3), + #'reserved' : _is_bit_set(right, 4), + #'reserved' : _is_bit_set(right, 5), + #'reserved' : _is_bit_set(right, 6), + #'reserved' : _is_bit_set(right, 7), + + # Byte 5 + 'le' : _is_bit_set(right, 8), + 'cfar' : _is_bit_set(right, 9), + 'eb' : _is_bit_set(right, 10), + 'lsq_2.07' : _is_bit_set(right, 11), + #'reserved' : _is_bit_set(right, 12), + #'reserved' : _is_bit_set(right, 13), + #'reserved' : _is_bit_set(right, 14), + #'reserved' : _is_bit_set(right, 15), + + # Byte 6 + 'dss_2.07' : _is_bit_set(right, 16), + #'reserved' : _is_bit_set(right, 17), + #'reserved' : _is_bit_set(right, 18), + #'reserved' : _is_bit_set(right, 19), + #'reserved' : _is_bit_set(right, 20), + #'reserved' : _is_bit_set(right, 21), + #'reserved' : _is_bit_set(right, 22), + #'reserved' : _is_bit_set(right, 23), + + # Byte 7 + #'reserved' : _is_bit_set(right, 24), + #'reserved' : _is_bit_set(right, 25), + #'reserved' : _is_bit_set(right, 26), + #'reserved' : _is_bit_set(right, 27), + #'reserved' : _is_bit_set(right, 28), + #'reserved' : _is_bit_set(right, 29), + #'reserved' : _is_bit_set(right, 30), + #'reserved' : _is_bit_set(right, 31), + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + flags.sort() + + info = { + 'flags' : flags + } + info = {k: v for k, v in info.items() if v} + + return info + except: + return {} + + +def _get_cpu_info_from_cat_var_run_dmesg_boot(): + ''' + Returns the CPU info gathered from /var/run/dmesg.boot. + Returns {} if dmesg is not found or does not have the desired info. + ''' + # Just return {} if there is no /var/run/dmesg.boot + if not DataSource.has_var_run_dmesg_boot(): + return {} + + # If dmesg.boot fails return {} + returncode, output = DataSource.cat_var_run_dmesg_boot() + if output == None or returncode != 0: + return {} + + return _parse_dmesg_output(output) + + +def _get_cpu_info_from_sysctl(): + ''' + Returns the CPU info gathered from sysctl. + Returns {} if sysctl is not found. + ''' + try: + # Just return {} if there is no sysctl + if not DataSource.has_sysctl(): + return {} + + # If sysctl fails return {} + returncode, output = DataSource.sysctl_machdep_cpu_hw_cpufrequency() + if output == None or returncode != 0: + return {} + + # Various fields + vendor_id = _get_field(False, output, None, None, 'machdep.cpu.vendor') + processor_brand = _get_field(True, output, None, None, 'machdep.cpu.brand_string') + cache_size = _get_field(False, output, None, None, 'machdep.cpu.cache.size') + stepping = _get_field(False, output, int, 0, 'machdep.cpu.stepping') + model = _get_field(False, output, int, 0, 'machdep.cpu.model') + family = _get_field(False, output, int, 0, 'machdep.cpu.family') + + # Flags + flags = _get_field(False, output, None, '', 'machdep.cpu.features').lower().split() + flags.extend(_get_field(False, output, None, '', 'machdep.cpu.leaf7_features').lower().split()) + flags.extend(_get_field(False, output, None, '', 'machdep.cpu.extfeatures').lower().split()) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + hz_actual = _get_field(False, output, None, None, 'hw.cpufrequency') + hz_actual = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'l2_cache_size' : _to_friendly_bytes(cache_size), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = {k: v for k, v in info.items() if v} + return info + except: + return {} + + +def _get_cpu_info_from_sysinfo(): + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + info = _get_cpu_info_from_sysinfo_v1() + info.update(_get_cpu_info_from_sysinfo_v2()) + return info + +def _get_cpu_info_from_sysinfo_v1(): + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + try: + # Just return {} if there is no sysinfo + if not DataSource.has_sysinfo(): + return {} + + # If sysinfo fails return {} + returncode, output = DataSource.sysinfo_cpu() + if output == None or returncode != 0: + return {} + + # Various fields + vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') + processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() + cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') + stepping = int(output.split(', stepping ')[1].split(',')[0].strip()) + model = int(output.split(', model ')[1].split(',')[0].strip()) + family = int(output.split(', family ')[1].split(',')[0].strip()) + + # Flags + flags = [] + for line in output.split('\n'): + if line.startswith('\t\t'): + for flag in line.strip().lower().split(): + flags.append(flag) + flags.sort() + + # Convert from GHz/MHz string to Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + hz_actual = hz_advertised + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, scale), + + 'l2_cache_size' : _to_friendly_bytes(cache_size), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_sysinfo_v2(): + ''' + Returns the CPU info gathered from sysinfo. + Returns {} if sysinfo is not found. + ''' + try: + # Just return {} if there is no sysinfo + if not DataSource.has_sysinfo(): + return {} + + # If sysinfo fails return {} + returncode, output = DataSource.sysinfo_cpu() + if output == None or returncode != 0: + return {} + + # Various fields + vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') + processor_brand = output.split('CPU #0: "')[1].split('"\n')[0].strip() + cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') + signature = output.split('Signature:')[1].split('\n')[0].strip() + # + stepping = int(signature.split('stepping ')[1].split(',')[0].strip()) + model = int(signature.split('model ')[1].split(',')[0].strip()) + family = int(signature.split('family ')[1].split(',')[0].strip()) + + # Flags + def get_subsection_flags(output): + retval = [] + for line in output.split('\n')[1:]: + if not line.startswith(' ') and not line.startswith(' '): break + for entry in line.strip().lower().split(' '): + retval.append(entry) + return retval + + flags = get_subsection_flags(output.split('Features: ')[1]) + \ + get_subsection_flags(output.split('Extended Features (0x00000001): ')[1]) + \ + get_subsection_flags(output.split('Extended Features (0x80000001): ')[1]) + flags.sort() + + # Convert from GHz/MHz string to Hz + lines = [n for n in output.split('\n') if n] + raw_hz = lines[0].split('running at ')[1].strip().lower() + hz_advertised = raw_hz.rstrip('mhz').rstrip('ghz').strip() + hz_advertised = _to_decimal_string(hz_advertised) + hz_actual = hz_advertised + + scale = 0 + if raw_hz.endswith('mhz'): + scale = 6 + elif raw_hz.endswith('ghz'): + scale = 9 + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, scale), + + 'l2_cache_size' : _to_friendly_bytes(cache_size), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_wmic(): + ''' + Returns the CPU info gathered from WMI. + Returns {} if not on Windows, or wmic is not installed. + ''' + + try: + # Just return {} if not Windows or there is no wmic + if not DataSource.is_windows or not DataSource.has_wmic(): + return {} + + returncode, output = DataSource.wmic_cpu() + if output == None or returncode != 0: + return {} + + # Break the list into key values pairs + value = output.split("\n") + value = [s.rstrip().split('=') for s in value if '=' in s] + value = {k: v for k, v in value if v} + + # Get the advertised MHz + processor_brand = value.get('Name') + hz_advertised, scale_advertised = _parse_cpu_brand_string(processor_brand) + + # Get the actual MHz + hz_actual = value.get('CurrentClockSpeed') + scale_actual = 6 + if hz_actual: + hz_actual = _to_decimal_string(hz_actual) + + # Get cache sizes + l2_cache_size = value.get('L2CacheSize') + if l2_cache_size: + l2_cache_size = l2_cache_size + ' KB' + + l3_cache_size = value.get('L3CacheSize') + if l3_cache_size: + l3_cache_size = l3_cache_size + ' KB' + + # Get family, model, and stepping + family, model, stepping = '', '', '' + description = value.get('Description') or value.get('Caption') + entries = description.split(' ') + + if 'Family' in entries and entries.index('Family') < len(entries)-1: + i = entries.index('Family') + family = int(entries[i + 1]) + + if 'Model' in entries and entries.index('Model') < len(entries)-1: + i = entries.index('Model') + model = int(entries[i + 1]) + + if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: + i = entries.index('Stepping') + stepping = int(entries[i + 1]) + + info = { + 'vendor_id_raw' : value.get('Manufacturer'), + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale_advertised), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, scale_actual), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale_advertised), + 'hz_actual' : _hz_short_to_full(hz_actual, scale_actual), + + 'l2_cache_size' : l2_cache_size, + 'l3_cache_size' : l3_cache_size, + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + } + + info = {k: v for k, v in info.items() if v} + return info + except: + #raise # NOTE: To have this throw on error, uncomment this line + return {} + +def _get_cpu_info_from_registry(): + ''' + FIXME: Is missing many of the newer CPU flags like sse3 + Returns the CPU info gathered from the Windows Registry. + Returns {} if not on Windows. + ''' + try: + # Just return {} if not on Windows + if not DataSource.is_windows: + return {} + + # Get the CPU name + processor_brand = DataSource.winreg_processor_brand().strip() + + # Get the CPU vendor id + vendor_id = DataSource.winreg_vendor_id_raw() + + # Get the CPU arch and bits + arch_string_raw = DataSource.winreg_arch_string_raw() + arch, bits = _parse_arch(arch_string_raw) + + # Get the actual CPU Hz + hz_actual = DataSource.winreg_hz_actual() + hz_actual = _to_decimal_string(hz_actual) + + # Get the advertised CPU Hz + hz_advertised, scale = _parse_cpu_brand_string(processor_brand) + + # If advertised hz not found, use the actual hz + if hz_advertised == '0.0': + scale = 6 + hz_advertised = _to_decimal_string(hz_actual) + + # Get the CPU features + feature_bits = DataSource.winreg_feature_bits() + + def is_set(bit): + mask = 0x80000000 >> bit + retval = mask & feature_bits > 0 + return retval + + # http://en.wikipedia.org/wiki/CPUID + # http://unix.stackexchange.com/questions/43539/what-do-the-flags-in-proc-cpuinfo-mean + # http://www.lohninger.com/helpcsuite/public_constants_cpuid.htm + flags = { + 'fpu' : is_set(0), # Floating Point Unit + 'vme' : is_set(1), # V86 Mode Extensions + 'de' : is_set(2), # Debug Extensions - I/O breakpoints supported + 'pse' : is_set(3), # Page Size Extensions (4 MB pages supported) + 'tsc' : is_set(4), # Time Stamp Counter and RDTSC instruction are available + 'msr' : is_set(5), # Model Specific Registers + 'pae' : is_set(6), # Physical Address Extensions (36 bit address, 2MB pages) + 'mce' : is_set(7), # Machine Check Exception supported + 'cx8' : is_set(8), # Compare Exchange Eight Byte instruction available + 'apic' : is_set(9), # Local APIC present (multiprocessor operation support) + 'sepamd' : is_set(10), # Fast system calls (AMD only) + 'sep' : is_set(11), # Fast system calls + 'mtrr' : is_set(12), # Memory Type Range Registers + 'pge' : is_set(13), # Page Global Enable + 'mca' : is_set(14), # Machine Check Architecture + 'cmov' : is_set(15), # Conditional MOVe instructions + 'pat' : is_set(16), # Page Attribute Table + 'pse36' : is_set(17), # 36 bit Page Size Extensions + 'serial' : is_set(18), # Processor Serial Number + 'clflush' : is_set(19), # Cache Flush + #'reserved1' : is_set(20), # reserved + 'dts' : is_set(21), # Debug Trace Store + 'acpi' : is_set(22), # ACPI support + 'mmx' : is_set(23), # MultiMedia Extensions + 'fxsr' : is_set(24), # FXSAVE and FXRSTOR instructions + 'sse' : is_set(25), # SSE instructions + 'sse2' : is_set(26), # SSE2 (WNI) instructions + 'ss' : is_set(27), # self snoop + #'reserved2' : is_set(28), # reserved + 'tm' : is_set(29), # Automatic clock control + 'ia64' : is_set(30), # IA64 instructions + '3dnow' : is_set(31) # 3DNow! instructions available + } + + # Get a list of only the flags that are true + flags = [k for k, v in flags.items() if v] + flags.sort() + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 6), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 6), + + 'flags' : flags + } + + info = {k: v for k, v in info.items() if v} + return info + except: + return {} + +def _get_cpu_info_from_kstat(): + ''' + Returns the CPU info gathered from isainfo and kstat. + Returns {} if isainfo or kstat are not found. + ''' + try: + # Just return {} if there is no isainfo or kstat + if not DataSource.has_isainfo() or not DataSource.has_kstat(): + return {} + + # If isainfo fails return {} + returncode, flag_output = DataSource.isainfo_vb() + if flag_output == None or returncode != 0: + return {} + + # If kstat fails return {} + returncode, kstat = DataSource.kstat_m_cpu_info() + if kstat == None or returncode != 0: + return {} + + # Various fields + vendor_id = kstat.split('\tvendor_id ')[1].split('\n')[0].strip() + processor_brand = kstat.split('\tbrand ')[1].split('\n')[0].strip() + stepping = int(kstat.split('\tstepping ')[1].split('\n')[0].strip()) + model = int(kstat.split('\tmodel ')[1].split('\n')[0].strip()) + family = int(kstat.split('\tfamily ')[1].split('\n')[0].strip()) + + # Flags + flags = flag_output.strip().split('\n')[-1].strip().lower().split() + flags.sort() + + # Convert from GHz/MHz string to Hz + scale = 6 + hz_advertised = kstat.split('\tclock_MHz ')[1].split('\n')[0].strip() + hz_advertised = _to_decimal_string(hz_advertised) + + # Convert from GHz/MHz string to Hz + hz_actual = kstat.split('\tcurrent_clock_Hz ')[1].split('\n')[0].strip() + hz_actual = _to_decimal_string(hz_actual) + + info = { + 'vendor_id_raw' : vendor_id, + 'brand_raw' : processor_brand, + + 'hz_advertised_friendly' : _hz_short_to_friendly(hz_advertised, scale), + 'hz_actual_friendly' : _hz_short_to_friendly(hz_actual, 0), + 'hz_advertised' : _hz_short_to_full(hz_advertised, scale), + 'hz_actual' : _hz_short_to_full(hz_actual, 0), + + 'stepping' : stepping, + 'model' : model, + 'family' : family, + 'flags' : flags + } + + info = {k: v for k, v in info.items() if v} + return info + except: + return {} + +def _get_cpu_info_from_platform_uname(): + try: + uname = DataSource.uname_string_raw.split(',')[0] + + family, model, stepping = (None, None, None) + entries = uname.split(' ') + + if 'Family' in entries and entries.index('Family') < len(entries)-1: + i = entries.index('Family') + family = int(entries[i + 1]) + + if 'Model' in entries and entries.index('Model') < len(entries)-1: + i = entries.index('Model') + model = int(entries[i + 1]) + + if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: + i = entries.index('Stepping') + stepping = int(entries[i + 1]) + + info = { + 'family' : family, + 'model' : model, + 'stepping' : stepping + } + info = {k: v for k, v in info.items() if v} + return info + except: + return {} + +def _get_cpu_info_internal(): + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns {} if nothing is found. + ''' + + # Get the CPU arch and bits + arch, bits = _parse_arch(DataSource.arch_string_raw) + + friendly_maxsize = { 2**31-1: '32 bit', 2**63-1: '64 bit' }.get(sys.maxsize) or 'unknown bits' + friendly_version = "{0}.{1}.{2}.{3}.{4}".format(*sys.version_info) + PYTHON_VERSION = "{0} ({1})".format(friendly_version, friendly_maxsize) + + info = { + 'python_version' : PYTHON_VERSION, + 'cpuinfo_version' : CPUINFO_VERSION, + 'cpuinfo_version_string' : CPUINFO_VERSION_STRING, + 'arch' : arch, + 'bits' : bits, + 'count' : DataSource.cpu_count, + 'arch_string_raw' : DataSource.arch_string_raw, + } + + # Try the Windows wmic + _copy_new_fields(info, _get_cpu_info_from_wmic()) + + # Try the Windows registry + _copy_new_fields(info, _get_cpu_info_from_registry()) + + # Try /proc/cpuinfo + _copy_new_fields(info, _get_cpu_info_from_proc_cpuinfo()) + + # Try cpufreq-info + _copy_new_fields(info, _get_cpu_info_from_cpufreq_info()) + + # Try LSCPU + _copy_new_fields(info, _get_cpu_info_from_lscpu()) + + # Try sysctl + _copy_new_fields(info, _get_cpu_info_from_sysctl()) + + # Try kstat + _copy_new_fields(info, _get_cpu_info_from_kstat()) + + # Try dmesg + _copy_new_fields(info, _get_cpu_info_from_dmesg()) + + # Try /var/run/dmesg.boot + _copy_new_fields(info, _get_cpu_info_from_cat_var_run_dmesg_boot()) + + # Try lsprop ibm,pa-features + _copy_new_fields(info, _get_cpu_info_from_ibm_pa_features()) + + # Try sysinfo + _copy_new_fields(info, _get_cpu_info_from_sysinfo()) + + # Try querying the CPU cpuid register + _copy_new_fields(info, _get_cpu_info_from_cpuid()) + + # Try platform.uname + _copy_new_fields(info, _get_cpu_info_from_platform_uname()) + + return info + +def get_cpu_info_json(): + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns the result in a json string + ''' + + import json + + output = None + + # If running under pyinstaller, run normally + if getattr(sys, 'frozen', False): + info = _get_cpu_info_internal() + output = json.dumps(info) + output = "{0}".format(output) + # if not running under pyinstaller, run in another process. + # This is done because multiprocesing has a design flaw that + # causes non main programs to run multiple times on Windows. + else: + from subprocess import Popen, PIPE + + command = [sys.executable, __file__, '--json'] + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + output = p1.communicate()[0] + + if p1.returncode != 0: + return "{}" + + if not IS_PY2: + output = output.decode(encoding='UTF-8') + + return output + +def get_cpu_info(): + ''' + Returns the CPU info by using the best sources of information for your OS. + Returns the result in a dict + ''' + + import json + + output = get_cpu_info_json() + + # Convert JSON to Python with non unicode strings + output = json.loads(output, object_hook = _utf_to_str) + + return output + +def main(): + from argparse import ArgumentParser + import json + + # Parse args + parser = ArgumentParser(description='Gets CPU info with pure Python 2 & 3') + parser.add_argument('--json', action='store_true', help='Return the info in JSON format') + parser.add_argument('--version', action='store_true', help='Return the version of py-cpuinfo') + args = parser.parse_args() + + try: + _check_arch() + except Exception as err: + sys.stderr.write(str(err) + "\n") + sys.exit(1) + + info = _get_cpu_info_internal() + + if not info: + sys.stderr.write("Failed to find cpu info\n") + sys.exit(1) + + if args.json: + from pprint import pprint + pprint(info) + #print(json.dumps(info)) + elif args.version: + print(CPUINFO_VERSION_STRING) + else: + print('Python Version: {0}'.format(info.get('python_version', ''))) + print('Cpuinfo Version: {0}'.format(info.get('cpuinfo_version_string', ''))) + print('Vendor ID Raw: {0}'.format(info.get('vendor_id_raw', ''))) + print('Hardware Raw: {0}'.format(info.get('hardware_raw', ''))) + # MSinn Additions begin + print('Revision Raw: {0}'.format(info.get('revision_raw', ''))) + print('Serial Raw: {0}'.format(info.get('serial_raw', ''))) + # MSinn Additions end + print('Brand Raw: {0}'.format(info.get('brand_raw', ''))) + print('Hz Advertised Friendly: {0}'.format(info.get('hz_advertised_friendly', ''))) + print('Hz Actual Friendly: {0}'.format(info.get('hz_actual_friendly', ''))) + print('Hz Advertised: {0}'.format(info.get('hz_advertised', ''))) + print('Hz Actual: {0}'.format(info.get('hz_actual', ''))) + print('Arch: {0}'.format(info.get('arch', ''))) + print('Bits: {0}'.format(info.get('bits', ''))) + print('Count: {0}'.format(info.get('count', ''))) + print('Arch String Raw: {0}'.format(info.get('arch_string_raw', ''))) + print('L1 Data Cache Size: {0}'.format(info.get('l1_data_cache_size', ''))) + print('L1 Instruction Cache Size: {0}'.format(info.get('l1_instruction_cache_size', ''))) + print('L2 Cache Size: {0}'.format(info.get('l2_cache_size', ''))) + print('L2 Cache Line Size: {0}'.format(info.get('l2_cache_line_size', ''))) + print('L2 Cache Associativity: {0}'.format(info.get('l2_cache_associativity', ''))) + print('L3 Cache Size: {0}'.format(info.get('l3_cache_size', ''))) + print('Stepping: {0}'.format(info.get('stepping', ''))) + print('Model: {0}'.format(info.get('model', ''))) + print('Family: {0}'.format(info.get('family', ''))) + print('Processor Type: {0}'.format(info.get('processor_type', ''))) + print('Extended Model: {0}'.format(info.get('extended_model', ''))) + print('Extended Family: {0}'.format(info.get('extended_family', ''))) + print('Flags: {0}'.format(', '.join(info.get('flags', '')))) + + +if __name__ == '__main__': + main() +else: + _check_arch() diff --git a/lib/daemon.py b/lib/daemon.py index 46b0da2c80..6cbb71c428 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -198,7 +198,8 @@ def kill(pidfile, waittime=15, pid0_warning=True): pid = read_pidfile(pidfile) if pid == 0 and pid0_warning: - logger.error("A Process ID of 0 can not be killed, please kill SmartHomeNG manually") + logger.error("SmartHomeNG cannot run with a process ID of 0, probably no instance of SmartHomeNG running otherwise kill SmartHomeNG manually") + return if psutil.pid_exists(pid): logger.warning("Stopping SmartHomeNG, please wait...") p = psutil.Process(pid) @@ -217,3 +218,4 @@ def kill(pidfile, waittime=15, pid0_warning=True): pass elif pid != 0: logger.warning("No instance of SmartHomeNG running") + return diff --git a/lib/item/item.py b/lib/item/item.py index f310dfecd7..ac9e4a366e 100644 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -127,8 +127,8 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self._trigger_condition = None self._on_update = None # -> KEY_ON_UPDATE eval expression self._on_change = None # -> KEY_ON_CHANGE eval expression - self._on_update_dest_var = None # -> KEY_ON_UPDATE destination var - self._on_change_dest_var = None # -> KEY_ON_CHANGE destination var + self._on_update_dest_var = None # -> KEY_ON_UPDATE destination var (list: only filled if '=' syntax is used) + self._on_change_dest_var = None # -> KEY_ON_CHANGE destination var (list: only filled if '=' syntax is used) self._on_update_unexpanded = [] # -> KEY_ON_UPDATE eval expression (with unexpanded item references) self._on_change_unexpanded = [] # -> KEY_ON_CHANGE eval expression (with unexpanded item references) self._on_update_dest_var_unexp = [] # -> KEY_ON_UPDATE destination var (with unexpanded item reference) @@ -341,8 +341,9 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): # Test if the plugin-specific attribute contains a valid value # and set the default value, if needed - value = self.plugins.meta.check_itemattribute(self, attr.split('@')[0], value, self._filename) - self.conf[attr] = value + if hasattr(self.plugins, 'meta'): + value = self.plugins.meta.check_itemattribute(self, attr.split('@')[0], value, self._filename) + self.conf[attr] = value self.property.init_dynamic_properties() @@ -398,7 +399,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): # Cache ############################################################# if self._cache: - self._cache = self._sh._cache_dir + self._path + self._cache = os.path.join(self._sh._cache_dir, self._path) try: self.__changed_by = 'Init:Cache' self.__last_change, self._value = cache_read(self._cache, self.shtime.tzinfo()) @@ -412,9 +413,13 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self._log_on_change(self._value, self.__changed_by, 'Cache', None) except Exception as e: if str(e).startswith('[Errno 2]'): - logger.info("Item {}: No cached value: {}".format(self._path, e)) + logger.info(f"Item {self._path}: No cached value: {e}") else: - logger.warning("Item {}: Problem reading cache: {}".format(self._path, e)) + if os.stat(self._cache).st_size == 0: + logger.warning(f"Item {self._path}: Problem reading cache: Filesize is 0 bytes. Deleting invalid cache file") + os.remove(self._cache) + else: + logger.warning(f"Item {self._path}: Problem reading cache: {e}") ############################################################# # Cache write/init @@ -422,7 +427,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): if self._cache: if not os.path.isfile(self._cache): cache_write(self._cache, self._value) - logger.notice("Created cache for item: {} in file {}".format(self._cache, self._cache)) + logger.notice(f"Created cache for item {self._cache} in file {self._cache}") ############################################################# # Plugins @@ -433,7 +438,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): update = plugin.parse_item(self) if update: try: - plugin._append_to_itemlist(self) + plugin.add_item(self, updating=True) except: pass self.add_method_trigger(update) @@ -592,7 +597,7 @@ def _process_eval(self, value): else: self._eval_unexpanded = value value = self.get_stringwithabsolutepathes(value, 'sh.', '(', KEY_EVAL) - value = self.get_stringwithabsolutepathes(value, 'sh.', '.property', KEY_EVAL) + #value = self.get_stringwithabsolutepathes(value, 'sh.', '.property', KEY_EVAL) self._eval = value @@ -618,13 +623,16 @@ def _process_on_xx_list(self, attr, value): for val in value: # separate destination item (if it exists) dest_item, val = self._split_destitem_from_value(val) - dest_var_list_unexp.append(dest_item) + dest_item = dest_item.strip() + if dest_item.startswith('sh.'): + dest_item = dest_item[3:] + dest_var_list_unexp.append(dest_item.strip()) # expand relative item paths - dest_item = self.get_absolutepath(dest_item, KEY_ON_CHANGE).strip() + dest_item = self.get_absolutepath(dest_item.strip()).strip() # val = 'sh.'+dest_item+'( '+ self.get_stringwithabsolutepathes(val, 'sh.', '(', KEY_ON_CHANGE) +' )' val_list_unexpanded.append(val) val = self.get_stringwithabsolutepathes(val, 'sh.', '(', KEY_ON_CHANGE) - val = self.get_stringwithabsolutepathes(val, 'sh.', '.property', KEY_ON_CHANGE) + #val = self.get_stringwithabsolutepathes(val, 'sh.', '.property', KEY_ON_CHANGE) # logger.warning("Item __init__: {}: for attr '{}', dest_item '{}', val '{}'".format(self._path, attr, dest_item, val)) val_list.append(val) dest_var_list.append(dest_item) @@ -633,7 +641,7 @@ def _process_on_xx_list(self, attr, value): setattr(self, '_' + attr + '_dest_var', dest_var_list) setattr(self, '_' + attr + '_dest_var_unexp', dest_var_list_unexp) return - +#### ms def _get_last_change(self): return self.__last_change @@ -996,6 +1004,7 @@ def get_stringwithabsolutepathes(self, evalstr, begintag, endtag, attribute=''): :return: string with the statement containing absolute item paths """ def __checkfortags(evalstr, begintag, endtag): + pref = '' rest = evalstr while (rest.find(begintag+'.') != -1): @@ -1008,11 +1017,15 @@ def __checkfortags(evalstr, begintag, endtag): rel = rest[:rest.find(endtag)] rest = rest[rest.find(endtag):] pref += self.get_absolutepath(rel, attribute) + # Re-combine string for next loop + rest = pref+rest + pref = '' pref += rest logger.debug("{}.get_stringwithabsolutepathes('{}') with begintag = '{}', endtag = '{}': result = '{}'".format( self._path, evalstr, begintag, endtag, pref)) - return pref + return pref # end of __checkfortags(...) + if not isinstance(evalstr, str): return evalstr @@ -1106,7 +1119,7 @@ def _build_trigger_condition_eval(self, trigger_condition): # expand relative item paths wrk = self.get_stringwithabsolutepathes(wrk, 'sh.', '(', KEY_CONDITION) - wrk = self.get_stringwithabsolutepathes(wrk, 'sh.', '.property', KEY_CONDITION) + #wrk = self.get_stringwithabsolutepathes(wrk, 'sh.', '.property', KEY_CONDITION) and_cond.append(wrk) diff --git a/lib/log.py b/lib/log.py index a3bd820620..5739bb31a3 100644 --- a/lib/log.py +++ b/lib/log.py @@ -37,6 +37,10 @@ class Logs(): _logs = {} root_handler_name = '' + NOTICE_level = 29 + DBGHIGH_level = 13 + DBGMED_level = 12 + DBGLOW_level = 11 def __init__(self, sh): @@ -91,15 +95,18 @@ def configure_logging(self, config_dict, config_filename='logging.yaml'): root_handler_level = '?' if root_handler_level.upper() in ['NOTICE', 'INFO', 'DEBUG']: - notice_level = 29 + self.NOTICE_level = 29 else: - notice_level = 31 + self.NOTICE_level = 31 + self.DBGHIGH_level = 13 + self.DBGMED_level = 12 + self.DBGLOW_level = 11 # add SmartHomeNG specific loglevels - self.add_logging_level('NOTICE', notice_level) - self.add_logging_level('DBGHIGH', 13) - self.add_logging_level('DBGMED', 12) - self.add_logging_level('DBGLOW', 11) + self.add_logging_level('NOTICE', self.NOTICE_level) + self.add_logging_level('DBGHIGH', self.DBGHIGH_level) + self.add_logging_level('DBGMED', self.DBGMED_level) + self.add_logging_level('DBGLOW', self.DBGLOW_level) try: logging.config.dictConfig(config_dict) diff --git a/lib/metadata.py b/lib/metadata.py index e4422eaec1..5fc9956324 100644 --- a/lib/metadata.py +++ b/lib/metadata.py @@ -30,6 +30,7 @@ from lib.constants import (YAML_FILE, FOO, META_DATA_TYPES, META_DATA_DEFAULTS) META_MODULE_PARAMETER_SECTION = 'parameters' +META_PLUGIN_SECTION = 'plugin' META_PLUGIN_PARAMETER_SECTION = 'parameters' META_PLUGIN_ITEMATTRIBUTE_SECTION = 'item_attributes' META_PLUGIN_ITEMATTRIBUTEPREFIX_SECTION = 'item_attribute_prefixes' @@ -125,7 +126,13 @@ def __init__(self, sh, addon_name, addon_type, classpath=''): self.parameters = self.meta.get(META_MODULE_PARAMETER_SECTION) self.itemstructs = self.meta.get(META_STRUCT_SECTION) else: - self.parameters = self.meta.get(META_PLUGIN_PARAMETER_SECTION) + self.global_parameters = self.get_global_plugin_parameters() + self.parameters = self.meta.get(META_PLUGIN_PARAMETER_SECTION, {}) + if isinstance(self.parameters, str): + # if plugin parameter section is declared as NONE + self.parameters = self.global_parameters + else: + self.parameters.update(self.global_parameters) self.itemdefinitions = self.meta.get(META_PLUGIN_ITEMATTRIBUTE_SECTION) self.itemprefixdefinitions = self.meta.get(META_PLUGIN_ITEMATTRIBUTEPREFIX_SECTION) self.itemstructs = self.meta.get(META_STRUCT_SECTION) @@ -249,6 +256,38 @@ def __init__(self, sh, addon_name, addon_type, classpath=''): return + def get_global_plugin_parameters(self): + + result = {} + + if self._sh.modules.get_module('http') is not None: + # only if http module is loaded: + # global plugin parameter 'webif_pagelength' + result['webif_pagelength'] = {} + result['webif_pagelength']['type'] = 'int' + result['webif_pagelength']['valid_list'] = [-1, 0, 25, 50, 100] + result['webif_pagelength']['description'] = {} + # get description of webif_pagelength-parameter in all available laguages + result['webif_pagelength']['description'] = self._sh.modules.get_module('http')._metadata.meta['parameters']['webif_pagelength'].get('description', {'en': 'No description found!'}) + try: + result['webif_pagelength']['default'] = self._sh.modules.get_module('http')._webif_pagelength + except: + result['webif_pagelength']['default'] = 0 + + self.pluginsettings = self.meta.get(META_PLUGIN_SECTION) + if self.pluginsettings.get('multi_instance', False): + # only for multi-instance Plugins: + # global plugin parameter 'instance' + result['instance'] = {} + result['instance']['type'] = 'str' + result['instance']['description'] = {} + result['instance']['description']['de'] = "Falls mehrere Instanzen eines Multi-Instance Plugins konfiguriert sind, muss hier ein eindeutiger Instanz-Name angegeben werden (eine Instanz darf ohnen Namen bleiben). Falls nur eine Instanz konfiguriert ist, sollte hier kein Name vergeben werden." + result['instance']['description']['en'] = "If several instances of a multi-instance plugin are configured, a unique instance name must be specified here (one instance may remain without a name). If only one instance is configured, no name should be assigned here." + result['instance']['description']['fr'] = "Si plusieurs instances d'un plug-in multi-instance sont configurées, un nom d'instance unique doit être spécifié ici (une instance peut rester sans nom). Si une seule instance est configurée, aucun nom ne doit être attribué ici." + + return result + + def get_plugin_function_defstrings(self, with_type=False, with_default=True): """ Build the documentation strings of the plugin's functions diff --git a/lib/model/smartplugin.py b/lib/model/smartplugin.py index 1e143e7636..3510d3d522 100644 --- a/lib/model/smartplugin.py +++ b/lib/model/smartplugin.py @@ -20,8 +20,6 @@ # along with SmartHomeNG If not, see . ######################################################################### -#import lib.scheduler - from lib.model.smartobject import SmartObject from lib.shtime import Shtime @@ -35,12 +33,13 @@ class SmartPlugin(SmartObject, Utils): """ - The class SmartPlugin implements the base class of call smart-plugins. + The class SmartPlugin implements the base class of all smart-plugins. The implemented methods are described below. - In adition the methods implemented in lib.utils.Utils are inhereted. + In addition the methods implemented in lib.utils.Utils are inherited. """ + PLUGIN_VERSION = '' ALLOW_MULTIINSTANCE = None __instance = '' #: Name of this instance of the plugin @@ -51,44 +50,303 @@ class SmartPlugin(SmartObject, Utils): _classname = '' #: Classname of the plugin; is initialized during loading of the plugin; :Warning: Don't change it shtime = None #: Variable containing a pointer to the SmartHomeNG time handling object; is initialized during loading of the plugin; :Warning: Don't change it + _stop_on_item_change = True # Plugin needs to be stopped on/before item changes + # needed by self.remove_item(), don't change unless you know how and why + _pluginname_prefix = 'plugins.' - _itemlist = [] # List of items, that trigger update methods of this plugin (filled by lib.item); :Warning: Don't change! + _plg_item_dict = {} # dict to hold the items assigned to the plugin and their plugin specific information + _item_lookup_dict = {} # dict for the reverse lookup from a mapping (device-command or matchstring) to an item, + # contains a list of items for each mapping _add_translation = None - _parameters = {} # Dict for storing the configuration parameters read from /etc/plugin.yaml + _parameters = {} # Dict for storing the configuration parameters read from /etc/plugin.yaml + _hide_parameters = {} # Dict for storing parameters to hide from AdminUI logger = logging.getLogger(__name__) alive = False - # Initialization of SmartPlugin class called by super().__init__() from the plugin's __init__() method def __init__(self, **kwargs): - pass + self._plg_item_dict = {} # make sure, that the dict is local to the plugin + self._item_lookup_dict = {} # make sure, that the dict is local to the plugin - def _append_to_itemlist(self, item): - self._itemlist.append(item) + def deinit(self, items=[]): + """ + If the Plugin needs special code to be executed before it is unloaded, this method + has to be overwritten with the code needed for de-initialization + If called without parameters, all registered items are unregistered. + items is a list of items (or a single Item() object). + """ + if self.alive: + self.stop() - def _get_itemlist(self): - return self._itemlist + if not items: + items = self.get_item_list() + elif not isinstance(items, list): + items = [items] + for item in items: + self.remove_item(item) - def deinit(self): + def add_item(self, item, config_data_dict={}, mapping=None, updating=False): """ - If the Plugin needs special code to be executed before it is unloaded, this method - has to be overwirtten with the code needed for de-initialization + For items that are used/handled by a plugin, this method stores the configuration information + that is individual for the plugin. The configuration information is/has to be stored in a dictionary + + The configuration information can be retrieved later by a call to the method get_item_configdata() + + If data is beeing received by the plugin, a mapping ( a 'device-command' or matchstring) has to be specified + as an optional 3rd parameter. This allows a reverse lookup. The method get_itemlist_for_mapping() + returns a list of items for the items that have defined the . In most cases, the list will have + only one entry, but if multiple items should receive data from the same device (or command), the list can + have more than one entry. + + Calling this method for an item already stored in `self._plg_item_dict` can be used to change the "is_updating" + key to True, if it was False before and the `updating` parameter is True. Otherwise, nothing happens. + + This method should be called from parse_item to register the item. If parse_item returns a reference to `update_item`, + this method is called again by the Item instance itself to change the `is_updating` key. + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :param item: item + :param config_data_dict: Dictionary with the plugin-specific configuration information for the item + :param mapping: String identifing the origin (source/kind) of received data (e.g. the address on a bus) + :param updating: Show if item updates from shng should be sent to the plugin + :type item: Item + :type config_data_dict: dict + :type mapping: str + :type updating: bool + + :return: True, if the information has been added + :rtype: bool + """ + if item.path() in self._plg_item_dict: + + # if called again (e.g. from lib/item/item.py) with updating == True, + # update "is_updating" key... + if updating: + self.logger.debug(f"add_item called with existing item {item.path()}, updating stored data: is_updating enabled") + self.register_updating(item) + return True + + # otherwise return error + self.logger.warning(f"Trying to add an existing item: {item.path()}") + return False + + self._plg_item_dict[item.path()] = { + 'item': item, + 'is_updating': updating, + 'mapping': mapping, + 'config_data': dict(config_data_dict) + } + + if mapping: + if mapping not in self._item_lookup_dict: + self._item_lookup_dict[mapping] = [] + self._item_lookup_dict[mapping].append(item) + + return True + + def remove_item(self, item): + """ + Remove configuration data for an item (and remove the item from the mapping's list + + :param item: item to remove + :type item: Item + + :return: True, if the information has been removed + :rtype: bool + """ + if item.path() not in self._plg_item_dict: + # There is no information stored for that item + self.logger.debug(f'item {item.path()} not associated to this plugin, doing nothing') + return False + + # check if plugin is running + if self.alive: + if self._stop_on_item_change: + self.logger.debug(f'stopping plugin for removal of item {item.path()}') + self.stop() + else: + self.logger.debug(f'not stopping plugin for removal of item {item.path()}') + + # remove data from item_dict early in case of concurrent actions + data = self._plg_item_dict[item.path()] + del self._plg_item_dict[item.path()] + + # remove item from self._item_lookup_dict if present + mapping = data.get('mapping') + if mapping: + # if a mapping was given for the item, the item is being removed from the list of the mapping + if item in self._item_lookup_dict[mapping]: + self._item_lookup_dict[mapping].remove(item) + + # unregister item update method + self.unparse_item(item) + + return True + + def update_item(self, item, caller=None, source=None, dest=None): + """ + Item has been updated + + This method is called, if the value of an item has been updated by + SmartHomeNG. It should write the changed value out to the device + (hardware/interface) that is managed by this plugin. + + Method must be overwritten to be functional. + + :param item: item to be updated towards the plugin + :param caller: if given it represents the callers name + :param source: if given it represents the source + :param dest: if given it represents the dest """ pass + def register_updating(self, item): + """ + Mark item in self._plg_item_dict as registered in shng for updating + (usually done by returning self.update_item from self.parse_item) + + NOTE: Items are added to _plg_item_dict by the item class as updating + by default. This could only be used if items were added manually + as non-updating first. Registering them as updating usually only + occurs via parse_item(), which in turn makes the item class + add the item as updating. + + :param item: item object + :type item: item + """ + if item.path() not in self._plg_item_dict: + self.add_item(item) + self._plg_item_dict[item.path()]['is_updating'] = True + + def get_item_config(self, item): + """ + Returns the plugin-specific configuration information (config_data_dict) for the given item + + :param item: item or item_path (str) to get config info for + :type item: item object or str + + :return: dict with the configuration information for the given item + :rtype: dict + """ + if isinstance(item, str): + item_path = item + else: + item_path = item.path() + return self._plg_item_dict[item_path].get('config_data') + + def get_item_mapping(self, item): + """ + Returns the plugin-specific mapping that was defined by add_item() + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :param item: item or item_path (str) to get config info for + :type item: item object or str + + :return: mapping string for that item + :rtype: str + """ + if isinstance(item, str): + item_path = item + else: + item_path = item.path() + return self._plg_item_dict[item_path].get('mapping') + + def get_item_path_list(self, filter_key=None, filter_value=None): + """ + Return list of stored item paths used by this plugin + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :param filter_key: key of the configdata dict used to filter + :param filter_value: value for filtering item_path_list + :type filter_key: str + :type filter_value: any + + :return: List of item pathes + :rtype: list(str) + """ + if filter_key is None or filter_value is None: + return self._plg_item_dict.keys() + + return [item_path for item_path in list(self._plg_item_dict.keys()) if self._plg_item_dict[item_path]['config_data'].get(filter_key, None) == filter_value] + + def get_item_list(self, filter_key=None, filter_value=None): + """ + Return list of stored items used by this plugin + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :param filter_key: key of the configdata dict used to filter + :param filter_value: value for filtering item_path_list + :type filter_key: str + :type filter_value: any + + :return: List of item objects + :rtype: list(item) + """ + if filter_key is None or filter_value is None: + return [self._plg_item_dict[item_path]['item'] for item_path in list(self._plg_item_dict.keys())] + + return [self._plg_item_dict[item_path]['item'] for item_path in list(self._plg_item_dict.keys()) if self._plg_item_dict[item_path]['config_data'].get(filter_key, None) == filter_value] + + def get_trigger_items(self): + """ + Return list of stored items which were marked as updating + + Only available in SmartHomeNG versions **v1.9.4 and up**. + """ + return [self._plg_item_dict[item_path]['item'] for item_path in self._plg_item_dict if self._plg_item_dict[item_path]['is_updating']] + + def get_items_for_mapping(self, mapping): + """ + Returns a list of items that should receive data for the given mapping + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :param mapping: mapping, for which the receiving items should be returned + :type mapping: str + + :return: List of items + :rtype: list + """ + return self._item_lookup_dict.get(mapping, []) + + def get_mappings(self): + """ + Returns a list containing all mappings, which have items associated with it + + Only available in SmartHomeNG versions **v1.9.4 and up**. + + :return: List of mappings + :rtype: list + """ + return list(self._item_lookup_dict.keys()) + + def unparse_item(self, item): + """ + Ensure that changes to are no longer propagated to this plugin + + :param item: item to unparse + :type item: class Item + """ + try: + item.remove_method_trigger(self.update_item) + return True + except Exception: + return False def get_configname(self): """ return the name of the plugin instance as defined in plugin.yaml (section name) - :note: Only available in SmartHomeNG versions **beyond** v1.4 - :return: name of the plugin instance as defined in plugin.yaml :rtype: str """ @@ -101,8 +359,6 @@ def _set_configname(self, configname): :Note: Usually **you don't need to call this method**, since it is called during loading of the plugin - :note: Only available in SmartHomeNG versions **beyond** v1.4 - :param configname: name of the plugin instance as defined in plugin.yaml :type configname: str """ @@ -113,8 +369,6 @@ def get_shortname(self): """ return the shortname of the plugin (name of it's directory) - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :return: shortname of the plugin :rtype: str """ @@ -127,8 +381,6 @@ def _set_shortname(self, shortname): :Note: Usually **you don't need to call this method**, since it is called during loading of the plugin - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :param shortname: short name of the plugin (name of it's directory) :type shortname: str """ @@ -157,31 +409,26 @@ def _set_instance_name(self, instance): if hasattr(self, 'ALLOW_MULTIINSTANCE') and self.ALLOW_MULTIINSTANCE: self.__instance = instance elif hasattr(self, 'ALLOW_MULTIINSTANCE') and not self.ALLOW_MULTIINSTANCE: - self.logger.warning("Plugin '{}': Only multi-instance capable plugins allow setting a name for an instance".format(self.get_shortname())) + self.logger.warning(f"Plugin '{self.get_shortname()}': Only multi-instance capable plugins allow setting a name for an instance") def get_fullname(self): """ return the full name of the plugin (shortname & instancename) - :note: Only available in SmartHomeNG versions v1.3c and up - :return: full name of the plugin :rtype: str """ if self.get_instance_name() == '': return self.get_shortname() else: -# return self.get_instance_name() + '@' + self.get_shortname() - return self.get_shortname() + '_' + self.get_instance_name() + return self.get_shortname() + '_' + self.get_instance_name() def get_classname(self): """ return the classname of the plugin - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :return: classname of the plugin :rtype: str """ @@ -194,8 +441,6 @@ def _set_classname(self, classname): :Note: Usually **you don't need to call this method**, since it is called during loading of the plugin - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :param classname: name of the plugin's class :type classname: str """ @@ -228,7 +473,7 @@ def _set_multi_instance_capable(self, mi): :return: True, if success or ALLOW_MULTIINSTANCE == mi :rtype: bool """ - if hasattr(self, 'ALLOW_MULTIINSTANCE') and (self.ALLOW_MULTIINSTANCE != None): + if hasattr(self, 'ALLOW_MULTIINSTANCE') and (self.ALLOW_MULTIINSTANCE is not None): return (self.ALLOW_MULTIINSTANCE == mi) else: self.ALLOW_MULTIINSTANCE = mi @@ -252,8 +497,6 @@ def get_plugin_dir(self): """ return the directory where the pluing files are stored in - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :return: name of the directory :rtype: str """ @@ -267,8 +510,6 @@ def _set_plugin_dir(self, dir): :Note: Usually **you don't need to call this method**, since it is called during loading of the plugin by PluginWrapper - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :param dir: name of the directory where the plugin resides in :type dir: str """ @@ -282,7 +523,7 @@ def get_info(self): :return: plugin Info :rtype: str """ - return "Plugin: '{0}.{1}', Version: '{2}', Instance: '{3}'".format(self.__module__, self.__class__.__name__, self.get_version(),self.get_instance_name()) + return f"Plugin: '{self.get_shortname()}.{self.__class__.__name__}', Version: '{self.get_version()}', Instance: '{self.get_instance_name()}'" def get_parameter_value(self, parameter_name): @@ -339,20 +580,20 @@ def update_config_section(self, param_dict): """ Update the config section of ../etc/plugin.yaml - :param param_dict: dict with the pareters that should be updated + :param param_dict: dict with the parameters that should be updated :return: """ param_names = list(self.metadata.parameters.keys()) - self.logger.debug("update_config_section: Beginning to update section '{}' of ../etc/plugin.yaml".format(self._configname)) - self.logger.debug("update_config_section: valid parameter names to update = {}".format(param_names)) - self.logger.info("update_config_section: Config file = '{}', update data = {}".format(self._configfilename, param_dict)) + self.logger.debug(f"update_config_section: Beginning to update section '{self._configname}' of ../etc/plugin.yaml") + self.logger.debug(f"update_config_section: valid parameter names to update = {param_names}") + self.logger.info(f"update_config_section: Config file = '{self._configfilename}', update data = {param_dict}") # read plugin.yaml plugin_conf = shyaml.yaml_load_roundtrip(self._configfilename) sect = plugin_conf.get(self._configname) if sect is None: - self.logger.error("update_config_section: Config section '{}' not found in ../etc/plugin.yaml".format(self._configname)) + self.logger.error(f"update_config_section: Config section '{self._configname}' not found in ../etc/plugin.yaml") return parameters_changed = False @@ -369,13 +610,11 @@ def update_config_section(self, param_dict): else: try: this_param = int(float(param_dict[param])) - #this_param = int(param_dict[param]) - except: - self.logger.error(f"update_config_section: Parameter {param} -> Cannot convert '{param_dict[param]}' to type 'int'" ) - #self.logger.warning("update_config_section: Changing Parameter '{}' -> type = '{}' from '{}' to '{}'".format(param, self.metadata.parameters[param]['type'], sect.get(param, None), this_param)) + except ValueError: + self.logger.error(f"update_config_section: Parameter {param} -> Cannot convert '{param_dict[param]}' to type 'int'") else: this_param = param_dict[param] - self.logger.info("update_config_section: Changing Parameter '{}' -> type = '{}' from '{}' to '{}'".format(param, self.metadata.parameters[param]['type'], sect.get(param, None), this_param)) + self.logger.info(f"update_config_section: Changing Parameter '{param}' -> type = '{self.metadata.parameters[param]['type']}' from '{sect.get(param, None)}' to '{this_param}'") if param_dict[param] == '' or param_dict[param] == {} or param_dict[param] == []: try: del sect[param] @@ -385,32 +624,15 @@ def update_config_section(self, param_dict): sect[param] = this_param parameters_changed = True else: - self.logger.error("update_config_section: Invalid parameter '{}' specified for update".format(param, param_dict[param])) + self.logger.error(f"update_config_section: Invalid parameter '{param}' specified for update") - self.logger.debug("update_config_section: Config section content = '{}'".format(sect)) + self.logger.debug(f"update_config_section: Config section content = '{sect}'") # write plugin.yaml if parameters_changed: shyaml.yaml_save_roundtrip(self._configfilename, plugin_conf, True) - self.logger.debug("update_config_section: Finished updating section '{}' of ../etc/plugin.yaml".format(self._configname)) + self.logger.debug(f"update_config_section: Finished updating section '{self._configname}' of ../etc/plugin.yaml") return -#--- -#plugin_conf = shyaml.yaml_load_roundtrip(config_filename) -#sect = plugin_conf.get(id) -#if sect is None: -# response = {'result': 'error', 'description': "Configuration section '{}' does not exist".format(id)} -#else: -# self.logger.debug("update: params = {}".format(params)) -# if params.get('config', {}).get('plugin_enabled', None) == True: -# del params['config']['plugin_enabled'] -# plugin_conf[id] = params.get('config', {}) -# shyaml.yaml_save_roundtrip(config_filename, plugin_conf, False) -# response = {'result': 'ok'} - - -#--- - - def get_loginstance(self): """ Returns a prefix for logmessages of multi instance capable plugins. @@ -430,7 +652,7 @@ def get_loginstance(self): if self.__instance == '': return '' else: - return self.__instance+'@: ' + return self.__instance + '@: ' def __get_iattr(self, attr): @@ -447,7 +669,7 @@ def __get_iattr(self, attr): if self.__instance == '': return attr else: - return "%s@%s"%(attr, self.__instance) + return f"{attr}@{self.__instance}" def __get_iattr_conf(self, conf, attr): @@ -466,8 +688,8 @@ def __get_iattr_conf(self, conf, attr): __attr = self.__get_iattr(attr) if __attr in conf: return __attr - elif "%s@*"%attr in conf: - return "%s@*"%attr + elif f"{attr}@*" in conf: + return f"{attr}@*" return None @@ -525,9 +747,9 @@ def __new__(cls, *args, **kargs): It tests, if PLUGIN_VERSION is defined. """ - if not hasattr(cls,'PLUGIN_VERSION'): + if not hasattr(cls, 'PLUGIN_VERSION'): raise NotImplementedError("'Plugin' subclasses should have a 'PLUGIN_VERSION' attribute") - return SmartObject.__new__(cls,*args,**kargs) + return SmartObject.__new__(cls, *args, **kargs) def get_sh(self): @@ -535,8 +757,6 @@ def get_sh(self): Return the main object of smarthomeNG (usually refered to as **smarthome** or **sh**) You can reference the main object of SmartHomeNG by using self.get_sh() in your plugin - :note: Only available in SmartHomeNG versions **beyond** v1.3 - :return: the main object of smarthomeNG (usually refered to as **smarthome** or **sh**) :rtype: object """ @@ -565,12 +785,12 @@ def get_module(self, modulename): """ try: mymod = Modules.get_instance().get_module(modulename) - except: - mymod = None - if mymod == None: - self.logger.error("Module '{}' not loaded".format(modulename)) + except Exception: + mymod = None + if mymod is None: + self.logger.error(f"Module '{modulename}' not loaded") else: - self.logger.info("Using module '{}'".format(str( mymod._shortname ) ) ) + self.logger.info(f"Using module '{str(mymod._shortname)}'") return mymod @@ -578,7 +798,7 @@ def path_join(self, path, dir): """ Join an existing path and a directory """ - return os.path.join( path, dir ) + return os.path.join(path, dir) def parse_logic(self, logic): @@ -611,7 +831,7 @@ def scheduler_return_next(self, name): if name != '': name = '.' + name name = self._pluginname_prefix + self.get_fullname() + name - self.logger.debug("scheduler_return_next: name = {}".format(name)) + self.logger.debug(f"scheduler_return_next: name = {name}") return self._sh.scheduler.return_next(name, from_smartplugin=True) def scheduler_trigger(self, name, obj=None, by=None, source=None, value=None, dest=None, prio=3, dt=None): @@ -626,10 +846,10 @@ def scheduler_trigger(self, name, obj=None, by=None, source=None, value=None, de name = '.' + name name = self._pluginname_prefix + self.get_fullname() + name if by is None: - by = 'Plugin {}'.format(name) - parameters = ', '.join(['{}={!r}'.format(k, v) for k, v in locals().items() - if v and not k in ['name', 'self', 'obj']]) - self.logger.debug("scheduler_trigger: name = {}, parameters: {}".format(name, parameters)) + by = f'Plugin {name}' + parameters = ', '.join([f'{k}={v!r}' for k, v in locals().items() + if v and k not in ['name', 'self', 'obj']]) + self.logger.debug(f"scheduler_trigger: name = {name}, parameters: {parameters}") self._sh.scheduler.trigger(name, obj, by, source, value, dest, prio, dt, from_smartplugin=True) def scheduler_add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, next=None): @@ -641,11 +861,11 @@ def scheduler_add(self, name, obj, prio=3, cron=None, cycle=None, value=None, of The parameters are identical to the scheduler.add method from lib.scheduler """ if name != '': - name = '.'+name - name = self._pluginname_prefix+self.get_fullname()+name - parameters = ', '.join(['{}={!r}'.format(k, v) for k, v in locals().items() - if v and not k in ['name', 'self', 'obj']]) - self.logger.debug("scheduler_add: name = {}, parameters: {}".format(name, parameters)) + name = '.' + name + name = self._pluginname_prefix + self.get_fullname() + name + parameters = ', '.join([f'{k}={v!r}' for k, v in locals().items() + if v and k not in ['name', 'self', 'obj']]) + self.logger.debug(f"scheduler_add: name = {name}, parameters: {parameters}") self._sh.scheduler.add(name, obj, prio, cron, cycle, value, offset, next, from_smartplugin=True) @@ -658,11 +878,11 @@ def scheduler_change(self, name, **kwargs): The parameters are identical to the scheduler.change method from lib.scheduler """ if name != '': - name = '.'+name - name = self._pluginname_prefix+self.get_fullname()+name - kwargs['from_smartplugin']=True - parameters = ', '.join(['{}={!r}'.format(k, v) for k, v in kwargs.items()]) - self.logger.debug("scheduler_change: name = {}, parameters: {}".format(name, parameters)) + name = '.' + name + name = self._pluginname_prefix + self.get_fullname() + name + kwargs['from_smartplugin'] = True + parameters = ', '.join([f'{k}={v!r}' for k, v in kwargs.items()]) + self.logger.debug(f"scheduler_change: name = {name}, parameters: {parameters}") self._sh.scheduler.change(name, **kwargs) @@ -675,9 +895,9 @@ def scheduler_remove(self, name): The parameters are identical to the scheduler.remove method from lib.scheduler """ if name != '': - name = '.'+name - name = self._pluginname_prefix+self.get_fullname()+name - self.logger.debug("scheduler_remove: name = {}".format(name)) + name = '.' + name + name = self._pluginname_prefix + self.get_fullname() + name + self.logger.debug(f"scheduler_remove: name = {name}") self._sh.scheduler.remove(name, from_smartplugin=True) @@ -692,7 +912,7 @@ def scheduler_get(self, name): if name != '': name = '.' + name name = self._pluginname_prefix + self.get_fullname() + name - self.logger.debug("scheduler_get: name = {}".format(name)) + self.logger.debug(f"scheduler_get: name = {name}") return self._sh.scheduler.get(name, from_smartplugin=True) @@ -716,11 +936,11 @@ def stop(self): def translate(self, txt, vars=None, block=None): """ - Returns translated text + Returns translated text for class SmartPlugin """ txt = str(txt) if block: - self.logger.warning("unsuported 3. parameter '{}' used in translation function _( ... )".format(block)) + self.logger.warning(f"unsuported 3. parameter '{block}' used in translation function _( ... )") if self._add_translation is None: # test initially, if plugin has additional translations @@ -728,7 +948,7 @@ def translate(self, txt, vars=None, block=None): self._add_translation = os.path.isfile(translation_fn) if self._add_translation: - return lib_translate(txt, vars, additional_translations='plugin/'+self.get_shortname()) + return lib_translate(txt, vars, plugin_translations='plugin/'+self.get_shortname()) else: return lib_translate(txt, vars) @@ -747,7 +967,7 @@ def init_webinterface(self, WebInterface=None): self.mod_http = Modules.get_instance().get_module('http') except: self.mod_http = None - if self.mod_http == None: + if self.mod_http is None: self.logger.warning("Module 'http' not loaded. Not initializing the web interface for the plugin") return False @@ -772,6 +992,19 @@ def init_webinterface(self, WebInterface=None): return True +# +# deprecated methods, kept in place in case anybody still uses them +# + + def _get_itemlist(self): + self._sh._deprecated_warning('SmartPlugin.get_items()') + return self.get_items() + + def _append_to_itemlist(self, item): + self._sh._deprecated_warning('SmartPlugin.add_item()') + self.add_item(item) + + try: from jinja2 import Environment, FileSystemLoader @@ -792,12 +1025,12 @@ def init_template_environment(self): :return: Jinja2 template engine environment :rtype: object """ - mytemplates = self.plugin.path_join( self.webif_dir, 'templates' ) + mytemplates = self.plugin.path_join(self.webif_dir, 'templates') globaltemplates = self.plugin.mod_http.gtemplates_dir - tplenv = Environment(loader=FileSystemLoader([mytemplates,globaltemplates])) + tplenv = Environment(loader=FileSystemLoader([mytemplates, globaltemplates])) tplenv.globals['isfile'] = self.is_staticfile - tplenv.globals['_'] = self.plugin.translate + tplenv.globals['_'] = self.translate # use translate method of webinterface class tplenv.globals['len'] = len return tplenv @@ -816,20 +1049,29 @@ def is_staticfile(self, path): :rtype: bool """ if path.startswith('/gstatic/'): -# complete_path = self.plugin.path_join(Modules.get_instance().get_module('http')._gstatic_dir, path[len('/gstatic/'):]) complete_path = self.plugin.path_join(self.plugin.mod_http.gstatic_dir, path[len('/gstatic/'):]) else: complete_path = self.plugin.path_join(self.webif_dir, path) from os.path import isfile as isfile - result = isfile(complete_path) - # self.logger.debug("is_staticfile: path={}, complete_path={}, result={}".format(path, complete_path, result)) - return result + return isfile(complete_path) - def translate(self, txt): + def translate(self, txt, vars=None): """ - Returns translated text + Returns translated text for class SmartPluginWebIf - This method extends the jinja2 template engine + This method extends the jinja2 template engine _( ... ) -> translate( ... ) """ - return self.plugin.translate(txt) + txt = str(txt) + + if self.plugin._add_translation is None: + # test initially, if plugin has additional translations + translation_fn = os.path.join(self.plugin._plugin_dir, 'locale.yaml') + self.plugin._add_translation = os.path.isfile(translation_fn) + + if self.plugin._add_translation: + return lib_translate(txt, vars, plugin_translations='plugin/' + self.plugin.get_shortname(), module_translations='module/http') + else: + return lib_translate(txt, vars, module_translations='module/http') + + diff --git a/lib/model/user_doc.rst b/lib/model/user_doc.rst new file mode 100644 index 0000000000..d7b19a1abc --- /dev/null +++ b/lib/model/user_doc.rst @@ -0,0 +1,263 @@ +SmartPlugin - Modifikation +========================== + +Ziel der Änderungen ist es, + +- das Starten und Stoppen von Plugins, +- (dazu) das Parsen (binden) und “Unparsen” (lösen) von Items zum + Plugin + +zu ermöglichen. Sofern das Plugin die notwendigen Konventionen einhält, +kann es im Betrieb gestoppt und wieder gestartet werden sowie Items neu +laden bzw. entfernen, die zur Laufzeit angelegt, geändert oder entfernt +wurden. + +Überlegungen +------------ + +Die bisherige Struktur bzw. Implementierung der meisten Plugins ist +nicht darauf ausgerichtet, Plugins nach dem Stoppen wieder zu starten. +Das liegt zum Einen daran, wie Initialisierung und “Aufräumen” +strukturiert sind (teilweise Initialisierung in ``run()``, Anhalten und +Aufräumen sind in ``stop()`` zusammengefasst); zum Anderen daran, dass +Initialisierung möglicherweise erneut durchlaufen werden muss, +spätestens dann, wenn sich Items oder Konfigurationsparameter des +Plugins geändert haben. + +Zur vorbereitenden Initialisierung gehören z.B. + +- Lesen der Konfiguration +- Vorbereiten von Verbindungen (nicht: Herstellen/Öffnen!) +- Parsen und Binden von Items +- ggf. Initialisieren von Items / Feldern +- Einrichten von Schedulern (sofern Einrichten/Starten getrennt möglich + ist) + +Zum Startvorgang gehören z.B. + +- Öffnen von Verbindungen +- Starten von Schedulern +- ggf. Starten zusätzlicher Threads + +Analog muss auch der Stop-Prozess geteilt werden. Zum Anhalten des +Plugins gehören u.a. + +- Schließen von Verbindungen +- Stoppen von Schedulern +- ggf. Stoppen zusätzlicher Threads + +Zum Herunterfahren sollten dann noch folgende Aktionen gehören: + +- Unparsen / Lösen von Items +- Löschen von Schedulern (s.o.) + +Die letzten beiden Punkte sind für den bisherigen Betrieb irrelevant, da +shng bzw. Python diese beim Beenden von shng automatisch durchführt bzw. +diese durch das Beenden des Programms nicht erforderlich sind. Wenn aber +ein echter Neustart eines Plugins, z.B. nach dem Neuanlegen oder Ändern +von Items oder ggf. sogar Änderungen am Programmcode gewünscht wird, +dann müssen diese Funktionen getrennt verfügbar und ausführbar gemacht +werden. + +Implementierung +~~~~~~~~~~~~~~~ + +Die vorgeschlagenen Änderungen ermöglichen die Deinitialisierung als +Gegenstück zur Initialisierung in ``__init__()``. Die Methode +``deinit()`` hält - falls noch nicht geschehen - das Plugin an und löst +alle gebundenen Items (s.u.). + +Um die Nutzung in - auch bestehenden - Plugins zu vereinfachen, werden +die Änderungen an der Klasse ``smartplugin`` als Basisklasse von Plugins +umgesetzt. Dabei muss darauf geachtet werden, so wenig wie möglich +“breaking changes” zu produzieren. + +Anpassung von Plugins +~~~~~~~~~~~~~~~~~~~~~ + +Sofern die Neuerungen nicht genutzt werden (sollen), sind die Änderungen +im Idealfall rückwärtskompatibel. Durch Anpassung des Plugins an die +Änderungen sollten die Aktivierung/Nutzung der neuen Funktionen mit +vergleichsweise geringem Aufwand möglich werden: + +- ``__init__()`` anpassen und ``deinit()`` überschreiben +- ``run()`` und ``stop()`` anpassen +- Plugin auf Nutzung der neuen Felder anpassen + +Binden / Lösen von Items +------------------------ + +Plugins bekommen von der ``items``-Klasse alle Items an ``parse_item()`` +übergeben, damit sie prüfen können, ob das jeweilige Item für sie +relevant ist. Falls ja, kann das Item in einer Plugin-internen Liste +gespeichert werden, um darauf zuzugreifen. Wenn Änderungen am Item +automatisch an das Plugin übergeben werden sollen, kann z.B. ein Verweis +auf ``update_item()`` als Update-Trigger zurückgegeben werden. Diesen +Prozess bezeichne ich im Folgenden als “Binden” (sowohl das interne +Speichern als auch das Registrieren in der ``item``-Klasse per +Update-Trigger). + +Bisher meist nicht implementiert ist der umgekehrte Prozess des +“Lösens”, das Entfernen aus den internen Listen oder das +“Unregistrieren” des Update-Triggers. + +Es ist offensichtlich, dass für dynamisch änderbare Items diese Prozesse +beide zur Laufzeit (ggf. mehrmals) durchlaufen werden können müssen. + +.. _implementierung-1: + +Implementierung +~~~~~~~~~~~~~~~ + +Die vorgeschlagenen Änderungen ermöglichen das Lösen/“Unparsing” von +Items als Gegenstück zum Parsen in ``parse_item()``. So wie +``parse_item()`` prüft, ob das Item im Plugin verwendet werden kann/soll +und den Update-Trigger ``update_item()`` zurückgibt, löscht +``unparse_item()`` die Update-Verknüpfung und entfernt das Item aus den +plugin-internen Listen/Feldern. + +Konventionen +------------ + +Um Klarheit und Verständlichkeit zu fördern, werden die folgenden +Konventionen genutzt: + +(TODO: Ich hoffe, ich habe sie selber überall eingehalten… prüfen!) + +- ``item`` bezeichnet ein Objekt der ``class Item()`` +- ``item_path`` bezeichnet einen String mit dem Item-Pfad (ehemals + item_id) +- ``items`` bezeichnet eine Liste mit ``class Item()``-Objekten +- ``item_dict`` bezeichnet ein dict, das item_path als Schlüssel + verwendet + +Beschreibung der Änderungen +=========================== + +Felder +------ + +- ``_plg_item_dict``: + + Schon länger gab es im SmartPlugin ein Feld ``_itemlist``, das + rudimentär von der Plugins-Klasse unterstützt wurde. Dieser Ansatz + wird ausgebaut: + + Es gibt - neu - im SmartPlugin ein dict ``_plg_item_dict``, in dem + alle dem Plugin zugeordneten Items und deren (pluginspezifischen) + Konfiguration vorgehalten werden: + +.. code:: python + + self._plg_item_dict[item.path()] = { + 'item': item, # das Item (Objektreferenz) + 'is_updating': True, # wurde das Item in shng über update_item registriert? + 'mapping': mapping, # ggf. "Aktionsbezeichner" für das Item + 'config_data': {} # dict mit Item-spezifischen Konfigurationsdaten + } + +- ``_item_lookup_dict``: + + Es gibt weiterhin ein “Rückwärtssuch”-dict ``_item_lookup_dict``, in + dem die Items nach ihren jeweiligen ``mappings`` gelistet + werden: + +.. code:: python + + self._item_lookup_dict[mapping] = [item1, item2, ...] + + Damit wird ein schneller Zugriff auf die entsprechenden Items + möglich, wenn ``mapping`` aktiviert wird (üblicherweise über + ein durch das Plugin verwaltetes Gerät/Netzwerkverbindung). + +Methoden +-------- + +- ``__init__()``: + + unverändert, muss aber ggf. auf die neuen Felder angepasst werden. + +- ``deinit()``: + + Das neue Gegenstück zu ``__init__()``. Standardmäßig wird - sofern + noch nicht geschehen - das Plugin per ``stop()`` angehalten. Danach + werden alle Items aus ``_plg_item_dict`` durch Aufruf von + ``remove_item(item)`` gelöst. + +- ``add_item()``: + + Ehemals ``_append_to_itemlist``. Das übergebene Item wird in + ``_plg_item_dict`` eingetragen, und falls ein ``mapping`` + übergeben wurde, wird ``_item_lookup_dict`` ebenfalls ergänzt. + + Diese Methode muss in ``parse_item()`` aufgerufen werden, wenn das + Item im Plugin genutzt werden soll. (“Muss” nicht, aber sollte, um + die Konventionen einzuhalten und die neuen Funktionen fehlerfrei + nutzen zu können…) + + Weiterhin wird diese Methode (erneut) durch das Item selbst + aufgerufen, wenn ``parse_item`` eine Triggermethode (``update_item``) + zurückgibt. In dem Fall wird ``register_updating`` aufgerufen, um die + entsprechenden Eintragungen in ``_plg_item_dict`` vorzunehmen. + +- ``remove_item()``: + + Das Gegenstück zu ``add_item()``, das übergebene Item wird aus den + internen Listen entfernt. Zusätzlich wird ``unparse_item()`` + aufgerufen, um ggf. gesetzte Update-Trigger zu löschen. + + Diese Methode wird automatisch für alle Items von ``deinit()`` + aufgerufen. + +- ``register_updating()``: + + Setzt im ``_plg_item_dict`` das Flag für Updates (“``is_updating``”). + Falls das Item noch nicht registriert ist, wird vorher automatisch + ``add_item(item)`` aufgerufen. + +- ``unparse_item()``: + + Löscht den Update-Trigger im Item-Objekt. Wird durch + ``remove_item()`` aufgerufen. + +- verschiedene Getter-Methoden (``get_items()``, + ``get_trigger_items()``, ``get_items_for_mapping()``, + ``get_item_path_list()``): + + Geben Listen von Items bzw. Item-Pfaden zurück. + + ``get_items()`` und ``get_item_path_list()`` können optional gefiltert werden. + Das Filtern geschieht über einen anzugebenen filter_key des config_data_dicts und einen anzugebenen filter_value. + Wenn filter_key und filter_value angegeben sind, wird ein Item nur in die Ergebnisliste eingeschlossen, + wenn der value im config_data_dict zu dem angegebenen filter_key dem filter_value entspricht. + + +TODO +==== + +Die bisher beschriebenen Methoden und Felder sind - nach meinem +Dafürhalten - fertig implementiert, bedürfen aber noch der Überprüfung +und ggf. Anpassung, wenn einzelne Funktionen anders umgesetzt werden +sollen. + +Wenn ein Plugin auf Basis der neuen ``SmartPlugin``-Klasse erstellt +wurde, sollte es starten und stoppen können und zusätzlich im Betrieb +Items neu laden (stoppen - deinitialisieren - initialisieren - starten). + +Nicht berücksichtigt sind die Fragen, + +- wie Items dynamisch angelegt / geändert werden +- ob / wie geänderte Items in den Konfigurationsdateien gesichert + werden (können), +- ob geänderte Plugin-Konfiguration in den Konfigurationsdateien + gesichert werden (können), +- ob man Plugin “inaktiv”/“aktiv” schalten können soll, + +Weiteres Vorgehen (meine Idee) +------------------------------ + +- Anpassung erster Plugins, Teste, ggf. Korrekturen +- Anpassung weiterer Plugins, idealerweise durch deren Autoren, Teste, + ggf. Korrekturen +- noch mehr Teste +- Übernahme in master, Release diff --git a/lib/module.py b/lib/module.py index 951e122e56..3ea7f3dbe2 100644 --- a/lib/module.py +++ b/lib/module.py @@ -246,6 +246,7 @@ def _load_module(self, name, classname, classpath, args): # load module-specific translations translation.load_translations('module', classpath.replace('.', '/'), 'module/'+classpath.split('.')[1]) +# translation.load_translations('global', classpath.replace('.', '/'), 'module/'+classpath.split('.')[1]) # get arguments defined in __init__ of module's class to self.args try: diff --git a/lib/network.py b/lib/network.py index 1e2d52c2f7..765df29bdd 100644 --- a/lib/network.py +++ b/lib/network.py @@ -922,7 +922,10 @@ def close(self): self.__running = False self.logger.info(f'{self._id} closing connection') if self._is_connected: - self._socket.shutdown(socket.SHUT_RD) + try: + self._socket.shutdown(socket.SHUT_RD) + except Exception as e: + self.logger.info(f"socket no longer connected on disconnect, exception is {e}") if self.__connect_thread is not None and self.__connect_thread.is_alive(): self.__connect_thread.join() if self.__receive_thread is not None and self.__receive_thread.is_alive(): diff --git a/lib/plugin.py b/lib/plugin.py index c5e773540e..34d1a05208 100644 --- a/lib/plugin.py +++ b/lib/plugin.py @@ -59,7 +59,7 @@ import lib.translation as translation import lib.shyaml as shyaml from lib.model.smartplugin import SmartPlugin -from lib.constants import (KEY_CLASS_NAME, KEY_CLASS_PATH, KEY_INSTANCE,YAML_FILE,CONF_FILE) +from lib.constants import (KEY_CLASS_NAME, KEY_CLASS_PATH, KEY_INSTANCE, KEY_WEBIF_PAGELENGTH, YAML_FILE,CONF_FILE) #from lib.utils import Utils from lib.metadata import Metadata #import lib.item @@ -68,6 +68,7 @@ _plugins_instance = None # Pointer to the initialized instance of the Plugins class (for use by static methods) +_SH = None def namestr(obj, namespace): return [name for name in namespace if namespace[name] is obj] @@ -112,6 +113,8 @@ def __init__(self, smarthome, configfile): return logger.info('Load plugins') + threads_early = [] + threads_late = [] # for every section (plugin) in the plugin.yaml file for plugin in _conf: @@ -157,22 +160,39 @@ def __init__(self, smarthome, configfile): args = self._get_conf_args(_conf[plugin]) # logger.warning("Plugin '{}' from section '{}': classname = {}, classpath = {}".format( str(classpath).split('.')[1], plugin, classname, classpath ) ) instance = self._get_instancename(_conf[plugin]).lower() + try: + plugin_version = self.meta.pluginsettings.get('version', 'ersion unknown') + plugin_version = 'v' + plugin_version + except Exception as e: + plugin_version = 'version unknown' dummy = self._test_duplicate_pluginconfiguration(plugin, classname, instance) try: plugin_thread = PluginWrapper(smarthome, plugin, classname, classpath, args, instance, self.meta, self._configfile) if plugin_thread._init_complete == True: try: + try: + startorder = self.meta.pluginsettings.get('startorder', 'normal').lower() + except Exception as e: + logger.warning(f"Plugin {str(classpath).split('.')[1]} error on getting startorder: {e}") + startorder = 'normal' self._plugins.append(plugin_thread.plugin) - self._threads.append(plugin_thread) + if startorder == 'early': + threads_early.append(plugin_thread) + elif startorder == 'late': + threads_late.append(plugin_thread) + else: + self._threads.append(plugin_thread) if instance == '': - logger.info("Initialized plugin '{}' from section '{}'".format( str(classpath).split('.')[1], plugin ) ) + logger.info(f"Initialized plugin '{str(classpath).split('.')[1]}' from section '{plugin}'") else: - logger.info("Initialized plugin '{}' instance '{}' from section '{}'".format( str(classpath).split('.')[1], instance, plugin ) ) - except: - logger.warning("Plugin '{}' from section '{}' not loaded".format( str(classpath).split('.')[1], plugin ) ) + logger.info(f"Initialized plugin '{str(classpath).split('.')[1]}' instance '{instance}' from section '{plugin}'") + except Exception as e: + logger.warning(f"Plugin '{str(classpath).split('.')[1]}' from section '{plugin}' not loaded - exception {e}" ) except Exception as e: - logger.exception("Plugin '{}' from section '{}' exception: {}".format(str(classpath).split('.')[1], plugin, e)) + logger.exception(f"Plugin '{str(classpath).split('.')[1]}' {plugin_version} from section '{plugin}'\nException: {e}\nrunning SmartHomeNG {self._sh.version} / plugins {self._sh.plugins_version}") + # join the start_early and start_late lists with the main thread list + self._threads = threads_early + self._threads + threads_late logger.info('Load of plugins finished') del(_conf) # clean up @@ -246,10 +266,11 @@ def _get_conf_args(self, plg_conf): """ args = {} for arg in plg_conf: + # ignore class_name, class_path and instance - those parameters ar not handed to the PluginWrapper if arg != KEY_CLASS_NAME and arg != KEY_CLASS_PATH and arg != KEY_INSTANCE: value = plg_conf[arg] if isinstance(value, str): - value = "'{0}'".format(value) + value = f"'{value}'" args[arg] = value return args @@ -486,14 +507,14 @@ def start(self): def stop(self): logger.info('Stop plugins') - for plugin in self._threads: + for plugin in list(reversed(self._threads)): try: instance = plugin.get_implementation().get_instance_name() if instance != '': instance = ", instance '"+instance+"'" - logger.debug("Stopping plugin '{}'{}".format(plugin.get_implementation().get_shortname(), instance)) + logger.debug(f"Stopping plugin '{plugin.get_implementation().get_shortname()}'{instance}") except: - logger.debug("Stopping classic-plugin from section '{}'".format(plugin.name)) + logger.debug(f"Stopping classic-plugin from section '{plugin.name}'") try: plugin.stop() except: @@ -538,9 +559,10 @@ def __init__(self, smarthome, name, classname, classpath, args, instance, meta, """ Initialization of wrapper class """ - logger.debug('PluginWrapper __init__: Section {}, classname {}, classpath {}'.format( name, classname, classpath )) + logger.debug("PluginWrapper __init__: Section {name}, classname {classname}, classpath {classpath}") threading.Thread.__init__(self, name=name) + self._sh = smarthome self._init_complete = False self.meta = meta # Load an instance of the plugin @@ -583,6 +605,10 @@ def __init__(self, smarthome, name, classname, classpath, args, instance, meta, self.get_implementation()._set_instance_name(instance) # addition by smai # Customized logger instance for plugin to append name of plugin instance to log text + # addition by msinn + global _SH + _SH = self._sh + # end addition by msinn self.get_implementation().logger = PluginLoggingAdapter(logging.getLogger(classpath), {'plugininstance': self.get_implementation().get_loginstance()}) # end addition by smai self.get_implementation()._set_sh(smarthome) @@ -724,7 +750,35 @@ class PluginLoggingAdapter(logging.LoggerAdapter): This class is used by PluginWrapper to set up a logger for the SmartPlugin class """ + from lib.log import Logs + + def __init__(self, logger, extra): + logging.LoggerAdapter.__init__(self, logger, extra) + self.logger = logger + + logging.addLevelName(_SH.logs.NOTICE_level, "NOTICE") + logging.addLevelName(_SH.logs.DBGHIGH_level, "DBGHIGH") + logging.addLevelName(_SH.logs.DBGMED_level, "DBGMED") + logging.addLevelName(_SH.logs.DBGLOW_level, "DBGLOW") + return + + def notice(self, msg, *args, **kwargs): + self.logger.log(_SH.logs.NOTICE_level, f"{self.extra['plugininstance']}{msg}", *args, **kwargs) + return + + def dbghigh(self, msg, *args, **kwargs): + self.logger.log(_SH.logs.DBGHIGH_level, f"{self.extra['plugininstance']}{msg}", *args, **kwargs) + return + + def dbgmed(self, msg, *args, **kwargs): + self.logger.log(_SH.logs.DBGMED_level, f"{self.extra['plugininstance']}{msg}", *args, **kwargs) + return + + def dbglow(self, msg, *args, **kwargs): + self.logger.log(_SH.logs.DBGLOW_level, f"{self.extra['plugininstance']}{msg}", *args, **kwargs) + return + def process(self, msg, kwargs): kwargs['extra'] = self.extra - return '{}{}'.format(self.extra['plugininstance'], msg), kwargs + return f"{self.extra['plugininstance']}{msg}", kwargs # end addition von smai diff --git a/lib/scheduler.py b/lib/scheduler.py index 6055779be0..584f4a85bd 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -2,8 +2,9 @@ # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab ######################################################################### # Copyright 2011-2014 Marcus Popp marcus@popp.mx -# Copyright 2016- Christian Straßburg -# Copyright 2017 Bernd Meiners Bernd.Meiners@mail.de +# Copyright 2016-2017 Christian Straßburg +# Copyright 2017-2022 Martin Sinn m.sinn@gmx.de +# Copyright 2017-2022 Bernd Meiners Bernd.Meiners@mail.de ######################################################################### # This file is part of SmartHomeNG # @@ -130,6 +131,7 @@ class Scheduler(threading.Thread): _worker_num = 5 _worker_max = 20 _worker_delta = 60 # wait 60 seconds before adding another worker thread + _scheduler = {} # holder schedulers, key is the scheduler name. Each scheduler is stored in a dict # (keys are 'obj', 'active', 'prio', 'next', 'value', 'cycle', 'cron') _runq = _PriorityQueue() # holds priority and a tuple of (name, obj, by, source, dest, value) for immediate execution @@ -189,6 +191,12 @@ def get_instance(): return _scheduler_instance + def set_worker_warn_count(self, count): + + self._worker_max = count + logger.info(f"Warn Level for maximum number of workers set to {self._worker_max}") + + def get_worker_count(self): """ Get number of worker threads initialized by scheduler @@ -226,7 +234,7 @@ def get_worker_names(self): def run(self): self.alive = True - logger.debug("creating {0} workers".format(self._worker_num)) + logger.debug(f"creating {self._worker_num} workers") for i in range(self._worker_num): self._add_worker() while self.alive: @@ -237,7 +245,7 @@ def run(self): if len(self._workers) < self._worker_max: self._add_worker() else: - logger.error("Needing more worker threads than the specified maximum of {}! ({} worker threads active)".format(self._worker_max, len(self._workers))) + logger.error(f"Needing more worker threads than the specified maximum of {self._worker_max}! ({len(self._workers)} worker threads active)") tn = {} # for t in threading.enumerate(): for t in self._workers: @@ -258,7 +266,7 @@ def run(self): try: (dt, prio), (name, obj, by, source, dest, value) = self._triggerq.get() except Exception as e: - logger.warning("Trigger queue exception: {0}".format(e)) + logger.warning(f"Trigger queue exception: {e}") break if dt < now: # run it @@ -284,7 +292,7 @@ def run(self): for name in self._scheduler: task = self._scheduler[name] if task['next'] is not None: - if task['next'] < now: + if task['next'] <= now: self._runc.acquire() # insert priority and a tuple of (name, obj, by, source, dest, value) # ms self._runq.insert(task['prio'], (name, task['obj'], 'Scheduler', task.get('source', None), None, task['value'])) @@ -300,12 +308,23 @@ def run(self): continue else: self._next_time(name) + except Exception as e: + tb_str = ''.join(traceback.format_exception(None, e, e.__traceback__)) + logger.warning(f"Exception: {e} while searching scheduler for due tasks. Traceback: {tb_str}") finally: self._lock.release() time.sleep(0.5) + if self._sh.shng_status['code'] > 20: + logger.info("scheduler leaves run method") + else: + logger.warning("scheduler leaves run method") + return + + def stop(self): self.alive = False + logger.debug("scheduler leaves stop method") def trigger(self, name, obj=None, by='Logic', source=None, value=None, dest=None, prio=3, dt=None, from_smartplugin=False): """ @@ -340,12 +359,12 @@ def trigger(self, name, obj=None, by='Logic', source=None, value=None, dest=None self._runc.release() else: if not isinstance(dt, datetime.datetime): - logger.warning("Trigger: Not a valid timezone aware datetime for {0}. Ignoring.".format(name)) + logger.warning(f"Trigger: Not a valid timezone aware datetime for {name}. Ignoring.") return if dt.tzinfo is None: - logger.warning("Trigger: Not a valid timezone aware datetime for {0}. Ignoring.".format(name)) + logger.warning(f"Trigger: Not a valid timezone aware datetime for {name}. Ignoring.") return - logger.debug("Triggering {0} - by: {1} source: {2} dest: {3} value: {4} at: {5}".format(name, by, source, dest, str(value)[:40], dt)) + logger.debug(f"Triggering {name} - by: {by} source: {source} dest: {dest} value: {str(value)[:40]} at: {dt}") self._triggerq.insert((dt, prio), (name, obj, by, source, dest, value)) def remove(self, name, from_smartplugin=False): @@ -359,11 +378,11 @@ def remove(self, name, from_smartplugin=False): self._lock.acquire() try: name = self.check_caller(name, from_smartplugin) - logger.debug("remove scheduler entry with name:{0}".format(name)) + logger.debug(f"remove scheduler entry with name: {name}") if name in self._scheduler: del(self._scheduler[name]) - except: - logger.error("Could not remove scheduler entry for {}".format(name)) + except Exception as e: + logger.error(f"Exception {e}: Could not remove scheduler entry for {name}") finally: self._lock.release() @@ -419,72 +438,76 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, self.shtime = Shtime.get_instance() if self.items == None: self.items = Items.get_instance() - self._lock.acquire() - try: - source = '??' - if isinstance(cron, str): - cron = [cron, ] - if isinstance(cron, list): - details = None - _cron = {} - for entry in cron: - desc, __, _value = entry.partition('=') - desc = desc.strip() - if _value == '': - _value = None - else: - _value = _value.strip() - if desc.lower().startswith('init'): - details = desc - offset = 5 # default init offset - desc, op, seconds = desc.partition('+') - if op: - offset += int(seconds) + if self._lock.acquire(): + try: + source = '??' + if isinstance(cron, str): + cron = [cron, ] + if isinstance(cron, list): + details = None + _cron = {} + for entry in cron: + desc, __, _value = entry.partition('=') + desc = desc.strip() + if _value == '': + _value = None else: - desc, op, seconds = desc.partition('-') + _value = _value.strip() + if desc.lower().startswith('init'): + details = desc + offset = 5 # default init offset + desc, op, seconds = desc.partition('+') if op: - offset -= int(seconds) - value = _value - next = self.shtime.now() + datetime.timedelta(seconds=offset) + offset += int(seconds) + else: + desc, op, seconds = desc.partition('-') + if op: + offset -= int(seconds) + value = _value + next = self.shtime.now() + datetime.timedelta(seconds=offset) + else: + _cron[desc] = _value + source = {'source': 'cron', 'details': details} + if _cron == {}: + cron = None else: - _cron[desc] = _value - source = {'source': 'cron', 'details': details} - if _cron == {}: - cron = None - else: - cron = _cron - - if isinstance(cycle, int): - source = {'source': 'cycle1', 'details': cycle} - cycle = {cycle: cycle} - elif isinstance(cycle, str): - cycle, __, _value = cycle.partition('=') - try: - cycle = int(cycle.strip()) - except Exception: - logger.warning("Scheduler: Invalid cycle entry for {0} {1}".format(name, cycle)) - return - if _value != '': - _value = _value.strip() - else: - _value = cycle - cycle = {cycle: _value} - source = {'source': 'cycle', 'details': _value} - if cycle is not None and offset is None: # spread cycle jobs - offset = random.randint(10, 15) - # change name for multi instance plugins - if obj.__class__.__name__ == 'method': - if isinstance(obj.__self__, SmartPlugin): - if obj.__self__.get_instance_name() != '': - #if not (name).startswith(self._pluginname_prefix): - if not from_smartplugin: - name = name +'_'+ obj.__self__.get_instance_name() - logger.debug("Scheduler: Name changed by adding plugin instance name to: " + name) - self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} - if next is None: - self._next_time(name, offset) - finally: - self._lock.release() + cron = _cron + + if isinstance(cycle, int): + source = {'source': 'cycle1', 'details': cycle} + cycle = {cycle: cycle} + elif isinstance(cycle, str): + cycle, __, _value = cycle.partition('=') + try: + cycle = int(cycle.strip()) + except Exception as e: + logger.warning(f"Scheduler: Exception {e}: Invalid cycle entry for {name} {cycle}") + return + if _value != '': + _value = _value.strip() + else: + _value = cycle + cycle = {cycle: _value} + source = {'source': 'cycle', 'details': _value} + if cycle is not None and offset is None: # spread cycle jobs + offset = random.randint(10, 15) + # change name for multi instance plugins + if obj.__class__.__name__ == 'method': + if isinstance(obj.__self__, SmartPlugin): + if obj.__self__.get_instance_name() != '': + #if not (name).startswith(self._pluginname_prefix): + if not from_smartplugin: + name = name +'_'+ obj.__self__.get_instance_name() + logger.debug("Scheduler: Name changed by adding plugin instance name to: " + name) + self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} + if next is None: + self._next_time(name, offset) + except Exception as e: + logger.error(f"Exception: {e} while trying to add a new entry to scheduler") + finally: + self._lock.release() + else: + logger.error(f"Could not aquire lock to add a new entry to scheduler") def get(self, name, from_smartplugin=False): """ @@ -497,64 +520,73 @@ def get(self, name, from_smartplugin=False): return None def change(self, name, from_smartplugin=False, **kwargs): + """changes a scheduler entry for a given name to settings given in kwargs""" name = self.check_caller(name, from_smartplugin) - if name in self._scheduler: - for key in kwargs: - if key in self._scheduler[name]: - if key == 'cron': - if isinstance(kwargs[key], str): - _cron = {} - for entry in kwargs[key].split('|'): - desc, __, _value = entry.partition('=') - desc = desc.strip() - if _value == '': - _value = None - else: - _value = _value.strip() - _cron[desc] = _value - if _cron == {}: - kwargs[key] = None - else: - kwargs[key] = _cron - elif key == 'cycle': - _cycle = kwargs[key] - if isinstance(kwargs[key], dict): - _cycle = kwargs[key] - elif isinstance(kwargs[key], int): - _cycle = {kwargs[key]: None} - elif isinstance(kwargs[key], str): - _param = kwargs[key].strip() - if _param[0] == '{' and _param[-1] == '}': - _param = _param[1:-1] - _cycle, __, _value = _param.partition(':') - try: - _cycle = int(_cycle.strip()) - except Exception: - logger.warning("scheduler.change: Invalid cycle entry for {} {}".format(name, _cycle)) - return - if _value != '': - _value = _value.strip() - else: - _value = None - _cycle = {_cycle: _value} - #logger.warning("scheduler.change: {}: {}, type = type(kwargs[key])={}".format(name, kwargs[key], type(kwargs[key]))) - kwargs[key] = _cycle - #logger.warning("scheduler.change: {}: cycle entry {}".format(name, _cycle)) - elif key == 'active': - if kwargs['active'] and not self._scheduler[name]['active']: - logger.info("Activating logic: {0}".format(name)) - elif not kwargs['active'] and self._scheduler[name]['active']: - logger.info("Deactivating logic: {0}".format(name)) - self._scheduler[name][key] = kwargs[key] + if self._lock.acquire(): + try: + if name in self._scheduler: + for key in kwargs: + if key in self._scheduler[name]: + if key == 'cron': + if isinstance(kwargs[key], str): + _cron = {} + for entry in kwargs[key].split('|'): + desc, __, _value = entry.partition('=') + desc = desc.strip() + if _value == '': + _value = None + else: + _value = _value.strip() + _cron[desc] = _value + if _cron == {}: + kwargs[key] = None + else: + kwargs[key] = _cron + elif key == 'cycle': + _cycle = kwargs[key] + if isinstance(kwargs[key], dict): + _cycle = kwargs[key] + elif isinstance(kwargs[key], int): + _cycle = {kwargs[key]: None} + elif isinstance(kwargs[key], str): + _param = kwargs[key].strip() + if _param[0] == '{' and _param[-1] == '}': + _param = _param[1:-1] + _cycle, __, _value = _param.partition(':') + try: + _cycle = int(_cycle.strip()) + except Exception: + logger.warning("scheduler.change: Invalid cycle entry for {} {}".format(name, _cycle)) + return + if _value != '': + _value = _value.strip() + else: + _value = None + _cycle = {_cycle: _value} + #logger.warning("scheduler.change: {}: {}, type = type(kwargs[key])={}".format(name, kwargs[key], type(kwargs[key]))) + kwargs[key] = _cycle + #logger.warning("scheduler.change: {}: cycle entry {}".format(name, _cycle)) + elif key == 'active': + if kwargs['active'] and not self._scheduler[name]['active']: + logger.info("Activating logic: {0}".format(name)) + elif not kwargs['active'] and self._scheduler[name]['active']: + logger.info("Deactivating logic: {0}".format(name)) + self._scheduler[name][key] = kwargs[key] + else: + logger.warning(f"Attribute {key} for {name} not specified. Could not change it.") + if self._scheduler[name]['active'] is True: + if 'cycle' in kwargs or 'cron' in kwargs: + self._next_time(name) + else: + self._scheduler[name]['next'] = None else: - logger.warning("Attribute {0} for {1} not specified. Could not change it.".format(key, name)) - if self._scheduler[name]['active'] is True: - if 'cycle' in kwargs or 'cron' in kwargs: - self._next_time(name) - else: - self._scheduler[name]['next'] = None + logger.warning(f"Could not change {name}. No logic/method with this name found.") + except Exception as e: + logger.error(f"Exception: {e} while trying to change entry for {name}") + finally: + self._lock.release() else: - logger.warning("Could not change {0}. No logic/method with this name found.".format(name)) + logger.error(f"Could not aquire lock to change entry for {name}") def _next_time(self, name, offset=None): """ @@ -600,8 +632,9 @@ def _next_time(self, name, offset=None): value = job['cron'][entry] self._scheduler[name]['next'] = next_time self._scheduler[name]['value'] = value - if name not in ['Connections', 'series', 'SQLite dump']: - logger.debug("{0} next time: {1}".format(name, next_time)) + #if name not in ['Connections', 'series', 'SQLite dump']: + # logger.debug(f"{name} next time: {next_time}") + logger.debug(f"{name} next time: {next_time}") def __iter__(self): for job in self._scheduler: @@ -728,7 +761,7 @@ def _execute_logic_task(self, logic, by, source, dest, value): try: method(logic, by, source, dest) except Exception as e: - logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) + logger.exception(f"Logic: Trigger {method} for {logic.name} failed: {e}") except LeaveLogic as e: # 'LeaveLogic' is no error if str(e) != '': diff --git a/lib/shpypi.py b/lib/shpypi.py index c2997823a5..b862043e3c 100644 --- a/lib/shpypi.py +++ b/lib/shpypi.py @@ -676,8 +676,9 @@ def get_packagelist(self): package['is_required_for_docbuild'] = True package['sort'] = self._build_sortstring(package) - if package['vers_req_min'] == '' and package['vers_req_max'] == '': + if package['vers_req_min'] == '': package['vers_req_min'] = required_packages[pkg_name].get('min', '*') + if package['vers_req_max'] == '': package['vers_req_max'] = required_packages[pkg_name].get('max', '*') package['vers_req_msg'] = '' package['vers_req_source'] = '' @@ -748,7 +749,10 @@ def lookup_pypi_releasedata(self, pypi_available=True): package['pypi_version_not_available_msg'] = 'PyPI nicht erreichbar' # check if installed version is ok and recent - self.check_package_version_data(package) + try: + self.check_package_version_data(package) + except Exception as e: + self.logger.exception(f"lookup_pypi_releasedata: Package {package} - Exception: {e}") return @@ -759,11 +763,13 @@ def check_package_version_data(self, package): max = package['vers_req_max'] recent = package['pypi_version'] inst_vers = package['vers_installed'] - if min == '*': + if min is None or min == '*': + min = '*' min_met = True else: min_met = self._compare_versions(min, inst_vers, '<=') - if max == '*': + if max is None or max == '*': + max = '*' max_met = True else: max_met = self._compare_versions(inst_vers, max, '<=') diff --git a/lib/shtime.py b/lib/shtime.py index 54fc8f3078..86c99933c6 100644 --- a/lib/shtime.py +++ b/lib/shtime.py @@ -123,7 +123,7 @@ def translate(self, txt, vars=None): """ txt = str(txt) - return lib_translate(txt, vars, additional_translations='lib/shtime') + return lib_translate(txt, vars, plugin_translations='lib/shtime') def set_tz(self, tzone): @@ -656,6 +656,47 @@ def current_month(self, offset=0): return (self.today() + dateutil.relativedelta.relativedelta(months=offset)).month + def current_monthname(self, offset=0): + """ + Return the name of the current month for a given date + + :param offset: negative number for previous months, positive for future ones + :type offset: int + + :return: monthname NAME + :rtype: str + """ + month = self.current_month(offset) + if month == 1: + monthname = "Januar" + elif month == 2: + monthname = "Februar" + elif month == 3: + monthname = "März" + elif month == 4: + monthname = "April" + elif month == 5: + monthname = "Mai" + elif month == 6: + monthname = "Juni" + elif month == 7: + monthname = "Juli" + elif month == 8: + monthname = "August" + elif month == 9: + monthname = "September" + elif month == 10: + monthname = "Oktober" + elif month == 11: + monthname = "November" + elif month == 12: + monthname = "Dezember" + else: + monthname = "?" + + return self.translate(monthname) + + def current_day(self, offset=0): """ Return the current day diff --git a/lib/shyaml.py b/lib/shyaml.py index ac5c61e0c4..3c95871cb0 100644 --- a/lib/shyaml.py +++ b/lib/shyaml.py @@ -187,7 +187,7 @@ def yaml_save(filename, data): :type filename: str :param data: configuration data to to save :type filename: str - :type data: OrderedDict + :type data: OrderedDict, dict :returns: Nothing """ diff --git a/lib/smarthome.py b/lib/smarthome.py index 67fff1ee03..53ffbbac20 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -100,6 +100,7 @@ from lib.triggertimes import TriggerTimes from lib.constants import (YAML_FILE, CONF_FILE, DEFAULT_FILE) import lib.userfunctions as uf +from lib.systeminfo import Systeminfo #import bin.shngversion #MODE = 'default' @@ -193,7 +194,7 @@ def __init__(self, MODE, extern_conf_dir=''): """ Initialization of main smarthome object """ - self.shng_status = {'code': 0, 'text': 'Initalizing'} + self.shng_status = {'code': 0, 'text': 'Initializing'} self._logger = logging.getLogger(__name__) self._logger_main = logging.getLogger(__name__) self.logs = lib.log.Logs(self) # initialize object for memory logs @@ -208,24 +209,24 @@ def __init__(self, MODE, extern_conf_dir=''): os.chdir(self._base_dir) self.PYTHON_VERSION = lib.utils.get_python_version() - - if os.name != 'nt': - self.python_bin = os.environ.get('_','') - else: - self.python_bin = sys.executable + self.python_bin = sys.executable if extern_conf_dir != '': self._extern_conf_dir = extern_conf_dir + # get systeminfo closs + self.systeminfo = Systeminfo + # set default timezone to UTC self.shtime = Shtime(self) threading.current_thread().name = 'Main' self.alive = True - import bin.shngversion - VERSION = bin.shngversion.get_shng_version() - self.version = VERSION + # import bin.shngversion as shngversion + # VERSION = shngversion.get_shng_version() + # self.branch = shngversion.get_shng_branch() + # self.version = shngversion.get_shng_version() self.connections = [] self._etc_dir = os.path.join(self._extern_conf_dir, 'etc') @@ -290,7 +291,17 @@ def __init__(self, MODE, extern_conf_dir=''): # setup logging self.init_logging(self._log_conf_basename, MODE) - self.shng_status = {'code': 1, 'text': 'Initalizing: Logging initalized'} + + ############################################################# + # get shng version information + # shngversion.get_plugins_version() may only be called after logging is initialized + import bin.shngversion as shngversion + #VERSION = shngversion.get_shng_version() + self.branch = shngversion.get_shng_branch() + self.version = shngversion.get_shng_version() + self.plugins_version = shngversion.get_plugins_version() + + self.shng_status = {'code': 1, 'text': 'Initializing: Logging initialized'} if hasattr(self, '_tz'): # set _tz again (now with logging enabled), @@ -314,7 +325,12 @@ def __init__(self, MODE, extern_conf_dir=''): virtual_text = ' in virtual environment' self._logger_main.notice("-------------------- Init SmartHomeNG {} --------------------".format(self.version)) self._logger_main.notice(f"Running in Python interpreter 'v{self.PYTHON_VERSION}'{virtual_text}, from directory {self._base_dir}") - self._logger_main.notice(f" - on {platform.platform()} (pid={pid})") + #self._logger_main.notice(f" - on {platform.platform()} (pid={pid})") + self._logger_main.notice(f" - operating system '{self.systeminfo.get_osname()}' (pid={pid})") + if self.systeminfo.get_rasppi_info() == '': + self._logger_main.notice(f" - on '{self.systeminfo.get_cpubrand()}'") + else: + self._logger_main.notice(f" - on '{self.systeminfo.get_rasppi_info()}'") if logging.getLevelName('NOTICE') == 31: self._logger_main.notice(f" - Loglevel NOTICE is set to value {logging.getLevelName('NOTICE')} because handler of root logger is set to level WARNING or higher - Set level of handler '{self.logs.root_handler_name}' to 'NOTICE'!") @@ -328,6 +344,9 @@ def __init__(self, MODE, extern_conf_dir=''): ############################################################# # Initialize multi-language support lib.translation.initialize_translations(self._base_dir, self._default_language, self._fallback_language_order) + self._logger.info("Translation initialized") + # make reload_translations() method publicly available for call by eval-syntax-checker + self.reload_translations = lib.translation.reload_translations ############################################################# # Test if plugins are installed @@ -341,6 +360,7 @@ def __init__(self, MODE, extern_conf_dir=''): self._logger.critical("Aborting") exit(1) + ############################################################# # test if needed Python packages for configured plugins # are installed @@ -371,11 +391,7 @@ def __init__(self, MODE, extern_conf_dir=''): self._logger.critical("Aborting") exit(1) - self.shng_status = {'code': 2, 'text': 'Initalizing: Requirements checked'} - - - self.shtime._initialize_holidays() - self._logger_main.notice(" - " + self.shtime.log_msg) + self.shng_status = {'code': 2, 'text': 'Initializing: Requirements checked'} # Add Signal Handling # signal.signal(signal.SIGHUP, self.reload_logics) @@ -392,6 +408,19 @@ def __init__(self, MODE, extern_conf_dir=''): # Catching Exceptions sys.excepthook = self._excepthook + ############################################################# + # Initialize holidays + self.shtime._initialize_holidays() + self._logger_main.notice(" - " + self.shtime.log_msg) + + ############################################################# + # check processor speed (if not done before) + self.cpu_speed_class = self.systeminfo.get_cpu_speed(self._var_dir) + if self.cpu_speed_class is None: + self.shng_status = {'code': 3, 'text': 'Checking processor speed'} + self.cpu_speed_class = self.systeminfo.check_cpu_speed(self._var_dir) + + ############################################################# # test if a valid locale is set in the operating system if os.name != 'nt': try: @@ -402,6 +431,7 @@ def __init__(self, MODE, extern_conf_dir=''): os.environ["LANG"] = 'en_US.UTF-8' os.environ["LC_ALL"] = 'en_US.UTF-8' + ############################################################# # Link Tools self.tools = lib.tools.Tools() @@ -575,6 +605,15 @@ def start(self): self.trigger = self.scheduler.trigger self.scheduler.start() + # set warn level to a higher number of workers on fast cpus + if self.cpu_speed_class == 'fast': + self.scheduler.set_worker_warn_count(60) + elif self.cpu_speed_class == 'medium': + self.scheduler.set_worker_warn_count(35) + else: + #leave it on standard (20 workers) + pass + ############################################################# # Init Connections ############################################################# @@ -585,7 +624,7 @@ def start(self): ############################################################# # Init and start loadable Modules ############################################################# - self.shng_status = {'code': 11, 'text': 'Starting: Initalizing and starting loadable modules'} + self.shng_status = {'code': 11, 'text': 'Starting: Initializing and starting loadable modules'} self._logger.info("Init loadable Modules") self.modules = lib.module.Modules(self, configfile=self._module_conf_basename) @@ -604,7 +643,7 @@ def start(self): ############################################################# # Init Plugins ############################################################# - self.shng_status = {'code': 12, 'text': 'Starting: Initalizing plugins'} + self.shng_status = {'code': 12, 'text': 'Starting: Initializing plugins'} self._logger.info("Init Plugins") self.plugins = lib.plugin.Plugins(self, configfile=self._plugin_conf_basename) @@ -835,7 +874,7 @@ def _maintenance(self): def _excepthook(self, typ, value, tb): mytb = "".join(traceback.format_tb(tb)) - self._logger.error("Unhandled exception: {1}\n{0}\n{2}".format(typ, value, mytb)) + self._logger.error(f"Unhandled exception: {value}\n{typ}\nrunning SmartHomeNG {self.version}\nException: {mytb}") def _garbage_collection(self): c = gc.collect() diff --git a/lib/systeminfo.py b/lib/systeminfo.py new file mode 100644 index 0000000000..151ba43eb7 --- /dev/null +++ b/lib/systeminfo.py @@ -0,0 +1,495 @@ +#!/usr/bin/env python3 +# -*- coding: utf8 -*- +######################################################################### +# Copyright 2023- Martin Sinn m.sinn@gmx.de +# Copyright 2016- René Frieß rene.friess@gmail.com +######################################################################### +# +# This plugin is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This plugin is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this plugin. If not, see . +######################################################################### + +import os +import logging +import sys +import platform +import subprocess + +try: + import utils + import cpuinfo +except: + import lib.utils as utils + import lib.cpuinfo as cpuinfo + +try: + import lib.shyaml as shyaml + yaml_support = True +except: + yaml_support = False + + +_logger = logging.getLogger(__name__) + + +def _run_and_get_stdout(command, pipe_command=None): + from subprocess import Popen, PIPE + + # Run the command normally + if not pipe_command: + p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + # Run the command and pipe it into another command + else: + p2 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) + p1 = Popen(pipe_command, stdin=p2.stdout, stdout=PIPE, stderr=PIPE) + p2.stdout.close() + + # Get the stdout and stderr + stdout_output, stderr_output = p1.communicate() + stdout_output = stdout_output.decode(encoding='UTF-8') + stderr_output = stderr_output.decode(encoding='UTF-8') + + return p1.returncode, stdout_output + + +def _get_field_actual(cant_be_number, raw_string, field_names): + for line in raw_string.splitlines(): + for field_name in field_names: + field_name = field_name.lower() + if ':' in line: + left, right = line.split(':', 1) + left = left.strip().lower() + right = right.strip() + if left == field_name and len(right) > 0: + if cant_be_number: + if not right.isdigit(): + return right + else: + return right + + return None + +def _get_field(cant_be_number, raw_string, convert_to, default_value, *field_names): + retval = _get_field_actual(cant_be_number, raw_string, field_names) + + # Convert the return value + if retval and convert_to: + try: + retval = convert_to(retval) + except Exception: + retval = default_value + + # Return the default if there is no return value + if retval is None: + retval = default_value + + return retval + + +# ========================================================================================== + +class Systeminfo: + + @classmethod + def read_linuxinfo(cls): + """ + Read info from /etc/os-release + + e.g.: + PRETTY_NAME="Debian GNU/Linux 9 (stretch)" + NAME="Debian GNU/Linux" + VERSION_ID="9" + VERSION="9 (stretch)" + ID=debian + HOME_URL="https://www.debian.org/" + SUPPORT_URL="https://www.debian.org/support" + BUG_REPORT_URL="https://bugs.debian.org/" + + or: + PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" + NAME="Raspbian GNU/Linux" + VERSION_ID="10" + VERSION="10 (buster)" + VERSION_CODENAME=buster + ID=raspbian + ID_LIKE=debian + HOME_URL="http://www.raspbian.org/" + SUPPORT_URL="http://www.raspbian.org/RaspbianForums" + BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs" + + """ + os_release = {} + pf = platform.system().lower() + if pf == 'linux': + if os_release == {}: + try: + with open('/etc/os-release') as fp: + for line in fp: + if line.startswith('#'): + continue + key, val = line.strip().split('=') + os_release[key] = val + except: + os_release = {} + return os_release + + + @classmethod + def read_macosinfo(cls): + + output = subprocess.Popen(["sw_vers", ], stdout=subprocess.PIPE).communicate() + os, vers, build, extra = output[0].decode().split('\n') + os = os.split('\t')[2] + vers = vers.split('\t')[2] + build = build.split('\t')[2] + os_release = os + ' ' + vers + ' (build ' + build + ')' + return os_release + + # --------- + + @classmethod + def get_ostype(cls): + pf = platform.system().lower() + return pf + + + @classmethod + def get_osflavor(cls): + pf = platform.system().lower() + + if pf == 'linux': + os_release = cls.read_linuxinfo() + if os_release == {}: + return pf + return os_release.get('ID', 'linux') + else: + return '' + + + @classmethod + def get_osname(cls): + pf = platform.system().lower() + + if pf == 'linux': + os_release = cls.read_linuxinfo() + if os_release == {}: + return pf + return utils.Utils.strip_quotes(os_release.get('PRETTY_NAME', 'Linux')) + elif pf == 'darwin': + os_release = cls.read_macosinfo() + return os_release + else: + return pf + + + @classmethod + def get_oskernelversion(cls): + + return getattr(platform.uname(),'release') + + + @classmethod + def get_osversion(cls): + + pf = platform.system().lower() + + if pf == 'linux': + os_release = cls.read_linuxinfo() + if os_release == {}: + return pf + return utils.Utils.strip_quotes(os_release.get('VERSION_ID', '?')) + elif pf == 'darwin': + return platform.platform().split('-')[1] + else: + return '' + + + # ========================================================================================== + + cpuinfo_dict = None + + + @classmethod + def ensure_cpuinfo(cls): + if cls.cpuinfo_dict is None: + cls.cpuinfo_dict = cpuinfo.get_cpu_info() + return + + + @classmethod + def get_cpuinfo(cls): + cls.ensure_cpuinfo() + return cls.cpuinfo_dict + + + @classmethod + def get_cpuarch(cls): + cls.ensure_cpuinfo() + return cls.cpuinfo_dict.get('arch_string_raw', '') + #return cls.cpuinfo_dict.get('arch', '') + + + @classmethod + def get_cpubrand(cls): + cls.ensure_cpuinfo() + return cls.cpuinfo_dict.get('brand_raw', '') + + + @classmethod + def get_cpucores(cls): + cls.ensure_cpuinfo() + return cls.cpuinfo_dict.get('count', '') + + + @classmethod + def get_cpubits(cls): + cls.ensure_cpuinfo() + return cls.cpuinfo_dict.get('bits', '') + + + @classmethod + def get_cpu_speed(cls, var_dir): + if yaml_support: + # read previous results from yaml file + cls._systeminfo_dict = shyaml.yaml_load(os.path.join(var_dir, 'systeminfo.yaml'), ignore_notfound=True) + pass + try: + cpu_speed_class = cls._systeminfo_dict['systeminfo']['cpu_speed_class'] + if cls.get_cpubrand() == cls._systeminfo_dict['systeminfo']['cpu_brand']: + # return time, if cpu brand has not changed since stored measurement + return cpu_speed_class + except: + return None + return None # None = No previous measurement stored + + + cpu_duration = None + cpu_speed_class = None + + _systeminfo_dict = {} + + @classmethod + def measure_cpu_speed(cls): + """ + Measure the cpu speed to classify the machine (e.g. for scheduler configuration) + + :return: number of seconds to complete the calculation loop + + measured data for 50000 calculations: slow > 120sec > medium > 50sec > fast + + computer / cpu seconds measured by Python version + ------------------------------------------ ----------- ----------- -------------- + Raspi 2, ARMv7 rev 5 (v7l) 181.47 morg42 3.7.3 + + Raspberry Pi 3 119.83 sisamiwe 3.8.6 + Raspberry Pi 3 108.04 onkelandy 3.9.2 + Raspberry Pi 3 ARMv7 Processor rev 4 (v7l) 99.65 msinn 3.7.3 + Raspi 3B+, ARMv7 rev4 (v7l) 87.8 morg42 3.7.3 + + Raspberry Pi 4 41.09 onkelandy 3.9.2 + Raspberry Pi 4 36.61 sisamiwe 3.9.2 + NUC mit Celeron(R) CPU N2820 @ 2.13GHz 36.05 bmxp 3.9.2 + NUC mit Celeron(R) CPU N2830 @ 2.16GHz 34.88 bmxp 3.9.2 + Intel(R) Celeron(R) CPU J3455 @ 1.50GHz 23.49-26.35 msinn 3.8.3 + NUC mit Celeron(R) J4005 CPU @ 2.00GHz 17.96 bmxp 3.9.2 + E31265L 10.39 morg42 3.9.2 + i5-8600K 9.78 morg42 3.9.7 + """ + + import timeit + + _logger.notice(f"Testing cpu speed...") + + #cpu_speed = round(timeit.timeit('"|".join(str(i) for i in range(99999))', number=1000), 2) + cpu_duration = round(timeit.timeit('"|".join(str(i) for i in range(50000))', number=1000), 2) + + if cpu_duration > 120: + cpu_speed_class = 'slow' + elif cpu_duration > 50: + cpu_speed_class = 'medium' + else: + cpu_speed_class = 'fast' + + return cpu_duration, cpu_speed_class + + + @classmethod + def check_cpu_speed(cls, var_dir): + + if yaml_support: + # read previous results from yaml file + cls._systeminfo_dict = shyaml.yaml_load(os.path.join(var_dir, 'systeminfo.yaml'), ignore_notfound=True) + pass + + # execute speed test + cls.cpu_duration, cls.cpu_speed_class = cls.measure_cpu_speed() + cls._systeminfo_dict = {} + cls._systeminfo_dict['systeminfo'] = {} + cls._systeminfo_dict['systeminfo']['cpu_brand'] = cls.get_cpubrand() + cls._systeminfo_dict['systeminfo']['cpu_measured_time'] = cls.cpu_duration + cls._systeminfo_dict['systeminfo']['cpu_speed_class'] = cls.cpu_speed_class # slow / medium / fast + + if yaml_support: + + # write results to yaml file + shyaml.yaml_save(os.path.join(var_dir, 'systeminfo.yaml'), cls._systeminfo_dict) + return cls._systeminfo_dict['systeminfo']['cpu_speed_class'] + + # ========================================================================================== + + proc_cpuinfo = None + + + @classmethod + def ensure_proc_cpuinfo(cls): + if os.path.exists('/proc/cpuinfo'): + if cls.proc_cpuinfo is None: + returncode, output = _run_and_get_stdout(['cat', '/proc/cpuinfo']) + cls.proc_cpuinfo = output + else: + cls.proc_cpuinfo = '' + + + @classmethod + def running_on_rasppi(self): + """ + Returns True, if running on a Raspberry Pi + """ + if self.get_rasppi_revision() != '': +# return self.cpu_info.get('revision_raw', '') + return self.get_rasppi_revision() + return '' + + + @classmethod + def get_rasppi_hardware(cls): + cls.ensure_proc_cpuinfo() + return _get_field(False, cls.proc_cpuinfo, None, '', 'Hardware') + + + @classmethod + def get_rasppi_revision(cls): + cls.ensure_proc_cpuinfo() + return _get_field(False, cls.proc_cpuinfo, None, '', 'Revision') + + + @classmethod + def get_rasppi_serial(cls): + cls.ensure_proc_cpuinfo() + return _get_field(False, cls.proc_cpuinfo, None, '', 'Serial') + + + @classmethod + def get_rasppi_info(cls): + rev_info = { + '0002' : {'model': 'Model B Rev 1', 'ram': '256MB', 'revision': ''}, + '0003' : {'model': 'Model B Rev 1 - ECN0001 (no fuses, D14 removed)', 'ram': '256MB', 'revision': ''}, + '0004' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, + '0005' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, + '0006' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, + '0007' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, + '0008' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, + '0009' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, + '000d' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, + '000e' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, + '000f' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, + '0010' : {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, + '0013' : {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, + '900032': {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, + '0011' : {'model': 'Compute Modul', 'ram': '512MB', 'revision': ''}, + '0014' : {'model': 'Compute Modul', 'ram': '512MB', 'revision': '', 'manufacturer': 'Embest, China'}, + '0012' : {'model': 'Model A+', 'ram': '256MB', 'revision': ''}, + '0015' : {'model': 'Model A+', 'ram': '256MB/512MB', 'revision': '', 'manufacturer': 'Embest, China'}, + #'0015' : {'model': 'Model A+', 'ram': '512MB', 'revision': '', 'manufacturer': 'Embest, China'}, + + 'a01041': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, + 'a21041': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Embest, China'}, + 'a22042': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.2'}, + '900092': {'model': 'Pi Zero v1.2', 'ram': '512MB', 'revision': '1.2'}, + '900093': {'model': 'Pi Zero v1.3', 'ram': '512MB', 'revision': '1.3'}, + '9000C1': {'model': 'Pi Zero W', 'ram': '512MB', 'revision': '1.1'}, + + 'a02082': {'model': 'Pi 3 Model B', 'ram': '1GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, + 'a22082': {'model': 'Pi 3 Model B', 'ram': '1GB', 'revision': '1.2', 'manufacturer': 'Embest, China'}, + 'a020d3': {'model': 'Pi 3 Model B+', 'ram': '1GB', 'revision': '1.3', 'manufacturer': 'Sony, UK'}, + + 'a03111': {'model': 'Pi 4', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, + 'b03111': {'model': 'Pi 4', 'ram': '2GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, + 'b03112': {'model': 'Pi 4', 'ram': '2GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, + 'c03111': {'model': 'Pi 4', 'ram': '4GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, + 'c03112': {'model': 'Pi 4', 'ram': '4GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, + 'd03114': {'model': 'Pi 4', 'ram': '8GB', 'revision': '1.4', 'manufacturer': 'Sony, UK'}, + } + if cls.get_rasppi_revision() != '': + result = 'Raspberry ' + info = rev_info.get(cls.get_rasppi_revision(), {}) + if info == {}: + return result + 'Pi (Rev. ' + cls.get_rasppi_revision() + ')' + + if info.get('model', ''): + result += info.get('model', '') + else: + result += 'Pi' + if info.get('revision', ''): + result += ' v' + info.get('revision', '') + if info.get('ram', ''): + result += ', '+info.get('ram', '') + if info.get('manufacturer', ''): + result += ' (' + info.get('manufacturer', '') + ')' + return result + return '' + + +# ========================================================================================== + +def main(): + #info = cpuinfo._get_cpu_info_internal() + #print(type(info)) + + print("lib.systeminfo") + print() + print(f"Platform : {platform.platform()}") + print(f"cpuinfo version : {cpuinfo.CPUINFO_VERSION_STRING}") + print() + print('Operating System Info:') + print(f"- OS Prettyname : {Systeminfo.get_osname()}") + print(f"- OS Type : {Systeminfo.get_ostype()}") + print(f"- Kernel Version : {Systeminfo.get_oskernelversion()}") + print(f"- OS Distribution : {Systeminfo.get_osflavor()}") + print(f"- OS/Distro Version: {Systeminfo.get_osversion()}") + print() + print('Hardware Info:') + print(f"- CPU Architecture : {Systeminfo.get_cpuarch()}") + print(f"- CPU Brand : {Systeminfo.get_cpubrand()}") + print(f"- Processor : {Systeminfo.get_cpubits()}-bit") + print(f"- Cores : {Systeminfo.get_cpucores()}") + print() + print('SOC Info:') + print(f"- Running on RaspPi: {Systeminfo.running_on_rasppi()}") + print(f"- Hardware : {Systeminfo.get_rasppi_hardware()}") + print(f"- Revision : {Systeminfo.get_rasppi_revision()}") + print(f"- Serial : {Systeminfo.get_rasppi_serial()}") + print(f"- Board Model : {Systeminfo.get_rasppi_info()}") + print() + + Systeminfo.ensure_cpuinfo() + import pprint +# pprint.pprint(Systeminfo.get_cpuinfo()) +# print() + return + + +if __name__ == '__main__': + main() +#else: +# _check_arch() diff --git a/lib/translation.py b/lib/translation.py index 776050447a..bae3c31969 100644 --- a/lib/translation.py +++ b/lib/translation.py @@ -118,13 +118,19 @@ def load_translations(translation_type='global', from_dir='bin', translation_id= relative_filename = os.path.join(from_dir, 'locale' + YAML_FILE) filename = os.path.join(_base_dir, relative_filename) trans_dict = shyaml.yaml_load(filename, ordered=False, ignore_notfound=True) + if trans_dict != None: + logger.info(f"load_translations: translation_type={translation_type}, translation_id={translation_id} ") if translation_type == 'global': for translation_section in trans_dict.keys(): if translation_section.endswith('_translations'): trans_id = translation_section.split('_')[0].replace('.', '/') trans = trans_dict.get(translation_section, {}) _translations[trans_id] = trans + #if translation_id == 'global': + # _translations[trans_id] = trans + #else: + # _translations[trans_id].update(trans) logger.info("Loading {} translations (id={}) from {}".format(translation_type, trans_id, relative_filename)) logger.debug(" - translations = {}".format(trans)) else: @@ -146,7 +152,7 @@ def reload_translations(): """ Reload translations for existing translation_ids - to test new translations without having to restart SmartHomeNG """ - logger.info("reload_translations") + logger.notice("Reloading translations") for id in _translation_files: translation_type = _translation_files[id]['type'] filename = _translation_files[id]['filename'] @@ -163,26 +169,33 @@ def reload_translations(): trans = trans_dict.get(translation_type+'_translations', {}) logger.info("Reloading {} translations (id={}) from {}".format(translation_type, id, filename)) _translations[id] = trans - return + return True -def _get_translation(translation_lang, txt, additional_translations=None): +def _get_translation(translation_lang, txt, plugin_translations=None, module_translations=None): """ - Returns translated text from for a specified language from additional_translations or global_translations + Returns translated text from for a specified language from plugin_translations or global_translations :param translation_lang: Language to be used for translation :param txt: Text to be translated - :param additional_translations: Additional translation definitions (e.g. for plugins) + :param plugin_translations: Additional translation definitions :return: translated text or '' if translation is not found """ translations = {} - if additional_translations is not None: - #translations = additional_translations.get(txt, {}) - if additional_translations in _translations.keys(): - translations = _translations[additional_translations].get(txt, {}) + if plugin_translations is not None: + #translations = plugin_translations.get(txt, {}) + if plugin_translations in _translations.keys(): + translations = _translations[plugin_translations].get(txt, {}) + else: + logger.warning("Trying to use undefined additional_translations '{}'".format(plugin_translations)) + + if translations == {} and module_translations is not None: + #translations = module_translations.get(txt, {}) + if module_translations in _translations.keys(): + translations = _translations[module_translations].get(txt, {}) else: - logger.warning("Trying to use undefined aditional_translations '{}'".format(additional_translations)) + logger.warning("Trying to use undefined additional_translations '{}'".format(plugin_translations)) if translations == {}: if 'global' in _translations.keys(): @@ -195,25 +208,26 @@ def _get_translation(translation_lang, txt, additional_translations=None): return translations.get(translation_lang, None) -def translate(txt, vars= None, additional_translations=None): +def translate(txt, vars=None, plugin_translations=None, module_translations=None): """ Returns translated text :param txt: TEXT TO TRANSLATE :param vars: dict with variables to replace in the translated text - :param additional_translations: ID for additional translations (if None, only global translations are used) + :param plugin_translations: ID for additional translations (if None, only global translations are used) + :param module_translations: ID for additional translations (if None, only global and plugin translations are used) :return: Translated text """ global _fallback_language_order txt = str(txt) - translated_txt = _get_translation(_default_language, txt, additional_translations=additional_translations) + translated_txt = _get_translation(_default_language, txt, plugin_translations=plugin_translations, module_translations=module_translations) if translated_txt is None: logger.debug("translation of '{}' to language '{}' not found -> using fallback languages".format(txt, _default_language)) if len(_fallback_language_order) > 0: for fallback_language in _fallback_language_order: - translated_txt = _get_translation(fallback_language, txt, additional_translations=additional_translations) + translated_txt = _get_translation(fallback_language, txt, plugin_translations=plugin_translations, module_translations=module_translations) if translated_txt is None: logger.debug(" - No translation found for fallback_language '{}'".format(fallback_language)) else: @@ -227,8 +241,8 @@ def translate(txt, vars= None, additional_translations=None): translated_txt = txt logger.debug("Translation '{}' to '{}' -> '{}'".format(txt, _default_language, translated_txt)) + # if variable parameters are given, replace them in the translated text if vars is not None: - # replace parameters in the translated text if isinstance(vars, dict): logger.info("translate: Trying to use parameters {} for string '{}'".format(vars, translated_txt)) try: diff --git a/lib/triggertimes.py b/lib/triggertimes.py index 0a05bcaf40..dc5b8b48c9 100644 --- a/lib/triggertimes.py +++ b/lib/triggertimes.py @@ -78,7 +78,7 @@ class TriggerTimes(): - An instance is created during initialization by bin/smarthome.py - There should be only one instance of this class. So: Don't create another instance """ - # dict with all the items that are defined in the form: + # dict with all the items that are defined in the form: # {"*/5 6-19/1 * * *": crontab object, "* * 6 * : crontab object, ..."} def __init__(self, smarthome): @@ -89,9 +89,9 @@ def __init__(self, smarthome): self._sh = smarthome Skytime.set_smarthome_reference(smarthome) self.logger = logging.getLogger(__name__) - + # a list with objects containing trigger times - self.__known_triggertimes = [] + self.__known_triggertimes = [] global _triggertimes_instance if _triggertimes_instance is not None: @@ -198,12 +198,12 @@ class TriggerTime(): This provides a base class for all trigger times like crontabs, or sun/moonbound trigger times It is mainly to share the same basics and static methods """ - named_days = { + named_days = { 'mon':'0', 'tue':'1','wed':'2','thu':'3','fri':'4','sat':'5','sun':'6', - 'mo':'0', 'di':'1','mi':'2','do':'3','fr':'4','sa':'5','so':'6' + 'mo':'0', 'di':'1','mi':'2','do':'3','fr':'4','sa':'5','so':'6' } - named_months = { + named_months = { 'jan':'1', 'feb':'2','mar':'3','apr':'4','may':'5','jun':'6','jul':'7', 'aug': '8', 'sep': '9', 'oct': '10', 'nov': '11', 'dec': '12' } @@ -221,11 +221,11 @@ def get_triggertime( self): @staticmethod def integer_range(entry, low, high): """ - Inspects a string containing + Inspects a string containing * intervals ('*/2' --> ['2','4','6', ... high] - * ranges ('9-11' --> ['9','10','11']), + * ranges ('9-11' --> ['9','10','11']), * single values ('1,2,5,9' --> ['1','2','5','9']) - * or a combination of those and + * or a combination of those and returns a sorted and distinct list of found integers :param entry: a string with single entries of intervals, numeric ranges or single values @@ -291,7 +291,7 @@ class Crontab(TriggerTime): └───────────── minute (0 - 59) If 5 parts specified: - + * * * * * │ │ │ │ │ │ │ │ │ └───────────── weekday (0 - 6) @@ -301,7 +301,7 @@ class Crontab(TriggerTime): └───────────── minute (0 - 59) If 6 parts specified: - + * * * * * * │ │ │ │ │ │ │ │ │ │ │ └───────────── weekday (0 - 6) @@ -374,7 +374,7 @@ def parse_triggertime(self): except: logger.error(f"crontab entry '{triggertime}' can not be split up into parts") return False - + self.parameter_count = len(parameter_set) if self.parameter_count < 4: logger.error(f"crontab entry '{triggertime}' has fewer than 4 parts and is invalid") @@ -458,7 +458,7 @@ def get_next(self, starttime: datetime): #logger.warning(f"{searchtime}") days = abs((starttime-searchtime).days) if days > days_max_count: - logger.error(f'No matches after {days} examined days, giving up') + logger.error(f"No matches for '{self._triggertime}' after {days} examined days, giving up") return get_invalid_time() # preset current searcher year = searchtime.year @@ -738,14 +738,14 @@ def split_skyevents(triggertime: str): @staticmethod def split_offset(skyevent): """ - This parses offsets from a sky event. It is expected for the offset to be - a float when indicating degrees or + This parses offsets from a sky event. It is expected for the offset to be + a float when indicating degrees or an integer when unit ``m`` is appended :param skyevent: (skyevent)[+|-][offset][unit] :type skyevent: str :return: a tuple of a skyevent and offsets for degree and time (in minutes) - :rtype: tuple + :rtype: tuple """ doff = 0.0 # degree offset moff = 0 # minute offset @@ -899,7 +899,7 @@ def get_next(self, starttime: datetime): #logger.warning(f"difference {searchtime-starttime}") days = abs((searchtime-starttime).days) if days > days_max_count: - logger.error(f'No matches after {days} examined days, giving up') + logger.error(f"No matches for '{self._triggertime}' after {days} examined days, giving up") return get_invalid_time() # preset current searcher year = searchtime.year @@ -926,10 +926,10 @@ def get_next(self, starttime: datetime): else: logger.error(f'No function found to get next skyevent time for {self._triggertime}') return get_invalid_time() - + # eventtime will contain the next time e.g. a sunset will take place - # thus - # - searchtime must be smaller than eventtime and + # thus + # - searchtime must be smaller than eventtime and # - eventtime might be one or more day(s) later logger.debug(f"starting with {starttime} the next {self.event}({self.doff},{self.moff}) is {eventtime}") diff --git a/lib/utils.py b/lib/utils.py index 18002c5e3a..a3c3fc157d 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -212,6 +212,9 @@ def is_knx_groupaddress(groupaddress): :return: True if a groupaddress can be recognized, false otherwise. :rtype: bool """ + if not isinstance(groupaddress, str): + return False + if groupaddress == '': return True diff --git a/modules/admin/__init__.py b/modules/admin/__init__.py index e498f257ac..77d681067d 100644 --- a/modules/admin/__init__.py +++ b/modules/admin/__init__.py @@ -108,6 +108,7 @@ def __init__(self, sh, testparam=''): self.websocket_port = self._parameters['websocket_port'] self.log_chunksize = self._parameters['log_chunksize'] self.developer_mode = self._parameters['developer_mode'] + self.rest_dispatch_force_exception = self._parameters['rest_dispatch_force_exception'] self.click_dropdown_header = self._parameters['click_dropdown_header'] except: self.logger.critical( @@ -334,7 +335,7 @@ def __init__(self, webif_dir, module, shng_url_root, url_root): self.shng_url_root = shng_url_root self.url_root = url_root - SystemData.__init__(self) + SystemData.__init__(self, self._sh) ItemData.__init__(self) PluginData.__init__(self) diff --git a/modules/admin/api_auth.py b/modules/admin/api_auth.py index 16ad273b63..d14599e2f4 100644 --- a/modules/admin/api_auth.py +++ b/modules/admin/api_auth.py @@ -47,6 +47,12 @@ def __init__(self, module): self.etc_dir = self._sh._etc_dir self.modules_dir = os.path.join(self.base_dir, 'modules') + if self.module.rest_dispatch_force_exception: + self.REST_dispatch_execute_warnlevel = 'EXCEPTION' + self.logger.notice(f"REST_dispatch_execute warnlevel is set to EXCEPTION") + else: + self.REST_dispatch_execute_warnlevel = 'WARNING' + #self._user_dict = user_dict self.send_hash = module.send_hash self.jwt_secret = module.jwt_secret @@ -158,7 +164,10 @@ def renew_token(self): if self.module.login_autorenew: new_token['iat'] = self.module.shtime.now() new_token['exp'] = self.module.shtime.now() + timedelta(hours=self.module.login_expiration) - response['token'] = jwt.encode(new_token, self.jwt_secret, algorithm='HS256').decode('utf-8') + try: + response['token'] = jwt.encode(new_token, self.jwt_secret, algorithm='HS256').decode('utf-8') + except: + response['token'] = jwt.encode(new_token, self.jwt_secret, algorithm='HS256') decoded = jwt.decode(response['token'], self.jwt_secret, verify=True, algorithms='HS256') self.logger.debug("- renew_token(): re-decoded token = {}".format(decoded)) diff --git a/modules/admin/api_loggers.py b/modules/admin/api_loggers.py index 2a21d78704..ef4b93221e 100644 --- a/modules/admin/api_loggers.py +++ b/modules/admin/api_loggers.py @@ -56,13 +56,14 @@ def __init__(self, module): return - def save_logging_config(self): + def save_logging_config(self, create_backup=False): """ Save dict to logging.yaml """ if self.logging_config is not None: + self.logging_config['shng_version'] = self._sh.version.split('-')[0][1:] conf_filename = os.path.join(self.etc_dir, 'logging') - shyaml.yaml_save_roundtrip(conf_filename, self.logging_config, create_backup=False) + shyaml.yaml_save_roundtrip(conf_filename, self.logging_config, create_backup=create_backup) return @@ -86,12 +87,12 @@ def load_logging_config_for_edit(self): """ conf_filename = os.path.join(self.etc_dir, 'logging') self.logging_config = shyaml.yaml_load_roundtrip(conf_filename) - self.logger.notice("load_logging_config: shng_version={}".format(self.logging_config.get('shng_version', None))) + self.logger.info("load_logging_config: shng_version={}".format(self.logging_config.get('shng_version', None))) if self.logging_config.get('shng_version', None) is None: - self.create_backupfile(conf_filename) - self.logging_config['shng_version'] = 'x' - self.save_logging_config() + self.logging_config['shng_version'] = self._sh.version.split('-')[0][1:] + #self.create_backupfile(conf_filename) + self.save_logging_config(create_backup=True) return @@ -129,11 +130,12 @@ def get_active_loggers(self): def set_active_logger_level(self, logger, level): if level is not None: - self.logger.notice("set_active_logger_level(): logger={}, level={}".format(logger, level)) lg = logging.getLogger(logger) lglevel = logging.getLevelName(level) - self.logger.notice("set_active_logger_level(): lg={}, lglevel={}".format(lg, lglevel)) + oldlevel = logging.getLevelName(lg.level) + lg.setLevel(lglevel) + self.logger.notice(f"Logger '{logger}' changed from {oldlevel} to {level}") self.load_logging_config_for_edit() try: @@ -141,9 +143,9 @@ def set_active_logger_level(self, logger, level): except: oldlevel = None if oldlevel != None: - self.logger.notice(" - old level={}".format(oldlevel)) self.logging_config['loggers'][logger]['level'] = level self.save_logging_config() + #self.logger.info("Saved changed logger configuration to ../etc/logging.yaml}") return True return False diff --git a/modules/admin/api_logs.py b/modules/admin/api_logs.py index b09a82a181..c2f321266e 100644 --- a/modules/admin/api_logs.py +++ b/modules/admin/api_logs.py @@ -82,7 +82,7 @@ def get_logs_with_files(self): logs = {} for fn in self.files: fnl = fn.split('.') - if (fnl[1] == 'log') and (len(fnl) == 2): + if (len(fnl) == 2) and (fnl[1] == 'log'): log_name = fnl[0] logfiles = self.get_files_of_log(log_name) diff --git a/modules/admin/api_plugin.py b/modules/admin/api_plugin.py index e6ef57c79a..78ecc67657 100644 --- a/modules/admin/api_plugin.py +++ b/modules/admin/api_plugin.py @@ -117,7 +117,7 @@ def read(self, id=None): if id is not None: for confplg in _conf: if (confplg == id) or (id == None): - self.logger.info("PluginController(): index('{}') - confplg {}".format(id, confplg)) + self.logger.info(f"PluginController(): index('{id}') - confplg {confplg}") info['config'] = _conf[confplg] plg_found = True diff --git a/modules/admin/api_plugins.py b/modules/admin/api_plugins.py index 8eafc46248..61593bf483 100644 --- a/modules/admin/api_plugins.py +++ b/modules/admin/api_plugins.py @@ -231,7 +231,6 @@ def read(self, id=None): # get path to plugin configuration file, withou extension _conf = lib.config.parse_basename(os.path.splitext(config_filename)[0], configtype='plugin') - for confplg in _conf: plg = _conf[confplg].get('plugin_name', '?') if plg == '?': @@ -336,7 +335,8 @@ def _test_for_blog_articles_task(self): if isinstance(plugin, SmartPlugin): plugin_name = plugin.get_shortname() if temp_blog_urls.get(plugin_name, None) is None: - # add link to blog, if articles exist, that have the pluginname as a tag + # Link to Blog: + # add link to blog, if articles exist, that have the pluginname as a tag # example: Blog articles with tag 'backend' # - https://www.smarthomeng.de/tag/backend # alternative example: Blog articles with category 'plugins' and tag 'backend' @@ -388,6 +388,12 @@ def read(self, id=None): conf_plugins[plugin] = {} conf_plugins[plugin] = _conf[plugin] + # Determine the base url for documentation (config and user_doc) + if self._sh.branch == 'develop': + documentation_base_url = 'https://smarthomeng.github.io/dev_doc/' + else: + documentation_base_url = 'https://smarthomeng.github.io/smarthome/' + #self._test_for_blog_articles() plugin_list = [] for x in self.plugins.return_plugins(): @@ -458,11 +464,24 @@ def read(self, id=None): plugin['metadata']['description'] = x._metadata.get_mlstring('description') plugin['metadata']['description_long'] = x._metadata.get_mlstring('description_long') plugin['metadata']['keywords'] = x._metadata.get_string('keywords') + # documentation link from metadata plugin['metadata']['documentation'] = x._metadata.get_string('documentation') + if plugin['metadata']['documentation'].endswith(f"plugins/{plugin['pluginname']}/user_doc.html"): + plugin['metadata']['documentation'] = '' + elif plugin['metadata']['documentation'].endswith(f"plugins_doc/config/{plugin['pluginname']}.html"): + plugin['metadata']['documentation'] = '' plugin['metadata']['support'] = x._metadata.get_string('support') plugin['metadata']['maintainer'] = x._metadata.get_string('maintainer') plugin['metadata']['tester'] = x._metadata.get_string('tester') + # construct urls to config page and user_doc page + plugin['documentation_config_doc'] = '' + plugin['documentation_user_doc'] = '' + if plugin['smartplugin'] and not(plugin['pluginname'].startswith('priv_')): + plugin['documentation_config_doc'] = documentation_base_url + f"plugins_doc/config/{plugin['pluginname']}.html" + if os.path.isfile(os.path.join(self.plugins_dir, plugin['pluginname'], 'user_doc.rst')): + plugin['documentation_user_doc'] = documentation_base_url + f"plugins/{plugin['pluginname']}/user_doc.html" + try: plugin['stopped'] = not x.alive plugin['stoppable'] = True diff --git a/modules/admin/api_services.py b/modules/admin/api_services.py index c4c31437db..ea92f74746 100755 --- a/modules/admin/api_services.py +++ b/modules/admin/api_services.py @@ -112,7 +112,8 @@ def eval_syntax_checker(self, eval_code, relative_to): rel_to_item = items.return_item(relative_to) if rel_to_item is not None: expanded_code = rel_to_item.get_stringwithabsolutepathes(eval_code, 'sh.', '(') - expanded_code = rel_to_item.get_stringwithabsolutepathes(expanded_code, 'sh.', '.property') + # Aufruf mit '.property' ist überflüssig + #expanded_code = rel_to_item.get_stringwithabsolutepathes(expanded_code, 'sh.', '.property') value = rel_to_item() # item value for use in eval else: expanded_code = "Error: Item {} does not exist!".format(relative_to) diff --git a/modules/admin/itemdata.py b/modules/admin/itemdata.py index 54b5f86e5f..b0a1bc504e 100644 --- a/modules/admin/itemdata.py +++ b/modules/admin/itemdata.py @@ -238,7 +238,7 @@ def item_detail_json_html(self, item_path): 'trigger_condition': self.disp_str(item._trigger_condition), 'trigger_condition_raw': self.disp_str(self._trigger_condition_raw), 'on_update': html.escape(self.list_to_displaystring(on_update_list)), - 'on_change': html.escape(self.list_to_displaystring(on_change_list)), + 'on_change': html.escape(self.list_to_displaystring(str(on_change_list))), 'log_change': self.disp_str(item._log_change), 'log_level': self.disp_str(item._log_level_name), 'log_text': self.disp_str(item._log_text), diff --git a/modules/admin/module.yaml b/modules/admin/module.yaml index dea4428b68..d8a9a9a1bf 100644 --- a/modules/admin/module.yaml +++ b/modules/admin/module.yaml @@ -85,6 +85,13 @@ parameters: de: 'Entwickler Modus aktivieren' en: 'Activate developer mode' + rest_dispatch_force_exception: + type: bool + default: False + description: + de: 'Sollen WARNINGs aus REST_dispatch_execute als EXECPTION geloggt werden?' + en: 'Activate developer mode' + click_dropdown_header: type: bool default: False diff --git a/modules/admin/rest.py b/modules/admin/rest.py index 7ded3bb72e..7cb3fbcc44 100644 --- a/modules/admin/rest.py +++ b/modules/admin/rest.py @@ -225,6 +225,7 @@ def REST_create(self, username): with the post with the slug 'my-first-post' that is owned by bob. """ + REST_dispatch_execute_warnlevel = 'WARNING' # default method mapping. ie, if a GET request is made for # the resource's url, it will try to call an index() method (if it exists); @@ -344,7 +345,11 @@ def REST_dispatch_execute(self, m, method, root, resource, **params): try: return m(resource, **params) except Exception as e: - self.logger.warning("REST_dispatch_execute: {}: {}".format(resource, e)) + if self.REST_dispatch_execute_warnlevel == 'WARNING': + self.logger.warning("REST_dispatch_execute: {}: {}".format(resource, e)) + else: + self.logger.notice("The following exception is thrown, due to the configuration in etc/module.yaml:") + self.logger.exception("REST_dispatch_execute: {}: {}".format(resource, e)) response = {'result': 'error', 'description': format(e)} return json.dumps(response) return None diff --git a/modules/admin/systemdata.py b/modules/admin/systemdata.py index 7719ba2e71..a2c7927474 100644 --- a/modules/admin/systemdata.py +++ b/modules/admin/systemdata.py @@ -42,165 +42,24 @@ from lib.utils import Utils, running_virtual import lib.config import lib.daemon +import lib.systeminfo as systeminfo class SystemData: - def __init__(self): + def __init__(self, sh): + self._sh = sh self.logger = logging.getLogger(__name__) self.logger = logging.getLogger('modules.admin.systemdata') self.logger.debug("Systemdata.__init__()") - - self.os_release = {} - self.cpu_info = {} self.pypi_sorted_package_list = [] self.shpypi = Shpypi.get_instance() - self.read_cpuinfo() + #self.read_cpuinfo() return - def read_linuxinfo(self): - """ - Read info from /etc/os-release - - e.g.: - PRETTY_NAME="Debian GNU/Linux 9 (stretch)" - NAME="Debian GNU/Linux" - VERSION_ID="9" - VERSION="9 (stretch)" - ID=debian - HOME_URL="https://www.debian.org/" - SUPPORT_URL="https://www.debian.org/support" - BUG_REPORT_URL="https://bugs.debian.org/" - - or: - PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" - NAME="Raspbian GNU/Linux" - VERSION_ID="10" - VERSION="10 (buster)" - VERSION_CODENAME=buster - ID=raspbian - ID_LIKE=debian - HOME_URL="http://www.raspbian.org/" - SUPPORT_URL="http://www.raspbian.org/RaspbianForums" - BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs" - - """ - self.os_release = {} - pf = platform.system().lower() - if pf == 'linux': - if self.os_release == {}: - try: - with open('/etc/os-release') as fp: - for line in fp: - if line.startswith('#'): - continue - key, val = line.strip().split('=') - self.os_release[key] = val - except: - self.os_release = {} - return - - - def read_cpuinfo(self): - import lib.cpuinfo - self.cpu_info = lib.cpuinfo._get_cpu_info_internal() - - - def running_on_rasppi(self): - """ - Returns True, if running on a Raspberry Pi - """ - if self.cpu_info.get('vendor_id_raw', '') == 'ARM': - if self.cpu_info.get('revision_raw', '') != '': - return self.cpu_info.get('revision_raw', '') - return '' - - - def decode_rasppi_revcode(self, revcode): - """ - NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR - - NOQu uuWu FMMM CCCC PPPP TTTT TTTT RRRR - - :param revcode: - :return: - """ - - def get_rasppi_info(self): - rev_info = { - '0002' : {'model': 'Model B Rev 1', 'ram': '256MB', 'revision': ''}, - '0003' : {'model': 'Model B Rev 1 - ECN0001 (no fuses, D14 removed)', 'ram': '256MB', 'revision': ''}, - '0004' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, - '0005' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, - '0006' : {'model': 'Model B Rev 2', 'ram': '256MB', 'revision': ''}, - '0007' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, - '0008' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, - '0009' : {'model': 'Model A', 'ram': '256MB', 'revision': ''}, - '000d' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, - '000e' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, - '000f' : {'model': 'Model B Rev 2', 'ram': '512MB', 'revision': ''}, - '0010' : {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, - '0013' : {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, - '900032': {'model': 'Model B+', 'ram': '512MB', 'revision': ''}, - '0011' : {'model': 'Compute Modul', 'ram': '512MB', 'revision': ''}, - '0014' : {'model': 'Compute Modul', 'ram': '512MB', 'revision': '', 'manufacturer': 'Embest, China'}, - '0012' : {'model': 'Model A+', 'ram': '256MB', 'revision': ''}, - '0015' : {'model': 'Model A+', 'ram': '256MB/512MB', 'revision': '', 'manufacturer': 'Embest, China'}, - #'0015' : {'model': 'Model A+', 'ram': '512MB', 'revision': '', 'manufacturer': 'Embest, China'}, - - 'a01041': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, - 'a21041': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Embest, China'}, - 'a22042': {'model': 'Pi 2 Model B', 'ram': '1GB', 'revision': '1.2'}, - '900092': {'model': 'Pi Zero v1.2', 'ram': '512MB', 'revision': '1.2'}, - '900093': {'model': 'Pi Zero v1.3', 'ram': '512MB', 'revision': '1.3'}, - '9000C1': {'model': 'Pi Zero W', 'ram': '512MB', 'revision': '1.1'}, - - 'a02082': {'model': 'Pi 3 Model B', 'ram': '1GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, - 'a22082': {'model': 'Pi 3 Model B', 'ram': '1GB', 'revision': '1.2', 'manufacturer': 'Embest, China'}, - 'a020d3': {'model': 'Pi 3 Model B+', 'ram': '1GB', 'revision': '1.3', 'manufacturer': 'Sony, UK'}, - - 'a03111': {'model': 'Pi 4', 'ram': '1GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, - 'b03111': {'model': 'Pi 4', 'ram': '2GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, - 'b03112': {'model': 'Pi 4', 'ram': '2GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, - 'c03111': {'model': 'Pi 4', 'ram': '4GB', 'revision': '1.1', 'manufacturer': 'Sony, UK'}, - 'c03112': {'model': 'Pi 4', 'ram': '4GB', 'revision': '1.2', 'manufacturer': 'Sony, UK'}, - 'd03114': {'model': 'Pi 4', 'ram': '8GB', 'revision': '1.4', 'manufacturer': 'Sony, UK'}, - } - if self.running_on_rasppi(): - result = 'Raspberry ' - info = rev_info.get(self.running_on_rasppi(), {}) - if info == {}: - return result + 'Pi (Rev. ' + self.running_on_rasppi() + ')' - - if info.get('model', ''): - result += info.get('model', '') - else: - result += 'Pi' - if info.get('revision', ''): - result += ' v' + info.get('revision', '') - if info.get('ram', ''): - result += ', '+info.get('ram', '') - if info.get('manufacturer', ''): - result += ' (' + info.get('manufacturer', '') + ')' - return result - return '' - - - def get_ostype(self): - pf = platform.system().lower() - - if pf == 'linux': - self.read_linuxinfo() - if self.os_release == {}: - return pf - return self.os_release.get('ID', 'linux') - else: - return pf - - # ----------------------------------------------------------------------------------- # SYSTEMINFO - Old Interface methods (from backend) # ----------------------------------------------------------------------------------- @@ -217,8 +76,9 @@ def systeminfo_json(self): now = str(self.module.shtime.now()) system = platform.system() - self.read_linuxinfo() - vers = Utils.strip_quotes(self.os_release.get('PRETTY_NAME', '')) + #self.read_linuxinfo() + #vers = Utils.strip_quotes(self.os_release.get('PRETTY_NAME', '')) + vers = self._sh.systeminfo.get_osname() if vers == '': vers = platform.version() arch = platform.machine() @@ -247,7 +107,8 @@ def systeminfo_json(self): response = {} response['now'] = now - response['ostype'] = self.get_ostype() + response['ostype'] = self._sh.systeminfo.get_ostype() + response['osflavor'] = self._sh.systeminfo.get_osflavor() response['system'] = system response['sh_vers'] = shngversion.get_shng_version() response['sh_desc'] = shngversion.get_shng_description() @@ -260,18 +121,18 @@ def systeminfo_json(self): response['arch'] = arch response['user'] = user response['freespace'] = freespace - response['hardware'] = self.get_rasppi_info() + response['hardware'] = self._sh.systeminfo.get_rasppi_info() if response['hardware'] == '': - response['hardware'] = self.cpu_info.get('brand_raw', '') - response['rasppi'] = self.running_on_rasppi() + response['hardware'] = self._sh.systeminfo.get_cpubrand() + response['rasppi'] = self._sh.systeminfo.running_on_rasppi() + #response['hwspeed'] = '' + if self._sh.systeminfo.cpu_speed_class is not None: + response['hwspeed'] = self._sh.systeminfo.cpu_speed_class response['uptime'] = time.mktime(datetime.datetime.now().timetuple()) - psutil.boot_time() response['sh_uptime'] = sh_runtime_seconds response['pyversion'] = pyversion - if self._sh.python_bin == '': - response['pypath'] = sys.executable - else: - response['pypath'] = self._sh.python_bin + response['pypath'] = self._sh.python_bin response['pyvirtual'] = lib.utils.running_virtual() response['ip'] = ip response['ipv6'] = ipv6 diff --git a/modules/admin/webif/static/assets/i18n/de.json b/modules/admin/webif/static/assets/i18n/de.json index f994b5fc41..0e88ddc210 100644 --- a/modules/admin/webif/static/assets/i18n/de.json +++ b/modules/admin/webif/static/assets/i18n/de.json @@ -336,7 +336,6 @@ "PARAMETERS": "Parameter", "PARAMETER": "Parameter", "VALUE": "Wert", - "DESCRIPTION_INSTANCE_ATTRIBUTE": "Falls mehrere Instanzen eines Multi-Instance Plugins konfiguriert sind, muss hier ein eindeutiger Instanz-Name angegeben werden (eine Instanz darf ohnen Namen bleiben). Falls nur eine Instanz konfiguriert ist, sollte hier kein Name vergeben werden.", "ITEM ATTRIBUTES": "Item Attribute", "UPDATES TRIGGERED BY": "Plugin Updates werden durch {{count}} Items getriggered", "PLUGIN METADATA": "Plugin Metadata", @@ -367,6 +366,7 @@ "OPEN_WEBIF": "Webinterface öffnen", "OPEN_CONFIGINFO": "Konfigurationsdokumentation öffnen", "OPEN_DOC": "Plugin-Dokumentation öffnen", + "OPEN_ADDITIONAL": "zusätzliche Plugin-Dokumentation öffnen", "OPEN_SUPPORT_THREAD": "Support Thread aufrufen", "OPEN_BLOGS": "Blog Artikel öffnen" }, diff --git a/modules/admin/webif/static/assets/i18n/en.json b/modules/admin/webif/static/assets/i18n/en.json index 49f34e5091..a5161b46ae 100644 --- a/modules/admin/webif/static/assets/i18n/en.json +++ b/modules/admin/webif/static/assets/i18n/en.json @@ -336,7 +336,6 @@ "PARAMETERS": "Parameters", "PARAMETER": "Parameter", "VALUE": "Value", - "DESCRIPTION_INSTANCE_ATTRIBUTE": "If several instances of a multi-instance plugin are configured, a unique instance name must be specified here (one instance may remain without a name). If only one instance is configured, no name should be assigned here.", "ITEM ATTRIBUTES": "Item Attributes", "UPDATES TRIGGERED BY": "Plugin updates are triggered by {{count}} items", "PLUGIN METADATA": "Plugin Metadata", @@ -367,6 +366,7 @@ "OPEN_WEBIF": "Open webinterface", "OPEN_CONFIGINFO": "Open configuration documentation", "OPEN_DOC": "Open plugin documentation", + "OPEN_ADDITIONAL": "Open additional plugin dokumentation", "OPEN_SUPPORT_THREAD": "Open support thread", "OPEN_BLOGS": "Open blogs articles" }, diff --git a/modules/admin/webif/static/assets/i18n/fr.json b/modules/admin/webif/static/assets/i18n/fr.json index 1994e408b0..63a59488e7 100644 --- a/modules/admin/webif/static/assets/i18n/fr.json +++ b/modules/admin/webif/static/assets/i18n/fr.json @@ -333,7 +333,6 @@ "PARAMETERS": "Paramètres", "PARAMETER": "Paramètre", "VALUE": "Valeur", - "DESCRIPTION_INSTANCE_ATTRIBUTE": "Si plusieurs instances d'un plug-in multi-instance sont configurées, un nom d'instance unique doit être spécifié ici (une instance peut rester sans nom). Si une seule instance est configurée, aucun nom ne doit être attribué ici.", "ITEM ATTRIBUTES": "Attributs de l'objet", "UPDATES TRIGGERED BY": "Mises à jour de l'extension déclenchées par {{count}} objets", "PLUGIN METADATA": "Métadonnées de l'extension", @@ -364,6 +363,7 @@ "OPEN_WEBIF": "Ouvrir interface web", "OPEN_CONFIGINFO": "Ouvrir documentation de configuration", "OPEN_DOC": "Ouvrir documentation de l'extension", + "OPEN_ADDITIONAL": "Ouvrir la documentation supplémentaire du plugin", "OPEN_SUPPORT_THREAD": "Ouvrir fil de support", "OPEN_BLOGS": "Ouvrir Articles de blogs" diff --git a/modules/admin/webif/static/assets/img/debian.svg b/modules/admin/webif/static/assets/img/debian.svg index eb60bfddd5..8003316041 100644 --- a/modules/admin/webif/static/assets/img/debian.svg +++ b/modules/admin/webif/static/assets/img/debian.svg @@ -1,6 +1,6 @@ - + diff --git a/modules/admin/webif/static/assets/testdata/api/logs/default.json b/modules/admin/webif/static/assets/testdata/api/logs/default.json index cd1a175602..36c9841216 100644 --- a/modules/admin/webif/static/assets/testdata/api/logs/default.json +++ b/modules/admin/webif/static/assets/testdata/api/logs/default.json @@ -336,7 +336,7 @@ 17.9 ], [ - "smarthome-warnings.2019-03-10.log", + "smarthome-warnings.log.2019-03-10", 7.4 ], [ diff --git a/modules/admin/webif/static/assets/testdata/api/plugins/config/default.json b/modules/admin/webif/static/assets/testdata/api/plugins/config/default.json index f0b3d95337..aa928bdb3e 100644 --- a/modules/admin/webif/static/assets/testdata/api/plugins/config/default.json +++ b/modules/admin/webif/static/assets/testdata/api/plugins/config/default.json @@ -110,11 +110,12 @@ } }, "cli": { - "class_path": "plugins.cli", + "plugin_name": "cli", "ip": "0.0.0.0", "port": "2323", "update": "True", "hashed_password": "none", + "webif_pagelength": "25", "_meta": { "plugin": { "type": "system", @@ -128,7 +129,7 @@ "state": "qa-passed", "version": "1.4.2", "sh_minversion": 1.4, - "multi_instance": false, + "multi_instance": true, "restartable": "unknown", "classname": "CLI" }, @@ -181,6 +182,30 @@ "foo" ], "listlen": 0 + }, + "webif_pagelength": { + "type": "int", + "default": 0, + "valid_list": [-1, 0, 25, 50, 100], + "description": { + "de": "... [webif_pagelength] ...", + "en": "... [webif_pagelength] ..." + }, + "listtype": [ + "foo" + ], + "listlen": 0 + }, + "instance": { + "type": "str", + "description": { + "de": "... [instance] ...", + "en": "... [instance] ..." + }, + "listtype": [ + "foo" + ], + "listlen": 0 } }, "item_attributes": "NONE", diff --git a/modules/admin/webif/static/assets/testdata/api/plugins/info/default.json b/modules/admin/webif/static/assets/testdata/api/plugins/info/default.json index 3667f8ab07..fac50d0a26 100644 --- a/modules/admin/webif/static/assets/testdata/api/plugins/info/default.json +++ b/modules/admin/webif/static/assets/testdata/api/plugins/info/default.json @@ -7,6 +7,8 @@ "multiinstance": true, "instancename": "willy_tel", "webif_url": "http://smarthomeng.local:1234/avm_willy_tel/", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/avm.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/avm/user_doc.html", "blog_url": "https://www.smarthomeng.de/tag/avm", "parameters": [ { @@ -100,6 +102,8 @@ "multiinstance": true, "instancename": "fritz_wz", "webif_url": "http://smarthomeng.fritz.box:1234/avm_fritz_wz/", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/avm.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/avm/user_doc.html", "blog_url": "", "parameters": [ { @@ -191,6 +195,8 @@ "multiinstance": false, "instancename": "", "webif_url": "http://smarthomeng.fritz.box:1234/backend/", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/backend.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/backend/user_doc.html", "blog_url": "https://www.smarthomeng.de/tag/backend", "parameters": [], "metadata": { @@ -215,16 +221,19 @@ "configname": "cli", "version": "1.4.2", "smartplugin": true, - "multiinstance": false, + "multiinstance": true, "instancename": "", "webif_url": "", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/cli.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/cli/user_doc.html", "parameters": [ { "name": "update", "type": "bool", "value": "True", "default": "False" - }, { + }, + { "name": "ip", "type": "ip", "value": "0.0.0.0", @@ -241,6 +250,12 @@ "type": "str", "value": "None", "default": "None" + }, + { + "name": "webif_pagelength", + "type": "int", + "value": 25, + "default": 0 } ], "metadata": { @@ -266,6 +281,8 @@ "multiinstance": true, "instancename": "", "webif_url": "http://smarthomeng.fritz.box:1234/database/", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/database.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/database/user_doc.html", "parameters": [], "metadata": { "type": "system", @@ -288,6 +305,8 @@ "multiinstance": true, "instancename": "hm1", "webif_url": "http://smarthomeng.fritz.box:1234/homematic_hm1/", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/homematic/avm.html", + "documentation_user_doc": "", "parameters": [], "metadata": { "type": "system", @@ -310,6 +329,8 @@ "multiinstance": true, "instancename": "", "webif_url": "", + "documentation_config_doc": "https://smarthomeng.github.io/dev_doc/plugins_doc/config/visu_smartvisu.html", + "documentation_user_doc": "https://smarthomeng.github.io/dev_doc/plugins/visu_smartvisu/user_doc.html", "parameters": [], "metadata": { "type": "system", diff --git a/modules/admin/webif/static/assets/testdata/api/plugins/installed/default.json b/modules/admin/webif/static/assets/testdata/api/plugins/installed/default.json index 6cd517deb5..9796f3e389 100644 --- a/modules/admin/webif/static/assets/testdata/api/plugins/installed/default.json +++ b/modules/admin/webif/static/assets/testdata/api/plugins/installed/default.json @@ -84,7 +84,7 @@ "en": "Commandline interface for SmartHomeNG - Works with SmartHomeNG v1.4 and up" }, "documentation": "http://smarthomeng.de/user/plugins/cli/user_doc.html", - "multi_instance": false, + "multi_instance": true, "state": "qa-passed", "type": "system", "version": "1.4.2" diff --git a/modules/admin/webif/static/assets/testdata/item_detail_json.html b/modules/admin/webif/static/assets/testdata/item_detail_json.html index 060f62d662..d143a9ea7d 100644 --- a/modules/admin/webif/static/assets/testdata/item_detail_json.html +++ b/modules/admin/webif/static/assets/testdata/item_detail_json.html @@ -35,7 +35,9 @@ "crontab": "-", "autotimer": "-", "threshold": "-", - "config": {"lin_mode": "ALL"}, + "config": { + "lin_mode": "ALL" + }, "logics": ["test"], "triggers": ["bound method WebSocket.update_item of plugins.visu_websocket.WebSocket"], "filename": "q21_c26lintronic.yaml" diff --git a/modules/admin/webif/static/assets/testdata/systeminfo.json b/modules/admin/webif/static/assets/testdata/systeminfo.json index 2ec2553ef7..b78418754e 100644 --- a/modules/admin/webif/static/assets/testdata/systeminfo.json +++ b/modules/admin/webif/static/assets/testdata/systeminfo.json @@ -1,22 +1,25 @@ { "now": "2018-08-16 00:32:26.614703+02:00", - "ostype": "debian", - "sh_vers": "1.5.1.f0c289f8.develop", + "ostype": "linux", + "osflavor": "debian", + "sh_vers": "1.9.1.f0c289f8.develop", "sh_desc": "heads/develop", - "plg_vers": "1.5.1.190ee809.develop", + "plg_vers": "1.9.1.190ee809.develop", "plg_desc": "heads/develop", "sh_dir": "/usr/local/shng_dev", "vers": "Debian GNU/Linux 9 (stretch)", "node": "SmartHomeNG.msinn.de", "arch": "x86_64", "user": "smarthome", - "hardware": "Hier fehlen die Hardware Daten", + "hardware": "\nIntel(R) Celeron(R) CPU J3455 @ 1.50GHz", + "hwspeed": "Hier fehlt die Geschwindigkeitsangabe", "rasppi": "", "freespace": 90249.54296875, "uptime": 4680364.345, "sh_uptime": 86550.212, "pyversion": "3.6.5 final", "pypath": "/usr/bin/python3", + "pyvirtual": false, "ip": "10.0.0.174", "ipv6": "::1", "pid": "4711" diff --git a/modules/admin/webif/static/index.html b/modules/admin/webif/static/index.html index 77e41c9d6d..38f0a6c70a 100644 --- a/modules/admin/webif/static/index.html +++ b/modules/admin/webif/static/index.html @@ -17,5 +17,5 @@ - + diff --git a/modules/admin/webif_0_4_2/static/main.cb52338bb12a3e86fd62.js b/modules/admin/webif/static/main.52f91ac84761d691196b.js similarity index 66% rename from modules/admin/webif_0_4_2/static/main.cb52338bb12a3e86fd62.js rename to modules/admin/webif/static/main.52f91ac84761d691196b.js index 28c9949df7..1b7acaee3e 100644 --- a/modules/admin/webif_0_4_2/static/main.cb52338bb12a3e86fd62.js +++ b/modules/admin/webif/static/main.52f91ac84761d691196b.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{"+dQi":function(e,t,n){!function(e){"use strict";e.defineMode("javascript",function(t,n){var l,i,o=t.indentUnit,r=n.statementIndent,u=n.jsonld,a=n.json||u,s=n.typescript,c=n.wordCharacters||/[\w$\xa1-\uffff]/,d=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),n=e("keyword b"),l=e("keyword c"),i=e("keyword d"),o=e("operator"),r={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:n,do:n,try:n,finally:n,return:i,break:i,continue:i,new:e("new"),delete:l,void:l,throw:l,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:o,typeof:o,instanceof:o,true:r,false:r,null:r,undefined:r,NaN:r,Infinity:r,this:e("this"),class:e("class"),super:e("atom"),yield:l,export:e("export"),import:e("import"),extends:l,await:l}}(),p=/[+\-*&%=<>!?|~^@]/,h=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function f(e,t,n){return l=e,i=n,t}function g(e,t){var n,l=e.next();if('"'==l||"'"==l)return t.tokenize=(n=l,function(e,t){var l,i=!1;if(u&&"@"==e.peek()&&e.match(h))return t.tokenize=g,f("jsonld-keyword","meta");for(;null!=(l=e.next())&&(l!=n||i);)i=!i&&"\\"==l;return i||(t.tokenize=g),f("string","string")}),t.tokenize(e,t);if("."==l&&e.match(/^\d+(?:[eE][+\-]?\d+)?/))return f("number","number");if("."==l&&e.match(".."))return f("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(l))return f(l);if("="==l&&e.eat(">"))return f("=>","operator");if("0"==l&&e.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i))return f("number","number");if(/\d/.test(l))return e.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/),f("number","number");if("/"==l)return e.eat("*")?(t.tokenize=m,m(e,t)):e.eat("/")?(e.skipToEnd(),f("comment","comment")):Ye(e,t,1)?(function(e){for(var t,n=!1,l=!1;null!=(t=e.next());){if(!n){if("/"==t&&!l)return;"["==t?l=!0:l&&"]"==t&&(l=!1)}n=!n&&"\\"==t}}(e),e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/),f("regexp","string-2")):(e.eat("="),f("operator","operator",e.current()));if("`"==l)return t.tokenize=v,v(e,t);if("#"==l)return e.skipToEnd(),f("error","error");if(p.test(l))return">"==l&&t.lexical&&">"==t.lexical.type||(e.eat("=")?"!"!=l&&"="!=l||e.eat("="):/[<>*+\-]/.test(l)&&(e.eat(l),">"==l&&e.eat(l))),f("operator","operator",e.current());if(c.test(l)){e.eatWhile(c);var i=e.current();if("."!=t.lastType){if(d.propertyIsEnumerable(i)){var o=d[i];return f(o.type,o.style,i)}if("async"==i&&e.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/,!1))return f("async","keyword",i)}return f("variable","variable",i)}}function m(e,t){for(var n,l=!1;n=e.next();){if("/"==n&&l){t.tokenize=g;break}l="*"==n}return f("comment","comment")}function v(e,t){for(var n,l=!1;null!=(n=e.next());){if(!l&&("`"==n||"$"==n&&e.eat("{"))){t.tokenize=g;break}l=!l&&"\\"==n}return f("quasi","string-2",e.current())}var y="([{}])";function b(e,t){t.fatArrowAt&&(t.fatArrowAt=null);var n=e.string.indexOf("=>",e.start);if(!(n<0)){if(s){var l=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,n));l&&(n=l.index)}for(var i=0,o=!1,r=n-1;r>=0;--r){var u=e.string.charAt(r),a=y.indexOf(u);if(a>=0&&a<3){if(!i){++r;break}if(0==--i){"("==u&&(o=!0);break}}else if(a>=3&&a<6)++i;else if(c.test(u))o=!0;else{if(/["'\/]/.test(u))return;if(o&&!i){++r;break}}}o&&!i&&(t.fatArrowAt=r)}}var C={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,"jsonld-keyword":!0};function w(e,t,n,l,i,o){this.indented=e,this.column=t,this.type=n,this.prev=i,this.info=o,null!=l&&(this.align=l)}function _(e,t){for(var n=e.localVars;n;n=n.next)if(n.name==t)return!0;for(var l=e.context;l;l=l.prev)for(n=l.vars;n;n=n.next)if(n.name==t)return!0}var x={state:null,column:null,marked:null,cc:null};function S(){for(var e=arguments.length-1;e>=0;e--)x.cc.push(arguments[e])}function E(){return S.apply(null,arguments),!0}function I(e,t){for(var n=t;n;n=n.next)if(n.name==e)return!0;return!1}function O(e){var t=x.state;if(x.marked="def",t.context)if("var"==t.lexical.info&&t.context&&t.context.block){var l=function e(t,n){if(n){if(n.block){var l=e(t,n.prev);return l?l==n.prev?n:new D(l,n.vars,!0):null}return I(t,n.vars)?n:new D(n.prev,new k(t,n.vars),!1)}return null}(e,t.context);if(null!=l)return void(t.context=l)}else if(!I(e,t.localVars))return void(t.localVars=new k(e,t.localVars));n.globalVars&&!I(e,t.globalVars)&&(t.globalVars=new k(e,t.globalVars))}function T(e){return"public"==e||"private"==e||"protected"==e||"abstract"==e||"readonly"==e}function D(e,t,n){this.prev=e,this.vars=t,this.block=n}function k(e,t){this.name=e,this.next=t}var R=new k("this",new k("arguments",null));function M(){x.state.context=new D(x.state.context,x.state.localVars,!1),x.state.localVars=R}function N(){x.state.context=new D(x.state.context,x.state.localVars,!0),x.state.localVars=null}function L(){x.state.localVars=x.state.context.vars,x.state.context=x.state.context.prev}function A(e,t){var n=function(){var n=x.state,l=n.indented;if("stat"==n.lexical.type)l=n.lexical.indented;else for(var i=n.lexical;i&&")"==i.type&&i.align;i=i.prev)l=i.indented;n.lexical=new w(l,x.stream.column(),e,null,n.lexical,t)};return n.lex=!0,n}function P(){var e=x.state;e.lexical.prev&&(")"==e.lexical.type&&(e.indented=e.lexical.indented),e.lexical=e.lexical.prev)}function F(e){return function t(n){return n==e?E():";"==e||"}"==n||")"==n||"]"==n?S():E(t)}}function V(e,t){return"var"==e?E(A("vardef",t),ye,F(";"),P):"keyword a"==e?E(A("form"),z,V,P):"keyword b"==e?E(A("form"),V,P):"keyword d"==e?x.stream.match(/^\s*$/,!1)?E():E(A("stat"),$,F(";"),P):"debugger"==e?E(F(";")):"{"==e?E(A("}"),N,re,P,L):";"==e?E():"if"==e?("else"==x.state.lexical.info&&x.state.cc[x.state.cc.length-1]==P&&x.state.cc.pop()(),E(A("form"),z,V,P,Se)):"function"==e?E(ke):"for"==e?E(A("form"),Ee,V,P):"class"==e||s&&"interface"==t?(x.marked="keyword",E(A("form","class"==e?e:t),Ae,P)):"variable"==e?s&&"declare"==t?(x.marked="keyword",E(V)):s&&("module"==t||"enum"==t||"type"==t)&&x.stream.match(/^\s*\w/,!1)?(x.marked="keyword","enum"==t?E(qe):"type"==t?E(Me,F("operator"),ce,F(";")):E(A("form"),be,F("{"),A("}"),re,P,P)):s&&"namespace"==t?(x.marked="keyword",E(A("form"),H,V,P)):s&&"abstract"==t?(x.marked="keyword",E(V)):E(A("stat"),J):"switch"==e?E(A("form"),z,F("{"),A("}","switch"),N,re,P,P,L):"case"==e?E(H,F(":")):"default"==e?E(F(":")):"catch"==e?E(A("form"),M,j,V,P,L):"export"==e?E(A("stat"),je,P):"import"==e?E(A("stat"),Be,P):"async"==e?E(V):"@"==t?E(H,V):S(A("stat"),H,F(";"),P)}function j(e){if("("==e)return E(Ne,F(")"))}function H(e,t){return U(e,t,!1)}function B(e,t){return U(e,t,!0)}function z(e){return"("!=e?S():E(A(")"),H,F(")"),P)}function U(e,t,n){if(x.state.fatArrowAt==x.stream.start){var l=n?Z:Y;if("("==e)return E(M,A(")"),ie(Ne,")"),P,F("=>"),l,L);if("variable"==e)return S(M,be,F("=>"),l,L)}var i=n?W:G;return C.hasOwnProperty(e)?E(i):"function"==e?E(ke,i):"class"==e||s&&"interface"==t?(x.marked="keyword",E(A("form"),Le,P)):"keyword c"==e||"async"==e?E(n?B:H):"("==e?E(A(")"),$,F(")"),P,i):"operator"==e||"spread"==e?E(n?B:H):"["==e?E(A("]"),We,P,i):"{"==e?oe(te,"}",null,i):"quasi"==e?S(q,i):"new"==e?E(function(e){return function(t){return"."==t?E(e?X:Q):"variable"==t&&s?E(ge,e?W:G):S(e?B:H)}}(n)):"import"==e?E(H):E()}function $(e){return e.match(/[;\}\)\],]/)?S():S(H)}function G(e,t){return","==e?E(H):W(e,t,!1)}function W(e,t,n){var l=0==n?G:W,i=0==n?H:B;return"=>"==e?E(M,n?Z:Y,L):"operator"==e?/\+\+|--/.test(t)||s&&"!"==t?E(l):s&&"<"==t&&x.stream.match(/^([^>]|<.*?>)*>\s*\(/,!1)?E(A(">"),ie(ce,">"),P,l):"?"==t?E(H,F(":"),i):E(i):"quasi"==e?S(q,l):";"!=e?"("==e?oe(B,")","call",l):"."==e?E(ee,l):"["==e?E(A("]"),$,F("]"),P,l):s&&"as"==t?(x.marked="keyword",E(ce,l)):"regexp"==e?(x.state.lastType=x.marked="operator",x.stream.backUp(x.stream.pos-x.stream.start-1),E(i)):void 0:void 0}function q(e,t){return"quasi"!=e?S():"${"!=t.slice(t.length-2)?E(q):E(H,K)}function K(e){if("}"==e)return x.marked="string-2",x.state.tokenize=v,E(q)}function Y(e){return b(x.stream,x.state),S("{"==e?V:H)}function Z(e){return b(x.stream,x.state),S("{"==e?V:B)}function Q(e,t){if("target"==t)return x.marked="keyword",E(G)}function X(e,t){if("target"==t)return x.marked="keyword",E(W)}function J(e){return":"==e?E(P,V):S(G,F(";"),P)}function ee(e){if("variable"==e)return x.marked="property",E()}function te(e,t){return"async"==e?(x.marked="property",E(te)):"variable"==e||"keyword"==x.style?(x.marked="property","get"==t||"set"==t?E(ne):(s&&x.state.fatArrowAt==x.stream.start&&(n=x.stream.match(/^\s*:\s*/,!1))&&(x.state.fatArrowAt=x.stream.pos+n[0].length),E(le))):"number"==e||"string"==e?(x.marked=u?"property":x.style+" property",E(le)):"jsonld-keyword"==e?E(le):s&&T(t)?(x.marked="keyword",E(te)):"["==e?E(H,ue,F("]"),le):"spread"==e?E(B,le):"*"==t?(x.marked="keyword",E(te)):":"==e?S(le):void 0;var n}function ne(e){return"variable"!=e?S(le):(x.marked="property",E(ke))}function le(e){return":"==e?E(B):"("==e?S(ke):void 0}function ie(e,t,n){function l(i,o){if(n?n.indexOf(i)>-1:","==i){var r=x.state.lexical;return"call"==r.info&&(r.pos=(r.pos||0)+1),E(function(n,l){return n==t||l==t?S():S(e)},l)}return i==t||o==t?E():n&&n.indexOf(";")>-1?S(e):E(F(t))}return function(n,i){return n==t||i==t?E():S(e,l)}}function oe(e,t,n){for(var l=3;l"),ce):void 0}function de(e){if("=>"==e)return E(ce)}function pe(e,t){return"variable"==e||"keyword"==x.style?(x.marked="property",E(pe)):"?"==t||"number"==e||"string"==e?E(pe):":"==e?E(ce):"["==e?E(F("variable"),ue,F("]"),pe):"("==e?S(Re,pe):void 0}function he(e,t){return"variable"==e&&x.stream.match(/^\s*[?:]/,!1)||"?"==t?E(he):":"==e?E(ce):"spread"==e?E(he):S(ce)}function fe(e,t){return"<"==t?E(A(">"),ie(ce,">"),P,fe):"|"==t||"."==e||"&"==t?E(ce):"["==e?E(ce,F("]"),fe):"extends"==t||"implements"==t?(x.marked="keyword",E(ce)):"?"==t?E(ce,F(":"),ce):void 0}function ge(e,t){if("<"==t)return E(A(">"),ie(ce,">"),P,fe)}function me(){return S(ce,ve)}function ve(e,t){if("="==t)return E(ce)}function ye(e,t){return"enum"==t?(x.marked="keyword",E(qe)):S(be,ue,_e,xe)}function be(e,t){return s&&T(t)?(x.marked="keyword",E(be)):"variable"==e?(O(t),E()):"spread"==e?E(be):"["==e?oe(we,"]"):"{"==e?oe(Ce,"}"):void 0}function Ce(e,t){return"variable"!=e||x.stream.match(/^\s*:/,!1)?("variable"==e&&(x.marked="property"),"spread"==e?E(be):"}"==e?S():"["==e?E(H,F("]"),F(":"),Ce):E(F(":"),be,_e)):(O(t),E(_e))}function we(){return S(be,_e)}function _e(e,t){if("="==t)return E(B)}function xe(e){if(","==e)return E(ye)}function Se(e,t){if("keyword b"==e&&"else"==t)return E(A("form","else"),V,P)}function Ee(e,t){return"await"==t?E(Ee):"("==e?E(A(")"),Ie,F(")"),P):void 0}function Ie(e){return"var"==e?E(ye,F(";"),Te):";"==e?E(Te):"variable"==e?E(Oe):S(H,F(";"),Te)}function Oe(e,t){return"in"==t||"of"==t?(x.marked="keyword",E(H)):E(G,Te)}function Te(e,t){return";"==e?E(De):"in"==t||"of"==t?(x.marked="keyword",E(H)):S(H,F(";"),De)}function De(e){")"!=e&&E(H)}function ke(e,t){return"*"==t?(x.marked="keyword",E(ke)):"variable"==e?(O(t),E(ke)):"("==e?E(M,A(")"),ie(Ne,")"),P,ae,V,L):s&&"<"==t?E(A(">"),ie(me,">"),P,ke):void 0}function Re(e,t){return"*"==t?(x.marked="keyword",E(Re)):"variable"==e?(O(t),E(Re)):"("==e?E(M,A(")"),ie(Ne,")"),P,ae,L):s&&"<"==t?E(A(">"),ie(me,">"),P,Re):void 0}function Me(e,t){return"keyword"==e||"variable"==e?(x.marked="type",E(Me)):"<"==t?E(A(">"),ie(me,">"),P):void 0}function Ne(e,t){return"@"==t&&E(H,Ne),"spread"==e?E(Ne):s&&T(t)?(x.marked="keyword",E(Ne)):S(be,ue,_e)}function Le(e,t){return"variable"==e?Ae(e,t):Pe(e,t)}function Ae(e,t){if("variable"==e)return O(t),E(Pe)}function Pe(e,t){return"<"==t?E(A(">"),ie(me,">"),P,Pe):"extends"==t||"implements"==t||s&&","==e?("implements"==t&&(x.marked="keyword"),E(s?ce:H,Pe)):"{"==e?E(A("}"),Fe,P):void 0}function Fe(e,t){return"async"==e||"variable"==e&&("static"==t||"get"==t||"set"==t||s&&T(t))&&x.stream.match(/^\s+[\w$\xa1-\uffff]/,!1)?(x.marked="keyword",E(Fe)):"variable"==e||"keyword"==x.style?(x.marked="property",E(s?Ve:ke,Fe)):"number"==e||"string"==e?E(s?Ve:ke,Fe):"["==e?E(H,ue,F("]"),s?Ve:ke,Fe):"*"==t?(x.marked="keyword",E(Fe)):s&&"("==e?S(Re,Fe):";"==e||","==e?E(Fe):"}"==e?E():"@"==t?E(H,Fe):void 0}function Ve(e,t){if("?"==t)return E(Ve);if(":"==e)return E(ce,_e);if("="==t)return E(B);var n=x.state.lexical.prev;return S(n&&"interface"==n.info?Re:ke)}function je(e,t){return"*"==t?(x.marked="keyword",E(Ge,F(";"))):"default"==t?(x.marked="keyword",E(H,F(";"))):"{"==e?E(ie(He,"}"),Ge,F(";")):S(V)}function He(e,t){return"as"==t?(x.marked="keyword",E(F("variable"))):"variable"==e?S(B,He):void 0}function Be(e){return"string"==e?E():"("==e?S(H):S(ze,Ue,Ge)}function ze(e,t){return"{"==e?oe(ze,"}"):("variable"==e&&O(t),"*"==t&&(x.marked="keyword"),E($e))}function Ue(e){if(","==e)return E(ze,Ue)}function $e(e,t){if("as"==t)return x.marked="keyword",E(ze)}function Ge(e,t){if("from"==t)return x.marked="keyword",E(H)}function We(e){return"]"==e?E():S(ie(B,"]"))}function qe(){return S(A("form"),be,F("{"),A("}"),ie(Ke,"}"),P,P)}function Ke(){return S(be,_e)}function Ye(e,t,n){return t.tokenize==g&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||"quasi"==t.lastType&&/\{\s*$/.test(e.string.slice(0,e.pos-(n||0)))}return L.lex=!0,P.lex=!0,{startState:function(e){var t={tokenize:g,lastType:"sof",cc:[],lexical:new w((e||0)-o,0,"block",!1),localVars:n.localVars,context:n.localVars&&new D(null,null,!1),indented:e||0};return n.globalVars&&"object"==typeof n.globalVars&&(t.globalVars=n.globalVars),t},token:function(e,t){if(e.sol()&&(t.lexical.hasOwnProperty("align")||(t.lexical.align=!1),t.indented=e.indentation(),b(e,t)),t.tokenize!=m&&e.eatSpace())return null;var n=t.tokenize(e,t);return"comment"==l?n:(t.lastType="operator"!=l||"++"!=i&&"--"!=i?l:"incdec",function(e,t,n,l,i){var o=e.cc;for(x.state=e,x.stream=i,x.marked=null,x.cc=o,x.style=t,e.lexical.hasOwnProperty("align")||(e.lexical.align=!0);;)if((o.length?o.pop():a?H:V)(n,l)){for(;o.length&&o[o.length-1].lex;)o.pop()();return x.marked?x.marked:"variable"==n&&_(e,l)?"variable-2":t}}(t,n,l,i,e))},indent:function(t,l){if(t.tokenize==m)return e.Pass;if(t.tokenize!=g)return 0;var i,u=l&&l.charAt(0),a=t.lexical;if(!/^\s*else\b/.test(l))for(var s=t.cc.length-1;s>=0;--s){var c=t.cc[s];if(c==P)a=a.prev;else if(c!=Se)break}for(;("stat"==a.type||"form"==a.type)&&("}"==u||(i=t.cc[t.cc.length-1])&&(i==G||i==W)&&!/^[,\.=+\-*:?[\(]/.test(l));)a=a.prev;r&&")"==a.type&&"stat"==a.prev.type&&(a=a.prev);var d=a.type,h=u==d;return"vardef"==d?a.indented+("operator"==t.lastType||","==t.lastType?a.info.length+1:0):"form"==d&&"{"==u?a.indented:"form"==d?a.indented+o:"stat"==d?a.indented+(function(e,t){return"operator"==e.lastType||","==e.lastType||p.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}(t,l)?r||o:0):"switch"!=a.info||h||0==n.doubleIndentSwitch?a.align?a.column+(h?0:1):a.indented+(h?0:o):a.indented+(/^(?:case|default)\b/.test(l)?o:2*o)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:a?null:"/*",blockCommentEnd:a?null:"*/",blockCommentContinue:a?null:" * ",lineComment:a?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:a?"json":"javascript",jsonldMode:u,jsonMode:a,expressionAllowed:Ye,skipExpression:function(e){var t=e.cc[e.cc.length-1];t!=H&&t!=B||e.cc.pop()}}}),e.registerHelper("wordChars","javascript",/[\w$]/),e.defineMIME("text/javascript","javascript"),e.defineMIME("text/ecmascript","javascript"),e.defineMIME("application/javascript","javascript"),e.defineMIME("application/x-javascript","javascript"),e.defineMIME("application/ecmascript","javascript"),e.defineMIME("application/json",{name:"javascript",json:!0}),e.defineMIME("application/x-json",{name:"javascript",json:!0}),e.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),e.defineMIME("text/typescript",{name:"javascript",typescript:!0}),e.defineMIME("application/typescript",{name:"javascript",typescript:!0})}(n("VrN/"))},"+tJ4":function(e,t,n){"use strict";n.d(t,"a",function(){return l});var l=function(e){return function(t){for(var n=0,l=e.length;n=0}},"/NBx":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("7LN8"),a=n("Fa87"),s=n("gIcY");t.CHIPS_VALUE_ACCESSOR={provide:s.NG_VALUE_ACCESSOR,useExisting:o.forwardRef(function(){return c}),multi:!0};var c=function(){function e(e){this.el=e,this.allowDuplicate=!0,this.onAdd=new o.EventEmitter,this.onRemove=new o.EventEmitter,this.onFocus=new o.EventEmitter,this.onBlur=new o.EventEmitter,this.onChipClick=new o.EventEmitter,this.onModelChange=function(){},this.onModelTouched=function(){}}return e.prototype.ngAfterContentInit=function(){var e=this;this.templates.forEach(function(t){switch(t.getType()){case"item":default:e.itemTemplate=t.template}})},e.prototype.onClick=function(e){this.inputViewChild.nativeElement.focus()},e.prototype.onItemClick=function(e,t){this.onChipClick.emit({originalEvent:e,value:t})},e.prototype.writeValue=function(e){this.value=e,this.updateMaxedOut()},e.prototype.registerOnChange=function(e){this.onModelChange=e},e.prototype.registerOnTouched=function(e){this.onModelTouched=e},e.prototype.setDisabledState=function(e){this.disabled=e},e.prototype.resolveFieldData=function(e,t){if(e&&t){if(-1==t.indexOf("."))return e[t];for(var n=t.split("."),l=e,i=0,o=n.length;i0){this.value=this.value.slice();var t=this.value.pop();this.onModelChange(this.value),this.onRemove.emit({originalEvent:e,value:t})}break;case 13:this.addItem(e,this.inputViewChild.nativeElement.value),this.inputViewChild.nativeElement.value="",e.preventDefault();break;case 9:this.addOnTab&&""!==this.inputViewChild.nativeElement.value&&(this.addItem(e,this.inputViewChild.nativeElement.value),this.inputViewChild.nativeElement.value="",e.preventDefault());break;default:this.max&&this.value&&this.max===this.value.length&&e.preventDefault()}},e.prototype.updateMaxedOut=function(){this.inputViewChild&&this.inputViewChild.nativeElement&&(this.inputViewChild.nativeElement.disabled=!(!this.max||!this.value||this.max!==this.value.length)||this.disabled||!1)},l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"disabled",void 0),l([o.Input(),i("design:type",String)],e.prototype,"field",void 0),l([o.Input(),i("design:type",String)],e.prototype,"placeholder",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"max",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"tabindex",void 0),l([o.Input(),i("design:type",String)],e.prototype,"inputId",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"allowDuplicate",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"inputStyle",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"inputStyleClass",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"addOnTab",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"addOnBlur",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onAdd",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRemove",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onFocus",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onBlur",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onChipClick",void 0),l([o.ViewChild("inputtext"),i("design:type",o.ElementRef)],e.prototype,"inputViewChild",void 0),l([o.ContentChildren(u.PrimeTemplate),i("design:type",o.QueryList)],e.prototype,"templates",void 0),l([o.Component({selector:"p-chips",template:'\n
\n
    \n
  • \n \n {{field ? resolveFieldData(item,field) : item}}\n \n
  • \n
  • \n \n
  • \n
\n
\n ',providers:[t.CHIPS_VALUE_ACCESSOR]})],e)}();t.Chips=c,t.ChipsModule=function(){return l([o.NgModule({imports:[r.CommonModule,a.InputTextModule,u.SharedModule],exports:[c,a.InputTextModule,u.SharedModule],declarations:[c]})],function(){})}()},"/WYv":function(e,t,n){"use strict";function l(e){return!!e&&"function"!=typeof e.subscribe&&"function"==typeof e.then}n.d(t,"a",function(){return l})},0:function(e,t,n){e.exports=n("zUnb")},"0/uQ":function(e,t,n){"use strict";n.d(t,"a",function(){return r});var l=n("6blF"),i=n("Fxb1"),o=n("i4X3");function r(e,t){return t?Object(o.a)(e,t):e instanceof l.a?e:new l.a(Object(i.a)(e))}},"0alx":function(e,t,n){"use strict";var l=n("VKeD");t.isIterable=function(e){return e&&"function"==typeof e[l.iterator]}},"15JJ":function(e,t,n){"use strict";n.d(t,"a",function(){return s});var l=n("mrSG"),i=n("MGBS"),o=n("rPjj"),r=n("zotm"),u=n("67Y/"),a=n("0/uQ");function s(e,t){return"function"==typeof t?function(n){return n.pipe(s(function(n,l){return Object(a.a)(e(n,l)).pipe(Object(u.a)(function(e,i){return t(n,e,l,i)}))}))}:function(t){return t.lift(new c(e))}}var c=function(){function e(e){this.project=e}return e.prototype.call=function(e,t){return t.subscribe(new d(e,this.project))},e}(),d=function(e){function t(t,n){var l=e.call(this,t)||this;return l.project=n,l.index=0,l}return l.__extends(t,e),t.prototype._next=function(e){var t,n=this.index++;try{t=this.project(e,n)}catch(l){return void this.destination.error(l)}this._innerSub(t,e,n)},t.prototype._innerSub=function(e,t,n){var l=this.innerSubscription;l&&l.unsubscribe();var i=new o.a(this,void 0,void 0);this.destination.add(i),this.innerSubscription=Object(r.a)(this,e,t,n,i)},t.prototype._complete=function(){var t=this.innerSubscription;t&&!t.closed||e.prototype._complete.call(this),this.unsubscribe()},t.prototype._unsubscribe=function(){this.innerSubscription=null},t.prototype.notifyComplete=function(t){this.destination.remove(t),this.innerSubscription=null,this.isStopped&&e.prototype._complete.call(this)},t.prototype.notifyNext=function(e,t,n,l,i){this.destination.next(t)},t}(i.a)},"1eCo":function(e,t,n){!function(e){"use strict";var t={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},n={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,allowMissingTagName:!1,caseFold:!1};e.defineMode("xml",function(l,i){var o,r,u=l.indentUnit,a={},s=i.htmlMode?t:n;for(var c in s)a[c]=s[c];for(var c in i)a[c]=i[c];function d(e,t){function n(n){return t.tokenize=n,n(e,t)}var l=e.next();return"<"==l?e.eat("!")?e.eat("[")?e.match("CDATA[")?n(h("atom","]]>")):null:e.match("--")?n(h("comment","--\x3e")):e.match("DOCTYPE",!0,!0)?(e.eatWhile(/[\w\._\-]/),n(function e(t){return function(n,l){for(var i;null!=(i=n.next());){if("<"==i)return l.tokenize=e(t+1),l.tokenize(n,l);if(">"==i){if(1==t){l.tokenize=d;break}return l.tokenize=e(t-1),l.tokenize(n,l)}}return"meta"}}(1))):null:e.eat("?")?(e.eatWhile(/[\w\._\-]/),t.tokenize=h("meta","?>"),"meta"):(o=e.eat("/")?"closeTag":"openTag",t.tokenize=p,"tag bracket"):"&"==l?(e.eat("#")?e.eat("x")?e.eatWhile(/[a-fA-F\d]/)&&e.eat(";"):e.eatWhile(/[\d]/)&&e.eat(";"):e.eatWhile(/[\w\.\-:]/)&&e.eat(";"))?"atom":"error":(e.eatWhile(/[^&<]/),null)}function p(e,t){var n,l,i=e.next();if(">"==i||"/"==i&&e.eat(">"))return t.tokenize=d,o=">"==i?"endTag":"selfcloseTag","tag bracket";if("="==i)return o="equals",null;if("<"==i){t.tokenize=d,t.state=v,t.tagName=t.tagStart=null;var r=t.tokenize(e,t);return r?r+" tag error":"tag error"}return/[\'\"]/.test(i)?(t.tokenize=(n=i,(l=function(e,t){for(;!e.eol();)if(e.next()==n){t.tokenize=p;break}return"string"}).isInAttribute=!0,l),t.stringStartCol=e.column(),t.tokenize(e,t)):(e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function h(e,t){return function(n,l){for(;!n.eol();){if(n.match(t)){l.tokenize=d;break}n.next()}return e}}function f(e,t,n){this.prev=e.context,this.tagName=t,this.indent=e.indented,this.startOfLine=n,(a.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)&&(this.noIndent=!0)}function g(e){e.context&&(e.context=e.context.prev)}function m(e,t){for(var n;;){if(!e.context)return;if(!a.contextGrabbers.hasOwnProperty(n=e.context.tagName)||!a.contextGrabbers[n].hasOwnProperty(t))return;g(e)}}function v(e,t,n){return"openTag"==e?(n.tagStart=t.column(),y):"closeTag"==e?b:v}function y(e,t,n){return"word"==e?(n.tagName=t.current(),r="tag",_):a.allowMissingTagName&&"endTag"==e?(r="tag bracket",_(e,0,n)):(r="error",y)}function b(e,t,n){if("word"==e){var l=t.current();return n.context&&n.context.tagName!=l&&a.implicitlyClosed.hasOwnProperty(n.context.tagName)&&g(n),n.context&&n.context.tagName==l||!1===a.matchClosing?(r="tag",C):(r="tag error",w)}return a.allowMissingTagName&&"endTag"==e?(r="tag bracket",C(e,0,n)):(r="error",w)}function C(e,t,n){return"endTag"!=e?(r="error",C):(g(n),v)}function w(e,t,n){return r="error",C(e,0,n)}function _(e,t,n){if("word"==e)return r="attribute",x;if("endTag"==e||"selfcloseTag"==e){var l=n.tagName,i=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==e||a.autoSelfClosers.hasOwnProperty(l)?m(n,l):(m(n,l),n.context=new f(n,l,i==n.indented)),v}return r="error",_}function x(e,t,n){return"equals"==e?S:(a.allowMissing||(r="error"),_(e,0,n))}function S(e,t,n){return"string"==e?E:"word"==e&&a.allowUnquoted?(r="string",_):(r="error",_(e,0,n))}function E(e,t,n){return"string"==e?E:_(e,0,n)}return d.isInText=!0,{startState:function(e){var t={tokenize:d,state:v,indented:e||0,tagName:null,tagStart:null,context:null};return null!=e&&(t.baseIndent=e),t},token:function(e,t){if(!t.tagName&&e.sol()&&(t.indented=e.indentation()),e.eatSpace())return null;o=null;var n=t.tokenize(e,t);return(n||o)&&"comment"!=n&&(r=null,t.state=t.state(o||n,e,t),r&&(n="error"==r?n+" error":r)),n},indent:function(t,n,l){var i=t.context;if(t.tokenize.isInAttribute)return t.tagStart==t.indented?t.stringStartCol+1:t.indented+u;if(i&&i.noIndent)return e.Pass;if(t.tokenize!=p&&t.tokenize!=d)return l?l.match(/^(\s*)/)[0].length:0;if(t.tagName)return!1!==a.multilineTagIndentPastTag?t.tagStart+t.tagName.length+2:t.tagStart+u*(a.multilineTagIndentFactor||1);if(a.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:a.htmlMode?"html":"xml",helperType:a.htmlMode?"html":"xml",skipAttribute:function(e){e.state==S&&(e.state=_)}}}),e.defineMIME("text/xml","xml"),e.defineMIME("application/xml","xml"),e.mimeModes.hasOwnProperty("text/html")||e.defineMIME("text/html",{name:"xml",htmlMode:!0})}(n("VrN/"))},"1fDf":function(e,t,n){"use strict";n.d(t,"a",function(){return i});var l=n("FFOo");function i(e){for(;e;){var t=e.destination;if(e.closed||e.isStopped)return!1;e=t&&t instanceof l.a?t:null}return!0}},"24Yq":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){for(var n in e)t.hasOwnProperty(n)||(t[n]=e[n])}(n("DqLj"))},"25Eh":function(e,t,n){!function(e){"use strict";function t(e){return new RegExp("^(("+e.join(")|(")+"))\\b")}var n=t(["and","or","not","is"]),l=["as","assert","break","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","lambda","pass","raise","return","try","while","with","yield","in"],i=["abs","all","any","bin","bool","bytearray","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip","__import__","NotImplemented","Ellipsis","__debug__"];function o(e){return e.scopes[e.scopes.length-1]}e.registerHelper("hintWords","python",l.concat(i)),e.defineMode("python",function(r,u){for(var a="error",s=u.delimiters||u.singleDelimiters||/^[\(\)\[\]\{\}@,:`=;\.\\]/,c=[u.singleOperators,u.doubleOperators,u.doubleDelimiters,u.tripleDelimiters,u.operators||/^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@])/],d=0;dl?_(t):i0&&x(e,t)&&(r+=" "+a),r}return w(e,t)}function w(e,t){if(e.eatSpace())return null;if(e.match(/^#.*/))return"comment";if(e.match(/^[0-9\.]/,!1)){var l=!1;if(e.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)&&(l=!0),e.match(/^[\d_]+\.\d*/)&&(l=!0),e.match(/^\.\d+/)&&(l=!0),l)return e.eat(/J/i),"number";var i=!1;if(e.match(/^0x[0-9a-f_]+/i)&&(i=!0),e.match(/^0b[01_]+/i)&&(i=!0),e.match(/^0o[0-7_]+/i)&&(i=!0),e.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)&&(e.eat(/J/i),i=!0),e.match(/^0(?![\dx])/i)&&(i=!0),i)return e.eat(/L/i),"number"}if(e.match(v))return-1!==e.current().toLowerCase().indexOf("f")?(t.tokenize=function(e,t){for(;"rubf".indexOf(e.charAt(0).toLowerCase())>=0;)e=e.substr(1);var n=1==e.length,l="string";function i(e){return function(t,n){var l=w(t,n);return"punctuation"==l&&("{"==t.current()?n.tokenize=i(e+1):"}"==t.current()&&(n.tokenize=e>1?i(e-1):o)),l}}function o(o,r){for(;!o.eol();)if(o.eatWhile(/[^'"\{\}\\]/),o.eat("\\")){if(o.next(),n&&o.eol())return l}else{if(o.match(e))return r.tokenize=t,l;if(o.match("{{"))return l;if(o.match("{",!1))return r.tokenize=i(0),o.current()?l:r.tokenize(o,r);if(o.match("}}"))return l;if(o.match("}"))return a;o.eat(/['"]/)}if(n){if(u.singleLineStringErrors)return a;r.tokenize=t}return l}return o.isString=!0,o}(e.current(),t.tokenize),t.tokenize(e,t)):(t.tokenize=function(e,t){for(;"rubf".indexOf(e.charAt(0).toLowerCase())>=0;)e=e.substr(1);var n=1==e.length,l="string";function i(i,o){for(;!i.eol();)if(i.eatWhile(/[^'"\\]/),i.eat("\\")){if(i.next(),n&&i.eol())return l}else{if(i.match(e))return o.tokenize=t,l;i.eat(/['"]/)}if(n){if(u.singleLineStringErrors)return a;o.tokenize=t}return l}return i.isString=!0,i}(e.current(),t.tokenize),t.tokenize(e,t));for(var o=0;o1&&o(t).offset>n;){if("py"!=o(t).type)return!0;t.scopes.pop()}return o(t).offset!=n}return{startState:function(e){return{tokenize:C,scopes:[{offset:e||0,type:"py",align:null}],indent:e||0,lastToken:null,lambda:!1,dedent:0}},token:function(e,t){var n=t.errorToken;n&&(t.errorToken=!1);var l=function(e,t){e.sol()&&(t.beginningOfLine=!0);var n=t.tokenize(e,t),l=e.current();if(t.beginningOfLine&&"@"==l)return e.match(m,!1)?"meta":g?"operator":a;if(/\S/.test(l)&&(t.beginningOfLine=!1),"variable"!=n&&"builtin"!=n||"meta"!=t.lastToken||(n="meta"),"pass"!=l&&"return"!=l||(t.dedent+=1),"lambda"==l&&(t.lambda=!0),":"!=l||t.lambda||"py"!=o(t).type||_(t),1==l.length&&!/string|comment/.test(n)){var i="[({".indexOf(l);if(-1!=i&&function(e,t,n){var l=e.match(/^([\s\[\{\(]|#.*)*$/,!1)?null:e.column()+1;t.scopes.push({offset:t.indent+p,type:n,align:l})}(e,t,"])}".slice(i,i+1)),-1!=(i="])}".indexOf(l))){if(o(t).type!=l)return a;t.indent=t.scopes.pop().offset-p}}return t.dedent>0&&e.eol()&&"py"==o(t).type&&(t.scopes.length>1&&t.scopes.pop(),t.dedent-=1),n}(e,t);return l&&"comment"!=l&&(t.lastToken="keyword"==l||"punctuation"==l?e.current():l),"punctuation"==l&&(l=null),e.eol()&&t.lambda&&(t.lambda=!1),n?l+" "+a:l},indent:function(t,n){if(t.tokenize!=C)return t.tokenize.isString?e.Pass:0;var l=o(t),i=l.type==n.charAt(0);return null!=l.align?l.align-(i?1:0):l.offset-(i?p:0)},electricInput:/^\s*[\}\]\)]$/,closeBrackets:{triples:"'\""},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-python","python"),e.defineMIME("text/x-cython",{name:"python",extra_keywords:"by cdef cimport cpdef ctypedef enum except extern gil include nogil property public readonly struct union DEF IF ELIF ELSE".split(" ")})}(n("VrN/"))},"26FU":function(e,t,n){"use strict";n.d(t,"a",function(){return r});var l=n("mrSG"),i=n("K9Ia"),o=n("8g8A"),r=function(e){function t(t){var n=e.call(this)||this;return n._value=t,n}return l.__extends(t,e),Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!0,configurable:!0}),t.prototype._subscribe=function(t){var n=e.prototype._subscribe.call(this,t);return n&&!n.closed&&t.next(this._value),n},t.prototype.getValue=function(){if(this.hasError)throw this.thrownError;if(this.closed)throw new o.a;return this._value},t.prototype.next=function(t){e.prototype.next.call(this,this._value=t)},t}(i.a)},"2Bdj":function(e,t,n){"use strict";function l(e){return"function"==typeof e}n.d(t,"a",function(){return l})},"2KeD":function(e,t,n){"use strict";var l=n("Q1FS"),i=n("zB/H"),o=n("zfKp");t.scheduleObservable=function(e,t){return new l.Observable(function(n){var l=new i.Subscription;return l.add(t.schedule(function(){var i=e[o.observable]();l.add(i.subscribe({next:function(e){l.add(t.schedule(function(){return n.next(e)}))},error:function(e){l.add(t.schedule(function(){return n.error(e)}))},complete:function(){l.add(t.schedule(function(){return n.complete()}))}}))})),l})}},"2bbZ":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("ihYY"),u=n("cdZy"),a=(n("V3HQ"),n("Ip0R")),s=n("sdDj"),c=(n("RWz4"),function(){function e(e,t,n,l,i,o){this.componentFactoryResolver=e,this.cd=t,this.renderer=n,this.config=l,this.dialogRef=i,this.zone=o,this.visible=!0}return e.prototype.ngAfterViewInit=function(){this.loadChildComponent(this.childComponentType),this.cd.detectChanges()},e.prototype.onOverlayClicked=function(e){this.dialogRef.close()},e.prototype.onDialogClicked=function(e){e.stopPropagation()},e.prototype.loadChildComponent=function(e){var t=this.componentFactoryResolver.resolveComponentFactory(e),n=this.insertionPoint.viewContainerRef;n.clear(),this.componentRef=n.createComponent(t)},e.prototype.moveOnTop=function(){if(!1!==this.config.autoZIndex){var e=this.config.baseZIndex||++s.DomHandler.zindex+0;this.container.style.zIndex=String(e),this.maskViewChild.nativeElement.style.zIndex=String(e-1)}},e.prototype.onAnimationStart=function(e){switch(e.toState){case"visible":this.container=e.element,this.moveOnTop(),this.bindGlobalListeners(),s.DomHandler.addClass(document.body,"ui-overflow-hidden");break;case"void":this.onContainerDestroy()}},e.prototype.onAnimationEnd=function(e){"void"===e.toState&&this.dialogRef.close()},e.prototype.onContainerDestroy=function(){s.DomHandler.removeClass(document.body,"ui-overflow-hidden"),this.unbindGlobalListeners(),this.container=null},e.prototype.close=function(){this.visible=!1},e.prototype.onMaskClick=function(){this.config.dismissableMask&&this.close()},e.prototype.bindGlobalListeners=function(){!1!==this.config.closeOnEscape&&!1!==this.config.closable&&this.bindDocumentEscapeListener()},e.prototype.unbindGlobalListeners=function(){this.unbindDocumentEscapeListener()},e.prototype.bindDocumentEscapeListener=function(){var e=this;this.documentEscapeListener=this.renderer.listen("document","keydown",function(t){27==t.which&&parseInt(e.container.style.zIndex)==s.DomHandler.zindex&&e.close()})},e.prototype.unbindDocumentEscapeListener=function(){this.documentEscapeListener&&(this.documentEscapeListener(),this.documentEscapeListener=null)},e.prototype.ngOnDestroy=function(){this.onContainerDestroy(),this.componentRef&&this.componentRef.destroy()},l([o.ViewChild(u.DynamicDialogContent),i("design:type",u.DynamicDialogContent)],e.prototype,"insertionPoint",void 0),l([o.ViewChild("mask"),i("design:type",o.ElementRef)],e.prototype,"maskViewChild",void 0),l([o.Component({selector:"p-dynamicDialog",template:'\n\t\t
\n\t\t
\n
\n {{config.header}}\n \n \n \n
\n
\n\t\t\t\t\n\t\t\t
\n\t\t\t\n\t\t
\n\t',animations:[r.trigger("animation",[r.state("void",r.style({transform:"translate3d(-50%, -25%, 0) scale(0.9)",opacity:0})),r.state("visible",r.style({transform:"translateX(-50%) translateY(-50%)",opacity:1})),r.transition("* => *",r.animate("{{transitionParams}}"))])]})],e)}());t.DynamicDialogComponent=c,t.DynamicDialogModule=function(){return l([o.NgModule({imports:[a.CommonModule],declarations:[c,u.DynamicDialogContent],entryComponents:[c]})],function(){})}()},"2ePl":function(e,t,n){"use strict";n.d(t,"a",function(){return l});var l=function(e){return e&&"number"==typeof e.length&&"function"!=typeof e}},"2m6e":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=(n("gIcY"),n("Ip0R")),u=function(){function e(e,t){this.el=e,this.ngModel=t,this.onResize=new o.EventEmitter}return e.prototype.ngDoCheck=function(){this.updateFilledState(),this.autoResize&&this.resize()},e.prototype.onInput=function(e){this.updateFilledState(),this.autoResize&&this.resize(e)},e.prototype.updateFilledState=function(){this.filled=this.el.nativeElement.value&&this.el.nativeElement.value.length||this.ngModel&&this.ngModel.model},e.prototype.onFocus=function(e){this.autoResize&&this.resize(e)},e.prototype.onBlur=function(e){this.autoResize&&this.resize(e)},e.prototype.resize=function(e){this.el.nativeElement.style.height="",this.el.nativeElement.style.height=this.el.nativeElement.scrollHeight+"px",parseFloat(this.el.nativeElement.style.height)>=parseFloat(this.el.nativeElement.style.maxHeight)?(this.el.nativeElement.style.overflowY="scroll",this.el.nativeElement.style.height=this.el.nativeElement.style.maxHeight):this.el.nativeElement.style.overflow="hidden",this.onResize.emit(e||{})},l([o.Input(),i("design:type",Boolean)],e.prototype,"autoResize",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onResize",void 0),l([o.HostListener("input",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"onInput",null),l([o.HostListener("focus",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"onFocus",null),l([o.HostListener("blur",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"onBlur",null),l([o.Directive({selector:"[pInputTextarea]",host:{"[class.ui-inputtext]":"true","[class.ui-corner-all]":"true","[class.ui-inputtextarea-resizable]":"autoResize","[class.ui-state-default]":"true","[class.ui-widget]":"true","[class.ui-state-filled]":"filled"}})],e)}();t.InputTextarea=u,t.InputTextareaModule=function(){return l([o.NgModule({imports:[r.CommonModule],exports:[u],declarations:[u]})],function(){})}()},"2qMH":function(e,t,n){"use strict";var l=n("Q1FS"),i=n("zB/H");t.scheduleArray=function(e,t){return new l.Observable(function(n){var l=new i.Subscription,o=0;return l.add(t.schedule(function(){o!==e.length?(n.next(e[o++]),n.closed||l.add(this.schedule())):n.complete()})),l})}},"3GNW":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("ihYY"),u=n("Ip0R"),a=n("sdDj"),s=n("7LN8"),c=n("VSng"),d=(n("oygf"),function(){function e(e,t,n,l){var i=this;this.el=e,this.renderer=t,this.confirmationService=n,this.zone=l,this.acceptIcon="pi pi-check",this.acceptLabel="Yes",this.acceptVisible=!0,this.rejectIcon="pi pi-times",this.rejectLabel="No",this.rejectVisible=!0,this.closeOnEscape=!0,this.closable=!0,this.autoZIndex=!0,this.baseZIndex=0,this.transitionOptions="400ms cubic-bezier(0.25, 0.8, 0.25, 1)",this.subscription=this.confirmationService.requireConfirmation$.subscribe(function(e){e.key===i.key&&(i.confirmation=e,i.message=i.confirmation.message||i.message,i.icon=i.confirmation.icon||i.icon,i.header=i.confirmation.header||i.header,i.rejectVisible=null==i.confirmation.rejectVisible?i.rejectVisible:i.confirmation.rejectVisible,i.acceptVisible=null==i.confirmation.acceptVisible?i.acceptVisible:i.confirmation.acceptVisible,i.acceptLabel=i.confirmation.acceptLabel||i.acceptLabel,i.rejectLabel=i.confirmation.rejectLabel||i.rejectLabel,i.confirmation.accept&&(i.confirmation.acceptEvent=new o.EventEmitter,i.confirmation.acceptEvent.subscribe(i.confirmation.accept)),i.confirmation.reject&&(i.confirmation.rejectEvent=new o.EventEmitter,i.confirmation.rejectEvent.subscribe(i.confirmation.reject)),i.visible=!0)})}return Object.defineProperty(e.prototype,"width",{get:function(){return this._width},set:function(e){this._width=e,console.warn("width property is deprecated, use style to define the width of the Dialog.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"height",{get:function(){return this._height},set:function(e){this._height=e,console.warn("height property is deprecated, use style to define the height of the Dialog.")},enumerable:!0,configurable:!0}),e.prototype.onAnimationStart=function(e){switch(e.toState){case"visible":this.container=e.element,this.setDimensions(),this.contentContainer=a.DomHandler.findSingle(this.container,".ui-dialog-content"),a.DomHandler.findSingle(this.container,"button").focus(),this.appendContainer(),this.moveOnTop(),this.bindGlobalListeners(),this.enableModality();break;case"void":this.onOverlayHide()}},e.prototype.setDimensions=function(){this.width&&(this.container.style.width=this.width+"px"),this.height&&(this.container.style.height=this.height+"px")},e.prototype.appendContainer=function(){this.appendTo&&("body"===this.appendTo?document.body.appendChild(this.container):a.DomHandler.appendChild(this.container,this.appendTo))},e.prototype.restoreAppend=function(){this.container&&this.appendTo&&this.el.nativeElement.appendChild(this.container)},e.prototype.enableModality=function(){this.mask||(this.mask=document.createElement("div"),this.mask.style.zIndex=String(parseInt(this.container.style.zIndex)-1),a.DomHandler.addMultipleClasses(this.mask,"ui-widget-overlay ui-dialog-mask"),document.body.appendChild(this.mask),a.DomHandler.addClass(document.body,"ui-overflow-hidden"))},e.prototype.disableModality=function(){this.mask&&(document.body.removeChild(this.mask),a.DomHandler.removeClass(document.body,"ui-overflow-hidden"),this.mask=null)},e.prototype.close=function(e){this.confirmation.rejectEvent&&this.confirmation.rejectEvent.emit(),this.hide(),e.preventDefault()},e.prototype.hide=function(){this.visible=!1},e.prototype.moveOnTop=function(){this.autoZIndex&&(this.container.style.zIndex=String(this.baseZIndex+ ++a.DomHandler.zindex))},e.prototype.bindGlobalListeners=function(){var e=this;this.closeOnEscape&&this.closable&&!this.documentEscapeListener&&(this.documentEscapeListener=this.renderer.listen("document","keydown",function(t){27==t.which&&parseInt(e.container.style.zIndex)===a.DomHandler.zindex&&e.visible&&e.close(t)}))},e.prototype.unbindGlobalListeners=function(){this.documentEscapeListener&&(this.documentEscapeListener(),this.documentEscapeListener=null)},e.prototype.onOverlayHide=function(){this.disableModality(),this.unbindGlobalListeners(),this.container=null},e.prototype.ngOnDestroy=function(){this.restoreAppend(),this.onOverlayHide(),this.subscription.unsubscribe()},e.prototype.accept=function(){this.confirmation.acceptEvent&&this.confirmation.acceptEvent.emit(),this.hide(),this.confirmation=null},e.prototype.reject=function(){this.confirmation.rejectEvent&&this.confirmation.rejectEvent.emit(),this.hide(),this.confirmation=null},l([o.Input(),i("design:type",Boolean)],e.prototype,"visible",void 0),l([o.Input(),i("design:type",String)],e.prototype,"header",void 0),l([o.Input(),i("design:type",String)],e.prototype,"icon",void 0),l([o.Input(),i("design:type",String)],e.prototype,"message",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Input(),i("design:type",String)],e.prototype,"acceptIcon",void 0),l([o.Input(),i("design:type",String)],e.prototype,"acceptLabel",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"acceptVisible",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rejectIcon",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rejectLabel",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"rejectVisible",void 0),l([o.Input(),i("design:type",String)],e.prototype,"acceptButtonStyleClass",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rejectButtonStyleClass",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"closeOnEscape",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"rtl",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"closable",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"appendTo",void 0),l([o.Input(),i("design:type",String)],e.prototype,"key",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"autoZIndex",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"baseZIndex",void 0),l([o.Input(),i("design:type",String)],e.prototype,"transitionOptions",void 0),l([o.ContentChild(s.Footer),i("design:type",Object)],e.prototype,"footer",void 0),l([o.ViewChild("content"),i("design:type",o.ElementRef)],e.prototype,"contentViewChild",void 0),l([o.Input(),i("design:type",Object),i("design:paramtypes",[Object])],e.prototype,"width",null),l([o.Input(),i("design:type",Object),i("design:paramtypes",[Object])],e.prototype,"height",null),l([o.Component({selector:"p-confirmDialog",template:'\n
\n
\n {{header}}\n \n \n \n
\n
\n \n \n
\n \n \n
\n ',animations:[r.trigger("animation",[r.state("void",r.style({transform:"translate3d(-50%, -25%, 0) scale(0.9)",opacity:0})),r.state("visible",r.style({transform:"translateX(-50%) translateY(-50%)",opacity:1})),r.transition("* => *",r.animate("{{transitionParams}}"))])]})],e)}());t.ConfirmDialog=d,t.ConfirmDialogModule=function(){return l([o.NgModule({imports:[u.CommonModule,c.ButtonModule],exports:[d,c.ButtonModule,s.SharedModule],declarations:[d]})],function(){})}()},"3PJ4":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("sdDj"),a=function(){function e(e,t){this.el=e,this.zone=t,this.onDragStart=new o.EventEmitter,this.onDragEnd=new o.EventEmitter,this.onDrag=new o.EventEmitter}return e.prototype.ngAfterViewInit=function(){this.pDraggableDisabled||(this.el.nativeElement.draggable=!0,this.bindMouseListeners())},e.prototype.bindDragListener=function(){var e=this;this.dragListener||this.zone.runOutsideAngular(function(){e.dragListener=e.drag.bind(e),e.el.nativeElement.addEventListener("drag",e.dragListener)})},e.prototype.unbindDragListener=function(){var e=this;this.dragListener&&this.zone.runOutsideAngular(function(){e.el.nativeElement.removeEventListener("drag",e.dragListener),e.dragListener=null})},e.prototype.bindMouseListeners=function(){var e=this;this.mouseDownListener||this.mouseUpListener||this.zone.runOutsideAngular(function(){e.mouseDownListener=e.mousedown.bind(e),e.mouseUpListener=e.mouseup.bind(e),e.el.nativeElement.addEventListener("mousedown",e.mouseDownListener),e.el.nativeElement.addEventListener("mouseup",e.mouseUpListener)})},e.prototype.unbindMouseListeners=function(){var e=this;this.mouseDownListener&&this.mouseUpListener&&this.zone.runOutsideAngular(function(){e.el.nativeElement.removeEventListener("mousedown",e.mouseDownListener),e.el.nativeElement.removeEventListener("mouseup",e.mouseUpListener),e.mouseDownListener=null,e.mouseUpListener=null})},e.prototype.drag=function(e){this.onDrag.emit(e)},e.prototype.dragStart=function(e){this.allowDrag()?(this.dragEffect&&(e.dataTransfer.effectAllowed=this.dragEffect),e.dataTransfer.setData("text",this.scope),this.onDragStart.emit(e),this.bindDragListener()):e.preventDefault()},e.prototype.dragEnd=function(e){this.onDragEnd.emit(e),this.unbindDragListener()},e.prototype.mousedown=function(e){this.handle=e.target},e.prototype.mouseup=function(e){this.handle=null},e.prototype.allowDrag=function(){return!this.dragHandle||!this.handle||u.DomHandler.matches(this.handle,this.dragHandle)},e.prototype.ngOnDestroy=function(){this.unbindDragListener(),this.unbindMouseListeners()},l([o.Input("pDraggable"),i("design:type",String)],e.prototype,"scope",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pDraggableDisabled",void 0),l([o.Input(),i("design:type",String)],e.prototype,"dragEffect",void 0),l([o.Input(),i("design:type",String)],e.prototype,"dragHandle",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onDragStart",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onDragEnd",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onDrag",void 0),l([o.HostListener("dragstart",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"dragStart",null),l([o.HostListener("dragend",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"dragEnd",null),l([o.Directive({selector:"[pDraggable]"})],e)}();t.Draggable=a;var s=function(){function e(e,t){this.el=e,this.zone=t,this.onDragEnter=new o.EventEmitter,this.onDragLeave=new o.EventEmitter,this.onDrop=new o.EventEmitter}return e.prototype.ngAfterViewInit=function(){this.pDroppableDisabled||this.bindDragOverListener()},e.prototype.bindDragOverListener=function(){var e=this;this.dragOverListener||this.zone.runOutsideAngular(function(){e.dragOverListener=e.dragOver.bind(e),e.el.nativeElement.addEventListener("dragover",e.dragOverListener)})},e.prototype.unbindDragOverListener=function(){var e=this;this.dragOverListener&&this.zone.runOutsideAngular(function(){e.el.nativeElement.removeEventListener("dragover",e.dragOverListener),e.dragOverListener=null})},e.prototype.dragOver=function(e){e.preventDefault()},e.prototype.drop=function(e){this.allowDrop(e)&&(e.preventDefault(),this.onDrop.emit(e))},e.prototype.dragEnter=function(e){e.preventDefault(),this.dropEffect&&(e.dataTransfer.dropEffect=this.dropEffect),this.onDragEnter.emit(e)},e.prototype.dragLeave=function(e){e.preventDefault(),this.onDragLeave.emit(e)},e.prototype.allowDrop=function(e){var t=e.dataTransfer.getData("text");if("string"==typeof this.scope&&t==this.scope)return!0;if(this.scope instanceof Array)for(var n=0;n\n
\n \n
\n
\n
{{header}}
\n
{{subheader}}
\n
\n \n
\n \n
\n
\n '})],e)}();t.Card=a,t.CardModule=function(){return l([o.NgModule({imports:[r.CommonModule],exports:[a,u.SharedModule],declarations:[a]})],function(){})}()},"5EhP":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DynamicDialogInjector=function(){function e(e,t){this._parentInjector=e,this._additionalTokens=t}return e.prototype.get=function(e,t,n){return this._additionalTokens.get(e)||this._parentInjector.get(e,t)},e}()},"5xuf":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=function(){function e(e,t,n){this.el=e,this.renderer=t,this.viewContainer=n,this.onLoad=new o.EventEmitter}return e.prototype.ngAfterViewInit=function(){var e=this;this.shouldLoad()&&this.load(),this.isLoaded()||(this.documentScrollListener=this.renderer.listen("window","scroll",function(){e.shouldLoad()&&(e.load(),e.documentScrollListener(),e.documentScrollListener=null)}))},e.prototype.shouldLoad=function(){if(this.isLoaded())return!1;var e=this.el.nativeElement.getBoundingClientRect();return document.documentElement.clientHeight>=e.top},e.prototype.load=function(){this.view=this.viewContainer.createEmbeddedView(this.template),this.onLoad.emit()},e.prototype.isLoaded=function(){return null!=this.view},e.prototype.ngOnDestroy=function(){this.view=null,this.documentScrollListener&&this.documentScrollListener()},l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onLoad",void 0),l([o.ContentChild(o.TemplateRef),i("design:type",o.TemplateRef)],e.prototype,"template",void 0),l([o.Directive({selector:"[pDefer]"})],e)}();t.DeferredLoader=u,t.DeferModule=function(){return l([o.NgModule({imports:[r.CommonModule],exports:[u],declarations:[u]})],function(){})}()},"60iU":function(e,t,n){"use strict";n.d(t,"b",function(){return l}),n.d(t,"a",function(){return u});var l,i=n("G5J1"),o=n("F/XL"),r=n("XlPw");l||(l={});var u=function(){function e(e,t,n){this.kind=e,this.value=t,this.error=n,this.hasValue="N"===e}return e.prototype.observe=function(e){switch(this.kind){case"N":return e.next&&e.next(this.value);case"E":return e.error&&e.error(this.error);case"C":return e.complete&&e.complete()}},e.prototype.do=function(e,t,n){switch(this.kind){case"N":return e&&e(this.value);case"E":return t&&t(this.error);case"C":return n&&n()}},e.prototype.accept=function(e,t,n){return e&&"function"==typeof e.next?this.observe(e):this.do(e,t,n)},e.prototype.toObservable=function(){switch(this.kind){case"N":return Object(o.a)(this.value);case"E":return Object(r.a)(this.error);case"C":return Object(i.b)()}throw new Error("unexpected notification kind value")},e.createNext=function(t){return void 0!==t?new e("N",t):e.undefinedValueNotification},e.createError=function(t){return new e("E",void 0,t)},e.createComplete=function(){return e.completeNotification},e.completeNotification=new e("C"),e.undefinedValueNotification=new e("N",void 0),e}()},"66nc":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("ihYY"),u=n("Ip0R"),a=n("sdDj"),s=n("7LN8"),c=0,d=function(){function e(e,t,n){this.el=e,this.renderer=t,this.zone=n,this.draggable=!0,this.resizable=!0,this.closeOnEscape=!0,this.closable=!0,this.responsive=!0,this.showHeader=!0,this.breakpoint=640,this.blockScroll=!1,this.autoZIndex=!0,this.baseZIndex=0,this.minX=0,this.minY=0,this.focusOnShow=!0,this.transitionOptions="400ms cubic-bezier(0.25, 0.8, 0.25, 1)",this.closeIcon="pi pi-times",this.minimizeIcon="pi pi-window-minimize",this.maximizeIcon="pi pi-window-maximize",this.onShow=new o.EventEmitter,this.onHide=new o.EventEmitter,this.visibleChange=new o.EventEmitter,this.id="ui-dialog-"+c++}return Object.defineProperty(e.prototype,"width",{get:function(){return this._width},set:function(e){this._width=e,console.warn("width property is deprecated, use style to define the width of the Dialog.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"height",{get:function(){return this._height},set:function(e){this._height=e,console.warn("height property is deprecated, use style to define the height of the Dialog.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"minWidth",{get:function(){return this._minWidth},set:function(e){this._minWidth=e,console.warn("minWidth property is deprecated, use style to define the minWidth of the Dialog.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"minHeight",{get:function(){return this._minHeight},set:function(e){this._minHeight=e,console.warn("minHeight property is deprecated, use style to define the minHeight of the Dialog.")},enumerable:!0,configurable:!0}),e.prototype.focus=function(){var e=a.DomHandler.findSingle(this.container,"button");e&&this.zone.runOutsideAngular(function(){setTimeout(function(){return e.focus()},5)})},e.prototype.positionOverlay=function(){var e=a.DomHandler.getViewport();a.DomHandler.getOuterHeight(this.container)>e.height?(this.contentViewChild.nativeElement.style.height=.75*e.height+"px",this.container.style.height="auto"):(this.contentViewChild.nativeElement.style.height=null,this.height&&(this.container.style.height=this.height+"px")),this.positionLeft>=0&&this.positionTop>=0?(this.container.style.left=this.positionLeft+"px",this.container.style.top=this.positionTop+"px"):this.positionTop>=0?(this.center(),this.container.style.top=this.positionTop+"px"):this.center()},e.prototype.close=function(e){this.visibleChange.emit(!1),e.preventDefault()},e.prototype.center=function(){var e=a.DomHandler.getOuterWidth(this.container),t=a.DomHandler.getOuterHeight(this.container);0==e&&0==t&&(this.container.style.visibility="hidden",this.container.style.display="block",e=a.DomHandler.getOuterWidth(this.container),t=a.DomHandler.getOuterHeight(this.container),this.container.style.display="none",this.container.style.visibility="visible");var n=a.DomHandler.getViewport(),l=Math.max(Math.floor((n.width-e)/2),0),i=Math.max(Math.floor((n.height-t)/2),0);this.container.style.left=l+"px",this.container.style.top=i+"px"},e.prototype.enableModality=function(){var e=this;if(!this.mask){this.mask=document.createElement("div"),this.mask.style.zIndex=String(parseInt(this.container.style.zIndex)-1);var t="ui-widget-overlay ui-dialog-mask";this.blockScroll&&(t+=" ui-dialog-mask-scrollblocker"),a.DomHandler.addMultipleClasses(this.mask,t),this.closable&&this.dismissableMask&&(this.maskClickListener=this.renderer.listen(this.mask,"click",function(t){e.close(t)})),document.body.appendChild(this.mask),this.blockScroll&&a.DomHandler.addClass(document.body,"ui-overflow-hidden")}},e.prototype.disableModality=function(){if(this.mask){if(this.unbindMaskClickListener(),document.body.removeChild(this.mask),this.blockScroll){for(var e=document.body.children,t=void 0,n=0;n=this.minX&&r+t=this.minY&&u+nparseInt(s))&&d.left+rparseInt(c))&&d.top+u\n
\n {{header}}\n \n \n \n \n \n \n \n \n \n
\n
\n \n
\n \n
\n \n ',animations:[r.trigger("animation",[r.state("void",r.style({transform:"translate3d(0, 25%, 0) scale(0.9)",opacity:0})),r.state("visible",r.style({transform:"none",opacity:1})),r.transition("* => *",r.animate("{{transitionParams}}"))])]})],e)}();t.Dialog=d,t.DialogModule=function(){return l([o.NgModule({imports:[u.CommonModule],exports:[d,s.SharedModule],declarations:[d]})],function(){})}()},"67Y/":function(e,t,n){"use strict";n.d(t,"a",function(){return o});var l=n("mrSG"),i=n("FFOo");function o(e,t){return function(n){if("function"!=typeof e)throw new TypeError("argument is not a function. Are you looking for `mapTo()`?");return n.lift(new r(e,t))}}var r=function(){function e(e,t){this.project=e,this.thisArg=t}return e.prototype.call=function(e,t){return t.subscribe(new u(e,this.project,this.thisArg))},e}(),u=function(e){function t(t,n,l){var i=e.call(this,t)||this;return i.project=n,i.count=0,i.thisArg=l||i,i}return l.__extends(t,e),t.prototype._next=function(e){var t;try{t=this.project.call(this.thisArg,e,this.count++)}catch(n){return void this.destination.error(n)}this.destination.next(t)},t}(i.a)},"6MUB":function(e,t,n){"use strict";var l=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,i){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?Object.keys(e).map(function(i){var o=encodeURIComponent(l(i))+n;return Array.isArray(e[i])?e[i].map(function(e){return o+encodeURIComponent(l(e))}).join(t):o+encodeURIComponent(l(e[i]))}).join(t):i?encodeURIComponent(l(i))+n+encodeURIComponent(l(e)):""}},"6ahw":function(e,t,n){"use strict";n.d(t,"a",function(){return o});var l=n("iLxQ"),i=n("DKTb"),o={closed:!0,next:function(e){},error:function(e){if(l.a.useDeprecatedSynchronousErrorHandling)throw e;Object(i.a)(e)},complete:function(){}}},"6blF":function(e,t,n){"use strict";n.d(t,"a",function(){return c});var l=n("1fDf"),i=n("FFOo"),o=n("L/V9"),r=n("6ahw"),u=n("xTla"),a=n("y3By"),s=n("iLxQ"),c=function(){function e(e){this._isScalar=!1,e&&(this._subscribe=e)}return e.prototype.lift=function(t){var n=new e;return n.source=this,n.operator=t,n},e.prototype.subscribe=function(e,t,n){var l=this.operator,u=function(e,t,n){if(e){if(e instanceof i.a)return e;if(e[o.a])return e[o.a]()}return e||t||n?new i.a(e,t,n):new i.a(r.a)}(e,t,n);if(u.add(l?l.call(u,this.source):this.source||s.a.useDeprecatedSynchronousErrorHandling&&!u.syncErrorThrowable?this._subscribe(u):this._trySubscribe(u)),s.a.useDeprecatedSynchronousErrorHandling&&u.syncErrorThrowable&&(u.syncErrorThrowable=!1,u.syncErrorThrown))throw u.syncErrorValue;return u},e.prototype._trySubscribe=function(e){try{return this._subscribe(e)}catch(t){s.a.useDeprecatedSynchronousErrorHandling&&(e.syncErrorThrown=!0,e.syncErrorValue=t),Object(l.a)(e)?e.error(t):console.warn(t)}},e.prototype.forEach=function(e,t){var n=this;return new(t=d(t))(function(t,l){var i;i=n.subscribe(function(t){try{e(t)}catch(n){l(n),i&&i.unsubscribe()}},l,t)})},e.prototype._subscribe=function(e){var t=this.source;return t&&t.subscribe(e)},e.prototype[u.a]=function(){return this},e.prototype.pipe=function(){for(var e=[],t=0;t\n \n \n '})],e)}();t.Steps=a,t.StepsModule=function(){return l([o.NgModule({imports:[r.CommonModule,u.RouterModule],exports:[a,u.RouterModule],declarations:[a]})],function(){})}()},"7LN8":function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("CcnG"),a=function(){return l([u.Component({selector:"p-header",template:""})],function(){})}();t.Header=a;var s=function(){return l([u.Component({selector:"p-footer",template:""})],function(){})}();t.Footer=s;var c=function(){function e(e){this.template=e}return e.prototype.getType=function(){return this.name},l([o.Input(),i("design:type",String)],e.prototype,"type",void 0),l([o.Input("pTemplate"),i("design:type",String)],e.prototype,"name",void 0),l([o.Directive({selector:"[pTemplate]",host:{}})],e)}();t.PrimeTemplate=c;var d=function(){function e(){this.filterType="text",this.exportable=!0,this.resizable=!0,this.sortFunction=new o.EventEmitter}return e.prototype.ngAfterContentInit=function(){var e=this;this.templates.forEach(function(t){switch(t.getType()){case"header":e.headerTemplate=t.template;break;case"body":e.bodyTemplate=t.template;break;case"footer":e.footerTemplate=t.template;break;case"filter":e.filterTemplate=t.template;break;case"editor":e.editorTemplate=t.template;break;default:e.bodyTemplate=t.template}})},l([o.Input(),i("design:type",String)],e.prototype,"field",void 0),l([o.Input(),i("design:type",String)],e.prototype,"colId",void 0),l([o.Input(),i("design:type",String)],e.prototype,"sortField",void 0),l([o.Input(),i("design:type",String)],e.prototype,"filterField",void 0),l([o.Input(),i("design:type",String)],e.prototype,"header",void 0),l([o.Input(),i("design:type",String)],e.prototype,"footer",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"sortable",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"editable",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"filter",void 0),l([o.Input(),i("design:type",String)],e.prototype,"filterMatchMode",void 0),l([o.Input(),i("design:type",String)],e.prototype,"filterType",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"excludeGlobalFilter",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"rowspan",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"colspan",void 0),l([o.Input(),i("design:type",String)],e.prototype,"scope",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"exportable",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"headerStyle",void 0),l([o.Input(),i("design:type",String)],e.prototype,"headerStyleClass",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"bodyStyle",void 0),l([o.Input(),i("design:type",String)],e.prototype,"bodyStyleClass",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"footerStyle",void 0),l([o.Input(),i("design:type",String)],e.prototype,"footerStyleClass",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"hidden",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"expander",void 0),l([o.Input(),i("design:type",String)],e.prototype,"selectionMode",void 0),l([o.Input(),i("design:type",String)],e.prototype,"filterPlaceholder",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"filterMaxlength",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"frozen",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"resizable",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"sortFunction",void 0),l([o.ContentChildren(c),i("design:type",o.QueryList)],e.prototype,"templates",void 0),l([o.ContentChild(o.TemplateRef),i("design:type",o.TemplateRef)],e.prototype,"template",void 0),l([u.Component({selector:"p-column",template:""})],e)}();t.Column=d;var p=function(){function e(){}return l([o.ContentChildren(d),i("design:type",o.QueryList)],e.prototype,"columns",void 0),l([u.Component({selector:"p-row",template:""})],e)}();t.Row=p;var h=function(){function e(){}return l([o.Input(),i("design:type",Boolean)],e.prototype,"frozen",void 0),l([o.ContentChildren(p),i("design:type",o.QueryList)],e.prototype,"rows",void 0),l([u.Component({selector:"p-headerColumnGroup",template:""})],e)}();t.HeaderColumnGroup=h;var f=function(){function e(){}return l([o.Input(),i("design:type",Boolean)],e.prototype,"frozen",void 0),l([o.ContentChildren(p),i("design:type",o.QueryList)],e.prototype,"rows",void 0),l([u.Component({selector:"p-footerColumnGroup",template:""})],e)}();t.FooterColumnGroup=f,t.SharedModule=function(){return l([o.NgModule({imports:[r.CommonModule],exports:[a,s,d,c,p,h,f],declarations:[a,s,d,c,p,h,f]})],function(){})}()},"8EBN":function(e,t,n){!function(e){"use strict";e.modeInfo=[{name:"APL",mime:"text/apl",mode:"apl",ext:["dyalog","apl"]},{name:"PGP",mimes:["application/pgp","application/pgp-encrypted","application/pgp-keys","application/pgp-signature"],mode:"asciiarmor",ext:["asc","pgp","sig"]},{name:"ASN.1",mime:"text/x-ttcn-asn",mode:"asn.1",ext:["asn","asn1"]},{name:"Asterisk",mime:"text/x-asterisk",mode:"asterisk",file:/^extensions\.conf$/i},{name:"Brainfuck",mime:"text/x-brainfuck",mode:"brainfuck",ext:["b","bf"]},{name:"C",mime:"text/x-csrc",mode:"clike",ext:["c","h","ino"]},{name:"C++",mime:"text/x-c++src",mode:"clike",ext:["cpp","c++","cc","cxx","hpp","h++","hh","hxx"],alias:["cpp"]},{name:"Cobol",mime:"text/x-cobol",mode:"cobol",ext:["cob","cpy"]},{name:"C#",mime:"text/x-csharp",mode:"clike",ext:["cs"],alias:["csharp"]},{name:"Clojure",mime:"text/x-clojure",mode:"clojure",ext:["clj","cljc","cljx"]},{name:"ClojureScript",mime:"text/x-clojurescript",mode:"clojure",ext:["cljs"]},{name:"Closure Stylesheets (GSS)",mime:"text/x-gss",mode:"css",ext:["gss"]},{name:"CMake",mime:"text/x-cmake",mode:"cmake",ext:["cmake","cmake.in"],file:/^CMakeLists.txt$/},{name:"CoffeeScript",mimes:["application/vnd.coffeescript","text/coffeescript","text/x-coffeescript"],mode:"coffeescript",ext:["coffee"],alias:["coffee","coffee-script"]},{name:"Common Lisp",mime:"text/x-common-lisp",mode:"commonlisp",ext:["cl","lisp","el"],alias:["lisp"]},{name:"Cypher",mime:"application/x-cypher-query",mode:"cypher",ext:["cyp","cypher"]},{name:"Cython",mime:"text/x-cython",mode:"python",ext:["pyx","pxd","pxi"]},{name:"Crystal",mime:"text/x-crystal",mode:"crystal",ext:["cr"]},{name:"CSS",mime:"text/css",mode:"css",ext:["css"]},{name:"CQL",mime:"text/x-cassandra",mode:"sql",ext:["cql"]},{name:"D",mime:"text/x-d",mode:"d",ext:["d"]},{name:"Dart",mimes:["application/dart","text/x-dart"],mode:"dart",ext:["dart"]},{name:"diff",mime:"text/x-diff",mode:"diff",ext:["diff","patch"]},{name:"Django",mime:"text/x-django",mode:"django"},{name:"Dockerfile",mime:"text/x-dockerfile",mode:"dockerfile",file:/^Dockerfile$/},{name:"DTD",mime:"application/xml-dtd",mode:"dtd",ext:["dtd"]},{name:"Dylan",mime:"text/x-dylan",mode:"dylan",ext:["dylan","dyl","intr"]},{name:"EBNF",mime:"text/x-ebnf",mode:"ebnf"},{name:"ECL",mime:"text/x-ecl",mode:"ecl",ext:["ecl"]},{name:"edn",mime:"application/edn",mode:"clojure",ext:["edn"]},{name:"Eiffel",mime:"text/x-eiffel",mode:"eiffel",ext:["e"]},{name:"Elm",mime:"text/x-elm",mode:"elm",ext:["elm"]},{name:"Embedded Javascript",mime:"application/x-ejs",mode:"htmlembedded",ext:["ejs"]},{name:"Embedded Ruby",mime:"application/x-erb",mode:"htmlembedded",ext:["erb"]},{name:"Erlang",mime:"text/x-erlang",mode:"erlang",ext:["erl"]},{name:"Esper",mime:"text/x-esper",mode:"sql"},{name:"Factor",mime:"text/x-factor",mode:"factor",ext:["factor"]},{name:"FCL",mime:"text/x-fcl",mode:"fcl"},{name:"Forth",mime:"text/x-forth",mode:"forth",ext:["forth","fth","4th"]},{name:"Fortran",mime:"text/x-fortran",mode:"fortran",ext:["f","for","f77","f90","f95"]},{name:"F#",mime:"text/x-fsharp",mode:"mllike",ext:["fs"],alias:["fsharp"]},{name:"Gas",mime:"text/x-gas",mode:"gas",ext:["s"]},{name:"Gherkin",mime:"text/x-feature",mode:"gherkin",ext:["feature"]},{name:"GitHub Flavored Markdown",mime:"text/x-gfm",mode:"gfm",file:/^(readme|contributing|history).md$/i},{name:"Go",mime:"text/x-go",mode:"go",ext:["go"]},{name:"Groovy",mime:"text/x-groovy",mode:"groovy",ext:["groovy","gradle"],file:/^Jenkinsfile$/},{name:"HAML",mime:"text/x-haml",mode:"haml",ext:["haml"]},{name:"Haskell",mime:"text/x-haskell",mode:"haskell",ext:["hs"]},{name:"Haskell (Literate)",mime:"text/x-literate-haskell",mode:"haskell-literate",ext:["lhs"]},{name:"Haxe",mime:"text/x-haxe",mode:"haxe",ext:["hx"]},{name:"HXML",mime:"text/x-hxml",mode:"haxe",ext:["hxml"]},{name:"ASP.NET",mime:"application/x-aspx",mode:"htmlembedded",ext:["aspx"],alias:["asp","aspx"]},{name:"HTML",mime:"text/html",mode:"htmlmixed",ext:["html","htm","handlebars","hbs"],alias:["xhtml"]},{name:"HTTP",mime:"message/http",mode:"http"},{name:"IDL",mime:"text/x-idl",mode:"idl",ext:["pro"]},{name:"Pug",mime:"text/x-pug",mode:"pug",ext:["jade","pug"],alias:["jade"]},{name:"Java",mime:"text/x-java",mode:"clike",ext:["java"]},{name:"Java Server Pages",mime:"application/x-jsp",mode:"htmlembedded",ext:["jsp"],alias:["jsp"]},{name:"JavaScript",mimes:["text/javascript","text/ecmascript","application/javascript","application/x-javascript","application/ecmascript"],mode:"javascript",ext:["js"],alias:["ecmascript","js","node"]},{name:"JSON",mimes:["application/json","application/x-json"],mode:"javascript",ext:["json","map"],alias:["json5"]},{name:"JSON-LD",mime:"application/ld+json",mode:"javascript",ext:["jsonld"],alias:["jsonld"]},{name:"JSX",mime:"text/jsx",mode:"jsx",ext:["jsx"]},{name:"Jinja2",mime:"text/jinja2",mode:"jinja2",ext:["j2","jinja","jinja2"]},{name:"Julia",mime:"text/x-julia",mode:"julia",ext:["jl"]},{name:"Kotlin",mime:"text/x-kotlin",mode:"clike",ext:["kt"]},{name:"LESS",mime:"text/x-less",mode:"css",ext:["less"]},{name:"LiveScript",mime:"text/x-livescript",mode:"livescript",ext:["ls"],alias:["ls"]},{name:"Lua",mime:"text/x-lua",mode:"lua",ext:["lua"]},{name:"Markdown",mime:"text/x-markdown",mode:"markdown",ext:["markdown","md","mkd"]},{name:"mIRC",mime:"text/mirc",mode:"mirc"},{name:"MariaDB SQL",mime:"text/x-mariadb",mode:"sql"},{name:"Mathematica",mime:"text/x-mathematica",mode:"mathematica",ext:["m","nb"]},{name:"Modelica",mime:"text/x-modelica",mode:"modelica",ext:["mo"]},{name:"MUMPS",mime:"text/x-mumps",mode:"mumps",ext:["mps"]},{name:"MS SQL",mime:"text/x-mssql",mode:"sql"},{name:"mbox",mime:"application/mbox",mode:"mbox",ext:["mbox"]},{name:"MySQL",mime:"text/x-mysql",mode:"sql"},{name:"Nginx",mime:"text/x-nginx-conf",mode:"nginx",file:/nginx.*\.conf$/i},{name:"NSIS",mime:"text/x-nsis",mode:"nsis",ext:["nsh","nsi"]},{name:"NTriples",mimes:["application/n-triples","application/n-quads","text/n-triples"],mode:"ntriples",ext:["nt","nq"]},{name:"Objective-C",mime:"text/x-objectivec",mode:"clike",ext:["m","mm"],alias:["objective-c","objc"]},{name:"OCaml",mime:"text/x-ocaml",mode:"mllike",ext:["ml","mli","mll","mly"]},{name:"Octave",mime:"text/x-octave",mode:"octave",ext:["m"]},{name:"Oz",mime:"text/x-oz",mode:"oz",ext:["oz"]},{name:"Pascal",mime:"text/x-pascal",mode:"pascal",ext:["p","pas"]},{name:"PEG.js",mime:"null",mode:"pegjs",ext:["jsonld"]},{name:"Perl",mime:"text/x-perl",mode:"perl",ext:["pl","pm"]},{name:"PHP",mimes:["text/x-php","application/x-httpd-php","application/x-httpd-php-open"],mode:"php",ext:["php","php3","php4","php5","php7","phtml"]},{name:"Pig",mime:"text/x-pig",mode:"pig",ext:["pig"]},{name:"Plain Text",mime:"text/plain",mode:"null",ext:["txt","text","conf","def","list","log"]},{name:"PLSQL",mime:"text/x-plsql",mode:"sql",ext:["pls"]},{name:"PowerShell",mime:"application/x-powershell",mode:"powershell",ext:["ps1","psd1","psm1"]},{name:"Properties files",mime:"text/x-properties",mode:"properties",ext:["properties","ini","in"],alias:["ini","properties"]},{name:"ProtoBuf",mime:"text/x-protobuf",mode:"protobuf",ext:["proto"]},{name:"Python",mime:"text/x-python",mode:"python",ext:["BUILD","bzl","py","pyw"],file:/^(BUCK|BUILD)$/},{name:"Puppet",mime:"text/x-puppet",mode:"puppet",ext:["pp"]},{name:"Q",mime:"text/x-q",mode:"q",ext:["q"]},{name:"R",mime:"text/x-rsrc",mode:"r",ext:["r","R"],alias:["rscript"]},{name:"reStructuredText",mime:"text/x-rst",mode:"rst",ext:["rst"],alias:["rst"]},{name:"RPM Changes",mime:"text/x-rpm-changes",mode:"rpm"},{name:"RPM Spec",mime:"text/x-rpm-spec",mode:"rpm",ext:["spec"]},{name:"Ruby",mime:"text/x-ruby",mode:"ruby",ext:["rb"],alias:["jruby","macruby","rake","rb","rbx"]},{name:"Rust",mime:"text/x-rustsrc",mode:"rust",ext:["rs"]},{name:"SAS",mime:"text/x-sas",mode:"sas",ext:["sas"]},{name:"Sass",mime:"text/x-sass",mode:"sass",ext:["sass"]},{name:"Scala",mime:"text/x-scala",mode:"clike",ext:["scala"]},{name:"Scheme",mime:"text/x-scheme",mode:"scheme",ext:["scm","ss"]},{name:"SCSS",mime:"text/x-scss",mode:"css",ext:["scss"]},{name:"Shell",mimes:["text/x-sh","application/x-sh"],mode:"shell",ext:["sh","ksh","bash"],alias:["bash","sh","zsh"],file:/^PKGBUILD$/},{name:"Sieve",mime:"application/sieve",mode:"sieve",ext:["siv","sieve"]},{name:"Slim",mimes:["text/x-slim","application/x-slim"],mode:"slim",ext:["slim"]},{name:"Smalltalk",mime:"text/x-stsrc",mode:"smalltalk",ext:["st"]},{name:"Smarty",mime:"text/x-smarty",mode:"smarty",ext:["tpl"]},{name:"Solr",mime:"text/x-solr",mode:"solr"},{name:"SML",mime:"text/x-sml",mode:"mllike",ext:["sml","sig","fun","smackspec"]},{name:"Soy",mime:"text/x-soy",mode:"soy",ext:["soy"],alias:["closure template"]},{name:"SPARQL",mime:"application/sparql-query",mode:"sparql",ext:["rq","sparql"],alias:["sparul"]},{name:"Spreadsheet",mime:"text/x-spreadsheet",mode:"spreadsheet",alias:["excel","formula"]},{name:"SQL",mime:"text/x-sql",mode:"sql",ext:["sql"]},{name:"SQLite",mime:"text/x-sqlite",mode:"sql"},{name:"Squirrel",mime:"text/x-squirrel",mode:"clike",ext:["nut"]},{name:"Stylus",mime:"text/x-styl",mode:"stylus",ext:["styl"]},{name:"Swift",mime:"text/x-swift",mode:"swift",ext:["swift"]},{name:"sTeX",mime:"text/x-stex",mode:"stex"},{name:"LaTeX",mime:"text/x-latex",mode:"stex",ext:["text","ltx","tex"],alias:["tex"]},{name:"SystemVerilog",mime:"text/x-systemverilog",mode:"verilog",ext:["v","sv","svh"]},{name:"Tcl",mime:"text/x-tcl",mode:"tcl",ext:["tcl"]},{name:"Textile",mime:"text/x-textile",mode:"textile",ext:["textile"]},{name:"TiddlyWiki ",mime:"text/x-tiddlywiki",mode:"tiddlywiki"},{name:"Tiki wiki",mime:"text/tiki",mode:"tiki"},{name:"TOML",mime:"text/x-toml",mode:"toml",ext:["toml"]},{name:"Tornado",mime:"text/x-tornado",mode:"tornado"},{name:"troff",mime:"text/troff",mode:"troff",ext:["1","2","3","4","5","6","7","8","9"]},{name:"TTCN",mime:"text/x-ttcn",mode:"ttcn",ext:["ttcn","ttcn3","ttcnpp"]},{name:"TTCN_CFG",mime:"text/x-ttcn-cfg",mode:"ttcn-cfg",ext:["cfg"]},{name:"Turtle",mime:"text/turtle",mode:"turtle",ext:["ttl"]},{name:"TypeScript",mime:"application/typescript",mode:"javascript",ext:["ts"],alias:["ts"]},{name:"TypeScript-JSX",mime:"text/typescript-jsx",mode:"jsx",ext:["tsx"],alias:["tsx"]},{name:"Twig",mime:"text/x-twig",mode:"twig"},{name:"Web IDL",mime:"text/x-webidl",mode:"webidl",ext:["webidl"]},{name:"VB.NET",mime:"text/x-vb",mode:"vb",ext:["vb"]},{name:"VBScript",mime:"text/vbscript",mode:"vbscript",ext:["vbs"]},{name:"Velocity",mime:"text/velocity",mode:"velocity",ext:["vtl"]},{name:"Verilog",mime:"text/x-verilog",mode:"verilog",ext:["v"]},{name:"VHDL",mime:"text/x-vhdl",mode:"vhdl",ext:["vhd","vhdl"]},{name:"Vue.js Component",mimes:["script/x-vue","text/x-vue"],mode:"vue",ext:["vue"]},{name:"XML",mimes:["application/xml","text/xml"],mode:"xml",ext:["xml","xsl","xsd","svg"],alias:["rss","wsdl","xsd"]},{name:"XQuery",mime:"application/xquery",mode:"xquery",ext:["xy","xquery"]},{name:"Yacas",mime:"text/x-yacas",mode:"yacas",ext:["ys"]},{name:"YAML",mimes:["text/x-yaml","text/yaml"],mode:"yaml",ext:["yaml","yml"],alias:["yml"]},{name:"Z80",mime:"text/x-z80",mode:"z80",ext:["z80"]},{name:"mscgen",mime:"text/x-mscgen",mode:"mscgen",ext:["mscgen","mscin","msc"]},{name:"xu",mime:"text/x-xu",mode:"mscgen",ext:["xu"]},{name:"msgenny",mime:"text/x-msgenny",mode:"mscgen",ext:["msgenny"]}];for(var t=0;t-1&&t.substring(i+1,t.length);if(o)return e.findModeByExtension(o)},e.findModeByName=function(t){t=t.toLowerCase();for(var n=0;n\n
\n \n
\n
\n \n
\n \n '})],e)}();t.DTRadioButton=h;var f=function(){function e(){this.onChange=new o.EventEmitter}return e.prototype.handleClick=function(e){this.disabled||this.onChange.emit({originalEvent:e,checked:!this.checked})},l([o.Input(),i("design:type",Boolean)],e.prototype,"checked",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"disabled",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onChange",void 0),l([o.Component({selector:"p-dtCheckbox",template:'\n
\n
\n \n
\n
\n \n
\n
\n '})],e)}();t.DTCheckbox=f;var g=function(){function e(e){this.dt=e}return l([o.Input("pColumnHeaders"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Component({selector:"[pColumnHeaders]",template:'\n \n
\n \n {{col.header}}\n \n \n \n \n \n \n \n \n \n \n {{col.footer}}\n \n \n \n
\n \n \n \n \n \n \n
\n {{col.header}}\n {{dt.resolveFieldData(rowData,col.field)}}\n \n \n \n
\n \n \n \n \n
\n \n \n \n \n \n
\n \n
\n {{dt.emptyMessage}}\n \n \n \n
\n \n \n \n \n \n \n \n
\n \n \n
\n
\n \n \n \n \n \n
\n
\n
\n \n '})],e)}();t.ScrollableView=y;var b=function(){function e(e,t,n,l,i){this.el=e,this.differs=t,this.renderer=n,this.changeDetector=l,this.zone=i,this.pageLinks=5,this.selectionChange=new o.EventEmitter,this.showHeaderCheckbox=!0,this.onRowClick=new o.EventEmitter,this.onRowSelect=new o.EventEmitter,this.onRowUnselect=new o.EventEmitter,this.onRowDblclick=new o.EventEmitter,this.onHeaderCheckboxToggle=new o.EventEmitter,this.onContextMenuSelect=new o.EventEmitter,this.filterDelay=300,this.onLazyLoad=new o.EventEmitter,this.columnResizeMode="fit",this.onColResize=new o.EventEmitter,this.onColReorder=new o.EventEmitter,this.sortMode="single",this.defaultSortOrder=1,this.csvSeparator=",",this.exportFilename="download",this.emptyMessage="No records found",this.paginatorPosition="bottom",this.alwaysShowPaginator=!0,this.metaKeySelection=!0,this.rowTrackBy=function(e,t){return t},this.immutable=!0,this.compareSelectionBy="deepEquals",this.onEditInit=new o.EventEmitter,this.onEditComplete=new o.EventEmitter,this.onEdit=new o.EventEmitter,this.onEditCancel=new o.EventEmitter,this.onPage=new o.EventEmitter,this.onSort=new o.EventEmitter,this.onFilter=new o.EventEmitter,this.rowExpandMode="multiple",this.expandedIcon="fa-chevron-circle-down",this.collapsedIcon="fa-chevron-circle-right",this.tabindex=1,this.sortableRowGroup=!0,this.filters={},this.loadingIcon="fa-circle-o-notch",this.virtualScrollDelay=500,this.rowGroupExpandMode="multiple",this.valueChange=new o.EventEmitter,this.firstChange=new o.EventEmitter,this.onRowExpand=new o.EventEmitter,this.onRowCollapse=new o.EventEmitter,this.onRowGroupExpand=new o.EventEmitter,this.onRowGroupCollapse=new o.EventEmitter,this.page=0,this.columnsChanged=!1,this._first=0,this._sortOrder=1,this.filterConstraints={startsWith:function(e,t){if(null==t||""===t.trim())return!0;if(null==e)return!1;var n=t.toLowerCase();return e.toString().toLowerCase().slice(0,n.length)===n},contains:function(e,t){return null==t||"string"==typeof t&&""===t.trim()||null!=e&&-1!==e.toString().toLowerCase().indexOf(t.toLowerCase())},endsWith:function(e,t){if(null==t||""===t.trim())return!0;if(null==e)return!1;var n=t.toString().toLowerCase();return-1!==e.toString().toLowerCase().indexOf(n,e.toString().length-n.length)},equals:function(e,t){return null==t||"string"==typeof t&&""===t.trim()||null!=e&&e.toString().toLowerCase()==t.toString().toLowerCase()},notEquals:function(e,t){return!(null==t||"string"==typeof t&&""===t.trim()||null!=e&&e.toString().toLowerCase()==t.toString().toLowerCase())},in:function(e,t){if(null==t||0===t.length)return!0;if(null==e)return!1;for(var n=0;n=this.totalRecords){var e=Math.ceil(this.totalRecords/this.rows);this._first=Math.max((e-1)*this.rows,0)}},e.prototype.updateTotalRecords=function(){this.totalRecords=this.lazy?this.totalRecords:this.value?this.value.length:0},e.prototype.onPageChange=function(e){this._first=e.first,this.firstChange.emit(this.first),this.rows=e.rows,this.paginate()},e.prototype.paginate=function(){this.lazy?this.onLazyLoad.emit(this.createLazyLoadMetadata()):this.updateDataToRender(this.filteredValue||this.value),this.onPage.emit({first:this.first,rows:this.rows})},e.prototype.updateDataToRender=function(e){if((this.paginator||this.virtualScroll)&&e){this.dataToRender=[];for(var t=this.lazy?0:this.first,n=this.virtualScroll?this.first+2*this.rows:t+this.rows,l=t;l=e.length);l++)this.dataToRender.push(e[l])}else this.dataToRender=e;this.rowGroupMode&&this.updateRowGroupMetadata(),this.changeDetector.markForCheck()},e.prototype.onVirtualScroll=function(e){var t=this;this._first=(e.page-1)*this.rows,this.virtualScrollCallback=e.callback,this.zone.run(function(){t.virtualScrollTimer&&clearTimeout(t.virtualScrollTimer),t.virtualScrollTimer=setTimeout(function(){t.lazy?t.onLazyLoad.emit(t.createLazyLoadMetadata()):t.updateDataToRender(t.filteredValue||t.value)},t.virtualScrollDelay)})},e.prototype.onHeaderKeydown=function(e,t){13==e.keyCode&&(this.sort(e,t),e.preventDefault())},e.prototype.onHeaderMousedown=function(e,t){this.reorderableColumns&&("INPUT"!==e.target.nodeName?t.draggable=!0:"INPUT"===e.target.nodeName&&(t.draggable=!1))},e.prototype.sort=function(e,t){if(t.sortable){var n=e.target;if(d.DomHandler.hasClass(n,"ui-sortable-column")||d.DomHandler.hasClass(n,"ui-column-title")||d.DomHandler.hasClass(n,"ui-sortable-column-icon")){this.immutable||(this.preventSortPropagation=!0);var l=t.sortField||t.field;this._sortOrder=this.sortField===l?-1*this.sortOrder:this.defaultSortOrder,this._sortField=l,this.sortColumn=t,"multiple"==this.sortMode&&(this.multiSortMeta&&(e.metaKey||e.ctrlKey)||(this._multiSortMeta=[]),this.addSortMeta({field:this.sortField,order:this.sortOrder})),this.lazy?(this._first=0,this.onLazyLoad.emit(this.createLazyLoadMetadata())):"multiple"==this.sortMode?this.sortMultiple():this.sortSingle(),this.onSort.emit({field:this.sortField,order:this.sortOrder,multisortmeta:this.multiSortMeta})}this.updateDataToRender(this.filteredValue||this.value)}},e.prototype.sortSingle=function(){var e=this;this.value&&(this.sortColumn&&"custom"===this.sortColumn.sortable?(this.preventSortPropagation=!0,this.sortColumn.sortFunction.emit({field:this.sortField,order:this.sortOrder})):this.value.sort(function(t,n){var l,i=e.resolveFieldData(t,e.sortField),o=e.resolveFieldData(n,e.sortField);return l=null==i&&null!=o?-1:null!=i&&null==o?1:null==i&&null==o?0:"string"==typeof i&&"string"==typeof o?i.localeCompare(o):io?1:0,e.sortOrder*l}),this._first=0,this.hasFilter()&&this._filter())},e.prototype.sortMultiple=function(){var e=this;this.value&&(this.value.sort(function(t,n){return e.multisortField(t,n,e.multiSortMeta,0)}),this.hasFilter()&&this._filter())},e.prototype.multisortField=function(e,t,n,l){var i=this.resolveFieldData(e,n[l].field),o=this.resolveFieldData(t,n[l].field),r=null;if("string"==typeof i||i instanceof String){if(i.localeCompare&&i!=o)return n[l].order*i.localeCompare(o)}else r=il?this.multisortField(e,t,n,l+1):0:n[l].order*r},e.prototype.addSortMeta=function(e){for(var t=-1,n=0;n=0?this.multiSortMeta[t]=e:this.multiSortMeta.push(e)},e.prototype.isSorted=function(e){if(!e.sortable)return!1;var t=e.sortField||e.field;if("single"===this.sortMode)return this.sortField&&t===this.sortField;if("multiple"===this.sortMode){var n=!1;if(this.multiSortMeta)for(var l=0;lthis.anchorRowIndex?(t=this.anchorRowIndex,n=this.rangeRowIndex):this.rangeRowIndext?(n=t,l=this.anchorRowIndex):this.anchorRowIndex-1:this.equals(e,this.selection))},e.prototype.equals=function(e,t){return"equals"===this.compareSelectionBy?e===t:p.ObjectUtils.equals(e,t,this.dataKey)},Object.defineProperty(e.prototype,"allSelected",{get:function(){if(this.headerCheckboxToggleAllPages)return this.selection&&this.value&&this.selection.length===this.value.length;var e=!0;if(this.dataToRender&&this.selection&&this.dataToRender.length<=this.selection.length){for(var t=0,n=this.dataToRender;tparseInt(this.resizeColumn.style.minWidth||15)){if("fit"===this.columnResizeMode){for(var i=this.resizeColumn.nextElementSibling;d.DomHandler.hasClass(i,"ui-helper-hidden");)i=i.nextElementSibling;if(i){var o=i.offsetWidth-t;if(l>15&&o>parseInt(i.style.minWidth||15)&&(this.resizeColumn.style.width=l+"px",i&&(i.style.width=o+"px"),this.scrollable)){var r=d.DomHandler.findSingle(this.el.nativeElement,"colgroup.ui-datatable-scrollable-colgroup"),u=d.DomHandler.index(this.resizeColumn);r.children[u].style.width=l+"px",i&&(r.children[u+1].style.width=o+"px")}}}else if("expand"===this.columnResizeMode){this.tbody.parentElement.style.width=this.tbody.parentElement.offsetWidth+t+"px",this.resizeColumn.style.width=l+"px";var a=this.tbody.parentElement.style.width;this.scrollable?(d.DomHandler.findSingle(this.el.nativeElement,".ui-datatable-scrollable-header-box").children[0].style.width=a,r=d.DomHandler.findSingle(this.el.nativeElement,"colgroup.ui-datatable-scrollable-colgroup"),u=d.DomHandler.index(this.resizeColumn),r.children[u].style.width=l+"px"):this.el.nativeElement.children[0].style.width=a}this.onColResize.emit({element:this.resizeColumn,delta:t})}this.resizerHelper.style.display="none",this.resizeColumn=null,d.DomHandler.removeClass(this.el.nativeElement.children[0],"ui-unselectable-text"),this.unbindColumnResizeEvents()},e.prototype.fixColumnWidths=function(){for(var e,t=d.DomHandler.find(this.el.nativeElement,"th.ui-resizable-column"),n=0;no?(this.reorderIndicatorUp.style.left=i+t.offsetWidth-Math.ceil(this.iconWidth/2)+"px",this.reorderIndicatorDown.style.left=i+t.offsetWidth-Math.ceil(this.iconWidth/2)+"px",this.dropPosition=1):(this.reorderIndicatorUp.style.left=i-Math.ceil(this.iconWidth/2)+"px",this.reorderIndicatorDown.style.left=i-Math.ceil(this.iconWidth/2)+"px",this.dropPosition=-1),this.reorderIndicatorUp.style.display="block",this.reorderIndicatorDown.style.display="block"}else e.dataTransfer.dropEffect="none"}},e.prototype.onColumnDragleave=function(e){this.reorderableColumns&&this.draggedColumn&&(e.preventDefault(),this.reorderIndicatorUp.style.display="none",this.reorderIndicatorDown.style.display="none",window.document.removeEventListener("dragover",this.onColumnDragover))},e.prototype.onColumnDrop=function(e){if(e.preventDefault(),this.draggedColumn){var t=d.DomHandler.index(this.draggedColumn),n=d.DomHandler.index(this.findParentHeader(e.target)),l=t!=n;l&&(n-t==1&&-1===this.dropPosition||t-n==1&&1===this.dropPosition)&&(l=!1),l&&(p.ObjectUtils.reorderArray(this.columns,t,n),this.scrollable&&this.initScrollableColumns(),this.onColReorder.emit({dragIndex:t,dropIndex:n,columns:this.columns})),this.reorderIndicatorUp.style.display="none",this.reorderIndicatorDown.style.display="none",this.draggedColumn.draggable=!1,this.draggedColumn=null,this.dropPosition=null}},e.prototype.initColumnReordering=function(){this.reorderIndicatorUp=d.DomHandler.findSingle(this.el.nativeElement.children[0],"span.ui-datatable-reorder-indicator-up"),this.reorderIndicatorDown=d.DomHandler.findSingle(this.el.nativeElement.children[0],"span.ui-datatable-reorder-indicator-down"),this.iconWidth=d.DomHandler.getHiddenElementOuterWidth(this.reorderIndicatorUp),this.iconHeight=d.DomHandler.getHiddenElementOuterHeight(this.reorderIndicatorUp)},e.prototype.findParentHeader=function(e){if("TH"==e.nodeName)return e;for(var t=e.parentElement;"TH"!=t.nodeName&&(t=t.parentElement););return t},e.prototype.hasFooter=function(){if(this.footerColumnGroups&&this.footerColumnGroups.first)return!0;if(this.columns)for(var e=0;e=0?(this.expandedRowsGroups.splice(n,1),this.onRowGroupCollapse.emit({originalEvent:e,group:l})):("single"===this.rowGroupExpandMode&&(this.expandedRowsGroups=[]),this.expandedRowsGroups.push(l),this.onRowGroupExpand.emit({originalEvent:e,group:l})),e.preventDefault()},e.prototype.reset=function(){this._sortField=null,this._sortOrder=1,this.filteredValue=null,this.filters={},this._first=0,this.firstChange.emit(this._first),this.updateTotalRecords(),this.lazy?this.onLazyLoad.emit(this.createLazyLoadMetadata()):this.updateDataToRender(this.value)},e.prototype.exportCSV=function(e){var t=this,n=this.filteredValue||this.value,l="\ufeff";e&&e.selectionOnly&&(n=this.selection||[]);for(var i=0;i0},e.prototype.ngOnDestroy=function(){this.globalFilterFunction&&this.globalFilterFunction(),this.resizableColumns&&this.unbindColumnResizeEvents(),this.unbindDocumentEditListener(),this.columnsSubscription&&this.columnsSubscription.unsubscribe(),this.virtualScrollCallback&&(this.virtualScrollCallback=null)},l([o.Input(),i("design:type",Boolean)],e.prototype,"paginator",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"rows",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"pageLinks",void 0),l([o.Input(),i("design:type",Array)],e.prototype,"rowsPerPageOptions",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"responsive",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"stacked",void 0),l([o.Input(),i("design:type",String)],e.prototype,"selectionMode",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"selectionChange",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"editable",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"showHeaderCheckbox",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowClick",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowSelect",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowUnselect",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowDblclick",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onHeaderCheckboxToggle",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"headerCheckboxToggleAllPages",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onContextMenuSelect",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"filterDelay",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"lazy",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onLazyLoad",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"resizableColumns",void 0),l([o.Input(),i("design:type",String)],e.prototype,"columnResizeMode",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onColResize",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"reorderableColumns",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onColReorder",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"scrollable",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"virtualScroll",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"scrollHeight",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"scrollWidth",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"frozenWidth",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"unfrozenWidth",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"tableStyle",void 0),l([o.Input(),i("design:type",String)],e.prototype,"tableStyleClass",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"globalFilter",void 0),l([o.Input(),i("design:type",String)],e.prototype,"sortMode",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"defaultSortOrder",void 0),l([o.Input(),i("design:type",String)],e.prototype,"groupField",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"contextMenu",void 0),l([o.Input(),i("design:type",String)],e.prototype,"csvSeparator",void 0),l([o.Input(),i("design:type",String)],e.prototype,"exportFilename",void 0),l([o.Input(),i("design:type",String)],e.prototype,"emptyMessage",void 0),l([o.Input(),i("design:type",String)],e.prototype,"paginatorPosition",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"alwaysShowPaginator",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"metaKeySelection",void 0),l([o.Input(),i("design:type",Function)],e.prototype,"rowTrackBy",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"immutable",void 0),l([o.Input(),i("design:type",Array)],e.prototype,"frozenValue",void 0),l([o.Input(),i("design:type",String)],e.prototype,"compareSelectionBy",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onEditInit",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onEditComplete",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onEdit",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onEditCancel",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onPage",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onSort",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onFilter",void 0),l([o.ContentChild(c.Header),i("design:type",Object)],e.prototype,"header",void 0),l([o.ContentChild(c.Footer),i("design:type",Object)],e.prototype,"footer",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"expandableRows",void 0),l([o.Input(),i("design:type",Array)],e.prototype,"expandedRows",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"expandableRowGroups",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rowExpandMode",void 0),l([o.Input(),i("design:type",Array)],e.prototype,"expandedRowsGroups",void 0),l([o.Input(),i("design:type",String)],e.prototype,"expandedIcon",void 0),l([o.Input(),i("design:type",String)],e.prototype,"collapsedIcon",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"tabindex",void 0),l([o.Input(),i("design:type",Function)],e.prototype,"rowStyleClass",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"rowStyleMap",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rowGroupMode",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"sortableRowGroup",void 0),l([o.Input(),i("design:type",String)],e.prototype,"sortFile",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"rowHover",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"filters",void 0),l([o.Input(),i("design:type",String)],e.prototype,"dataKey",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"loading",void 0),l([o.Input(),i("design:type",String)],e.prototype,"loadingIcon",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"virtualScrollDelay",void 0),l([o.Input(),i("design:type",String)],e.prototype,"rowGroupExpandMode",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"valueChange",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"firstChange",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowExpand",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowCollapse",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowGroupExpand",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onRowGroupCollapse",void 0),l([o.ContentChildren(c.PrimeTemplate),i("design:type",o.QueryList)],e.prototype,"templates",void 0),l([o.ContentChildren(c.Column),i("design:type",o.QueryList)],e.prototype,"cols",void 0),l([o.ContentChildren(c.HeaderColumnGroup),i("design:type",o.QueryList)],e.prototype,"headerColumnGroups",void 0),l([o.ContentChildren(c.FooterColumnGroup),i("design:type",o.QueryList)],e.prototype,"footerColumnGroups",void 0),l([o.Input(),i("design:type",Array),i("design:paramtypes",[Array])],e.prototype,"multiSortMeta",null),l([o.Input(),i("design:type",String),i("design:paramtypes",[String])],e.prototype,"sortField",null),l([o.Input(),i("design:type",Number),i("design:paramtypes",[Number])],e.prototype,"sortOrder",null),l([o.Input(),i("design:type",Array),i("design:paramtypes",[Array])],e.prototype,"value",null),l([o.Input(),i("design:type",Number),i("design:paramtypes",[Number])],e.prototype,"first",null),l([o.Input(),i("design:type",Number),i("design:paramtypes",[Number])],e.prototype,"totalRecords",null),l([o.Input(),i("design:type",Object),i("design:paramtypes",[Object])],e.prototype,"selection",null),l([o.Component({selector:"p-dataTable",template:'\n
\n
\n
\n \n
\n
\n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n \n \n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n
\n '})],e)}();t.DataTable=b,t.DataTableModule=function(){return l([o.NgModule({imports:[r.CommonModule,a.SharedModule,s.PaginatorModule,u.FormsModule],exports:[b,a.SharedModule],declarations:[b,h,f,g,m,v,y]})],function(){})}()},ARwZ:function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("gIcY");t.RATING_VALUE_ACCESSOR={provide:u.NG_VALUE_ACCESSOR,useExisting:o.forwardRef(function(){return a}),multi:!0};var a=function(){function e(e){this.cd=e,this.stars=5,this.cancel=!0,this.iconOnClass="pi pi-star",this.iconOffClass="pi pi-star-o",this.iconCancelClass="pi pi-ban",this.onRate=new o.EventEmitter,this.onCancel=new o.EventEmitter,this.onModelChange=function(){},this.onModelTouched=function(){}}return e.prototype.ngOnInit=function(){this.starsArray=[];for(var e=0;e\n \n \n \n \n \n \n \n ',providers:[t.RATING_VALUE_ACCESSOR]})],e)}();t.Rating=a,t.RatingModule=function(){return l([o.NgModule({imports:[r.CommonModule],exports:[a],declarations:[a]})],function(){})}()},Avra:function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("gIcY"),u=n("Ip0R"),a=n("sdDj"),s=(n("VeV1"),function(){function e(e,t){var n=this;this.el=e,this.terminalService=t,this.commands=[],this.subscription=t.responseHandler.subscribe(function(e){n.commands[n.commands.length-1].response=e,n.commandProcessed=!0})}return e.prototype.ngAfterViewInit=function(){this.container=a.DomHandler.find(this.el.nativeElement,".ui-terminal")[0]},e.prototype.ngAfterViewChecked=function(){this.commandProcessed&&(this.container.scrollTop=this.container.scrollHeight,this.commandProcessed=!1)},Object.defineProperty(e.prototype,"response",{set:function(e){e&&(this.commands[this.commands.length-1].response=e,this.commandProcessed=!0)},enumerable:!0,configurable:!0}),e.prototype.handleCommand=function(e){13==e.keyCode&&(this.commands.push({text:this.command}),this.terminalService.sendCommand(this.command),this.command="")},e.prototype.focus=function(e){e.focus()},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input(),i("design:type",String)],e.prototype,"welcomeMessage",void 0),l([o.Input(),i("design:type",String)],e.prototype,"prompt",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Input(),i("design:type",String),i("design:paramtypes",[String])],e.prototype,"response",null),l([o.Component({selector:"p-terminal",template:'\n
\n
{{welcomeMessage}}
\n
\n
\n {{prompt}}\n {{command.text}}\n
{{command.response}}
\n
\n
\n
\n {{prompt}}\n \n
\n
\n '})],e)}());t.Terminal=s,t.TerminalModule=function(){return l([o.NgModule({imports:[u.CommonModule,r.FormsModule],exports:[s],declarations:[s]})],function(){})}()},AxiF:function(e,t,n){"use strict";n.d(t,"a",function(){return u});var l=n("mrSG"),i=n("FFOo"),o=n("b7mW"),r=n("G5J1");function u(e){return function(t){return 0===e?Object(r.b)():t.lift(new a(e))}}var a=function(){function e(e){if(this.total=e,this.total<0)throw new o.a}return e.prototype.call=function(e,t){return t.subscribe(new s(e,this.total))},e}(),s=function(e){function t(t,n){var l=e.call(this,t)||this;return l.total=n,l.ring=new Array,l.count=0,l}return l.__extends(t,e),t.prototype._next=function(e){var t=this.ring,n=this.total,l=this.count++;t.length0)for(var n=this.count>=this.total?this.total:this.count,l=this.ring,i=0;it.cursorCoords(n,"window").top&&((h=l).style.opacity=.4)}))};!function(e,t,n,l,i){e.openDialog(t,f,{value:s,selectValueOnOpen:!0,closeOnEnter:!1,onClose:function(){d(e)},onKeyDown:i})}(t,p(t),0,0,function(l,i){var o=e.keyName(l),r=t.getOption("extraKeys"),u=r&&r[o]||e.keyMap[t.getOption("keyMap")][o];"findNext"==u||"findPrev"==u||"findPersistentNext"==u||"findPersistentPrev"==u?(e.e_stop(l),a(t,n(t),i),t.execCommand(u)):"find"!=u&&"findPersistent"!=u||(e.e_stop(l),f(i,l))}),r&&s&&(a(t,u,s),c(t,l))}else o(t,p(t),"Search for:",s,function(e){e&&!u.query&&t.operation(function(){a(t,u,e),u.posFrom=u.posTo=t.getCursor(),c(t,l)})})}function c(t,l,o){t.operation(function(){var r=n(t),u=i(t,r.query,l?r.posFrom:r.posTo);(u.find(l)||(u=i(t,r.query,l?e.Pos(t.lastLine()):e.Pos(t.firstLine(),0))).find(l))&&(t.setSelection(u.from(),u.to()),t.scrollIntoView({from:u.from(),to:u.to()},20),r.posFrom=u.from(),r.posTo=u.to(),o&&o(u.from(),u.to()))})}function d(e){e.operation(function(){var t=n(e);t.lastQuery=t.query,t.query&&(t.query=t.queryText=null,e.removeOverlay(t.overlay),t.annotate&&(t.annotate.clear(),t.annotate=null))})}function p(e){return''+e.phrase("Search:")+' '+e.phrase("(Use /re/ syntax for regexp search)")+""}function h(e,t,n){e.operation(function(){for(var l=i(e,t);l.findNext();)if("string"!=typeof t){var o=e.getRange(l.from(),l.to()).match(t);l.replace(n.replace(/\$(\d)/g,function(e,t){return o[t]}))}else l.replace(n)})}function f(e,t){if(!e.getOption("readOnly")){var l=e.getSelection()||n(e).lastQuery,a=''+e.phrase(t?"Replace all:":"Replace:")+"";o(e,a+function(e){return' '+e.phrase("(Use /re/ syntax for regexp search)")+""}(e),a,l,function(n){n&&(n=u(n),o(e,function(e){return''+e.phrase("With:")+' '}(e),e.phrase("Replace with:"),"",function(l){if(l=r(l),t)h(e,n,l);else{d(e);var o=i(e,n,e.getCursor("from")),u=function(){var t,r=o.from();!(t=o.findNext())&&(o=i(e,n),!(t=o.findNext())||r&&o.from().line==r.line&&o.from().ch==r.ch)||(e.setSelection(o.from(),o.to()),e.scrollIntoView({from:o.from(),to:o.to()}),function(e,t,n,l){e.openConfirm?e.openConfirm(t,l):confirm(n)&&l[0]()}(e,function(e){return''+e.phrase("Replace?")+" "}(e),e.phrase("Replace?"),[function(){a(t)},u,function(){h(e,n,l)}]))},a=function(e){o.replace("string"==typeof n?l:l.replace(/\$(\d)/g,function(t,n){return e[n]})),u()};u()}}))})}}e.commands.find=function(e){d(e),s(e)},e.commands.findPersistent=function(e){d(e),s(e,!1,!0)},e.commands.findPersistentNext=function(e){s(e,!1,!0,!0)},e.commands.findPersistentPrev=function(e){s(e,!0,!0,!0)},e.commands.findNext=s,e.commands.findPrev=function(e){s(e,!0)},e.commands.clearSearch=d,e.commands.replace=f,e.commands.replaceAll=function(e){f(e,!0)}}(n("VrN/"),n("uTOq"),n("Ku0u"))},CC75:function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("7LN8"),a=n("ZYCi"),s=function(){function e(){}return e.prototype.ngAfterContentInit=function(){var e=this;this.templates.forEach(function(t){switch(t.getType()){case"item":default:e.itemTemplate=t.template}})},e.prototype.itemClick=function(e,t){t.disabled?e.preventDefault():(t.url||e.preventDefault(),t.command&&t.command({originalEvent:e,item:t}),this.activeItem=t)},l([o.Input(),i("design:type",Array)],e.prototype,"model",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"activeItem",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"popup",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.ContentChildren(u.PrimeTemplate),i("design:type",o.QueryList)],e.prototype,"templates",void 0),l([o.Component({selector:"p-tabMenu",template:'\n \n '})],e)}();t.TabMenu=s,t.TabMenuModule=function(){return l([o.NgModule({imports:[r.CommonModule,a.RouterModule],exports:[s,a.RouterModule],declarations:[s]})],function(){})}()},CS9Q:function(e,t,n){"use strict";n.d(t,"a",function(){return o});var l=n("mrSG"),i=n("siIJ"),o=function(e){function t(n,l){void 0===l&&(l=i.a.now);var o=e.call(this,n,function(){return t.delegate&&t.delegate!==o?t.delegate.now():l()})||this;return o.actions=[],o.active=!1,o.scheduled=void 0,o}return l.__extends(t,e),t.prototype.schedule=function(n,l,i){return void 0===l&&(l=0),t.delegate&&t.delegate!==this?t.delegate.schedule(n,l,i):e.prototype.schedule.call(this,n,l,i)},t.prototype.flush=function(e){var t=this.actions;if(this.active)t.push(e);else{var n;this.active=!0;do{if(n=e.execute(e.state,e.delay))break}while(e=t.shift());if(this.active=!1,n){for(;e=t.shift();)e.unsubscribe();throw n}}},t}(i.a)},CcnG:function(e,t,n){"use strict";n.r(t),n.d(t,"\u0275angular_packages_core_core_t",function(){return Xf}),n.d(t,"\u0275angular_packages_core_core_q",function(){return Yf}),n.d(t,"\u0275angular_packages_core_core_r",function(){return Zf}),n.d(t,"\u0275angular_packages_core_core_s",function(){return Qf}),n.d(t,"\u0275angular_packages_core_core_h",function(){return Zp}),n.d(t,"\u0275angular_packages_core_core_o",function(){return Df}),n.d(t,"\u0275angular_packages_core_core_p",function(){return Pf}),n.d(t,"\u0275angular_packages_core_core_n",function(){return mf}),n.d(t,"\u0275angular_packages_core_core_m",function(){return gf}),n.d(t,"\u0275angular_packages_core_core_c",function(){return hu}),n.d(t,"\u0275angular_packages_core_core_d",function(){return Rt}),n.d(t,"\u0275angular_packages_core_core_e",function(){return $p}),n.d(t,"\u0275angular_packages_core_core_f",function(){return Mp}),n.d(t,"\u0275angular_packages_core_core_g",function(){return Vp}),n.d(t,"\u0275angular_packages_core_core_l",function(){return od}),n.d(t,"\u0275angular_packages_core_core_u",function(){return Sh}),n.d(t,"\u0275angular_packages_core_core_w",function(){return Ch}),n.d(t,"\u0275angular_packages_core_core_v",function(){return bh}),n.d(t,"\u0275angular_packages_core_core_z",function(){return xh}),n.d(t,"\u0275angular_packages_core_core_x",function(){return wh}),n.d(t,"\u0275angular_packages_core_core_y",function(){return _h}),n.d(t,"\u0275angular_packages_core_core_bc",function(){return Tn}),n.d(t,"\u0275angular_packages_core_core_bj",function(){return h}),n.d(t,"\u0275angular_packages_core_core_bd",function(){return Qt}),n.d(t,"\u0275angular_packages_core_core_be",function(){return Jt}),n.d(t,"\u0275angular_packages_core_core_bf",function(){return vn}),n.d(t,"\u0275angular_packages_core_core_bi",function(){return ii}),n.d(t,"\u0275angular_packages_core_core_bm",function(){return Qe}),n.d(t,"\u0275angular_packages_core_core_i",function(){return da}),n.d(t,"\u0275angular_packages_core_core_j",function(){return pa}),n.d(t,"\u0275angular_packages_core_core_k",function(){return ha}),n.d(t,"\u0275angular_packages_core_core_a",function(){return D}),n.d(t,"\u0275angular_packages_core_core_b",function(){return k}),n.d(t,"\u0275angular_packages_core_core_bk",function(){return s}),n.d(t,"\u0275angular_packages_core_core_ba",function(){return qm}),n.d(t,"\u0275angular_packages_core_core_bb",function(){return sg}),n.d(t,"createPlatform",function(){return Gh}),n.d(t,"assertPlatform",function(){return qh}),n.d(t,"destroyPlatform",function(){return Kh}),n.d(t,"getPlatform",function(){return Yh}),n.d(t,"PlatformRef",function(){return Zh}),n.d(t,"ApplicationRef",function(){return Xh}),n.d(t,"createPlatformFactory",function(){return Wh}),n.d(t,"NgProbeToken",function(){return $h}),n.d(t,"enableProdMode",function(){return Ha}),n.d(t,"isDevMode",function(){return ja}),n.d(t,"APP_ID",function(){return Yp}),n.d(t,"PACKAGE_ROOT_URL",function(){return nh}),n.d(t,"PLATFORM_INITIALIZER",function(){return Jp}),n.d(t,"PLATFORM_ID",function(){return eh}),n.d(t,"APP_BOOTSTRAP_LISTENER",function(){return th}),n.d(t,"APP_INITIALIZER",function(){return qp}),n.d(t,"ApplicationInitStatus",function(){return Kp}),n.d(t,"DebugElement",function(){return Tf}),n.d(t,"DebugNode",function(){return Of}),n.d(t,"asNativeElements",function(){return vf}),n.d(t,"getDebugNode",function(){return Ef}),n.d(t,"Testability",function(){return Fh}),n.d(t,"TestabilityRegistry",function(){return Vh}),n.d(t,"setTestabilityGetter",function(){return jh}),n.d(t,"TRANSLATIONS",function(){return Wf}),n.d(t,"TRANSLATIONS_FORMAT",function(){return qf}),n.d(t,"LOCALE_ID",function(){return Gf}),n.d(t,"MissingTranslationStrategy",function(){return Kf}),n.d(t,"ApplicationModule",function(){return Jf}),n.d(t,"wtfCreateScope",function(){return Ih}),n.d(t,"wtfLeave",function(){return Oh}),n.d(t,"wtfStartTimeRange",function(){return Th}),n.d(t,"wtfEndTimeRange",function(){return Dh}),n.d(t,"Type",function(){return sd}),n.d(t,"EventEmitter",function(){return gc}),n.d(t,"ErrorHandler",function(){return Sp}),n.d(t,"Sanitizer",function(){return Oa}),n.d(t,"SecurityContext",function(){return Ia}),n.d(t,"ANALYZE_FOR_ENTRY_COMPONENTS",function(){return R}),n.d(t,"Attribute",function(){return M}),n.d(t,"ContentChild",function(){return A}),n.d(t,"ContentChildren",function(){return L}),n.d(t,"Query",function(){return N}),n.d(t,"ViewChild",function(){return F}),n.d(t,"ViewChildren",function(){return P}),n.d(t,"Component",function(){return Ud}),n.d(t,"Directive",function(){return zd}),n.d(t,"HostBinding",function(){return Kd}),n.d(t,"HostListener",function(){return Yd}),n.d(t,"Input",function(){return Wd}),n.d(t,"Output",function(){return qd}),n.d(t,"Pipe",function(){return $d}),n.d(t,"CUSTOM_ELEMENTS_SCHEMA",function(){return op}),n.d(t,"NO_ERRORS_SCHEMA",function(){return rp}),n.d(t,"NgModule",function(){return up}),n.d(t,"ViewEncapsulation",function(){return le}),n.d(t,"Version",function(){return Ta}),n.d(t,"VERSION",function(){return Da}),n.d(t,"defineInjectable",function(){return b}),n.d(t,"defineInjector",function(){return C}),n.d(t,"forwardRef",function(){return Q}),n.d(t,"resolveForwardRef",function(){return X}),n.d(t,"Injectable",function(){return gp}),n.d(t,"INJECTOR",function(){return pu}),n.d(t,"Injector",function(){return fu}),n.d(t,"inject",function(){return Mt}),n.d(t,"\u0275inject",function(){return Mt}),n.d(t,"InjectFlags",function(){return Ot}),n.d(t,"ReflectiveInjector",function(){return Up}),n.d(t,"createInjector",function(){return Au}),n.d(t,"ResolvedReflectiveFactory",function(){return Ap}),n.d(t,"ReflectiveKey",function(){return Dp}),n.d(t,"InjectionToken",function(){return x}),n.d(t,"Inject",function(){return _t}),n.d(t,"Optional",function(){return xt}),n.d(t,"Self",function(){return St}),n.d(t,"SkipSelf",function(){return Et}),n.d(t,"Host",function(){return It}),n.d(t,"NgZone",function(){return kh}),n.d(t,"\u0275NoopNgZone",function(){return Ph}),n.d(t,"RenderComponentType",function(){return va}),n.d(t,"Renderer",function(){return ba}),n.d(t,"Renderer2",function(){return xa}),n.d(t,"RendererFactory2",function(){return wa}),n.d(t,"RendererStyleFlags2",function(){return _a}),n.d(t,"RootRenderer",function(){return Ca}),n.d(t,"COMPILER_OPTIONS",function(){return vh}),n.d(t,"Compiler",function(){return mh}),n.d(t,"CompilerFactory",function(){return yh}),n.d(t,"ModuleWithComponentFactories",function(){return ih}),n.d(t,"ComponentFactory",function(){return Qu}),n.d(t,"\u0275ComponentFactory",function(){return Qu}),n.d(t,"ComponentRef",function(){return Zu}),n.d(t,"ComponentFactoryResolver",function(){return ia}),n.d(t,"ElementRef",function(){return fa}),n.d(t,"NgModuleFactory",function(){return aa}),n.d(t,"NgModuleRef",function(){return ua}),n.d(t,"NgModuleFactoryLoader",function(){return td}),n.d(t,"getModuleFactory",function(){return ud}),n.d(t,"QueryList",function(){return ef}),n.d(t,"SystemJsNgModuleLoader",function(){return lf}),n.d(t,"SystemJsNgModuleLoaderConfig",function(){return tf}),n.d(t,"TemplateRef",function(){return mc}),n.d(t,"ViewContainerRef",function(){return rf}),n.d(t,"EmbeddedViewRef",function(){return hf}),n.d(t,"ViewRef",function(){return pf}),n.d(t,"ChangeDetectionStrategy",function(){return V}),n.d(t,"ChangeDetectorRef",function(){return sf}),n.d(t,"DefaultIterableDiffer",function(){return Rf}),n.d(t,"IterableDiffers",function(){return jf}),n.d(t,"KeyValueDiffers",function(){return Hf}),n.d(t,"SimpleChange",function(){return cl}),n.d(t,"WrappedValue",function(){return sl}),n.d(t,"platformCore",function(){return $f}),n.d(t,"\u0275ALLOW_MULTIPLE_PLATFORMS",function(){return Uh}),n.d(t,"\u0275APP_ID_RANDOM_PROVIDER",function(){return Qp}),n.d(t,"\u0275defaultIterableDiffers",function(){return zf}),n.d(t,"\u0275defaultKeyValueDiffers",function(){return Uf}),n.d(t,"\u0275devModeEqual",function(){return al}),n.d(t,"\u0275isListLikeIterable",function(){return dl}),n.d(t,"\u0275ChangeDetectorStatus",function(){return j}),n.d(t,"\u0275isDefaultChangeDetectionStrategy",function(){return H}),n.d(t,"\u0275Console",function(){return lh}),n.d(t,"\u0275getInjectableDef",function(){return w}),n.d(t,"\u0275setCurrentInjector",function(){return Dt}),n.d(t,"\u0275APP_ROOT",function(){return Du}),n.d(t,"\u0275ivyEnabled",function(){return tg}),n.d(t,"\u0275CodegenComponentFactoryResolver",function(){return oa}),n.d(t,"\u0275resolveComponentResources",function(){return J}),n.d(t,"\u0275ReflectionCapabilities",function(){return fd}),n.d(t,"\u0275RenderDebugInfo",function(){return ya}),n.d(t,"\u0275_sanitizeHtml",function(){return as}),n.d(t,"\u0275_sanitizeStyle",function(){return Wc}),n.d(t,"\u0275_sanitizeUrl",function(){return $a}),n.d(t,"\u0275global",function(){return U}),n.d(t,"\u0275looseIdentical",function(){return K}),n.d(t,"\u0275stringify",function(){return Y}),n.d(t,"\u0275makeDecorator",function(){return O}),n.d(t,"\u0275isObservable",function(){return Wp}),n.d(t,"\u0275isPromise",function(){return Gp}),n.d(t,"\u0275clearOverrides",function(){return Dy}),n.d(t,"\u0275initServicesIfNeeded",function(){return $v}),n.d(t,"\u0275overrideComponentView",function(){return Ty}),n.d(t,"\u0275overrideProvider",function(){return Oy}),n.d(t,"\u0275NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR",function(){return ev}),n.d(t,"\u0275defineBase",function(){return pe}),n.d(t,"\u0275defineComponent",function(){return ue}),n.d(t,"\u0275defineDirective",function(){return he}),n.d(t,"\u0275definePipe",function(){return fe}),n.d(t,"\u0275defineNgModule",function(){return ce}),n.d(t,"\u0275detectChanges",function(){return mr}),n.d(t,"\u0275renderComponent",function(){return Zr}),n.d(t,"\u0275Render3ComponentFactory",function(){return Aa}),n.d(t,"\u0275Render3ComponentRef",function(){return Pa}),n.d(t,"\u0275directiveInject",function(){return Pr}),n.d(t,"\u0275injectAttribute",function(){return Fr}),n.d(t,"\u0275getFactoryOf",function(){return Fn}),n.d(t,"\u0275getInheritedFactory",function(){return Vn}),n.d(t,"\u0275templateRefExtractor",function(){return Ac}),n.d(t,"\u0275ProvidersFeature",function(){return Yu}),n.d(t,"\u0275InheritDefinitionFeature",function(){return lu}),n.d(t,"\u0275NgOnChangesFeature",function(){return ru}),n.d(t,"\u0275LifecycleHooksFeature",function(){return eu}),n.d(t,"\u0275Render3NgModuleRef",function(){return Ks}),n.d(t,"\u0275markDirty",function(){return Cr}),n.d(t,"\u0275NgModuleFactory",function(){return Ys}),n.d(t,"\u0275NO_CHANGE",function(){return fl}),n.d(t,"\u0275container",function(){return lr}),n.d(t,"\u0275nextContext",function(){return po}),n.d(t,"\u0275elementStart",function(){return _o}),n.d(t,"\u0275namespaceHTML",function(){return yo}),n.d(t,"\u0275namespaceMathML",function(){return vo}),n.d(t,"\u0275namespaceSVG",function(){return mo}),n.d(t,"\u0275element",function(){return bo}),n.d(t,"\u0275listener",function(){return Do}),n.d(t,"\u0275text",function(){return Go}),n.d(t,"\u0275embeddedViewStart",function(){return ur}),n.d(t,"\u0275query",function(){return Nc}),n.d(t,"\u0275registerContentQuery",function(){return Vr}),n.d(t,"\u0275projection",function(){return dr}),n.d(t,"\u0275bind",function(){return wr}),n.d(t,"\u0275interpolation1",function(){return Sr}),n.d(t,"\u0275interpolation2",function(){return Er}),n.d(t,"\u0275interpolation3",function(){return Ir}),n.d(t,"\u0275interpolation4",function(){return Or}),n.d(t,"\u0275interpolation5",function(){return Tr}),n.d(t,"\u0275interpolation6",function(){return Dr}),n.d(t,"\u0275interpolation7",function(){return kr}),n.d(t,"\u0275interpolation8",function(){return Rr}),n.d(t,"\u0275interpolationV",function(){return xr}),n.d(t,"\u0275pipeBind1",function(){return ac}),n.d(t,"\u0275pipeBind2",function(){return sc}),n.d(t,"\u0275pipeBind3",function(){return cc}),n.d(t,"\u0275pipeBind4",function(){return dc}),n.d(t,"\u0275pipeBindV",function(){return pc}),n.d(t,"\u0275pureFunction0",function(){return Qs}),n.d(t,"\u0275pureFunction1",function(){return Xs}),n.d(t,"\u0275pureFunction2",function(){return Js}),n.d(t,"\u0275pureFunction3",function(){return ec}),n.d(t,"\u0275pureFunction4",function(){return tc}),n.d(t,"\u0275pureFunction5",function(){return nc}),n.d(t,"\u0275pureFunction6",function(){return lc}),n.d(t,"\u0275pureFunction7",function(){return ic}),n.d(t,"\u0275pureFunction8",function(){return oc}),n.d(t,"\u0275pureFunctionV",function(){return rc}),n.d(t,"\u0275getCurrentView",function(){return Br}),n.d(t,"\u0275getHostElement",function(){return tl}),n.d(t,"\u0275restoreView",function(){return Xt}),n.d(t,"\u0275containerRefreshStart",function(){return or}),n.d(t,"\u0275containerRefreshEnd",function(){return rr}),n.d(t,"\u0275queryRefresh",function(){return Lc}),n.d(t,"\u0275loadQueryList",function(){return Lr}),n.d(t,"\u0275elementEnd",function(){return ko}),n.d(t,"\u0275elementProperty",function(){return Mo}),n.d(t,"\u0275componentHostSyntheticProperty",function(){return No}),n.d(t,"\u0275projectionDef",function(){return sr}),n.d(t,"\u0275reference",function(){return Nr}),n.d(t,"\u0275enableBindings",function(){return Yt}),n.d(t,"\u0275disableBindings",function(){return Zt}),n.d(t,"\u0275allocHostVars",function(){return _r}),n.d(t,"\u0275elementAttribute",function(){return Ro}),n.d(t,"\u0275elementContainerStart",function(){return Co}),n.d(t,"\u0275elementContainerEnd",function(){return wo}),n.d(t,"\u0275elementStyling",function(){return jo}),n.d(t,"\u0275elementHostAttrs",function(){return Ho}),n.d(t,"\u0275elementStylingMap",function(){return $o}),n.d(t,"\u0275elementStyleProp",function(){return zo}),n.d(t,"\u0275elementStylingApply",function(){return Bo}),n.d(t,"\u0275elementClassProp",function(){return Uo}),n.d(t,"\u0275textBinding",function(){return Wo}),n.d(t,"\u0275template",function(){return nr}),n.d(t,"\u0275embeddedViewEnd",function(){return ar}),n.d(t,"\u0275store",function(){return Mr}),n.d(t,"\u0275load",function(){return Ar}),n.d(t,"\u0275pipe",function(){return uc}),n.d(t,"\u0275whenRendered",function(){return tu}),n.d(t,"\u0275i18n",function(){return Ps}),n.d(t,"\u0275i18nAttributes",function(){return Fs}),n.d(t,"\u0275i18nExp",function(){return Hs}),n.d(t,"\u0275i18nStart",function(){return ks}),n.d(t,"\u0275i18nEnd",function(){return Ns}),n.d(t,"\u0275i18nApply",function(){return Bs}),n.d(t,"\u0275i18nPostprocess",function(){return Ms}),n.d(t,"\u0275setClassMetadata",function(){return Zs}),n.d(t,"\u0275compileComponent",function(){return Ld}),n.d(t,"\u0275compileDirective",function(){return Ad}),n.d(t,"\u0275compileNgModule",function(){return Ed}),n.d(t,"\u0275compileNgModuleDefs",function(){return Id}),n.d(t,"\u0275patchComponentDefWithScope",function(){return Dd}),n.d(t,"\u0275resetCompiledComponents",function(){return Od}),n.d(t,"\u0275compilePipe",function(){return Bd}),n.d(t,"\u0275sanitizeHtml",function(){return qc}),n.d(t,"\u0275sanitizeStyle",function(){return Kc}),n.d(t,"\u0275defaultStyleSanitizer",function(){return Xc}),n.d(t,"\u0275sanitizeScript",function(){return Qc}),n.d(t,"\u0275sanitizeUrl",function(){return Yc}),n.d(t,"\u0275sanitizeResourceUrl",function(){return Zc}),n.d(t,"\u0275bypassSanitizationTrustHtml",function(){return Vc}),n.d(t,"\u0275bypassSanitizationTrustStyle",function(){return jc}),n.d(t,"\u0275bypassSanitizationTrustScript",function(){return Hc}),n.d(t,"\u0275bypassSanitizationTrustUrl",function(){return Bc}),n.d(t,"\u0275bypassSanitizationTrustResourceUrl",function(){return zc}),n.d(t,"\u0275getLContext",function(){return jn}),n.d(t,"\u0275bindPlayerFactory",function(){return li}),n.d(t,"\u0275addPlayer",function(){return $r}),n.d(t,"\u0275getPlayers",function(){return Gr}),n.d(t,"\u0275compileNgModuleFactory__POST_R3__",function(){return zh}),n.d(t,"\u0275SWITCH_COMPILE_COMPONENT__POST_R3__",function(){return Zd}),n.d(t,"\u0275SWITCH_COMPILE_DIRECTIVE__POST_R3__",function(){return Qd}),n.d(t,"\u0275SWITCH_COMPILE_PIPE__POST_R3__",function(){return Xd}),n.d(t,"\u0275SWITCH_COMPILE_NGMODULE__POST_R3__",function(){return ap}),n.d(t,"\u0275getDebugNode__POST_R3__",function(){return Sf}),n.d(t,"\u0275SWITCH_COMPILE_INJECTABLE__POST_R3__",function(){return mp}),n.d(t,"\u0275SWITCH_IVY_ENABLED__POST_R3__",function(){return eg}),n.d(t,"\u0275SWITCH_CHANGE_DETECTOR_REF_FACTORY__POST_R3__",function(){return cf}),n.d(t,"\u0275Compiler_compileModuleSync__POST_R3__",function(){return ah}),n.d(t,"\u0275Compiler_compileModuleAsync__POST_R3__",function(){return ch}),n.d(t,"\u0275Compiler_compileModuleAndAllComponentsSync__POST_R3__",function(){return ph}),n.d(t,"\u0275Compiler_compileModuleAndAllComponentsAsync__POST_R3__",function(){return fh}),n.d(t,"\u0275SWITCH_ELEMENT_REF_FACTORY__POST_R3__",function(){return ga}),n.d(t,"\u0275SWITCH_TEMPLATE_REF_FACTORY__POST_R3__",function(){return vc}),n.d(t,"\u0275SWITCH_VIEW_CONTAINER_REF_FACTORY__POST_R3__",function(){return uf}),n.d(t,"\u0275SWITCH_RENDERER2_FACTORY__POST_R3__",function(){return Sa}),n.d(t,"\u0275getModuleFactory__POST_R3__",function(){return rd}),n.d(t,"\u0275publishGlobalUtil",function(){return Yr}),n.d(t,"\u0275publishDefaultGlobalUtils",function(){return Kr}),n.d(t,"\u0275SWITCH_INJECTOR_FACTORY__POST_R3__",function(){return gu}),n.d(t,"\u0275registerModuleFactory",function(){return ld}),n.d(t,"\u0275EMPTY_ARRAY",function(){return Qg}),n.d(t,"\u0275EMPTY_MAP",function(){return Xg}),n.d(t,"\u0275and",function(){return Jg}),n.d(t,"\u0275ccf",function(){return bm}),n.d(t,"\u0275cmf",function(){return ky}),n.d(t,"\u0275crt",function(){return Cg}),n.d(t,"\u0275did",function(){return $m}),n.d(t,"\u0275eld",function(){return em}),n.d(t,"\u0275elementEventFullName",function(){return Rg}),n.d(t,"\u0275getComponentViewDefinitionFactory",function(){return Cm}),n.d(t,"\u0275inlineInterpolate",function(){return Yg}),n.d(t,"\u0275interpolate",function(){return Kg}),n.d(t,"\u0275mod",function(){return cm}),n.d(t,"\u0275mpd",function(){return sm}),n.d(t,"\u0275ncd",function(){return pv}),n.d(t,"\u0275nov",function(){return Rm}),n.d(t,"\u0275pid",function(){return Gm}),n.d(t,"\u0275prd",function(){return Wm}),n.d(t,"\u0275pad",function(){return gv}),n.d(t,"\u0275pod",function(){return mv}),n.d(t,"\u0275ppd",function(){return fv}),n.d(t,"\u0275qud",function(){return uv}),n.d(t,"\u0275ted",function(){return yv}),n.d(t,"\u0275unv",function(){return vg}),n.d(t,"\u0275vid",function(){return wv});var l=n("mrSG"),i=n("pugT"),o=n("K9Ia"),r=n("6blF"),u=n("p0ib"),a=n("S1nX");function s(e){for(var t in e)if(e[t]===s)return t;throw Error("Could not find renamed property on target object.")}function c(e,t){for(var n in t)t.hasOwnProperty(n)&&!e.hasOwnProperty(n)&&(e[n]=t[n])}var d=s({ngComponentDef:s}),p=s({ngDirectiveDef:s}),h=s({ngInjectableDef:s}),f=s({ngInjectorDef:s}),g=s({ngPipeDef:s}),m=s({ngModuleDef:s}),v=s({ngBaseDef:s}),y=s({__NG_ELEMENT_ID__:s});function b(e){return{providedIn:e.providedIn||null,factory:e.factory,value:void 0}}function C(e){return{factory:e.factory,providers:e.providers||[],imports:e.imports||[]}}function w(e){return e&&e.hasOwnProperty(h)?e[h]:null}function _(e){return e&&e.hasOwnProperty(f)?e[f]:null}var x=function(){function e(e,t){this._desc=e,this.ngMetadataName="InjectionToken",this.ngInjectableDef=void 0!==t?b({providedIn:t.providedIn||"root",factory:t.factory}):void 0}return e.prototype.toString=function(){return"InjectionToken "+this._desc},e}(),S="__annotations__",E="__parameters__",I="__prop__metadata__";function O(e,t,n,i,o){var r=T(t);function u(){for(var e,t=[],n=0;n=Fe?n:n[Se]}function lt(e){return 0!=(4&e.flags)}function it(e){return 1==(1&e.flags)}function ot(e){return null!==e.template}function rt(e){return Array.isArray(e)&&e.length===ze}function ut(e){return 0!=(128&e[Ce])}function at(e){for(var t=Array.isArray(e)?e:dt(e);t&&!(128&t[Ce]);)t=t[we];return t}function st(e){return at(e)[Te]}function ct(e){return e[Ue]}function dt(e){var t=ct(e);return t?Array.isArray(t)?t:t.lView:null}function pt(e){return e!==qe}function ht(e){return 32767&e}function ft(e){return e>>16}function gt(e,t){for(var n=ft(e),l=t;n>0;)l=l[Pe],n--;return l}var mt=("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(U);function vt(e,t){for(var n=0;n0;)t=t[Pe],e--;return t}(e,rn))[Te]}function yn(e){var t=Wt[be];on(Wt)?Wt[Ce]&=-2:(Bt(Wt,t.viewHooks,t.viewCheckHooks,un),Wt[Ce]&=-11,Wt[Ce]|=32,Wt[Ie]=t.bindingStartIndex),mn(e,null)}var bn=!0;function Cn(e){var t=bn;return bn=e,t}var wn=255,_n=0;function xn(e,t){var n=En(e,t);if(-1!==n)return n;var l=t[be];l.firstTemplatePass&&(e.injectorIndex=t.length,Sn(l.data,e),Sn(t,null),Sn(l.blueprint,null));var i=In(e,t),o=ht(i),r=gt(i,t),u=e.injectorIndex;if(pt(i))for(var a=r[be].data,s=0;s<8;s++)t[u+s]=r[o+s]|a[o+s];return t[u+Ge]=i,u}function Sn(e,t){e.push(0,0,0,0,0,0,0,0,t)}function En(e,t){return-1===e.injectorIndex||e.parent&&e.parent.injectorIndex===e.injectorIndex||null==t[e.injectorIndex+Ge]?-1:e.injectorIndex}function In(e,t){if(e.parent&&-1!==e.parent.injectorIndex)return e.parent.injectorIndex;for(var n=t[Ee],l=1;n&&-1===n.injectorIndex;)n=(t=t[Pe])?t[Ee]:null,l++;return n?n.injectorIndex|l<<16:-1}function On(e,t,n){!function(e,t,n){var l="string"!=typeof n?n[y]:n.charCodeAt(0)||0;null==l&&(l=n[y]=_n++);var i=l&wn,o=1<>16,c=i?u+s:e.directiveEnd,d=l?u:u+s;d=a&&p.type===n)return d}if(i){var h=r[a];if(h&&ot(h)&&h.type===n)return a}return null}function Nn(e,t,n,l){var i,o=t[n];if(null!=(i=o)&&"object"==typeof i&&Object.getPrototypeOf(i)==Ye){var r=o;if(r.resolving)throw new Error("Circular dep for "+Ze(e[n]));var u=Cn(r.canSeeViewProviders);r.resolving=!0;var a=void 0;r.injectImpl&&(a=kt(r.injectImpl));var s=Jt(),c=Qt();tn(l,t);try{o=t[n]=r.factory(null,e,t,l)}finally{r.injectImpl&&kt(a),Cn(u),r.resolving=!1,tn(s,c)}}return o}function Ln(e,t,n){var l=64&e,i=32&e;return!!((128&e?l?i?n[t+7]:n[t+6]:i?n[t+5]:n[t+4]:l?i?n[t+3]:n[t+2]:i?n[t+1]:n[t])&1<=0){var f;zn(f=Xe(l[h]),a=Hn(l,h,f)),n=a;break}}}return n||null}function Hn(e,t,n){return{lView:e,nodeIndex:t,native:n,component:void 0,directives:void 0,localRefs:void 0}}function Bn(e){var t,n=ct(e);if(Array.isArray(n)){var l=Gn(n,e);(i=Hn(n,l,(t=nt(l,n))[Se])).component=e,zn(e,i),zn(i.native,i)}else{var i;t=nt((i=n).nodeIndex,i.lView)}return t}function zn(e,t){e[Ue]=t}function Un(e,t){for(var n=e[be].firstChild;n;){if(et(n,e)===t)return n.index;n=$n(n)}return-1}function $n(e){if(e.child)return e.child;if(e.next)return e.next;for(;e.parent&&!e.parent.next;)e=e.parent;return e.parent&&e.parent.next}function Gn(e,t){var n=e[be].components;if(n)for(var l=0;l=0)&&null})}}return i.sort(ol),i}function ol(e,t){return e.name==t.name?0:e.name-1?t[we][n]:null}return t[we][e.parent.index]}function Tl(e,t){var n=Ol(e,t);return n?n[Be]:null}var Dl=[];function kl(e,t,n,l,i){for(var o=e[be].node,r=-1,u=e,a=o.child;a;){var s=null;if(3===a.type){Rl(t,n,l,et(a,u),i);var c=u[a.index];rt(c)&&Rl(t,n,l,c[He],i)}else if(0===a.type){var d=u[a.index];Rl(t,n,l,d[He],i),l&&(d[Be]=l),d[je].length&&(s=(u=d[je][0])[be].node,i=d[He])}else if(1===a.type){var p=yt(u),h=p[Ee].projection[a.projection];Dl[++r]=a,Dl[++r]=u,h&&(s=(u=p[we])[be].data[h.index])}else s=a.child;if(null===s)for(null===a.next&&2&a.flags&&(u=Dl[r--],a=Dl[r--]),s=a.next;!s;){if(null===(a=a.parent||u[be].node)||a===o)return null;0===a.type&&(i=(u=u[we])[a.index][He]),s=2===a.type&&u[_e]?(u=u[_e])[be].node:a.next}a=s}}function Rl(e,t,n,l,i){0===e?xl(t)?t.insertBefore(n,l,i):n.insertBefore(l,i,!0):1===e?xl(t)?t.removeChild(n,l):n.removeChild(l):2===e&&t.destroyNode(l)}function Ml(e,t){return xl(t)?t.createText(Ze(e)):t.createTextNode(Ze(e))}function Nl(e,t,n){var l=Tl(e[be].node,e);l&&kl(e,t?0:1,e[Re],l,n)}function Ll(e,t,n,l,i){var o=t[je];l>0&&(o[l-1][_e]=e),l-1&&(e[Le]=i,e[we]=n),e[xe]&&e[xe].insertView(l),e[Ce]|=16}function Al(e,t,n){var l=e[je],i=l[t];return t>0&&(l[t-1][_e]=i[_e]),l.splice(t,1),n||Nl(i,!1),i[xe]&&i[xe].removeView(),i[Le]=-1,i[we]=null,i[Ce]&=-17,i}function Pl(e,t,n){var l=e[je][n];Al(e,n,!!t.detached),Vl(l)}function Fl(e){var t=e[be].childIndex;return-1===t?null:e[t]}function Vl(e){var t=e[Re];xl(t)&&t.destroyNode&&kl(e,2,t,null),function(e){if(-1===e[be].childIndex)return Hl(e);for(var t=Fl(e);t;){var n=null;if(t.length>=Fe?t[be].childIndex>-1&&(n=Fl(t)):t[je].length&&(n=t[je][0]),null==n){for(;t&&!t[_e]&&t!==e;)Hl(t),t=jl(t,e);Hl(t||e),n=t&&t[_e]}t=n}}(e),e[Ce]|=64}function jl(e,t){var n;return e.length>=Fe&&(n=e[Ee])&&2===n.type?Ol(n,e):e[we]===t?null:e[we]}function Hl(e){if(e.length>=Fe){var t=e;!function(e){var t,n=e[be];null!=n&&null!=(t=n.destroyHooks)&&zt(e,t)}(t),(i=(l=t)[be]&&l[be].pipeDestroyHooks)&&zt(l,i),function(e){var t=e[be].cleanup;if(null!=t){for(var n=e[Oe],l=0;l=0?n[r]():n[-r].unsubscribe(),l+=2}else"number"==typeof t[l]?(0,n[t[l]])():t[l].call(n[t[l+1]]);e[Oe]=null}}(t);var n=t[Ee];n&&3===n.type&&xl(t[Re])&&t[Re].destroy()}var l,i}function Bl(e,t){if(zl(e,t)){if(ut(t))return $l(t[Re],et(e,t));var n=t[Ee],l=e.parent;return null!=l&&4===l.type&&(e=Wl(l)),null==e.parent&&2===n.type?Tl(n,t):El(e,t)}return null}function zl(e,t){var n,l=e,i=e.parent;return e.parent&&(4===e.parent.type?i=(l=Wl(e)).parent:5===e.parent.type&&(i=(l=Il(l)).parent)),null===i&&(i=t[Ee]),i&&2===i.type?null!=(n=Ol(i,t))&&null!=n[Be]:null==l.parent||!(3!==l.parent.type||1&l.parent.flags)}function Ul(e,t,n,l){xl(e)?e.insertBefore(t,n,l):t.insertBefore(n,l,!0)}function $l(e,t){return xl(e)?e.parentNode(t):t.parentNode}function Gl(e,t,n){if(void 0===e&&(e=null),null!==e&&zl(t,n)){var l=n[Re],i=El(t,n),o=t.parent||n[Ee];if(2===o.type){var r=Ol(o,n),u=r[je],a=u.indexOf(n);Ul(l,r[Be],e,ql(a,u,r[He]))}else 4===o.type?Ul(l,Bl(t,n),e,i):5===o.type?Ul(l,i,e,et(t.parent,n)):xl(l)?l.appendChild(i,e):i.appendChild(e);return!0}return!1}function Wl(e){for(;null!=e.parent&&4===e.parent.type;)e=e.parent;return e}function ql(e,t,n){if(e+1-1&&f>c?"":s[f+1],8&a&&(o=(l=g).length,u=(r=l.indexOf(i=m))+i.length,-1===r||r>0&&" "!==l[r-1]||u=9?t+0:t;n?e[l]|=1:e[l]&=-2}function _i(e,t){return 1==(1&e[t>=9?t+0:t])}function xi(e,t){return 2==(2&e[t>=9?t+0:t])}function Si(e,t){return 4==(4&e[t>=9?t+0:t])}function Ei(e,t,n){return 63&e|t<<6|n<<20}function Ii(e,t){var n=Oi(t);return(2&t?e[3]:e[2])[n]}function Oi(e){return e>>6&16383}function Ti(e){var t=e>>20&16383;return t>=9?t:-1}function Di(e){return Ti(e[0])}function ki(e,t,n){e[t+1]=n}function Ri(e,t,n){e[t+2]=n}function Mi(e,t,n){var l=e[8];if(t){if(!l||0===n)return!0}else if(!l)return!1;return l[n]!==t}function Ni(e,t,n){var l=e[8]||pi(e);return n>0?l[n]=t:(l.splice(n=l[0],0,t,null),l[0]+=2),n}function Li(e,t,n,l){var i=function(e,t){return n<<16|e}(l);e[t+3]=i}function Ai(e,t){return e[t+3]>>16&65535}function Pi(e,t){var n=Ai(e,t);if(n){var l=e[8];if(l)return l[n]}return null}function Fi(e,t,n){e[0===t?t:t+0]=n}function Vi(e,t){return e[0===t?t:t+0]}function ji(e,t){return e[t+2]}function Hi(e,t){return e[t+1]}function Bi(e){return 16&e[0]}function zi(e,t){wi(e,0,t)}function Ui(e,t){t?e[0]|=8:e[0]&=-9}function $i(e,t,n){for(var l=(n||0)+1;l=0&&Fi(e,s,Ei(c=Vi(e,s),Oi(c),n));var c,d=Ti(a);d>=0&&Fi(e,d,Ei(c=Vi(e,d),Oi(c),t)),Ri(e,t,ji(e,n)),ki(e,t,Hi(e,n)),Fi(e,t,Vi(e,n)),Li(e,t,Ai(e,n),0),Ri(e,n,l),ki(e,n,i),Fi(e,n,o),Li(e,n,r,0)}function Wi(e,t,n,l,i,o,r,u){var a=t0){var o=Oi(Vi(e,i));Fi(e,i,Ei((_i(e,i)?1:0)|(xi(e,i)?2:0)|(Si(e,i)?4:0),o,l))}}}(e)}function qi(e,t){return t?!!e:null!==e}function Ki(e,t,n,l){var i,o=l&&l(t)?4:0;return n?(o|=2,i=eo(e[3],t)):i=eo(e[2],t),Ei(o,i=i>0?i+1:0,0)}function Yi(e,t,n){return!(2&e)&&t&&n&&4&e?t.toString()!==n.toString():t!==n}var Zi=function(){function e(e,t,n){this._element=t,this._type=n,this._values={},this._dirty=!1,this._factory=e}return e.prototype.setValue=function(e,t){this._values[e]!==t&&(this._values[e]=t,this._dirty=!0)},e.prototype.buildPlayer=function(e,t){if(this._dirty){var n=this._factory.fn(this._element,this._type,this._values,t,e||null);return this._values={},this._dirty=!1,n}},e}();function Qi(e,t){return 65535&e[t+3]}function Xi(e,t){var n=Ji(e[1],t);return n>0?n/4:0}function Ji(e,t){for(var n=0;ni)for(var r=0===t,u=n.data,a=i;a=0)return-1;l=r/4,i[r+1]=o,i[r+3]=n||null}return l}(e,t,i);if(-1!==r){var u=e[4],a=u[1],s=u[0],c=4*s,d=9+c,p=d+4*a,h=p+c,f=u.length;u.push(l?l.length:0,n?n.length:0);var g=0,m=[];if(l&&l.length)for(var v=0;v=p,R=D>=(k?h:d),M=Vi(e,D),N=Oi(M),L=Ti(M);Fi(e,D,Ei(M,N,L+=k?R?4*m.length:0:4*T+4*(R?m.length:0)))}for(var A=0;A<4*m.length;A++)e.splice(h,0,null),e.splice(d,0,null),d++,p++,h+=2;for(var P=0;P<4*b.length;P++)e.splice(p,0,null),e.push(null),p++,h++;for(var F=e[3],V=e[2],j=0;j=m.length,B=H?j-m.length:j,z=H?b[B]:m[B],U=void 0,$=void 0;H?(U=h+4*(a+B),$=d+4*(a+B)):(U=p+4*(s+B),$=9+4*(s+B));var G=H?F:V,W=eo(G,z);-1===W?(W=G.length+1,G.push(z,!H&&null)):W+=1;var q=Ki(e,z,H,i||null);Fi(e,$,Ei(q,W,U)),ki(e,$,z),Ri(e,$,null),Li(e,$,0,r),Fi(e,U,Ei(q,W,$)),ki(e,U,z),Ri(e,U,null),Li(e,U,0,r)}u[1]=a+b.length,u[0]=s+m.length,Fi(e,0,Ei(0,0,p)|(o?16:0))}}}(i.stylingTemplate,l||null,e,t,n,hi(i))}function Ho(e,t){var n=Jt();n.stylingTemplate||(n.stylingTemplate=fi(t)),function(e,t,n){var l=e[1];if(-1==Ji(l,n)){l.push(n,-1,!1,null);for(var i=null,o=null,r=-1,u=0;u0&&fr(st(n),2)}function zo(e,t,n,l,i){var o=null;null!==n&&(o=l?Ze(n)+l:n),yi(ai(e+Fe,Qt()),t,o,!1,i)}function Uo(e,t,n,l){var i=n instanceof ii?n:!!n;yi(ai(e+Fe,Qt()),t,i,!0,l)}function $o(e,t,n,l){if(null!=l)return function(e,t,n,l){throw new Error("unimplemented. Should not be needed by ViewEngine compatibility")}();var i=Qt(),o=tt(e,i),r=ai(e+Fe,i);if(hi(o)&&t!==fl){var u=lo(r);Fo(i,o.inputs.class,(u.length?u+" ":"")+t)}else!function(e,t,n,l){n=n||null;var i=Xi(e,null),o=e[5],r=t instanceof ii?new Zi(t,o,1):null,u=n instanceof ii?new Zi(n,o,2):null,a=r?t.value:t,s=u?n.value:n,c=Bi(e)||a===fl||a===e[6],d=s===fl||s===e[7];if(!c||!d){e[6]=a,e[7]=s;var p=oe,h=!1,f=!1,g=r?1:0;Mi(e,r,1)&&(Ni(e,r,1),f=!0);var m=u?3:0;Mi(e,u,3)&&(Ni(e,u,3),f=!0),c||("string"==typeof a?(p=a.split(/\s+/),h=!0):p=a?Object.keys(a):oe);for(var v=a||ie,y=s?Object.keys(s):oe,b=s||ie,C=y.length,w=!1,_=Di(e),x=0,S=y.length+p.length;_=C)&&!d||P&&!c){var E=P?x-C:x,I=P?p[E]:y[E],O=P?!!h||v[I]:b[I],T=P?g:m;if((F=Hi(e,_))===I){var D=ji(e,_),k=Vi(e,_);if(Li(e,_,T,i),Yi(k,D,O)){Ri(e,_,O),f=f||!!T;var R=Ii(e,k);(null!=D||Yi(k,R,O))&&(wi(e,_,!0),w=!0)}}else{var M=$i(e,I,_);if(M>0){var N=ji(e,M),L=Vi(e,M);Gi(e,_,M),Yi(L,N,O)&&(R=Ii(e,L),Ri(e,_,O),(null!=N||Yi(L,R,O))&&(wi(e,_,!0),f=f||!!T,w=!0))}else f=f||!!T,Wi(e,_,P,I,Ki(e,I,P,to(e,i)),O,i,T),w=!0}}_+=4,x++}for(;_=C)&&!d||P&&!c){E=P?x-C:x;var F=P?p[E]:y[E],V=(D=P?!!h||v[F]:b[F],k=1|Ki(e,F,P,A),T=P?g:m,e.length);e.push(k,F,D,0),Li(e,V,T,i),w=!0}x++}w&&(zi(e,!0),no(e,i,!0)),f&&Ui(e,!0)}}(r,t,n)}function Go(e,t){var n=Qt(),l=Ml(t,n[Re]),i=uo(e,3,l,null,null);ln(!1),Gl(l,i,n)}function Wo(e,t){if(t!==fl){var n=Qt(),l=Je(e,n),i=n[Re];xl(i)?i.setValue(l,Ze(t)):l.textContent=Ze(t)}}function qo(e,t,n){var l=-(t.index-Fe),i=e.data.length-(65535&t.providerIndexes);(e.expandoInstructions||(e.expandoInstructions=[])).push(l,i,n)}function Ko(e,t,n,l){var i=Jt();Yo(e,i,t,n),i&&i.attrs&&function(e,t,l,o){var r=i.initialInputs;(void 0===r||e>=r.length)&&(r=function(e,t,n){var l=n.initialInputs||(n.initialInputs=[]);l[e]=null;for(var i=n.attrs,o=0;o=o.length||null==o[e])&&(o[e]=Io(e,null,t,n,i.directiveRegistry,i.pipeRegistry,null)),o[e]}(e,t,n,o),null,4),r[xe]&&(u[xe]=r[xe].createView()),ao(e,u),mn(u,u[be].node)),r&&(on(u)&&Ll(u,r,l,r[Ve],-1),r[Ve]++),on(u)?3:2}function ar(){var e=Qt(),t=e[Ee];on(e)&&(oo(e),e[Ce]&=-2),oo(e),yn(e[we]),en(t),ln(!1)}function sr(e,t){var n=yt(Qt())[Ee];if(!n.projection)for(var l=n.projection=new Array(e?e.length+1:1).fill(null),i=l.slice(),o=n.child;null!==o;){var r=e?ni(o,e,t):0,u=o.next;i[r]?i[r].next=o:(l[r]=o,o.next=null),i[r]=o,o=u}}var cr=[];function dr(e,t,n){void 0===t&&(t=0);var l=Qt(),i=uo(e,1,null,null,n||null);null===i.projection&&(i.projection=t),ln(!1);for(var o=yt(l),r=o[Ee].projection[t],u=o[we],a=-1;r;){if(1===r.type){var s=yt(u),c=s[Ee].projection[r.projection];if(c){cr[++a]=r,cr[++a]=u,r=c,u=s[we];continue}}else r.flags|=2,Yl(r,i,l,u);null===r.next&&u!==o[we]&&(u=cr[a--],r=cr[a--]),r=r.next}}function pr(e,t,n){var l=e[be],i=dn();return e[Ne]?e[Ne][_e]=n:i&&(l.childIndex=t),e[Ne]=n,n}function hr(e){for(;e&&!(128&e[Ce]);)e[Ce]|=8,e=e[we];e[Ce]|=8,fr(e[Te],1)}function fr(e,t){var n,l=0===e.flags;e.flags|=t,l&&e.clean==io&&(e.clean=new Promise(function(e){return n=e}),e.scheduler(function(){if(1&e.flags&&(e.flags&=-2,gr(e)),2&e.flags){e.flags&=-3;var t=e.playerHandler;t&&t.flushPlayers()}e.clean=io,n(null)}))}function gr(e){for(var t=0;t=2&&l[i-2]===t.hostBindings?l[i-1]=l[i-1]+n:l.push(t.hostBindings,n)}(n,qt,e),function(e,t,n){for(var l=0;l=l.data.length&&(l.data[i]=null),n[i]=t}function Nr(e){return Qe(rn,e)}function Lr(e){return Qt()[Ae][e]}function Ar(e){return Qe(Qt(),e)}function Pr(e,t){return void 0===t&&(t=Ot.Default),e=X(e),Dn(Jt(),Qt(),e,t)}function Fr(e){return Tn(Jt(),e)}function Vr(e,t){var n=Qt(),l=n[be],i=(n[Ae]||(n[Ae]=[])).push(e);if(dn()){var o=l.contentQueries||(l.contentQueries=[]);t!==(l.contentQueries.length?l.contentQueries[l.contentQueries.length-2]:-1)&&o.push(t,i-1)}}var jr=io;function Hr(e){return e?(void 0===e.inputs&&(e.inputs=Vo(e,0)),e.inputs):null}function Br(){return Qt()}function zr(e){return e[Oe]||(e[Oe]=[])}function Ur(e){return e[be].cleanup||(e[be].cleanup=[])}function $r(e,t){var n=jn(e);if(n){var l=n.native,i=n.lView,o=function(e,t){if(!(t=t||jn(l)))return null;var n=ai(t.nodeIndex,t.lView);return di(n)||pi(n)}(0,n),r=Zn(i);ci(o,r,l,t,0,e),fr(r,2)}}function Gr(e){var t=jn(e);if(!t)return[];var n=ai(t.nodeIndex,t.lView),l=n?di(n):null;return l?function(e){for(var t=[],n=e[0],l=2;l ");else if("object"==typeof t){var i=[];for(var o in t)if(t.hasOwnProperty(o)){var r=t[o];i.push(o+":"+("string"==typeof r?JSON.stringify(r):Y(r)))}l="{"+i.join(", ")+"}"}return"StaticInjectorError"+(n?"("+n+")":"")+"["+l+"]: "+e.replace(xu,"\n ")}function Tu(e,t){return new Error(Ou(e,t))}var Du=new x("The presence of this token marks an injector as being the root injector."),ku={},Ru={},Mu=[],Nu=void 0;function Lu(){return void 0===Nu&&(Nu=new hu),Nu}function Au(e,t,n){return void 0===t&&(t=null),void 0===n&&(n=null),t=t||Lu(),new Pu(e,n,t)}var Pu=function(){function e(e,t,n){var l=this;this.parent=n,this.records=new Map,this.injectorDefTypes=new Set,this.onDestroy=new Set,this.destroyed=!1;var i=[];Hu([e],function(e){return l.processInjectorType(e,[],i)}),t&&Hu(t,function(n){return l.processProvider(n,e,t)}),this.records.set(pu,ju(void 0,this)),this.isRootInjector=this.records.has(Du),this.injectorDefTypes.forEach(function(e){return l.get(e)})}return e.prototype.destroy=function(){this.assertNotDestroyed(),this.destroyed=!0;try{this.onDestroy.forEach(function(e){return e.ngOnDestroy()})}finally{this.records.clear(),this.onDestroy.clear(),this.injectorDefTypes.clear()}},e.prototype.get=function(e,t,n){void 0===t&&(t=du),void 0===n&&(n=Ot.Default),this.assertNotDestroyed();var l,i=Dt(this);try{if(!(n&Ot.SkipSelf)){var o=this.records.get(e);if(void 0===o){var r=("function"==typeof(l=e)||"object"==typeof l&&l instanceof x)&&w(e);r&&this.injectableDefInScope(r)&&(o=ju(Fu(e),ku),this.records.set(e,o))}if(void 0!==o)return this.hydrate(e,o)}return(n&Ot.Self?Lu():this.parent).get(e,t)}finally{Dt(i)}},e.prototype.assertNotDestroyed=function(){if(this.destroyed)throw new Error("Injector has already been destroyed.")},e.prototype.processInjectorType=function(e,t,n){var l=this;if(e=X(e)){var i=_(e),o=null==i&&e.ngModule||void 0,r=void 0===o?e:o,u=-1!==n.indexOf(r),a=void 0!==o&&e.providers||Mu;if(void 0!==o&&(i=_(o)),null!=i){if(this.injectorDefTypes.add(r),this.records.set(r,ju(i.factory,ku)),null!=i.imports&&!u){n.push(r);try{Hu(i.imports,function(e){return l.processInjectorType(e,t,n)})}finally{}}var s=i.providers;if(null!=s&&!u){var c=e;Hu(s,function(e){return l.processProvider(e,c,s)})}var d=e.ngModule;Hu(a,function(e){return l.processProvider(e,d,a)})}}},e.prototype.processProvider=function(e,t,n){var l=zu(e=X(e))?e:X(e&&e.provide),i=function(e,t,n){var l=Vu(e,t,n);return Bu(e)?ju(void 0,e.useValue):ju(l,ku)}(e,t,n);if(zu(e)||!0!==e.multi){var o=this.records.get(l);if(o&&void 0!==o.multi)throw new Error("Mixed multi-provider for "+Y(l))}else{var r=this.records.get(l);if(r){if(void 0===r.multi)throw new Error("Mixed multi-provider for "+l+".")}else(r=ju(void 0,ku,!0)).factory=function(){return Lt(r.multi)},this.records.set(l,r);l=e,r.multi.push(e)}this.records.set(l,i)},e.prototype.hydrate=function(e,t){if(t.value===Ru)throw new Error("Cannot instantiate cyclic dependency! "+Y(e));var n;return t.value===ku&&(t.value=Ru,t.value=t.factory()),"object"==typeof t.value&&t.value&&"object"==typeof(n=t.value)&&null!=n&&n.ngOnDestroy&&"function"==typeof n.ngOnDestroy&&this.onDestroy.add(t.value),t.value},e.prototype.injectableDefInScope=function(e){return!!e.providedIn&&("string"==typeof e.providedIn?"any"===e.providedIn||"root"===e.providedIn&&this.isRootInjector:this.injectorDefTypes.has(e.providedIn))},e}();function Fu(e){var t=w(e);if(null===t){var n=_(e);if(null!==n)return n.factory;if(e instanceof x)throw new Error("Token "+Y(e)+" is missing an ngInjectableDef definition.");if(e instanceof Function){var l=e.length;if(l>0){var i=new Array(l).fill("?");throw new Error("Can't resolve all parameters for "+Y(e)+": ("+i.join(", ")+").")}return function(){return new e}}throw new Error("unreachable")}return t.factory}function Vu(e,t,n){var i,o=void 0;if(zu(e))return Fu(X(e));if(Bu(e))o=function(){return X(e.useValue)};else if((i=e)&&i.useExisting)o=function(){return Mt(X(e.useExisting))};else if(e&&e.useFactory)o=function(){return e.useFactory.apply(e,Object(l.__spread)(Lt(e.deps||[])))};else{var r=X(e&&(e.useClass||e.provide));if(!r){var u="";throw t&&n&&(u=" - only instances of Provider and Type are allowed, got: ["+n.map(function(t){return t==e?"?"+e+"?":"..."}).join(", ")+"]"),new Error("Invalid provider for the NgModule '"+Y(t)+"'"+u)}if(!e.deps)return Fu(r);o=function(){return new(r.bind.apply(r,Object(l.__spread)([void 0],Lt(e.deps))))}}return o}function ju(e,t,n){return void 0===n&&(n=!1),{factory:e,value:t,multi:n?[]:void 0}}function Hu(e,t){e.forEach(function(e){return Array.isArray(e)?Hu(e,t):t(e)})}function Bu(e){return e&&"object"==typeof e&&wu in e}function zu(e){return"function"==typeof e}function Uu(e,t,n,l,i){if(e=X(e),Array.isArray(e))for(var o=0;o>16;if(zu(e)||!e.multi){var h=new Ke(a,i,Pr),f=Gu(u,t,i?c:c+p,d);-1==f?(On(xn(s,r),r,u),t.push(u),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=65536),n.push(h),r.push(h)):(n[f]=h,r[f]=h)}else{var g=Gu(u,t,c+p,d),m=Gu(u,t,c,c+p),v=m>=0&&n[m];i&&!v||!i&&!(g>=0&&n[g])?(On(xn(s,r),r,u),h=function(e,t,n,l,i){var o=new Ke(e,n,Pr);return o.multi=[],o.index=t,o.componentProviders=0,$u(o,i,l&&!n),o}(i?qu:Wu,n.length,i,l,a),!i&&v&&(n[m].providerFactory=h),t.push(u),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=65536),n.push(h),r.push(h)):$u(n[i?m:g],a,!i&&l),!i&&l&&v&&n[m].componentProviders++}}}function $u(e,t,n){e.multi.push(t),n&&e.componentProviders++}function Gu(e,t,n,l){for(var i=n;i-1&&this._viewContainerRef.detach(e),this._viewContainerRef=null}Vl(this._lView)},e.prototype.onDestroy=function(e){var t,n;n=e,zr(t=this._lView).push(n),t[be].firstTemplatePass&&Ur(t).push(t[Oe].length-1,null)},e.prototype.markForCheck=function(){hr(this._lView)},e.prototype.detach=function(){this._lView[Ce]&=-17},e.prototype.reattach=function(){this._lView[Ce]|=16},e.prototype.detectChanges=function(){vr(this._lView,this.context)},e.prototype.checkNoChanges=function(){!function(e){sn(!0);try{mr(e)}finally{sn(!1)}}(this.context)},e.prototype.attachToViewContainerRef=function(e){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=e},e.prototype.detachFromAppRef=function(){this._appRef=null},e.prototype.attachToAppRef=function(e){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=e},e.prototype._lookUpContext=function(){return this._context=this._lView[we][this._componentIndex]},e}(),ca=function(e){function t(t){var n=e.call(this,t,null,-1)||this;return n._view=t,n}return Object(l.__extends)(t,e),t.prototype.detectChanges=function(){yr(this._view)},t.prototype.checkNoChanges=function(){!function(e){sn(!0);try{yr(e)}finally{sn(!1)}}(this._view)},Object.defineProperty(t.prototype,"context",{get:function(){return null},enumerable:!0,configurable:!0}),t}(sa);function da(e,t,n){return Ju||(Ju=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(l.__extends)(t,e),t}(e)),new Ju(et(t,n))}function pa(e,t,n,i){if(ea||(ea=function(e){function t(t,n,l,i,o,r){var u=e.call(this)||this;return u._declarationParentView=t,u.elementRef=n,u._tView=l,u._renderer=i,u._queries=o,u._injectorIndex=r,u}return Object(l.__extends)(t,e),t.prototype.createEmbeddedView=function(e,t,n,l,i){var o=function(e,t,n,l,i,o){var r=nn(),u=Jt();ln(!0),en(null);var a=ro(n,e,t,4);return a[Pe]=n,i&&(a[xe]=i.createView()),ao(-1,a),e.firstTemplatePass&&(e.node.injectorIndex=o),ln(r),en(u),a}(this._tView,e,this._declarationParentView,0,this._queries,this._injectorIndex);t&&Ll(o,t,l,i,n.index),co(o,this._tView,e);var r=new sa(o,e,-1);return r._tViewNode=o[Ee],r},t}(e)),0===n.type){var o=i[n.index];return new ea(i,da(t,n,i),n.tViews,Qt()[Re],o[xe],n.injectorIndex)}return null}function ha(e,t,n){if(it(e)){var l=e.directiveStart,i=nt(e.index,t);return new sa(i,n,l)}if(3===e.type){var o=yt(t);return new sa(o,o[Te],-1)}return null}var fa=function(){function e(e){this.nativeElement=e}return e.__NG_ELEMENT_ID__=function(){return ma(e)},e}(),ga=function(e){return da(e,Jt(),Qt())},ma=au,va=function(){return function(e,t,n,l,i,o){this.id=e,this.templateUrl=t,this.slotCount=n,this.encapsulation=l,this.styles=i,this.animations=o}}(),ya=function(){return function(){}}(),ba=function(){return function(){}}(),Ca=function(){return function(){}}(),wa=function(){return function(){}}(),_a=function(e){return e[e.Important=1]="Important",e[e.DashCase=2]="DashCase",e}({}),xa=function(){function e(){}return e.__NG_ELEMENT_ID__=function(){return Ea()},e}(),Sa=function(){return function(e){var t=Qt()[Re];if(xl(t))return t;throw new Error("Cannot inject Renderer2 when the application uses Renderer3!")}()},Ea=au,Ia=function(e){return e[e.NONE=0]="NONE",e[e.HTML=1]="HTML",e[e.STYLE=2]="STYLE",e[e.SCRIPT=3]="SCRIPT",e[e.URL=4]="URL",e[e.RESOURCE_URL=5]="RESOURCE_URL",e}({}),Oa=function(){return function(){}}(),Ta=function(){return function(e){this.full=e,this.major=e.split(".")[0],this.minor=e.split(".")[1],this.patch=e.split(".").slice(2).join(".")}}(),Da=new Ta("7.2.14"),ka=function(e){function t(t){var n=e.call(this)||this;return n.ngModule=t,n}return Object(l.__extends)(t,e),t.prototype.resolveComponentFactory=function(e){var t=ge(e);return new Aa(t,this.ngModule)},t}(ia);function Ra(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push({propName:e[n],templateName:n});return t}var Ma=new x("ROOT_CONTEXT_TOKEN",{providedIn:"root",factory:function(){return Jr(Mt(Na))}}),Na=new x("SCHEDULER_TOKEN",{providedIn:"root",factory:function(){return mt}}),La={},Aa=function(e){function t(t,n){var l=e.call(this)||this;return l.componentDef=t,l.ngModule=n,l.componentType=t.type,l.selector=t.selectors[0][0],l.ngContentSelectors=[],l}return Object(l.__extends)(t,e),Object.defineProperty(t.prototype,"inputs",{get:function(){return Ra(this.componentDef.inputs)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"outputs",{get:function(){return Ra(this.componentDef.outputs)},enumerable:!0,configurable:!0}),t.prototype.create=function(e,t,n,l){var i=void 0===n,o=(l=l||this.ngModule)?function(e,t){return{get:function(n,l){var i=e.get(n,La);return i!==La||l===La?i:t.get(n,l)}}}(e,l.injector):e,r=o.get(wa,Sl),u=o.get(Oa,null),a=i?xo(this.selector,r.createRenderer(null,this.componentDef)):To(r,n),s=this.componentDef.onPush?136:132,c=i?Jr():o.get(Ma),d=r.createRenderer(a,this.componentDef);n&&a&&(xl(d)?d.setAttribute(a,"ng-version",Da.full):a.setAttribute("ng-version",Da.full));var p,h,f=ro(null,Io(-1,null,1,0,null,null,null),c,s,r,d,u,o),g=mn(f,null);try{r.begin&&r.begin();var m=Qr(a,this.componentDef,f,r,d);if(h=tt(0,f),t)for(var v=0,y=f[be],b=h.projection=[],C=0;C',!this.inertBodyElement.querySelector||this.inertBodyElement.querySelector("svg")?(this.inertBodyElement.innerHTML='

',this.getInertBodyElement=this.inertBodyElement.querySelector&&this.inertBodyElement.querySelector("svg img")&&function(){try{return!!window.DOMParser}catch(e){return!1}}()?this.getInertBodyElement_DOMParser:this.getInertBodyElement_InertDocument):this.getInertBodyElement=this.getInertBodyElement_XHR}return e.prototype.getInertBodyElement_XHR=function(e){e=""+e+"";try{e=encodeURI(e)}catch(l){return null}var t=new XMLHttpRequest;t.responseType="document",t.open("GET","data:text/html;charset=utf-8,"+e,!1),t.send(void 0);var n=t.response.body;return n.removeChild(n.firstChild),n},e.prototype.getInertBodyElement_DOMParser=function(e){e=""+e+"";try{var t=(new window.DOMParser).parseFromString(e,"text/html").body;return t.removeChild(t.firstChild),t}catch(n){return null}},e.prototype.getInertBodyElement_InertDocument=function(e){var t=this.inertDocument.createElement("template");return"content"in t?(t.innerHTML=e,t):(this.inertBodyElement.innerHTML=e,this.defaultDoc.documentMode&&this.stripCustomNsAttrs(this.inertBodyElement),this.inertBodyElement)},e.prototype.stripCustomNsAttrs=function(e){for(var t=e.attributes,n=t.length-1;0"),!0},e.prototype.endElement=function(e){var t=e.nodeName.toLowerCase();Ja.hasOwnProperty(t)&&!Ya.hasOwnProperty(t)&&(this.buf.push(""))},e.prototype.chars=function(e){this.buf.push(us(e))},e.prototype.checkClobberedElement=function(e,t){if(t&&(e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error("Failed to sanitize html because the element is clobbered: "+e.outerHTML);return t},e}(),os=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,rs=/([^\#-~ |!])/g;function us(e){return e.replace(/&/g,"&").replace(os,function(e){return"&#"+(1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320)+65536)+";"}).replace(rs,function(e){return"&#"+e.charCodeAt(0)+";"}).replace(//g,">")}function as(e,t){var n=null;try{Ka=Ka||new Ba(e);var l=t?String(t):"";n=Ka.getInertBodyElement(l);var i=5,o=l;do{if(0===i)throw new Error("Failed to sanitize html because the input is unstable");i--,l=o,o=n.innerHTML,n=Ka.getInertBodyElement(l)}while(l!==o);var r=new is,u=r.sanitizeChildren(ss(n)||n);return ja()&&r.sanitizedSomething&&console.warn("WARNING: sanitizing HTML stripped some content, see http://g.co/ng/security#xss"),u}finally{if(n)for(var a=ss(n)||n;a.firstChild;)a.removeChild(a.firstChild)}}function ss(e){return"content"in e&&function(e){return e.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===e.nodeName}(e)?e.content:null}var cs={marker:"element"},ds={marker:"comment"},ps="\ufffd",hs=/^\s*(\ufffd\d+:?\d*\ufffd)\s*,\s*(select|plural)\s*,/,fs=/\ufffd\/?\*(\d+:\d+)\ufffd/gi,gs=/\ufffd(\/?[#*]\d+):?\d*\ufffd/gi,ms=/\ufffd(\d+):?\d*\ufffd/gi,vs=/({\s*\ufffd\d+:?\d*\ufffd\s*,\s*\S{6}\s*,[\s\S]*})/gi,ys=/\[(\ufffd.+?\ufffd?)\]/g,bs=/({\s*)(VAR_(PLURAL|SELECT)(_\d+)?)(\s*,)/g,Cs=/\ufffdI18N_EXP_(ICU(_\d+)?)\ufffd/g;function ws(e){if(!e)return[];var t,n=0,l=[],i=[],o=/[{}]/g;for(o.lastIndex=0;t=o.exec(e);){var r=t.index;if("}"==t[0]){if(l.pop(),0==l.length){var u=e.substring(n,r);hs.test(u)?i.push(_s(u)):u&&i.push(u),n=r+1}}else{if(0==l.length){var a=e.substring(n,r);i.push(a),n=r+1}l.push("{")}}var s=e.substring(n);return""!=s&&i.push(s),i}function _s(e){for(var t=[],n=[],l=1,i=0,o=ws(e=e.replace(hs,function(e,t,n){return l="select"===n?0:1,i=parseInt(t.substr(1),10),""})),r=0;r0&&r!==u&&c.push(r.index<<3|0);for(var d=[],p=[],h=function(e,t){if("number"!=typeof t)return xs(e);var n=e.indexOf(":"+t+ps)+2+t.toString().length,l=e.search(new RegExp(ps+"\\/\\*\\d+:"+t+ps));return xs(e.substring(n,l))}(n,l).split(gs),f=0;f>>17,d=void 0;r=Rs(o,d=c===e?l[Ee]:tt(c,l),r),d.next=null;break;case 0:var p=s>>>3;u.push(p),r=o,(o=tt(p,l))&&(en(o),3===o.type&&ln(!0));break;case 5:r=o=tt(s>>>3,l),en(o),ln(!1);break;case 4:Ro(s>>>3,t[++a],t[++a]);break;default:throw new Error('Unable to determine the type of mutate operation for "'+s+'"')}else switch(s){case ds:var h=t[++a],f=i.createComment(h);r=o,o=uo(n++,5,f,null,null),zn(f,l),o.activeCaseIndex=null,ln(!1);break;case cs:var g=t[++a];r=o,o=uo(n++,3,i.createElement(g),g,null);break;default:throw new Error('Unable to determine the type of mutate operation for "'+s+'"')}}return ln(!1),u}function As(e,t){var n=tt(e,t);Kl(n,Je(e,t)||null,t),n.detached=!0;var l=Ar(e);if(rt(l)){var i=l;0!==n.type&&Kl(n,i[He]||null,t),i[Be]=null}}function Ps(e,t,n){ks(e,t,n),Ns()}function Fs(e,t){var n=Qt()[be];n.firstTemplatePass&&null===n.data[e+Fe]&&function(e,t,n){for(var l=Jt().index-Fe,i=[],o=0;o>>2,g=void 0,m=void 0;switch(3&h){case 1:Ro(f,t[++p],d,t[++p]);break;case 0:Wo(f,d);break;case 2:if(g=n[t[++p]],null!==(m=tt(f,o)).activeCaseIndex)for(var v=g.remove[m.activeCaseIndex],y=0;y>>3,o);break;case 6:var C=tt(v[y+1]>>>3,o).activeCaseIndex;null!==C&&vt(n[b>>>3].remove[C],v)}}var w=Us(g,d);m.activeCaseIndex=-1!==w?w:null,Ls(-1,g.create[w],g.expandoStartIndex,o),u=!0;break;case 3:g=n[t[++p]],m=tt(f,o),e(g.update[m.activeCaseIndex],n,l,i,o,u)}}}a+=c}}(l,i,t[Ie]-js-1,Vs,t),Vs=0,js=0}}var zs=function(e){return e[e.Zero=0]="Zero",e[e.One=1]="One",e[e.Two=2]="Two",e[e.Few=3]="Few",e[e.Many=4]="Many",e[e.Other=5]="Other",e}({});function Us(e,t){var n=e.cases.indexOf(t);if(-1===n)switch(e.type){case 1:var l=function(e,n){switch(function(e,t){"string"==typeof t&&(t=parseInt(t,10));var n=t,l=n.toString().replace(/^[^.]*\.?/,""),i=Math.floor(Math.abs(n)),o=l.length,r=parseInt(l,10),u=parseInt(n.toString().replace(/^[^.]*\.?|0+$/g,""),10)||0;switch(e.split("-")[0].toLowerCase()){case"af":case"asa":case"az":case"bem":case"bez":case"bg":case"brx":case"ce":case"cgg":case"chr":case"ckb":case"ee":case"el":case"eo":case"es":case"eu":case"fo":case"fur":case"gsw":case"ha":case"haw":case"hu":case"jgo":case"jmc":case"ka":case"kk":case"kkj":case"kl":case"ks":case"ksb":case"ky":case"lb":case"lg":case"mas":case"mgo":case"ml":case"mn":case"nb":case"nd":case"ne":case"nn":case"nnh":case"nyn":case"om":case"or":case"os":case"ps":case"rm":case"rof":case"rwk":case"saq":case"seh":case"sn":case"so":case"sq":case"ta":case"te":case"teo":case"tk":case"tr":case"ug":case"uz":case"vo":case"vun":case"wae":case"xog":return 1===n?zs.One:zs.Other;case"ak":case"ln":case"mg":case"pa":case"ti":return n===Math.floor(n)&&n>=0&&n<=1?zs.One:zs.Other;case"am":case"as":case"bn":case"fa":case"gu":case"hi":case"kn":case"mr":case"zu":return 0===i||1===n?zs.One:zs.Other;case"ar":return 0===n?zs.Zero:1===n?zs.One:2===n?zs.Two:n%100===Math.floor(n%100)&&n%100>=3&&n%100<=10?zs.Few:n%100===Math.floor(n%100)&&n%100>=11&&n%100<=99?zs.Many:zs.Other;case"ast":case"ca":case"de":case"en":case"et":case"fi":case"fy":case"gl":case"it":case"nl":case"sv":case"sw":case"ur":case"yi":return 1===i&&0===o?zs.One:zs.Other;case"be":return n%10==1&&n%100!=11?zs.One:n%10===Math.floor(n%10)&&n%10>=2&&n%10<=4&&!(n%100>=12&&n%100<=14)?zs.Few:n%10==0||n%10===Math.floor(n%10)&&n%10>=5&&n%10<=9||n%100===Math.floor(n%100)&&n%100>=11&&n%100<=14?zs.Many:zs.Other;case"br":return n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?zs.One:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?zs.Two:n%10===Math.floor(n%10)&&(n%10>=3&&n%10<=4||n%10==9)&&!(n%100>=10&&n%100<=19||n%100>=70&&n%100<=79||n%100>=90&&n%100<=99)?zs.Few:0!==n&&n%1e6==0?zs.Many:zs.Other;case"bs":case"hr":case"sr":return 0===o&&i%10==1&&i%100!=11||r%10==1&&r%100!=11?zs.One:0===o&&i%10===Math.floor(i%10)&&i%10>=2&&i%10<=4&&!(i%100>=12&&i%100<=14)||r%10===Math.floor(r%10)&&r%10>=2&&r%10<=4&&!(r%100>=12&&r%100<=14)?zs.Few:zs.Other;case"cs":case"sk":return 1===i&&0===o?zs.One:i===Math.floor(i)&&i>=2&&i<=4&&0===o?zs.Few:0!==o?zs.Many:zs.Other;case"cy":return 0===n?zs.Zero:1===n?zs.One:2===n?zs.Two:3===n?zs.Few:6===n?zs.Many:zs.Other;case"da":return 1===n||0!==u&&(0===i||1===i)?zs.One:zs.Other;case"dsb":case"hsb":return 0===o&&i%100==1||r%100==1?zs.One:0===o&&i%100==2||r%100==2?zs.Two:0===o&&i%100===Math.floor(i%100)&&i%100>=3&&i%100<=4||r%100===Math.floor(r%100)&&r%100>=3&&r%100<=4?zs.Few:zs.Other;case"ff":case"fr":case"hy":case"kab":return 0===i||1===i?zs.One:zs.Other;case"fil":return 0===o&&(1===i||2===i||3===i)||0===o&&i%10!=4&&i%10!=6&&i%10!=9||0!==o&&r%10!=4&&r%10!=6&&r%10!=9?zs.One:zs.Other;case"ga":return 1===n?zs.One:2===n?zs.Two:n===Math.floor(n)&&n>=3&&n<=6?zs.Few:n===Math.floor(n)&&n>=7&&n<=10?zs.Many:zs.Other;case"gd":return 1===n||11===n?zs.One:2===n||12===n?zs.Two:n===Math.floor(n)&&(n>=3&&n<=10||n>=13&&n<=19)?zs.Few:zs.Other;case"gv":return 0===o&&i%10==1?zs.One:0===o&&i%10==2?zs.Two:0!==o||i%100!=0&&i%100!=20&&i%100!=40&&i%100!=60&&i%100!=80?0!==o?zs.Many:zs.Other:zs.Few;case"he":return 1===i&&0===o?zs.One:2===i&&0===o?zs.Two:0!==o||n>=0&&n<=10||n%10!=0?zs.Other:zs.Many;case"is":return 0===u&&i%10==1&&i%100!=11||0!==u?zs.One:zs.Other;case"ksh":return 0===n?zs.Zero:1===n?zs.One:zs.Other;case"kw":case"naq":case"se":case"smn":return 1===n?zs.One:2===n?zs.Two:zs.Other;case"lag":return 0===n?zs.Zero:0!==i&&1!==i||0===n?zs.Other:zs.One;case"lt":return n%10!=1||n%100>=11&&n%100<=19?n%10===Math.floor(n%10)&&n%10>=2&&n%10<=9&&!(n%100>=11&&n%100<=19)?zs.Few:0!==r?zs.Many:zs.Other:zs.One;case"lv":case"prg":return n%10==0||n%100===Math.floor(n%100)&&n%100>=11&&n%100<=19||2===o&&r%100===Math.floor(r%100)&&r%100>=11&&r%100<=19?zs.Zero:n%10==1&&n%100!=11||2===o&&r%10==1&&r%100!=11||2!==o&&r%10==1?zs.One:zs.Other;case"mk":return 0===o&&i%10==1||r%10==1?zs.One:zs.Other;case"mt":return 1===n?zs.One:0===n||n%100===Math.floor(n%100)&&n%100>=2&&n%100<=10?zs.Few:n%100===Math.floor(n%100)&&n%100>=11&&n%100<=19?zs.Many:zs.Other;case"pl":return 1===i&&0===o?zs.One:0===o&&i%10===Math.floor(i%10)&&i%10>=2&&i%10<=4&&!(i%100>=12&&i%100<=14)?zs.Few:0===o&&1!==i&&i%10===Math.floor(i%10)&&i%10>=0&&i%10<=1||0===o&&i%10===Math.floor(i%10)&&i%10>=5&&i%10<=9||0===o&&i%100===Math.floor(i%100)&&i%100>=12&&i%100<=14?zs.Many:zs.Other;case"pt":return n===Math.floor(n)&&n>=0&&n<=2&&2!==n?zs.One:zs.Other;case"ro":return 1===i&&0===o?zs.One:0!==o||0===n||1!==n&&n%100===Math.floor(n%100)&&n%100>=1&&n%100<=19?zs.Few:zs.Other;case"ru":case"uk":return 0===o&&i%10==1&&i%100!=11?zs.One:0===o&&i%10===Math.floor(i%10)&&i%10>=2&&i%10<=4&&!(i%100>=12&&i%100<=14)?zs.Few:0===o&&i%10==0||0===o&&i%10===Math.floor(i%10)&&i%10>=5&&i%10<=9||0===o&&i%100===Math.floor(i%100)&&i%100>=11&&i%100<=14?zs.Many:zs.Other;case"shi":return 0===i||1===n?zs.One:n===Math.floor(n)&&n>=2&&n<=10?zs.Few:zs.Other;case"si":return 0===n||1===n||0===i&&1===r?zs.One:zs.Other;case"sl":return 0===o&&i%100==1?zs.One:0===o&&i%100==2?zs.Two:0===o&&i%100===Math.floor(i%100)&&i%100>=3&&i%100<=4||0!==o?zs.Few:zs.Other;case"tzm":return n===Math.floor(n)&&n>=0&&n<=1||n===Math.floor(n)&&n>=11&&n<=99?zs.One:zs.Other;default:return zs.Other}}("en-US",t)){case zs.Zero:return"zero";case zs.One:return"one";case zs.Two:return"two";case zs.Few:return"few";case zs.Many:return"many";default:return"other"}}();-1===(n=e.cases.indexOf(l))&&"other"!==l&&(n=e.cases.indexOf("other"));break;case 0:n=e.cases.indexOf("other")}return n}function $s(e,t,n,i){for(var o=[],r=[],u=[],a=[],s=[],c=0;c=0;n--){var l=t[n];if(e===l.name)return l}throw new Error("The pipe '"+e+"' could not be found!")}(t,l.pipeRegistry),l.data[i]=n,n.onDestroy&&(l.pipeDestroyHooks||(l.pipeDestroyHooks=[])).push(i,n.onDestroy)):n=l.data[i];var o=n.factory(null);return Mr(e,o),o}function ac(e,t,n){var l=Ar(e);return fc(hc(e)?Xs(t,l.transform,n,l):l.transform(n))}function sc(e,t,n,l){var i=Ar(e);return fc(hc(e)?Js(t,i.transform,n,l,i):i.transform(n,l))}function cc(e,t,n,l,i){var o=Ar(e);return fc(hc(e)?ec(t,o.transform,n,l,i,o):o.transform(n,l,i))}function dc(e,t,n,l,i,o){var r=Ar(e);return fc(hc(e)?tc(t,r.transform,n,l,i,o,r):r.transform(n,l,i,o))}function pc(e,t,n){var l=Ar(e);return fc(hc(e)?rc(t,l.transform,n,l):l.transform.apply(l,n))}function hc(e){return Qt()[be].data[e+Fe].pure}function fc(e){return sl.isWrapped(e)&&(e=sl.unwrap(e),Qt()[fn()]=fl),e}var gc=function(e){function t(t){void 0===t&&(t=!1);var n=e.call(this)||this;return n.__isAsync=t,n}return Object(l.__extends)(t,e),t.prototype.emit=function(t){e.prototype.next.call(this,t)},t.prototype.subscribe=function(t,n,l){var o,r=function(e){return null},u=function(){return null};t&&"object"==typeof t?(o=this.__isAsync?function(e){setTimeout(function(){return t.next(e)})}:function(e){t.next(e)},t.error&&(r=this.__isAsync?function(e){setTimeout(function(){return t.error(e)})}:function(e){t.error(e)}),t.complete&&(u=this.__isAsync?function(){setTimeout(function(){return t.complete()})}:function(){t.complete()})):(o=this.__isAsync?function(e){setTimeout(function(){return t(e)})}:function(e){t(e)},n&&(r=this.__isAsync?function(e){setTimeout(function(){return n(e)})}:function(e){n(e)}),l&&(u=this.__isAsync?function(){setTimeout(function(){return l()})}:function(){l()}));var a=e.prototype.subscribe.call(this,o,r,u);return t instanceof i.a&&t.add(a),a},t}(o.a),mc=function(){function e(){}return e.__NG_ELEMENT_ID__=function(){return yc(e,fa)},e}(),vc=function(e,t){return pa(e,t,Jt(),Qt())},yc=au,bc=function(){function e(e,t,n){this.parent=e,this.shallow=t,this.deep=n}return e.prototype.track=function(e,t,n,l){n?this.deep=Rc(this.deep,e,t,null!=l?l:null):this.shallow=Rc(this.shallow,e,t,null!=l?l:null)},e.prototype.clone=function(){return new e(this,null,this.deep)},e.prototype.container=function(){var t=Cc(this.shallow),n=Cc(this.deep);return t||n?new e(this,t,n):null},e.prototype.createView=function(){var t=wc(this.shallow),n=wc(this.deep);return t||n?new e(this,t,n):null},e.prototype.insertView=function(e){_c(e,this.shallow),_c(e,this.deep)},e.prototype.addNode=function(e){return Tc(this.deep,e),lt(e)?(Tc(this.shallow,e),e.parent&<(e.parent)&&Tc(this.parent.shallow,e),this.parent):(function(e){return null===e.parent||lt(e.parent)}(e)&&Tc(this.shallow,e),this)},e.prototype.removeView=function(){xc(this.shallow),xc(this.deep)},e}();function Cc(e){for(var t=null;e;){var n=[];e.values.push(n),t={next:t,list:e.list,predicate:e.predicate,values:n,containerValues:null},e=e.next}return t}function wc(e){for(var t=null;e;)t={next:t,list:e.list,predicate:e.predicate,values:[],containerValues:e.values},e=e.next;return t}function _c(e,t){for(;t;)t.containerValues.splice(e,0,t.values),t=t.next}function xc(e){for(;e;){var t=e.containerValues,n=t.indexOf(e.values);t.splice(n,1)[0].length&&e.list.setDirty(),e=e.next}}function Sc(e,t){var n=e.localNames;if(n)for(var l=0;l-1?Nn(t[be].data,t,l,e):function(e,t){return 3===e.type||4===e.type?da(fa,e,t):0===e.type?pa(mc,fa,e,t):null}(e,t)}function Tc(e,t){for(var n=Qt();e;){var l=e.predicate,i=l.type;if(i){var o=null;i===mc?o=Ic(i,t,n,l.read):null!==(a=Mn(t,n,i,!1,!1))&&(o=Oc(t,n,l.read,a)),null!==o&&Dc(e,o)}else for(var r=l.selector,u=0;u0?(e=l.concat(e.slice(n+1)),n=0):n++:(t.push(l),n++)}return t}(e),this.dirty=!1},e.prototype.notifyOnChanges=function(){this.changes.emit(this)},e.prototype.setDirty=function(){this.dirty=!0},e.prototype.destroy=function(){this.changes.complete(),this.changes.unsubscribe()},e}();function Nc(e,t,n,l){var i,o,r,u=new Mc;return(i=bc,o=Qt(),r=o[xe],$t&&$t!==o[Ee]&&!lt($t)&&(r&&(r=o[xe]=r.clone()),$t.flags|=4),r||(o[xe]=new i(null,null,null))).track(u,t,n,l),function(e,t,n){var l=zr(e);l.push(t),e[be].firstTemplatePass&&Ur(e).push(n,l.length-1)}(Qt(),u,u.destroy),null!=e&&Mr(e,u),u}function Lc(e){return!!e.dirty&&(e.reset(e._valuesTree),e.notifyOnChanges(),!0)}function Ac(e,t){return pa(mc,fa,e,t)}var Pc="__SANITIZER_TRUSTED_BRAND__";function Fc(e,t){return e instanceof String&&e[Pc]===t}function Vc(e){return Uc(e,"Html")}function jc(e){return Uc(e,"Style")}function Hc(e){return Uc(e,"Script")}function Bc(e){return Uc(e,"Url")}function zc(e){return Uc(e,"ResourceUrl")}function Uc(e,t){var n=new String(e);return n[Pc]=t,n}var $c=new RegExp("^([-,.\"'%_!# a-zA-Z0-9]+|(?:(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?|(?:rgb|hsl)a?|(?:repeating-)?(?:linear|radial)-gradient|(?:calc|attr))\\([-0-9.%, #a-zA-Z]+\\))$","g"),Gc=/^url\(([^)]+)\)$/;function Wc(e){if(!(e=String(e).trim()))return"";var t=e.match(Gc);return t&&$a(t[1])===t[1]||e.match($c)&&function(e){for(var t=!0,n=!0,l=0;l=0;e--){var t=_d[e],n=t.moduleType,l=t.ngModule;l.declarations&&l.declarations.every(Sd)&&(_d.splice(e,1),Td(n,l))}}finally{xd=!1}}}(),void 0!==e.ngSelectorScope){var u=kd(e.ngSelectorScope);Dd(n,u)}}return n},configurable:!1})}function Ad(e,t){var n=null;Object.defineProperty(e,p,{get:function(){if(null===n){var l=Fd(e,t);n=bt().compileDirective(ed,"ng://"+(e&&e.name)+"/ngDirectiveDef.js",l)}return n},configurable:!1})}function Pd(e){return Object.getPrototypeOf(e.prototype)===Object.prototype}function Fd(e,t){var n=yd().propMetadata(e);return{name:e.name,type:e,typeArgumentCount:0,selector:t.selector,deps:bd(e),host:t.host||ie,propMetadata:n,inputs:t.inputs||oe,outputs:t.outputs||oe,queries:Vd(e,n,jd),lifecycle:{usesOnChanges:void 0!==e.prototype.ngOnChanges},typeSourceSpan:null,usesInheritance:!Pd(e),exportAs:t.exportAs||null,providers:t.providers||null}}function Vd(e,t,n){var l=[],i=function(i){t.hasOwnProperty(i)&&t[i].forEach(function(t){if(n(t)){if(!t.selector)throw new Error("Can't construct a query for the property \""+i+'" of "'+Ze(e)+"\" since the query selector wasn't defined.");l.push(function(e,t){return{propertyName:i,predicate:(n=t.selector,"string"==typeof n?n.split(",").map(function(e){return e.trim()}):X(n)),descendants:t.descendants,first:t.first,read:t.read?t.read:null};var n}(0,t))}})};for(var o in t)i(o);return l}function jd(e){var t=e.ngMetadataName;return"ContentChild"===t||"ContentChildren"===t}function Hd(e){var t=e.ngMetadataName;return"ViewChild"===t||"ViewChildren"===t}function Bd(e,t){var n=null;Object.defineProperty(e,g,{get:function(){return null===n&&(n=bt().compilePipe(ed,"ng://"+Ze(e)+"/ngPipeDef.js",{type:e,name:e.name,deps:bd(e),pipeName:t.name,pure:void 0===t.pure||t.pure})),n},configurable:!1})}var zd=O("Directive",function(e){return void 0===e&&(e={}),e},void 0,void 0,function(e,t){return ep(e,t)}),Ud=O("Component",function(e){return void 0===e&&(e={}),Object(l.__assign)({changeDetection:V.Default},e)},zd,void 0,function(e,t){return Jd(e,t)}),$d=O("Pipe",function(e){return Object(l.__assign)({pure:!0},e)},void 0,void 0,function(e,t){return tp(e,t)}),Gd=function(e){return function(t,n){for(var l=[],i=2;i1?" ("+function(e){for(var t=[],n=0;n-1)return t.push(e[n]),t;t.push(e[n])}return t}(e.slice().reverse()).map(function(e){return Y(e.token)}).join(" -> ")+")":""}function Ip(e,t,n,l){var i=[t],o=n(i),r=l?function(e,t){var n=o+" caused by: "+(t instanceof Error?t.message:t),l=Error(n);return l[bp]=t,l}(0,l):Error(o);return r.addKey=Op,r.keys=i,r.injectors=[e],r.constructResolvingMessage=n,r[bp]=l,r}function Op(e,t){this.injectors.push(e),this.keys.push(t),this.message=this.constructResolvingMessage(this.keys)}function Tp(e,t){for(var n=[],l=0,i=t.length;l=this._providers.length)throw function(e){return Error("Index "+e+" is out-of-bounds.")}(e);return this._providers[e]},e.prototype._new=function(e){if(this._constructionCounter++>this._getMaxNumberOfObjects())throw Ip(this,e.key,function(e){return"Cannot instantiate cyclic dependency!"+Ep(e)});return this._instantiateProvider(e)},e.prototype._getMaxNumberOfObjects=function(){return this.objs.length},e.prototype._instantiateProvider=function(e){if(e.multiProvider){for(var t=new Array(e.resolvedFactories.length),n=0;n0&&(i=setTimeout(function(){l._callbacks=l._callbacks.filter(function(e){return e.timeoutId!==i}),e(l._didWork,l.getPendingTasks())},t)),this._callbacks.push({doneCb:e,timeoutId:i,updateCb:n})},e.prototype.whenStable=function(e,t,n){if(n&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/dist/task-tracking.js" loaded?');this.addCallback(e,t,n),this._runCallbacksIfReady()},e.prototype.getPendingRequestCount=function(){return this._pendingCount},e.prototype.findProviders=function(e,t,n){return[]},e}(),Vh=function(){function e(){this._applications=new Map,Bh.addToWindow(this)}return e.prototype.registerApplication=function(e,t){this._applications.set(e,t)},e.prototype.unregisterApplication=function(e){this._applications.delete(e)},e.prototype.unregisterAllApplications=function(){this._applications.clear()},e.prototype.getTestability=function(e){return this._applications.get(e)||null},e.prototype.getAllTestabilities=function(){return Array.from(this._applications.values())},e.prototype.getAllRootElements=function(){return Array.from(this._applications.keys())},e.prototype.findTestabilityInTree=function(e,t){return void 0===t&&(t=!0),Bh.findTestabilityInTree(this,e,t)},Object(l.__decorate)([Object(l.__metadata)("design:paramtypes",[])],e)}();function jh(e){Bh=e}var Hh,Bh=new(function(){function e(){}return e.prototype.addToWindow=function(e){},e.prototype.findTestabilityInTree=function(e,t,n){return null},e}());function zh(e,t,n){return Promise.resolve(new Ys(n))}var Uh=new x("AllowMultipleToken"),$h=function(){return function(e,t){this.name=e,this.token=t}}();function Gh(e){if(Hh&&!Hh.destroyed&&!Hh.injector.get(Uh,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Hh=e.get(Zh);var t=e.get(Jp,null);return t&&t.forEach(function(e){return e()}),Hh}function Wh(e,t,n){void 0===n&&(n=[]);var l="Platform: "+t,i=new x(l);return function(t){void 0===t&&(t=[]);var o=Yh();if(!o||o.injector.get(Uh,!1))if(e)e(n.concat(t).concat({provide:i,useValue:!0}));else{var r=n.concat(t).concat({provide:i,useValue:!0});Gh(fu.create({providers:r,name:l}))}return qh(i)}}function qh(e){var t=Yh();if(!t)throw new Error("No platform exists!");if(!t.injector.get(e,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return t}function Kh(){Hh&&!Hh.destroyed&&Hh.destroy()}function Yh(){return Hh&&!Hh.destroyed?Hh:null}var Zh=function(){function e(e){this._injector=e,this._modules=[],this._destroyListeners=[],this._destroyed=!1}return e.prototype.bootstrapModuleFactory=function(e,t){var n,l=this,i="noop"===(n=t?t.ngZone:void 0)?new Ph:("zone.js"===n?void 0:n)||new kh({enableLongStackTrace:ja()}),o=[{provide:kh,useValue:i}];return i.run(function(){var t=fu.create({providers:o,parent:l.injector,name:e.moduleType.name}),n=e.create(t),r=n.injector.get(Sp,null);if(!r)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return n.onDestroy(function(){return Jh(l._modules,n)}),i.runOutsideAngular(function(){return i.onError.subscribe({next:function(e){r.handleError(e)}})}),function(e,t,i){try{var o=((r=n.injector.get(Kp)).runInitializers(),r.donePromise.then(function(){return l._moduleDoBootstrap(n),n}));return Gp(o)?o.catch(function(n){throw t.runOutsideAngular(function(){return e.handleError(n)}),n}):o}catch(u){throw t.runOutsideAngular(function(){return e.handleError(u)}),u}var r}(r,i)})},e.prototype.bootstrapModule=function(e,t){var n=this;void 0===t&&(t=[]);var l=Qh({},t);return function(e,t,n){return e.get(yh).createCompiler([t]).compileModuleAsync(n)}(this.injector,l,e).then(function(e){return n.bootstrapModuleFactory(e,l)})},e.prototype._moduleDoBootstrap=function(e){var t=e.injector.get(Xh);if(e._bootstrapComponents.length>0)e._bootstrapComponents.forEach(function(e){return t.bootstrap(e)});else{if(!e.instance.ngDoBootstrap)throw new Error("The module "+Y(e.instance.constructor)+' was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.');e.instance.ngDoBootstrap(t)}this._modules.push(e)},e.prototype.onDestroy=function(e){this._destroyListeners.push(e)},Object.defineProperty(e.prototype,"injector",{get:function(){return this._injector},enumerable:!0,configurable:!0}),e.prototype.destroy=function(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(function(e){return e.destroy()}),this._destroyListeners.forEach(function(e){return e()}),this._destroyed=!0},Object.defineProperty(e.prototype,"destroyed",{get:function(){return this._destroyed},enumerable:!0,configurable:!0}),e}();function Qh(e,t){return Array.isArray(t)?t.reduce(Qh,e):Object(l.__assign)({},e,t)}var Xh=function(){function e(e,t,n,l,i,o){var s=this;this._zone=e,this._console=t,this._injector=n,this._exceptionHandler=l,this._componentFactoryResolver=i,this._initStatus=o,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._enforceNoNewChanges=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._enforceNoNewChanges=ja(),this._zone.onMicrotaskEmpty.subscribe({next:function(){s._zone.run(function(){s.tick()})}});var c=new r.a(function(e){s._stable=s._zone.isStable&&!s._zone.hasPendingMacrotasks&&!s._zone.hasPendingMicrotasks,s._zone.runOutsideAngular(function(){e.next(s._stable),e.complete()})}),d=new r.a(function(e){var t;s._zone.runOutsideAngular(function(){t=s._zone.onStable.subscribe(function(){kh.assertNotInAngularZone(),q(function(){s._stable||s._zone.hasPendingMacrotasks||s._zone.hasPendingMicrotasks||(s._stable=!0,e.next(!0))})})});var n=s._zone.onUnstable.subscribe(function(){kh.assertInAngularZone(),s._stable&&(s._stable=!1,s._zone.runOutsideAngular(function(){e.next(!1)}))});return function(){t.unsubscribe(),n.unsubscribe()}});this.isStable=Object(u.a)(c,d.pipe(Object(a.a)()))}var t;return t=e,e.prototype.bootstrap=function(e,t){var n,l=this;if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");n=e instanceof Qu?e:this._componentFactoryResolver.resolveComponentFactory(e),this.componentTypes.push(n.componentType);var i=n instanceof ra?null:this._injector.get(ua),o=n.create(fu.NULL,[],t||n.selector,i);o.onDestroy(function(){l._unloadComponent(o)});var r=o.injector.get(Fh,null);return r&&o.injector.get(Vh).registerApplication(o.location.nativeElement,r),this._loadComponent(o),ja()&&this._console.log("Angular is running in the development mode. Call enableProdMode() to enable the production mode."),o},e.prototype.tick=function(){var e=this;if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");var n=t._tickScope();try{this._runningTick=!0,this._views.forEach(function(e){return e.detectChanges()}),this._enforceNoNewChanges&&this._views.forEach(function(e){return e.checkNoChanges()})}catch(l){this._zone.runOutsideAngular(function(){return e._exceptionHandler.handleError(l)})}finally{this._runningTick=!1,Oh(n)}},e.prototype.attachView=function(e){var t=e;this._views.push(t),t.attachToAppRef(this)},e.prototype.detachView=function(e){var t=e;Jh(this._views,t),t.detachFromAppRef()},e.prototype._loadComponent=function(e){this.attachView(e.hostView),this.tick(),this.components.push(e),this._injector.get(th,[]).concat(this._bootstrapListeners).forEach(function(t){return t(e)})},e.prototype._unloadComponent=function(e){this.detachView(e.hostView),Jh(this.components,e)},e.prototype.ngOnDestroy=function(){this._views.slice().forEach(function(e){return e.destroy()})},Object.defineProperty(e.prototype,"viewCount",{get:function(){return this._views.length},enumerable:!0,configurable:!0}),e._tickScope=Ih("ApplicationRef#tick()"),e}();function Jh(e,t){var n=e.indexOf(t);n>-1&&e.splice(n,1)}var ef=function(){function e(){this.dirty=!0,this._results=[],this.changes=new gc,this.length=0}return e.prototype.map=function(e){return this._results.map(e)},e.prototype.filter=function(e){return this._results.filter(e)},e.prototype.find=function(e){return this._results.find(e)},e.prototype.reduce=function(e,t){return this._results.reduce(e,t)},e.prototype.forEach=function(e){this._results.forEach(e)},e.prototype.some=function(e){return this._results.some(e)},e.prototype.toArray=function(){return this._results.slice()},e.prototype[W()]=function(){return this._results[W()]()},e.prototype.toString=function(){return this._results.toString()},e.prototype.reset=function(e){this._results=function e(t){return t.reduce(function(t,n){var l=Array.isArray(n)?e(n):n;return t.concat(l)},[])}(e),this.dirty=!1,this.length=this._results.length,this.last=this._results[this.length-1],this.first=this._results[0]},e.prototype.notifyOnChanges=function(){this.changes.emit(this)},e.prototype.setDirty=function(){this.dirty=!0},e.prototype.destroy=function(){this.changes.complete(),this.changes.unsubscribe()},e}(),tf=function(){return function(){}}(),nf={factoryPathPrefix:"",factoryPathSuffix:".ngfactory"},lf=function(){function e(e,t){this._compiler=e,this._config=t||nf}return e.prototype.load=function(e){return this._compiler instanceof mh?this.loadFactory(e):this.loadAndCompile(e)},e.prototype.loadAndCompile=function(e){var t=this,i=Object(l.__read)(e.split("#"),2),o=i[0],r=i[1];return void 0===r&&(r="default"),n("crnd")(o).then(function(e){return e[r]}).then(function(e){return of(e,o,r)}).then(function(e){return t._compiler.compileModuleAsync(e)})},e.prototype.loadFactory=function(e){var t=Object(l.__read)(e.split("#"),2),i=t[0],o=t[1],r="NgFactory";return void 0===o&&(o="default",r=""),n("crnd")(this._config.factoryPathPrefix+i+this._config.factoryPathSuffix).then(function(e){return e[o+r]}).then(function(e){return of(e,i,o)})},e}();function of(e,t,n){if(!e)throw new Error("Cannot find '"+n+"' in '"+t+"'");return e}var rf=function(){function e(){}return e.__NG_ELEMENT_ID__=function(){return af(e,fa)},e}(),uf=function(e,t){return function(e,t,n,i){var o;ta||(ta=function(e){function n(t,n,l){var i=e.call(this)||this;return i._lContainer=t,i._hostTNode=n,i._hostView=l,i._viewRefs=[],i}return Object(l.__extends)(n,e),Object.defineProperty(n.prototype,"element",{get:function(){return da(t,this._hostTNode,this._hostView)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"injector",{get:function(){return new Pn(this._hostTNode,this._hostView)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"parentInjector",{get:function(){var e=In(this._hostTNode,this._hostView),t=gt(e,this._hostView),n=function(e,t,n){if(n.parent&&-1!==n.parent.injectorIndex){for(var l=n.parent.injectorIndex,i=n.parent;null!=i.parent&&l==i.injectorIndex;)i=i.parent;return i}for(var o=ft(e),r=t,u=t[Ee];o>1;)u=(r=r[Pe])[Ee],o--;return u}(e,this._hostView,this._hostTNode);return pt(e)&&null!=n?new Pn(n,t):new Pn(null,this._hostView)},enumerable:!0,configurable:!0}),n.prototype.clear=function(){for(;this._lContainer[je].length;)this.remove(0)},n.prototype.get=function(e){return this._viewRefs[e]||null},Object.defineProperty(n.prototype,"length",{get:function(){return this._lContainer[je].length},enumerable:!0,configurable:!0}),n.prototype.createEmbeddedView=function(e,t,n){var l=this._adjustIndex(n),i=e.createEmbeddedView(t||{},this._lContainer,this._hostTNode,this._hostView,l);return i.attachToViewContainerRef(this),this._viewRefs.splice(l,0,i),i},n.prototype.createComponent=function(e,t,n,l,i){var o=n||this.parentInjector;!i&&null==e.ngModule&&o&&(i=o.get(ua,null));var r=e.create(o,l,void 0,i);return this.insert(r.hostView,t),r},n.prototype.insert=function(e,t){if(e.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var n=e._lView,l=this._adjustIndex(t);return Ll(n,this._lContainer,this._hostView,l,this._hostTNode.index),Nl(n,!0,ql(l,this._lContainer[je],this._lContainer[He])),e.attachToViewContainerRef(this),this._viewRefs.splice(l,0,e),e},n.prototype.move=function(e,t){if(e.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var n=this.indexOf(e);return this.detach(n),this.insert(e,this._adjustIndex(t)),e},n.prototype.indexOf=function(e){return this._viewRefs.indexOf(e)},n.prototype.remove=function(e){var t=this._adjustIndex(e,-1);Pl(this._lContainer,this._hostTNode,t),this._viewRefs.splice(t,1)},n.prototype.detach=function(e){var t=this._adjustIndex(e,-1),n=Al(this._lContainer,t,!!this._hostTNode.detached);return null!=this._viewRefs.splice(t,1)[0]?new sa(n,n[Te],n[Le]):null},n.prototype._adjustIndex=function(e,t){return void 0===t&&(t=0),null==e?this._lContainer[je].length+t:e},n}(e));var r=i[n.index];if(rt(r))(o=r)[Ve]=-1;else{var u=i[Re].createComment("");if(ut(i)){var a=i[Re],s=et(n,i);Ul(a,$l(a,s),u,function(e,t){return xl(e)?e.nextSibling(t):t.nextSibling}(a,s))}else Gl(u,n,i);i[n.index]=o=tr(r,n,i,u,!0),pr(i,n.index,o)}return new ta(o,n,i)}(e,t,Jt(),Qt())},af=au,sf=function(){function e(){}return e.__NG_ELEMENT_ID__=function(){return df()},e}(),cf=function(){return ha(Jt(),Qt(),null)},df=function(){for(var e=[],t=0;t-1}(l)||"root"===i.providedIn&&l._def.isRoot))){var c=e._providers.length;return e._def.providersByKey[t.tokenKey]={flags:5120,value:u.factory,deps:[],index:c,token:t.token},e._providers[c]=om,e._providers[c]=pm(e,e._def.providersByKey[t.tokenKey])}return 4&t.flags?n:e._parent.get(t.token,n)}finally{Dt(o)}}function pm(e,t){var n;switch(201347067&t.flags){case 512:n=function(e,t,n){var i=n.length;switch(i){case 0:return new t;case 1:return new t(dm(e,n[0]));case 2:return new t(dm(e,n[0]),dm(e,n[1]));case 3:return new t(dm(e,n[0]),dm(e,n[1]),dm(e,n[2]));default:for(var o=new Array(i),r=0;r=n.length)&&(t=n.length-1),t<0)return null;var l=n[t];return l.viewContainerParent=null,vm(n,t),cg.dirtyParentQueries(l),gm(l),l}function fm(e,t,n){var l=t?kg(t,t.def.lastRenderRootNode):e.renderElement,i=n.renderer.parentNode(l),o=n.renderer.nextSibling(l);Hg(n,2,i,o,void 0)}function gm(e){Hg(e,3,null,null,void 0)}function mm(e,t,n){t>=e.length?e.push(n):e.splice(t,0,n)}function vm(e,t){t>=e.length-1?e.pop():e.splice(t,1)}var ym=new Object;function bm(e,t,n,l,i,o){return new wm(e,t,n,l,i,o)}function Cm(e){return e.viewDefFactory}var wm=function(e){function t(t,n,l,i,o,r){var u=e.call(this)||this;return u.selector=t,u.componentType=n,u._inputs=i,u._outputs=o,u.ngContentSelectors=r,u.viewDefFactory=l,u}return Object(l.__extends)(t,e),Object.defineProperty(t.prototype,"inputs",{get:function(){var e=[],t=this._inputs;for(var n in t)e.push({propName:n,templateName:t[n]});return e},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"outputs",{get:function(){var e=[];for(var t in this._outputs)e.push({propName:t,templateName:this._outputs[t]});return e},enumerable:!0,configurable:!0}),t.prototype.create=function(e,t,n,l){if(!l)throw new Error("ngModule should be provided");var i=jg(this.viewDefFactory),o=i.nodes[0].element.componentProvider.nodeIndex,r=cg.createRootView(e,t||[],n,i,l,ym),u=rg(r,o).instance;return n&&r.renderer.setAttribute(og(r,0).renderElement,"ng-version",Da.full),new _m(r,new Im(r),u)},t}(Qu),_m=function(e){function t(t,n,l){var i=e.call(this)||this;return i._view=t,i._viewRef=n,i._component=l,i._elDef=i._view.def.nodes[0],i.hostView=n,i.changeDetectorRef=n,i.instance=l,i}return Object(l.__extends)(t,e),Object.defineProperty(t.prototype,"location",{get:function(){return new fa(og(this._view,this._elDef.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"injector",{get:function(){return new km(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"componentType",{get:function(){return this._component.constructor},enumerable:!0,configurable:!0}),t.prototype.destroy=function(){this._viewRef.destroy()},t.prototype.onDestroy=function(e){this._viewRef.onDestroy(e)},t}(Zu);function xm(e,t,n){return new Sm(e,t,n)}var Sm=function(){function e(e,t,n){this._view=e,this._elDef=t,this._data=n,this._embeddedViews=[]}return Object.defineProperty(e.prototype,"element",{get:function(){return new fa(this._data.renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"injector",{get:function(){return new km(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"parentInjector",{get:function(){for(var e=this._view,t=this._elDef.parent;!t&&e;)t=Dg(e),e=e.parent;return e?new km(e,t):new km(this._view,null)},enumerable:!0,configurable:!0}),e.prototype.clear=function(){for(var e=this._embeddedViews.length-1;e>=0;e--){var t=hm(this._data,e);cg.destroyView(t)}},e.prototype.get=function(e){var t=this._embeddedViews[e];if(t){var n=new Im(t);return n.attachToViewContainerRef(this),n}return null},Object.defineProperty(e.prototype,"length",{get:function(){return this._embeddedViews.length},enumerable:!0,configurable:!0}),e.prototype.createEmbeddedView=function(e,t,n){var l=e.createEmbeddedView(t||{});return this.insert(l,n),l},e.prototype.createComponent=function(e,t,n,l,i){var o=n||this.parentInjector;i||e instanceof ra||(i=o.get(ua));var r=e.create(o,l,void 0,i);return this.insert(r.hostView,t),r},e.prototype.insert=function(e,t){if(e.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var n,l,i,o,r=e;return o=(n=this._data).viewContainer._embeddedViews,null==(l=t)&&(l=o.length),(i=r._view).viewContainerParent=this._view,mm(o,l,i),function(e,t){var n=Tg(t);if(n&&n!==e&&!(16&t.state)){t.state|=16;var l=n.template._projectedViews;l||(l=n.template._projectedViews=[]),l.push(t),function(e,n){if(!(4&n.flags)){t.parent.def.nodeFlags|=4,n.flags|=4;for(var l=n.parent;l;)l.childFlags|=4,l=l.parent}}(0,t.parentNodeDef)}}(n,i),cg.dirtyParentQueries(i),fm(n,l>0?o[l-1]:null,i),r.attachToViewContainerRef(this),e},e.prototype.move=function(e,t){if(e.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var n,l,i,o,r,u=this._embeddedViews.indexOf(e._view);return i=t,r=(o=(n=this._data).viewContainer._embeddedViews)[l=u],vm(o,l),null==i&&(i=o.length),mm(o,i,r),cg.dirtyParentQueries(r),gm(r),fm(n,i>0?o[i-1]:null,r),e},e.prototype.indexOf=function(e){return this._embeddedViews.indexOf(e._view)},e.prototype.remove=function(e){var t=hm(this._data,e);t&&cg.destroyView(t)},e.prototype.detach=function(e){var t=hm(this._data,e);return t?new Im(t):null},e}();function Em(e){return new Im(e)}var Im=function(){function e(e){this._view=e,this._viewContainerRef=null,this._appRef=null}return Object.defineProperty(e.prototype,"rootNodes",{get:function(){return Hg(this._view,0,void 0,void 0,e=[]),e;var e},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"context",{get:function(){return this._view.context},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"destroyed",{get:function(){return 0!=(128&this._view.state)},enumerable:!0,configurable:!0}),e.prototype.markForCheck=function(){Eg(this._view)},e.prototype.detach=function(){this._view.state&=-5},e.prototype.detectChanges=function(){var e=this._view.root.rendererFactory;e.begin&&e.begin();try{cg.checkAndUpdateView(this._view)}finally{e.end&&e.end()}},e.prototype.checkNoChanges=function(){cg.checkNoChangesView(this._view)},e.prototype.reattach=function(){this._view.state|=4},e.prototype.onDestroy=function(e){this._view.disposables||(this._view.disposables=[]),this._view.disposables.push(e)},e.prototype.destroy=function(){this._appRef?this._appRef.detachView(this):this._viewContainerRef&&this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)),cg.destroyView(this._view)},e.prototype.detachFromAppRef=function(){this._appRef=null,gm(this._view),cg.dirtyParentQueries(this._view)},e.prototype.attachToAppRef=function(e){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=e},e.prototype.attachToViewContainerRef=function(e){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=e},e}();function Om(e,t){return new Tm(e,t)}var Tm=function(e){function t(t,n){var l=e.call(this)||this;return l._parentView=t,l._def=n,l}return Object(l.__extends)(t,e),t.prototype.createEmbeddedView=function(e){return new Im(cg.createEmbeddedView(this._parentView,this._def,this._def.element.template,e))},Object.defineProperty(t.prototype,"elementRef",{get:function(){return new fa(og(this._parentView,this._def.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),t}(mc);function Dm(e,t){return new km(e,t)}var km=function(){function e(e,t){this.view=e,this.elDef=t}return e.prototype.get=function(e,t){return void 0===t&&(t=fu.THROW_IF_NOT_FOUND),cg.resolveDep(this.view,this.elDef,!!this.elDef&&0!=(33554432&this.elDef.flags),{flags:0,token:e,tokenKey:mg(e)},t)},e}();function Rm(e,t){var n=e.def.nodes[t];if(1&n.flags){var l=og(e,n.nodeIndex);return n.element.template?l.template:l.renderElement}if(2&n.flags)return ig(e,n.nodeIndex).renderText;if(20240&n.flags)return rg(e,n.nodeIndex).instance;throw new Error("Illegal state: read nodeValue for node index "+t)}function Mm(e){return new Nm(e.renderer)}var Nm=function(){function e(e){this.delegate=e}return e.prototype.selectRootElement=function(e){return this.delegate.selectRootElement(e)},e.prototype.createElement=function(e,t){var n=Object(l.__read)(Wg(t),2),i=this.delegate.createElement(n[1],n[0]);return e&&this.delegate.appendChild(e,i),i},e.prototype.createViewRoot=function(e){return e},e.prototype.createTemplateAnchor=function(e){var t=this.delegate.createComment("");return e&&this.delegate.appendChild(e,t),t},e.prototype.createText=function(e,t){var n=this.delegate.createText(t);return e&&this.delegate.appendChild(e,n),n},e.prototype.projectNodes=function(e,t){for(var n=0;n0,t.provider.value,t.provider.deps);if(t.outputs.length)for(var l=0;l0,i=t.provider;switch(201347067&t.flags){case 512:return Jm(e,t.parent,n,i.value,i.deps);case 1024:return function(e,t,n,i,o){var r=o.length;switch(r){case 0:return i();case 1:return i(tv(e,t,n,o[0]));case 2:return i(tv(e,t,n,o[0]),tv(e,t,n,o[1]));case 3:return i(tv(e,t,n,o[0]),tv(e,t,n,o[1]),tv(e,t,n,o[2]));default:for(var u=Array(r),a=0;a0)s=g,_v(g)||(c=g);else for(;s&&f===s.nodeIndex+s.childCount;){var y=s.parent;y&&(y.childFlags|=s.childFlags,y.childMatchedQueries|=s.childMatchedQueries),c=(s=y)&&_v(s)?s.renderParent:s}}return{factory:null,nodeFlags:r,rootNodeFlags:u,nodeMatchedQueries:a,flags:e,nodes:t,updateDirectives:n||fg,updateRenderer:l||fg,handleEvent:function(e,n,l,i){return t[n].element.handleEvent(e,l,i)},bindingCount:i,outputCount:o,lastRenderRootNode:h}}function _v(e){return 0!=(1&e.flags)&&null===e.element.name}function xv(e,t,n){var l=t.element&&t.element.template;if(l){if(!l.lastRenderRootNode)throw new Error("Illegal State: Embedded templates without nodes are not allowed!");if(l.lastRenderRootNode&&16777216&l.lastRenderRootNode.flags)throw new Error("Illegal State: Last root node of a template can't have embedded views, at index "+t.nodeIndex+"!")}if(20224&t.flags&&0==(1&(e?e.flags:0)))throw new Error("Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index "+t.nodeIndex+"!");if(t.query){if(67108864&t.flags&&(!e||0==(16384&e.flags)))throw new Error("Illegal State: Content Query nodes need to be children of directives, at index "+t.nodeIndex+"!");if(134217728&t.flags&&e)throw new Error("Illegal State: View Query nodes have to be top level nodes, at index "+t.nodeIndex+"!")}if(t.childCount){var i=e?e.nodeIndex+e.childCount:n-1;if(t.nodeIndex<=i&&t.nodeIndex+t.childCount>i)throw new Error("Illegal State: childCount of node leads outside of parent, at index "+t.nodeIndex+"!")}}function Sv(e,t,n,l){var i=Ov(e.root,e.renderer,e,t,n);return Tv(i,e.component,l),Dv(i),i}function Ev(e,t,n){var l=Ov(e,e.renderer,null,null,t);return Tv(l,n,n),Dv(l),l}function Iv(e,t,n,l){var i,o=t.element.componentRendererType;return i=o?e.root.rendererFactory.createRenderer(l,o):e.root.renderer,Ov(e.root,i,e,t.element.componentProvider,n)}function Ov(e,t,n,l,i){var o=new Array(i.nodes.length),r=i.outputCount?new Array(i.outputCount):null;return{def:i,parent:n,viewContainerParent:null,parentNodeDef:l,context:null,component:null,nodes:o,state:13,root:e,renderer:t,oldValues:new Array(i.bindingCount),disposables:r,initIndex:-1}}function Tv(e,t,n){e.component=t,e.context=n}function Dv(e){var t;Mg(e)&&(t=og(e.parent,e.parentNodeDef.parent.nodeIndex).renderElement);for(var n=e.def,l=e.nodes,i=0;i0&&im(e,t,0,n)&&(h=!0),p>1&&im(e,t,1,l)&&(h=!0),p>2&&im(e,t,2,i)&&(h=!0),p>3&&im(e,t,3,o)&&(h=!0),p>4&&im(e,t,4,r)&&(h=!0),p>5&&im(e,t,5,u)&&(h=!0),p>6&&im(e,t,6,a)&&(h=!0),p>7&&im(e,t,7,s)&&(h=!0),p>8&&im(e,t,8,c)&&(h=!0),p>9&&im(e,t,9,d)&&(h=!0),h}(e,t,n,l,i,o,r,u,a,s,c,d);case 2:return function(e,t,n,l,i,o,r,u,a,s,c,d){var p=!1,h=t.bindings,f=h.length;if(f>0&&xg(e,t,0,n)&&(p=!0),f>1&&xg(e,t,1,l)&&(p=!0),f>2&&xg(e,t,2,i)&&(p=!0),f>3&&xg(e,t,3,o)&&(p=!0),f>4&&xg(e,t,4,r)&&(p=!0),f>5&&xg(e,t,5,u)&&(p=!0),f>6&&xg(e,t,6,a)&&(p=!0),f>7&&xg(e,t,7,s)&&(p=!0),f>8&&xg(e,t,8,c)&&(p=!0),f>9&&xg(e,t,9,d)&&(p=!0),p){var g=t.text.prefix;f>0&&(g+=Cv(n,h[0])),f>1&&(g+=Cv(l,h[1])),f>2&&(g+=Cv(i,h[2])),f>3&&(g+=Cv(o,h[3])),f>4&&(g+=Cv(r,h[4])),f>5&&(g+=Cv(u,h[5])),f>6&&(g+=Cv(a,h[6])),f>7&&(g+=Cv(s,h[7])),f>8&&(g+=Cv(c,h[8])),f>9&&(g+=Cv(d,h[9]));var m=ig(e,t.nodeIndex).renderText;e.renderer.setValue(m,g)}return p}(e,t,n,l,i,o,r,u,a,s,c,d);case 16384:return function(e,t,n,l,i,o,r,u,a,s,c,d){var p=rg(e,t.nodeIndex),h=p.instance,f=!1,g=void 0,m=t.bindings.length;return m>0&&_g(e,t,0,n)&&(f=!0,g=lv(e,p,t,0,n,g)),m>1&&_g(e,t,1,l)&&(f=!0,g=lv(e,p,t,1,l,g)),m>2&&_g(e,t,2,i)&&(f=!0,g=lv(e,p,t,2,i,g)),m>3&&_g(e,t,3,o)&&(f=!0,g=lv(e,p,t,3,o,g)),m>4&&_g(e,t,4,r)&&(f=!0,g=lv(e,p,t,4,r,g)),m>5&&_g(e,t,5,u)&&(f=!0,g=lv(e,p,t,5,u,g)),m>6&&_g(e,t,6,a)&&(f=!0,g=lv(e,p,t,6,a,g)),m>7&&_g(e,t,7,s)&&(f=!0,g=lv(e,p,t,7,s,g)),m>8&&_g(e,t,8,c)&&(f=!0,g=lv(e,p,t,8,c,g)),m>9&&_g(e,t,9,d)&&(f=!0,g=lv(e,p,t,9,d,g)),g&&h.ngOnChanges(g),65536&t.flags&&lg(e,256,t.nodeIndex)&&h.ngOnInit(),262144&t.flags&&h.ngDoCheck(),f}(e,t,n,l,i,o,r,u,a,s,c,d);case 32:case 64:case 128:return function(e,t,n,l,i,o,r,u,a,s,c,d){var p=t.bindings,h=!1,f=p.length;if(f>0&&xg(e,t,0,n)&&(h=!0),f>1&&xg(e,t,1,l)&&(h=!0),f>2&&xg(e,t,2,i)&&(h=!0),f>3&&xg(e,t,3,o)&&(h=!0),f>4&&xg(e,t,4,r)&&(h=!0),f>5&&xg(e,t,5,u)&&(h=!0),f>6&&xg(e,t,6,a)&&(h=!0),f>7&&xg(e,t,7,s)&&(h=!0),f>8&&xg(e,t,8,c)&&(h=!0),f>9&&xg(e,t,9,d)&&(h=!0),h){var g=ug(e,t.nodeIndex),m=void 0;switch(201347067&t.flags){case 32:m=new Array(p.length),f>0&&(m[0]=n),f>1&&(m[1]=l),f>2&&(m[2]=i),f>3&&(m[3]=o),f>4&&(m[4]=r),f>5&&(m[5]=u),f>6&&(m[6]=a),f>7&&(m[7]=s),f>8&&(m[8]=c),f>9&&(m[9]=d);break;case 64:m={},f>0&&(m[p[0].name]=n),f>1&&(m[p[1].name]=l),f>2&&(m[p[2].name]=i),f>3&&(m[p[3].name]=o),f>4&&(m[p[4].name]=r),f>5&&(m[p[5].name]=u),f>6&&(m[p[6].name]=a),f>7&&(m[p[7].name]=s),f>8&&(m[p[8].name]=c),f>9&&(m[p[9].name]=d);break;case 128:var v=n;switch(f){case 1:m=v.transform(n);break;case 2:m=v.transform(l);break;case 3:m=v.transform(l,i);break;case 4:m=v.transform(l,i,o);break;case 5:m=v.transform(l,i,o,r);break;case 6:m=v.transform(l,i,o,r,u);break;case 7:m=v.transform(l,i,o,r,u,a);break;case 8:m=v.transform(l,i,o,r,u,a,s);break;case 9:m=v.transform(l,i,o,r,u,a,s,c);break;case 10:m=v.transform(l,i,o,r,u,a,s,c,d)}}g.value=m}return h}(e,t,n,l,i,o,r,u,a,s,c,d);default:throw"unreachable"}}(e,t,i,o,r,u,a,s,c,d,p,h):function(e,t,n){switch(201347067&t.flags){case 1:return function(e,t,n){for(var l=!1,i=0;i0&&Sg(e,t,0,n),p>1&&Sg(e,t,1,l),p>2&&Sg(e,t,2,i),p>3&&Sg(e,t,3,o),p>4&&Sg(e,t,4,r),p>5&&Sg(e,t,5,u),p>6&&Sg(e,t,6,a),p>7&&Sg(e,t,7,s),p>8&&Sg(e,t,8,c),p>9&&Sg(e,t,9,d)}(e,t,l,i,o,r,u,a,s,c,d,p):function(e,t,n){for(var l=0;l0){var o=new Set(e.modules);Xv.forEach(function(t,l){if(o.has(w(l).providedIn)){var i={token:l,flags:t.flags|(n?4096:0),deps:Pg(t.deps),value:t.value,index:e.providers.length};e.providers.push(i),e.providersByKey[mg(l)]=i}})}}(e=e.factory(function(){return fg})),e):e}(l))}var Qv=new Map,Xv=new Map,Jv=new Map;function ey(e){var t;Qv.set(e.token,e),"function"==typeof e.token&&(t=w(e.token))&&"function"==typeof t.providedIn&&Xv.set(e.token,e)}function ty(e,t){var n=jg(Cm(t)),l=jg(n.nodes[0].element.componentView);Jv.set(e,l)}function ny(){Qv.clear(),Xv.clear(),Jv.clear()}function ly(e){if(0===Qv.size)return e;var t=function(e){for(var t=[],n=null,l=0;l",'"',"`"," ","\r","\n","\t"]),c=["'"].concat(s),d=["%","/","?",";","#"].concat(c),p=["/","?","#"],h=/^[+a-z0-9A-Z_-]{0,63}$/,f=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,g={javascript:!0,"javascript:":!0},m={javascript:!0,"javascript:":!0},v={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},y=n("r8II");function b(e,t,n){if(e&&i.isObject(e)&&e instanceof o)return e;var l=new o;return l.parse(e,t,n),l}o.prototype.parse=function(e,t,n){if(!i.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var o=e.indexOf("?"),u=-1!==o&&o127?N+="x":N+=M[L];if(!N.match(h)){var P=k.slice(0,O),F=k.slice(O+1),V=M.match(f);V&&(P.push(V[1]),F.unshift(V[2])),F.length&&(b="/"+F.join(".")+b),this.hostname=P.join(".");break}}}this.hostname=this.hostname.length>255?"":this.hostname.toLowerCase(),D||(this.hostname=l.toASCII(this.hostname)),this.host=(this.hostname||"")+(this.port?":"+this.port:""),this.href+=this.host,D&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==b[0]&&(b="/"+b))}if(!g[_])for(O=0,R=c.length;O0)&&n.host.split("@"))&&(n.auth=O.shift(),n.host=n.hostname=O.shift())),n.search=e.search,n.query=e.query,i.isNull(n.pathname)&&i.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n;if(!w.length)return n.pathname=null,n.path=n.search?"/"+n.search:null,n.href=n.format(),n;for(var x=w.slice(-1)[0],S=(n.host||e.host||w.length>1)&&("."===x||".."===x)||""===x,E=0,I=w.length;I>=0;I--)"."===(x=w[I])?w.splice(I,1):".."===x?(w.splice(I,1),E++):E&&(w.splice(I,1),E--);if(!b&&!C)for(;E--;E)w.unshift("..");!b||""===w[0]||w[0]&&"/"===w[0].charAt(0)||w.unshift(""),S&&"/"!==w.join("/").substr(-1)&&w.push("");var O,T=""===w[0]||w[0]&&"/"===w[0].charAt(0);return _&&(n.hostname=n.host=T?"":w.length?w.shift():"",(O=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@"))&&(n.auth=O.shift(),n.host=n.hostname=O.shift())),(b=b||n.host&&w.length)&&!T&&w.unshift(""),w.length?n.pathname=w.join("/"):(n.pathname=null,n.path=null),i.isNull(n.pathname)&&i.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},o.prototype.parseHost=function(){var e=this.host,t=u.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},Czxz:function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("7LN8"),a=n("mU/a"),s=n("sdDj"),c=n("P3jN"),d=n("CcnG"),p=n("DtyJ"),h=function(){function e(){this.sortSource=new p.Subject,this.selectionSource=new p.Subject,this.contextMenuSource=new p.Subject,this.valueSource=new p.Subject,this.totalRecordsSource=new p.Subject,this.columnsSource=new p.Subject,this.sortSource$=this.sortSource.asObservable(),this.selectionSource$=this.selectionSource.asObservable(),this.contextMenuSource$=this.contextMenuSource.asObservable(),this.valueSource$=this.valueSource.asObservable(),this.totalRecordsSource$=this.totalRecordsSource.asObservable(),this.columnsSource$=this.columnsSource.asObservable()}return e.prototype.onSort=function(e){this.sortSource.next(e)},e.prototype.onSelectionChange=function(){this.selectionSource.next()},e.prototype.onContextMenu=function(e){this.contextMenuSource.next(e)},e.prototype.onValueChange=function(e){this.valueSource.next(e)},e.prototype.onTotalRecordsChange=function(e){this.totalRecordsSource.next(e)},e.prototype.onColumnsChange=function(e){this.columnsSource.next(e)},l([d.Injectable()],e)}();t.TableService=h;var f=function(){function e(e,t,n){this.el=e,this.zone=t,this.tableService=n,this.first=0,this.pageLinks=5,this.alwaysShowPaginator=!0,this.paginatorPosition="bottom",this.defaultSortOrder=1,this.sortMode="single",this.resetPageOnSort=!0,this.selectionChange=new o.EventEmitter,this.contextMenuSelectionChange=new o.EventEmitter,this.contextMenuSelectionMode="separate",this.rowTrackBy=function(e,t){return t},this.lazy=!1,this.lazyLoadOnInit=!0,this.compareSelectionBy="deepEquals",this.csvSeparator=",",this.exportFilename="download",this.filters={},this.filterDelay=300,this.expandedRowKeys={},this.rowExpandMode="multiple",this.virtualScrollDelay=150,this.virtualRowHeight=28,this.columnResizeMode="fit",this.loadingIcon="pi pi-spinner",this.stateStorage="session",this.onRowSelect=new o.EventEmitter,this.onRowUnselect=new o.EventEmitter,this.onPage=new o.EventEmitter,this.onSort=new o.EventEmitter,this.onFilter=new o.EventEmitter,this.onLazyLoad=new o.EventEmitter,this.onRowExpand=new o.EventEmitter,this.onRowCollapse=new o.EventEmitter,this.onContextMenuSelect=new o.EventEmitter,this.onColResize=new o.EventEmitter,this.onColReorder=new o.EventEmitter,this.onRowReorder=new o.EventEmitter,this.onEditInit=new o.EventEmitter,this.onEditComplete=new o.EventEmitter,this.onEditCancel=new o.EventEmitter,this.onHeaderCheckboxToggle=new o.EventEmitter,this.sortFunction=new o.EventEmitter,this._value=[],this._totalRecords=0,this.selectionKeys={},this._sortOrder=1,this.filterConstraints={startsWith:function(e,t){if(null==t||""===t.trim())return!0;if(null==e)return!1;var n=c.ObjectUtils.removeAccents(t.toString()).toLowerCase();return c.ObjectUtils.removeAccents(e.toString()).toLowerCase().slice(0,n.length)===n},contains:function(e,t){if(null==t||"string"==typeof t&&""===t.trim())return!0;if(null==e)return!1;var n=c.ObjectUtils.removeAccents(t.toString()).toLowerCase();return-1!==c.ObjectUtils.removeAccents(e.toString()).toLowerCase().indexOf(n)},endsWith:function(e,t){if(null==t||""===t.trim())return!0;if(null==e)return!1;var n=c.ObjectUtils.removeAccents(t.toString()).toLowerCase(),l=c.ObjectUtils.removeAccents(e.toString()).toLowerCase();return-1!==l.indexOf(n,l.length-n.length)},equals:function(e,t){return null==t||"string"==typeof t&&""===t.trim()||null!=e&&(e.getTime&&t.getTime?e.getTime()===t.getTime():c.ObjectUtils.removeAccents(e.toString()).toLowerCase()==c.ObjectUtils.removeAccents(t.toString()).toLowerCase())},notEquals:function(e,t){return!(null==t||"string"==typeof t&&""===t.trim()||null!=e&&(e.getTime&&t.getTime?e.getTime()===t.getTime():c.ObjectUtils.removeAccents(e.toString()).toLowerCase()==c.ObjectUtils.removeAccents(t.toString()).toLowerCase()))},in:function(e,t){if(null==t||0===t.length)return!0;for(var n=0;nt.getTime():e>t)},gte:function(e,t){return null==t||null!=e&&(e.getTime&&t.getTime?e.getTime()>=t.getTime():e>=t)}}}return e.prototype.ngOnInit=function(){this.lazy&&this.lazyLoadOnInit&&this.onLazyLoad.emit(this.createLazyLoadMetadata()),this.initialized=!0},e.prototype.ngAfterContentInit=function(){var e=this;this.templates.forEach(function(t){switch(t.getType()){case"caption":e.captionTemplate=t.template;break;case"header":e.headerTemplate=t.template;break;case"body":e.bodyTemplate=t.template;break;case"footer":e.footerTemplate=t.template;break;case"summary":e.summaryTemplate=t.template;break;case"colgroup":e.colGroupTemplate=t.template;break;case"rowexpansion":e.expandedRowTemplate=t.template;break;case"frozenrows":e.frozenRowsTemplate=t.template;break;case"frozenheader":e.frozenHeaderTemplate=t.template;break;case"frozenbody":e.frozenBodyTemplate=t.template;break;case"frozenfooter":e.frozenFooterTemplate=t.template;break;case"frozencolgroup":e.frozenColGroupTemplate=t.template;break;case"emptymessage":e.emptyMessageTemplate=t.template;break;case"paginatorleft":e.paginatorLeftTemplate=t.template;break;case"paginatorright":e.paginatorRightTemplate=t.template}})},e.prototype.ngAfterViewInit=function(){this.isStateful()&&this.resizableColumns&&this.restoreColumnWidths()},Object.defineProperty(e.prototype,"value",{get:function(){return this._value},set:function(e){this.isStateful()&&!this.stateRestored&&this.restoreState(),this._value=e,this.lazy||(this.totalRecords=this._value?this._value.length:0,"single"==this.sortMode&&this.sortField?this.sortSingle():"multiple"==this.sortMode&&this.multiSortMeta?this.sortMultiple():this.hasFilter()&&this._filter()),this.virtualScroll&&this.virtualScrollCallback&&this.virtualScrollCallback(),this.tableService.onValueChange(e)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"columns",{get:function(){return this._columns},set:function(e){this._columns=e,this.tableService.onColumnsChange(e),this._columns&&this.isStateful()&&this.reorderableColumns&&!this.columnOrderStateRestored&&this.restoreColumnOrder()},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"totalRecords",{get:function(){return this._totalRecords},set:function(e){this._totalRecords=e,this.tableService.onTotalRecordsChange(this._totalRecords)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"sortField",{get:function(){return this._sortField},set:function(e){this._sortField=e,this.lazy&&!this.initialized||"single"===this.sortMode&&this.sortSingle()},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"sortOrder",{get:function(){return this._sortOrder},set:function(e){this._sortOrder=e,this.lazy&&!this.initialized||"single"===this.sortMode&&this.sortSingle()},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"multiSortMeta",{get:function(){return this._multiSortMeta},set:function(e){this._multiSortMeta=e,"multiple"===this.sortMode&&this.sortMultiple()},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"selection",{get:function(){return this._selection},set:function(e){this._selection=e,this.preventSelectionSetterPropagation||(this.updateSelectionKeys(),this.tableService.onSelectionChange()),this.preventSelectionSetterPropagation=!1},enumerable:!0,configurable:!0}),e.prototype.updateSelectionKeys=function(){if(this.dataKey&&this._selection)if(this.selectionKeys={},Array.isArray(this._selection))for(var e=0,t=this._selection;eo?1:0,e.sortOrder*l}),this.hasFilter()&&this._filter());var t={field:this.sortField,order:this.sortOrder};this.onSort.emit(t),this.tableService.onSort(t)}},e.prototype.sortMultiple=function(){var e=this;this.multiSortMeta&&(this.lazy?this.onLazyLoad.emit(this.createLazyLoadMetadata()):this.value&&(this.customSort?this.sortFunction.emit({data:this.value,mode:this.sortMode,multiSortMeta:this.multiSortMeta}):this.value.sort(function(t,n){return e.multisortField(t,n,e.multiSortMeta,0)}),this.hasFilter()&&this._filter()),this.onSort.emit({multisortmeta:this.multiSortMeta}),this.tableService.onSort(this.multiSortMeta))},e.prototype.multisortField=function(e,t,n,l){var i=c.ObjectUtils.resolveFieldData(e,n[l].field),o=c.ObjectUtils.resolveFieldData(t,n[l].field),r=null;if(null==i&&null!=o?r=-1:null!=i&&null==o?r=1:null==i&&null==o&&(r=0),"string"==typeof i||i instanceof String){if(i.localeCompare&&i!=o)return n[l].order*i.localeCompare(o)}else r=il?this.multisortField(e,t,n,l+1):0:n[l].order*r},e.prototype.getSortMeta=function(e){if(this.multiSortMeta&&this.multiSortMeta.length)for(var t=0;tt?(n=t,l=this.anchorRowIndex):this.anchorRowIndexthis.anchorRowIndex?(t=this.anchorRowIndex,n=this.rangeRowIndex):this.rangeRowIndex-1:this.equals(e,this.selection))},e.prototype.findIndexInSelection=function(e){var t=-1;if(this.selection&&this.selection.length)for(var n=0;n=i){if("fit"===this.columnResizeMode){for(var r=t.nextElementSibling;!r.offsetParent;)r=r.nextElementSibling;if(r){var u=r.offsetWidth-n;if(o>15&&u>parseInt(r.style.minWidth||15))if(this.scrollable){var a=this.findParentScrollableView(t),c=s.DomHandler.findSingle(a,"table.ui-table-scrollable-body-table"),d=s.DomHandler.findSingle(a,"table.ui-table-scrollable-header-table"),p=s.DomHandler.findSingle(a,"table.ui-table-scrollable-footer-table"),h=s.DomHandler.index(t);this.resizeColGroup(d,h,o,u),this.resizeColGroup(c,h,o,u),this.resizeColGroup(p,h,o,u)}else t.style.width=o+"px",r&&(r.style.width=u+"px")}}else"expand"===this.columnResizeMode&&o>i&&(this.scrollable?(a=this.findParentScrollableView(t),c=s.DomHandler.findSingle(a,"table.ui-table-scrollable-body-table"),d=s.DomHandler.findSingle(a,"table.ui-table-scrollable-header-table"),p=s.DomHandler.findSingle(a,"table.ui-table-scrollable-footer-table"),c.style.width=c.offsetWidth+n+"px",d.style.width=d.offsetWidth+n+"px",p&&(p.style.width=d.offsetWidth+n+"px"),h=s.DomHandler.index(t),this.resizeColGroup(d,h,o,null),this.resizeColGroup(c,h,o,null),this.resizeColGroup(p,h,o,null)):(this.tableViewChild.nativeElement.style.width=this.tableViewChild.nativeElement.offsetWidth+n+"px",t.style.width=o+"px",this.containerViewChild.nativeElement.style.width=this.tableViewChild.nativeElement.style.width+"px"));this.onColResize.emit({element:t,delta:n}),this.isStateful()&&this.saveState()}this.resizeHelperViewChild.nativeElement.style.display="none",s.DomHandler.removeClass(this.containerViewChild.nativeElement,"ui-unselectable-text")},e.prototype.findParentScrollableView=function(e){if(e){for(var t=e.parentElement;t&&!s.DomHandler.hasClass(t,"ui-table-scrollable-view");)t=t.parentElement;return t}return null},e.prototype.resizeColGroup=function(e,t,n,l){if(e){var i="COLGROUP"===e.children[0].nodeName?e.children[0]:null;if(!i)throw"Scrollable tables require a colgroup to support resizable columns";var o=i.children[t],r=o.nextElementSibling;o.style.width=n+"px",r&&l&&(r.style.width=l+"px")}},e.prototype.onColumnDragStart=function(e,t){this.reorderIconWidth=s.DomHandler.getHiddenElementOuterWidth(this.reorderIndicatorUpViewChild.nativeElement),this.reorderIconHeight=s.DomHandler.getHiddenElementOuterHeight(this.reorderIndicatorDownViewChild.nativeElement),this.draggedColumn=t,e.dataTransfer.setData("text","b")},e.prototype.onColumnDragEnter=function(e,t){if(this.reorderableColumns&&this.draggedColumn&&t){e.preventDefault();var n=s.DomHandler.getOffset(this.containerViewChild.nativeElement),l=s.DomHandler.getOffset(t);if(this.draggedColumn!=t){var i=l.left-n.left,o=l.left+t.offsetWidth/2;this.reorderIndicatorUpViewChild.nativeElement.style.top=l.top-n.top-(this.reorderIconHeight-1)+"px",this.reorderIndicatorDownViewChild.nativeElement.style.top=l.top-n.top+t.offsetHeight+"px",e.pageX>o?(this.reorderIndicatorUpViewChild.nativeElement.style.left=i+t.offsetWidth-Math.ceil(this.reorderIconWidth/2)+"px",this.reorderIndicatorDownViewChild.nativeElement.style.left=i+t.offsetWidth-Math.ceil(this.reorderIconWidth/2)+"px",this.dropPosition=1):(this.reorderIndicatorUpViewChild.nativeElement.style.left=i-Math.ceil(this.reorderIconWidth/2)+"px",this.reorderIndicatorDownViewChild.nativeElement.style.left=i-Math.ceil(this.reorderIconWidth/2)+"px",this.dropPosition=-1),this.reorderIndicatorUpViewChild.nativeElement.style.display="block",this.reorderIndicatorDownViewChild.nativeElement.style.display="block"}else e.dataTransfer.dropEffect="none"}},e.prototype.onColumnDragLeave=function(e){this.reorderableColumns&&this.draggedColumn&&(e.preventDefault(),this.reorderIndicatorUpViewChild.nativeElement.style.display="none",this.reorderIndicatorDownViewChild.nativeElement.style.display="none")},e.prototype.onColumnDrop=function(e,t){if(e.preventDefault(),this.draggedColumn){var n=s.DomHandler.indexWithinGroup(this.draggedColumn,"preorderablecolumn"),l=s.DomHandler.indexWithinGroup(t,"preorderablecolumn"),i=n!=l;i&&(l-n==1&&-1===this.dropPosition||n-l==1&&1===this.dropPosition)&&(i=!1),i&&ln&&-1===this.dropPosition&&(l-=1),i&&(c.ObjectUtils.reorderArray(this.columns,n,l),this.onColReorder.emit({dragIndex:n,dropIndex:l,columns:this.columns}),this.isStateful()&&this.saveState()),this.reorderIndicatorUpViewChild.nativeElement.style.display="none",this.reorderIndicatorDownViewChild.nativeElement.style.display="none",this.draggedColumn.draggable=!1,this.draggedColumn=null,this.dropPosition=null}},e.prototype.onRowDragStart=function(e,t){this.rowDragging=!0,this.draggedRowIndex=t,e.dataTransfer.setData("text","b")},e.prototype.onRowDragOver=function(e,t,n){if(this.rowDragging&&this.draggedRowIndex!==t){var l=s.DomHandler.getOffset(n).top+s.DomHandler.getWindowScrollTop(),i=e.pageY,o=l+s.DomHandler.getOuterHeight(n)/2,r=n.previousElementSibling;ithis.droppedRowIndex?this.droppedRowIndex:0===this.droppedRowIndex?0:this.droppedRowIndex-1),this.onRowReorder.emit({dragIndex:this.draggedRowIndex,dropIndex:this.droppedRowIndex})),this.onRowDragLeave(e,t),this.onRowDragEnd(e)},e.prototype.handleVirtualScroll=function(e){var t=this;this.first=(e.page-1)*this.rows,this.virtualScrollCallback=e.callback,this.zone.run(function(){t.virtualScrollTimer&&clearTimeout(t.virtualScrollTimer),t.virtualScrollTimer=setTimeout(function(){t.onLazyLoad.emit(t.createLazyLoadMetadata())},t.virtualScrollDelay)})},e.prototype.isEmpty=function(){var e=this.filteredValue||this.value;return null==e||0==e.length},e.prototype.getBlockableElement=function(){return this.el.nativeElement.children[0]},e.prototype.getStorage=function(){switch(this.stateStorage){case"local":return window.localStorage;case"session":return window.sessionStorage;default:throw new Error(this.stateStorage+' is not a valid value for the state storage, supported values are "local" and "session".')}},e.prototype.isStateful=function(){return null!=this.stateKey},e.prototype.saveState=function(){var e=this.getStorage(),t={};this.paginator&&(t.first=this.first,t.rows=this.rows),this.sortField&&(t.sortField=this.sortField,t.sortOrder=this.sortOrder),this.multiSortMeta&&(t.multiSortMeta=this.multiSortMeta),this.hasFilter()&&(t.filters=this.filters),this.resizableColumns&&this.saveColumnWidths(t),this.reorderableColumns&&this.saveColumnOrder(t),this.selection&&(t.selection=this.selection),Object.keys(this.expandedRowKeys).length&&(t.expandedRowKeys=this.expandedRowKeys),Object.keys(t).length&&e.setItem(this.stateKey,JSON.stringify(t))},e.prototype.clearState=function(){var e=this.getStorage();this.stateKey&&e.removeItem(this.stateKey)},e.prototype.restoreState=function(){var e=this.getStorage().getItem(this.stateKey);if(e){var t=JSON.parse(e);this.paginator&&(this.first=t.first,this.rows=t.rows),t.sortField&&(this.restoringSort=!0,this._sortField=t.sortField,this._sortOrder=t.sortOrder),t.multiSortMeta&&(this.restoringSort=!0,this._multiSortMeta=t.multiSortMeta),t.filters&&(this.restoringFilter=!0,this.filters=t.filters),this.resizableColumns&&(this.columnWidthsState=t.columnWidths,this.tableWidthState=t.tableWidth),t.expandedRowKeys&&(this.expandedRowKeys=t.expandedRowKeys),t.selection&&(this.selection=t.selection),this.stateRestored=!0}},e.prototype.saveColumnWidths=function(e){var t=[];s.DomHandler.find(this.containerViewChild.nativeElement,".ui-table-thead > tr:first-child > th").map(function(e){return t.push(s.DomHandler.getOuterWidth(e))}),e.columnWidths=t.join(","),"expand"===this.columnResizeMode&&(e.tableWidth=this.scrollable?s.DomHandler.findSingle(this.containerViewChild.nativeElement,".ui-table-scrollable-header-table").style.width:s.DomHandler.getOuterWidth(this.tableViewChild.nativeElement)+"px")},e.prototype.restoreColumnWidths=function(){if(this.columnWidthsState){var e=this.columnWidthsState.split(",");if("expand"===this.columnResizeMode&&this.tableWidthState)if(this.scrollable){var t=s.DomHandler.findSingle(this.containerViewChild.nativeElement,".ui-table-scrollable-body-table"),n=s.DomHandler.findSingle(this.containerViewChild.nativeElement,".ui-table-scrollable-header-table"),l=s.DomHandler.findSingle(this.containerViewChild.nativeElement,".ui-table-scrollable-footer-table");t.style.width=this.tableWidthState,n.style.width=this.tableWidthState,l&&(l.style.width=this.tableWidthState)}else this.tableViewChild.nativeElement.style.width=this.tableWidthState,this.containerViewChild.nativeElement.style.width=this.tableWidthState;if(this.scrollable){var i=s.DomHandler.find(this.containerViewChild.nativeElement,".ui-table-scrollable-header-table > colgroup > col"),o=s.DomHandler.find(this.containerViewChild.nativeElement,".ui-table-scrollable-body-table > colgroup > col");i.map(function(t,n){return t.style.width=e[n]+"px"}),o.map(function(t,n){return t.style.width=e[n]+"px"})}else s.DomHandler.find(this.tableViewChild.nativeElement,".ui-table-thead > tr:first-child > th").map(function(t,n){return t.style.width=e[n]+"px"})}},e.prototype.saveColumnOrder=function(e){if(this.columns){var t=[];this.columns.map(function(e){t.push(e.field||e.key)}),e.columnOrder=t}},e.prototype.restoreColumnOrder=function(){var e=this,t=this.getStorage().getItem(this.stateKey);if(t){var n=JSON.parse(t).columnOrder;if(n){var l=[];n.map(function(t){return l.push(e.findColumnByKey(t))}),this.columnOrderStateRestored=!0,this.columns=l}}},e.prototype.findColumnByKey=function(e){if(!this.columns)return null;for(var t=0,n=this.columns;t\n

\n
\n \n
\n
\n \n
\n \n \n
\n \n \n \n \n \n \n \n \n \n
\n
\n\n
\n
\n
\n
\n \n \n
\n \n
\n\n \n\n \n \n \n ',providers:[h]})],e)}();t.Table=f;var g=function(){function e(e){this.dt=e}return l([o.Input("pTableBody"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Input("pTableBodyTemplate"),i("design:type",o.TemplateRef)],e.prototype,"template",void 0),l([o.Component({selector:"[pTableBody]",template:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '})],e)}();t.TableBody=g;var m=function(){function e(e,t,n){var l=this;this.dt=e,this.el=t,this.zone=n,this.subscription=this.dt.tableService.valueSource$.subscribe(function(){l.zone.runOutsideAngular(function(){setTimeout(function(){l.alignScrollBar()},50)})}),this.dt.virtualScroll&&(this.totalRecordsSubscription=this.dt.tableService.totalRecordsSource$.subscribe(function(){l.zone.runOutsideAngular(function(){setTimeout(function(){l.setVirtualScrollerHeight()},50)})})),this.initialized=!1}return Object.defineProperty(e.prototype,"scrollHeight",{get:function(){return this._scrollHeight},set:function(e){this._scrollHeight=e,this.setScrollHeight()},enumerable:!0,configurable:!0}),e.prototype.ngAfterViewChecked=function(){!this.initialized&&this.el.nativeElement.offsetParent&&(this.alignScrollBar(),this.setScrollHeight(),this.initialized=!0)},e.prototype.ngAfterViewInit=function(){var e=this;if(this.frozen){this.scrollBodyViewChild.nativeElement.style.marginBottom=s.DomHandler.calculateScrollbarWidth()+"px";var t=this.el.nativeElement.nextElementSibling;t&&(this.scrollableSiblingBody=s.DomHandler.findSingle(t,".ui-table-scrollable-body"))}else{(this.dt.frozenColumns||this.dt.frozenBodyTemplate)&&s.DomHandler.addClass(this.el.nativeElement,"ui-table-unfrozen-view");var n=this.el.nativeElement.previousElementSibling;n&&(this.frozenSiblingBody=s.DomHandler.findSingle(n,".ui-table-scrollable-body"))}this.bindEvents(),this.setScrollHeight(),this.alignScrollBar(),this.frozen&&(this.columnsSubscription=this.dt.tableService.columnsSource$.subscribe(function(){e.zone.runOutsideAngular(function(){setTimeout(function(){e.setScrollHeight()},50)})})),this.dt.virtualScroll&&this.setVirtualScrollerHeight()},e.prototype.bindEvents=function(){var e=this;this.zone.runOutsideAngular(function(){s.DomHandler.calculateScrollbarWidth(),e.scrollHeaderViewChild&&e.scrollHeaderViewChild.nativeElement&&(e.headerScrollListener=e.onHeaderScroll.bind(e),e.scrollHeaderBoxViewChild.nativeElement.addEventListener("scroll",e.headerScrollListener)),e.scrollFooterViewChild&&e.scrollFooterViewChild.nativeElement&&(e.footerScrollListener=e.onFooterScroll.bind(e),e.scrollFooterViewChild.nativeElement.addEventListener("scroll",e.footerScrollListener)),e.frozen||(e.bodyScrollListener=e.onBodyScroll.bind(e),e.scrollBodyViewChild.nativeElement.addEventListener("scroll",e.bodyScrollListener))})},e.prototype.unbindEvents=function(){this.scrollHeaderViewChild&&this.scrollHeaderViewChild.nativeElement&&this.scrollHeaderBoxViewChild.nativeElement.removeEventListener("scroll",this.headerScrollListener),this.scrollFooterViewChild&&this.scrollFooterViewChild.nativeElement&&this.scrollFooterViewChild.nativeElement.removeEventListener("scroll",this.footerScrollListener),this.scrollBodyViewChild.nativeElement.removeEventListener("scroll",this.bodyScrollListener)},e.prototype.onHeaderScroll=function(e){this.scrollHeaderViewChild.nativeElement.scrollLeft=0},e.prototype.onFooterScroll=function(e){this.scrollFooterViewChild.nativeElement.scrollLeft=0},e.prototype.onBodyScroll=function(e){var t=this;if(this.scrollHeaderViewChild&&this.scrollHeaderViewChild.nativeElement&&(this.scrollHeaderBoxViewChild.nativeElement.style.marginLeft=-1*this.scrollBodyViewChild.nativeElement.scrollLeft+"px"),this.scrollFooterViewChild&&this.scrollFooterViewChild.nativeElement&&(this.scrollFooterBoxViewChild.nativeElement.style.marginLeft=-1*this.scrollBodyViewChild.nativeElement.scrollLeft+"px"),this.frozenSiblingBody&&(this.frozenSiblingBody.scrollTop=this.scrollBodyViewChild.nativeElement.scrollTop),this.dt.virtualScroll){var n=s.DomHandler.getOuterHeight(this.scrollBodyViewChild.nativeElement),l=s.DomHandler.getOuterHeight(this.scrollTableViewChild.nativeElement),i=this.dt.virtualRowHeight*this.dt.rows,o=s.DomHandler.getOuterHeight(this.virtualScrollerViewChild.nativeElement)/i||1,r=this.scrollTableViewChild.nativeElement.style.top||"0";if(this.scrollBodyViewChild.nativeElement.scrollTop+n>parseFloat(r)+l||this.scrollBodyViewChild.nativeElement.scrollTops.DomHandler.getOuterHeight(this.scrollBodyViewChild.nativeElement)},e.prototype.alignScrollBar=function(){if(!this.frozen){var e=this.hasVerticalOverflow()?s.DomHandler.calculateScrollbarWidth():0;this.scrollHeaderBoxViewChild.nativeElement.style.marginRight=e+"px",this.scrollFooterBoxViewChild&&this.scrollFooterBoxViewChild.nativeElement&&(this.scrollFooterBoxViewChild.nativeElement.style.marginRight=e+"px")}this.initialized=!1},e.prototype.ngOnDestroy=function(){this.unbindEvents(),this.frozenSiblingBody=null,this.subscription&&this.subscription.unsubscribe(),this.totalRecordsSubscription&&this.totalRecordsSubscription.unsubscribe(),this.columnsSubscription&&this.columnsSubscription.unsubscribe(),this.initialized=!1},l([o.Input("pScrollableView"),i("design:type",Array)],e.prototype,"columns",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"frozen",void 0),l([o.ViewChild("scrollHeader"),i("design:type",o.ElementRef)],e.prototype,"scrollHeaderViewChild",void 0),l([o.ViewChild("scrollHeaderBox"),i("design:type",o.ElementRef)],e.prototype,"scrollHeaderBoxViewChild",void 0),l([o.ViewChild("scrollBody"),i("design:type",o.ElementRef)],e.prototype,"scrollBodyViewChild",void 0),l([o.ViewChild("scrollTable"),i("design:type",o.ElementRef)],e.prototype,"scrollTableViewChild",void 0),l([o.ViewChild("scrollFooter"),i("design:type",o.ElementRef)],e.prototype,"scrollFooterViewChild",void 0),l([o.ViewChild("scrollFooterBox"),i("design:type",o.ElementRef)],e.prototype,"scrollFooterBoxViewChild",void 0),l([o.ViewChild("virtualScroller"),i("design:type",o.ElementRef)],e.prototype,"virtualScrollerViewChild",void 0),l([o.Input(),i("design:type",String),i("design:paramtypes",[String])],e.prototype,"scrollHeight",null),l([o.Component({selector:"[pScrollableView]",template:'\n
\n
\n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n \n \n \n
\n
\n
\n \n '})],e)}();t.ScrollableView=m;var v=function(){function e(e){var t=this;this.dt=e,this.isEnabled()&&(this.subscription=this.dt.tableService.sortSource$.subscribe(function(e){t.updateSortState()}))}return e.prototype.ngOnInit=function(){this.isEnabled()&&this.updateSortState()},e.prototype.updateSortState=function(){this.sorted=this.dt.isSorted(this.field)},e.prototype.onClick=function(e){this.isEnabled()&&(this.updateSortState(),this.dt.sort({originalEvent:e,field:this.field}),s.DomHandler.clearSelection())},e.prototype.onEnterKey=function(e){this.onClick(e)},e.prototype.isEnabled=function(){return!0!==this.pSortableColumnDisabled},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input("pSortableColumn"),i("design:type",String)],e.prototype,"field",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pSortableColumnDisabled",void 0),l([o.HostListener("click",["$event"]),i("design:type",Function),i("design:paramtypes",[MouseEvent]),i("design:returntype",void 0)],e.prototype,"onClick",null),l([o.HostListener("keydown.enter",["$event"]),i("design:type",Function),i("design:paramtypes",[MouseEvent]),i("design:returntype",void 0)],e.prototype,"onEnterKey",null),l([o.Directive({selector:"[pSortableColumn]",host:{"[class.ui-sortable-column]":"isEnabled()","[class.ui-state-highlight]":"sorted","[attr.tabindex]":'isEnabled() ? "0" : null'}})],e)}();t.SortableColumn=v;var y=function(){function e(e){var t=this;this.dt=e,this.subscription=this.dt.tableService.sortSource$.subscribe(function(e){t.updateSortState()})}return e.prototype.ngOnInit=function(){this.updateSortState()},e.prototype.onClick=function(e){e.preventDefault()},e.prototype.updateSortState=function(){if("single"===this.dt.sortMode)this.sortOrder=this.dt.isSorted(this.field)?this.dt.sortOrder:0;else if("multiple"===this.dt.sortMode){var e=this.dt.getSortMeta(this.field);this.sortOrder=e?e.order:0}},Object.defineProperty(e.prototype,"ariaText",{get:function(){var e;switch(this.sortOrder){case 1:e=this.ariaLabelAsc;break;case-1:e=this.ariaLabelDesc;break;default:e=this.ariaLabel}return e},enumerable:!0,configurable:!0}),e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input(),i("design:type",String)],e.prototype,"field",void 0),l([o.Input(),i("design:type",String)],e.prototype,"ariaLabel",void 0),l([o.Input(),i("design:type",String)],e.prototype,"ariaLabelDesc",void 0),l([o.Input(),i("design:type",String)],e.prototype,"ariaLabelAsc",void 0),l([o.Component({selector:"p-sortIcon",template:"\n \n "})],e)}();t.SortIcon=y;var b=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.isEnabled()&&(this.subscription=this.dt.tableService.selectionSource$.subscribe(function(){n.selected=n.dt.isSelected(n.data)}))}return e.prototype.ngOnInit=function(){this.isEnabled()&&(this.selected=this.dt.isSelected(this.data))},e.prototype.onClick=function(e){this.isEnabled()&&this.dt.handleRowClick({originalEvent:e,rowData:this.data,rowIndex:this.index})},e.prototype.onTouchEnd=function(e){this.isEnabled()&&this.dt.handleRowTouchEnd(e)},e.prototype.onKeyDown=function(e){if(this.isEnabled()){var t=e.target;switch(e.which){case 40:var n=this.findNextSelectableRow(t);n&&n.focus(),e.preventDefault();break;case 38:var l=this.findPrevSelectableRow(t);l&&l.focus(),e.preventDefault();break;case 13:this.dt.handleRowClick({originalEvent:e,rowData:this.data,rowIndex:this.index})}}},e.prototype.findNextSelectableRow=function(e){var t=e.nextElementSibling;return t?s.DomHandler.hasClass(t,"ui-selectable-row")?t:this.findNextSelectableRow(t):null},e.prototype.findPrevSelectableRow=function(e){var t=e.previousElementSibling;return t?s.DomHandler.hasClass(t,"ui-selectable-row")?t:this.findPrevSelectableRow(t):null},e.prototype.isEnabled=function(){return!0!==this.pSelectableRowDisabled},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input("pSelectableRow"),i("design:type",Object)],e.prototype,"data",void 0),l([o.Input("pSelectableRowIndex"),i("design:type",Number)],e.prototype,"index",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pSelectableRowDisabled",void 0),l([o.HostListener("click",["$event"]),i("design:type",Function),i("design:paramtypes",[Event]),i("design:returntype",void 0)],e.prototype,"onClick",null),l([o.HostListener("touchend",["$event"]),i("design:type",Function),i("design:paramtypes",[Event]),i("design:returntype",void 0)],e.prototype,"onTouchEnd",null),l([o.HostListener("keydown",["$event"]),i("design:type",Function),i("design:paramtypes",[KeyboardEvent]),i("design:returntype",void 0)],e.prototype,"onKeyDown",null),l([o.Directive({selector:"[pSelectableRow]",host:{"[class.ui-selectable-row]":"isEnabled()","[class.ui-state-highlight]":"selected","[attr.tabindex]":"isEnabled() ? 0 : undefined"}})],e)}();t.SelectableRow=b;var C=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.isEnabled()&&(this.subscription=this.dt.tableService.selectionSource$.subscribe(function(){n.selected=n.dt.isSelected(n.data)}))}return e.prototype.ngOnInit=function(){this.isEnabled()&&(this.selected=this.dt.isSelected(this.data))},e.prototype.onClick=function(e){this.isEnabled()&&this.dt.handleRowClick({originalEvent:e,rowData:this.data,rowIndex:this.index})},e.prototype.isEnabled=function(){return!0!==this.pSelectableRowDisabled},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input("pSelectableRowDblClick"),i("design:type",Object)],e.prototype,"data",void 0),l([o.Input("pSelectableRowIndex"),i("design:type",Number)],e.prototype,"index",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pSelectableRowDisabled",void 0),l([o.HostListener("dblclick",["$event"]),i("design:type",Function),i("design:paramtypes",[Event]),i("design:returntype",void 0)],e.prototype,"onClick",null),l([o.Directive({selector:"[pSelectableRowDblClick]",host:{"[class.ui-state-highlight]":"selected"}})],e)}();t.SelectableRowDblClick=C;var w=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.isEnabled()&&(this.subscription=this.dt.tableService.contextMenuSource$.subscribe(function(e){n.selected=n.dt.equals(n.data,e)}))}return e.prototype.onContextMenu=function(e){this.isEnabled()&&(this.dt.handleRowRightClick({originalEvent:e,rowData:this.data}),e.preventDefault())},e.prototype.isEnabled=function(){return!0!==this.pContextMenuRowDisabled},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input("pContextMenuRow"),i("design:type",Object)],e.prototype,"data",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pContextMenuRowDisabled",void 0),l([o.HostListener("contextmenu",["$event"]),i("design:type",Function),i("design:paramtypes",[Event]),i("design:returntype",void 0)],e.prototype,"onContextMenu",null),l([o.Directive({selector:"[pContextMenuRow]",host:{"[class.ui-contextmenu-selected]":"selected"}})],e)}();t.ContextMenuRow=w;var _=function(){function e(e){this.dt=e}return e.prototype.onClick=function(e){this.isEnabled()&&(this.dt.toggleRow(this.data,e),e.preventDefault())},e.prototype.isEnabled=function(){return!0!==this.pRowTogglerDisabled},l([o.Input("pRowToggler"),i("design:type",Object)],e.prototype,"data",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pRowTogglerDisabled",void 0),l([o.HostListener("click",["$event"]),i("design:type",Function),i("design:paramtypes",[Event]),i("design:returntype",void 0)],e.prototype,"onClick",null),l([o.Directive({selector:"[pRowToggler]"})],e)}();t.RowToggler=_;var x=function(){function e(e,t,n){this.dt=e,this.el=t,this.zone=n}return e.prototype.ngAfterViewInit=function(){var e=this;this.isEnabled()&&(s.DomHandler.addClass(this.el.nativeElement,"ui-resizable-column"),this.resizer=document.createElement("span"),this.resizer.className="ui-column-resizer ui-clickable",this.el.nativeElement.appendChild(this.resizer),this.zone.runOutsideAngular(function(){e.resizerMouseDownListener=e.onMouseDown.bind(e),e.resizer.addEventListener("mousedown",e.resizerMouseDownListener)}))},e.prototype.bindDocumentEvents=function(){var e=this;this.zone.runOutsideAngular(function(){e.documentMouseMoveListener=e.onDocumentMouseMove.bind(e),document.addEventListener("mousemove",e.documentMouseMoveListener),e.documentMouseUpListener=e.onDocumentMouseUp.bind(e),document.addEventListener("mouseup",e.documentMouseUpListener)})},e.prototype.unbindDocumentEvents=function(){this.documentMouseMoveListener&&(document.removeEventListener("mousemove",this.documentMouseMoveListener),this.documentMouseMoveListener=null),this.documentMouseUpListener&&(document.removeEventListener("mouseup",this.documentMouseUpListener),this.documentMouseUpListener=null)},e.prototype.onMouseDown=function(e){this.dt.onColumnResizeBegin(e),this.bindDocumentEvents()},e.prototype.onDocumentMouseMove=function(e){this.dt.onColumnResize(e)},e.prototype.onDocumentMouseUp=function(e){this.dt.onColumnResizeEnd(e,this.el.nativeElement),this.unbindDocumentEvents()},e.prototype.isEnabled=function(){return!0!==this.pResizableColumnDisabled},e.prototype.ngOnDestroy=function(){this.resizerMouseDownListener&&this.resizer.removeEventListener("mousedown",this.resizerMouseDownListener),this.unbindDocumentEvents()},l([o.Input(),i("design:type",Boolean)],e.prototype,"pResizableColumnDisabled",void 0),l([o.Directive({selector:"[pResizableColumn]"})],e)}();t.ResizableColumn=x;var S=function(){function e(e,t,n){this.dt=e,this.el=t,this.zone=n}return e.prototype.ngAfterViewInit=function(){this.isEnabled()&&this.bindEvents()},e.prototype.bindEvents=function(){var e=this;this.zone.runOutsideAngular(function(){e.mouseDownListener=e.onMouseDown.bind(e),e.el.nativeElement.addEventListener("mousedown",e.mouseDownListener),e.dragStartListener=e.onDragStart.bind(e),e.el.nativeElement.addEventListener("dragstart",e.dragStartListener),e.dragOverListener=e.onDragEnter.bind(e),e.el.nativeElement.addEventListener("dragover",e.dragOverListener),e.dragEnterListener=e.onDragEnter.bind(e),e.el.nativeElement.addEventListener("dragenter",e.dragEnterListener),e.dragLeaveListener=e.onDragLeave.bind(e),e.el.nativeElement.addEventListener("dragleave",e.dragLeaveListener)})},e.prototype.unbindEvents=function(){this.mouseDownListener&&(document.removeEventListener("mousedown",this.mouseDownListener),this.mouseDownListener=null),this.dragOverListener&&(document.removeEventListener("dragover",this.dragOverListener),this.dragOverListener=null),this.dragEnterListener&&(document.removeEventListener("dragenter",this.dragEnterListener),this.dragEnterListener=null),this.dragEnterListener&&(document.removeEventListener("dragenter",this.dragEnterListener),this.dragEnterListener=null),this.dragLeaveListener&&(document.removeEventListener("dragleave",this.dragLeaveListener),this.dragLeaveListener=null)},e.prototype.onMouseDown=function(e){this.el.nativeElement.draggable="INPUT"!==e.target.nodeName&&"TEXTAREA"!==e.target.nodeName&&!s.DomHandler.hasClass(e.target,"ui-column-resizer")},e.prototype.onDragStart=function(e){this.dt.onColumnDragStart(e,this.el.nativeElement)},e.prototype.onDragOver=function(e){e.preventDefault()},e.prototype.onDragEnter=function(e){this.dt.onColumnDragEnter(e,this.el.nativeElement)},e.prototype.onDragLeave=function(e){this.dt.onColumnDragLeave(e)},e.prototype.onDrop=function(e){this.isEnabled()&&this.dt.onColumnDrop(e,this.el.nativeElement)},e.prototype.isEnabled=function(){return!0!==this.pReorderableColumnDisabled},e.prototype.ngOnDestroy=function(){this.unbindEvents()},l([o.Input(),i("design:type",Boolean)],e.prototype,"pReorderableColumnDisabled",void 0),l([o.HostListener("drop",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"onDrop",null),l([o.Directive({selector:"[pReorderableColumn]"})],e)}();t.ReorderableColumn=S;var E=function(){function e(e,t,n){this.dt=e,this.el=t,this.zone=n}return e.prototype.ngAfterViewInit=function(){this.isEnabled()&&s.DomHandler.addClass(this.el.nativeElement,"ui-editable-column")},e.prototype.onClick=function(e){if(this.isEnabled())if(this.dt.editingCellClick=!0,this.dt.editingCell){if(this.dt.editingCell!==this.el.nativeElement){if(!this.dt.isEditingCellValid())return;s.DomHandler.removeClass(this.dt.editingCell,"ui-editing-cell"),this.openCell()}}else this.openCell()},e.prototype.openCell=function(){var e=this;this.dt.updateEditingCell(this.el.nativeElement),s.DomHandler.addClass(this.el.nativeElement,"ui-editing-cell"),this.dt.onEditInit.emit({field:this.field,data:this.data}),this.zone.runOutsideAngular(function(){setTimeout(function(){var t=s.DomHandler.findSingle(e.el.nativeElement,"input, textarea");t&&t.focus()},50)})},e.prototype.closeEditingCell=function(){s.DomHandler.removeClass(this.dt.editingCell,"ui-editing-cell"),this.dt.editingCell=null,this.dt.unbindDocumentEditListener()},e.prototype.onKeyDown=function(e){this.isEnabled()&&(13==e.keyCode?(this.dt.isEditingCellValid()&&(this.closeEditingCell(),this.dt.onEditComplete.emit({field:this.field,data:this.data})),e.preventDefault()):27==e.keyCode?(this.dt.isEditingCellValid()&&(this.closeEditingCell(),this.dt.onEditCancel.emit({field:this.field,data:this.data})),e.preventDefault()):9==e.keyCode&&(this.dt.onEditComplete.emit({field:this.field,data:this.data}),e.shiftKey?this.moveToPreviousCell(e):this.moveToNextCell(e)))},e.prototype.findCell=function(e){if(e){for(var t=e;t&&!s.DomHandler.hasClass(t,"ui-editing-cell");)t=t.parentElement;return t}return null},e.prototype.moveToPreviousCell=function(e){var t=this.findCell(e.target),n=this.findPreviousEditableColumn(t);n&&(s.DomHandler.invokeElementMethod(e.target,"blur"),s.DomHandler.invokeElementMethod(n,"click"),e.preventDefault())},e.prototype.moveToNextCell=function(e){var t=this.findCell(e.target),n=this.findNextEditableColumn(t);n&&(s.DomHandler.invokeElementMethod(e.target,"blur"),s.DomHandler.invokeElementMethod(n,"click"),e.preventDefault())},e.prototype.findPreviousEditableColumn=function(e){var t=e.previousElementSibling;if(!t){var n=e.parentElement.previousElementSibling;n&&(t=n.lastElementChild)}return t?s.DomHandler.hasClass(t,"ui-editable-column")?t:this.findPreviousEditableColumn(t):null},e.prototype.findNextEditableColumn=function(e){var t=e.nextElementSibling;if(!t){var n=e.parentElement.nextElementSibling;n&&(t=n.firstElementChild)}return t?s.DomHandler.hasClass(t,"ui-editable-column")?t:this.findNextEditableColumn(t):null},e.prototype.isEnabled=function(){return!0!==this.pEditableColumnDisabled},l([o.Input("pEditableColumn"),i("design:type",Object)],e.prototype,"data",void 0),l([o.Input("pEditableColumnField"),i("design:type",Object)],e.prototype,"field",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pEditableColumnDisabled",void 0),l([o.HostListener("click",["$event"]),i("design:type",Function),i("design:paramtypes",[MouseEvent]),i("design:returntype",void 0)],e.prototype,"onClick",null),l([o.HostListener("keydown",["$event"]),i("design:type",Function),i("design:paramtypes",[KeyboardEvent]),i("design:returntype",void 0)],e.prototype,"onKeyDown",null),l([o.Directive({selector:"[pEditableColumn]"})],e)}();t.EditableColumn=E;var I=function(){function e(e,t){this.dt=e,this.editableColumn=t}return e.prototype.ngAfterContentInit=function(){var e=this;this.templates.forEach(function(t){switch(t.getType()){case"input":e.inputTemplate=t.template;break;case"output":e.outputTemplate=t.template}})},l([o.ContentChildren(u.PrimeTemplate),i("design:type",o.QueryList)],e.prototype,"templates",void 0),l([o.Component({selector:"p-cellEditor",template:'\n \n \n \n \n \n \n '})],e)}();t.CellEditor=I;var O=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.subscription=this.dt.tableService.selectionSource$.subscribe(function(){n.checked=n.dt.isSelected(n.value)})}return e.prototype.ngOnInit=function(){this.checked=this.dt.isSelected(this.value)},e.prototype.onClick=function(e){this.disabled||this.dt.toggleRowWithRadio({originalEvent:e,rowIndex:this.index},this.value),s.DomHandler.clearSelection()},e.prototype.onFocus=function(){s.DomHandler.addClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.onBlur=function(){s.DomHandler.removeClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input(),i("design:type",Boolean)],e.prototype,"disabled",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"value",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"index",void 0),l([o.ViewChild("box"),i("design:type",o.ElementRef)],e.prototype,"boxViewChild",void 0),l([o.Component({selector:"p-tableRadioButton",template:'\n
\n
\n \n
\n
\n \n
\n
\n '})],e)}();t.TableRadioButton=O;var T=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.subscription=this.dt.tableService.selectionSource$.subscribe(function(){n.checked=n.dt.isSelected(n.value)})}return e.prototype.ngOnInit=function(){this.checked=this.dt.isSelected(this.value)},e.prototype.onClick=function(e){this.disabled||this.dt.toggleRowWithCheckbox({originalEvent:e,rowIndex:this.index},this.value),s.DomHandler.clearSelection()},e.prototype.onFocus=function(){s.DomHandler.addClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.onBlur=function(){s.DomHandler.removeClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.ngOnDestroy=function(){this.subscription&&this.subscription.unsubscribe()},l([o.Input(),i("design:type",Boolean)],e.prototype,"disabled",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"value",void 0),l([o.Input(),i("design:type",Number)],e.prototype,"index",void 0),l([o.ViewChild("box"),i("design:type",o.ElementRef)],e.prototype,"boxViewChild",void 0),l([o.Component({selector:"p-tableCheckbox",template:'\n
\n
\n \n
\n
\n \n
\n
\n '})],e)}();t.TableCheckbox=T;var D=function(){function e(e,t){var n=this;this.dt=e,this.tableService=t,this.valueChangeSubscription=this.dt.tableService.valueSource$.subscribe(function(){n.checked=n.updateCheckedState()}),this.selectionChangeSubscription=this.dt.tableService.selectionSource$.subscribe(function(){n.checked=n.updateCheckedState()})}return e.prototype.ngOnInit=function(){this.checked=this.updateCheckedState()},e.prototype.onClick=function(e,t){this.disabled||this.dt.value&&this.dt.value.length>0&&this.dt.toggleRowsWithCheckbox(e,!t),s.DomHandler.clearSelection()},e.prototype.onFocus=function(){s.DomHandler.addClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.onBlur=function(){s.DomHandler.removeClass(this.boxViewChild.nativeElement,"ui-state-focus")},e.prototype.isDisabled=function(){return this.disabled||!this.dt.value||!this.dt.value.length},e.prototype.ngOnDestroy=function(){this.selectionChangeSubscription&&this.selectionChangeSubscription.unsubscribe(),this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe()},e.prototype.updateCheckedState=function(){var e;return this.dt.filteredValue?(e=this.dt.filteredValue)&&e.length>0&&this.dt.selection&&this.dt.selection.length>0&&this.isAllFilteredValuesChecked():(e=this.dt.value)&&e.length>0&&this.dt.selection&&this.dt.selection.length>0&&this.dt.selection.length===e.length},e.prototype.isAllFilteredValuesChecked=function(){if(this.dt.filteredValue){for(var e=0,t=this.dt.filteredValue;e\n
\n \n
\n
\n \n
\n \n '})],e)}();t.TableHeaderCheckbox=D;var k=function(){function e(e){this.el=e}return e.prototype.ngAfterViewInit=function(){s.DomHandler.addClass(this.el.nativeElement,"ui-table-reorderablerow-handle")},l([o.Input("pReorderableRowHandle"),i("design:type",Number)],e.prototype,"index",void 0),l([o.Directive({selector:"[pReorderableRowHandle]"})],e)}();t.ReorderableRowHandle=k;var R=function(){function e(e,t,n){this.dt=e,this.el=t,this.zone=n}return e.prototype.ngAfterViewInit=function(){this.isEnabled()&&(this.el.nativeElement.droppable=!0,this.bindEvents())},e.prototype.bindEvents=function(){var e=this;this.zone.runOutsideAngular(function(){e.mouseDownListener=e.onMouseDown.bind(e),e.el.nativeElement.addEventListener("mousedown",e.mouseDownListener),e.dragStartListener=e.onDragStart.bind(e),e.el.nativeElement.addEventListener("dragstart",e.dragStartListener),e.dragEndListener=e.onDragEnd.bind(e),e.el.nativeElement.addEventListener("dragend",e.dragEndListener),e.dragOverListener=e.onDragOver.bind(e),e.el.nativeElement.addEventListener("dragover",e.dragOverListener),e.dragLeaveListener=e.onDragLeave.bind(e),e.el.nativeElement.addEventListener("dragleave",e.dragLeaveListener)})},e.prototype.unbindEvents=function(){this.mouseDownListener&&(document.removeEventListener("mousedown",this.mouseDownListener),this.mouseDownListener=null),this.dragStartListener&&(document.removeEventListener("dragstart",this.dragStartListener),this.dragStartListener=null),this.dragEndListener&&(document.removeEventListener("dragend",this.dragEndListener),this.dragEndListener=null),this.dragOverListener&&(document.removeEventListener("dragover",this.dragOverListener),this.dragOverListener=null),this.dragLeaveListener&&(document.removeEventListener("dragleave",this.dragLeaveListener),this.dragLeaveListener=null)},e.prototype.onMouseDown=function(e){this.el.nativeElement.draggable=!!s.DomHandler.hasClass(e.target,"ui-table-reorderablerow-handle")},e.prototype.onDragStart=function(e){this.dt.onRowDragStart(e,this.index)},e.prototype.onDragEnd=function(e){this.dt.onRowDragEnd(e),this.el.nativeElement.draggable=!1},e.prototype.onDragOver=function(e){this.dt.onRowDragOver(e,this.index,this.el.nativeElement),e.preventDefault()},e.prototype.onDragLeave=function(e){this.dt.onRowDragLeave(e,this.el.nativeElement)},e.prototype.isEnabled=function(){return!0!==this.pReorderableRowDisabled},e.prototype.onDrop=function(e){this.isEnabled()&&this.dt.rowDragging&&this.dt.onRowDrop(e,this.el.nativeElement),e.preventDefault()},l([o.Input("pReorderableRow"),i("design:type",Number)],e.prototype,"index",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"pReorderableRowDisabled",void 0),l([o.HostListener("drop",["$event"]),i("design:type",Function),i("design:paramtypes",[Object]),i("design:returntype",void 0)],e.prototype,"onDrop",null),l([o.Directive({selector:"[pReorderableRow]"})],e)}();t.ReorderableRow=R,t.TableModule=function(){return l([o.NgModule({imports:[r.CommonModule,a.PaginatorModule],exports:[f,u.SharedModule,v,b,_,w,x,S,E,I,y,O,T,D,k,R,C],declarations:[f,v,b,_,w,x,S,E,I,g,m,y,O,T,D,k,R,C]})],function(){})}()},DKTb:function(e,t,n){"use strict";function l(e){setTimeout(function(){throw e},0)}n.d(t,"a",function(){return l})},DqLj:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=n("sdDj");t.DomHandler=l.DomHandler;var i=n("B58V");t.TreeDragDropService=i.TreeDragDropService;var o=n("oygf");t.ConfirmationService=o.ConfirmationService;var r=n("4Vzq");t.MessageService=r.MessageService;var u=n("6xRK");t.DialogService=u.DialogService;var a=n("V3HQ");t.DynamicDialogConfig=a.DynamicDialogConfig;var s=n("RWz4");t.DynamicDialogRef=s.DynamicDialogRef},DtyJ:function(e,t,n){"use strict";n.r(t),n.d(t,"Observable",function(){return l.a}),n.d(t,"ConnectableObservable",function(){return i.a}),n.d(t,"GroupedObservable",function(){return s}),n.d(t,"observable",function(){return d.a}),n.d(t,"Subject",function(){return a.a}),n.d(t,"BehaviorSubject",function(){return p.a}),n.d(t,"ReplaySubject",function(){return h.a}),n.d(t,"AsyncSubject",function(){return f}),n.d(t,"asapScheduler",function(){return g.a}),n.d(t,"asyncScheduler",function(){return m.a}),n.d(t,"queueScheduler",function(){return v.a}),n.d(t,"animationFrameScheduler",function(){return y.a}),n.d(t,"VirtualTimeScheduler",function(){return C}),n.d(t,"VirtualAction",function(){return w}),n.d(t,"Scheduler",function(){return _.a}),n.d(t,"Subscription",function(){return u.a}),n.d(t,"Subscriber",function(){return r.a}),n.d(t,"Notification",function(){return x.a}),n.d(t,"NotificationKind",function(){return x.b}),n.d(t,"pipe",function(){return S.a}),n.d(t,"noop",function(){return E.a}),n.d(t,"identity",function(){return I.a}),n.d(t,"isObservable",function(){return O.a}),n.d(t,"ArgumentOutOfRangeError",function(){return T.a}),n.d(t,"EmptyError",function(){return D.a}),n.d(t,"ObjectUnsubscribedError",function(){return k.a}),n.d(t,"UnsubscriptionError",function(){return R.a}),n.d(t,"TimeoutError",function(){return N}),n.d(t,"bindCallback",function(){return V}),n.d(t,"bindNodeCallback",function(){return B}),n.d(t,"combineLatest",function(){return G.a}),n.d(t,"concat",function(){return W.a}),n.d(t,"defer",function(){return q.a}),n.d(t,"empty",function(){return K.b}),n.d(t,"forkJoin",function(){return Y.a}),n.d(t,"from",function(){return Z.a}),n.d(t,"fromEvent",function(){return Q.a}),n.d(t,"fromEventPattern",function(){return J}),n.d(t,"generate",function(){return ee}),n.d(t,"iif",function(){return ne}),n.d(t,"interval",function(){return ie}),n.d(t,"merge",function(){return re.a}),n.d(t,"never",function(){return ae}),n.d(t,"of",function(){return se.a}),n.d(t,"onErrorResumeNext",function(){return ce}),n.d(t,"pairs",function(){return de}),n.d(t,"partition",function(){return me}),n.d(t,"race",function(){return Ce}),n.d(t,"range",function(){return xe}),n.d(t,"throwError",function(){return Ee.a}),n.d(t,"timer",function(){return Ie.a}),n.d(t,"using",function(){return Oe}),n.d(t,"zip",function(){return De}),n.d(t,"scheduled",function(){return Ae.a}),n.d(t,"EMPTY",function(){return K.a}),n.d(t,"NEVER",function(){return ue}),n.d(t,"config",function(){return Pe.a});var l=n("6blF"),i=n("KhEm"),o=n("mrSG"),r=n("FFOo"),u=n("pugT"),a=n("K9Ia"),s=function(e){function t(t,n,l){var i=e.call(this)||this;return i.key=t,i.groupSubject=n,i.refCountSubscription=l,i}return o.__extends(t,e),t.prototype._subscribe=function(e){var t=new u.a,n=this.refCountSubscription,l=this.groupSubject;return n&&!n.closed&&t.add(new c(n)),t.add(l.subscribe(e)),t},t}(l.a),c=function(e){function t(t){var n=e.call(this)||this;return n.parent=t,t.count++,n}return o.__extends(t,e),t.prototype.unsubscribe=function(){var t=this.parent;t.closed||this.closed||(e.prototype.unsubscribe.call(this),t.count-=1,0===t.count&&t.attemptedToUnsubscribe&&t.unsubscribe())},t}(u.a),d=n("xTla"),p=n("26FU"),h=n("S5bw"),f=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.value=null,t.hasNext=!1,t.hasCompleted=!1,t}return o.__extends(t,e),t.prototype._subscribe=function(t){return this.hasError?(t.error(this.thrownError),u.a.EMPTY):this.hasCompleted&&this.hasNext?(t.next(this.value),t.complete(),u.a.EMPTY):e.prototype._subscribe.call(this,t)},t.prototype.next=function(e){this.hasCompleted||(this.value=e,this.hasNext=!0)},t.prototype.error=function(t){this.hasCompleted||e.prototype.error.call(this,t)},t.prototype.complete=function(){this.hasCompleted=!0,this.hasNext&&e.prototype.next.call(this,this.value),e.prototype.complete.call(this)},t}(a.a),g=n("KQya"),m=n("T1DM"),v=n("zo3G"),y=n("tHPV"),b=n("h9Dq"),C=function(e){function t(t,n){void 0===t&&(t=w),void 0===n&&(n=Number.POSITIVE_INFINITY);var l=e.call(this,t,function(){return l.frame})||this;return l.maxFrames=n,l.frame=0,l.index=-1,l}return o.__extends(t,e),t.prototype.flush=function(){for(var e,t,n=this.actions,l=this.maxFrames;(t=n[0])&&t.delay<=l&&(n.shift(),this.frame=t.delay,!(e=t.execute(t.state,t.delay))););if(e){for(;t=n.shift();)t.unsubscribe();throw e}},t.frameTimeFactor=10,t}(n("CS9Q").a),w=function(e){function t(t,n,l){void 0===l&&(l=t.index+=1);var i=e.call(this,t,n)||this;return i.scheduler=t,i.work=n,i.index=l,i.active=!0,i.index=t.index=l,i}return o.__extends(t,e),t.prototype.schedule=function(n,l){if(void 0===l&&(l=0),!this.id)return e.prototype.schedule.call(this,n,l);this.active=!1;var i=new t(this.scheduler,this.work);return this.add(i),i.schedule(n,l)},t.prototype.requestAsyncId=function(e,n,l){void 0===l&&(l=0),this.delay=e.frame+l;var i=e.actions;return i.push(this),i.sort(t.sortActions),!0},t.prototype.recycleAsyncId=function(e,t,n){void 0===n&&(n=0)},t.prototype._execute=function(t,n){if(!0===this.active)return e.prototype._execute.call(this,t,n)},t.sortActions=function(e,t){return e.delay===t.delay?e.index===t.index?0:e.index>t.index?1:-1:e.delay>t.delay?1:-1},t}(b.a),_=n("siIJ"),x=n("60iU"),S=n("y3By"),E=n("+umK"),I=n("mChF"),O=n("zrt+"),T=n("b7mW"),D=n("3fWJ"),k=n("8g8A"),R=n("awvh");function M(){return Error.call(this),this.message="Timeout has occurred",this.name="TimeoutError",this}M.prototype=Object.create(Error.prototype);var N=M,L=n("67Y/"),A=n("1fDf"),P=n("isby"),F=n("nkY7");function V(e,t,n){if(t){if(!Object(F.a)(t))return function(){for(var l=[],i=0;i=t){l.complete();break}if(l.next(o++),l.closed)break}})}function Se(e){var t=e.start,n=e.index,l=e.subscriber;n>=e.count?l.complete():(l.next(t),l.closed||(e.index=n+1,e.start=t+1,this.schedule(e)))}var Ee=n("XlPw"),Ie=n("gI3B");function Oe(e,t){return new l.a(function(n){var l,i;try{l=e()}catch(r){return void n.error(r)}try{i=t(l)}catch(r){return void n.error(r)}var o=(i?Object(Z.a)(i):K.a).subscribe(n);return function(){o.unsubscribe(),l&&l.unsubscribe()}})}var Te=n("En8+");function De(){for(var e=[],t=0;tthis.index},e.prototype.hasCompleted=function(){return this.array.length===this.index},e}(),Le=function(e){function t(t,n,l){var i=e.call(this,t)||this;return i.parent=n,i.observable=l,i.stillUnsubscribed=!0,i.buffer=[],i.isComplete=!1,i}return o.__extends(t,e),t.prototype[Te.a]=function(){return this},t.prototype.next=function(){var e=this.buffer;return 0===e.length&&this.isComplete?{value:null,done:!0}:{value:e.shift(),done:!1}},t.prototype.hasValue=function(){return this.buffer.length>0},t.prototype.hasCompleted=function(){return 0===this.buffer.length&&this.isComplete},t.prototype.notifyComplete=function(){this.buffer.length>0?(this.isComplete=!0,this.parent.notifyInactive()):this.destination.complete()},t.prototype.notifyNext=function(e,t,n,l,i){this.buffer.push(t),this.parent.checkIterators()},t.prototype.subscribe=function(e,t){return Object(be.a)(this,this.observable,this,t)},t}(ye.a),Ae=n("i4X3"),Pe=n("iLxQ")},EBtg:function(e,t,n){"use strict";var l=n("2KeD"),i=n("n73p"),o=n("2qMH"),r=n("LJ/p"),u=n("HZF8"),a=n("90cg"),s=n("yRPT"),c=n("0alx");t.scheduled=function(e,t){if(null!=e){if(u.isInteropObservable(e))return l.scheduleObservable(e,t);if(a.isPromise(e))return i.schedulePromise(e,t);if(s.isArrayLike(e))return o.scheduleArray(e,t);if(c.isIterable(e)||"string"==typeof e)return r.scheduleIterable(e,t)}throw new TypeError((null!==e&&typeof e||e)+" is not observable")}},EPYN:function(e,t,n){var l=n("mrSG").__decorate,i=n("mrSG").__metadata;Object.defineProperty(t,"__esModule",{value:!0});var o=n("CcnG"),r=n("Ip0R"),u=n("VSng"),a=function(){return l([o.Component({selector:"p-inplaceDisplay",template:""})],function(){})}();t.InplaceDisplay=a;var s=function(){return l([o.Component({selector:"p-inplaceContent",template:""})],function(){})}();t.InplaceContent=s;var c=function(){function e(){this.onActivate=new o.EventEmitter,this.onDeactivate=new o.EventEmitter}return e.prototype.activate=function(e){this.disabled||(this.active=!0,this.onActivate.emit(e))},e.prototype.deactivate=function(e){this.disabled||(this.active=!1,this.hover=!1,this.onDeactivate.emit(e))},l([o.Input(),i("design:type",Boolean)],e.prototype,"active",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"closable",void 0),l([o.Input(),i("design:type",Boolean)],e.prototype,"disabled",void 0),l([o.Input(),i("design:type",Object)],e.prototype,"style",void 0),l([o.Input(),i("design:type",String)],e.prototype,"styleClass",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onActivate",void 0),l([o.Output(),i("design:type",o.EventEmitter)],e.prototype,"onDeactivate",void 0),l([o.Component({selector:"p-inplace",template:'\n
\n
\n \n
\n
\n \n \n
\n
\n '})],e)}();t.Inplace=c,t.InplaceModule=function(){return l([o.NgModule({imports:[r.CommonModule,u.ButtonModule],exports:[c,a,s,u.ButtonModule],declarations:[c,a,s]})],function(){})}()},EVdn:function(e,t,n){var l;!function(t,n){"use strict";"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(t)}("undefined"!=typeof window?window:this,function(n,i){"use strict";var o=[],r=Object.getPrototypeOf,u=o.slice,a=o.flat?function(e){return o.flat.call(e)}:function(e){return o.concat.apply([],e)},s=o.push,c=o.indexOf,d={},p=d.toString,h=d.hasOwnProperty,f=h.toString,g=f.call(Object),m={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},y=function(e){return null!=e&&e===e.window},b=n.document,C={type:!0,src:!0,nonce:!0,noModule:!0};function w(e,t,n){var l,i,o=(n=n||b).createElement("script");if(o.text=e,t)for(l in C)(i=t[l]||t.getAttribute&&t.getAttribute(l))&&o.setAttribute(l,i);n.head.appendChild(o).parentNode.removeChild(o)}function _(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?d[p.call(e)]||"object":typeof e}var x=function(e,t){return new x.fn.init(e,t)};function S(e){var t=!!e&&"length"in e&&e.length,n=_(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}x.fn=x.prototype={jquery:"3.5.0",constructor:x,length:0,toArray:function(){return u.call(this)},get:function(e){return null==e?u.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return x.each(this,e)},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(u.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(x.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(x.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+F+")"+F+"*"),G=new RegExp(F+"|>"),W=new RegExp(H),q=new RegExp("^"+V+"$"),K={ID:new RegExp("^#("+V+")"),CLASS:new RegExp("^\\.("+V+")"),TAG:new RegExp("^("+V+"|[*])"),ATTR:new RegExp("^"+j),PSEUDO:new RegExp("^"+H),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+F+"*(even|odd|(([+-]|)(\\d*)n|)"+F+"*(?:([+-]|)"+F+"*(\\d+)|))"+F+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+F+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+F+"*((?:-\\d)?\\d*)"+F+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Z=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,X=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+F+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},le=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){p()},re=Ce(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{N.apply(k=L.call(w.childNodes),w.childNodes)}catch(Ee){N={apply:k.length?function(e,t){M.apply(e,L.call(t))}:function(e,t){for(var n=e.length,l=0;e[n++]=t[l++];);e.length=n-1}}}function ue(e,t,l,i){var o,u,s,c,d,f,v,y=t&&t.ownerDocument,w=t?t.nodeType:9;if(l=l||[],"string"!=typeof e||!e||1!==w&&9!==w&&11!==w)return l;if(!i&&(p(t),t=t||h,g)){if(11!==w&&(d=J.exec(e)))if(o=d[1]){if(9===w){if(!(s=t.getElementById(o)))return l;if(s.id===o)return l.push(s),l}else if(y&&(s=y.getElementById(o))&&b(t,s)&&s.id===o)return l.push(s),l}else{if(d[2])return N.apply(l,t.getElementsByTagName(e)),l;if((o=d[3])&&n.getElementsByClassName&&t.getElementsByClassName)return N.apply(l,t.getElementsByClassName(o)),l}if(n.qsa&&!O[e+" "]&&(!m||!m.test(e))&&(1!==w||"object"!==t.nodeName.toLowerCase())){if(v=e,y=t,1===w&&(G.test(e)||$.test(e))){for((y=ee.test(e)&&ve(t.parentNode)||t)===t&&n.scope||((c=t.getAttribute("id"))?c=c.replace(le,ie):t.setAttribute("id",c=C)),u=(f=r(e)).length;u--;)f[u]=(c?"#"+c:":scope")+" "+be(f[u]);v=f.join(",")}try{return N.apply(l,y.querySelectorAll(v)),l}catch(_){O(e,!0)}finally{c===C&&t.removeAttribute("id")}}}return a(e.replace(z,"$1"),t,l,i)}function ae(){var e=[];return function t(n,i){return e.push(n+" ")>l.cacheLength&&delete t[e.shift()],t[n+" "]=i}}function se(e){return e[C]=!0,e}function ce(e){var t=h.createElement("fieldset");try{return!!e(t)}catch(Ee){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function de(e,t){for(var n=e.split("|"),i=n.length;i--;)l.attrHandle[n[i]]=t}function pe(e,t){var n=t&&e,l=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(l)return l;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function he(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function fe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ge(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&re(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function me(e){return se(function(t){return t=+t,se(function(n,l){for(var i,o=e([],n.length,t),r=o.length;r--;)n[i=o[r]]&&(n[i]=!(l[i]=n[i]))})})}function ve(e){return e&&void 0!==e.getElementsByTagName&&e}for(t in n=ue.support={},o=ue.isXML=function(e){var t=(e.ownerDocument||e).documentElement;return!Y.test(e.namespaceURI||t&&t.nodeName||"HTML")},p=ue.setDocument=function(e){var t,i,r=e?e.ownerDocument||e:w;return r!=h&&9===r.nodeType&&r.documentElement?(f=(h=r).documentElement,g=!o(h),w!=h&&(i=h.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",oe,!1):i.attachEvent&&i.attachEvent("onunload",oe)),n.scope=ce(function(e){return f.appendChild(e).appendChild(h.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),n.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ce(function(e){return e.appendChild(h.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=X.test(h.getElementsByClassName),n.getById=ce(function(e){return f.appendChild(e).id=C,!h.getElementsByName||!h.getElementsByName(C).length}),n.getById?(l.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},l.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(l.filter.ID=function(e){var t=e.replace(te,ne);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},l.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n,l,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),l=0;o=i[l++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),l.find.TAG=n.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,l=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&l.push(n);return l}return o},l.find.CLASS=n.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],m=[],(n.qsa=X.test(h.querySelectorAll))&&(ce(function(e){var t;f.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+F+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||m.push("\\["+F+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+C+"-]").length||m.push("~="),(t=h.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||m.push("\\["+F+"*name"+F+"*="+F+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||m.push(":checked"),e.querySelectorAll("a#"+C+"+*").length||m.push(".#.+[+~]"),e.querySelectorAll("\\\f"),m.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=h.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&m.push("name"+F+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),f.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),m.push(",.*:")})),(n.matchesSelector=X.test(y=f.matches||f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ce(function(e){n.disconnectedMatch=y.call(e,"*"),y.call(e,"[s!='']:x"),v.push("!=",H)}),m=m.length&&new RegExp(m.join("|")),v=v.length&&new RegExp(v.join("|")),t=X.test(f.compareDocumentPosition),b=t||X.test(f.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,l=t&&t.parentNode;return e===l||!(!l||1!==l.nodeType||!(n.contains?n.contains(l):e.compareDocumentPosition&&16&e.compareDocumentPosition(l)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},T=t?function(e,t){if(e===t)return d=!0,0;var l=!e.compareDocumentPosition-!t.compareDocumentPosition;return l||(1&(l=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===l?e==h||e.ownerDocument==w&&b(w,e)?-1:t==h||t.ownerDocument==w&&b(w,t)?1:c?A(c,e)-A(c,t):0:4&l?-1:1)}:function(e,t){if(e===t)return d=!0,0;var n,l=0,i=e.parentNode,o=t.parentNode,r=[e],u=[t];if(!i||!o)return e==h?-1:t==h?1:i?-1:o?1:c?A(c,e)-A(c,t):0;if(i===o)return pe(e,t);for(n=e;n=n.parentNode;)r.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;r[l]===u[l];)l++;return l?pe(r[l],u[l]):r[l]==w?-1:u[l]==w?1:0},h):h},ue.matches=function(e,t){return ue(e,null,null,t)},ue.matchesSelector=function(e,t){if(p(e),n.matchesSelector&&g&&!O[t+" "]&&(!v||!v.test(t))&&(!m||!m.test(t)))try{var l=y.call(e,t);if(l||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return l}catch(Ee){O(t,!0)}return ue(t,h,null,[e]).length>0},ue.contains=function(e,t){return(e.ownerDocument||e)!=h&&p(e),b(e,t)},ue.attr=function(e,t){(e.ownerDocument||e)!=h&&p(e);var i=l.attrHandle[t.toLowerCase()],o=i&&D.call(l.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},ue.escape=function(e){return(e+"").replace(le,ie)},ue.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ue.uniqueSort=function(e){var t,l=[],i=0,o=0;if(d=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(T),d){for(;t=e[o++];)t===e[o]&&(i=l.push(o));for(;i--;)e.splice(l[i],1)}return c=null,e},i=ue.getText=function(e){var t,n="",l=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[l++];)n+=i(t);return n},(l=ue.selectors={cacheLength:50,createPseudo:se,match:K,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ue.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ue.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return K.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&W.test(n)&&(t=r(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=S[e+" "];return t||(t=new RegExp("(^|"+F+")"+e+"("+F+"|$)"))&&S(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(l){var i=ue.attr(l,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(B," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,l,i){var o="nth"!==e.slice(0,3),r="last"!==e.slice(-4),u="of-type"===t;return 1===l&&0===i?function(e){return!!e.parentNode}:function(t,n,a){var s,c,d,p,h,f,g=o!==r?"nextSibling":"previousSibling",m=t.parentNode,v=u&&t.nodeName.toLowerCase(),y=!a&&!u,b=!1;if(m){if(o){for(;g;){for(p=t;p=p[g];)if(u?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;f=g="only"===e&&!f&&"nextSibling"}return!0}if(f=[r?m.firstChild:m.lastChild],r&&y){for(b=(h=(s=(c=(d=(p=m)[C]||(p[C]={}))[p.uniqueID]||(d[p.uniqueID]={}))[e]||[])[0]===_&&s[1])&&s[2],p=h&&m.childNodes[h];p=++h&&p&&p[g]||(b=h=0)||f.pop();)if(1===p.nodeType&&++b&&p===t){c[e]=[_,h,b];break}}else if(y&&(b=h=(s=(c=(d=(p=t)[C]||(p[C]={}))[p.uniqueID]||(d[p.uniqueID]={}))[e]||[])[0]===_&&s[1]),!1===b)for(;(p=++h&&p&&p[g]||(b=h=0)||f.pop())&&((u?p.nodeName.toLowerCase()!==v:1!==p.nodeType)||!++b||(y&&((c=(d=p[C]||(p[C]={}))[p.uniqueID]||(d[p.uniqueID]={}))[e]=[_,b]),p!==t)););return(b-=i)===l||b%l==0&&b/l>=0}}},PSEUDO:function(e,t){var n,i=l.pseudos[e]||l.setFilters[e.toLowerCase()]||ue.error("unsupported pseudo: "+e);return i[C]?i(t):i.length>1?(n=[e,e,"",t],l.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){for(var l,o=i(e,t),r=o.length;r--;)e[l=A(e,o[r])]=!(n[l]=o[r])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],l=u(e.replace(z,"$1"));return l[C]?se(function(e,t,n,i){for(var o,r=l(e,null,i,[]),u=e.length;u--;)(o=r[u])&&(e[u]=!(t[u]=o))}):function(e,i,o){return t[0]=e,l(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return ue(e,t).length>0}}),contains:se(function(e){return e=e.replace(te,ne),function(t){return(t.textContent||i(t)).indexOf(e)>-1}}),lang:se(function(e){return q.test(e||"")||ue.error("unsupported lang: "+e),e=e.replace(te,ne).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===h.activeElement&&(!h.hasFocus||h.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!l.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:me(function(){return[0]}),last:me(function(e,t){return[t-1]}),eq:me(function(e,t,n){return[n<0?n+t:n]}),even:me(function(e,t){for(var n=0;nt?t:n;--l>=0;)e.push(l);return e}),gt:me(function(e,t,n){for(var l=n<0?n+t:n;++l1?function(t,n,l){for(var i=e.length;i--;)if(!e[i](t,n,l))return!1;return!0}:e[0]}function _e(e,t,n,l,i){for(var o,r=[],u=0,a=e.length,s=null!=t;u-1&&(o[s]=!(r[s]=d))}}else v=_e(v===r?v.splice(f,v.length):v),i?i(null,r,v,a):N.apply(r,v)})}function Se(e){for(var t,n,i,o=e.length,r=l.relative[e[0].type],u=r||l.relative[" "],a=r?1:0,c=Ce(function(e){return e===t},u,!0),d=Ce(function(e){return A(t,e)>-1},u,!0),p=[function(e,n,l){var i=!r&&(l||n!==s)||((t=n).nodeType?c(e,n,l):d(e,n,l));return t=null,i}];a1&&we(p),a>1&&be(e.slice(0,a-1).concat({value:" "===e[a-2].type?"*":""})).replace(z,"$1"),n,a0,i=e.length>0,o=function(o,r,u,a,c){var d,f,m,v=0,y="0",b=o&&[],C=[],w=s,x=o||i&&l.find.TAG("*",c),S=_+=null==w?1:Math.random()||.1,E=x.length;for(c&&(s=r==h||r||c);y!==E&&null!=(d=x[y]);y++){if(i&&d){for(f=0,r||d.ownerDocument==h||(p(d),u=!g);m=e[f++];)if(m(d,r||h,u)){a.push(d);break}c&&(_=S)}n&&((d=!m&&d)&&v--,o&&b.push(d))}if(v+=y,n&&y!==v){for(f=0;m=t[f++];)m(b,C,r,u);if(o){if(v>0)for(;y--;)b[y]||C[y]||(C[y]=R.call(a));C=_e(C)}N.apply(a,C),c&&!o&&C.length>0&&v+t.length>1&&ue.uniqueSort(a)}return c&&(_=S,s=w),b};return n?se(o):o}(o,i))).selector=e}return u},a=ue.select=function(e,t,n,i){var o,a,s,c,d,p="function"==typeof e&&e,h=!i&&r(e=p.selector||e);if(n=n||[],1===h.length){if((a=h[0]=h[0].slice(0)).length>2&&"ID"===(s=a[0]).type&&9===t.nodeType&&g&&l.relative[a[1].type]){if(!(t=(l.find.ID(s.matches[0].replace(te,ne),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(a.shift().value.length)}for(o=K.needsContext.test(e)?0:a.length;o--&&!l.relative[c=(s=a[o]).type];)if((d=l.find[c])&&(i=d(s.matches[0].replace(te,ne),ee.test(a[0].type)&&ve(t.parentNode)||t))){if(a.splice(o,1),!(e=i.length&&be(a)))return N.apply(n,i),n;break}}return(p||u(e,h))(i,t,!g,n,!t||ee.test(e)&&ve(t.parentNode)||t),n},n.sortStable=C.split("").sort(T).join("")===C,n.detectDuplicates=!!d,p(),n.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(h.createElement("fieldset"))}),ce(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||de("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ce(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||de("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||de(P,function(e,t,n){var l;if(!n)return!0===e[t]?t.toLowerCase():(l=e.getAttributeNode(t))&&l.specified?l.value:null}),ue}(n);x.find=E,x.expr=E.selectors,x.expr[":"]=x.expr.pseudos,x.uniqueSort=x.unique=E.uniqueSort,x.text=E.getText,x.isXMLDoc=E.isXML,x.contains=E.contains,x.escapeSelector=E.escape;var I=function(e,t,n){for(var l=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&x(e).is(n))break;l.push(e)}return l},O=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},T=x.expr.match.needsContext;function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var k=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function R(e,t,n){return v(t)?x.grep(e,function(e,l){return!!t.call(e,l,e)!==n}):t.nodeType?x.grep(e,function(e){return e===t!==n}):"string"!=typeof t?x.grep(e,function(e){return c.call(t,e)>-1!==n}):x.filter(t,e,n)}x.filter=function(e,t,n){var l=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===l.nodeType?x.find.matchesSelector(l,e)?[l]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},x.fn.extend({find:function(e){var t,n,l=this.length,i=this;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;t1?x.uniqueSort(n):n},filter:function(e){return this.pushStack(R(this,e||[],!1))},not:function(e){return this.pushStack(R(this,e||[],!0))},is:function(e){return!!R(this,"string"==typeof e&&T.test(e)?x(e):e||[],!1).length}});var M,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(x.fn.init=function(e,t,n){var l,i;if(!e)return this;if(n=n||M,"string"==typeof e){if(!(l="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:N.exec(e))||!l[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(l[1]){if(x.merge(this,x.parseHTML(l[1],(t=t instanceof x?t[0]:t)&&t.nodeType?t.ownerDocument||t:b,!0)),k.test(l[1])&&x.isPlainObject(t))for(l in t)v(this[l])?this[l](t[l]):this.attr(l,t[l]);return this}return(i=b.getElementById(l[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(x):x.makeArray(e,this)}).prototype=x.fn,M=x(b);var L=/^(?:parents|prev(?:Until|All))/,A={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}x.fn.extend({has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&x.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?x.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?c.call(x(e),this[0]):c.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(x.uniqueSort(x.merge(this.get(),x(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return I(e,"parentNode")},parentsUntil:function(e,t,n){return I(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return I(e,"nextSibling")},prevAll:function(e){return I(e,"previousSibling")},nextUntil:function(e,t,n){return I(e,"nextSibling",n)},prevUntil:function(e,t,n){return I(e,"previousSibling",n)},siblings:function(e){return O((e.parentNode||{}).firstChild,e)},children:function(e){return O(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(D(e,"template")&&(e=e.content||e),x.merge([],e.childNodes))}},function(e,t){x.fn[e]=function(n,l){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(l=n),l&&"string"==typeof l&&(i=x.filter(l,i)),this.length>1&&(A[e]||x.uniqueSort(i),L.test(e)&&i.reverse()),this.pushStack(i)}});var F=/[^\x20\t\r\n\f]+/g;function V(e){return e}function j(e){throw e}function H(e,t,n,l){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(n):e&&v(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(l))}catch(e){n.apply(void 0,[e])}}x.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return x.each(e.match(F)||[],function(e,n){t[n]=!0}),t}(e):x.extend({},e);var t,n,l,i,o=[],r=[],u=-1,a=function(){for(i=i||e.once,l=t=!0;r.length;u=-1)for(n=r.shift();++u-1;)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?x.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=r=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=r=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],r.push(n),t||a()),this},fire:function(){return s.fireWith(this,arguments),this},fired:function(){return!!l}};return s},x.extend({Deferred:function(e){var t=[["notify","progress",x.Callbacks("memory"),x.Callbacks("memory"),2],["resolve","done",x.Callbacks("once memory"),x.Callbacks("once memory"),0,"resolved"],["reject","fail",x.Callbacks("once memory"),x.Callbacks("once memory"),1,"rejected"]],l="pending",i={state:function(){return l},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,l){var i=v(e[l[4]])&&e[l[4]];o[l[1]](function(){var e=i&&i.apply(this,arguments);e&&v(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[l[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(e,l,i){var o=0;function r(e,t,l,i){return function(){var u=this,a=arguments,s=function(){var n,s;if(!(e=o&&(l!==j&&(u=void 0,a=[n]),t.rejectWith(u,a))}};e?c():(x.Deferred.getStackHook&&(c.stackTrace=x.Deferred.getStackHook()),n.setTimeout(c))}}return x.Deferred(function(n){t[0][3].add(r(0,n,v(i)?i:V,n.notifyWith)),t[1][3].add(r(0,n,v(e)?e:V)),t[2][3].add(r(0,n,v(l)?l:j))}).promise()},promise:function(e){return null!=e?x.extend(e,i):i}},o={};return x.each(t,function(e,n){var r=n[2],u=n[5];i[n[1]]=r.add,u&&r.add(function(){l=u},t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),r.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=r.fireWith}),i.promise(o),e&&e.call(o,o),o},when:function(e){var t=arguments.length,n=t,l=Array(n),i=u.call(arguments),o=x.Deferred(),r=function(e){return function(n){l[e]=this,i[e]=arguments.length>1?u.call(arguments):n,--t||o.resolveWith(l,i)}};if(t<=1&&(H(e,o.done(r(n)).resolve,o.reject,!t),"pending"===o.state()||v(i[n]&&i[n].then)))return o.then();for(;n--;)H(i[n],r(n),o.reject);return o.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;x.Deferred.exceptionHook=function(e,t){n.console&&n.console.warn&&e&&B.test(e.name)&&n.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},x.readyException=function(e){n.setTimeout(function(){throw e})};var z=x.Deferred();function U(){b.removeEventListener("DOMContentLoaded",U),n.removeEventListener("load",U),x.ready()}x.fn.ready=function(e){return z.then(e).catch(function(e){x.readyException(e)}),this},x.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--x.readyWait:x.isReady)||(x.isReady=!0,!0!==e&&--x.readyWait>0||z.resolveWith(b,[x]))}}),x.ready.then=z.then,"complete"===b.readyState||"loading"!==b.readyState&&!b.documentElement.doScroll?n.setTimeout(x.ready):(b.addEventListener("DOMContentLoaded",U),n.addEventListener("load",U));var $=function(e,t,n,l,i,o,r){var u=0,a=e.length,s=null==n;if("object"===_(n))for(u in i=!0,n)$(e,t,u,n[u],!0,o,r);else if(void 0!==l&&(i=!0,v(l)||(r=!0),s&&(r?(t.call(e,l),t=null):(s=t,t=function(e,t,n){return s.call(x(e),n)})),t))for(;u1,null,!0)},removeData:function(e){return this.each(function(){X.remove(this,e)})}}),x.extend({queue:function(e,t,n){var l;if(e)return l=Q.get(e,t=(t||"fx")+"queue"),n&&(!l||Array.isArray(n)?l=Q.access(e,t,x.makeArray(n)):l.push(n)),l||[]},dequeue:function(e,t){var n=x.queue(e,t=t||"fx"),l=n.length,i=n.shift(),o=x._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),l--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){x.dequeue(e,t)},o)),!l&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Q.get(e,n)||Q.access(e,n,{empty:x.Callbacks("once memory").add(function(){Q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]*)/i,ve=/^$|^module$|\/(?:java|ecma)script/i;he=b.createDocumentFragment().appendChild(b.createElement("div")),(fe=b.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),he.appendChild(fe),m.checkClone=he.cloneNode(!0).cloneNode(!0).lastChild.checked,he.innerHTML="",m.noCloneChecked=!!he.cloneNode(!0).lastChild.defaultValue,he.innerHTML="",m.option=!!he.lastChild;var ye={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function be(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?x.merge([e],n):n}function Ce(e,t){for(var n=0,l=e.length;n",""]);var we=/<|&#?\w+;/;function _e(e,t,n,l,i){for(var o,r,u,a,s,c,d=t.createDocumentFragment(),p=[],h=0,f=e.length;h-1)i&&i.push(o);else if(s=re(o),r=be(d.appendChild(o),"script"),s&&Ce(r),n)for(c=0;o=r[c++];)ve.test(o.type||"")&&n.push(o);return d}var xe=/^key/,Se=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function Ie(){return!0}function Oe(){return!1}function Te(e,t){return e===function(){try{return b.activeElement}catch(e){}}()==("focus"===t)}function De(e,t,n,l,i,o){var r,u;if("object"==typeof t){for(u in"string"!=typeof n&&(l=l||n,n=void 0),t)De(e,u,n,l,t[u],o);return e}if(null==l&&null==i?(i=n,l=n=void 0):null==i&&("string"==typeof n?(i=l,l=void 0):(i=l,l=n,n=void 0)),!1===i)i=Oe;else if(!i)return e;return 1===o&&(r=i,(i=function(e){return x().off(e),r.apply(this,arguments)}).guid=r.guid||(r.guid=x.guid++)),e.each(function(){x.event.add(this,t,i,l,n)})}function ke(e,t,n){n?(Q.set(e,t,!1),x.event.add(e,t,{namespace:!1,handler:function(e){var l,i,o=Q.get(this,t);if(1&e.isTrigger&&this[t]){if(o.length)(x.event.special[t]||{}).delegateType&&e.stopPropagation();else if(o=u.call(arguments),Q.set(this,t,o),l=n(this,t),this[t](),o!==(i=Q.get(this,t))||l?Q.set(this,t,!1):i={},o!==i)return e.stopImmediatePropagation(),e.preventDefault(),i.value}else o.length&&(Q.set(this,t,{value:x.event.trigger(x.extend(o[0],x.Event.prototype),o.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,t)&&x.event.add(e,t,Ie)}x.event={global:{},add:function(e,t,n,l,i){var o,r,u,a,s,c,d,p,h,f,g,m=Q.get(e);if(Y(e))for(n.handler&&(n=(o=n).handler,i=o.selector),i&&x.find.matchesSelector(oe,i),n.guid||(n.guid=x.guid++),(a=m.events)||(a=m.events=Object.create(null)),(r=m.handle)||(r=m.handle=function(t){return void 0!==x&&x.event.triggered!==t.type?x.event.dispatch.apply(e,arguments):void 0}),s=(t=(t||"").match(F)||[""]).length;s--;)h=g=(u=Ee.exec(t[s])||[])[1],f=(u[2]||"").split(".").sort(),h&&(d=x.event.special[h]||{},d=x.event.special[h=(i?d.delegateType:d.bindType)||h]||{},c=x.extend({type:h,origType:g,data:l,handler:n,guid:n.guid,selector:i,needsContext:i&&x.expr.match.needsContext.test(i),namespace:f.join(".")},o),(p=a[h])||((p=a[h]=[]).delegateCount=0,d.setup&&!1!==d.setup.call(e,l,f,r)||e.addEventListener&&e.addEventListener(h,r)),d.add&&(d.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),x.event.global[h]=!0)},remove:function(e,t,n,l,i){var o,r,u,a,s,c,d,p,h,f,g,m=Q.hasData(e)&&Q.get(e);if(m&&(a=m.events)){for(s=(t=(t||"").match(F)||[""]).length;s--;)if(h=g=(u=Ee.exec(t[s])||[])[1],f=(u[2]||"").split(".").sort(),h){for(d=x.event.special[h]||{},p=a[h=(l?d.delegateType:d.bindType)||h]||[],u=u[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),r=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||u&&!u.test(c.namespace)||l&&l!==c.selector&&("**"!==l||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,d.remove&&d.remove.call(e,c));r&&!p.length&&(d.teardown&&!1!==d.teardown.call(e,f,m.handle)||x.removeEvent(e,h,m.handle),delete a[h])}else for(h in a)x.event.remove(e,h+t[s],n,l,!0);x.isEmptyObject(a)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,l,i,o,r,u=new Array(arguments.length),a=x.event.fix(e),s=(Q.get(this,"events")||Object.create(null))[a.type]||[],c=x.event.special[a.type]||{};for(u[0]=a,t=1;t=1))for(;s!==this;s=s.parentNode||this)if(1===s.nodeType&&("click"!==e.type||!0!==s.disabled)){for(o=[],r={},n=0;n-1:x.find(i,this,null,[s]).length),r[i]&&o.push(l);o.length&&u.push({elem:s,handlers:o})}return s=this,a\s*$/g;function Le(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")&&x(e).children("tbody")[0]||e}function Ae(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,l,i,o,r,u;if(1===t.nodeType){if(Q.hasData(e)&&(u=Q.get(e).events))for(i in Q.remove(t,"handle events"),u)for(n=0,l=u[i].length;n1&&"string"==typeof f&&!m.checkClone&&Me.test(f))return e.each(function(i){var o=e.eq(i);g&&(t[0]=f.call(this,i,o.html())),Ve(o,t,n,l)});if(p&&(o=(i=_e(t,e[0].ownerDocument,!1,e,l)).firstChild,1===i.childNodes.length&&(i=o),o||l)){for(u=(r=x.map(be(i,"script"),Ae)).length;d0&&Ce(r,!d&&be(e,"script")),c},cleanData:function(e){for(var t,n,l,i=x.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[Q.expando]){if(t.events)for(l in t.events)i[l]?x.event.remove(n,l):x.removeEvent(n,l,t.handle);n[Q.expando]=void 0}n[X.expando]&&(n[X.expando]=void 0)}}}),x.fn.extend({detach:function(e){return je(this,e,!0)},remove:function(e){return je(this,e)},text:function(e){return $(this,function(e){return void 0===e?x.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ve(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Ve(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ve(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ve(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(be(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,l=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Re.test(e)&&!ye[(me.exec(e)||["",""])[1].toLowerCase()]){e=x.htmlPrefilter(e);try{for(;n3,oe.removeChild(e)),u}}))}();var We=["Webkit","Moz","ms"],qe=b.createElement("div").style,Ke={};function Ye(e){return x.cssProps[e]||Ke[e]||(e in qe?e:Ke[e]=function(e){for(var t=e[0].toUpperCase()+e.slice(1),n=We.length;n--;)if((e=We[n]+t)in qe)return e}(e)||e)}var Ze=/^(none|table(?!-c[ea]).+)/,Qe=/^--/,Xe={position:"absolute",visibility:"hidden",display:"block"},Je={letterSpacing:"0",fontWeight:"400"};function et(e,t,n){var l=le.exec(t);return l?Math.max(0,l[2]-(n||0))+(l[3]||"px"):t}function tt(e,t,n,l,i,o){var r="width"===t?1:0,u=0,a=0;if(n===(l?"border":"content"))return 0;for(;r<4;r+=2)"margin"===n&&(a+=x.css(e,n+ie[r],!0,i)),l?("content"===n&&(a-=x.css(e,"padding"+ie[r],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+ie[r]+"Width",!0,i))):(a+=x.css(e,"padding"+ie[r],!0,i),"padding"!==n?a+=x.css(e,"border"+ie[r]+"Width",!0,i):u+=x.css(e,"border"+ie[r]+"Width",!0,i));return!l&&o>=0&&(a+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-a-u-.5))||0),a}function nt(e,t,n){var l=Be(e),i=(!m.boxSizingReliable()||n)&&"border-box"===x.css(e,"boxSizing",!1,l),o=i,r=$e(e,t,l),u="offset"+t[0].toUpperCase()+t.slice(1);if(He.test(r)){if(!n)return r;r="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&D(e,"tr")||"auto"===r||!parseFloat(r)&&"inline"===x.css(e,"display",!1,l))&&e.getClientRects().length&&(i="border-box"===x.css(e,"boxSizing",!1,l),(o=u in e)&&(r=e[u])),(r=parseFloat(r)||0)+tt(e,t,n||(i?"border":"content"),o,l,r)+"px"}function lt(e,t,n,l,i){return new lt.prototype.init(e,t,n,l,i)}x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=$e(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,l){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,r,u=K(t),a=Qe.test(t),s=e.style;if(a||(t=Ye(u)),r=x.cssHooks[t]||x.cssHooks[u],void 0===n)return r&&"get"in r&&void 0!==(i=r.get(e,!1,l))?i:s[t];"string"==(o=typeof n)&&(i=le.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||a||(n+=i&&i[3]||(x.cssNumber[u]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(s[t]="inherit"),r&&"set"in r&&void 0===(n=r.set(e,n,l))||(a?s.setProperty(t,n):s[t]=n))}},css:function(e,t,n,l){var i,o,r,u=K(t);return Qe.test(t)||(t=Ye(u)),(r=x.cssHooks[t]||x.cssHooks[u])&&"get"in r&&(i=r.get(e,!0,n)),void 0===i&&(i=$e(e,t,l)),"normal"===i&&t in Je&&(i=Je[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),x.each(["height","width"],function(e,t){x.cssHooks[t]={get:function(e,n,l){if(n)return!Ze.test(x.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?nt(e,t,l):ze(e,Xe,function(){return nt(e,t,l)})},set:function(e,n,l){var i,o=Be(e),r=!m.scrollboxSize()&&"absolute"===o.position,u=(r||l)&&"border-box"===x.css(e,"boxSizing",!1,o),a=l?tt(e,t,l,u,o):0;return u&&r&&(a-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-tt(e,t,"border",!1,o)-.5)),a&&(i=le.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=x.css(e,t)),et(0,n,a)}}}),x.cssHooks.marginLeft=Ge(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat($e(e,"marginLeft"))||e.getBoundingClientRect().left-ze(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){for(var l=0,i={},o="string"==typeof n?n.split(" "):[n];l<4;l++)i[e+ie[l]+t]=o[l]||o[l-2]||o[0];return i}},"margin"!==e&&(x.cssHooks[e+t].set=et)}),x.fn.extend({css:function(e,t){return $(this,function(e,t,n){var l,i,o={},r=0;if(Array.isArray(t)){for(l=Be(e),i=t.length;r1)}}),x.Tween=lt,(lt.prototype={constructor:lt,init:function(e,t,n,l,i,o){this.elem=e,this.prop=n,this.easing=i||x.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=l,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=lt.propHooks[this.prop];return e&&e.get?e.get(this):lt.propHooks._default.get(this)},run:function(e){var t,n=lt.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):lt.propHooks._default.set(this),this}}).init.prototype=lt.prototype,(lt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=x.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):1!==e.elem.nodeType||!x.cssHooks[e.prop]&&null==e.elem.style[Ye(e.prop)]?e.elem[e.prop]=e.now:x.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=lt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},x.fx=lt.prototype.init,x.fx.step={};var it,ot,rt=/^(?:toggle|show|hide)$/,ut=/queueHooks$/;function at(){ot&&(!1===b.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(at):n.setTimeout(at,x.fx.interval),x.fx.tick())}function st(){return n.setTimeout(function(){it=void 0}),it=Date.now()}function ct(e,t){var n,l=0,i={height:e};for(t=t?1:0;l<4;l+=2-t)i["margin"+(n=ie[l])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function dt(e,t,n){for(var l,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,r=i.length;o1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})}}),x.extend({attr:function(e,t,n){var l,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?x.prop(e,t,n):(1===o&&x.isXMLDoc(e)||(i=x.attrHooks[t.toLowerCase()]||(x.expr.match.bool.test(t)?ht:void 0)),void 0!==n?null===n?void x.removeAttr(e,t):i&&"set"in i&&void 0!==(l=i.set(e,n,t))?l:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(l=i.get(e,t))?l:null==(l=x.find.attr(e,t))?void 0:l)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,l=0,i=t&&t.match(F);if(i&&1===e.nodeType)for(;n=i[l++];)e.removeAttribute(n)}}),ht={set:function(e,t,n){return!1===t?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ft[t]||x.find.attr;ft[t]=function(e,t,l){var i,o,r=t.toLowerCase();return l||(o=ft[r],ft[r]=i,i=null!=n(e,t,l)?r:null,ft[r]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,mt=/^(?:a|area)$/i;function vt(e){return(e.match(F)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function bt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(F)||[]}x.fn.extend({prop:function(e,t){return $(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})}}),x.extend({prop:function(e,t,n){var l,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&x.isXMLDoc(e)||(i=x.propHooks[t=x.propFix[t]||t]),void 0!==n?i&&"set"in i&&void 0!==(l=i.set(e,n,t))?l:e[t]=n:i&&"get"in i&&null!==(l=i.get(e,t))?l:e[t]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||mt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(x.propHooks.selected={get:function(e){return null},set:function(e){}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.fn.extend({addClass:function(e){var t,n,l,i,o,r,u,a=0;if(v(e))return this.each(function(t){x(this).addClass(e.call(this,t,yt(this)))});if((t=bt(e)).length)for(;n=this[a++];)if(i=yt(n),l=1===n.nodeType&&" "+vt(i)+" "){for(r=0;o=t[r++];)l.indexOf(" "+o+" ")<0&&(l+=o+" ");i!==(u=vt(l))&&n.setAttribute("class",u)}return this},removeClass:function(e){var t,n,l,i,o,r,u,a=0;if(v(e))return this.each(function(t){x(this).removeClass(e.call(this,t,yt(this)))});if(!arguments.length)return this.attr("class","");if((t=bt(e)).length)for(;n=this[a++];)if(i=yt(n),l=1===n.nodeType&&" "+vt(i)+" "){for(r=0;o=t[r++];)for(;l.indexOf(" "+o+" ")>-1;)l=l.replace(" "+o+" "," ");i!==(u=vt(l))&&n.setAttribute("class",u)}return this},toggleClass:function(e,t){var n=typeof e,l="string"===n||Array.isArray(e);return"boolean"==typeof t&&l?t?this.addClass(e):this.removeClass(e):v(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,yt(this),t),t)}):this.each(function(){var t,i,o,r;if(l)for(i=0,o=x(this),r=bt(e);t=r[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=yt(this))&&Q.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Q.get(this,"__className__")||""))})},hasClass:function(e){var t,n,l=0;for(t=" "+e+" ";n=this[l++];)if(1===n.nodeType&&(" "+vt(yt(n))+" ").indexOf(t)>-1)return!0;return!1}});var Ct=/\r/g;x.fn.extend({val:function(e){var t,n,l,i=this[0];return arguments.length?(l=v(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=l?e.call(this,n,x(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),(t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(Ct,""):null==n?"":n:void 0}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:vt(x.text(e))}},select:{get:function(e){var t,n,l,i=e.options,o=e.selectedIndex,r="select-one"===e.type,u=r?null:[],a=r?o+1:i.length;for(l=o<0?a:r?o:0;l-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=x.inArray(x(e).val(),t)>-1}},m.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),m.focusin="onfocusin"in n;var wt=/^(?:focusinfocus|focusoutblur)$/,_t=function(e){e.stopPropagation()};x.extend(x.event,{trigger:function(e,t,l,i){var o,r,u,a,s,c,d,p,f=[l||b],g=h.call(e,"type")?e.type:e,m=h.call(e,"namespace")?e.namespace.split("."):[];if(r=p=u=l=l||b,3!==l.nodeType&&8!==l.nodeType&&!wt.test(g+x.event.triggered)&&(g.indexOf(".")>-1&&(m=g.split("."),g=m.shift(),m.sort()),s=g.indexOf(":")<0&&"on"+g,(e=e[x.expando]?e:new x.Event(g,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=l),t=null==t?[e]:x.makeArray(t,[e]),d=x.event.special[g]||{},i||!d.trigger||!1!==d.trigger.apply(l,t))){if(!i&&!d.noBubble&&!y(l)){for(wt.test((a=d.delegateType||g)+g)||(r=r.parentNode);r;r=r.parentNode)f.push(r),u=r;u===(l.ownerDocument||b)&&f.push(u.defaultView||u.parentWindow||n)}for(o=0;(r=f[o++])&&!e.isPropagationStopped();)p=r,e.type=o>1?a:d.bindType||g,(c=(Q.get(r,"events")||Object.create(null))[e.type]&&Q.get(r,"handle"))&&c.apply(r,t),(c=s&&r[s])&&c.apply&&Y(r)&&(e.result=c.apply(r,t),!1===e.result&&e.preventDefault());return e.type=g,i||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(f.pop(),t)||!Y(l)||s&&v(l[g])&&!y(l)&&((u=l[s])&&(l[s]=null),x.event.triggered=g,e.isPropagationStopped()&&p.addEventListener(g,_t),l[g](),e.isPropagationStopped()&&p.removeEventListener(g,_t),x.event.triggered=void 0,u&&(l[s]=u)),e.result}},simulate:function(e,t,n){var l=x.extend(new x.Event,n,{type:e,isSimulated:!0});x.event.trigger(l,null,t)}}),x.fn.extend({trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return x.event.trigger(e,t,n,!0)}}),m.focusin||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){x.event.simulate(t,e.target,x.event.fix(e))};x.event.special[t]={setup:function(){var l=this.ownerDocument||this.document||this,i=Q.access(l,t);i||l.addEventListener(e,n,!0),Q.access(l,t,(i||0)+1)},teardown:function(){var l=this.ownerDocument||this.document||this,i=Q.access(l,t)-1;i?Q.access(l,t,i):(l.removeEventListener(e,n,!0),Q.remove(l,t))}}});var xt=n.location,St={guid:Date.now()},Et=/\?/;x.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(l){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+e),t};var It=/\[\]$/,Ot=/\r?\n/g,Tt=/^(?:submit|button|image|reset|file)$/i,Dt=/^(?:input|select|textarea|keygen)/i;function kt(e,t,n,l){var i;if(Array.isArray(t))x.each(t,function(t,i){n||It.test(e)?l(e,i):kt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,l)});else if(n||"object"!==_(t))l(e,t);else for(i in t)kt(e+"["+i+"]",t[i],n,l)}x.param=function(e,t){var n,l=[],i=function(e,t){var n=v(t)?t():t;l[l.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){i(this.name,this.value)});else for(n in e)kt(n,e[n],t,i);return l.join("&")},x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&Dt.test(this.nodeName)&&!Tt.test(e)&&(this.checked||!ge.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:Array.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(Ot,"\r\n")}}):{name:t.name,value:n.replace(Ot,"\r\n")}}).get()}});var Rt=/%20/g,Mt=/#.*$/,Nt=/([?&])_=[^&]*/,Lt=/^(.*?):[ \t]*([^\r\n]*)$/gm,At=/^(?:GET|HEAD)$/,Pt=/^\/\//,Ft={},Vt={},jt="*/".concat("*"),Ht=b.createElement("a");function Bt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var l,i=0,o=t.toLowerCase().match(F)||[];if(v(n))for(;l=o[i++];)"+"===l[0]?(l=l.slice(1)||"*",(e[l]=e[l]||[]).unshift(n)):(e[l]=e[l]||[]).push(n)}}function zt(e,t,n,l){var i={},o=e===Vt;function r(u){var a;return i[u]=!0,x.each(e[u]||[],function(e,u){var s=u(t,n,l);return"string"!=typeof s||o||i[s]?o?!(a=s):void 0:(t.dataTypes.unshift(s),r(s),!1)}),a}return r(t.dataTypes[0])||!i["*"]&&r("*")}function Ut(e,t){var n,l,i=x.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:l||(l={}))[n]=t[n]);return l&&x.extend(!0,e,l),e}Ht.href=xt.href,x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:xt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(xt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":jt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Ut(Ut(e,x.ajaxSettings),t):Ut(x.ajaxSettings,e)},ajaxPrefilter:Bt(Ft),ajaxTransport:Bt(Vt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0);var l,i,o,r,u,a,s,c,d,p,h=x.ajaxSetup({},t=t||{}),f=h.context||h,g=h.context&&(f.nodeType||f.jquery)?x(f):x.event,m=x.Deferred(),v=x.Callbacks("once memory"),y=h.statusCode||{},C={},w={},_="canceled",S={readyState:0,getResponseHeader:function(e){var t;if(s){if(!r)for(r={};t=Lt.exec(o);)r[t[1].toLowerCase()+" "]=(r[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=r[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return s?o:null},setRequestHeader:function(e,t){return null==s&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,C[e]=t),this},overrideMimeType:function(e){return null==s&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(s)S.always(e[S.status]);else for(t in e)y[t]=[y[t],e[t]];return this},abort:function(e){var t=e||_;return l&&l.abort(t),E(0,t),this}};if(m.promise(S),h.url=((e||h.url||xt.href)+"").replace(Pt,xt.protocol+"//"),h.type=t.method||t.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(F)||[""],null==h.crossDomain){a=b.createElement("a");try{a.href=h.url,a.href=a.href,h.crossDomain=Ht.protocol+"//"+Ht.host!=a.protocol+"//"+a.host}catch(I){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=x.param(h.data,h.traditional)),zt(Ft,h,t,S),s)return S;for(d in(c=x.event&&h.global)&&0==x.active++&&x.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!At.test(h.type),i=h.url.replace(Mt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Rt,"+")):(p=h.url.slice(i.length),h.data&&(h.processData||"string"==typeof h.data)&&(i+=(Et.test(i)?"&":"?")+h.data,delete h.data),!1===h.cache&&(i=i.replace(Nt,"$1"),p=(Et.test(i)?"&":"?")+"_="+St.guid+++p),h.url=i+p),h.ifModified&&(x.lastModified[i]&&S.setRequestHeader("If-Modified-Since",x.lastModified[i]),x.etag[i]&&S.setRequestHeader("If-None-Match",x.etag[i])),(h.data&&h.hasContent&&!1!==h.contentType||t.contentType)&&S.setRequestHeader("Content-Type",h.contentType),S.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+jt+"; q=0.01":""):h.accepts["*"]),h.headers)S.setRequestHeader(d,h.headers[d]);if(h.beforeSend&&(!1===h.beforeSend.call(f,S,h)||s))return S.abort();if(_="abort",v.add(h.complete),S.done(h.success),S.fail(h.error),l=zt(Vt,h,t,S)){if(S.readyState=1,c&&g.trigger("ajaxSend",[S,h]),s)return S;h.async&&h.timeout>0&&(u=n.setTimeout(function(){S.abort("timeout")},h.timeout));try{s=!1,l.send(C,E)}catch(I){if(s)throw I;E(-1,I)}}else E(-1,"No Transport");function E(e,t,r,a){var d,p,b,C,w,_=t;s||(s=!0,u&&n.clearTimeout(u),l=void 0,o=a||"",S.readyState=e>0?4:0,d=e>=200&&e<300||304===e,r&&(C=function(e,t,n){for(var l,i,o,r,u=e.contents,a=e.dataTypes;"*"===a[0];)a.shift(),void 0===l&&(l=e.mimeType||t.getResponseHeader("Content-Type"));if(l)for(i in u)if(u[i]&&u[i].test(l)){a.unshift(i);break}if(a[0]in n)o=a[0];else{for(i in n){if(!a[0]||e.converters[i+" "+a[0]]){o=i;break}r||(r=i)}o=o||r}if(o)return o!==a[0]&&a.unshift(o),n[o]}(h,S,r)),!d&&x.inArray("script",h.dataTypes)>-1&&(h.converters["text script"]=function(){}),C=function(e,t,n,l){var i,o,r,u,a,s={},c=e.dataTypes.slice();if(c[1])for(r in e.converters)s[r.toLowerCase()]=e.converters[r];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!a&&l&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),a=o,o=c.shift())if("*"===o)o=a;else if("*"!==a&&a!==o){if(!(r=s[a+" "+o]||s["* "+o]))for(i in s)if((u=i.split(" "))[1]===o&&(r=s[a+" "+u[0]]||s["* "+u[0]])){!0===r?r=s[i]:!0!==s[i]&&(o=u[0],c.unshift(u[1]));break}if(!0!==r)if(r&&e.throws)t=r(t);else try{t=r(t)}catch(I){return{state:"parsererror",error:r?I:"No conversion from "+a+" to "+o}}}return{state:"success",data:t}}(h,C,S,d),d?(h.ifModified&&((w=S.getResponseHeader("Last-Modified"))&&(x.lastModified[i]=w),(w=S.getResponseHeader("etag"))&&(x.etag[i]=w)),204===e||"HEAD"===h.type?_="nocontent":304===e?_="notmodified":(_=C.state,p=C.data,d=!(b=C.error))):(b=_,!e&&_||(_="error",e<0&&(e=0))),S.status=e,S.statusText=(t||_)+"",d?m.resolveWith(f,[p,_,S]):m.rejectWith(f,[S,_,b]),S.statusCode(y),y=void 0,c&&g.trigger(d?"ajaxSuccess":"ajaxError",[S,h,d?p:b]),v.fireWith(f,[S,_]),c&&(g.trigger("ajaxComplete",[S,h]),--x.active||x.event.trigger("ajaxStop")))}return S},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,void 0,t,"script")}}),x.each(["get","post"],function(e,t){x[t]=function(e,n,l,i){return v(n)&&(i=i||l,l=n,n=void 0),x.ajax(x.extend({url:e,type:t,dataType:i,data:n,success:l},x.isPlainObject(e)&&e))}}),x.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),x._evalUrl=function(e,t,n){return x.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){x.globalEval(e,t,n)}})},x.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return v(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){x(this).replaceWith(this.childNodes)}),this}}),x.expr.pseudos.hidden=function(e){return!x.expr.pseudos.visible(e)},x.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},x.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var $t={0:200,1223:204},Gt=x.ajaxSettings.xhr();m.cors=!!Gt&&"withCredentials"in Gt,m.ajax=Gt=!!Gt,x.ajaxTransport(function(e){var t,l;if(m.cors||Gt&&!e.crossDomain)return{send:function(i,o){var r,u=e.xhr();if(u.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(r in e.xhrFields)u[r]=e.xhrFields[r];for(r in e.mimeType&&u.overrideMimeType&&u.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)u.setRequestHeader(r,i[r]);t=function(e){return function(){t&&(t=l=u.onload=u.onerror=u.onabort=u.ontimeout=u.onreadystatechange=null,"abort"===e?u.abort():"error"===e?"number"!=typeof u.status?o(0,"error"):o(u.status,u.statusText):o($t[u.status]||u.status,u.statusText,"text"!==(u.responseType||"text")||"string"!=typeof u.responseText?{binary:u.response}:{text:u.responseText},u.getAllResponseHeaders()))}},u.onload=t(),l=u.onerror=u.ontimeout=t("error"),void 0!==u.onabort?u.onabort=l:u.onreadystatechange=function(){4===u.readyState&&n.setTimeout(function(){t&&l()})},t=t("abort");try{u.send(e.hasContent&&e.data||null)}catch(a){if(t)throw a}},abort:function(){t&&t()}}}),x.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),x.ajaxTransport("script",function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(l,i){t=x(" + diff --git a/modules/admin/webif_0_4_2/static/line.567f57385ea3dde2c9ae.gif b/modules/admin/webif_0_6_0/static/line.567f57385ea3dde2c9ae.gif similarity index 100% rename from modules/admin/webif_0_4_2/static/line.567f57385ea3dde2c9ae.gif rename to modules/admin/webif_0_6_0/static/line.567f57385ea3dde2c9ae.gif diff --git a/modules/admin/webif_0_4_2/static/loading.8732a6660b528fadfaeb.gif b/modules/admin/webif_0_6_0/static/loading.8732a6660b528fadfaeb.gif similarity index 100% rename from modules/admin/webif_0_4_2/static/loading.8732a6660b528fadfaeb.gif rename to modules/admin/webif_0_6_0/static/loading.8732a6660b528fadfaeb.gif diff --git a/modules/admin/webif/static/main.866b2cb95a0aef5453fe.js b/modules/admin/webif_0_6_0/static/main.866b2cb95a0aef5453fe.js similarity index 100% rename from modules/admin/webif/static/main.866b2cb95a0aef5453fe.js rename to modules/admin/webif_0_6_0/static/main.866b2cb95a0aef5453fe.js diff --git a/modules/admin/webif_0_4_2/static/manifest.json b/modules/admin/webif_0_6_0/static/manifest.json similarity index 100% rename from modules/admin/webif_0_4_2/static/manifest.json rename to modules/admin/webif_0_6_0/static/manifest.json diff --git a/modules/admin/webif_0_4_2/static/password-meter.d59e6dc2616c53ce8e77.png b/modules/admin/webif_0_6_0/static/password-meter.d59e6dc2616c53ce8e77.png similarity index 100% rename from modules/admin/webif_0_4_2/static/password-meter.d59e6dc2616c53ce8e77.png rename to modules/admin/webif_0_6_0/static/password-meter.d59e6dc2616c53ce8e77.png diff --git a/modules/admin/webif_0_4_2/static/polyfills.ac95705c8ecb4d230bfb.js b/modules/admin/webif_0_6_0/static/polyfills.ac95705c8ecb4d230bfb.js similarity index 100% rename from modules/admin/webif_0_4_2/static/polyfills.ac95705c8ecb4d230bfb.js rename to modules/admin/webif_0_6_0/static/polyfills.ac95705c8ecb4d230bfb.js diff --git a/modules/admin/webif_0_4_2/static/primeicons.38d77552b0353684a208.svg b/modules/admin/webif_0_6_0/static/primeicons.38d77552b0353684a208.svg similarity index 100% rename from modules/admin/webif_0_4_2/static/primeicons.38d77552b0353684a208.svg rename to modules/admin/webif_0_6_0/static/primeicons.38d77552b0353684a208.svg diff --git a/modules/admin/webif_0_4_2/static/primeicons.473e2a746d3c151d7dca.ttf b/modules/admin/webif_0_6_0/static/primeicons.473e2a746d3c151d7dca.ttf similarity index 100% rename from modules/admin/webif_0_4_2/static/primeicons.473e2a746d3c151d7dca.ttf rename to modules/admin/webif_0_6_0/static/primeicons.473e2a746d3c151d7dca.ttf diff --git a/modules/admin/webif_0_4_2/static/primeicons.71bb3d79dcf18b45ae84.woff b/modules/admin/webif_0_6_0/static/primeicons.71bb3d79dcf18b45ae84.woff similarity index 100% rename from modules/admin/webif_0_4_2/static/primeicons.71bb3d79dcf18b45ae84.woff rename to modules/admin/webif_0_6_0/static/primeicons.71bb3d79dcf18b45ae84.woff diff --git a/modules/admin/webif_0_4_2/static/primeicons.b8eccb1059ea5faaf6d8.eot b/modules/admin/webif_0_6_0/static/primeicons.b8eccb1059ea5faaf6d8.eot similarity index 100% rename from modules/admin/webif_0_4_2/static/primeicons.b8eccb1059ea5faaf6d8.eot rename to modules/admin/webif_0_6_0/static/primeicons.b8eccb1059ea5faaf6d8.eot diff --git a/modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.16e1d930cf13fb7a9563.woff b/modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.16e1d930cf13fb7a9563.woff similarity index 100% rename from modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.16e1d930cf13fb7a9563.woff rename to modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.16e1d930cf13fb7a9563.woff diff --git a/modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.38861cba61c66739c145.ttf b/modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.38861cba61c66739c145.ttf similarity index 100% rename from modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.38861cba61c66739c145.ttf rename to modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.38861cba61c66739c145.ttf diff --git a/modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.3d3a53586bd78d1069ae.svg b/modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.3d3a53586bd78d1069ae.svg similarity index 100% rename from modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.3d3a53586bd78d1069ae.svg rename to modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.3d3a53586bd78d1069ae.svg diff --git a/modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.7e367be02cd17a96d513.woff2 b/modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.7e367be02cd17a96d513.woff2 similarity index 100% rename from modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.7e367be02cd17a96d513.woff2 rename to modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.7e367be02cd17a96d513.woff2 diff --git a/modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.9f916e330c478bbfa2a0.eot b/modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.9f916e330c478bbfa2a0.eot similarity index 100% rename from modules/admin/webif_0_4_2/static/roboto-v15-latin-regular.9f916e330c478bbfa2a0.eot rename to modules/admin/webif_0_6_0/static/roboto-v15-latin-regular.9f916e330c478bbfa2a0.eot diff --git a/modules/admin/webif_0_4_2/static/runtime.e330f42ab6b744c4debd.js b/modules/admin/webif_0_6_0/static/runtime.e330f42ab6b744c4debd.js similarity index 100% rename from modules/admin/webif_0_4_2/static/runtime.e330f42ab6b744c4debd.js rename to modules/admin/webif_0_6_0/static/runtime.e330f42ab6b744c4debd.js diff --git a/modules/admin/webif_0_4_2/static/scripts.e384037e7215c1a5366d.js b/modules/admin/webif_0_6_0/static/scripts.e384037e7215c1a5366d.js similarity index 100% rename from modules/admin/webif_0_4_2/static/scripts.e384037e7215c1a5366d.js rename to modules/admin/webif_0_6_0/static/scripts.e384037e7215c1a5366d.js diff --git a/modules/admin/webif_0_4_2/static/slider_handles.1868e2550c9853a938a6.png b/modules/admin/webif_0_6_0/static/slider_handles.1868e2550c9853a938a6.png similarity index 100% rename from modules/admin/webif_0_4_2/static/slider_handles.1868e2550c9853a938a6.png rename to modules/admin/webif_0_6_0/static/slider_handles.1868e2550c9853a938a6.png diff --git a/modules/admin/webif_0_4_2/static/styles.cdc7be39415b36e71c26.css b/modules/admin/webif_0_6_0/static/styles.cdc7be39415b36e71c26.css similarity index 100% rename from modules/admin/webif_0_4_2/static/styles.cdc7be39415b36e71c26.css rename to modules/admin/webif_0_6_0/static/styles.cdc7be39415b36e71c26.css diff --git a/modules/http/README.md b/modules/http/README.md index 57f08ee7be..e6c4651ead 100644 --- a/modules/http/README.md +++ b/modules/http/README.md @@ -2,7 +2,7 @@ This module allows plugins to implement a web interface. The API is described below. The first plugin to utilize this API is the backend plugin. -> Note: To write a plugin that utilizes this module, you have to be familiar with CherryPy. +> Note: To write a plugin that utilizes this module, you have to be familiar with CherryPy. ## Requirements @@ -14,7 +14,7 @@ This module is running under SmartHomeNG versions beyond v1.3. It requires Pytho sudo pip3 install cherrypy ``` -And please pay attention that the lib(s) are installed for Python3 and not an older Python 2.7 that is probably installed on your system. Be carefull to use `pip3` and nor `pip`. +And please pay attention that the lib(s) are installed for Python3 and not an older Python 2.7 that is probably installed on your system. Be careful to use `pip3` and not `pip`. > Note: This module needs the module handling in SmartHomeNG to be activated. Make sure, that `use_modules`in `etc/smarthome.yaml` is **not** set to False! @@ -35,7 +35,7 @@ http: # starturl: backend # threads: 8 # showtraceback: True - +# webif_pagelength: 0 ``` #### user (optional) @@ -75,7 +75,7 @@ If set to `False` no list of pluins with web interface is shown under `smarthome If set to `True` a list of webservices is shown under `smarthomeNG.local:8384/services`. By default, ** showservicelist** is **False**. #### starturl (optional) -The name of the plugin that is started when calling url `smarthomeNG.local:8383` without further detailing that url. If you want to startup the **backend** plugin for example: You set `starturl: backend`. That results in a redirect which redirects `smarthomeNG.local:8383` to `smarthomeNG.local:8383/backend`. +The name of the plugin that is started when calling url `smarthomeNG.local:8383` without further detailing that url. If you want to startup the **backend** plugin for example: You set `starturl: backend`. That results in a redirect which redirects `smarthomeNG.local:8383` to `smarthomeNG.local:8383/backend`. if `starturl` is not specified or point to an url that does not exist, a redirect to `smarthomeNG.local:8383/plugins` will take place (if ** showpluginlist** is **True**). It points to a page that lists all plugins that have registered a html interface and allows you to start those interfaces. @@ -87,6 +87,10 @@ Number of worker threads to start by cherrypy (default 8, which may be too much #### showtraceback If set to **True, error-pages (except for error 404) will show the Python traceback for that error. +#### webif_pagelength +Amount of items being listed in a web interface table per page by default. +0 adjusts the table height automatically based on the height of the browser windows. +-1 shows all table entries on one page. ## API of module http @@ -101,7 +105,7 @@ try: self.mod_http = self._sh.get_module('http') except: self.mod_http = None - + if self.mod_http == None: # Do what is necessary if you can't start a web interface # for your plugin. For example: @@ -137,9 +141,9 @@ app_config = { For registering a web application/interface you have to call the `register_app` of module `http`: ``` -register_app(app_object, - appname, - app_config, +register_app(app_object, + appname, + app_config, pluginclass, instance, description) ``` @@ -151,9 +155,9 @@ appname = 'backend' # Name of the plugin pluginclass = self.__class__.__name__ instance = self.get_instance_name() -self.mod_http.register_app(Backend(self, self.updates_allowed, language, self.developer_mode, self.pypi_timeout), - appname, - app_config, +self.mod_http.register_app(Backend(self, self.updates_allowed, language, self.developer_mode, self.pypi_timeout), + appname, + app_config, pluginclass, instance, description='Administration interface for SmartHomeNG') ``` @@ -197,4 +201,3 @@ Returns the port under which the implemented webservices can be reached. - **pluginclass** - Class of the plugin - **instance** - Optional: Instance of the plugin (if multi-instance) - **description** - Optional: Description to be shown on page with services-list - diff --git a/modules/http/__init__.py b/modules/http/__init__.py old mode 100644 new mode 100755 index e5bd71f53c..3c149910ac --- a/modules/http/__init__.py +++ b/modules/http/__init__.py @@ -61,7 +61,7 @@ def filter(self, record): class Http(Module): - version = '1.6.0' + version = '1.7.0' _shortname = '' _longname = 'CherryPy http module for SmartHomeNG' @@ -126,6 +126,8 @@ def __init__(self, sh): self._showtraceback = self._parameters['showtraceback'] self._starturl = self._parameters['starturl'] + self._connectionretries = self._parameters['connectionretries'] + self._webif_pagelength = self._parameters['webif_pagelength'] except: self.logger.critical("Inconsistent module (invalid metadata definition)") self._init_complete = False @@ -251,7 +253,8 @@ def __init__(self, sh): { 'log.screen': False, 'log.access_file': '', - 'log.error_file': '' + 'log.error_file': '', + 'webif_pagelength': self._webif_pagelength } ) if self._use_tls: @@ -473,12 +476,13 @@ def _get_local_ip_address(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) connected = False count = 0 - while (not connected) and (count < 5): + while (not connected) and (count < self._connectionretries): try: s.connect(("10.10.10.10", 80)) connected = True except: count += 1 + self.logger.debug(f"Network access issue. Retry {count}/{self._connectionretries}") time.sleep(5) if connected: return s.getsockname()[0] diff --git a/modules/http/locale.yaml b/modules/http/locale.yaml index d7c54ddbec..d55dbbc42e 100644 --- a/modules/http/locale.yaml +++ b/modules/http/locale.yaml @@ -1,14 +1,14 @@ # translations for the web interface +#module_translations: module_translations: # Translations for the plugin specially for the web interface 'Test': {'de': '=', 'en': 'English test'} 'Aktualisieren': {'de': '=', 'en': 'Refresh'} 'Autom. Aktualisierung': {'de': '=', 'en': 'Auto-Refresh'} - 'Sekunden': {'de': '=', 'en': 'seconds'} 'Schließen': {'de': '=', 'en': 'Close'} 'Plugin': {'de': '=', 'en': '='} 'Gestoppt': {'de': '=', 'en': 'Stopped'} 'Aktiv': {'de': '=', 'en': 'Active'} 'Instanz': {'de': '=', 'en': 'Instance'} - '(De)Aktiviere automatische Seitenaktualisierung': {'de': '=', 'en': '(De)activate Automatic Page Refresh'} + '(De)Aktiviere automatische Seitenaktualisierung': {'de': '=', 'en': '(De)activate Automatic Page Refresh'} 'Aktualisierungsintervall in Sekunden': {'de': '=', 'en': 'Refresh Interval in Seconds'} diff --git a/modules/http/module.yaml b/modules/http/module.yaml old mode 100644 new mode 100755 index ace6ced8ee..be9120c815 --- a/modules/http/module.yaml +++ b/modules/http/module.yaml @@ -2,7 +2,7 @@ module: # Global module attributes classname: Http - version: 1.6.0 + version: 1.7.0 sh_minversion: 1.5b # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) description: @@ -12,6 +12,15 @@ module: parameters: # Definition of parameters to be configured in etc/module.yaml + connectionretries: + type: int + valid_min: 0 + valid_max: 100 + default: 5 + description: + de: Maximale Anzahl an Verbindungsversuchen zum Start von smarthomeNG, um die lokale IP zu eruieren. + en: Maximum number of connection retries to retrieve local IP address + fr: Nombre maximum de tentatives de connexion pour récupérer l'adresse IP locale user: type: str default: 'admin' @@ -154,3 +163,28 @@ parameters: de: Traceback bei Python Exceptions in der Fehlermeldung im Browser anzeigen. (Sollte in Produktionsinstallationen False sein) en: Show traceback of Python exceptions on the error page displayed in the browser. (Should be False in production environment) fr: Montrer les traces des exepctions Python sur la page d'erreurs affiché dans le navigateur. (Normallement 'False' dans en environnement de production) + + webif_pagelength: + type: int + default: 0 + valid_list: + - -1 + - 0 + - 25 + - 50 + - 100 + description: + de: 'Anzahl an Tabellen-Zeilen, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden. + 0 = automatisch (so viele in das Browser-Fenster passen) , -1 = alle + (Wirkt sich nur auf Web Interfaces mit sortierbaren Tabellen aus)' + en: 'Amount of table lines being listed in a web interface table per page by default. + 0 = automatic (as many as can be fitted into the browser window), -1 = all + (Only affects web interfaces with sortable tables)' + description_long: + de: 'Anzahl an Tabellen-Zeilen, die standardmäßig in einer Web Interface Tabelle pro Seite angezeigt werden.\n + Bei 0 wird die Tabelle automatisch an die Höhe des Browserfensters angepasst.\n + Bei -1 werden alle Tabelleneinträge auf einer Seite angezeigt.' + en: 'Amount of table lines being listed in a web interface table per page by default.\n + 0 adjusts the table height automatically based on the height of the browser windows.\n + + -1 shows all table entries on one page.' diff --git a/modules/http/webif/gstatic/css/smarthomeng.css b/modules/http/webif/gstatic/css/smarthomeng.css old mode 100644 new mode 100755 index a5e3a6a1ea..7be8be2c6c --- a/modules/http/webif/gstatic/css/smarthomeng.css +++ b/modules/http/webif/gstatic/css/smarthomeng.css @@ -31,6 +31,10 @@ plusIconlist-group-item.py-2.node-tree.node-selected { background-color: #ecf3f8; } +#datepicker > div.datepicker { + margin-left: auto; + margin-right: auto; +} table td.truncate { overflow: hidden; text-overflow: ellipsis; @@ -44,12 +48,15 @@ table td.truncate:active { } .notruncate { - overflow: visible; + overflow: visible!important; } +table th.datatable-responsive { + width: 10px; +} table.dataTableAdditional { display: none; - table-layout: fixed; + table-layout: auto; } tr.even td.sorting_1 { @@ -312,3 +319,21 @@ These are necessary for highligt effect in web interface when a value changes. C .shng_effect_standard { background-color: none; } + +#webif-toprow { + flex-wrap: nowrap; + -ms-flex-wrap: nowrap; +} + +#webif-headtable > table { + table-layout: auto; + padding-left: 15px; +} + +#webif-headtable > table>tbody>tr>td { + white-space: nowrap; +} + +#webif-plugininfo { + min-width: 185px; +} diff --git a/modules/http/webif/gstatic/datatables/dataTables.pageResize.min.js b/modules/http/webif/gstatic/datatables/dataTables.pageResize.min.js old mode 100644 new mode 100755 index 6cb26f8fb5..72c6c9c90b --- a/modules/http/webif/gstatic/datatables/dataTables.pageResize.min.js +++ b/modules/http/webif/gstatic/datatables/dataTables.pageResize.min.js @@ -1,34 +1,42 @@ -/*! - Copyright 2015 SpryMedia Ltd. - - License MIT - http://datatables.net/license/mit - - This feature plug-in for DataTables will automatically change the DataTables - page length in order to fit inside its container. This can be particularly - useful for control panels and other interfaces which resize dynamically with - the user's browser window instead of scrolling. - - Page resizing in DataTables can be enabled by using any one of the following - options: - - * Adding the class `pageResize` to the HTML table - * Setting the `pageResize` parameter in the DataTables initialisation to - be true - i.e. `pageResize: true` - * Setting the `pageResize` parameter to be true in the DataTables - defaults (thus causing all tables to have this feature) - i.e. - `$.fn.dataTable.defaults.pageResize = true`. - * Creating a new instance: `new $.fn.dataTable.PageResize( table );` where - `table` is a DataTable's API instance. - - For more detailed information please see: - http://datatables.net/blog/2015-04-10 - PageResize for DataTables v1.0.0 - 2015 SpryMedia Ltd - datatables.net/license -*/ +/** + * @summary PageResize + * @description Automatically alter the DataTables page length to fit the table + into a container + * @version 1.1.0 + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * + * This feature plug-in for DataTables will automatically change the DataTables + * page length in order to fit inside its container. This can be particularly + * useful for control panels and other interfaces which resize dynamically with + * the user's browser window instead of scrolling. + * + * Page resizing in DataTables can be enabled by using any one of the following + * options: + * + * * Adding the class `pageResize` to the HTML table + * * Setting the `pageResize` parameter in the DataTables initialisation to + * be true - i.e. `pageResize: true` + * * Setting the `pageResize` parameter to be true in the DataTables + * defaults (thus causing all tables to have this feature) - i.e. + * `DataTable.defaults.pageResize = true`. + * * Creating a new instance: `new DataTable.PageResize( table );` where + * `table` is a DataTable's API instance. + * + * For more detailed information please see: + * http://datatables.net/blog/2015-04-10 + */ $(window).resize(function(){ - $('#resize_wrapper').css('height', $(window).height() - $('#webif-navbar').outerHeight() - $('#webif-tabs').outerHeight() - $('.dataTables_info').outerHeight() - 30); + additional = 0; + if ( $( "#resize_wrapper > div#" + window.activeTab + ">div.mb-2" ).length ){ + $("#resize_wrapper > div#" + window.activeTab).find('div.mb-2').filter(':visible').each(function(){ + additional = additional + $(this).outerHeight(true); + }); + } + else if ( $( "#resize_wrapper > div#" + window.activeTab + ">div>div.mb-2" ).length ){ + $("#resize_wrapper > div#" + window.activeTab + ">div").find('div.mb-2').filter(':visible').each(function(){ + additional = additional + $(this).outerHeight(true); + }); + } + $('#resize_wrapper').css('height', $(window).height() - $('#webif-navbar').outerHeight() - $('#webif-tabs').outerHeight() - additional - $('.dataTables_info').outerHeight() - 5); }); -(function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return b(e,window,document)}):"object"===typeof exports?module.exports=function(e,f){e||(e=window);f&&f.fn.dataTable||(f=require("datatables.net")(e,f).$);return b(f,e,e.document)}:b(jQuery,window,document)})(function(b,e,f,p){var l=function(a,d){var c=a.table();this.s={dt:a,host:b(c.container()).parent(),header:b(c.header()),footer:b(c.footer()),body:b(c.body()),container:b(c.container()),table:b(c.node()), -delta:d};a=this.s.host;"static"===a.css("position")&&a.css("position","relative");this._attach();this._size()};l.prototype={_size:function(){var a=this.s,d=a.dt,c=d.table(),k=b(a.table).offset().top,g=b("tr",a.body);g=g.eq(1").css({position:"absolute",top:0,left:0,height:"100%",width:"100%",zIndex:-1}).attr("type","text/html");d[0].onload=function(){var c=this.contentDocument.body,k=c.offsetHeight;this.contentDocument.defaultView.onresize=function(){var g=c.clientHeight||c.offsetHeight;g!==k&&(k=g,a._size())}};d.appendTo(this.s.host).attr("data","about:blank")}};b.fn.dataTable.PageResize= -l;b.fn.DataTable.PageResize=l;b(f).on("preInit.dt",function(a,d){"dt"===a.namespace&&(a=new b.fn.dataTable.Api(d),(b(a.table().node()).hasClass("pageResize")||d.oInit.pageResize||b.fn.dataTable.defaults.pageResize)&&new l(a,d.oInit.pageResizeManualDelta))})}); +!function(i){"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return i(e,window,document)}):"object"==typeof exports?module.exports=function(e,t){return e=e||window,(t=t||("undefined"!=typeof window?require("jquery"):require("jquery")(e))).fn.dataTable||require("datatables.net")(e,t),i(t,0,e.document)}:i(jQuery,window,document)}(function(d,e,t,i){"use strict";function o(e,t){var i=e.table();this.s={dt:e,host:d(i.container()).parent(),header:d(i.header()),footer:d(i.footer()),body:d(i.body()),container:d(i.container()),table:d(i.node()),delta:t},this.sizes={offsetTop:this._getOffsetTop(),tableHeight:this._getTableHeight(),containerHeight:this._getContainerHeight(),headerHeight:this._getHeaderHeight(),footerHeight:this._getFooterHeight()};"static"===(i=this.s.host).css("position")&&i.css("position","relative");var o=function(){e.off(".pageResize",o),this.s.obj&&this.s.obj.remove()}.bind(this),s=(e.on("destroy.pageResize",o),this._attach(),"init.pageResize");e.on(s,function(){e.off(s),this._size()}.bind(this))}var s=d.fn.dataTable;return o.prototype={_size:function(){var e=this.s,t=e.dt,i=t.table(),o=d("tr",e.body),o=o.eq(1").css({position:"absolute",top:0,left:0,height:"100%",width:"100%",zIndex:-1}).attr("type","text/html");e[0].onload=function(){var e=this.contentDocument,t=e.body,i=t.offsetHeight;e.defaultView.onresize=function(){var e=t.clientHeight||t.offsetHeight;e!==i?(i=e,o._size()):o.sizes.offsetTop===o._getOffsetTop()&&o.sizes.containerHeight===o._getContainerHeight()&&o.sizes.tableHeight===o._getTableHeight()&&o.sizes.headerHeight===o._getHeaderHeight()&&o.sizes.footerHeight===o._getFooterHeight()||o._size()}},e.appendTo(this.s.host).attr("data","about:blank"),this.s.obj=e},_getOffsetTop:function(){return d(this.s.table).offset().top},_getTableHeight:function(){return this.s.table.height()},_getContainerHeight:function(){return this.s.container.height()},_getHeaderHeight:function(){return this.s.dt.table().header()?this.s.header.height():0},_getFooterHeight:function(){return this.s.dt.table().footer()?this.s.footer.height():0}},s.PageResize=o,d(t).on("preInit.dt",function(e,t){"dt"===e.namespace&&(e=new s.Api(t),d(e.table().node()).hasClass("pageResize")||t.oInit.pageResize||s.defaults.pageResize)&&new o(e,t.oInit.pageResizeManualDelta)}),s}); diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js index 63fe415e19..bdcd3353dd 100755 --- a/modules/http/webif/gstatic/datatables/datatables.defaults.js +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -23,6 +23,7 @@ $(window).bind('datatables_defaults', function() { try { top_offset = $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight(); + initialized = false; // Set datatable useful defaults $.extend( $.fn.dataTable.ext.classes, { "sTable": "table table-striped table-hover pluginList display dataTable dataTableAdditional" }); $.extend( $.fn.dataTable.defaults, { @@ -33,43 +34,92 @@ $(window).bind('datatables_defaults', function() { pageLength: 100, // default to "100" pagingType: "full_numbers", // include first and last in pagination menu colReorder: true, // enable colomn reorder by drag and drop - columnDefs: [{ targets: '_all', className: 'truncate' }], + columnDefs: [{className: 'dtr-control datatable-responsive', orderable: false, targets: 0}, { targets: '_all', className: 'truncate' }], + order: [[1, 'asc']], fixedHeader: {header: true, // header will always be visible on top of page when scrolling headerOffset: top_offset}, autoWidth: false, - initComplete: function () {$(this).show(); + initComplete: function () { + // update content on ordering - if activated + $(this).on( 'click', 'thead tr th', function () { + if ($(this).hasClass( "sorting" ) && window.initial_update == 'true'){ + console.log("Instant value update after sorting"); + shngGetUpdatedData(); + } + }); + // slightly change resize_wrapper when expanding/collapsing responsive rows to fix some issues + $(this).on( 'click', 'tbody tr td', function () { + if ($(this).hasClass( "datatable-responsive" )){ + window.toggle = window.toggle * -1 + $('#resize_wrapper').css('height', $(window).height() - $('#webif-navbar').outerHeight() - $('#webif-tabs').outerHeight() - additional - $('.dataTables_info').outerHeight() - 5 - window.toggle); + $(this).parent().parent().parent().DataTable().responsive.recalc(); + } + }); + // update content on searching - if activated + $(".dataTables_filter").change( function () { + if (window.initial_update == 'true'){ + console.log("Instant value update after search filter"); + shngGetUpdatedData(); + } + }); + // Warning if first column is not empty (for responsive + sign) + td_content = $(this).find('tbody').find('td:first-child').html(); + if (td_content != '' && td_content != 'No data available in table') + console.warn("First column has to be empty! The plugin author has to add an empty first column in thead and tbody of " + $(this).attr('id')); + this.api().columns.adjust(); this.api().responsive.recalc(); + initialized = true; + $(this).show(); + if (typeof window.row_count !== 'undefined' && window.row_count !== 'false') { + setTimeout(function() { window.row_count = $.fn.dataTable.tables({ visible: true, api: true }).rows( {page:'current'} ).count(); console.log("Row count after init is " + window.row_count);}, 200); + } + console.log("Instant value update is " + window.initial_update); + if (window.initial_update == 'true') { + setTimeout(function() { shngGetUpdatedData(); }, 200); + } setTimeout(function() { $(window).resize(); }, 2000);// show table (only) after init, adjust height of wrapper after 2s }, - responsive: {details: {renderer: $.fn.dataTable.Responsive.renderer.listHidden()}}, //makes it possible to update columns even if they are not shown as columns (but as collapsable items) + responsive: {details: {type: 'column', renderer: $.fn.dataTable.Responsive.renderer.listHidden()}}, //makes it possible to update columns even if they are not shown as columns (but as collapsable items) preDrawCallback: function (oSettings) { pageScrollPos = $(oSettings.nTableWrapper).find('.dataTables_scrollBody').scrollTop(); bodyScrollPos = $('html, body').scrollTop(); + }, drawCallback: function(oSettings) { // hide pagination if not needed $(window).resize(); + console.log("draw datatable " + this.attr('id')); if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); } else { $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); $(oSettings.nTableWrapper).find('.paginate_button').on('click', function(){ // scroll to top on page change - $('html, body').animate({ - scrollTop: $('#'+oSettings.sTableId).offset().top - top_offset - }, 'slow'); + $('html, body').animate({ + scrollTop: $('#'+oSettings.sTableId).offset().top - top_offset - $(oSettings.nTableWrapper).find('.dataTables_filter').outerHeight() - 10 + }, 'slow'); + console.log("Instant value update is " + window.initial_update); + if (window.initial_update == 'true') { + setTimeout(function() { shngGetUpdatedData(); }, 200); + } }); } + $('html, body').scrollTop(bodyScrollPos); this.api().fixedHeader.enable( false ); this.api().fixedHeader.enable( true ); this.api().fixedHeader.adjust(); - - $('html, body').scrollTop(bodyScrollPos); + this.api().responsive.recalc(); $(this).addClass( "display" ); - if (typeof window.row_count !== 'undefined') { - window.row_count = $.fn.dataTable.tables({ visible: true, api: true }).rows( {page:'current'} ).count(); + /* + if (initialized == true) { + $(this).find('tbody').find('tr:nth-child(3)').find('td:first-child').click(); + } + */ + if (typeof window.row_count !== 'undefined' && window.row_count !== 'false' && initialized == true) { + setTimeout(function() { window.row_count = $.fn.dataTable.tables({ visible: true, api: true }).rows( {page:'current'} ).count(); console.log("Row count after draw is " + window.row_count);initialized = false;}, 200); } + }, createdRow: function (row, data, index) { $(row).hide().fadeIn('slow'); @@ -77,8 +127,8 @@ $(window).bind('datatables_defaults', function() { this.api().columns.adjust(); this.api().fixedHeader.adjust(); this.api().responsive.recalc(); - if (typeof window.row_count !== 'undefined') { - window.row_count = $.fn.dataTable.tables({ visible: true, api: true }).rows( {page:'current'} ).count(); + if (typeof window.row_count !== 'undefined' && window.row_count !== 'false' && initialized == true) { + setTimeout(function() { window.row_count = $.fn.dataTable.tables({ visible: true, api: true }).rows( {page:'current'} ).count(); console.log("Row count after row creation is " + window.row_count);}, 200); } } @@ -86,12 +136,24 @@ $(window).bind('datatables_defaults', function() { // Set date format for correct sorting of columns containing date strings $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss'); $.fn.dataTable.moment('YYYY-MM-DD HH:mm:ss'); + $.fn.dataTable.moment('MM/DD/YYYY HH:mm:ss'); + $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss.SSSS'); + $.fn.dataTable.moment('YYYY-MM-DD HH:mm:ss.SSSS'); + $.fn.dataTable.moment('MM/DD/YYYY HH:mm:ss.SSSS'); $.fn.dataTable.moment('DD.MM.'); $.fn.dataTable.moment('DD.MM.YY'); + $.fn.dataTable.moment('DD.MM.YYYY'); + $.fn.dataTable.moment('MM/DD/YYYY'); + $.fn.dataTable.moment('MM/DD/YY'); $.fn.dataTable.moment('HH:mm'); + $.fn.dataTable.moment('HH:mm:ss'); + $.fn.dataTable.moment('HH:mm:ss.SSSS'); + $.fn.dataTable.moment('HH:mm DD.MM.YYYY'); + $.fn.dataTable.moment('HH:mm:ss DD.MM.YYYY'); + $.fn.dataTable.moment('HH:mm:ss.SSSS DD.MM.YYYY'); $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){ - + window.activeTab = $(this).attr("href").replace("#", "") ; $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.adjust(); $.fn.dataTable.tables({ visible: true, api: true }).responsive.recalc(); diff --git a/modules/http/webif/gstatic/datatables/datatables.min.css b/modules/http/webif/gstatic/datatables/datatables.min.css index 0048e93fba..ea880ee5a3 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.css +++ b/modules/http/webif/gstatic/datatables/datatables.min.css @@ -4,13 +4,13 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/cr-1.5.6/fh-3.2.4/r-2.3.0 + * https://datatables.net/download/#dt/dt-1.13.3/cr-1.6.1/fh-3.3.1/r-2.4.0 * * Included libraries: - * DataTables 1.12.1, ColReorder 1.5.6, FixedHeader 3.2.4, Responsive 2.3.0 + * DataTables 1.13.3, ColReorder 1.6.1, FixedHeader 3.3.1, Responsive 2.4.0 */ -table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{height:1em;width:1em;margin-top:-9px;display:inline-block;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable tr.dt-hasChild td.dt-control:before{content:"-";background-color:#d33333}table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting_asc_disabled,table.dataTable thead>tr>th.sorting_desc_disabled,table.dataTable thead>tr>td.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting_asc_disabled,table.dataTable thead>tr>td.sorting_desc_disabled{cursor:pointer;position:relative;padding-right:26px}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after{position:absolute;display:block;opacity:.125;right:10px;line-height:9px;font-size:.9em}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:before{bottom:50%;content:"▴"}table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:after{top:50%;content:"▾"}table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting_asc_disabled:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dataTables_scrollBody table.dataTable thead>tr>th:before,div.dataTables_scrollBody table.dataTable thead>tr>th:after,div.dataTables_scrollBody table.dataTable thead>tr>td:before,div.dataTables_scrollBody table.dataTable thead>tr>td:after{display:none}div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:2px}div.dataTables_processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dataTables_processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgba(13, 110, 253, 0.9);animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dataTables_processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px;border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 10px 6px 10px;border-top:1px solid rgba(0, 0, 0, 0.3)}table.dataTable tbody tr{background-color:transparent}table.dataTable tbody tr.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.9);color:white}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid rgba(0, 0, 0, 0.15);border-right:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe>tbody>tr.odd>*,table.dataTable.display>tbody>tr.odd>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.023)}table.dataTable.stripe>tbody>tr.odd.selected>*,table.dataTable.display>tbody>tr.odd.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.923)}table.dataTable.hover>tbody>tr:hover>*,table.dataTable.display>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.035)}table.dataTable.hover>tbody>tr.selected:hover>*,table.dataTable.display>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.935)}table.dataTable.order-column>tbody tr>.sorting_1,table.dataTable.order-column>tbody tr>.sorting_2,table.dataTable.order-column>tbody tr>.sorting_3,table.dataTable.display>tbody tr>.sorting_1,table.dataTable.display>tbody tr>.sorting_2,table.dataTable.display>tbody tr>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.order-column>tbody tr.selected>.sorting_1,table.dataTable.order-column>tbody tr.selected>.sorting_2,table.dataTable.order-column>tbody tr.selected>.sorting_3,table.dataTable.display>tbody tr.selected>.sorting_1,table.dataTable.display>tbody tr.selected>.sorting_2,table.dataTable.display>tbody tr.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919)}table.dataTable.display>tbody>tr.odd>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.054)}table.dataTable.display>tbody>tr.odd>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.047)}table.dataTable.display>tbody>tr.odd>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.039)}table.dataTable.display>tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.954)}table.dataTable.display>tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.947)}table.dataTable.display>tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.939)}table.dataTable.display>tbody>tr.even>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.display>tbody>tr.even>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.011)}table.dataTable.display>tbody>tr.even>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.003)}table.dataTable.display>tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919)}table.dataTable.display>tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.911)}table.dataTable.display>tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.903)}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.082)}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.074)}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.062)}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.982)}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.974)}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.962)}table.dataTable.no-footer{border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid rgba(0, 0, 0, 0.3);background-color:rgba(230, 230, 230, 0.1);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1)));background:-webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid rgba(0, 0, 0, 0.3)}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} +:root{--dt-row-selected: 13, 110, 253;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{height:1em;width:1em;margin-top:-9px;display:inline-block;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable tr.dt-hasChild td.dt-control:before{content:"-";background-color:#d33333}table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting_asc_disabled,table.dataTable thead>tr>th.sorting_desc_disabled,table.dataTable thead>tr>td.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting_asc_disabled,table.dataTable thead>tr>td.sorting_desc_disabled{cursor:pointer;position:relative;padding-right:26px}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after{position:absolute;display:block;opacity:.125;right:10px;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:before{bottom:50%;content:"▲"}table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:after{top:50%;content:"▼"}table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting_asc_disabled:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dataTables_scrollBody>table.dataTable>thead>tr>th:before,div.dataTables_scrollBody>table.dataTable>thead>tr>th:after,div.dataTables_scrollBody>table.dataTable>thead>tr>td:before,div.dataTables_scrollBody>table.dataTable>thead>tr>td:after{display:none}div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:2px}div.dataTables_processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dataTables_processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(13, 110, 253);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dataTables_processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px;border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 10px 6px 10px;border-top:1px solid rgba(0, 0, 0, 0.3)}table.dataTable tbody tr{background-color:transparent}table.dataTable tbody tr.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.9);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9);color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.dataTable tbody tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid rgba(0, 0, 0, 0.15);border-right:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe>tbody>tr.odd>*,table.dataTable.display>tbody>tr.odd>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.023)}table.dataTable.stripe>tbody>tr.odd.selected>*,table.dataTable.display>tbody>tr.odd.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.923);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.923))}table.dataTable.hover>tbody>tr:hover>*,table.dataTable.display>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.035)}table.dataTable.hover>tbody>tr.selected:hover>*,table.dataTable.display>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px #0d6efd !important;box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 1)) !important}table.dataTable.order-column>tbody tr>.sorting_1,table.dataTable.order-column>tbody tr>.sorting_2,table.dataTable.order-column>tbody tr>.sorting_3,table.dataTable.display>tbody tr>.sorting_1,table.dataTable.display>tbody tr>.sorting_2,table.dataTable.display>tbody tr>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.order-column>tbody tr.selected>.sorting_1,table.dataTable.order-column>tbody tr.selected>.sorting_2,table.dataTable.order-column>tbody tr.selected>.sorting_3,table.dataTable.display>tbody tr.selected>.sorting_1,table.dataTable.display>tbody tr.selected>.sorting_2,table.dataTable.display>tbody tr.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919))}table.dataTable.display>tbody>tr.odd>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.054)}table.dataTable.display>tbody>tr.odd>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.047)}table.dataTable.display>tbody>tr.odd>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.039)}table.dataTable.display>tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.954);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.954))}table.dataTable.display>tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.947);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.947))}table.dataTable.display>tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.939);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.939))}table.dataTable.display>tbody>tr.even>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.display>tbody>tr.even>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.011)}table.dataTable.display>tbody>tr.even>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.003)}table.dataTable.display>tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919))}table.dataTable.display>tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.911);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.911))}table.dataTable.display>tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.903);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.903))}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.082)}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.074)}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.062)}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.982);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.982))}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.974);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.974))}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.962);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.962))}table.dataTable.no-footer{border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable.compact thead th,table.dataTable.compact thead td,table.dataTable.compact tfoot th,table.dataTable.compact tfoot td,table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;color:inherit !important;border:1px solid transparent;border-radius:2px;background:transparent}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:inherit !important;border:1px solid rgba(0, 0, 0, 0.3);background-color:rgba(230, 230, 230, 0.1);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1)));background:-webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:inherit}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid rgba(0, 0, 0, 0.3)}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} table.DTCR_clonedTable.dataTable{position:absolute !important;background-color:rgba(255, 255, 255, 0.7);z-index:202}div.DTCR_pointer{width:1px;background-color:#0259c4;z-index:201} diff --git a/modules/http/webif/gstatic/datatables/datatables.min.js b/modules/http/webif/gstatic/datatables/datatables.min.js index 54b82ef788..1805bcc840 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.js +++ b/modules/http/webif/gstatic/datatables/datatables.min.js @@ -4,352 +4,39 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.12.1/cr-1.5.6/fh-3.2.4/r-2.3.0 + * https://datatables.net/download/#dt/dt-1.13.3/cr-1.6.1/fh-3.3.1/r-2.4.0 * * Included libraries: - * DataTables 1.12.1, ColReorder 1.5.6, FixedHeader 3.2.4, Responsive 2.3.0 + * DataTables 1.13.3, ColReorder 1.6.1, FixedHeader 3.3.1, Responsive 2.4.0 */ -/*! - SpryMedia Ltd. - - This source file is free software, available under the following license: - MIT license - http://datatables.net/license - - This source file is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - - For details please refer to: http://www.datatables.net - DataTables 1.12.1 - ©2008-2022 SpryMedia Ltd - datatables.net/license -*/ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(l,y,A){l instanceof String&&(l=String(l));for(var q=l.length,E=0;E").css({position:"fixed",top:0,left:-1*l(y).scrollLeft(),height:1, -width:1,overflow:"hidden"}).append(l("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(l("
").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}l.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth} -function Gb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function cb(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ia(a,d,l(b).data())}function Ia(a,b,c){b=a.aoColumns[b]; -var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(Eb(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),h=b.sClass,l.extend(b,c),Y(b,c,"sWidth","sWidthOrig"),h!==b.sClass&&(b.sClass=h+" "+b.sClass),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]), -Y(b,c,"aDataSort"));var f=b.mData,g=ma(f),k=b.mRender?ma(b.mRender):null;c=function(m){return"string"===typeof m&&-1!==m.indexOf("@")};b._bAttrSrc=l.isPlainObject(f)&&(c(f.sort)||c(f.type)||c(f.filter));b._setter=null;b.fnGetData=function(m,n,p){var t=g(m,n,q,p);return k&&n?k(t,n,m,p):t};b.fnSetData=function(m,n,p){return ha(f)(m,n,p)};"number"!==typeof f&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc", -b.asSorting);b.bSortable&&(a||c)?a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function sa(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;db(a);for(var c=0,d=b.length;cm[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;pb&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function va(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c").appendTo(d));var k=0;for(b=g.length;k=a.fnRecordsDisplay()?0:d,a.iInitDisplayStart=-1);c=F(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==l.inArray(!1,c))V(a,!1);else{c=[];var e=0;d=a.asStripeClasses;var h=d.length,f=a.oLanguage,g="ssp"==Q(a),k=a.aiDisplay,m=a._iDisplayStart,n=a.fnDisplayEnd();a.bDrawing=!0;if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!g)a.iDraw++;else if(!a.bDestroying&&!b){Kb(a);return}if(0!==k.length)for(b=g?a.aoData.length:n,f=g?0:m;f",{"class":h?d[0]:""}).append(l("
",{valign:"top",colSpan:na(a),"class":a.oClasses.sRowEmpty}).html(e))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0], -ib(a),m,n,k]);F(a,"aoFooterCallback","footer",[l(a.nTFoot).children("tr")[0],ib(a),m,n,k]);d=l(a.nTBody);d.children().detach();d.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Lb(a);d?ya(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Mb(a){var b=a.oClasses,c=l(a.nTable);c=l("
").insertBefore(c);var d=a.oFeatures, -e=l("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),f,g,k,m,n,p,t=0;t")[0];m=h[t+1];if("'"==m||'"'==m){n="";for(p=2;h[t+p]!=m;)n+=h[t+p],p++;"H"==n?n=b.sJUIHeader:"F"==n&&(n=b.sJUIFooter);-1!=n.indexOf(".")?(m=n.split("."),k.id=m[0].substr(1,m[0].length-1),k.className=m[1]):"#"==n.charAt(0)?k.id=n.substr(1, -n.length-1):k.className=n;t+=p}e.append(k);e=l(k)}else if(">"==g)e=e.parent();else if("l"==g&&d.bPaginate&&d.bLengthChange)f=Nb(a);else if("f"==g&&d.bFilter)f=Ob(a);else if("r"==g&&d.bProcessing)f=Pb(a);else if("t"==g)f=Qb(a);else if("i"==g&&d.bInfo)f=Rb(a);else if("p"==g&&d.bPaginate)f=Sb(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,m=k.length;p',g=d.sSearch;g=g.match(/_INPUT_/)?g.replace("_INPUT_",f):g+f;b=l("
",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("
").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){pb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Sb(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("
").addClass(a.oClasses.sPaging+ -b)[0];var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;nh&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).append("
").insertBefore(a.nTable)[0]}function V(a, -b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none");F(a,null,"processing",[a,b])}function Qb(a){var b=l(a.nTable),c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,f=b.children("caption"),g=f.length?f[0]._captionSide:null,k=l(b[0].cloneNode(!1)),m=l(b[0].cloneNode(!1)),n=b.children("tfoot");n.length||(n=null);k=l("
",{"class":h.sScrollWrapper}).append(l("
",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0, -width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left",0).append("top"===g?f:null).append(b.children("thead"))))).append(l("
",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));n&&k.append(l("
",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollFootInner}).append(m.removeAttr("id").css("margin-left", -0).append("bottom"===g?f:null).append(b.children("tfoot")))));b=k.children();var p=b[0];h=b[1];var t=n?b[2]:null;if(d)l(h).on("scroll.DT",function(v){v=this.scrollLeft;p.scrollLeft=v;n&&(t.scrollLeft=v)});l(h).css("max-height",e);c.bCollapse||l(h).css("height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Ja,sName:"scrolling"});return k[0]}function Ja(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=l(a.nScrollHead),f=h[0].style,g=h.children("div"),k= -g[0].style,m=g.children("table");g=a.nScrollBody;var n=l(g),p=g.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead),w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,ba=a.oBrowser,L=ba.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],fa=[],Z,Ba=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var X=g.scrollHeight>g.clientHeight;if(a.scrollBarVis!==X&&a.scrollBarVis!==q)a.scrollBarVis=X,sa(a); -else{a.scrollBarVis=X;w.children("thead, tfoot").remove();if(G){X=G.clone().prependTo(w);var ca=G.find("tr");var Ca=X.find("tr");X.find("[id]").removeAttr("id")}var Ua=x.clone().prependTo(w);x=x.find("tr");X=Ua.find("tr");Ua.find("th, td").removeAttr("tabindex");Ua.find("[id]").removeAttr("id");c||(p.width="100%",h[0].style.width="100%");l.each(Pa(a,Ua),function(D,W){Z=ta(a,D);W.style.width=a.aoColumns[Z].sWidth});G&&da(function(D){D.style.width=""},Ca);h=w.outerWidth();""===c?(C.width="100%",L&& -(w.find("tbody").height()>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()):""!==d&&(C.width=K(d),h=w.outerWidth());da(Ba,X);da(function(D){var W=y.getComputedStyle?y.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},X);da(function(D,W){D.style.width=O[W]},x);l(X).css("height",0);G&&(da(Ba,Ca),da(function(D){fa.push(D.innerHTML);I.push(K(l(D).css("width")))},Ca),da(function(D,W){D.style.width=I[W]},ca),l(Ca).height(0));da(function(D, -W){D.innerHTML='
'+H[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=O[W]},X);G&&da(function(D,W){D.innerHTML='
'+fa[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},Ca);Math.round(w.outerWidth())g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&& -(C.width=K(ca-b)),""!==c&&""===d||ea(a,1,"Possible column misalignment",6)):ca="100%";p.width=K(ca);f.width=K(ca);G&&(a.nScrollFoot.style.width=K(ca));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(ba.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead")); -n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function da(a,b,c){for(var d=0,e=0,h=b.length,f,g;e").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Pa(a,g.find("thead")[0]); -for(v=0;v").css({width:w.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h? -(g.css("width","auto"),g.removeAttr("width"),g.width()").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function dc(a,b){var c=ec(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:l("
").html(T(a,c,b,"display"))[0]}function ec(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;hd&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function oa(a){var b= -[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&&!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d);e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;aG?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return CG?1:0}):f.sort(function(n,p){var t,v=g.length, -x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;tG?1:0})}a.bSorted=!0}function gc(a){var b=a.aoColumns,c=oa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0e?e+1:3))}e=0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function fc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ua(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g=e.length?[0,m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,$b(b.search));if(b.columns){f=0;for(d=b.columns.length;f=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function lb(a,b){a=a.renderer;var c=u.ext.renderer[b]; -return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Ea(a,b){var c=ic.numbers_length,d=Math.floor(c/2);b<=c?a=pa(0,b):a<=d?(a=pa(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=pa(b-(c-2),b):(a=pa(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function bb(a){l.each({num:function(b){return Xa(b,a)},"num-fmt":function(b){return Xa(b, -a,vb)},"html-num":function(b){return Xa(b,a,Ya)},"html-num-fmt":function(b){return Xa(b,a,Ya,vb)}},function(b,c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function jc(a,b,c,d,e){return y.moment?a[b](e):y.luxon?a[c](e):d?a[d](e):a}function Za(a,b,c){if(y.moment){var d=y.moment.utc(a,b,c,!0);if(!d.isValid())return null}else if(y.luxon){d=b?y.luxon.DateTime.fromFormat(a,b):y.luxon.DateTime.fromISO(a);if(!d.isValid)return null;d.setLocale(c)}else b?(kc|| -alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17"),kc=!0):d=new Date(a);return d}function wb(a){return function(b,c,d,e){0===arguments.length?(d="en",b=c=null):1===arguments.length?(d="en",c=b,b=null):2===arguments.length&&(d=c,c=b,b=null);var h="datetime-"+c;u.ext.type.order[h]||(u.ext.type.detect.unshift(function(f){return f===h?h:!1}),u.ext.type.order[h+"-asc"]=function(f,g){f=f.valueOf();g=g.valueOf();return f===g?0:fg?-1:1});return function(f,g){if(null===f||f===q)"--now"===e?(f=new Date,f=new Date(Date.UTC(f.getFullYear(),f.getMonth(),f.getDate(),f.getHours(),f.getMinutes(),f.getSeconds()))):f="";if("type"===g)return h;if(""===f)return"sort"!==g?"":Za("0000-01-01 00:00:00",null,d);if(null!==c&&b===c&&"sort"!==g&&"type"!==g&&!(f instanceof Date))return f;var k=Za(f,b,d);if(null===k)return f;if("sort"===g)return k;f=null===c?jc(k,"toDate","toJSDate", -"")[a]():jc(k,"format","toFormat","toISOString",c);return"display"===g?$a(f):f}}}function lc(a){return function(){var b=[Wa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Wa(this[M.iApiIndex])):new B(this)};this.fnAddData=function(f, -g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f);(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ja(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m=this.api(!0); -f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw();return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data():k.row(f).data()|| -null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node():g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]};this.fnPageChange= -function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g);(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Wa(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust();(m===q||m)&& -p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal;for(var h in u.ext.internal)h&&(this[h]=lc(h));this.each(function(){var f={},g=1").appendTo(t));r.nTHead=H[0];var fa=t.children("tbody");0===fa.length&&(fa=l("
",{valign:"top",colSpan:T(t),class:t.oClasses.sRowEmpty}).html(e))[0]}R(t,"aoHeaderCallback","header",[P(t.nTHead).children("tr")[0],ht(t),n,c,u]),R(t,"aoFooterCallback","footer",[P(t.nTFoot).children("tr")[0],ht(t),n,c,u]);s=P(t.nTBody);s.children().detach(),s.append(P(a)),R(t,"aoDrawCallback","draw",[t]),t.bSorted=!1,t.bFiltered=!1,t.bDrawing=!1}}function u(t,e){var n=t.oFeatures,a=n.bSort,n=n.bFilter;a&&ie(t),n?Rt(t,t.oPreviousSearch):t.aiDisplay=t.aiDisplayMaster.slice(),!0!==e&&(t._iDisplayStart=0),t._drawHold=e,v(t),t._drawHold=!1}function _t(t){for(var e,n,a,r,o,i,l,s=t.oClasses,u=P(t.nTable),u=P("
").insertBefore(u),c=t.oFeatures,f=P("
",{id:t.sTableId+"_wrapper",class:s.sWrapper+(t.nTFoot?"":" "+s.sNoFooter)}),d=(t.nHolding=u[0],t.nTableWrapper=f[0],t.nTableReinsertBefore=t.nTable.nextSibling,t.sDom.split("")),h=0;h")[0],"'"==(r=d[h+1])||'"'==r){for(o="",i=2;d[h+i]!=r;)o+=d[h+i],i++;"H"==o?o=s.sJUIHeader:"F"==o&&(o=s.sJUIFooter),-1!=o.indexOf(".")?(l=o.split("."),a.id=l[0].substr(1,l[0].length-1),a.className=l[1]):"#"==o.charAt(0)?a.id=o.substr(1,o.length-1):a.className=o,h+=i}f.append(a),f=P(a)}else if(">"==n)f=f.parent();else if("l"==n&&c.bPaginate&&c.bLengthChange)e=$t(t);else if("f"==n&&c.bFilter)e=Lt(t);else if("r"==n&&c.bProcessing)e=Zt(t);else if("t"==n)e=Kt(t);else if("i"==n&&c.bInfo)e=Ut(t);else if("p"==n&&c.bPaginate)e=zt(t);else if(0!==C.ext.feature.length)for(var p=C.ext.feature,g=0,b=p.length;g',s=(s=r.sSearch).match(/_INPUT_/)?s.replace("_INPUT_",l):s+l,l=P("
",{id:i.f?null:a+"_filter",class:t.sFilter}).append(P("
").addClass(t.sLength);return a.aanFeatures.l||(c[0].id=e+"_length"),c.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",l[0].outerHTML)),P("select",c).val(a._iDisplayLength).on("change.DT",function(t){Gt(a,P(this).val()),v(a)}),P(a.nTable).on("length.dt.DT",function(t,e,n){a===e&&P("select",c).val(n)}),c[0]}function zt(t){function c(t){v(t)}var e=t.sPaginationType,f=C.ext.pager[e],d="function"==typeof f,e=P("
").addClass(t.oClasses.sPaging+e)[0],h=t.aanFeatures;return d||f.fnInit(t,e,c),h.p||(e.id=t.sTableId+"_paginate",t.aoDrawCallback.push({fn:function(t){if(d)for(var e=t._iDisplayStart,n=t._iDisplayLength,a=t.fnRecordsDisplay(),r=-1===n,o=r?0:Math.ceil(e/n),i=r?1:Math.ceil(a/n),l=f(o,i),s=0,u=h.p.length;s",{id:t.aanFeatures.r?null:t.sTableId+"_processing",class:t.oClasses.sProcessing,role:"status"}).html(t.oLanguage.sProcessing).append("
").insertBefore(t.nTable)[0]}function D(t,e){t.oFeatures.bProcessing&&P(t.aanFeatures.r).css("display",e?"block":"none"),R(t,null,"processing",[t,e])}function Kt(t){var e,n,a,r,o,i,l,s,u,c,f,d,h=P(t.nTable),p=t.oScroll;return""===p.sX&&""===p.sY?t.nTable:(e=p.sX,n=p.sY,a=t.oClasses,o=(r=h.children("caption")).length?r[0]._captionSide:null,s=P(h[0].cloneNode(!1)),i=P(h[0].cloneNode(!1)),u=function(t){return t?M(t):null},(l=h.children("tfoot")).length||(l=null),s=P(f="
",{class:a.sScrollWrapper}).append(P(f,{class:a.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:e?u(e):"100%"}).append(P(f,{class:a.sScrollHeadInner}).css({"box-sizing":"content-box",width:p.sXInner||"100%"}).append(s.removeAttr("id").css("margin-left",0).append("top"===o?r:null).append(h.children("thead"))))).append(P(f,{class:a.sScrollBody}).css({position:"relative",overflow:"auto",width:u(e)}).append(h)),l&&s.append(P(f,{class:a.sScrollFoot}).css({overflow:"hidden",border:0,width:e?u(e):"100%"}).append(P(f,{class:a.sScrollFootInner}).append(i.removeAttr("id").css("margin-left",0).append("bottom"===o?r:null).append(h.children("tfoot"))))),u=s.children(),c=u[0],f=u[1],d=l?u[2]:null,e&&P(f).on("scroll.DT",function(t){var e=this.scrollLeft;c.scrollLeft=e,l&&(d.scrollLeft=e)}),P(f).css("max-height",n),p.bCollapse||P(f).css("height",n),t.nScrollHead=c,t.nScrollBody=f,t.nScrollFoot=d,t.aoDrawCallback.push({fn:Qt,sName:"scrolling"}),s[0])}function Qt(n){function t(t){(t=t.style).paddingTop="0",t.paddingBottom="0",t.borderTopWidth="0",t.borderBottomWidth="0",t.height=0}var e,a,r,o,i,l=n.oScroll,s=l.sX,u=l.sXInner,c=l.sY,l=l.iBarWidth,f=P(n.nScrollHead),d=f[0].style,h=f.children("div"),p=h[0].style,h=h.children("table"),g=n.nScrollBody,b=P(g),m=g.style,S=P(n.nScrollFoot).children("div"),v=S.children("table"),y=P(n.nTHead),D=P(n.nTable),_=D[0],C=_.style,w=n.nTFoot?P(n.nTFoot):null,T=n.oBrowser,x=T.bScrollOversize,A=(H(n.aoColumns,"nTh"),[]),I=[],F=[],L=[],R=g.scrollHeight>g.clientHeight;n.scrollBarVis!==R&&n.scrollBarVis!==N?(n.scrollBarVis=R,O(n)):(n.scrollBarVis=R,D.children("thead, tfoot").remove(),w&&(R=w.clone().prependTo(D),i=w.find("tr"),a=R.find("tr"),R.find("[id]").removeAttr("id")),R=y.clone().prependTo(D),y=y.find("tr"),e=R.find("tr"),R.find("th, td").removeAttr("tabindex"),R.find("[id]").removeAttr("id"),s||(m.width="100%",f[0].style.width="100%"),P.each(wt(n,R),function(t,e){r=rt(n,t),e.style.width=n.aoColumns[r].sWidth}),w&&k(function(t){t.style.width=""},a),f=D.outerWidth(),""===s?(C.width="100%",x&&(D.find("tbody").height()>g.offsetHeight||"scroll"==b.css("overflow-y"))&&(C.width=M(D.outerWidth()-l)),f=D.outerWidth()):""!==u&&(C.width=M(u),f=D.outerWidth()),k(t,e),k(function(t){var e=j.getComputedStyle?j.getComputedStyle(t).width:M(P(t).width());F.push(t.innerHTML),A.push(e)},e),k(function(t,e){t.style.width=A[e]},y),P(e).css("height",0),w&&(k(t,a),k(function(t){L.push(t.innerHTML),I.push(M(P(t).css("width")))},a),k(function(t,e){t.style.width=I[e]},i),P(a).height(0)),k(function(t,e){t.innerHTML='
'+F[e]+"
",t.childNodes[0].style.height="0",t.childNodes[0].style.overflow="hidden",t.style.width=A[e]},e),w&&k(function(t,e){t.innerHTML='
'+L[e]+"
",t.childNodes[0].style.height="0",t.childNodes[0].style.overflow="hidden",t.style.width=I[e]},a),Math.round(D.outerWidth())g.offsetHeight||"scroll"==b.css("overflow-y")?f+l:f,x&&(g.scrollHeight>g.offsetHeight||"scroll"==b.css("overflow-y"))&&(C.width=M(o-l)),""!==s&&""===u||W(n,1,"Possible column misalignment",6)):o="100%",m.width=M(o),d.width=M(o),w&&(n.nScrollFoot.style.width=M(o)),c||x&&(m.height=M(_.offsetHeight+l)),R=D.outerWidth(),h[0].style.width=M(R),p.width=M(R),y=D.height()>g.clientHeight||"scroll"==b.css("overflow-y"),p[i="padding"+(T.bScrollbarLeft?"Left":"Right")]=y?l+"px":"0px",w&&(v[0].style.width=M(R),S[0].style.width=M(R),S[0].style[i]=y?l+"px":"0px"),D.children("colgroup").insertBefore(D.children("thead")),b.trigger("scroll"),!n.bSorted&&!n.bFiltered||n._drawHold||(g.scrollTop=0))}function k(t,e,n){for(var a,r,o=0,i=0,l=e.length;i/g;function ee(t){var e,n,a=t.nTable,r=t.aoColumns,o=t.oScroll,i=o.sY,l=o.sX,o=o.sXInner,s=r.length,u=it(t,"bVisible"),c=P("th",t.nTHead),f=a.getAttribute("width"),d=a.parentNode,h=!1,p=t.oBrowser,g=p.bScrollOversize,b=a.style.width;for(b&&-1!==b.indexOf("%")&&(f=b),D=0;D").appendTo(b.find("tbody")));for(b.find("thead, tfoot").remove(),b.append(P(t.nTHead).clone()).append(P(t.nTFoot).clone()),b.find("tfoot th, tfoot td").css("width",""),c=wt(t,b.find("thead")[0]),D=0;D").css({width:e.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(t.aoData.length)for(D=0;D").css(l||i?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(b).appendTo(d),y=(l&&o?b.width(o):l?(b.css("width","auto"),b.removeAttr("width"),b.width()").css("width",M(t)).appendTo(e||y.body))[0].offsetWidth,t.remove(),e):0}function re(t,e){var n,a=oe(t,e);return a<0?null:(n=t.aoData[a]).nTr?n.anCells[e]:P("
").html(S(t,a,e,"display"))[0]}function oe(t,e){for(var n,a=-1,r=-1,o=0,i=t.aoData.length;oa&&(a=n.length,r=o);return r}function M(t){return null===t?"0px":"number"==typeof t?t<0?"0px":t+"px":t.match(/\d$/)?t+"px":t}function I(t){function e(t){t.length&&!Array.isArray(t[0])?h.push(t):P.merge(h,t)}var n,a,r,o,i,l,s,u=[],c=t.aoColumns,f=t.aaSortingFixed,d=P.isPlainObject(f),h=[];for(Array.isArray(f)&&e(f),d&&f.pre&&e(f.pre),e(t.aaSorting),d&&f.post&&e(f.post),n=0;n/g,""),u=i.nTh;u.removeAttribute("aria-sort"),i=i.bSortable?s+("asc"===(0=o.length?[0,e[1]]:e)})),t.search!==N&&P.extend(n.oPreviousSearch,Bt(t.search)),t.columns){for(a=0,r=t.columns.length;a
'+f.title+": "+f.data+"
').append(c)}}};u.defaults={breakpoints:u.breakpoints,auto:!0,details:{display:u.display.childRow,renderer:u.renderer.listHidden(), -target:0,type:"inline"},orthogonal:"display"};var C=b.fn.dataTable.Api;C.register("responsive()",function(){return this});C.register("responsive.index()",function(a){a=b(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});C.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});C.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(), -a._responsive._resize())})});C.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==b.inArray(!1,a._responsive._responsiveOnlyHidden()):!1});C.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(a,c){return a._responsive?a._responsive._responsiveOnlyHidden()[c]:!1},1)});u.version="2.3.0";b.fn.dataTable.Responsive=u;b.fn.DataTable.Responsive=u;b(m).on("preInit.dt.dtr",function(a,c,d){"dt"=== -a.namespace&&(b(c.nTable).hasClass("responsive")||b(c.nTable).hasClass("dt-responsive")||c.oInit.responsive||z.defaults.responsive)&&(a=c.oInit.responsive,!1!==a&&new u(c,b.isPlainObject(a)?a:{}))});return u}); +/*! FixedHeader 3.3.1 + * ©2009-2022 SpryMedia Ltd - datatables.net/license + */ +!function(o){"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(t){return o(t,window,document)}):"object"==typeof exports?module.exports=function(t,e){return t=t||window,(e=e||("undefined"!=typeof window?require("jquery"):require("jquery")(t))).fn.dataTable||require("datatables.net")(t,e),o(e,t,t.document)}:o(jQuery,window,document)}(function(m,H,x,v){"use strict";function s(t,e){if(!(this instanceof s))throw"FixedHeader must be initialised with the 'new' keyword.";if(!0===e&&(e={}),t=new r.Api(t),this.c=m.extend(!0,{},s.defaults,e),this.s={dt:t,position:{theadTop:0,tbodyTop:0,tfootTop:0,tfootBottom:0,width:0,left:0,tfootHeight:0,theadHeight:0,windowHeight:m(H).height(),visible:!0},headerMode:null,footerMode:null,autoWidth:t.settings()[0].oFeatures.bAutoWidth,namespace:".dtfc"+o++,scrollLeft:{header:-1,footer:-1},enable:!0},this.dom={floatingHeader:null,thead:m(t.table().header()),tbody:m(t.table().body()),tfoot:m(t.table().footer()),header:{host:null,floating:null,floatingParent:m('
'),placeholder:null},footer:{host:null,floating:null,floatingParent:m('
'),placeholder:null}},this.dom.header.host=this.dom.thead.parent(),this.dom.footer.host=this.dom.tfoot.parent(),(e=t.settings()[0])._fixedHeader)throw"FixedHeader already initialised on table "+e.nTable.id;(e._fixedHeader=this)._constructor()}var r=m.fn.dataTable,o=0;return m.extend(s.prototype,{destroy:function(){var t=this.dom;this.s.dt.off(".dtfc"),m(H).off(this.s.namespace),t.header.rightBlocker&&t.header.rightBlocker.remove(),t.header.leftBlocker&&t.header.leftBlocker.remove(),t.footer.rightBlocker&&t.footer.rightBlocker.remove(),t.footer.leftBlocker&&t.footer.leftBlocker.remove(),this.c.header&&this._modeChange("in-place","header",!0),this.c.footer&&t.tfoot.length&&this._modeChange("in-place","footer",!0)},enable:function(t,e){this.s.enable=t,!e&&e!==v||(this._positions(),this._scroll(!0))},enabled:function(){return this.s.enable},headerOffset:function(t){return t!==v&&(this.c.headerOffset=t,this.update()),this.c.headerOffset},footerOffset:function(t){return t!==v&&(this.c.footerOffset=t,this.update()),this.c.footerOffset},update:function(t){var e;this.s.enable&&(e=this.s.dt.table().node(),m(e).is(":visible")?this.enable(!0,!1):this.enable(!1,!1),0!==m(e).children("thead").length)&&(this._positions(),this._scroll(t===v||t))},_constructor:function(){var o=this,i=this.s.dt,t=(m(H).on("scroll"+this.s.namespace,function(){o._scroll()}).on("resize"+this.s.namespace,r.util.throttle(function(){o.s.position.windowHeight=m(H).height(),o.update()},50)),m(".fh-fixedHeader")),t=(!this.c.headerOffset&&t.length&&(this.c.headerOffset=t.outerHeight()),m(".fh-fixedFooter"));!this.c.footerOffset&&t.length&&(this.c.footerOffset=t.outerHeight()),i.on("column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc",function(t,e){o.update()}).on("draw.dt.dtfc",function(t,e){o.update(e!==i.settings()[0])}),i.on("destroy.dtfc",function(){o.destroy()}),this._positions(),this._scroll()},_clone:function(t,e){var o,i,s,r,n=this,d=this.s.dt,a=this.dom[t],f="header"===t?this.dom.thead:this.dom.tfoot;"footer"===t&&this._scrollEnabled()||(!e&&a.floating?a.floating.removeClass("fixedHeader-floating fixedHeader-locked"):(e=m(x).scrollLeft(),o=m(x).scrollTop(),a.floating&&(null!==a.placeholder&&a.placeholder.remove(),this._unsize(t),a.floating.children().detach(),a.floating.remove()),i=m(d.table().node()),s=m(i.parent()),r=this._scrollEnabled(),a.floating=m(d.table().node().cloneNode(!1)).attr("aria-hidden","true").css({"table-layout":"fixed",top:0,left:0}).removeAttr("id").append(f),a.floatingParent.css({width:s.width(),overflow:"hidden",height:"fit-content",position:"fixed",left:r?i.offset().left+s.scrollLeft():0}).css("header"===t?{top:this.c.headerOffset,bottom:""}:{top:"",bottom:this.c.footerOffset}).addClass("footer"===t?"dtfh-floatingparentfoot":"dtfh-floatingparenthead").append(a.floating).appendTo("body"),this._stickyPosition(a.floating,"-"),(d=function(){var t=s.scrollLeft();n.s.scrollLeft={footer:t,header:t},a.floatingParent.scrollLeft(n.s.scrollLeft.header)})(),s.off("scroll.dtfh").on("scroll.dtfh",d),a.placeholder=f.clone(!1),a.placeholder.find("*[id]").removeAttr("id"),a.host.prepend(a.placeholder),this._matchWidths(a.placeholder,a.floating),m(x).scrollTop(o).scrollLeft(e)))},_stickyPosition:function(t,i){var s,r;this._scrollEnabled()&&(r="rtl"===m((s=this).s.dt.table().node()).css("direction"),t.find("th").each(function(){var t,e,o;"sticky"===m(this).css("position")&&(t=m(this).css("right"),e=m(this).css("left"),"auto"===t||r?"auto"!==e&&r&&(o=+e.replace(/px/g,"")+("-"===i?-1:1)*s.s.dt.settings()[0].oBrowser.barWidth,m(this).css("left",0b&&s+this.c.headerOffset+p.theadHeightc||this.dom.header.floatingParent===v?t=!0:this.dom.header.floatingParent.css({top:this.c.headerOffset,position:"fixed"}).append(this.dom.header.floating)):f="below",!t&&f===this.s.headerMode||this._modeChange(f,"header",t),this._horizontal("header",i)),u={offset:{top:0,left:0},height:0},g={offset:{top:0,left:0},height:0},this.c.footer&&this.dom.tfoot.length&&(!this.s.enable||!p.visible||p.tfootBottom+this.c.footerOffset<=a?r="in-place":c+p.tfootHeight+this.c.footerOffset>a&&b+this.c.footerOffsets&&(p=a+((c=s-o.top)>-u.height?c:0)-(u.offset.top+(c<-u.height?u.height:0)+g.height),h.outerHeight(p=p<0?0:p),Math.round(h.outerHeight())>=Math.round(p)?m(this.dom.tfoot.parent()).addClass("fixedHeader-floating"):m(this.dom.tfoot.parent()).removeClass("fixedHeader-floating")),this.dom.header.floating&&this.dom.header.floatingParent.css("left",n-i),this.dom.footer.floating&&this.dom.footer.floatingParent.css("left",n-i),this.s.dt.settings()[0]._fixedColumns!==v&&(this.dom.header.rightBlocker=(b=function(t,e,o){var i;return null!==(o=o===v?0===(i=m("div.dtfc-"+t+"-"+e+"-blocker")).length?null:i.clone().css("z-index",1):o)&&("in"===f||"below"===f?o.appendTo("body").css({top:("top"===e?u:g).offset.top,left:"right"===t?n+d-o.width():n}):o.detach()),o})("right","top",this.dom.header.rightBlocker),this.dom.header.leftBlocker=b("left","top",this.dom.header.leftBlocker),this.dom.footer.rightBlocker=b("right","bottom",this.dom.footer.rightBlocker),this.dom.footer.leftBlocker=b("left","bottom",this.dom.footer.leftBlocker)))},_scrollEnabled:function(){var t=this.s.dt.settings()[0].oScroll;return""!==t.sY||""!==t.sX}}),s.version="3.3.1",s.defaults={header:!0,footer:!1,headerOffset:0,footerOffset:0},m.fn.dataTable.FixedHeader=s,m.fn.DataTable.FixedHeader=s,m(x).on("init.dt.dtfh",function(t,e,o){var i;"dt"===t.namespace&&(t=e.oInit.fixedHeader,i=r.defaults.fixedHeader,t||i)&&!e._fixedHeader&&(i=m.extend({},i,t),!1!==t)&&new s(e,i)}),r.Api.register("fixedHeader()",function(){}),r.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(t){t=t._fixedHeader;t&&t.update()})}),r.Api.register("fixedHeader.enable()",function(e){return this.iterator("table",function(t){t=t._fixedHeader;e=e===v||e,t&&e!==t.enabled()&&t.enable(e)})}),r.Api.register("fixedHeader.enabled()",function(){if(this.context.length){var t=this.context[0]._fixedHeader;if(t)return t.enabled()}return!1}),r.Api.register("fixedHeader.disable()",function(){return this.iterator("table",function(t){t=t._fixedHeader;t&&t.enabled()&&t.enable(!1)})}),m.each(["header","footer"],function(t,o){r.Api.register("fixedHeader."+o+"Offset()",function(e){var t=this.context;return e===v?t.length&&t[0]._fixedHeader?t[0]._fixedHeader[o+"Offset"]():v:this.iterator("table",function(t){t=t._fixedHeader;t&&t[o+"Offset"](e)})})}),r}); +/*! Responsive 2.4.0 + * 2014-2022 SpryMedia Ltd - datatables.net/license + */ +!function(n){"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return n(e,window,document)}):"object"==typeof exports?module.exports=function(e,t){return e=e||window,(t=t||("undefined"!=typeof window?require("jquery"):require("jquery")(e))).fn.dataTable||require("datatables.net")(e,t),n(t,e,e.document)}:n(jQuery,window,document)}(function(f,m,o,h){"use strict";function d(e,t){if(!r.versionCheck||!r.versionCheck("1.10.10"))throw"DataTables Responsive requires DataTables 1.10.10 or newer";this.s={childNodeStore:{},columns:[],current:[],dt:new r.Api(e)},this.s.dt.settings()[0].responsive||(t&&"string"==typeof t.details?t.details={type:t.details}:t&&!1===t.details?t.details={type:!1}:t&&!0===t.details&&(t.details={type:"inline"}),this.c=f.extend(!0,{},d.defaults,r.defaults.responsive,t),(e.responsive=this)._constructor())}var r=f.fn.dataTable,e=(f.extend(d.prototype,{_constructor:function(){var s=this,i=this.s.dt,e=i.settings()[0],t=f(m).innerWidth(),e=(i.settings()[0]._responsive=this,f(m).on("resize.dtr orientationchange.dtr",r.util.throttle(function(){var e=f(m).innerWidth();e!==t&&(s._resize(),t=e)})),e.oApi._fnCallbackReg(e,"aoRowCreatedCallback",function(e,t,n){-1!==f.inArray(!1,s.s.current)&&f(">td, >th",e).each(function(e){e=i.column.index("toData",e);!1===s.s.current[e]&&f(this).css("display","none")})}),i.on("destroy.dtr",function(){i.off(".dtr"),f(i.table().body()).off(".dtr"),f(m).off("resize.dtr orientationchange.dtr"),i.cells(".dtr-control").nodes().to$().removeClass("dtr-control"),f.each(s.s.current,function(e,t){!1===t&&s._setColumnVis(e,!0)})}),this.c.breakpoints.sort(function(e,t){return e.widtht.width?-1:0}),this._classLogic(),this._resizeAuto(),this.c.details);!1!==e.type&&(s._detailsInit(),i.on("column-visibility.dtr",function(){s._timer&&clearTimeout(s._timer),s._timer=setTimeout(function(){s._timer=null,s._classLogic(),s._resizeAuto(),s._resize(!0),s._redrawChildren()},100)}),i.on("draw.dtr",function(){s._redrawChildren()}),f(i.table().node()).addClass("dtr-"+e.type)),i.on("column-reorder.dtr",function(e,t,n){s._classLogic(),s._resizeAuto(),s._resize(!0)}),i.on("column-sizing.dtr",function(){s._resizeAuto(),s._resize()}),i.on("column-calc.dt",function(e,t){for(var n=s.s.current,i=0;i=r&&u(e,l[s].name);else if("not-"===n)for(s=0,o=l.length;s").append(r).appendTo(i)),f("
").append(n).appendTo(t),"inline"===this.c.details.type&&f(e).addClass("dtr-inline collapsed"),f(e).find("[name]").removeAttr("name"),f(e).css("position","relative"),(r=f("
").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(e)).insertBefore(s.table().node()),n.each(function(e){e=s.column.index("fromVisible",e);o[e].minWidth=this.offsetWidth||0}),r.remove())},_responsiveOnlyHidden:function(){var n=this.s.dt;return f.map(this.s.current,function(e,t){return!1===n.column(t).visible()||e})},_setColumnVis:function(e,t){var n=this,i=this.s.dt,r=t?"":"none";f(i.column(e).header()).css("display",r).toggleClass("dtr-hidden",!t),f(i.column(e).footer()).css("display",r).toggleClass("dtr-hidden",!t),i.column(e).nodes().to$().css("display",r).toggleClass("dtr-hidden",!t),f.isEmptyObject(this.s.childNodeStore)||i.cells(null,e).indexes().each(function(e){n._childNodesRestore(i,e.row,e.column)})},_tabIndexes:function(){var e=this.s.dt,t=e.cells({page:"current"}).nodes().to$(),n=e.settings()[0],i=this.c.details.target;t.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]"),("number"==typeof i?e.cells(null,i,{page:"current"}).nodes().to$():f(i="td:first-child, th:first-child"===i?">td:first-child, >th:first-child":i,e.rows({page:"current"}).nodes())).attr("tabIndex",n.iTabIndex).data("dtr-keyboard",1)}}),d.defaults={breakpoints:d.breakpoints=[{name:"desktop",width:1/0},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}],auto:!0,details:{display:(d.display={childRow:function(e,t,n){return t?f(e.node()).hasClass("parent")?(e.child(n(),"child").show(),!0):void 0:e.child.isShown()?(e.child(!1),f(e.node()).removeClass("parent"),!1):(e.child(n(),"child").show(),f(e.node()).addClass("parent"),!0)},childRowImmediate:function(e,t,n){return!t&&e.child.isShown()||!e.responsive.hasHidden()?(e.child(!1),f(e.node()).removeClass("parent"),!1):(e.child(n(),"child").show(),f(e.node()).addClass("parent"),!0)},modal:function(s){return function(e,t,n){var i,r;t?f("div.dtr-modal-content").empty().append(n()):(i=function(){r.remove(),f(o).off("keypress.dtr")},r=f('
').append(f('
').append(f('
').append(n())).append(f('
×
').click(function(){i()}))).append(f('
').click(function(){i()})).appendTo("body"),f(o).on("keyup.dtr",function(e){27===e.keyCode&&(e.stopPropagation(),i())})),s&&s.header&&f("div.dtr-modal-content").prepend("

"+s.header(e)+"

")}}}).childRow,renderer:(d.renderer={listHiddenNodes:function(){return function(i,e,t){var r=this,s=f('
    '),o=!1;f.each(t,function(e,t){var n;t.hidden&&(n=t.className?'class="'+t.className+'"':"",f("
  • '+t.title+"
  • ").append(f('').append(r._childNodes(i,t.rowIndex,t.columnIndex))).appendTo(s),o=!0)});return!!o&&s}},listHidden:function(){return function(e,t,n){n=f.map(n,function(e){var t=e.className?'class="'+e.className+'"':"";return e.hidden?"
  • '+e.title+' '+e.data+"
  • ":""}).join("");return!!n&&f('
      ').append(n)}},tableAll:function(i){return i=f.extend({tableClass:""},i),function(e,t,n){n=f.map(n,function(e){return"
"}).join("");return f('
'+e.title+": "+e.data+"
').append(n)}}}).listHidden(),target:0,type:"inline"},orthogonal:"display"},f.fn.dataTable.Api);return e.register("responsive()",function(){return this}),e.register("responsive.index()",function(e){return{column:(e=f(e)).data("dtr-index"),row:e.parent().data("dtr-index")}}),e.register("responsive.rebuild()",function(){return this.iterator("table",function(e){e._responsive&&e._responsive._classLogic()})}),e.register("responsive.recalc()",function(){return this.iterator("table",function(e){e._responsive&&(e._responsive._resizeAuto(),e._responsive._resize())})}),e.register("responsive.hasHidden()",function(){var e=this.context[0];return!!e._responsive&&-1!==f.inArray(!1,e._responsive._responsiveOnlyHidden())}),e.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(e,t){return!!e._responsive&&e._responsive._responsiveOnlyHidden()[t]},1)}),d.version="2.4.0",f.fn.dataTable.Responsive=d,f.fn.DataTable.Responsive=d,f(o).on("preInit.dt.dtr",function(e,t,n){"dt"===e.namespace&&(f(t.nTable).hasClass("responsive")||f(t.nTable).hasClass("dt-responsive")||t.oInit.responsive||r.defaults.responsive)&&!1!==(e=t.oInit.responsive)&&new d(t,f.isPlainObject(e)?e:{})}),r}); /*! DataTables styling wrapper for Responsive - * ©2018 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ @@ -363,17 +50,26 @@ a.namespace&&(b(c.nTable).hasClass("responsive")||b(c.nTable).hasClass("dt-respo // CommonJS module.exports = function (root, $) { if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise root = window; } - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net-dt')(root, $).$; + if ( ! $ ) { + $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window + require('jquery') : + require('jquery')( root ); + } + + if ( ! $.fn.dataTable ) { + require('datatables.net-dt')(root, $); } - if ( ! $.fn.dataTable.Responsive ) { + if ( ! $.fn.dataTable ) { require('datatables.net-responsive')(root, $); } + return factory( $, root, root.document ); }; } @@ -382,8 +78,13 @@ a.namespace&&(b(c.nTable).hasClass("responsive")||b(c.nTable).hasClass("dt-respo factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; -return $.fn.dataTable; + + +return DataTable; })); + diff --git a/modules/http/webif/gstatic/datatables/datetime-moment.min.js b/modules/http/webif/gstatic/datatables/datetime-moment.min.js old mode 100644 new mode 100755 index 41d36128ff..f78886e4c9 --- a/modules/http/webif/gstatic/datatables/datetime-moment.min.js +++ b/modules/http/webif/gstatic/datatables/datetime-moment.min.js @@ -19,4 +19,4 @@ * * $('#example').DataTable(); */ -!function(n){"function"==typeof define&&define.amd?define(["jquery","moment","datatables.net"],n):n(jQuery,moment)}((function(n,e){function t(n){return"string"==typeof n&&(n=(n=n.replace(/(<.*?>)|(\r?\n|\r)/g,"")).trim()),n}n.fn.dataTable.moment=function(r,i,a){var f=n.fn.dataTable.ext.type;f.detect.unshift((function(n){return""===(n=t(n))||null===n||e(n,r,i,!0).isValid()?"moment-"+r:null})),f.order["moment-"+r+"-pre"]=function(n){return n=t(n),e(n,r,i,!0).isValid()?parseInt(e(n,r,i,!0).format("x"),10):a?-1/0:1/0}}})); +!function(n){"function"==typeof define&&define.amd?define(["jquery","moment","datatables.net"],n):n(jQuery,moment)}(function(n,e){function t(n){return"string"==typeof n&&(n=(n=n.replace(/(<.*?>)|(\r?\n|\r)/g,"")).trim()),n}n.fn.dataTable.moment=function(r,i,o){var a=n.fn.dataTable.ext.type;a.detect.unshift(function(n){return""===(n=t(n))||null===n?"moment-"+r:e(n,r,i,!0).isValid()?"moment-"+r:null}),a.order["moment-"+r+"-pre"]=function(n){return n=t(n),e(n,r,i,!0).isValid()?parseInt(e(n,r,i,!0).format("x"),10):o?-1/0:1/0}}}); diff --git a/modules/http/webif/gstatic/datatables/moment.min.js b/modules/http/webif/gstatic/datatables/moment.min.js old mode 100644 new mode 100755 index 02f30abfd8..32b0d29188 --- a/modules/http/webif/gstatic/datatables/moment.min.js +++ b/modules/http/webif/gstatic/datatables/moment.min.js @@ -1,2 +1,8 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var H;function f(){return H.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function F(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function c(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function L(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(c(e,t))return;return 1}function o(e){return void 0===e}function u(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function V(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function G(e,t){for(var n=[],s=e.length,i=0;i>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0>>0;for(t=0;t0)for(n=0;n=0?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+s}var N=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,P=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,R={},W={};function C(e,t,n,s){var i=s;"string"==typeof s&&(i=function(){return this[s]()}),e&&(W[e]=i),t&&(W[t[0]]=function(){return T(i.apply(this,arguments),t[1],t[2])}),n&&(W[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function U(e,t){return e.isValid()?(t=H(t,e.localeData()),R[t]=R[t]||function(e){var t,n,s,i=e.match(N);for(t=0,n=i.length;t=0&&P.test(e);)e=e.replace(P,s),P.lastIndex=0,n-=1;return e}var F={};function L(e,t){var n=e.toLowerCase();F[n]=F[n+"s"]=F[t]=e}function V(e){return"string"==typeof e?F[e]||F[e.toLowerCase()]:void 0}function G(e){var t,n,s={};for(n in e)r(e,n)&&(t=V(n))&&(s[t]=e[n]);return s}var E={};function A(e,t){E[e]=t}function j(e){return e%4==0&&e%100!=0||e%400==0}function I(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function Z(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=I(t)),n}function z(e,t){return function(s){return null!=s?(q(this,e,s),n.updateOffset(this,t),this):$(this,e)}}function $(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function q(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&j(e.year())&&1===e.month()&&29===e.date()?(n=Z(n),e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),Te(n,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](n))}var B,J=/\d/,Q=/\d\d/,X=/\d{3}/,K=/\d{4}/,ee=/[+-]?\d{6}/,te=/\d\d?/,ne=/\d\d\d\d?/,se=/\d\d\d\d\d\d?/,ie=/\d{1,3}/,re=/\d{1,4}/,ae=/[+-]?\d{1,6}/,oe=/\d+/,ue=/[+-]?\d+/,le=/Z|[+-]\d\d:?\d\d/gi,he=/Z|[+-]\d\d(?::?\d\d)?/gi,de=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function ce(e,t,n){B[e]=O(t)?t:function(e,s){return e&&n?n:t}}function fe(e,t){return r(B,e)?B[e](t._strict,t._locale):new RegExp(me(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,s,i){return t||n||s||i})))}function me(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}B={};var _e={};function ye(e,t){var n,s,i=t;for("string"==typeof e&&(e=[e]),u(t)&&(i=function(e,n){n[t]=Z(e)}),s=e.length,n=0;n68?1900:2e3)};var Ve=z("FullYear",!0);function Ge(e){var t,n;return e<100&&e>=0?((n=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,n)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function Ee(e,t,n){var s=7+t-n;return-((7+Ge(e,0,s).getUTCDay()-t)%7)+s-1}function Ae(e,t,n,s,i){var r,a,o=1+7*(t-1)+(7+n-s)%7+Ee(e,s,i);return o<=0?a=Le(r=e-1)+o:o>Le(e)?(r=e+1,a=o-Le(e)):(r=e,a=o),{year:r,dayOfYear:a}}function je(e,t,n){var s,i,r=Ee(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ie(i=e.year()-1,t,n):a>Ie(e.year(),t,n)?(s=a-Ie(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ie(e,t,n){var s=Ee(e,t,n),i=Ee(e+1,t,n);return(Le(e)-s+i)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),A("week",5),A("isoWeek",5),ce("w",te),ce("ww",te,Q),ce("W",te),ce("WW",te,Q),ge(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=Z(e)});function Ze(e,t){return e.slice(t,7).concat(e.slice(0,t))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),A("day",11),A("weekday",11),A("isoWeekday",11),ce("d",te),ce("e",te),ce("E",te),ce("dd",function(e,t){return t.weekdaysMinRegex(e)}),ce("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ce("dddd",function(e,t){return t.weekdaysRegex(e)}),ge(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:f(n).invalidWeekday=e}),ge(["d","e","E"],function(e,t,n,s){t[s]=Z(e)});var ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),$e="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),qe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Be=de,Je=de,Qe=de;function Xe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=c([2e3,1]).day(t),s=me(this.weekdaysMin(n,"")),i=me(this.weekdaysShort(n,"")),r=me(this.weekdays(n,"")),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);a.sort(e),o.sort(e),u.sort(e),l.sort(e),this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Ke(){return this.hours()%12||12}function et(e,t){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function tt(e,t){return t._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Ke),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Ke.apply(this)+T(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Ke.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),et("a",!0),et("A",!1),L("hour","h"),A("hour",13),ce("a",tt),ce("A",tt),ce("H",te),ce("h",te),ce("k",te),ce("HH",te,Q),ce("hh",te,Q),ce("kk",te,Q),ce("hmm",ne),ce("hmmss",se),ce("Hmm",ne),ce("Hmmss",se),ye(["H","HH"],De),ye(["k","kk"],function(e,t,n){var s=Z(e);t[De]=24===s?0:s}),ye(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ye(["h","hh"],function(e,t,n){t[De]=Z(e),f(n).bigHour=!0}),ye("hmm",function(e,t,n){var s=e.length-2;t[De]=Z(e.substr(0,s)),t[Se]=Z(e.substr(s)),f(n).bigHour=!0}),ye("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[De]=Z(e.substr(0,s)),t[Se]=Z(e.substr(s,2)),t[Ye]=Z(e.substr(i)),f(n).bigHour=!0}),ye("Hmm",function(e,t,n){var s=e.length-2;t[De]=Z(e.substr(0,s)),t[Se]=Z(e.substr(s))}),ye("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[De]=Z(e.substr(0,s)),t[Se]=Z(e.substr(s,2)),t[Ye]=Z(e.substr(i))});var nt=z("Hours",!0);var st,it={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ne,monthsShort:Pe,week:{dow:0,doy:6},weekdays:ze,weekdaysMin:qe,weekdaysShort:$e,meridiemParse:/[ap]\.?m?\.?/i},rt={},at={};function ot(e,t){var n,s=Math.min(e.length,t.length);for(n=0;n0;){if(s=lt(i.slice(0,t).join("-")))return s;if(n&&n.length>=t&&ot(i,n)>=t-1)break;t--}r++}return st}(e)}function ft(e){var t,n=e._a;return n&&-2===f(e).overflow&&(t=n[ke]<0||n[ke]>11?ke:n[Me]<1||n[Me]>Te(n[ve],n[ke])?Me:n[De]<0||n[De]>24||24===n[De]&&(0!==n[Se]||0!==n[Ye]||0!==n[Oe])?De:n[Se]<0||n[Se]>59?Se:n[Ye]<0||n[Ye]>59?Ye:n[Oe]<0||n[Oe]>999?Oe:-1,f(e)._overflowDayOfYear&&(tMe)&&(t=Me),f(e)._overflowWeeks&&-1===t&&(t=be),f(e)._overflowWeekday&&-1===t&&(t=xe),f(e).overflow=t),e}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],wt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((-?\d+)/i,vt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,kt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Mt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o),l=gt.length,h=wt.length;if(u){for(f(e).iso=!0,t=0,n=l;t7)&&(u=!0)):(r=e._locale._week.dow,a=e._locale._week.doy,l=je(Nt(),r,a),n=Yt(t.gg,e._a[ve],l.year),s=Yt(t.w,l.week),null!=t.d?((i=t.d)<0||i>6)&&(u=!0):null!=t.e?(i=t.e+r,(t.e<0||t.e>6)&&(u=!0)):i=r);s<1||s>Ie(n,r,a)?f(e)._overflowWeeks=!0:null!=u?f(e)._overflowWeekday=!0:(o=Ae(n,s,i,r,a),e._a[ve]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(a=Yt(e._a[ve],i[ve]),(e._dayOfYear>Le(a)||0===e._dayOfYear)&&(f(e)._overflowDayOfYear=!0),s=Ge(a,0,e._dayOfYear),e._a[ke]=s.getUTCMonth(),e._a[Me]=s.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=o[t]=i[t];for(;t<7;t++)e._a[t]=o[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[De]&&0===e._a[Se]&&0===e._a[Ye]&&0===e._a[Oe]&&(e._nextDay=!0,e._a[De]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&e>=0?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,o),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[De]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(f(e).weekdayMismatch=!0)}}function bt(e){if(e._f!==n.ISO_8601)if(e._f!==n.RFC_2822){e._a=[],f(e).empty=!0;var t,s,i,r,a,o,u,l=""+e._i,h=l.length,d=0;for(u=(i=H(e._f,e._locale).match(N)||[]).length,t=0;t0&&f(e).unusedInput.push(a),l=l.slice(l.indexOf(s)+s.length),d+=s.length),W[r]?(s?f(e).empty=!1:f(e).unusedTokens.push(r),we(r,s,e)):e._strict&&!s&&f(e).unusedTokens.push(r);f(e).charsLeftOver=h-d,l.length>0&&f(e).unusedInput.push(l),e._a[De]<=12&&!0===f(e).bigHour&&e._a[De]>0&&(f(e).bigHour=void 0),f(e).parsedDateParts=e._a.slice(0),f(e).meridiem=e._meridiem,e._a[De]=function(e,t,n){var s;if(null==n)return t;return null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((s=e.isPM(n))&&t<12&&(t+=12),s||12!==t||(t=0),t):t}(e._locale,e._a[De],e._meridiem),null!==(o=f(e).era)&&(e._a[ve]=e._locale.erasConvertYear(o,e._a[ve])),Ot(e),ft(e)}else St(e);else Mt(e)}function xt(e){var t=e._i,r=e._f;return e._locale=e._locale||ct(e._l),null===t||void 0===r&&""===t?_({nullInput:!0}):("string"==typeof t&&(e._i=t=e._locale.preparse(t)),v(t)?new p(ft(t)):(l(t)?e._d=t:s(r)?function(e){var t,n,s,i,r,a,o=!1,u=e._f.length;if(0===u)return f(e).invalidFormat=!0,void(e._d=new Date(NaN));for(i=0;ithis?this:e:_()});function Wt(e,t){var n,i;if(1===t.length&&s(t[0])&&(t=t[0]),!t.length)return Nt();for(n=t[0],i=1;i=0?new Date(e+400,t,n)-ln:new Date(e,t,n).valueOf()}function cn(e,t,n){return e<100&&e>=0?Date.UTC(e+400,t,n)-ln:Date.UTC(e,t,n)}function fn(e,t){return t.erasAbbrRegex(e)}function mn(){var e,t,n=[],s=[],i=[],r=[],a=this.eras();for(e=0,t=a.length;e(r=Ie(e,s,i))&&(t=r),function(e,t,n,s,i){var r=Ae(e,t,n,s,i),a=Ge(r.year,0,r.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}.call(this,e,t,n,s,i))}C("N",0,0,"eraAbbr"),C("NN",0,0,"eraAbbr"),C("NNN",0,0,"eraAbbr"),C("NNNN",0,0,"eraName"),C("NNNNN",0,0,"eraNarrow"),C("y",["y",1],"yo","eraYear"),C("y",["yy",2],0,"eraYear"),C("y",["yyy",3],0,"eraYear"),C("y",["yyyy",4],0,"eraYear"),ce("N",fn),ce("NN",fn),ce("NNN",fn),ce("NNNN",function(e,t){return t.erasNameRegex(e)}),ce("NNNNN",function(e,t){return t.erasNarrowRegex(e)}),ye(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,s){var i=n._locale.erasParse(e,s,n._strict);i?f(n).era=i:f(n).invalidEra=e}),ce("y",oe),ce("yy",oe),ce("yyy",oe),ce("yyyy",oe),ce("yo",function(e,t){return t._eraYearOrdinalRegex||oe}),ye(["y","yy","yyy","yyyy"],ve),ye(["yo"],function(e,t,n,s){var i;n._locale._eraYearOrdinalRegex&&(i=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[ve]=n._locale.eraYearOrdinalParse(e,i):t[ve]=parseInt(e,10)}),C(0,["gg",2],0,function(){return this.weekYear()%100}),C(0,["GG",2],0,function(){return this.isoWeekYear()%100}),_n("gggg","weekYear"),_n("ggggg","weekYear"),_n("GGGG","isoWeekYear"),_n("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),A("weekYear",1),A("isoWeekYear",1),ce("G",ue),ce("g",ue),ce("GG",te,Q),ce("gg",te,Q),ce("GGGG",re,K),ce("gggg",re,K),ce("GGGGG",ae,ee),ce("ggggg",ae,ee),ge(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,s){t[s.substr(0,2)]=Z(e)}),ge(["gg","GG"],function(e,t,s,i){t[i]=n.parseTwoDigitYear(e)}),C("Q",0,"Qo","quarter"),L("quarter","Q"),A("quarter",7),ce("Q",J),ye("Q",function(e,t){t[ke]=3*(Z(e)-1)}),C("D",["DD",2],"Do","date"),L("date","D"),A("date",9),ce("D",te),ce("DD",te,Q),ce("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),ye(["D","DD"],Me),ye("Do",function(e,t){t[Me]=Z(e.match(te)[0])});var gn=z("Date",!0);C("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),A("dayOfYear",4),ce("DDD",ie),ce("DDDD",X),ye(["DDD","DDDD"],function(e,t,n){n._dayOfYear=Z(e)}),C("m",["mm",2],0,"minute"),L("minute","m"),A("minute",14),ce("m",te),ce("mm",te,Q),ye(["m","mm"],Se);var wn=z("Minutes",!1);C("s",["ss",2],0,"second"),L("second","s"),A("second",15),ce("s",te),ce("ss",te,Q),ye(["s","ss"],Ye);var pn,vn,kn=z("Seconds",!1);for(C("S",0,0,function(){return~~(this.millisecond()/100)}),C(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),C(0,["SSS",3],0,"millisecond"),C(0,["SSSS",4],0,function(){return 10*this.millisecond()}),C(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),C(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),C(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),C(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),C(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),L("millisecond","ms"),A("millisecond",16),ce("S",ie,J),ce("SS",ie,Q),ce("SSS",ie,X),pn="SSSS";pn.length<=9;pn+="S")ce(pn,oe);function Mn(e,t){t[Oe]=Z(1e3*("0."+e))}for(pn="S";pn.length<=9;pn+="S")ye(pn,Mn);vn=z("Milliseconds",!1),C("z",0,0,"zoneAbbr"),C("zz",0,0,"zoneName");var Dn=p.prototype;function Sn(e){return e}Dn.add=Qt,Dn.calendar=function(e,t){1===arguments.length&&(arguments[0]?en(arguments[0])?(e=arguments[0],t=void 0):function(e){var t,n=i(e)&&!a(e),s=!1,o=["sameDay","nextDay","lastDay","nextWeek","lastWeek","sameElse"];for(t=0;tn.valueOf():n.valueOf()9999?U(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):O(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",U(n,"Z")):U(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},Dn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t,n,s="moment",i="";return this.isLocal()||(s=0===this.utcOffset()?"moment.utc":"moment.parseZone",i="Z"),e="["+s+'("]',t=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",n=i+'[")]',this.format(e+t+"-MM-DD[T]HH:mm:ss.SSS"+n)},"undefined"!=typeof Symbol&&null!=Symbol.for&&(Dn[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),Dn.toJSON=function(){return this.isValid()?this.toISOString():null},Dn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},Dn.unix=function(){return Math.floor(this.valueOf()/1e3)},Dn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},Dn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},Dn.eraName=function(){var e,t,n,s=this.localeData().eras();for(e=0,t=s.length;ethis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Dn.isLocal=function(){return!!this.isValid()&&!this._isUTC},Dn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Dn.isUtc=jt,Dn.isUTC=jt,Dn.zoneAbbr=function(){return this._isUTC?"UTC":""},Dn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Dn.dates=M("dates accessor is deprecated. Use date instead.",gn),Dn.months=M("months accessor is deprecated. Use month instead",He),Dn.years=M("years accessor is deprecated. Use year instead",Ve),Dn.zone=M("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),Dn.isDSTShifted=M("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return w(t,this),(t=xt(t))._a?(e=t._isUTC?c(t._a):Nt(t._a),this._isDSTShifted=this.isValid()&&function(e,t,n){var s,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),a=0;for(s=0;s0):this._isDSTShifted=!1,this._isDSTShifted});var Yn=x.prototype;function On(e,t,n,s){var i=ct(),r=c().set(s,t);return i[n](r,e)}function bn(e,t,n){if(u(e)&&(t=e,e=void 0),e=e||"",null!=t)return On(e,t,n,"month");var s,i=[];for(s=0;s<12;s++)i[s]=On(e,s,n,"month");return i}function xn(e,t,n,s){"boolean"==typeof e?(u(t)&&(n=t,t=void 0),t=t||""):(n=t=e,e=!1,u(t)&&(n=t,t=void 0),t=t||"");var i,r=ct(),a=e?r._week.dow:0,o=[];if(null!=n)return On(t,(n+a)%7,s,"day");for(i=0;i<7;i++)o[i]=On(t,(i+a)%7,s,"day");return o}Yn.calendar=function(e,t,n){var s=this._calendar[e]||this._calendar.sameElse;return O(s)?s.call(t,n):s},Yn.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(N).map(function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e}).join(""),this._longDateFormat[e])},Yn.invalidDate=function(){return this._invalidDate},Yn.ordinal=function(e){return this._ordinal.replace("%d",e)},Yn.preparse=Sn,Yn.postformat=Sn,Yn.relativeTime=function(e,t,n,s){var i=this._relativeTime[n];return O(i)?i(e,t,n,s):i.replace(/%d/i,e)},Yn.pastFuture=function(e,t){var n=this._relativeTime[e>0?"future":"past"];return O(n)?n(t):n.replace(/%s/i,t)},Yn.set=function(e){var t,n;for(n in e)r(e,n)&&(O(t=e[n])?this[n]=t:this["_"+n]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},Yn.eras=function(e,t){var s,i,r,a=this._eras||ct("en")._eras;for(s=0,i=a.length;s=0)return u[s]},Yn.erasConvertYear=function(e,t){var s=e.since<=e.until?1:-1;return void 0===t?n(e.since).year():n(e.since).year()+(t-e.offset)*s},Yn.erasAbbrRegex=function(e){return r(this,"_erasAbbrRegex")||mn.call(this),e?this._erasAbbrRegex:this._erasRegex},Yn.erasNameRegex=function(e){return r(this,"_erasNameRegex")||mn.call(this),e?this._erasNameRegex:this._erasRegex},Yn.erasNarrowRegex=function(e){return r(this,"_erasNarrowRegex")||mn.call(this),e?this._erasNarrowRegex:this._erasRegex},Yn.months=function(e,t){return e?s(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||Re).test(t)?"format":"standalone"][e.month()]:s(this._months)?this._months:this._months.standalone},Yn.monthsShort=function(e,t){return e?s(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[Re.test(t)?"format":"standalone"][e.month()]:s(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},Yn.monthsParse=function(e,t,n){var s,i,r;if(this._monthsParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=c([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===t?-1!==(i=pe.call(this._shortMonthsParse,a))?i:null:-1!==(i=pe.call(this._longMonthsParse,a))?i:null:"MMM"===t?-1!==(i=pe.call(this._shortMonthsParse,a))?i:-1!==(i=pe.call(this._longMonthsParse,a))?i:null:-1!==(i=pe.call(this._longMonthsParse,a))?i:-1!==(i=pe.call(this._shortMonthsParse,a))?i:null}.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(i=c([2e3,s]),n&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[s]||(r="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[s]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[s].test(e))return s;if(n&&"MMM"===t&&this._shortMonthsParse[s].test(e))return s;if(!n&&this._monthsParse[s].test(e))return s}},Yn.monthsRegex=function(e){return this._monthsParseExact?(r(this,"_monthsRegex")||Fe.call(this),e?this._monthsStrictRegex:this._monthsRegex):(r(this,"_monthsRegex")||(this._monthsRegex=Ce),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},Yn.monthsShortRegex=function(e){return this._monthsParseExact?(r(this,"_monthsRegex")||Fe.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(r(this,"_monthsShortRegex")||(this._monthsShortRegex=We),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},Yn.week=function(e){return je(e,this._week.dow,this._week.doy).week},Yn.firstDayOfYear=function(){return this._week.doy},Yn.firstDayOfWeek=function(){return this._week.dow},Yn.weekdays=function(e,t){var n=s(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"];return!0===e?Ze(n,this._week.dow):e?n[e.day()]:n},Yn.weekdaysMin=function(e){return!0===e?Ze(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},Yn.weekdaysShort=function(e){return!0===e?Ze(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},Yn.weekdaysParse=function(e,t,n){var s,i,r;if(this._weekdaysParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=c([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=pe.call(this._weekdaysParse,a))?i:null:"ddd"===t?-1!==(i=pe.call(this._shortWeekdaysParse,a))?i:null:-1!==(i=pe.call(this._minWeekdaysParse,a))?i:null:"dddd"===t?-1!==(i=pe.call(this._weekdaysParse,a))?i:-1!==(i=pe.call(this._shortWeekdaysParse,a))?i:-1!==(i=pe.call(this._minWeekdaysParse,a))?i:null:"ddd"===t?-1!==(i=pe.call(this._shortWeekdaysParse,a))?i:-1!==(i=pe.call(this._weekdaysParse,a))?i:-1!==(i=pe.call(this._minWeekdaysParse,a))?i:null:-1!==(i=pe.call(this._minWeekdaysParse,a))?i:-1!==(i=pe.call(this._weekdaysParse,a))?i:-1!==(i=pe.call(this._shortWeekdaysParse,a))?i:null}.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(i=c([2e3,1]).day(s),n&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[s]||(r="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[s]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[s].test(e))return s;if(n&&"ddd"===t&&this._shortWeekdaysParse[s].test(e))return s;if(n&&"dd"===t&&this._minWeekdaysParse[s].test(e))return s;if(!n&&this._weekdaysParse[s].test(e))return s}},Yn.weekdaysRegex=function(e){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||Xe.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(r(this,"_weekdaysRegex")||(this._weekdaysRegex=Be),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},Yn.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||Xe.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(r(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Je),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},Yn.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||Xe.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(r(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Qe),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},Yn.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},Yn.meridiem=function(e,t,n){return e>11?n?"pm":"PM":n?"am":"AM"},ht("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===Z(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),n.lang=M("moment.lang is deprecated. Use moment.locale instead.",ht),n.langData=M("moment.langData is deprecated. Use moment.localeData instead.",ct);var Tn=Math.abs;function Nn(e,t,n,s){var i=zt(t,n);return e._milliseconds+=s*i._milliseconds,e._days+=s*i._days,e._months+=s*i._months,e._bubble()}function Pn(e){return e<0?Math.floor(e):Math.ceil(e)}function Rn(e){return 4800*e/146097}function Wn(e){return 146097*e/4800}function Cn(e){return function(){return this.as(e)}}var Un=Cn("ms"),Hn=Cn("s"),Fn=Cn("m"),Ln=Cn("h"),Vn=Cn("d"),Gn=Cn("w"),En=Cn("M"),An=Cn("Q"),jn=Cn("y");function In(e){return function(){return this.isValid()?this._data[e]:NaN}}var Zn=In("milliseconds"),zn=In("seconds"),$n=In("minutes"),qn=In("hours"),Bn=In("days"),Jn=In("months"),Qn=In("years");var Xn=Math.round,Kn={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};var es=Math.abs;function ts(e){return(e>0)-(e<0)||+e}function ns(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,s,i,r,a,o,u=es(this._milliseconds)/1e3,l=es(this._days),h=es(this._months),d=this.asSeconds();return d?(t=I((e=I(u/60))/60),u%=60,e%=60,n=I(h/12),h%=12,s=u?u.toFixed(3).replace(/\.?0+$/,""):"",i=d<0?"-":"",r=ts(this._months)!==ts(d)?"-":"",a=ts(this._days)!==ts(d)?"-":"",o=ts(this._milliseconds)!==ts(d)?"-":"",i+"P"+(n?r+n+"Y":"")+(h?r+h+"M":"")+(l?a+l+"D":"")+(t||e||u?"T":"")+(t?o+t+"H":"")+(e?o+e+"M":"")+(u?o+s+"S":"")):"P0D"}var ss=Ut.prototype;return ss.isValid=function(){return this._isValid},ss.abs=function(){var e=this._data;return this._milliseconds=Tn(this._milliseconds),this._days=Tn(this._days),this._months=Tn(this._months),e.milliseconds=Tn(e.milliseconds),e.seconds=Tn(e.seconds),e.minutes=Tn(e.minutes),e.hours=Tn(e.hours),e.months=Tn(e.months),e.years=Tn(e.years),this},ss.add=function(e,t){return Nn(this,e,t,1)},ss.subtract=function(e,t){return Nn(this,e,t,-1)},ss.as=function(e){if(!this.isValid())return NaN;var t,n,s=this._milliseconds;if("month"===(e=V(e))||"quarter"===e||"year"===e)switch(t=this._days+s/864e5,n=this._months+Rn(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(Wn(this._months)),e){case"week":return t/7+s/6048e5;case"day":return t+s/864e5;case"hour":return 24*t+s/36e5;case"minute":return 1440*t+s/6e4;case"second":return 86400*t+s/1e3;case"millisecond":return Math.floor(864e5*t)+s;default:throw new Error("Unknown unit "+e)}},ss.asMilliseconds=Un,ss.asSeconds=Hn,ss.asMinutes=Fn,ss.asHours=Ln,ss.asDays=Vn,ss.asWeeks=Gn,ss.asMonths=En,ss.asQuarters=An,ss.asYears=jn,ss.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*Z(this._months/12):NaN},ss._bubble=function(){var e,t,n,s,i,r=this._milliseconds,a=this._days,o=this._months,u=this._data;return r>=0&&a>=0&&o>=0||r<=0&&a<=0&&o<=0||(r+=864e5*Pn(Wn(o)+a),a=0,o=0),u.milliseconds=r%1e3,e=I(r/1e3),u.seconds=e%60,t=I(e/60),u.minutes=t%60,n=I(t/60),u.hours=n%24,o+=i=I(Rn(a+=I(n/24))),a-=Pn(Wn(i)),s=I(o/12),o%=12,u.days=a,u.months=o,u.years=s,this},ss.clone=function(){return zt(this)},ss.get=function(e){return e=V(e),this.isValid()?this[e+"s"]():NaN},ss.milliseconds=Zn,ss.seconds=zn,ss.minutes=$n,ss.hours=qn,ss.days=Bn,ss.weeks=function(){return I(this.days()/7)},ss.months=Jn,ss.years=Qn,ss.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var n,s,i=!1,r=Kn;return"object"==typeof e&&(t=e,e=!1),"boolean"==typeof e&&(i=e),"object"==typeof t&&(r=Object.assign({},Kn,t),null!=t.s&&null==t.ss&&(r.ss=t.s-1)),s=function(e,t,n,s){var i=zt(e).abs(),r=Xn(i.as("s")),a=Xn(i.as("m")),o=Xn(i.as("h")),u=Xn(i.as("d")),l=Xn(i.as("M")),h=Xn(i.as("w")),d=Xn(i.as("y")),c=r<=n.ss&&["s",r]||r0,c[4]=s,function(e,t,n,s,i){return i.relativeTime(t||1,!!n,e,s)}.apply(null,c)}(this,!i,r,n=this.localeData()),i&&(s=n.pastFuture(+this,s)),n.postformat(s)},ss.toISOString=ns,ss.toString=ns,ss.toJSON=ns,ss.locale=nn,ss.localeData=rn,ss.toIsoString=M("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ns),ss.lang=sn,C("X",0,0,"unix"),C("x",0,0,"valueOf"),ce("x",ue),ce("X",/[+-]?\d+(\.\d{1,3})?/),ye("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e))}),ye("x",function(e,t,n){n._d=new Date(Z(e))}), +//! moment.js +n.version="2.29.4",e=Nt,n.fn=Dn,n.min=function(){return Wt("isBefore",[].slice.call(arguments,0))},n.max=function(){return Wt("isAfter",[].slice.call(arguments,0))},n.now=function(){return Date.now?Date.now():+new Date},n.utc=c,n.unix=function(e){return Nt(1e3*e)},n.months=function(e,t){return bn(e,t,"months")},n.isDate=l,n.locale=ht,n.invalid=_,n.duration=zt,n.isMoment=v,n.weekdays=function(e,t,n){return xn(e,t,n,"weekdays")},n.parseZone=function(){return Nt.apply(null,arguments).parseZone()},n.localeData=ct,n.isDuration=Ht,n.monthsShort=function(e,t){return bn(e,t,"monthsShort")},n.weekdaysMin=function(e,t,n){return xn(e,t,n,"weekdaysMin")},n.defineLocale=dt,n.updateLocale=function(e,t){if(null!=t){var n,s,i=it;null!=rt[e]&&null!=rt[e].parentLocale?rt[e].set(b(rt[e]._config,t)):(null!=(s=lt(e))&&(i=s._config),t=b(i,t),null==s&&(t.abbr=e),(n=new x(t)).parentLocale=rt[e],rt[e]=n),ht(e)}else null!=rt[e]&&(null!=rt[e].parentLocale?(rt[e]=rt[e].parentLocale,e===ht()&&ht(e)):null!=rt[e]&&delete rt[e]);return rt[e]},n.locales=function(){return D(rt)},n.weekdaysShort=function(e,t,n){return xn(e,t,n,"weekdaysShort")},n.normalizeUnits=V,n.relativeTimeRounding=function(e){return void 0===e?Xn:"function"==typeof e&&(Xn=e,!0)},n.relativeTimeThreshold=function(e,t){return void 0!==Kn[e]&&(void 0===t?Kn[e]:(Kn[e]=t,"s"===e&&(Kn.ss=t-1),!0))},n.calendarFormat=function(e,t){var n=e.diff(t,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},n.prototype=Dn,n.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},n}); diff --git a/modules/http/webif/gstatic/js/smarthomeng.js b/modules/http/webif/gstatic/js/smarthomeng.js old mode 100644 new mode 100755 index 5d5db057dd..4ad55eb509 --- a/modules/http/webif/gstatic/js/smarthomeng.js +++ b/modules/http/webif/gstatic/js/smarthomeng.js @@ -115,8 +115,8 @@ function shngInsertText (id, text, table_id=null, highlight=0) { // update datatable cell if (new_content) { $('#' + table_id).DataTable().cell( $('#' + $.escapeSelector(id)) ).data(text); - console.log("Redrawing table because new cell data found: " + text + " for id " + id + ", old text: " + old_text); - $('#' + table_id).DataTable().draw(false); + console.log("Updating table because new cell data found: " + text + " for id " + id + ", old text: " + old_text); + // $('#' + table_id).DataTable().draw(false); } } } @@ -182,6 +182,129 @@ function shngGetUpdatedData(dataSet=null, update_params=null) { } } +/** + * calculates the optimal width of the headtable if no min-width css attribute is defined + */ +function calculateHeadtable() { + // try to get min-width from style attribute. If not available, calculate it + let headminwidth = parseFloat($( "#webif-headtable > table:first" ).css('min-width')); + if (headminwidth > 0) + { + console.log("Min-width " + headminwidth + "px of headtable (responsive)"); + return; + } + let arrOfTable1=[], i=0, x=0, y=0, columnWidth={}; + let table_margins = parseFloat($('#webif-headtable').css('padding-right')) + parseFloat($('#webif-headtable').css('padding-left')) + parseFloat($('#webif-headtable').css('margin-right')) + parseFloat($('#webif-headtable').css('margin-left')); + // iterate through all tr and td and wrap content into a span to calculate it's width + $('#webif-headtable tr').each(function() { + rowWidth = 0; + $(this).children('td').each(function() { + let calcVal = ''+ $(this).html()+''; + $(this).html(calcVal); + let margins = parseFloat($(this).css('padding-right')) + parseFloat($(this).css('padding-left')) + parseFloat($(this).css('margin-right')) + parseFloat($(this).css('margin-left')); + mWid = $(this).children('span:first').outerWidth() + margins; + rowWidth += mWid; + let current_mWid = columnWidth[y]; + if (isNaN(current_mWid)) current_mWid = 0; + columnWidth[y] = Math.max.apply(Math, [mWid, current_mWid]); + arrOfTable1.push(mWid); + //console.log(columnWidth); + y++; + }); + x++; + y = 0; + }); + // iterate through all td to add a min-width style attribute + $('#webif-headtable td').each(function() { + $(this).css("min-width",arrOfTable1[i]+"px"); + i++; + }); + function sum( obj ) { + var sum = 0; + for( var el in obj ) { + if( obj.hasOwnProperty( el ) ) { + sum += parseFloat( obj[el] ); + } + } + return sum; + } + let tableMinWidth = sum(columnWidth) + table_margins; + + console.log("Setting min-width " + tableMinWidth + "px for headtable (responsive)"); + // set the table min-width based on row with the highest width value + $( "#webif-headtable > table:first" ).css("min-width",tableMinWidth+"px"); +} + + +/** + * make the top navigation bar as responsive as possible + */ +function responsiveHeader() { + // remove the display:none attribute to show the element on the page after everything else is rendered + + // calculate different relevant element width values + let width = $( "#webif-toprow" ).outerWidth(true) - parseFloat($( "#webif-top" ).css('padding-left')) - parseFloat($( "#webif-top" ).css('padding-right')); + let headminwidth = parseFloat($( "#webif-headtable > table:first" ).css('min-width'), 10); + let headwidth = parseFloat($( "#webif-headtable" ).width()); + let buttonsrow = $( "#webif-allbuttons" ).outerWidth(true) + $( "#webif-custombuttons" ).outerWidth(true); + if (isNaN(buttonsrow)) buttonsrow = 0; + let buttonswidth = $( "#webif-custombuttons" ).width() + $( "#webif-autorefresh" ).width() + $( "#webif-reload_orig" ).width() + $( "#webif-close_orig" ).width() + $( "#webif-seconds_orig" ).width() + 82; + if (isNaN(buttonswidth)) buttonswidth = 0; + let logowidth = $( "#webif-pluginlogo" ).outerWidth(true); + let infowidth = $( "#webif-plugininfo" ).outerWidth(true); + //let total = width - headwidth - infowidth - logowidth; + // calculate table width based on button width or table min-width + let tablewidth = Math.max.apply(Math, [headminwidth, buttonsrow]) + //console.log("Widthes " + width + "-" + headminwidth + "-" + infowidth + "-" + logowidth + " table " + tablewidth + " buttonrow " + buttonsrow); + // disable logo and info and fully expand the table + if (width - infowidth - tablewidth <= 0) { + $( "#webif-plugininfo" ).css("display", "none"); + $( "#webif-pluginlogo" ).css("display", "none"); + $( "#webif-headtable" ).attr('class', 'col-sm-12'); + } + // disable logo and expand table a bit + else if (width - tablewidth - infowidth - logowidth - 35 <= 0) { + $( "#webif-pluginlogo" ).css("display", "none"); + $( "#webif-plugininfo" ).css("display", ""); + $( "#webif-headtable" ).attr('class', 'col-sm-9'); + } + else { + // show all elements as in the beginning + $( "#webif-plugininfo" ).css("display", ""); + $( "#webif-pluginlogo" ).css("display", ""); + $( "#webif-headtable" ).attr('class', 'col-sm-7'); + } + headwidth = $( "#webif-headtable" ).width(); + // minimize auto-refresh elements if necessary + if (headwidth - buttonswidth <= 0) { + $( "#webif-seconds" ).text(" s"); + $( "#webif-reload" ).text(""); + $( "#webif-close" ).text(""); + } + else { + // revert auto-refresh elements + $( "#webif-seconds" ).text($( "#webif-seconds_orig" ).text()); + $( "#webif-reload" ).text($( "#webif-reload_orig" ).text()); + $( "#webif-close" ).text($( "#webif-close_orig" ).text()); + } + // adjust height of top element based on height of table and other elements + let headheight = $( "#webif-navbar" ).outerHeight(true); + $('#webif-lowercontent').css('margin', headheight+'px auto 0'); + $('#webif-tabs').css('top', headheight+'px'); + let topheight = headheight + $( "#webif-tabs" ).outerHeight(true); + $('#resize_wrapper').css('top', topheight+'px'); + $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.headerOffset( topheight ); +} + +function initHeader() { + $( "#webif-navbar" ).show(); + calculateHeadtable(); + responsiveHeader(); +} +//const resizeObserver = new ResizeObserver(entries => responsiveHeader() ); +addEventListener('DOMContentLoaded', initHeader, false); + +addEventListener('resize', responsiveHeader, false); /** * replaces the body attribs onload approach. The major difference is that all parameters like activate diff --git a/modules/http/webif/gstatic/js/smarthomeng_pageupdate.js b/modules/http/webif/gstatic/js/smarthomeng_pageupdate.js old mode 100644 new mode 100755 index 27159fc571..c7ed5a8c7c --- a/modules/http/webif/gstatic/js/smarthomeng_pageupdate.js +++ b/modules/http/webif/gstatic/js/smarthomeng_pageupdate.js @@ -2,14 +2,14 @@ * enables or disables the auto refresh checkbox based on form input for interval */ function set_update_active() { - if(document.getElementById("update_interval").value.length == 0 || document.getElementById("update_interval").value == 0 || window.update_blocked == true) + if (document.body.contains(document.getElementById("update_interval")) && (document.getElementById("update_interval").value.length == 0 || document.getElementById("update_interval").value == 0 || window.update_blocked == true)) { $('input[name=update_active]').attr('disabled','disabled'); document.getElementById("update_active").checked = false; window.update_active = false; $('input[name=update_active]').attr('title', 'Automatische Aktualisierung nicht möglich/deaktiviert'); } - else + else if (document.body.contains(document.getElementById("update_active"))) { window.update_active = document.getElementById("update_active").checked; $('input[name=update_active]').removeAttr('disabled'); @@ -20,11 +20,13 @@ function set_update_active() { * sets the update interval based on form input and setting via JS */ function set_update_interval() { - window.update_active = document.getElementById("update_active").checked; - window.update_interval = document.getElementById("update_interval").value * 1000; + if ( document.body.contains(document.getElementById("update_active")) ) + window.update_active = document.getElementById("update_active").checked; + if ( document.body.contains(document.getElementById("update_interval")) ) + window.update_interval = document.getElementById("update_interval").value * 1000; if (window.update_active) { - refresh.set_interval(update_interval, false); + refresh.set_interval(update_interval, true); console.log("Set Refresh Interval to " + update_interval + ", active " + update_active); } else { @@ -36,11 +38,15 @@ function set_update_interval() { * initialize form values correctly and update active checkbox based on interval value (disabled on 0) */ $(window).on('load', function (e) { - document.getElementById("update_active").checked = window.update_active; - document.getElementById("update_interval").value = window.update_interval / 1000; + if ( document.body.contains(document.getElementById("update_active")) ) + document.getElementById("update_active").checked = window.update_active; + if ( document.body.contains(document.getElementById("update_interval")) ) + document.getElementById("update_interval").value = window.update_interval / 1000; set_update_active(); - let interval_input = document.getElementById("update_interval"); - interval_input.addEventListener('input', event => { - set_update_active(); - }); + if ( document.body.contains(document.getElementById("update_interval")) ) { + let interval_input = document.getElementById("update_interval"); + interval_input.addEventListener('input', event => { + set_update_active(); + }); + } }); diff --git a/modules/http/webif/gtemplates/base.html b/modules/http/webif/gtemplates/base.html index c8378babf9..cb3a988b87 100755 --- a/modules/http/webif/gtemplates/base.html +++ b/modules/http/webif/gtemplates/base.html @@ -19,12 +19,19 @@ {%- endblock metas %} {%- block styles %} + - - - - - + {% if isfile("static/img/plugin_logo.png") %} + + {% if isfile("static/img/plugin_logo_big.png") %} + + {% else %} + + {% endif %} + {% else %} + + + {% endif %} @@ -53,12 +60,14 @@ + + diff --git a/modules/http/webif/gtemplates/base_plugin.html b/modules/http/webif/gtemplates/base_plugin.html index 68d65a4595..273edb38ff 100755 --- a/modules/http/webif/gtemplates/base_plugin.html +++ b/modules/http/webif/gtemplates/base_plugin.html @@ -20,11 +20,19 @@ // Make refresh variable, dataSet and update_params available for plugins html files. window.refresh = new timer(); window.dataSet = null; + window.activeTab = 1; window.update_params = null; window.update_interval = 0; window.update_active = false; window.update_blocked = false; - // window.row_count = 0; If you want to get the row count on the current page, put this line in the resepctive plugin index.html + window.toggle = 1; + window.initial_update = false; + {%- if row_count is defined %} + window.row_count = '{{ row_count|lower }}'; + {%- endif %} + {%- if initial_update is defined %} + window.initial_update = '{{ initial_update|lower }}'; + {%- endif %} {%- if dataSet is defined %} window.dataSet = '{{ dataSet }}'; {%- endif %} @@ -75,13 +83,13 @@ {% block content -%} {% if scroll_heading is not defined %} -
+
- - - - -
- {% block buttons %} - {% endblock buttons %} - - - {{ _('Autom. Aktualisierung') }} - - {{ _('Sekunden') }} - - - -
@@ -140,7 +161,7 @@
{{ _('Plugin') }}     : {% if p.aliv {% if scroll_heading is not defined %} -
+
{% endif %} {% if tabcount is not defined %} @@ -153,7 +174,9 @@
{{ _('Plugin') }}     : {% if p.aliv {% if start_tab > tabcount %} {% set start_tab = tabcount %} {% endif %} - + {% if tabcount > 6 %} {% set tabcount = 6 %} {% endif %} @@ -211,7 +234,6 @@
{{ _('Plugin') }}     : {% if p.aliv
-
{% block bodytab1 %} {% endblock bodytab1 %} diff --git a/modules/mqtt/__init__.py b/modules/mqtt/__init__.py index 1b62e48436..166971073f 100644 --- a/modules/mqtt/__init__.py +++ b/modules/mqtt/__init__.py @@ -43,7 +43,7 @@ class Mqtt(Module): - version = '1.7.3' + version = '1.7.4' longname = 'MQTT module for SmartHomeNG' __plugif_CallbackTopics = {} # for plugin interface @@ -556,7 +556,7 @@ def _on_mqtt_message(self, client, userdata, message): if (topic == message.topic) or topics_equal: topic_dict = self._subscribed_topics[topic] - for subscription in topic_dict: + for subscription in list(topic_dict): self.logger.debug("_on_mqtt_message: subscription '{}': {}".format(subscription, topic_dict[subscription])) subscriber_type = topic_dict[subscription].get('subscriber_type', None) if subscriber_type == 'plugin': diff --git a/modules/mqtt/module.yaml b/modules/mqtt/module.yaml index a28edec60a..97944a0e17 100644 --- a/modules/mqtt/module.yaml +++ b/modules/mqtt/module.yaml @@ -2,7 +2,7 @@ module: # Global plugin attributes classname: Mqtt - version: 1.7.3 + version: 1.7.4 sh_minversion: 1.6a # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) description: diff --git a/modules/websocket/__init__.py b/modules/websocket/__init__.py index 1d54c6ac0f..5fecc6219e 100644 --- a/modules/websocket/__init__.py +++ b/modules/websocket/__init__.py @@ -21,10 +21,8 @@ import asyncio -import janus import ssl import threading -import decimal import websockets import time @@ -33,21 +31,15 @@ import sys import socket import logging -import json -import collections -from datetime import date, datetime - from lib.model.module import Module -from lib.item import Items -from lib.logic import Logics from lib.shtime import Shtime from lib.utils import Utils class Websocket(Module): - version = '1.0.5' + version = '1.0.9' longname = 'Websocket module for SmartHomeNG' port = 0 @@ -62,7 +54,7 @@ def __init__(self, sh, testparam=''): self.logger = logging.getLogger(__name__) self._sh = sh self.etc_dir = sh._etc_dir - self.shtime = Shtime.get_instance() + #self.shtime = Shtime.get_instance() self.logger.debug(f"Module '{self._shortname}': Initializing") @@ -77,16 +69,6 @@ def __init__(self, sh, testparam=''): self.tls_cert = self.get_parameter_value('tls_cert') self.tls_key = self.get_parameter_value('tls_key') - # parameters for smartVISU handling are initialized by the smartvisu plugin - # self.sv_enabled = self.get_parameter_value('sv_enabled') - # self.sv_acl = self.get_parameter_value('default_acl') - # self.sv_querydef = self.get_parameter_value('sv_querydef') - # self.sv_ser_upd_cycle = self.get_parameter_value('sv_ser_upd_cycle') - self.sv_enabled = False - self.sv_acl = 'deny' - self.sv_querydef = False - self.sv_ser_upd_cycle = 0 - self.ssl_context = None if self.use_tls: self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) @@ -110,17 +92,12 @@ def __init__(self, sh, testparam=''): self.logger.info(f"Listening on IP .: {self.ip}") self.logger.info(f"port / tls_port .: {self.port} / {self.tls_port}") self.logger.info(f"use_tls .........: {self.use_tls}") - self.logger.info(f"certificate / key: {self.tls_cert} / {self.tls_key}") - - # try to get API handles - self.items = Items.get_instance() - self.logics = Logics.get_instance() + self.logger.info(f"certificate .....: key: ../etc/{self.tls_cert} / ../etc/{self.tls_key}") self.loop = None # Var to hold the event loop for asyncio - # For Release 1.8 only: Enable smartVISU protocol support even if smartvisu plugin is not loaded - self.set_smartvisu_support(protocol_enabled=True) - + self.initialize_payload_protocols() + return def start(self): """ @@ -133,7 +110,7 @@ def start(self): try: self._server_thread = threading.Thread(target=self._ws_server_thread, name=_name) self._server_thread.start() - self.logger.info("Starting websocket server(s)...") + self.logger.dbghigh("Starting websocket server(s)...") except Exception as e: self.conn = None self.logger.error(f"Websocket Server: Cannot start server - Error: {e}") @@ -158,38 +135,75 @@ def stop(self): pass return - def set_smartvisu_support(self, protocol_enabled=False, default_acl='ro', query_definitions=False, series_updatecycle=0): + + def initialize_payload_protocols(self): """ - Set state of smartvisu support + Initialize the supported payload protocols - :param protocol_enabled: enable or disable the payload protocol for smartVISU - :param query_definitions: enable or disable the query of item definitions over websocket protocol - :param series_updatecycle: update cycle for smartVISU series requests (if 0, timing from database plugin is used) + :return: """ - self.sv_enabled = protocol_enabled - self.sv_acl = default_acl - self.sv_querydef = query_definitions - self.sv_ser_upd_cycle = int(series_updatecycle) - self.logger.info(f"set_smartvisu_support: Set to protocol_enabled={protocol_enabled}, default_acl={default_acl}, query_definitions={query_definitions}, series_updatecycle={series_updatecycle}") - # self.sv_config = {'enabled': self.sv_enabled, 'acl': self.sv_acl, 'query_def': self.sv_querydef, 'upd_cycle': self.sv_ser_upd_cycle} - # self.logger.warning(f"sv_config {self.sv_config}") - - # self.stop() - # self.start() + self.protocols = {} + + # parameters and class instance for sync_example protocol + from . import sync_example + self.initialize_payload_protocol(sync_example.Protocol) + + # parameters and class instance for smartVISU protocol + from . import smartvisu + self.initialize_payload_protocol(smartvisu.Protocol) + + return + + + def initialize_payload_protocol(self, Protocol): + + # hand the websocket module instance (self) to protocol object + id = Protocol.protocol_id + prot = Protocol(self, self.logger.name+'.'+id) + self.protocols[prot.protocol_path] = {} + self.protocols[prot.protocol_path]['id'] = id + self.protocols[prot.protocol_path]['name'] = prot.protocol_name + self.protocols[prot.protocol_path]['protocol'] = prot + self.logger.info(f"Payload protocol '{ prot.protocol_name}' initialized ({'enabled' if prot.protocol_enabled else 'disabled'})") return + def get_payload_protocol_by_id(self, id): + + result = None + for path in self.protocols: + if self.protocols[path]['id'] == id: + result = self.protocols[path]['protocol'] + break + return result + + def get_port(self): + """ + Returns the port used for the ws:// protocol + + :return: port number + """ return self.port def get_tls_port(self): + """ + Returns the port used for the secure wss:// protocol + + :return: port number + """ return self.tls_port def get_use_tls(self): + """ + Returns True, if secure websocket protocol (wss://) is enabled + + :return: True, if secure websocket protocol is enabled + """ return self.use_tls @@ -215,27 +229,16 @@ def _ws_server_thread(self): self.loop.create_task(self.ws_server(self.ip, self.port), name='ws_server') # self.loop.ensure_future(self.ws_server(self.ip, self.port)) if self.ssl_context is not None: - if python_version == '3.6': - self.loop.ensure_future(self.ws_server(self.ip, self.tls_port, self.ssl_context)) - elif python_version == '3.7': + if python_version == '3.7': self.loop.create_task(self.ws_server(self.ip, self.tls_port, self.ssl_context)) else: self.loop.create_task(self.ws_server(self.ip, self.tls_port, self.ssl_context), name='wss_server') # self.loop.ensure_future(self.ws_server(self.ip, self.tls_port, self.ssl_context)) - if python_version == '3.6': - self.loop.ensure_future(self.update_visu()) - self.loop.ensure_future(self.update_all_series()) - elif python_version == '3.7': - self.loop.create_task(self.update_visu()) - self.loop.create_task(self.update_all_series()) - else: - self.loop.create_task(self.update_visu(), name='update_visu') - self.loop.create_task(self.update_all_series(), name='update_all_series') - - # self.loop.ensure_future(self.update_visu()) - # self.loop.ensure_future(self.update_all_series()) + # start tasks, that are global for a payload protocol + for path in self.protocols: + self.protocols[path]['protocol'].start_global_tasks(self.loop) try: self.loop.run_forever() @@ -246,17 +249,14 @@ def _ws_server_thread(self): #if python_version >= '3.9': # self.loop.shutdown_default_executor() #time.sleep(3) - #self.logger.notice(f"dir(self.loop): {dir(self.loop)}") #self.logger.notice(f"all_tasks: {self.loop.Task.all_tasks()}") #self.loop.run_until_complete(self.loop.shutdown_asyncgens()) except Exception as e: - self.logger.warning(f"_ws_server_thread: finally *1 - Exception {e}") - #self.logger.warning("_ws_server_thread: finally *1x") + self.logger.warning(f"_ws_server_thread: finally - Exception on loop.shutdown_asyncgens(): {e}") try: self.loop.close() except Exception as e: - self.logger.warning(f"_ws_server_thread: finally *2 - Exception {e}") - #self.logger.warning("_ws_server_thread: finally *2x") + self.logger.warning(f"_ws_server_thread: finally - Exception on loop.close(): {e}") USERS = set() @@ -279,6 +279,15 @@ async def ws_server(self, ip, port, ssl_context=None): return + + """ + =============================================================================== + = + = The following method(s) implement the handling of new connections (users) + = and the disconnections after the websocket connection terminates + = + """ + async def handle_new_connection(self, websocket, path): """ Wait for incoming connection and handle the request @@ -288,15 +297,20 @@ async def handle_new_connection(self, websocket, path): await self.register(websocket) try: - - if path == '/' and self.sv_enabled: - await self.smartVISU_protocol_v4(websocket) - elif path == '/sync': - await self.counter_sync(websocket) + # Determine payload protocol and start it if found and enabled + payload = self.protocols.get(path, None) + if payload is None: + self.logger.warning(f"Unsupported websocket path '{path}' used by {self.client_address(websocket)}. Cannot determine payload protocol - terminating connection") + else: + if payload['protocol'].protocol_enabled: + self.logger.info(f"Starting '{payload['name']}' payload protocol") + await payload['protocol'].handle_protocol(websocket) + else: + self.logger.notice(f"Payload protocol '{payload['name']}' is disabled - terminating connection") except Exception as e: # connection has been ended or not established in payload protocol - self.logger.info(f"handle_new_connection - Connection to {e} has been terminated in payload protocol or couldn't be established") + self.logger.info(f"handle_new_connection: Connection to {e} has been terminated in payload protocol") finally: await self.unregister(websocket) return @@ -313,893 +327,39 @@ async def unregister(self, websocket): """ Unregister an incoming connection """ + payload = self.protocols.get(websocket.path, None) + if payload is not None: + await payload['protocol'].cleanup_connection(websocket) + self.USERS.remove(websocket) await self.log_connection_event('removed', websocket) return async def log_connection_event(self, action, websocket): """ - Print info about connection/disconnection of users + Log info about connection/disconnection of users """ if not websocket.remote_address: - self.logger.info(f"USER {action}: {'with SSL connection'} - local port: {websocket.port}") + self.logger.info(f"USER {action}: {'with SSL connection'} - local port: {websocket.port} - path: {websocket.path}") else: - self.logger.info(f"USER {action}: {self.build_client_info(websocket.remote_address)} - local port: {websocket.port}") + self.logger.info(f"USER {action}: {self.client_address(websocket)} - local port: {websocket.port} - path: {websocket.path}") - self.logger.debug(f"Connected USERS: {len(self.USERS)}") + self.logger.dbghigh(f"Connected USERS: {len(self.USERS)}") for u in self.USERS: - self.logger.debug(f"- user: {u.remote_address} path: {u.path} secure: {u.secure}") + self.logger.dbghigh(f"- user: {self.client_address(u)} path: {u.path} secure: {u.secure} port: {u.port}") return - """ - =============================================================================== - = - = The following method(s) implement the webmethods protocol for sync example - = - """ - - STATE = {"value": 0} - - def state_event(self): - return json.dumps({"type": "state", **self.STATE}) - - def users_event(self): - return json.dumps({"type": "users", "count": len(self.USERS)}) - - async def notify_state(self): - if self.USERS: # asyncio.wait doesn't accept an empty list - message = self.state_event() - await asyncio.wait([user.send(message) for user in self.USERS]) - - async def notify_users(self): - if self.USERS: # asyncio.wait doesn't accept an empty list - message = self.users_event() - await asyncio.wait([user.send(message) for user in self.USERS]) - - async def counter_sync(self, websocket): - await self.notify_users() - await websocket.send(self.state_event()) - - async for message in websocket: - data = json.loads(message) - if data.get("cmd", ''): - self.logger.warning(f"CMD: {data}") - elif data.get("action", '') == "minus": - self.STATE["value"] -= 1 - await self.notify_state() - elif data.get("action", '') == "plus": - self.STATE["value"] += 1 - await self.notify_state() - else: - logging.error(f"unsupported event: {data}") - - await self.notify_users() - return - """ - =============================================================================== - = - = The following method(s) implement the webmethods protocol for smartVISU - = - = The protocol implements the version 4 of the protocol as it has been - = implemented by the visu_websocket plugin - = - """ - # variables for smartVISU protocol - # monitor = {'item': [], 'rrd': [], 'log': []} - sv_monitor_items = {} - sv_monitor_logs = {} - sv_clients = {} - sv_update_series = {} - clients = [] - proto = 4 - _series_lock = threading.Lock() - - janus_queue = None # var that holds the queue betweed threaded and async - - async def get_shng_class_instances(self): - """ - Ensure that the instance vars for items and logics are initialized - """ - while self.items is None: - self.items = Items.get_instance() - if self.items is None: - await asyncio.sleep(1) - while self.logics is None: - self.logics = Logics.get_instance() - if self.logics is None: - await asyncio.sleep(1) - return def client_address(self, websocket): return websocket.remote_address[0] + ':' + str(websocket.remote_address[1]) - def json_serial(self, obj): - """JSON serializer for objects not serializable by default json code""" - - if isinstance(obj, decimal.Decimal): - return float(obj) - if isinstance(obj, (datetime, date)): - return obj.isoformat() - - raise TypeError("Type %s not serializable" % type(obj)) - - async def smartVISU_protocol_v4(self, websocket): - - # items = [] - - self.logs = self._sh.logs.return_logs() - - # Prevent the event listener for event types "log" from being added multiple times: - known_log_listeners = self._sh.return_event_listeners(event='log') - if self.update_visulog not in known_log_listeners: - self._sh.add_event_listener(['log'], self.update_visulog) - else: - self.logger.debug(f"smartVISU_protocol_v4: self.update_visulog function already subscribed as event listener") - - client_addr = self.client_address(websocket) - client_ip = websocket.remote_address[0] - self.sv_clients[client_addr] = {} - self.sv_clients[client_addr]['websocket'] = websocket - try: - self.sv_clients[client_addr]['hostname'] = socket.gethostbyaddr(client_ip)[0] - except: - pass - self.sv_clients[client_addr]['sw'] = "'some_visu'" - self.logger.info(f"smartVISU_protocol_v4: Client {self.build_log_info(client_addr)} started") - # client_addr = websocket.remote_address[0] + ':' + str(websocket.remote_address[1]) - await self.get_shng_class_instances() - - if not self.janus_queue: - self.janus_queue = janus.Queue() - - try: - async for message in websocket: - data = json.loads(message) - command = data.get("cmd", '') - protocol = 'wss' if websocket.secure else 'ws ' - # self.logger.warning("{} No reply from prepare_series() (for request {data})") - else: - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested a series for an unknown item: {path}") - - elif command == 'series_cancel': - answer = await self.cancel_series(data, client_addr) - - elif command == 'log': - answer = {} - name = data['name'] - num = 10 - if 'max' in data: - num = int(data['max']) - #self.logger.notice(f"command == 'log': data={data}") - #self.logger.notice(f"command == 'log': self.logs={self.logs}") - if name in self.logs: - answer = {'cmd': 'log', 'name': name, 'log': self.logs[name].export(num), 'init': 'y'} - if client_addr not in self.sv_monitor_logs: - self.sv_monitor_logs[client_addr] = [] - if name not in self.sv_monitor_logs[client_addr]: - self.sv_monitor_logs[client_addr].append(name) - else: - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid log: {name}") - - elif command == 'ping': - answer = {'cmd': 'pong'} - - elif command == 'proto': # protocol version - proto = data['ver'] - if proto > self.proto: - self.logger.warning(f"WebSocket: protocol mismatch. SmartHomeNG protocol version={self.proto}, visu protocol version={proto}") - elif proto < self.proto: - self.logger.warning(f"WebSocket: protocol mismatch. Update your client: {self.build_log_info(client_addr)}") - answer = {'cmd': 'proto', 'ver': self.proto, 'server': 'module.websocket', 'time': self.shtime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")} - - elif command == 'identity': # identify client - client = data.get('sw', "'some_visu'") - self.sv_clients[client_addr]['sw'] = data.get('sw', '') - self.sv_clients[client_addr]['ver'] = data.get('ver', '') - if data.get('hostname', '') != '': - self.sv_clients[client_addr]['hostname'] = data.get('hostname', '') - self.sv_clients[client_addr]['browser'] = data.get('browser', '') - self.sv_clients[client_addr]['bver'] = data.get('bver', '') - self.logger.info(f"smartVISU_protocol_v4: Client {self.build_client_info(client_addr)} identified as {self.build_sw_info(client_addr)}") - answer = {} - - elif command == 'list_items': - answer = {} - if self.sv_querydef: - path = data.get('path', '') - answer = await self.request_list_items(path, client_addr) - self.logger.warning(f"{protocol} REPLY: '{answer}' - to {self.build_log_info(websocket.remote_address)}") - #except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: - except Exception as e: - self.logger.warning(f"smartVISU_protocol_v4: Exception in 'await websocket.send(reply)': {e} - reply = {reply} to {self.build_log_info(websocket.remote_address)}") - - except Exception as e: - ex = str(e) - if str(e).startswith(('code = 1005', 'code = 1006', 'no close frame received or sent')) or str(e).endswith('keepalive ping timeout; no close frame received'): - self.logger.warning(f"smartVISU_protocol_v4 error: Client {self.build_log_info(client_addr)} - {e}") - else: - self.logger.error(f"smartVISU_protocol_v4 exception: Client {self.build_log_info(client_addr)} - {ex}") - - # ms: Test wg. "iPad Sleep" - #if str(e).startswith('no close frame received or sent'): - # return - - # Remove client from monitoring dict and from dict of active clients #ms: Block eingerückt am 26.10.22 - del(self.sv_monitor_items[client_addr]) - try: - del(self.sv_clients[client_addr]) - except Exception as e: - self.logger.error(f"smartVISU_protocol_v4 error deleting client session data: {e}") - - self.logger.info(f"smartVISU_protocol_v4: Client {self.build_log_info(client_addr)} stopped") - return - - - def build_client_info(self, client_addr): - """ - Build string with client host info for info/error logging - :param client_addr: - :return: info string - """ - if isinstance(client_addr, tuple): - client_addr = client_addr[0] + ':' + str(client_addr[1]) - - if self.sv_clients.get(client_addr): - if self.sv_clients[client_addr].get('hostname', '') == '': - return f"{client_addr}" - else: - return f"{self.sv_clients[client_addr].get('hostname', '')} ({client_addr})" - else: - return f"{client_addr}" - - def build_sw_info(self, client_addr): - """ - Build string with client host info for info/error logging - :param client_addr: - :return: info string - """ - if self.sv_clients.get(client_addr): - sw = f"{self.sv_clients[client_addr].get('sw', '')} {self.sv_clients[client_addr].get('ver', '')}".strip() - browser = f"{self.sv_clients[client_addr].get('browser', '')} {self.sv_clients[client_addr].get('bver', '')}".strip() - - if browser == '': - return sw - else: - return f"{sw}, {browser}" - else: - return "" - - def build_log_info(self, client_addr): - """ - Build string with client info (name and software) for info/error logging - :param client_addr: - :return: info string - """ - if isinstance(client_addr, tuple): - client_addr = client_addr[0] + ':' + str(client_addr[1]) - - sw = self.build_sw_info(client_addr) - if sw != '': - sw = ', ' + sw - - return f"{self.build_client_info(client_addr)}{sw}" - - - async def prepare_monitor(self, data, client_addr): - """ - Prepare the return of item monitoring data - - :param data: data of the visu's request - :param client_addr: address of the client (visu) - - :return: answer to the visu - """ - answer = {} - items = [] - newmonitor_items = [] - for path in list(data['items']): - path_parts = 0 if path is None else path.split('.property.') - if len(path_parts) == 1: - self.logger.debug(f"Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]}") - try: - item = self.items.return_item(path) - if item is not None: - item_acl = item.conf.get('acl', None) - if item_acl is None: - item_acl = self.sv_acl - if item_acl != 'deny': - items.append([path, item()]) - if self.update_visuitem not in item.get_method_triggers(): - item.add_method_trigger(self.update_visuitem) - else: - self.logger.error(f"prepare_monitor: No item '{path}' found (requested by client {self.build_log_info(client_addr)}") - except KeyError as e: - self.logger.warning(f"KeyError: Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]} which can not be found") - else: - newmonitor_items.append(path) - elif len(path_parts) == 2: - self.logger.debug(f"Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[1]} with property {path_parts[0]}") - try: - prop = self.items.return_item(path_parts[0]).property - prop_attr = getattr(prop, path_parts[1]) - items.append([path, prop_attr]) - newmonitor_items.append(path) - except KeyError as e: - self.logger.warning(f"Property KeyError: Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]} with property {path_parts[1]}") - except AttributeError as e: - self.logger.warning(f"Property AttributeError: Client {self.build_log_info(client_addr)} requested to monitor property {path_parts[1]} of item {path_parts[0]}") - - else: - self.logger.warning("Client {self.build_log_info(client_addr)} requested invalid item: {path}") - self.logger.debug(f"json_parse: send to {self.build_log_info(client_addr)}: {({'cmd': 'item', 'items': items})}") - answer = {'cmd': 'item', 'items': items} - self.sv_monitor_items[client_addr] = newmonitor_items - self.logger.info(f"Client {self.build_log_info(client_addr)} new monitored items are {newmonitor_items}") - return answer - - async def prepare_series(self, data, client_addr): - """ - Prepare the return of series data - - :param data: data of the visu's request - :param client_addr: address of the client (visu) - - :return: answer to the visu - """ - answer = {} - path = data['item'] - series = data['series'] - start = data['start'] - if 'end' in data: - end = data['end'] - else: - end = 'now' - if 'count' in data: - count = data['count'] - else: - count = 100 - - item = self.items.return_item(path) - if item is not None: - if hasattr(item, 'series'): - try: - # reply = item.series(series, start, end, count) - reply = await self.loop.run_in_executor(None, item.series, series, start, end, count) - except Exception as e: - self.logger.error(f"Problem fetching series for {path}: {e} - Wrong sqlite/database plugin?") - else: - if 'update' in reply: - await self.loop.run_in_executor(None, self.set_periodic_series_updates, reply, client_addr) - # with self._series_lock: - # self.sv_update_series[reply['sid']] = {'update': reply['update'], 'params': reply['params']} - del (reply['update']) - del (reply['params']) - if reply['series'] is not None: - answer = reply - else: - self.logger.info(f"WebSocket: no entries for series {path} {series}") - else: - if path.startswith('env.'): - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid series: {path}. Probably not database plugin is configured") - else: - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid series: {path}.") - return answer - - def set_periodic_series_updates(self, reply, client_addr): - """ - -> blocking method - called via run_in_executor() - """ - with self._series_lock: - if self.sv_update_series.get(client_addr, None) is None: - self.sv_update_series[client_addr] = {} - self.sv_update_series[client_addr][reply['sid']] = {'update': reply['update'], 'params': reply['params']} - return - - async def update_all_series(self): - """ - Async task to periodically update the series data for the visu(s) - """ - # wait until SmartHomeNG is completly initialized - while self._sh.shng_status['code'] != 20: - await asyncio.sleep(1) - - self.logger.info("update_all_series: Started") - keep_running = True - while keep_running: - remove = [] - series_list = list(self.sv_update_series.keys()) - if series_list != []: - txt = '' - if self.sv_ser_upd_cycle > 0: - txt = " - Fixed update-cycle time" - #self.logger.info("update_all_series: series_list={}{}".format(series_list, txt)) - for client_addr in series_list: - if (client_addr in self.sv_clients) and not (client_addr in remove): - self.logger.debug(f"update_all_series: Updating client {self.build_log_info(client_addr)}...") - websocket = self.sv_clients[client_addr]['websocket'] - replys = await self.loop.run_in_executor(None, self.update_series, client_addr) - for reply in replys: - if (client_addr in self.sv_clients) and not (client_addr in remove): - self.logger.info(f"update_all_series: reply {reply} --> Replys for client {self.build_log_info(client_addr)}: {replys}") - try: - await websocket.send(json.dumps(reply, default=self.json_serial)) - self.logger.info(f">SerUp {reply}: {self.build_log_info(client_addr)}") - # except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: - except Exception as e: - self.logger.info(f"update_all_series: Exception in 'await websocket.send(reply)': {e}") - remove.append(client_addr) - else: - self.logger.info(f"update_all_series: Client {self.build_log_info(client_addr)} is not active any more #1") - pass - else: - self.logger.info(f"update_all_series: Client {self.build_log_info(client_addr)} is not active any more #2") - remove.append(client_addr) - - # Remove series for clients that are not connected any more - for client_addr in remove: - del (self.sv_update_series[client_addr]) - - await self.sleep(10) - - if self.sv_ser_upd_cycle > 0: - # wait for sv_ser_upd_cycle seconds before running update loop and update all series - #await asyncio.sleep(self.sv_ser_upd_cycle) - await self.sleep(self.sv_ser_upd_cycle) - else: - # wait for 10 seconds before running update loop again (loop gets update cycle from database plugin) - await self.sleep(10) - - if self._sh.shng_status['code'] != 20: - # if SmartHomeNG leaves running mode - keep_running = False - self.logger.info("update_all_series: Terminating loop, because SmartHomeNG left running mode") - - - async def sleep(self, seconds): - """ - sleep method with abort, if smarthomeNG leaves running mode - :param seconds: - """ - for i in range(seconds): - if self._sh.shng_status['code'] == 20: - await asyncio.sleep(1) - - - def update_series(self, client_addr): - """ - -> blocking method - called via run_in_executor() - """ - # websocket = self.sv_clients[client_addr]['websocket'] - now = self.shtime.now() - with self._series_lock: - remove = [] - series_replys = [] - - series_entry = self.sv_update_series.get(client_addr, None) - if series_entry is not None: - for sid, series in self.sv_update_series[client_addr].items(): - if (series['update'] < now) or self.sv_ser_upd_cycle > 0: - # self.logger.warning("update_series: {} - Processing sid={}, series={}".format(client_addr, sid, series)) - item = self.items.return_item(series['params']['item']) - try: - reply = item.series(**series['params']) - except Exception as e: - self.logger.exception(f"Problem updating series for {series['params']}: {e}") - remove.append(sid) - continue - self.sv_update_series[client_addr][reply['sid']] = {'update': reply['update'], 'params': reply['params']} - del (reply['update']) - del (reply['params']) - if reply['series'] is not None: - series_replys.append(reply) - - for sid in remove: - del (self.sv_update_series[client_addr][sid]) - - return series_replys - - async def cancel_series(self, data, client_addr): - """ - Cancel the update of series data - - :param data: data of the visu's request - :param client_addr: address of the client (visu) - - :return: answer to the visu - """ - answer = {} - path = data['item'] - series = data['series'] - - if 'start' in data: - start = data['start'] - else: - start = '72h' - if 'end' in data: - end = data['end'] - else: - end = 'now' - if 'count' in data: - count = data['count'] - else: - count = 100 - - self.logger.info(f"Series cancelation: path={path}, series={series}, start={start}, end={end}, count={count}") - item = self.items.return_item(path) - try: - # reply = item.series(series, start, end, count) - reply = await self.loop.run_in_executor(None, item.series, series, start, end, count) - self.logger.info(f"cancel_series: reply={reply}") - self.logger.info(f"cancel_series: self.sv_update_series={self.sv_update_series}") - except Exception as e: - self.logger.error(f"cancel_series: Problem fetching series for {path}: {e} - Wrong sqlite plugin?") - else: - answer = await self.loop.run_in_executor(None, self.cancel_periodic_series_updates, reply, path, client_addr) - return answer - - def cancel_periodic_series_updates(self, reply, path, client_addr): - """ - -> blocking method - called via run_in_executor() - """ - with self._series_lock: - try: - del (self.sv_update_series[client_addr][reply['sid']]) - if self.sv_update_series[client_addr] == {}: - del (self.sv_update_series[client_addr]) - self.logger.info(f"Series cancelation: Series updates for path {path} canceled") - answer = {'cmd': 'series_cancel', 'result': "Series updates for path {} canceled".format(path)} - except: - self.logger.warning(f"Series cancelation: No series for path {path} found in list") - answer = {'cmd': 'series_cancel', 'error': "No series for path {} found in list".format(path)} - return answer - - async def update_visu(self): - """ - Async task to update the visu(s) if items have changed or an url command has been issued - """ - while not self.janus_queue: - await asyncio.sleep(1) - - while True: - if self.janus_queue: - queue_entry = await self.janus_queue.async_q.get() - if queue_entry[0] == 'item': - item_data = queue_entry[1] - # item_data: set (item_name, item_value, caller, source) - try: - await self.update_item(item_data[0], item_data[1], item_data[3]) - except Exception as e: - self.logger.error(f"update_visu: Error in 'await self.update_item(...)': {e}") - elif queue_entry[0] == 'log': - log_entry = queue_entry[1] - # log_entry: dict {'name', 'log'} - # log is a list and contains dicts: {'time', 'thread', 'level', 'message'} - #self.logger.info(f"update_visu: queue_entry = {queue_entry}") - try: - await self.update_log(log_entry) - except Exception as e: - self.logger.error(f"update_visu: Error in 'await self.update_log(...)': {e}") - elif queue_entry[0] == 'command': - # send command to visu (e.g. url command) - command = queue_entry[1] - client_addr = queue_entry[2] - websocket = self.sv_clients[client_addr]['websocket'] - try: - await websocket.send(command) - self.logger.warning(f"visu >command: '{command}' - to {client_addr}") - # except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: - except Exception as e: - self.logger.error(f"smartVISU_protocol_v4: Exception in 'await websocket.send(url-command)': {e}") - else: - self.logger.error(f"update_visu: Unknown queueentry type '{queue_entry[0]}'") - - async def update_item(self, item_name, item_value, source): - """ - send JSON data with new value of an item - """ - items = [] - # self.logger.warning("update_item: self.monitor['item']") - items_list = list(self.sv_monitor_items.keys()) - for client_addr in items_list: - websocket = self.sv_clients[client_addr]['websocket'] - for candidate in self.sv_monitor_items[client_addr]: - - try: - # self.logger.debug("Send update to Client {0} for candidate {1} and item_name {2}?".format(client_addr, candidate, item_name)) - path_parts = candidate.split('.property.') - if path_parts[0] != item_name: - continue - - if len(path_parts) == 1 and client_addr != source: - self.logger.debug(f"Send update to Client {self.build_log_info(client_addr)} for item {path_parts[0]}") - items.append([path_parts[0], item_value]) - continue - - if len(path_parts) == 2: - self.logger.debug(f"Send update to Client {self.build_log_info(client_addr)} for item {path_parts[0]} with property {path_parts[1]}") - prop = self.items[path_parts[0]]['item'].property - prop_attr = getattr(prop,path_parts[1]) - items.append([candidate, prop_attr]) - continue - - if client_addr == source: - self.logger.warning(f"update_item: client_addr == source - {self.build_log_info(client_addr)}") - continue - - self.logger.warning(f"Could not send update to Client {self.build_log_info(client_addr)}: something is wrong with item path {item_name}, value={item_value}, source={source}") - except: - pass - - if len(items): # only send an update if item/value pairs found to be send - data = {'cmd': 'item', 'items': items} - msg = json.dumps(data, default=self.json_serial) - try: - self.logger.dbgmed(f"visu >MONIT: '{msg}' - to {self.build_log_info(self.client_address(websocket))}") - await websocket.send(msg) - except Exception as e: - if str(e).startswith(('code = 1001', 'code = 1005', 'code = 1006')): - self.logger.info(f"update_item: Error sending {data} - to {self.build_log_info(self.client_address(websocket))} - Error in 'await websocket.send(data)': {e}") - else: - self.logger.notice(f"update_item: Error sending {data} - to {self.build_log_info(self.client_address(websocket))} - Error in 'await websocket.send(data)': {e}") - - return - - async def update_log(self, log_entry): - """ - send JSON data with update to log - """ - remove = [] - logs_list = list(self.sv_monitor_logs.keys()) - for client_addr in logs_list: - if (client_addr in self.sv_clients) and not (client_addr in remove): - websocket = self.sv_clients[client_addr]['websocket'] - - log_entry['cmd'] = 'log' - msg = json.dumps(log_entry, default=self.json_serial) - try: - #self.logger.notice(">LogUp {}: {}".format(self.client_address(websocket), msg)) - await websocket.send(msg) - except Exception as e: - if not str(e).startswith(('code = 1005', 'code = 1006')): - self.logger.exception(f"update_log - Error in 'await websocket.send(data)': {e}") - else: - self.logger.info(f"update_log - Error in 'await websocket.send(data)': {e}") - else: - self.logger.info(f"update_log: Client {self.build_log_info(client_addr)} is not active any more") - remove.append(client_addr) - - # Remove series for clients that are not connected any more - for client_addr in remove: - del (self.sv_monitor_logs[client_addr]) - - return - - async def request_logic(self, data, client_addr): - """ - Request logic (trigger, enable, disable) - """ - if 'name' not in data: - return - name = data['name'] - mylogic = self.logics.return_logic(name) - if mylogic is not None: - linfo = self.logics.get_logic_info(name) - if linfo['visu_access']: - if 'val' in data: - value = data['val'] - self.logger.info(f"Client {self.build_log_info(client_addr)} triggerd logic {name} with '{value}'") - mylogic.trigger(by="'some_visu'", value=value, source=client_addr) - if 'enabled' in data: - if data['enabled']: - self.logger.info(f"Client {self.build_log_info(client_addr)} enabled logic {name}") - self.logics.enable_logic(name) - # non-persistant enable - # self.visu_logics[name].enable() - else: - self.logger.info(f"Client {self.build_log_info(client_addr)} disabled logic {name}") - self.logics.disable_logic(name) - # non-persistant disable - # self.visu_logics[name].disable() - else: - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested logic without visu-access: {name}") - else: - self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid logic: {name}") - return - - async def request_list_items(self, path, client_addr): - """ - Build the requested list of logics - """ - self.logger.info(f"Client {self.build_log_info(client_addr)} requested a list of defined items.") - myitems = [] - for i in self._sh.return_items(): - include = False - # if i.get('visu_acl', '').lower() != 'no': - if (path == '') and ('.' not in i._path): - include = True - else: - if i._path.startswith(path + '.'): - p = i._path[len(path + '.'):] - if '.' not in p: - include = True - if include: - myitem = collections.OrderedDict() - myitem['path'] = i._path - myitem['name'] = i._name - myitem['type'] = i.type() - myitems.append(myitem) - - response = collections.OrderedDict([('cmd', 'list_items'), ('items', myitems)]) - self.logger.info(f"Requested a list of defined items: {response}") - return response - - async def request_list_logics(self, enabled, client_addr): - """ - Build the requested list of logics - """ - self.logger.info(f"Client {self.build_log_info(client_addr)} requested a list of defined logics.") - logiclist = [] - for l in self.logics.return_loaded_logics(): - linfo = self.logics.get_logic_info(l) - if linfo['visu_access']: - if linfo['userlogic']: - logic_def = collections.OrderedDict() - logic_def['name'] = l - logic_def['desc'] = linfo['description'] - logic_def['enabled'] = 1 - if not linfo['enabled']: - logic_def['enabled'] = 0 - if (not enabled) or (logic_def['enabled'] == 1): - logiclist.append(logic_def) - - response = collections.OrderedDict([('cmd', 'list_logics'), ('logics', logiclist)]) - self.logger.info(f"Requested a list of defined logics: {response}") - return response - - # =============================================================================== - # Thread based (sync) methods of smartVISU support - - def update_visuitem(self, item, caller=None, source=None, dest=None): - """ - This method gets called when an item value changes - - it is thread based and is called from other threads than the websocket module uses - - :param item: item object that has been changed - :param caller: Caller that changed the item - :param source: Source that made the caller change the item - :param dest: Destination for the change (usually None) - :return: - """ - item_data = (item.id(), item(), caller, source) - if self.janus_queue: - # if queue has been created from the async side - self.janus_queue.sync_q.put(['item', item_data]) - # self.logger.warning("update_visuitem: item={}, value={}, caller={}, source={}".format(item_data[0], item_data[1], item_data[2], item_data[3])) - - return - - def update_visulog(self, event, data): - """ - This method gets called when an item value changes - - it is thread based and is called from other threads than the websocket module uses - - :param event: Type of monitored event (only 'log' is handled) - :param data: data of log entry - :return: - """ - if event != 'log': - self.logger.warning(f"update_visulog: Unknown event {event} received.") - return - - log_data = data.copy() # don't filter the orignal data dict - - if not log_data['log'][0]['message'].startswith('>LogUp'): - log_data['cmd'] = 'log' - if self.janus_queue: - # if queue has been created from the async side - self.janus_queue.sync_q.put(['log', log_data]) - - return - - - def set_visu_url(self, url, clientip=''): - """ - Tell the websocket client (visu) to load a specific url - """ - for client_addr in self.sv_clients: - ip, _, port = client_addr.partition(':') - - if (clientip == '') or (clientip == ip): - command = json.dumps({'cmd': 'url', 'url': url}) - self.janus_queue.sync_q.put(['command', command, client_addr]) - - return True - - - def get_visu_client_info(self): - """ - Get client info for web interface of smartvisu plugin - :return: - """ - client_list = [] - - for client_addr in self.sv_clients: - ip, _, port = client_addr.partition(':') - - infos = {} - infos['ip'] = ip - infos['port'] = port - websocket = self.sv_clients[client_addr]['websocket'] - infos['protocol'] = 'wss' if websocket.secure else 'ws' - infos['sw'] = self.sv_clients[client_addr].get('sw', '') - infos['swversion'] = self.sv_clients[client_addr].get('ver','') - infos['hostname'] = self.sv_clients[client_addr].get('hostname', '') - infos['browser'] = self.sv_clients[client_addr].get('browser', '') - infos['browserversion'] = self.sv_clients[client_addr].get('bver', '') - - client_list.append(infos) - return client_list + def get_payload_users(self, protocol_path): + # get USERS, that use this protocol + payload_USERS = set() + for user in self.USERS: + if user.path == protocol_path: + payload_USERS.add(user) + return payload_USERS diff --git a/modules/websocket/module.yaml b/modules/websocket/module.yaml index cfe328acce..99699e12d5 100644 --- a/modules/websocket/module.yaml +++ b/modules/websocket/module.yaml @@ -2,10 +2,10 @@ module: # Global plugin attributes classname: Websocket - version: 1.0.5 + version: 1.0.9 sh_minversion: 1.9.1.2 # sh_maxversion: # maximum shNG version to use this module (leave empty if latest) -# py_minversion: 3.6 # minimum Python version to use for this module + py_minversion: 3.7 # minimum Python version to use for this module # py_maxversion: # maximum Python version to use for this module (leave empty if latest) description: de: 'Modul implementiert die Websocket Kommunikation für SmartHomeNG' diff --git a/modules/websocket/smartvisu.py b/modules/websocket/smartvisu.py new file mode 100644 index 0000000000..c95d13a1ec --- /dev/null +++ b/modules/websocket/smartvisu.py @@ -0,0 +1,1041 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2020- Martin Sinn m.sinn@gmx.de +######################################################################### +# This file is part of SmartHomeNG. +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +######################################################################### + +from functools import partial + +import asyncio +import janus +import threading +import decimal + +import sys +import socket +import logging +from datetime import date, datetime + +import json +import collections + +from lib.item import Items +from lib.logic import Logics + +from lib.shtime import Shtime + +""" +=============================================================================== += += The following method(s) implement the webmethods protocol for smartVISU += += The protocol implements the version 4 of the protocol as it has been += implemented by the visu_websocket plugin += +""" + +class Protocol(): + + version = '1.0.0' + + protocol_id = 'sv' + protocol_name = 'smartvisu' + protocol_path = '/' + protocol_enabled = False + + + def __init__(self, ws_server, logger_name): + + self.logger = logging.getLogger(logger_name) + + # Get API handles + self.shtime = Shtime.get_instance() + self.items = Items.get_instance() + self.logics = Logics.get_instance() + + self._sh = ws_server._sh + + self.client_address = ws_server.client_address + #self.get_users = partial(ws_server.get_payload_users, self.protocol_path) + + return + + + def start_global_tasks(self, loop): + + self.loop = loop + #self.client_address = ws_server.client_address + + python_version = str(sys.version_info[0]) + '.' + str(sys.version_info[1]) + if python_version == '3.7': + self.loop.create_task(self.update_visu()) + self.loop.create_task(self.update_all_series()) + else: + self.loop.create_task(self.update_visu(), name='update_visu') + self.loop.create_task(self.update_all_series(), name='update_all_series') + + self.logger.dbghigh(f"start_global_tasks: create_task(s) for update_visu() and update_all_series()") + return + + + async def handle_protocol(self, websocket): + + await self.smartVISU_protocol_v4(websocket) + return + + + async def cleanup_connection(self, websocket): + + client_addr = self.client_address(websocket) + self.sv_cancel_all_abos(client_addr) + return + +# -------------- + + sv_acl = 'deny' + sv_querydef = False + sv_ser_upd_cycle = 0 + + sv_monitor_items = {} + sv_monitor_logs = {} + sv_clients = {} + sv_update_series = {} + clients = [] + proto = 4.1 + _series_lock = threading.Lock() + + janus_queue = None # var that holds the queue betweed threaded and async + + async def get_shng_class_instances(self): + """ + Ensure that the instance vars for items and logics are initialized + """ + while self.items is None: + self.items = Items.get_instance() + if self.items is None: + await asyncio.sleep(1) + while self.logics is None: + self.logics = Logics.get_instance() + if self.logics is None: + await asyncio.sleep(1) + return + + + def json_serial(self, obj): + """JSON serializer for objects not serializable by default json code""" + + if isinstance(obj, decimal.Decimal): + return float(obj) + if isinstance(obj, (datetime, date)): + return obj.isoformat() + + raise TypeError("Type %s not serializable" % type(obj)) + + +# --------------------------------- + + + def set_smartvisu_support(self, protocol_enabled=False, default_acl='ro', query_definitions=False, series_updatecycle=0): + """ + Set state of smartvisu support + + :param protocol_enabled: enable or disable the payload protocol for smartVISU + :param query_definitions: enable or disable the query of item definitions over websocket protocol + :param series_updatecycle: update cycle for smartVISU series requests (if 0, timing from database plugin is used) + """ + self.sv_acl = default_acl + self.sv_querydef = query_definitions + self.sv_ser_upd_cycle = int(series_updatecycle) + self.protocol_enabled = protocol_enabled + if self.protocol_enabled: + self.logger.info(f"Payload protocol '{self.protocol_name}' enabled") + self.logger.info(f"smartvisu support: default_acl={default_acl}, query_definitions={query_definitions}, series_updatecycle={series_updatecycle}") + # self.sv_config = {'enabled': self.sv_protocol_enabled, 'acl': self.sv_acl, 'query_def': self.sv_querydef, 'upd_cycle': self.sv_ser_upd_cycle} + # self.logger.warning(f"sv_config {self.sv_config}") + + # self.stop() + # self.start() + return + + # --------------------------------- + + async def smartVISU_protocol_v4(self, websocket): + + # items = [] + + self.logs = self._sh.logs.return_logs() + + # Prevent the event listener for event types "log" from being added multiple times: + known_log_listeners = self._sh.return_event_listeners(event='log') + if self.update_visulog not in known_log_listeners: + self._sh.add_event_listener(['log'], self.update_visulog) + else: + self.logger.debug(f"self.update_visulog function already subscribed as event listener") + + client_addr = self.client_address(websocket) + client_ip = websocket.remote_address[0] + self.sv_clients[client_addr] = {} + self.sv_clients[client_addr]['websocket'] = websocket + try: + self.sv_clients[client_addr]['hostname'] = socket.gethostbyaddr(client_ip)[0] + except: + pass + self.sv_clients[client_addr]['sw'] = "" + self.logger.info(f"Client {self.build_log_info(client_addr)} started") + self.sv_clients[client_addr]['sw'] = "'some_visu'" + # client_addr = websocket.remote_address[0] + ':' + str(websocket.remote_address[1]) + await self.get_shng_class_instances() + + #if not self.janus_queue: + # self.janus_queue = janus.Queue() + + try: + async for message in websocket: + data = json.loads(message) + command = data.get("cmd", '') + protocol = 'wss' if websocket.secure else 'ws ' + # self.logger.warning("{} No reply from prepare_series() (for request {data})") + else: + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested a series for an unknown item: {path}") + + elif command == 'series_cancel': + answer = await self.cancel_series(data, client_addr) + + elif command == 'log': + answer = {} + name = data['name'] + num = 10 + if 'max' in data: + num = int(data['max']) + if name in self.logs: + answer = {'cmd': 'log', 'name': name, 'log': self.logs[name].export(num), 'init': 'y'} + if client_addr not in self.sv_monitor_logs: + self.sv_monitor_logs[client_addr] = [] + if name not in self.sv_monitor_logs[client_addr]: + self.sv_monitor_logs[client_addr].append(name) + else: + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid log: {name}") + + elif command == 'log_cancel': + answer = await self.cancel_log(data, client_addr) + + elif command == 'ping': + answer = {'cmd': 'pong'} + + elif command == 'proto': # protocol version + proto = data['ver'] + if int(proto) > int(self.proto): + self.logger.warning(f"WebSocket: protocol mismatch. SmartHomeNG protocol version={self.proto}, visu protocol version={proto}") + elif int(proto) < int(self.proto): + self.logger.warning(f"WebSocket: protocol mismatch. Update your client: {self.build_log_info(client_addr)}") + self.sv_clients[client_addr]['proto'] = data.get('ver', '') + answer = {'cmd': 'proto', 'ver': self.proto, 'server': 'module.websocket', 'time': self.shtime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")} + + elif command == 'identity': # identify client + self.sv_clients[client_addr]['sw'] = data.get('sw', '') + self.sv_clients[client_addr]['ver'] = data.get('ver', '') + if data.get('hostname', '') != '': + self.sv_clients[client_addr]['hostname'] = data.get('hostname', '') + self.sv_clients[client_addr]['browser'] = data.get('browser', '') + self.sv_clients[client_addr]['bver'] = data.get('bver', '') + self.logger.info(f"Client {self.build_client_info(client_addr)} identified as {self.build_sw_info(client_addr)}") + answer = {} + + elif command == 'list_items': + answer = {} + if self.sv_querydef: + path = data.get('path', '') + answer = await self.request_list_items(path, client_addr) + self.logger.warning(f"{protocol} REPLY: '{answer}' - to {self.build_log_info(websocket.remote_address)}") + #except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: + except Exception as e: + self.logger.warning(f"Exception in 'await websocket.send(reply)': {e} - reply = {reply} to {self.build_log_info(websocket.remote_address)}") + + except Exception as e: + logmsg = f"smartVISU_protocol_v4 error: Client {self.build_log_info(client_addr)} - {e}" + if str(e).startswith(('no close frame received or sent', 'received 1005', 'code = 1005')): + self.logger.info(logmsg) + elif str(e).startswith(('code = 1006')) or str(e).endswith('keepalive ping timeout; no close frame received'): + self.logger.warning(logmsg) + else: + self.logger.error(logmsg) + + self.sv_cancel_all_abos(client_addr) + + self.logger.info(f"Client {self.build_log_info(client_addr)} stopped") + return # smartVISU_protocol_v4 + + def sv_cancel_all_abos(self, client_addr): + """ + Remove all abos for a smartVISU client session + + :param client_addr: + :return: + """ + self.logger.debug(f"sv_cancel_all_abos - Client-Addr {client_addr} : ") + self.logger.debug(f"- sv_Series-Dict : {self.sv_update_series}") + self.logger.debug(f"- sv_clients-Dict : {self.sv_clients}") + self.logger.debug(f"- sv_monitor_logs-Dict : {self.sv_monitor_logs}") + self.logger.debug(f"- sv_monitor_items-Dict : {self.sv_monitor_items}") + + # Remove client from series updates + if (client_addr in self.sv_update_series): + del (self.sv_update_series[client_addr]) + self.logger.info(f"sv_cancel_all_abos: Series updates for {client_addr} were stoped") + + # Remove client from log updates + if (client_addr in self.sv_monitor_logs): + del (self.sv_monitor_logs[client_addr]) + self.logger.info(f"sv_cancel_all_abos: Log updates for {client_addr} were stoped") + + # Remove client from item monitoring dict + if (client_addr in self.sv_monitor_items): + del(self.sv_monitor_items[client_addr]) + self.logger.info(f"sv_cancel_all_abos: Item monitoring for {client_addr} was removed") + + # Remove client dict of active clients + if (client_addr in self.sv_clients): + del(self.sv_clients[client_addr]) + self.logger.info(f"sv_cancel_all_abos: Client {client_addr} was removed") + + return + + + async def prepare_monitor(self, data, client_addr): + """ + Prepare the return of item monitoring data + + :param data: data of the visu's request + :param client_addr: address of the client (visu) + + :return: answer to the visu + """ + answer = {} + items = [] + newmonitor_items = [] + for path in list(data['items']): + path_parts = 0 if path is None else path.split('.property.') + if len(path_parts) == 1: + self.logger.debug(f"Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]}") + try: + item = self.items.return_item(path) + if item is not None: + item_acl = item.conf.get('acl', None) + if item_acl is None: + item_acl = self.sv_acl + if item_acl != 'deny': + items.append([path, item()]) + if self.update_visuitem not in item.get_method_triggers(): + item.add_method_trigger(self.update_visuitem) + else: + self.logger.error(f"prepare_monitor: No item '{path}' found (requested by client {self.build_log_info(client_addr)}") + except KeyError as e: + self.logger.warning(f"KeyError: Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]} which can not be found") + else: + newmonitor_items.append(path) + elif len(path_parts) == 2: + self.logger.debug(f"Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[1]} with property {path_parts[0]}") + try: + prop = self.items.return_item(path_parts[0]).property + prop_attr = getattr(prop, path_parts[1]) + items.append([path, prop_attr]) + newmonitor_items.append(path) + except KeyError as e: + self.logger.warning(f"Property KeyError: Client {self.build_log_info(client_addr)} requested to monitor item {path_parts[0]} with property {path_parts[1]}") + except AttributeError as e: + self.logger.warning(f"Property AttributeError: Client {self.build_log_info(client_addr)} requested to monitor property {path_parts[1]} of item {path_parts[0]}") + + else: + self.logger.warning("Client {self.build_log_info(client_addr)} requested invalid item: {path}") + self.logger.debug(f"json_parse: send to {self.build_log_info(client_addr)}: {({'cmd': 'item', 'items': items})}") + answer = {'cmd': 'item', 'items': items} + self.sv_monitor_items[client_addr] = newmonitor_items + self.logger.info(f"Client {self.build_log_info(client_addr)} new monitored items are {newmonitor_items}") + return answer + + + def build_client_info(self, client_addr): + """ + Build string with client host info for info/error logging + :param client_addr: + :return: info string + """ + if isinstance(client_addr, tuple): + client_addr = client_addr[0] + ':' + str(client_addr[1]) + + if self.sv_clients.get(client_addr): + if self.sv_clients[client_addr].get('hostname', '') == '': + return f"{client_addr}" + else: + return f"{self.sv_clients[client_addr].get('hostname', '')} ({client_addr})" + else: + return f"{client_addr}" + + def build_sw_info(self, client_addr): + """ + Build string with client host info for info/error logging + :param client_addr: + :return: info string + """ + if self.sv_clients.get(client_addr): + sw = f"{self.sv_clients[client_addr].get('sw', '')} {self.sv_clients[client_addr].get('ver', '')}".strip() + browser = f"{self.sv_clients[client_addr].get('browser', '')} {self.sv_clients[client_addr].get('bver', '')}".strip() + + if browser == '': + return sw + else: + return f"{sw}, {browser}" + else: + return "" + + def build_log_info(self, client_addr): + """ + Build string with client info (name and software) for info/error logging + + :param client_addr: + :return: info string + """ + if isinstance(client_addr, tuple): + client_addr = client_addr[0] + ':' + str(client_addr[1]) + + sw = self.build_sw_info(client_addr) + if sw != '': + sw = ', ' + sw + + return f"{self.build_client_info(client_addr)}{sw}" + + + async def prepare_series(self, data, client_addr): + """ + Prepare the return of series data + + :param data: data of the visu's request + :param client_addr: address of the client (visu) + + :return: answer to the visu + """ + answer = {} + path = data['item'] + series = data['series'] + start = data['start'] + if 'end' in data: + end = data['end'] + else: + end = 'now' + if 'count' in data: + count = data['count'] + else: + count = 100 + + item = self.items.return_item(path) + if item is not None: + if hasattr(item, 'series'): + try: + # reply = item.series(series, start, end, count) + reply = await self.loop.run_in_executor(None, item.series, series, start, end, count) + except Exception as e: + self.logger.error(f"Problem fetching series for {path}: {e} - Wrong sqlite/database plugin?") + else: + if 'update' in reply: + await self.loop.run_in_executor(None, self.set_periodic_series_updates, reply, client_addr) + # with self._series_lock: + # self.sv_update_series[reply['sid']] = {'update': reply['update'], 'params': reply['params']} + del (reply['update']) + del (reply['params']) + if reply['series'] is not None: + answer = reply + else: + self.logger.info(f"WebSocket: no entries for series {path} {series}") + else: + if path.startswith('env.'): + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid series: {path}. Probably, the database plugin is not configured") + else: + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid series: {path}.") + return answer + + def set_periodic_series_updates(self, reply, client_addr): + """ + -> blocking method - called via run_in_executor() + """ + with self._series_lock: + if self.sv_update_series.get(client_addr, None) is None: + self.sv_update_series[client_addr] = {} + self.sv_update_series[client_addr][reply['sid']] = {'update': reply['update'], 'params': reply['params']} + return + + + async def update_all_series(self): + """ + Async task to periodically update the series data for all active visus + + This task ist started once, before the first client connection is started + """ + # wait until SmartHomeNG is completly initialized + while self._sh.shng_status['code'] != 20: + await asyncio.sleep(1) + self.logger.info("Task update_all_series() started") + + keep_running = True + while keep_running: + remove = [] + series_list = list(self.sv_update_series.keys()) + if series_list != []: + txt = '' + if self.sv_ser_upd_cycle > 0: + txt = " - Fixed update-cycle time" + #self.logger.info("update_all_series: series_list={}{}".format(series_list, txt)) + for client_addr in series_list: + if (client_addr in self.sv_clients) and not (client_addr in remove): + self.logger.debug(f"update_all_series: Updating client {self.build_log_info(client_addr)}...") + websocket = self.sv_clients[client_addr]['websocket'] + replys = await self.loop.run_in_executor(None, self.update_series, client_addr) + for reply in replys: + if (client_addr in self.sv_clients) and not (client_addr in remove): + self.logger.dbgmed(f"update_all_series: reply {reply} --> Replys for client {self.build_log_info(client_addr)}: {replys}") + try: + await websocket.send(json.dumps(reply, default=self.json_serial)) + self.logger.debug(f">SerUp {reply}: {self.build_log_info(client_addr)}") + # except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: + except Exception as e: + self.logger.info(f"update_all_series: Exception in 'await websocket.send(reply)': {e}") + remove.append(client_addr) + else: + self.logger.info(f"update_all_series: Client {self.build_log_info(client_addr)} is not active any more #1") + pass + else: + self.logger.info(f"update_all_series: Client {self.build_log_info(client_addr)} is not active any more #2") + remove.append(client_addr) + + # Remove series for clients that are not connected any more + for client_addr in remove: + self.sv_cancel_all_abos(client_addr) + + await self.sleep(10) + + if self.sv_ser_upd_cycle > 0: + # wait for sv_ser_upd_cycle seconds before running update loop and update all series + #await asyncio.sleep(self.sv_ser_upd_cycle) + await self.sleep(self.sv_ser_upd_cycle) + else: + # wait for 10 seconds before running update loop again (loop gets update cycle from database plugin) + await self.sleep(10) + + if self._sh.shng_status['code'] != 20: + # if SmartHomeNG leaves running mode + keep_running = False + self.logger.info("update_all_series: Terminating loop, because SmartHomeNG left running mode") + + async def sleep(self, seconds): + """ + sleep method with abort, if smarthomeNG leaves running mode + :param seconds: + """ + for i in range(seconds): + if self._sh.shng_status['code'] == 20: + await asyncio.sleep(1) + + + def update_series(self, client_addr): + """ + -> blocking method - called via run_in_executor() + """ + # websocket = self.sv_clients[client_addr]['websocket'] + now = self.shtime.now() + with self._series_lock: + remove = [] + series_replys = [] + + series_entry = self.sv_update_series.get(client_addr, None) + if series_entry is not None: + for sid, series in self.sv_update_series[client_addr].items(): + if (series['update'] < now) or self.sv_ser_upd_cycle > 0: + # self.logger.warning("update_series: {} - Processing sid={}, series={}".format(client_addr, sid, series)) + item = self.items.return_item(series['params']['item']) + try: + reply = item.series(**series['params']) + except Exception as e: + self.logger.exception(f"Problem updating series for {series['params']}: {e}") + remove.append(sid) + continue + self.sv_update_series[client_addr][reply['sid']] = {'update': reply['update'], 'params': reply['params']} + del (reply['update']) + del (reply['params']) + if reply['series'] is not None: + series_replys.append(reply) + + for sid in remove: + del (self.sv_update_series[client_addr][sid]) + + return series_replys + + async def cancel_series(self, data, client_addr): + """ + Cancel the update of series data + + :param data: data of the visu's request + :param client_addr: address of the client (visu) + + :return: answer to the visu + """ + answer = {} + path = data['item'] + series = data['series'] + + if 'start' in data: + start = data['start'] + else: + start = '72h' + if 'end' in data: + end = data['end'] + else: + end = 'now' + if 'count' in data: + count = data['count'] + else: + count = 100 + + self.logger.info(f"Series cancelation: path={path}, series={series}, start={start}, end={end}, count={count}") + item = self.items.return_item(path) + try: + # reply = item.series(series, start, end, count) + reply = await self.loop.run_in_executor(None, item.series, series, start, end, count) + self.logger.dbghigh(f"cancel_series: reply={reply}") + self.logger.dbghigh(f"cancel_series: self.sv_update_series={self.sv_update_series}") + except Exception as e: + self.logger.error(f"cancel_series: Problem fetching series for {path}: {e} - Wrong sqlite plugin?") + else: + answer = await self.loop.run_in_executor(None, self.cancel_periodic_series_updates, reply, path, client_addr) + return answer + + async def cancel_log(self, data, client_addr): + """ + Cancel the update of log data for a specified log + + :param data: data of the visu's request + :param client_addr: address of the client (visu) + + :return: answer to the visu + """ + answer = {} + path = data['name'] + if 'max' in data: + max = data['max'] + else: + max = 100 + + self.logger.info(f"Logs cancelation: path={path}, max={max}") + to_remove = "" + logdict = self.sv_monitor_logs.get(client_addr, {}) + to_remove = None + for entry in logdict: + if entry == path: + to_remove = entry + + if to_remove is None: + answer = {"cmd": "log_cancel", "error": f"Log updates for {path} were not subscribed"} + else: + try: + # Delete the log-Abos here + self.sv_monitor_logs[client_addr].remove(to_remove) + reply = f"path={path}, max={max}" + self.logger.info(f"cancel_log: reply=cancel log for :{reply}") + except Exception as e: + self.logger.error(f"cancel_log: Problem to cancel log for {path}: {e}") + answer = {"cmd": "log_cancel", "error": f"Problem to cancel log for {path}: {e}"} + else: + if len(self.sv_monitor_logs[client_addr]) == 0: + try: + del self.sv_monitor_logs[client_addr] + except Exception as e: + self.logger.error(f"cancel_log: Quere for {client_addr} is empty problem to remove client from dict : {e}") + answer = {"cmd": "log_cancel", "result": f"Log updates for {path} canceled"} + return answer + + + def cancel_periodic_series_updates(self, reply, path, client_addr): + """ + -> blocking method - called via run_in_executor() + """ + with self._series_lock: + try: + del (self.sv_update_series[client_addr][reply['sid']]) + if self.sv_update_series[client_addr] == {}: + del (self.sv_update_series[client_addr]) + self.logger.info(f"Series cancelation: Series updates for path {path} canceled") + answer = {'cmd': 'series_cancel', 'result': "Series updates for path {} canceled".format(path)} + except: + self.logger.warning(f"Series cancelation: No series for path {path} found in list") + answer = {'cmd': 'series_cancel', 'error': "No series for path {} found in list".format(path)} + return answer + + async def update_visu(self): + """ + Async task to update all active visus, if items have changed or an url command has been issued + """ + # wait until SmartHomeNG is completly initialized + while self._sh.shng_status['code'] != 20: + await asyncio.sleep(1) + self.logger.info("Task update_visu() started") + + if not self.janus_queue: + self.janus_queue = janus.Queue() + self.logger.dbghigh("janus queue initialized") + + while True: + if self.janus_queue: + queue_entry = await self.janus_queue.async_q.get() + if queue_entry[0] == 'item': + item_data = queue_entry[1] + # item_data: set (item_name, item_value, caller, source) + try: + await self.update_item(item_data[0], item_data[1], item_data[3]) + except Exception as e: + self.logger.error(f"update_visu: Error in 'await self.update_item(...)': {e}") + elif queue_entry[0] == 'log': + log_entry = queue_entry[1] + # log_entry: dict {'name', 'log'} + # log is a list and contains dicts: {'time', 'thread', 'level', 'message'} + #self.logger.info(f"update_visu: queue_entry = {queue_entry}") + try: + await self.update_log(log_entry) + except Exception as e: + self.logger.error(f"update_visu: Error in 'await self.update_log(...)': {e}") + elif queue_entry[0] == 'command': + # send command to visu (e.g. url command) + command = queue_entry[1] + client_addr = queue_entry[2] + websocket = self.sv_clients[client_addr]['websocket'] + try: + await websocket.send(command) + self.logger.warning(f"visu >command: '{command}' - to {client_addr}") + # except (asyncio.IncompleteReadError, asyncio.connection_closed) as e: + except Exception as e: + self.logger.error(f"Exception in 'await websocket.send(url-command)': {e}") + else: + self.logger.error(f"update_visu: Unknown queueentry type '{queue_entry[0]}'") + + async def update_item(self, item_name, item_value, source): + """ + send JSON data with new value of an item + """ + items = [] + # self.logger.warning("update_item: self.monitor['item']") + items_list = list(self.sv_monitor_items.keys()) + for client_addr in items_list: + websocket = self.sv_clients[client_addr]['websocket'] + for candidate in self.sv_monitor_items[client_addr]: + + try: + # self.logger.debug("Send update to Client {0} for candidate {1} and item_name {2}?".format(client_addr, candidate, item_name)) + path_parts = candidate.split('.property.') + if path_parts[0] != item_name: + continue + + if len(path_parts) == 1 and client_addr != source: + self.logger.debug(f"Send update to Client {self.build_log_info(client_addr)} for item {path_parts[0]}") + items.append([path_parts[0], item_value]) + continue + + if len(path_parts) == 2: + self.logger.debug(f"Send update to Client {self.build_log_info(client_addr)} for item {path_parts[0]} with property {path_parts[1]}") + prop = self.items[path_parts[0]]['item'].property + prop_attr = getattr(prop,path_parts[1]) + items.append([candidate, prop_attr]) + continue + + if client_addr == source: + self.logger.warning(f"update_item: client_addr == source - {self.build_log_info(client_addr)}") + continue + + self.logger.warning(f"Could not send update to Client {self.build_log_info(client_addr)}: something is wrong with item path {item_name}, value={item_value}, source={source}") + except: + pass + + if len(items): # only send an update if item/value pairs found to be send + data = {'cmd': 'item', 'items': items} + msg = json.dumps(data, default=self.json_serial) + try: + self.logger.dbgmed(f"visu >MONIT: '{msg}' - to {self.build_log_info(self.client_address(websocket))}") + await websocket.send(msg) + except Exception as e: + if str(e).startswith(('code = 1001', 'code = 1005', 'code = 1006')): + self.logger.info(f"update_item: Error sending {data} - to {self.build_log_info(self.client_address(websocket))} - Error in 'await websocket.send(data)': {e}") + else: + self.logger.notice(f"update_item: Error sending {data} - to {self.build_log_info(self.client_address(websocket))} - Error in 'await websocket.send(data)': {e}") + + return + + async def update_log(self, log_entry): + """ + send JSON data with update to log + """ + remove = [] + logs_list = list(self.sv_monitor_logs.keys()) + for client_addr in logs_list: + if (client_addr in self.sv_clients) and not (client_addr in remove): + websocket = self.sv_clients[client_addr]['websocket'] + + log_entry['cmd'] = 'log' + msg = json.dumps(log_entry, default=self.json_serial) + try: + #self.logger.notice(">LogUp {}: {}".format(self.client_address(websocket), msg)) + await websocket.send(msg) + except Exception as e: + if not str(e).startswith(('code = 1005', 'code = 1006')): + self.logger.exception(f"update_log - Error in 'await websocket.send(data)': {e}") + else: + self.logger.info(f"update_log - Error in 'await websocket.send(data)': {e}") + else: + self.logger.info(f"update_log: Client {self.build_log_info(client_addr)} is not active any more") + remove.append(client_addr) + + # Remove series for clients that are not connected any more + for client_addr in remove: + self.sv_cancel_all_abos(client_addr) + + return + + async def request_logic(self, data, client_addr): + """ + Request logic (trigger, enable, disable) + """ + if 'name' not in data: + return + name = data['name'] + mylogic = self.logics.return_logic(name) + if mylogic is not None: + linfo = self.logics.get_logic_info(name) + if linfo['visu_access']: + if 'val' in data: + value = data['val'] + self.logger.info(f"Client {self.build_log_info(client_addr)} triggerd logic {name} with '{value}'") + mylogic.trigger(by="'some_visu'", value=value, source=client_addr) + if 'enabled' in data: + if data['enabled']: + self.logger.info(f"Client {self.build_log_info(client_addr)} enabled logic {name}") + self.logics.enable_logic(name) + # non-persistant enable + # self.visu_logics[name].enable() + else: + self.logger.info(f"Client {self.build_log_info(client_addr)} disabled logic {name}") + self.logics.disable_logic(name) + # non-persistant disable + # self.visu_logics[name].disable() + else: + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested logic without visu-access: {name}") + else: + self.logger.warning(f"Client {self.build_log_info(client_addr)} requested invalid logic: {name}") + return + + async def request_list_items(self, path, client_addr): + """ + Build the requested list of logics + """ + self.logger.info(f"Client {self.build_log_info(client_addr)} requested a list of defined items.") + myitems = [] + for i in self._sh.return_items(): + include = False + # if i.get('visu_acl', '').lower() != 'no': + if (path == '') and ('.' not in i._path): + include = True + else: + if i._path.startswith(path + '.'): + p = i._path[len(path + '.'):] + if '.' not in p: + include = True + if include: + myitem = collections.OrderedDict() + myitem['path'] = i._path + myitem['name'] = i._name + myitem['type'] = i.type() + myitems.append(myitem) + + response = collections.OrderedDict([('cmd', 'list_items'), ('items', myitems)]) + self.logger.info(f"Requested a list of defined items: {response}") + return response + + async def request_list_logics(self, enabled, client_addr): + """ + Build the requested list of logics + """ + self.logger.info(f"Client {self.build_log_info(client_addr)} requested a list of defined logics.") + logiclist = [] + for l in self.logics.return_loaded_logics(): + linfo = self.logics.get_logic_info(l) + if linfo['visu_access']: + if linfo['userlogic']: + logic_def = collections.OrderedDict() + logic_def['name'] = l + logic_def['desc'] = linfo['description'] + logic_def['enabled'] = 1 + if not linfo['enabled']: + logic_def['enabled'] = 0 + if (not enabled) or (logic_def['enabled'] == 1): + logiclist.append(logic_def) + + response = collections.OrderedDict([('cmd', 'list_logics'), ('logics', logiclist)]) + self.logger.info(f"Requested a list of defined logics: {response}") + return response + + # =============================================================================== + # Thread based (sync) methods of smartVISU support + + def update_visuitem(self, item, caller=None, source=None, dest=None): + """ + This method gets called when an item value changes + + it is thread based and is called from other threads than the websocket module uses + + :param item: item object that has been changed + :param caller: Caller that changed the item + :param source: Source that made the caller change the item + :param dest: Destination for the change (usually None) + :return: + """ + item_data = (item.id(), item(), caller, source) + if self.janus_queue: + # if queue has been created from the async side + self.janus_queue.sync_q.put(['item', item_data]) + # self.logger.warning("update_visuitem: item={}, value={}, caller={}, source={}".format(item_data[0], item_data[1], item_data[2], item_data[3])) + + return + + def update_visulog(self, event, data): + """ + This method gets called when an item value changes + + it is thread based and is called from other threads than the websocket module uses + + :param event: Type of monitored event (only 'log' is handled) + :param data: data of log entry + :return: + """ + if event != 'log': + self.logger.warning(f"update_visulog: Unknown event {event} received.") + return + + log_data = data.copy() # don't filter the orignal data dict + + if not log_data['log'][0]['message'].startswith('>LogUp'): + log_data['cmd'] = 'log' + if self.janus_queue: + # if queue has been created from the async side + self.janus_queue.sync_q.put(['log', log_data]) + + return + + + def set_visu_url(self, url, clientip=''): + """ + Tell the websocket client (visu) to load a specific url + """ + for client_addr in self.sv_clients: + ip, _, port = client_addr.partition(':') + + if (clientip == '') or (clientip == ip): + command = json.dumps({'cmd': 'url', 'url': url}) + self.janus_queue.sync_q.put(['command', command, client_addr]) + + return True + + + def get_visu_client_info(self): + """ + Get client info for web interface of smartvisu plugin + :return: + """ + client_list = [] + + for client_addr in self.sv_clients: + ip, _, port = client_addr.partition(':') + + infos = {} + infos['ip'] = ip + infos['port'] = port + websocket = self.sv_clients[client_addr]['websocket'] + infos['protocol'] = 'wss' if websocket.secure else 'ws' + infos['proto'] = self.sv_clients[client_addr].get('proto', '') + infos['sw'] = self.sv_clients[client_addr].get('sw', '') + infos['swversion'] = self.sv_clients[client_addr].get('ver','') + infos['hostname'] = self.sv_clients[client_addr].get('hostname', '') + infos['browser'] = self.sv_clients[client_addr].get('browser', '') + infos['browserversion'] = self.sv_clients[client_addr].get('bver', '') + + client_list.append(infos) + + return client_list + diff --git a/modules/websocket/sync_example.py b/modules/websocket/sync_example.py new file mode 100644 index 0000000000..f34f3c10c1 --- /dev/null +++ b/modules/websocket/sync_example.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2020- Martin Sinn m.sinn@gmx.de +######################################################################### +# This file is part of SmartHomeNG. +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +######################################################################### + +from functools import partial + +import asyncio +import logging + +import json + +""" +=============================================================================== += += The following method(s) implement the webmethods protocol for sync example += +""" + +class Protocol(): + + version = '1.0.0' + + protocol_id = 'ex' + protocol_name = 'sync_example' + protocol_path = '/sync' + protocol_enabled = True + + + def __init__(self, ws_server, logger_name): + + self.logger = logging.getLogger(logger_name) + + self.client_address = ws_server.client_address + self.get_users = partial(ws_server.get_payload_users, self.protocol_path) + + return + + + def start_global_tasks(self, loop): + + self.loop = loop + + self.logger.dbghigh("start_global_tasks: Nothing to start") + return + + + async def handle_protocol(self, websocket): + + await self.counter_sync(websocket) + return + + + async def cleanup_connection(self, websocket): + + # Nothing to clean up for this protocol + return + + +#-------------- + + STATE = {"value": 0} + + def state_event(self): + return json.dumps({"type": "state", **self.STATE}) + + def users_event(self): + sync_USERS = self.get_users() + return json.dumps({"type": "users", "count": len(sync_USERS)}) + + async def notify_state(self): + sync_USERS = self.get_users() + if sync_USERS: # asyncio.wait doesn't accept an empty list + message = self.state_event() + await asyncio.wait([user.send(message) for user in sync_USERS]) + + async def notify_users(self): + try: + sync_USERS = self.get_users() + + if sync_USERS: # asyncio.wait doesn't accept an empty list + message = self.users_event() + done, pending = await asyncio.wait([user.send(message) for user in sync_USERS]) + + for task in done: + name = task.get_name() + exception = task.exception() + if isinstance(exception, Exception): + if not str(exception).startswith('received 1000'): + self.logger.info(f"notify_users: Finished task {name} threw {exception}") + + for task in pending: + name = task.get_name() + exception = task.exception() + if isinstance(exception, Exception): + self.logger.info(f"notify_users: Pending task {name} threw {exception}") + except Exception as e: + self.logger.exception(f"Exception: {e}") + + async def counter_sync(self, websocket): + await self.notify_users() + await websocket.send(self.state_event()) + + try: + async for message in websocket: + data = json.loads(message) + if data.get("cmd", ''): + self.logger.info(f"CMD: {data}") + elif data.get("action", '') == "minus": + self.STATE["value"] -= 1 + self.logger.info(f"Decremented value to {self.STATE['value']}") + await self.notify_state() + elif data.get("action", '') == "plus": + self.STATE["value"] += 1 + self.logger.info(f"Incremented value to {self.STATE['value']}") + await self.notify_state() + else: + self.logging.error(f"Sync-protocol: unsupported event: {data}") + + await self.notify_users() + + except Exception as e: + #logmsg = f"counter_sync error: Client {self.build_log_info(client_addr)} - {e}" + logmsg = f"counter_sync error: Client {self.client_address(websocket)} - {e}" + if str(e).startswith(('no close frame received or sent', 'received 1005')): + self.logger.info(logmsg) + elif str(e).startswith(('code = 1005', 'code = 1006')) or str(e).endswith('keepalive ping timeout; no close frame received'): + self.logger.warning(logmsg) + else: + self.logger.error(logmsg) + + return + + diff --git a/tests/mock/core.py b/tests/mock/core.py index 74d4b5083c..50808365c4 100644 --- a/tests/mock/core.py +++ b/tests/mock/core.py @@ -10,6 +10,7 @@ import lib.config import lib.item +import lib.log import lib.plugin from lib.shtime import Shtime from lib.module import Modules @@ -101,6 +102,8 @@ def __init__(self): if self.shtime is None: self.shtime = Shtime.get_instance() + self.logs = lib.log.Logs(self) # initialize object for memory logs and extended log levels for plugins + self.scheduler = MockScheduler() if self.shtime is None: diff --git a/tools/backup_restore.py b/tools/backup_restore.py index c89e0ca8ca..d3baebb975 100644 --- a/tools/backup_restore.py +++ b/tools/backup_restore.py @@ -12,6 +12,12 @@ import os import argparse +# for tarfile fix, maybe not necessary as os is already imported... +from os.path import abspath, realpath, dirname, join as joinpath +# also for tarfile fix. As no logging is defined, this skips files instead of halting... +import sys + + class BackupAndRestore: def __init__(self): self.files = [] @@ -37,7 +43,6 @@ def backup(self, outfile, include=None, exclude=None): for name in self.files: tar.add(name, filter=self.change_fileinfo) tar.close() - def get_files(self,apath): if not os.path.exists(apath): @@ -57,6 +62,32 @@ def change_fileinfo(self,tarinfo): return tarinfo def restore(self, afile, outdir, selector=None): + + # added and adjusted fix for CVE-2007-4559) from https://stackoverflow.com/questions/10060069/safely-extract-zip-or-tar-using-python/10077309#10077309 + resolved = lambda x: realpath(abspath(x)) + + def badpath(path, base): + # joinpath will ignore base if path is absolute + return not resolved(joinpath(base, path)).startswith(base) + + def badlink(info, base): + # Links are interpreted relative to the directory containing the link + tip = resolved(joinpath(base, dirname(info.name))) + return badpath(info.linkname, base=tip) + + def safemembers(members): + base = resolved(".") + + for finfo in members: + if badpath(finfo.name, base): + print(f'{finfo.name} is blocked (illegal path)', file=sys.stderr) + elif finfo.issym() and badlink(finfo, base): + print(f'{finfo.name} is blocked: hard link to {finfo.linkname}', file=sys.stderr) + elif finfo.islnk() and badlink(finfo, base): + print(f'{finfo.name} is blocked: symlink to {finfo.linkname}', file=sys.stderr) + else: + yield finfo + readmode = None extractor = None afilelow = afile.lower() diff --git a/tools/check_plugin.py b/tools/check_plugin.py new file mode 100644 index 0000000000..5468f631ec --- /dev/null +++ b/tools/check_plugin.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2023- Martin Sinn m.sinn@gmx.de +######################################################################### +# This file is part of SmartHomeNG +# https://github.com/smarthomeNG/smarthome +# http://knx-user-forum.de/ +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +######################################################################### + +#TODO + +# user_doc: Prüfen, ob im Abschnitt Konfiguration ein Link auf die generierte Konfigurationsseite vorhanden ist +# user_doc: Prüfen, ob das eingebundene Icon auch existiert + +import os +import argparse + +import plugin_metadata_checker as mc + +BASE = os.path.dirname(os.path.dirname(os.path.abspath(os.path.basename(__file__)))) + +VERSION = '0.6.3' + +sum_errors = 0 +sum_warnings = 0 +sum_hints = 0 +total_errors = 0 +total_warnings = 0 +total_hints = 0 + + +def check_metadata(plg, quiet=False): + """ + Check metadata for the selected plugin + + :param plg: plugin name + :type plg: str + """ + mc.check_metadata_of_plugin(plg, quiet=quiet) + #mc.print_errorcount('-> Metadata', mc.errors, mc.warnings, mc.hints) + + global sum_errors, sum_warnings, sum_hints + sum_errors = mc.errors + sum_warnings = mc.warnings + sum_hints = mc.hints + global total_errors, total_warnings, total_hints + total_errors += mc.errors + total_warnings += mc.warnings + total_hints += mc.hints + return + + +# =============================================================================================== +# The following functions perform the checks on the documentation of the plugin(user_doc.rst) +# + +title_line = None +section_titles = {} + + +def check_documentation_title(plg, lines): + # - Prüfen der Hauptüberschrift: Form === / / === + for lineno, line in enumerate(lines): + if line.strip().lower() == plg: + break + else: + mc.disp_error(f"The main title of the documentation is wrong", + f"The title of the main section of the documentation should only contain the name of the plugin in lowercase letters (in this case '{plg}').", + "!!! No further checks of the documentation have been made !!!") + return + if line.strip() != plg: + mc.disp_warning(f"The main title of the documentation is wrong", + f"The title of the main section of the documentation should only contain the name of the plugin in lowercase letters (in this case '{plg}').") + + if len(lines[lineno + 1]) == 0: + mc.disp_error(f"The main title of the documentation was not found", + "The lines above and below the name should contain an number of '=' characters", + "!!! No further checks of the documentation have been made !!!") + return + else: + if lines[lineno+1] != '=' * len(lines[lineno]): + mc.disp_warning(f"The line below the document title ({plg}) contains the wrong number of '='", "The line should have the same length as the document title. The lines above and below the title should only consist of a number of '=' characters.") + + if lineno > 1: + if lines[lineno-1] != '=' * len(lines[lineno]): + mc.disp_warning(f"The line above the document title ({plg}) contains the wrong number of '='", "The line should have the same length as the document title. The lines above and below the title should only consist of a number of '=' characters.") + else: + mc.disp_error(f"The main title of the documentation is wrong", + "The line above the name should contain a number of '=' characters", "The line should have the same length as the title.") + + global title_line + title_line = lineno + return + + +def find_documentation_sections(lines): + + global section_titles + section_titles = {} + for lineno, line in enumerate(lines): + try: + if line != '' and lineno != title_line and lines[lineno+1] == '=' * len(lines[lineno]): + section_titles[line] = lineno + except: pass + + if section_titles == {}: + mc.disp_error("No sections (beside the document title) have been found.", "The line below the section title should have the same length as the section title. The line below the title should only consist of a number of '=' characters.") + + section_titles['###END OF FILE###'] = len(lines) + return + + +def check_documentation_logo(lines): + # - Prüfen ob der Logo-Block eingebunden ist (.. image:: webif/static/img/plugin_logo.png) + found = None + for lineno, line in enumerate(lines): + if line.strip() == '.. image:: webif/static/img/plugin_logo.jpg': + found = lineno + break + if line.strip() == '.. image:: webif/static/img/plugin_logo.png': + found = lineno + break + if line.strip() == '.. image:: webif/static/img/plugin_logo.svg': + found = lineno + break + else: + mc.disp_warning(f"There has been no plugin logo included in the documentation", + f"The logo should be included after the title with the following line: '.. image:: webif/static/img/plugin_logo.'. For an example how to include a plugin logo, take a look at the sample_plugin in the ../dev folder.", + "The logo itself should be stored in the subdirectory 'webif/static/img' of the plugin, should be named 'plugin_logo' and have the extension of '.jpg', '.png' or '.svg'") + + # - Prüfen, ob das Logo an der richtigen Stelle im Dokument steht + if found is not None: + if found < title_line or found > next(iter(section_titles.values())): + mc.disp_error(f"The plugin logo should be includes below the document title and above the first section title", "The plugin logo should be the first information following the document title and be included before the beginning of the global description text.") + else: + for lineno in range(title_line+2, found): + if lines[lineno] != '' and not lines[lineno].startswith('..'): + break + if lineno != found-1: + mc.disp_error( + f"The plugin logo should be includes below the document title and above the first section title", + "The plugin logo should be the first information following the document title and be included before the beginning of the global description text.") + + # - Prüfen ob ein Logo (im webif directory) abgelegt ist + if (not os.path.isfile('webif/static/img/plugin_logo.jpg')) and \ + (not os.path.isfile('webif/static/img/plugin_logo.png')) and \ + (not os.path.isfile('webif/static/img/plugin_logo.svg')): + mc.disp_warning(f"No plugin logo has been found in the directory 'webif/static/img'") + + +def check_documentation(plg, quiet=False): + """ + Check documentation for the selected plugin + + :param plg: plugin name + :type plg: str + """ + mc.errors = 0 + mc.warnings = 0 + mc.hints = 0 + + doc_filename = 'user_doc.rst' + if not quiet: + print() + print(f"*** Checking documentation of plugin '{plg}' ({doc_filename}):") + print() + + if not os.path.isfile(doc_filename): + mc.disp_warning(f"No documentation file '{doc_filename}'", + "You should create a documentation file for the plugin") + else: + # read documentation file + with open(doc_filename) as f: + lines = f.readlines() + # remove newlines from the end of each line + for lineno, line in enumerate(lines): + lines[lineno] = lines[lineno].rstrip() + + check_documentation_title(plg, lines) + if title_line is not None: + find_documentation_sections(lines) + check_documentation_logo(lines) + + # - Prüfen ob die grundsätzlichen Stichworte am Dateianfang definiert sind (.. index:: Plugins; , .. index:: + index1_line = None + index2_line = None + for lineno, line in enumerate(lines): + if lineno >= title_line: + break + if line.find('.. index:: Plugins; '+plg) == 0: + index1_line = lineno + if line == '.. index:: '+plg or line.find('.. index:: '+plg+' ') == 0 or line.find('.. index:: '+plg+';') == 0: + index2_line = lineno + if index1_line is None or index2_line is None: + mc.disp_warning(f"No global index entries found for the documentation", "You should at least include two lines with index statements before the title of the documentation.", f"The index statements should be '.. index:: Plugins; {plg}' and '.. index:: {plg}'") + + # - Prüfen ob ein Abschnitt für das Webinterface existiert + if webif_found: + if not 'Web Interface' in section_titles.keys(): + mc.disp_warning(f"No section 'Web Interface' with the documentation for the web interface of the plugin found.", "You should document, what the web interface of the plugin does and include pictures as an example.") + + if not quiet: + mc.print_errorcount('Documentation', mc.errors, mc.warnings, mc.hints) + + global sum_errors, sum_warnings, sum_hints + sum_errors += mc.errors + sum_warnings += mc.warnings + sum_hints += mc.hints + global total_errors, total_warnings, total_hints + total_errors += mc.errors + total_warnings += mc.warnings + total_hints += mc.hints + + return + + +# =============================================================================================== +# The following functions perform the checks on the doce of the plugin (__init__.py) +# + +webif_found = False + + +def check_code_webinterface(lines, webif_lines): + + # - is a webinterface defined? + webif_seperated = False + for line in lines: + if line.find('from .webif import WebInterface') > -1: + webif_seperated = True + break + for line in lines: + if line.find('def init_webinterface(self') > -1: + break + else: + if not webif_seperated: + mc.disp_hint("No webinterface is implemented", "You should consider to to implement a webinterface.") + return + + # - is the webinterface being initialized? + for line in lines: + if line.find('self.init_webinterface') > -1: + if line.find('#') == -1: + break + if not line.find('#') < line.find('self.init_webinterface'): + break + else: + mc.disp_warning("Webinterface is not beeing initialized") + global webif_found + webif_found = True + + # - is the code of the webinterface seperated into the webif subfolder? + if not webif_seperated: + mc.disp_hint("The code of the webinterface is not seperated into a different file", "You should consider to seperate the code the webinterface into the subfolder 'webif'. Take a look at the sample plugin in the ../dev folder.") + else: + # - was "Sample plugin" from the templaate file changed? + for line in webif_lines: + if line.lower().find('sample plugin') > -1 and line.startswith('#'): + mc.disp_hint("The description of the sample plugin in the comments of the web interface code was not replaced", "Replace the description in webif/__init__.py with a meaningful text") + break + + +def check_code(plg, quiet=False): + mc.errors = 0 + mc.warnings = 0 + mc.hints = 0 + + code_filename = '__init__.py' + webif_code_filename = os.path.join('webif', '__init__.py') + if not quiet: + print() + print(f"*** Checking python code of plugin '{plg}' (__init__.py, webif{os.sep}__init__.py):") + print() + + if not os.path.isfile(code_filename): + mc.disp_error(f"No code ('{code_filename}') found for the plugin '{plg}'", "Aborting code check") + else: + # read code file of the plugin + with open(code_filename) as f: + lines = f.readlines() + # remove newlines from the end of each line + for lineno, line in enumerate(lines): + lines[lineno] = lines[lineno].rstrip() + + # read code file of the web interface + with open(code_filename) as f: + webif_lines = f.readlines() + # remove newlines from the end of each line + for lineno, line in enumerate(webif_lines): + webif_lines[lineno] = webif_lines[lineno].rstrip() + + # TODO + # - check if get_parameter() is used in __init__ -> Warning + # - check if add_item is used in parse_item -> Hint + + # - is super().__init__() being called from __init__()? + in_init_method = False + init_found = False + for lineno, line in enumerate(webif_lines): + if line.lstrip().startswith('def'): + if line.lstrip().startswith('def __init__(self'): + in_init_method = True + init_found = True + else: + in_init_method = False + + if in_init_method: + if line.find('super().__init__()') > -1: + if line.find('#') == -1: + break + if not line.find('#') < line.find('super().__init__()'): + break + else: + if init_found: + mc.disp_error("super().__init__() is not called from __init__()") + else: + mc.disp_error("__init__() method not found") + + # - is a stop() method defined? + for lineno, line in enumerate(webif_lines): + if line.find('def stop(') > -1: + if line.find('#') == -1: + break + if not line.find('#') < line.find('def stop('): + break + else: + mc.disp_error("no stop() method found") + + # - was "Sample plugin" from the templaate file changed? + for line in lines: + if line.lower().find('sample plugin') > -1 and line.startswith('#'): + mc.disp_hint("The description of the sample plugin in the comments was not replaced", "Replace the description in __init__.py with a meaningful text") + break + + check_code_webinterface(lines, webif_lines) + + if not quiet: + mc.print_errorcount('Code', mc.errors, mc.warnings, mc.hints) + print() + + global sum_errors, sum_warnings, sum_hints + sum_errors += mc.errors + sum_warnings += mc.warnings + sum_hints += mc.hints + global total_errors, total_warnings, total_hints + total_errors += mc.errors + total_warnings += mc.warnings + total_hints += mc.hints + return + + +def check_one_plugin(plg, chk_meta, chk_code, chk_docu): + try: + os.chdir(plugindir) + except: + print(f"ERROR: No plugin with name '{plg}' found.") + print() + exit(1) + if chk_meta: + check_metadata(plg) + os.chdir(plugindir) + if chk_code: + check_code(plg) + if chk_docu: + check_documentation(plg) + + print() + mc.print_errorcount('TOTAL', sum_errors, sum_warnings, sum_hints) + print() + + +def check_all_plugins(chk_meta, chk_code, chk_docu, quiet=True): + pluginsdir = os.path.join(BASE, 'plugins') + + plugins = mc.get_local_pluginlist(pluginsdir) + print(f"{'Plugin':<16.16} {'Errors':>10} {'Warnings':>10} {'Hints':>10}") + print(f"{'-'*16:<16.16} {'-'*8:>10} {'-'*8:>10} {'-'*8:>10}") + + for plg in plugins: + mc.quiet = quiet + if chk_meta: + check_metadata(plg, quiet=quiet) + + os.chdir(os.path.join(pluginsdir, plg)) + if chk_code: + check_code(plg, quiet=quiet) + + os.chdir(os.path.join(pluginsdir, plg)) + if chk_docu: + check_documentation(plg, quiet=quiet) + + global sum_errors, sum_warnings, sum_hints + print(f"{plg:<16.16} {sum_errors:10} {sum_warnings:10} {sum_hints:10}") + + sum_errors = 0 + sum_warnings = 0 + sum_hints = 0 + + print(f"{'-'*16:<16.16} {'-'*8:<10} {'-'*8:<10} {'-'*8:<10}") + print(f"{'':<16.16} {total_errors:10} {total_warnings:10} {total_hints:10}") + print() + + +# ================================================================================== +# Main Routine of the tool +# + +if __name__ == '__main__': + print('') + print(os.path.basename(__file__) + ' v' + VERSION + ' - Check plugin for formal errors or improvement potential') + print('') + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('pluginname', nargs='?', default='', help='name of the plugin to check') # positional argument# + parser.add_argument('-all', dest='check_all', help='check all plugins', action='store_true') + group = parser.add_mutually_exclusive_group() + group.add_argument('-m', dest='check_metadata_only', help='check only the metadata of a plugin', action='store_true') + group.add_argument('-d', dest='check_documentation_only', help='check only the documentation of a plugin', action='store_true') + group.add_argument('-c', dest='check_code_only', help='check only the code of a plugin', action='store_true') + group.add_argument('-cd', dest='check_code_doc_only', help='check only the code and documentation of a plugin', action='store_true') + args = parser.parse_args() + + plg = args.pluginname + + plugindir = os.path.join(BASE, 'plugins', plg) + + chk_meta = True + chk_code = True + chk_docu = True + + if args.check_metadata_only: + chk_code = False + chk_docu = False + if args.check_code_only: + chk_meta = False + chk_docu = False + if args.check_documentation_only: + chk_meta = False + chk_code = False + if args.check_code_doc_only: + chk_meta = False + + if args.check_all: + check_all_plugins(chk_meta, chk_code, chk_docu) + elif plg == '': + parser.print_help() + print() + else: + check_one_plugin(plg, chk_meta, chk_code, chk_docu) + diff --git a/tools/measure_cpu_speed.py b/tools/measure_cpu_speed.py new file mode 100644 index 0000000000..f48684a78b --- /dev/null +++ b/tools/measure_cpu_speed.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2016- Martin Sinn m.sinn@gmx.de +######################################################################### +# This file is part of SmartHomeNG +# https://github.com/smarthomeNG/smarthome +# http://knx-user-forum.de/ +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +######################################################################### + +import timeit +import os +import sys +import platform + +BASE = os.path.dirname(os.path.dirname(os.path.abspath(os.path.basename(__file__)))) + +VERSION = '0.1.0' + + +def measure(): + + #return round(timeit.timeit('"|".join(str(i) for i in range(99999))', number=1000), 2) + return round(timeit.timeit('"|".join(str(i) for i in range(50000))', number=1000), 2) + +def read_cpuinfo(): + + try: + with open('/proc/cpuinfo') as f: + lines = f.readlines() + + for line in lines: + if line.startswith('model name'): + print('cpu '+ line) + break + except: + print("cpu model name\t: Could not determine cpu model - unable to read /proc/cpuinfo") + print() + + +# ================================================================================== +# Main Routine of the tool +# +if __name__ == '__main__': + print('') + print(os.path.basename(__file__) + ' v' + VERSION + ' - Check the cpu speed') + print('') + + version = platform.python_version() + print(f"Python version\t: {version}") + print() + + read_cpuinfo() + + sys.stdout.write(f"test duration\t: ") + sys.stdout.flush() + print(f"{measure()} seconds") + print() diff --git a/tools/plugin_metadata_checker.py b/tools/plugin_metadata_checker.py index 5fa77223c2..ae768e3d3c 100644 --- a/tools/plugin_metadata_checker.py +++ b/tools/plugin_metadata_checker.py @@ -32,11 +32,8 @@ import os import argparse -VERSION = '1.7.8' +VERSION = '1.8.2' -print('') -print(os.path.basename(__file__) + ' v' + VERSION + ' - Checks the care status of plugin metadata') -print('') start_dir = os.getcwd() import sys @@ -63,8 +60,11 @@ # Functions of the tool # -def get_local_pluginlist(): - plglist = os.listdir('.') +def get_local_pluginlist(pluginsdirectory=None): + if pluginsdirectory is None: + plglist = os.listdir('.') + else: + plglist = os.listdir(pluginsdirectory) for entry in plglist: if os.path.isfile(entry): @@ -76,9 +76,12 @@ def get_local_pluginlist(): if entry[0] in ['.' ,'_']: plglist.remove(entry) for entry in plglist: - if entry[0] in ['.' ,'_']: + if entry.lower().endswith('.md') or entry.lower().endswith('.rst'): plglist.remove(entry) - return plglist + #for entry in plglist: + # if entry[0] in ['.' ,'_']: + # plglist.remove(entry) + return sorted(plglist) def get_plugintype(plgName): @@ -405,6 +408,7 @@ def display_metadata(plg, with_description): if plg_type == 'Classic': return + # Display section 'parameters' if metadata.get('parameters', None) == None: print("ERROR: Section 'parameters' not defined in metadata") print() @@ -426,6 +430,7 @@ def display_metadata(plg, with_description): display_def_description(par, par_dict) print() + # Display section 'item_attributes' if metadata.get('item_attributes', None) == None: print("ERROR: Section 'item_attributes' not defined in metadata") print() @@ -447,6 +452,7 @@ def display_metadata(plg, with_description): display_def_description(attr, attr_dict) print() + # Display section 'item_structs' if metadata.get('item_structs', None) == None: print("ERROR: Section 'item_structs' not defined in metadata") print() @@ -463,6 +469,7 @@ def display_metadata(plg, with_description): display_struct_definition(attr, attr_dict) print() + # Display section 'plugin_functions' if metadata.get('plugin_functions', None) == None: print("ERROR: Section 'plugin_functions' not defined in metadata") print() @@ -503,7 +510,7 @@ def display_metadata(plg, with_description): hints = 0 quiet = False -def disp_error_formatted(level, msg): +def disp_error_formatted_alt(level, msg): if level != '': level += ':' print('{level:<8.8} {msg:<65.65}'.format(level=level, msg=msg)) @@ -513,6 +520,26 @@ def disp_error_formatted(level, msg): return +def disp_error_formatted(level, msg): + if level != '': + level += ':' + + words = msg.split() + while len(words) > 0: + line = '' + + while (len(words) > 0) and (len(line) + 1 + len(words[0]) <= 65): + if len(line) > 0: + line += ' ' + line += words[0] + words.pop(0) + + print(f"{level:<8.8} {line:<65.65}") + level = '' + + return + + def disp_hints_formatted(hint, hint2): if hint != '': print() @@ -520,7 +547,7 @@ def disp_hints_formatted(hint, hint2): if hint2 != '': print() disp_error_formatted('', hint2) - print() + print() def disp_error(msg, hint='', hint2=''): @@ -583,7 +610,26 @@ def test_description(section, par, par_dict): return -def check_metadata(plg, with_description, check_quiet=False, only_inc=False, list_classic=False): +def print_errorcount(checkname, errors, warnings, hints): + + disp_str = checkname + ': ' + if errors == 1: + disp_str += f"{errors} error, " + else: + disp_str += f"{errors} errors, " + if warnings == 1: + disp_str += f"{warnings} warning, " + else: + disp_str += f"{warnings} warnings, " + if hints == 1: + disp_str += f"{hints} hint" + else: + disp_str += f"{hints} hints" + + print(disp_str) + + +def check_metadata(plg, with_description, check_quiet=False, only_inc=False, list_classic=False, pluginsdirectory=None, suppress_summery=False): global errors, warnings, hints, quiet quiet = check_quiet @@ -592,12 +638,12 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis hints = 0 plg_type = get_plugintype(plg).lower() - plugins_local = get_local_pluginlist() + plugins_local = get_local_pluginlist(pluginsdirectory) metadata = readMetadata(plg, plugins_local) if metadata == None: return if not check_quiet: - print("Check metadata of {} plugin '{}'".format(plg_type, plg)) + print(f"*** Check metadata of {plg_type}-plugin '{plg}' (plugin.yaml):") print() # Checking plugin name @@ -609,7 +655,8 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis # Checking global metadata if metadata.get('plugin', None) == None: - disp_error("No global metadata defined", "Make sure to create a section 'plugin' and fill it with the necessary entries", "Take a look at https://www.smarthomeng.de/developer/development_plugin/plugin_metadata.html") + disp_error("No global metadata defined", "Make sure to create a section 'plugin' and fill it with the necessary entries", "Take a look at the plugin development documentation of SmartHomeNG") + return else: if metadata['plugin'].get('version', None) == None: disp_error('No version number given', "Add 'version:' to the plugin section") @@ -631,6 +678,18 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis elif not metadata['plugin'].get('state', None) in ['qa-passed', 'ready', 'develop', 'deprecated', '-']: disp_error('An invalid development state is given for the plugin', "Set'state:' to one of the followind valid values ['develop', 'ready', 'qa-passed', 'deprecated']", "The state'qa-passed' should only be set by the shNG core team") + doc_url = metadata['plugin'].get('documentation', '') + if (doc_url is not None) and (doc_url != ''): + if doc_url.endswith(f"plugins/{plg}/user_doc.html"): + disp_hint("The 'documentation' parameter in section 'plugin' should not contain an url to the SmartHomeNG documentation (user_doc)", + "The 'documentation' parameter is optional and only to be used to link to additional documentation outside of SmartHomeNG", "If there is no additional documentation, leave this parameter empty") + elif doc_url.endswith(f"plugins_doc/config/{plg}.html"): + disp_warning("The 'documentation' parameter in section 'plugin' should not contain an url to the SmartHomeNG documentation (configuration)", + "The 'documentation' parameter is optional and only to be used to link to additional documentation outside of SmartHomeNG", "If there is no additional documentation, leave this parameter empty") + + if metadata['plugin'].get('support', '') == '': + disp_hint("The 'support' parameter is empty.", "Is there no support thread for this plugin in the knx-user-forum?", "Enter the url to the support thread in the 'support' parameter.") + if metadata['plugin'].get('multi_instance', None) == None: disp_warning('It is not documented if wether the plugin is multi-instance capable or not', "Add 'multi_instance:' to the plugin section") else: @@ -651,7 +710,6 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis if metadata.get('parameters', None) == None: disp_error("No parameters defined in metadata", "When defining parameters, make sure that you define ALL parameters of the plugin, but dont define global parameters like 'instance'. If only a part of the parameters are defined in the metadata, the missing parameters won't be handed over to the plugin at runtime.", "If the plugin has no parameters, document this by writing 'parameters: NONE' to the metadata file.") - # Checking item attribute metadata if metadata.get('item_attributes', None) == None: disp_error("No item attributes defined in metadata", "If the plugin defines no item attributes, document this by creating an empty section. Write 'item_attributes: NONE' to the metadata file.") @@ -676,13 +734,16 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis if metadata.get('parameters', None) != None: if metadata.get('parameters', None) != 'NONE': for par in metadata.get('parameters', None): - par_dict = metadata['parameters'][par] - if not is_dict(par_dict): - disp_error("Definition of parameter '{}' is not a dict".format(par), '') + if par.lower() in ['instance', 'webif_pagelength']: + disp_warning(f"parameter '{par}' should not be defined in the plugin", f"Parameter '{par}' is a globaly defined plugin parameter. You should not explicitly define it in your plugin '{plg}'") else: - if par_dict.get('mandatory', False) != False and par_dict.get('default', None) != None: - disp_error("parameter '{}': mandatory and default cannot be used together".format(par), "If mandatory and a default value are specified togeather, mandatory has no effect, since a value for the parameter is already specified (the default value).") - test_description('parameter', par, par_dict.get('description', None)) + par_dict = metadata['parameters'][par] + if not is_dict(par_dict): + disp_error("Definition of parameter '{}' is not a dict".format(par), '') + else: + if par_dict.get('mandatory', False) != False and par_dict.get('default', None) != None: + disp_error("parameter '{}': mandatory and default cannot be used together".format(par), "If mandatory and a default value are specified togeather, mandatory has no effect, since a value for the parameter is already specified (the default value).") + test_description('parameter', par, par_dict.get('description', None)) if metadata.get('item_attributes', None) != None: if metadata.get('item_attributes', None) != 'NONE': @@ -729,16 +790,17 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis else: res = 'TO DOs' if check_quiet: - if not(only_inc) or (only_inc and (errors!=0 or warnings!=0 or hints!=0 or state == '-')): - if state == 'qa-passed': - state = 'qa-pass' - summary = "{:<8.8} {:<7.7} {:<5.5} {:<8.8} {:<7.7} {}".format(res, state, plg_type, str(errors), str(warnings), str(hints)) - print('{plugin:<14.14} {summary:<60.60}'.format(plugin=plg, summary=summary)) + if not suppress_summery: + if not(only_inc) or (only_inc and (errors!=0 or warnings!=0 or hints!=0 or state == '-')): + if state == 'qa-passed': + state = 'qa-pass' + summary = "{:<8.8} {:<7.7} {:<5.5} {:<8.8} {:<7.7} {}".format(res, state, plg_type, str(errors), str(warnings), str(hints)) + print('{plugin:<14.14} {summary:<60.60}'.format(plugin=plg, summary=summary)) else: if errors == 0 and warnings == 0 and hints == 0: - print("Metadata is complete ({} errors, {} warnings and {} hints)".format(errors, warnings, hints)) + print_errorcount('Metadata is complete', errors, warnings, hints) else: - print("{} errors, {} warnings and {} hints".format(errors, warnings, hints)) + print_errorcount('Metadata', errors, warnings, hints) print() return @@ -844,11 +906,31 @@ def check_plglist(option, list_classic=False): print() +def check_metadata_of_plugin(plg, quiet=False): + + BASE = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-2]) + + # change the working diractory to the directory from which the converter is loaded (../tools) + os.chdir(os.path.dirname(os.path.abspath(os.path.basename(__file__)))) + + plugindirectory = '../plugins' + #pluginabsdirectory = os.path.abspath(plugindirectory) + pluginabsdirectory = os.path.join(BASE, 'plugins') + + os.chdir(pluginabsdirectory) + + check_metadata(plg, False, check_quiet=quiet, suppress_summery=quiet) + return + + # ================================================================================== # Main Routine of the tool # if __name__ == '__main__': + print('') + print(os.path.basename(__file__) + ' v' + VERSION + ' - Checks the care status of plugin metadata') + print('') # change the working diractory to the directory from which the converter is loaded (../tools) os.chdir(os.path.dirname(os.path.abspath(os.path.basename(__file__)))) diff --git a/tools/setpermissions b/tools/setpermissions new file mode 100644 index 0000000000..e791d5d1f0 --- /dev/null +++ b/tools/setpermissions @@ -0,0 +1,21 @@ +#!/bin/bash +cd "$(dirname "$0")" +cd .. + +chmod +x bin/smarthome.py + +chmod +x tools/build_requirements.py +chmod +x tools/check_plugin.py +chmod +x tools/conf_to_yaml_converter.py +chmod +x tools/getshngpid +chmod +x tools/measure_cpu_speed.py +chmod +x tools/plugin_metadata_checker.py +chmod +x tools/test_running + +#echo `pwd` +#find . -type d -exec chmod g+rwsx {} + +#find . -type f -exec chmod g+r {} + +#find . -name '*.ini' -exec chmod g+rw {} + +#find . -name '*.var' -exec chmod g+rw {} + + + diff --git a/tools/test_running.sh b/tools/test_running similarity index 75% rename from tools/test_running.sh rename to tools/test_running index d021d2bb09..9a0d17311c 100644 --- a/tools/test_running.sh +++ b/tools/test_running @@ -1,2 +1,3 @@ +#!/bin/bash ps -ef | grep smarthome | grep bin